From a1d576fe8acff7943e4c42af9491c8935a60fef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Fri, 30 Jun 2023 15:14:47 +0200 Subject: [PATCH] update flecs --- code/vendors/flecs/flecs.c | 4800 +++++++++++++++++++++++++----------- code/vendors/flecs/flecs.h | 1059 +++++++- 2 files changed, 4291 insertions(+), 1568 deletions(-) diff --git a/code/vendors/flecs/flecs.c b/code/vendors/flecs/flecs.c index 47dd2bd..f82d9cf 100644 --- a/code/vendors/flecs/flecs.c +++ b/code/vendors/flecs/flecs.c @@ -573,17 +573,17 @@ typedef struct ecs_table_event_t { /** Stage-specific component data */ struct ecs_data_t { - ecs_vec_t entities; /* Entity identifiers */ - ecs_vec_t records; /* Ptrs to records in main entity index */ - ecs_vec_t *columns; /* Component columns */ + ecs_vec_t entities; /* Entity identifiers */ + ecs_vec_t records; /* Ptrs to records in main entity index */ + ecs_vec_t *columns; /* Component columns */ }; /** Cache of added/removed components for non-trivial edges between tables */ #define ECS_TABLE_DIFF_INIT { .added = {0}} typedef struct ecs_table_diff_t { - ecs_type_t added; /* Components added between tables */ - ecs_type_t removed; /* Components removed between tables */ + ecs_type_t added; /* Components added between tables */ + ecs_type_t removed; /* Components removed between tables */ } ecs_table_diff_t; /** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to @@ -603,16 +603,16 @@ typedef struct ecs_graph_edge_hdr_t { /** Single edge. */ typedef struct ecs_graph_edge_t { ecs_graph_edge_hdr_t hdr; - ecs_table_t *from; /* Edge source table */ - ecs_table_t *to; /* Edge destination table */ - ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */ - ecs_id_t id; /* Id associated with edge */ + ecs_table_t *from; /* Edge source table */ + ecs_table_t *to; /* Edge destination table */ + ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */ + ecs_id_t id; /* Id associated with edge */ } ecs_graph_edge_t; /* Edges to other tables. */ typedef struct ecs_graph_edges_t { - ecs_graph_edge_t *lo; /* Small array optimized for low edges */ - ecs_map_t hi; /* Map for hi edges (map) */ + ecs_graph_edge_t *lo; /* Small array optimized for low edges */ + ecs_map_t *hi; /* Map for hi edges (map) */ } ecs_graph_edges_t; /* Table graph node */ @@ -625,16 +625,26 @@ typedef struct ecs_graph_node_t { ecs_graph_edge_hdr_t refs; } ecs_graph_node_t; -/** Optional data for unions, bitsets and flattened relationships */ -typedef struct ecs_table_ext_t { - ecs_switch_t *sw_columns; /* Switch columns */ - ecs_bitset_t *bs_columns; /* Bitset columns */ +/** Infrequently accessed data not stored inline in ecs_table_t */ +typedef struct ecs_table__t { + uint64_t hash; /* Type hash */ + int32_t lock; /* Prevents modifications */ + int32_t refcount; /* Increased when used as storage table */ + int32_t traversable_count; /* Number of observed entities in table */ + uint16_t generation; /* Used for table cleanup */ + uint16_t record_count; /* Table record count including wildcards */ + + struct ecs_table_record_t *records; /* Array with table records */ + ecs_hashmap_t *name_index; /* Cached pointer to name index */ + + ecs_switch_t *sw_columns; /* Switch columns */ + ecs_bitset_t *bs_columns; /* Bitset columns */ int16_t sw_count; int16_t sw_offset; int16_t bs_count; int16_t bs_offset; int16_t ft_offset; -} ecs_table_ext_t; +} ecs_table__t; /** A table is the Flecs equivalent of an archetype. Tables store all entities * with a specific set of components. Tables are automatically created when an @@ -642,30 +652,23 @@ typedef struct ecs_table_ext_t { * table is created, it is automatically matched with existing queries */ struct ecs_table_t { uint64_t id; /* Table id in sparse set */ - ecs_type_t type; /* Identifies table type in type_index */ ecs_flags32_t flags; /* Flags for testing table properties */ uint16_t storage_count; /* Number of components (excluding tags) */ - uint16_t generation; /* Used for table cleanup */ - - struct ecs_table_record_t *records; /* Array with table records */ - ecs_table_t *storage_table; /* Table without tags */ - ecs_id_t *storage_ids; /* Component ids (prevent indirection) */ - int32_t *storage_map; /* Map type <-> data type - * - 0..count(T): type -> data_type - * - count(T)..count(S): data_type -> type - */ + ecs_type_t type; /* Identifies table type in type_index */ ecs_graph_node_t node; /* Graph node */ ecs_data_t data; /* Component storage */ ecs_type_info_t **type_info; /* Cached type info */ - int32_t *dirty_state; /* Keep track of changes in columns */ - ecs_table_ext_t *ext; /* Optional extended data structures */ - uint16_t record_count; /* Table record count including wildcards */ - int32_t refcount; /* Increased when used as storage table */ - int32_t lock; /* Prevents modifications */ - int32_t observed_count; /* Number of observed entities in table */ + ecs_table_t *storage_table; /* Table without tags */ + ecs_id_t *storage_ids; /* Component ids (prevent indirection) */ + int32_t *storage_map; /* Map type <-> data type + * - 0..count(T): type -> data_type + * - count(T)..count(S): data_type -> type + */ + + ecs_table__t *_; /* Infrequently accessed table metadata */ }; /** Must appear as first member in payload of table cache */ @@ -718,11 +721,10 @@ typedef struct flecs_flat_table_term_t { /* Entity filter. This filters the entities of a matched table, for example when * it has disabled components or union relationships (switch). */ typedef struct ecs_entity_filter_t { - ecs_vec_t sw_terms; /* Terms with switch (union) entity filter */ - ecs_vec_t bs_terms; /* Terms with bitset (toggle) entity filter */ - ecs_vec_t ft_terms; /* Terms with components from flattened tree */ + ecs_vec_t sw_terms; /* Terms with switch (union) entity filter */ + ecs_vec_t bs_terms; /* Terms with bitset (toggle) entity filter */ + ecs_vec_t ft_terms; /* Terms with components from flattened tree */ int32_t flat_tree_column; - bool has_filter; } ecs_entity_filter_t; typedef struct ecs_entity_filter_iter_t { @@ -738,41 +740,30 @@ typedef struct ecs_entity_filter_iter_t { int32_t target_count; } ecs_entity_filter_iter_t; -typedef struct ecs_query_table_match_t ecs_query_table_match_t; - -/** List node used to iterate tables in a query. - * The list of nodes dictates the order in which tables should be iterated by a - * query. A single node may refer to the table multiple times with different - * offset/count parameters, which enables features such as sorting. */ -struct ecs_query_table_node_t { - ecs_query_table_node_t *next, *prev; +/** Table match data. + * Each table matched by the query is represented by a ecs_query_table_match_t + * instance, which are linked together in a list. A table may match a query + * multiple times (due to wildcard queries) with different columns being matched + * by the query. */ +struct ecs_query_table_match_t { + ecs_query_table_match_t *next, *prev; ecs_table_t *table; /* The current table. */ - uint64_t group_id; /* Value used to organize tables in groups */ int32_t offset; /* Starting point in table */ int32_t count; /* Number of entities to iterate in table */ - ecs_query_table_match_t *match; /* Reference to the match */ -}; - -/** Type containing data for a table matched with a query. - * A single table can have multiple matches, if the query contains wildcards. */ -struct ecs_query_table_match_t { - ecs_query_table_node_t node; /* Embedded list node */ - - int32_t *columns; /* Mapping from query fields to table columns */ - int32_t *storage_columns; /* Mapping from query fields to storage columns */ - ecs_id_t *ids; /* Resolved (component) ids for current table */ - ecs_entity_t *sources; /* Subjects (sources) of ids */ - ecs_vec_t refs; /* Cached components for non-this terms */ - ecs_entity_filter_t entity_filter; /* Entity specific filters */ + int32_t *columns; /* Mapping from query fields to table columns */ + int32_t *storage_columns; /* Mapping from query fields to storage columns */ + ecs_id_t *ids; /* Resolved (component) ids for current table */ + ecs_entity_t *sources; /* Subjects (sources) of ids */ + ecs_vec_t refs; /* Cached components for non-this terms */ + uint64_t group_id; /* Value used to organize tables in groups */ + int32_t *monitor; /* Used to monitor table for changes */ + ecs_entity_filter_t *entity_filter; /* Entity specific filters */ /* Next match in cache for same table (includes empty tables) */ ecs_query_table_match_t *next_match; - - int32_t *monitor; /* Used to monitor table for changes */ }; -/** A single table can occur multiple times in the cache when a term matches - * multiple table columns. */ +/** Table record type for query table cache. A query only has one per table. */ typedef struct ecs_query_table_t { ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ ecs_query_table_match_t *first; /* List with matches for table */ @@ -783,8 +774,8 @@ typedef struct ecs_query_table_t { /** Points to the beginning & ending of a query group */ typedef struct ecs_query_table_list_t { - ecs_query_table_node_t *first; - ecs_query_table_node_t *last; + ecs_query_table_match_t *first; + ecs_query_table_match_t *last; ecs_query_group_info_t info; } ecs_query_table_list_t; @@ -851,10 +842,10 @@ struct ecs_query_t { /* Monitor generation */ int32_t monitor_generation; - int32_t cascade_by; /* Identify cascade column */ - int32_t match_count; /* How often have tables been (un)matched */ - int32_t prev_match_count; /* Track if sorting is needed */ - int32_t rematch_count; /* Track which tables were added during rematch */ + int32_t cascade_by; /* Identify cascade column */ + int32_t match_count; /* How often have tables been (un)matched */ + int32_t prev_match_count; /* Track if sorting is needed */ + int32_t rematch_count; /* Track which tables were added during rematch */ /* Mixins */ ecs_iterable_t iterable; @@ -867,17 +858,17 @@ struct ecs_query_t { /** All observers for a specific (component) id */ typedef struct ecs_event_id_record_t { /* Triggers for Self */ - ecs_map_t self; /* map */ - ecs_map_t self_up; /* map */ - ecs_map_t up; /* map */ + ecs_map_t self; /* map */ + ecs_map_t self_up; /* map */ + ecs_map_t up; /* map */ - ecs_map_t observers; /* map */ + ecs_map_t observers; /* map */ /* Triggers for SuperSet, SubSet */ - ecs_map_t set_observers; /* map */ + ecs_map_t set_observers; /* map */ /* Triggers for Self with non-This subject */ - ecs_map_t entity_observers; /* map */ + ecs_map_t entity_observers; /* map */ /* Number of active observers for (component) id */ int32_t observer_count; @@ -929,9 +920,9 @@ typedef enum ecs_cmd_kind_t { } ecs_cmd_kind_t; typedef struct ecs_cmd_1_t { - void *value; /* Component value (used by set / get_mut) */ - ecs_size_t size; /* Size of value */ - bool clone_value; /* Clone entity with value (used for clone) */ + void *value; /* Component value (used by set / get_mut) */ + ecs_size_t size; /* Size of value */ + bool clone_value; /* Clone entity with value (used for clone) */ } ecs_cmd_1_t; typedef struct ecs_cmd_n_t { @@ -940,22 +931,22 @@ typedef struct ecs_cmd_n_t { } ecs_cmd_n_t; typedef struct ecs_cmd_t { - ecs_cmd_kind_t kind; /* Command kind */ - int32_t next_for_entity; /* Next operation for entity */ - ecs_id_t id; /* (Component) id */ - ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ - ecs_entity_t entity; /* Entity id */ + ecs_cmd_kind_t kind; /* Command kind */ + int32_t next_for_entity; /* Next operation for entity */ + ecs_id_t id; /* (Component) id */ + ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ + ecs_entity_t entity; /* Entity id */ union { - ecs_cmd_1_t _1; /* Data for single entity operation */ - ecs_cmd_n_t _n; /* Data for multi entity operation */ + ecs_cmd_1_t _1; /* Data for single entity operation */ + ecs_cmd_n_t _n; /* Data for multi entity operation */ } is; } ecs_cmd_t; /* Entity specific metadata for command in defer queue */ typedef struct ecs_cmd_entry_t { int32_t first; - int32_t last; /* If -1, a delete command was inserted */ + int32_t last; /* If -1, a delete command was inserted */ } ecs_cmd_entry_t; /** A stage is a context that allows for safely using the API from multiple @@ -971,26 +962,26 @@ struct ecs_stage_t { /* Deferred command queue */ int32_t defer; ecs_vec_t commands; - ecs_stack_t defer_stack; /* Temp memory used by deferred commands */ - ecs_sparse_t cmd_entries; /* - command combining */ + ecs_stack_t defer_stack; /* Temp memory used by deferred commands */ + ecs_sparse_t cmd_entries; /* - command combining */ /* Thread context */ - ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ - ecs_world_t *world; /* Reference to world */ - ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ + ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ + ecs_world_t *world; /* Reference to world */ + ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ /* One-shot actions to be executed after the merge */ ecs_vec_t post_frame_actions; /* Namespacing */ - ecs_entity_t scope; /* Entity of current scope */ - ecs_entity_t with; /* Id to add by default to new entities */ - ecs_entity_t base; /* Currently instantiated top-level base */ - ecs_entity_t *lookup_path; /* Search path used by lookup operations */ + ecs_entity_t scope; /* Entity of current scope */ + ecs_entity_t with; /* Id to add by default to new entities */ + ecs_entity_t base; /* Currently instantiated top-level base */ + ecs_entity_t *lookup_path; /* Search path used by lookup operations */ /* Properties */ - bool auto_merge; /* Should this stage automatically merge? */ - bool async; /* Is stage asynchronous? (write only) */ + bool auto_merge; /* Should this stage automatically merge? */ + bool async; /* Is stage asynchronous? (write only) */ /* Thread specific allocators */ ecs_stage_allocators_t allocators; @@ -1003,21 +994,21 @@ struct ecs_stage_t { /* Component monitor */ typedef struct ecs_monitor_t { - ecs_vec_t queries; /* vector */ - bool is_dirty; /* Should queries be rematched? */ + ecs_vec_t queries; /* vector */ + bool is_dirty; /* Should queries be rematched? */ } ecs_monitor_t; /* Component monitors */ typedef struct ecs_monitor_set_t { - ecs_map_t monitors; /* map */ - bool is_dirty; /* Should monitors be evaluated? */ + ecs_map_t monitors; /* map */ + bool is_dirty; /* Should monitors be evaluated? */ } ecs_monitor_set_t; /* Data stored for id marked for deletion */ typedef struct ecs_marked_id_t { ecs_id_record_t *idr; ecs_id_t id; - ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ + ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ bool delete_id; } ecs_marked_id_t; @@ -1026,10 +1017,10 @@ typedef struct ecs_store_t { ecs_entity_index_t entity_index; /* Table lookup by id */ - ecs_sparse_t tables; /* sparse */ + ecs_sparse_t tables; /* sparse */ /* Table lookup by hash */ - ecs_hashmap_t table_map; /* hashmap */ + ecs_hashmap_t table_map; /* hashmap */ /* Root table */ ecs_table_t root; @@ -1038,7 +1029,7 @@ typedef struct ecs_store_t { ecs_vec_t records; /* Stack of ids being deleted. */ - ecs_vec_t marked_ids; /* vector */ + ecs_vec_t marked_ids; /* vector */ /* Entity ids associated with depth (for flat hierarchies) */ ecs_vec_t depth_ids; @@ -1058,8 +1049,8 @@ struct ecs_world_t { /* -- Type metadata -- */ ecs_id_record_t *id_index_lo; - ecs_map_t id_index_hi; /* map */ - ecs_sparse_t type_info; /* sparse */ + ecs_map_t id_index_hi; /* map */ + ecs_sparse_t type_info; /* sparse */ /* -- Cached handle to id records -- */ ecs_id_record_t *idr_wildcard; @@ -1085,34 +1076,35 @@ struct ecs_world_t { ecs_store_t store; /* -- Pending table event buffers -- */ - ecs_sparse_t *pending_buffer; /* sparse */ - ecs_sparse_t *pending_tables; /* sparse */ + ecs_sparse_t *pending_buffer; /* sparse */ + ecs_sparse_t *pending_tables; /* sparse */ /* Used to track when cache needs to be updated */ - ecs_monitor_set_t monitors; /* map */ + ecs_monitor_set_t monitors; /* map */ /* -- Systems -- */ - ecs_entity_t pipeline; /* Current pipeline */ + ecs_entity_t pipeline; /* Current pipeline */ /* -- Identifiers -- */ ecs_hashmap_t aliases; ecs_hashmap_t symbols; /* -- Staging -- */ - ecs_stage_t *stages; /* Stages */ - int32_t stage_count; /* Number of stages */ + ecs_stage_t *stages; /* Stages */ + int32_t stage_count; /* Number of stages */ /* -- Multithreading -- */ - ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ - ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ - ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ - int32_t workers_running; /* Number of threads running */ - int32_t workers_waiting; /* Number of workers waiting on sync */ + ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ + ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ + ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ + int32_t workers_running; /* Number of threads running */ + int32_t workers_waiting; /* Number of workers waiting on sync */ + bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ /* -- Time management -- */ - ecs_time_t world_start_time; /* Timestamp of simulation start */ - ecs_time_t frame_start_time; /* Timestamp of frame start */ - ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ + ecs_time_t world_start_time; /* Timestamp of simulation start */ + ecs_time_t frame_start_time; /* Timestamp of frame start */ + ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ /* -- Metrics -- */ ecs_world_info_t info; @@ -1125,10 +1117,10 @@ struct ecs_world_t { /* -- Allocators -- */ ecs_world_allocators_t allocators; /* Static allocation sizes */ - ecs_allocator_t allocator; /* Dynamic allocation sizes */ + ecs_allocator_t allocator; /* Dynamic allocation sizes */ - void *context; /* Application context */ - ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ + void *context; /* Application context */ + ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ }; #endif @@ -1245,14 +1237,17 @@ struct ecs_id_record_t { /* Flags for id */ ecs_flags32_t flags; + /* Cached pointer to type info for id, if id contains data. */ + const ecs_type_info_t *type_info; + + /* Name lookup index (currently only used for ChildOf pairs) */ + ecs_hashmap_t *name_index; + /* Lists for all id records that match a pair wildcard. The wildcard id * record is at the head of the list. */ ecs_id_record_elem_t first; /* (R, *) */ ecs_id_record_elem_t second; /* (*, O) */ - ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */ - - /* Cached pointer to type info for id, if id contains data. */ - const ecs_type_info_t *type_info; + ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */ /* Parent id record. For pair records the parent is the (R, *) record. */ ecs_id_record_t *parent; @@ -1267,9 +1262,6 @@ struct ecs_id_record_t { /* Cache invalidation counter */ ecs_reachable_cache_t reachable; - - /* Name lookup index (currently only used for ChildOf pairs) */ - ecs_hashmap_t *name_index; }; /* Get id record for id */ @@ -1315,6 +1307,10 @@ ecs_hashmap_t* flecs_id_name_index_ensure( ecs_world_t *world, ecs_id_t id); +ecs_hashmap_t* flecs_id_record_name_index_ensure( + ecs_world_t *world, + ecs_id_record_t *idr); + /* Get name index for id record */ ecs_hashmap_t* flecs_id_name_index_get( const ecs_world_t *world, @@ -1376,8 +1372,8 @@ void flecs_observable_init( void flecs_observable_fini( ecs_observable_t *observable); -bool flecs_check_observers_for_event( - const ecs_poly_t *world, +bool flecs_observers_exist( + ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event); @@ -1463,7 +1459,7 @@ void flecs_iter_free( #endif /** - * @file table.c + * @file table.h * @brief Table storage implementation. */ @@ -1667,7 +1663,7 @@ bool flecs_table_release( ecs_table_t *table); /* Increase observer count of table */ -void flecs_table_observer_add( +void flecs_table_traversable_add( ecs_table_t *table, int32_t value); @@ -1905,7 +1901,7 @@ bool flecs_defer_purge( #endif /** - * @file world.c + * @file world.h * @brief World-level API. */ @@ -2044,210 +2040,6 @@ void flecs_resume_readonly( #endif -/** - * @file datastructures/qsort.h - * @brief Quicksort implementation. - */ - -/* From: https://github.com/svpv/qsort/blob/master/qsort.h - * Use custom qsort implementation rather than relying on the version in libc to - * ensure that results are consistent across platforms. - */ - -/* - * Copyright (c) 2013, 2017 Alexey Tourbin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* - * This is a traditional Quicksort implementation which mostly follows - * [Sedgewick 1978]. Sorting is performed entirely on array indices, - * while actual access to the array elements is abstracted out with the - * user-defined `LESS` and `SWAP` primitives. - * - * Synopsis: - * QSORT(N, LESS, SWAP); - * where - * N - the number of elements in A[]; - * LESS(i, j) - compares A[i] to A[j]; - * SWAP(i, j) - exchanges A[i] with A[j]. - */ - -#ifndef QSORT_H -#define QSORT_H - -/* Sort 3 elements. */ -#define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \ -do { \ - if (Q_LESS(q_a2, q_a1)) { \ - if (Q_LESS(q_a3, q_a2)) \ - Q_SWAP(q_a1, q_a3); \ - else { \ - Q_SWAP(q_a1, q_a2); \ - if (Q_LESS(q_a3, q_a2)) \ - Q_SWAP(q_a2, q_a3); \ - } \ - } \ - else if (Q_LESS(q_a3, q_a2)) { \ - Q_SWAP(q_a2, q_a3); \ - if (Q_LESS(q_a2, q_a1)) \ - Q_SWAP(q_a1, q_a2); \ - } \ -} while (0) - -/* Partition [q_l,q_r] around a pivot. After partitioning, - * [q_l,q_j] are the elements that are less than or equal to the pivot, - * while [q_i,q_r] are the elements greater than or equal to the pivot. */ -#define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \ -do { \ - /* The middle element, not to be confused with the median. */ \ - Q_UINT q_m = q_l + ((q_r - q_l) >> 1); \ - /* Reorder the second, the middle, and the last items. \ - * As [Edelkamp Weiss 2016] explain, using the second element \ - * instead of the first one helps avoid bad behaviour for \ - * decreasingly sorted arrays. This method is used in recent \ - * versions of gcc's std::sort, see gcc bug 58437#c13, although \ - * the details are somewhat different (cf. #c14). */ \ - Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP); \ - /* Place the median at the beginning. */ \ - Q_SWAP(q_l, q_m); \ - /* Partition [q_l+2, q_r-1] around the median which is in q_l. \ - * q_i and q_j are initially off by one, they get decremented \ - * in the do-while loops. */ \ - q_i = q_l + 1; q_j = q_r; \ - while (1) { \ - do q_i++; while (Q_LESS(q_i, q_l)); \ - do q_j--; while (Q_LESS(q_l, q_j)); \ - if (q_i >= q_j) break; /* Sedgewick says "until j < i" */ \ - Q_SWAP(q_i, q_j); \ - } \ - /* Compensate for the i==j case. */ \ - q_i = q_j + 1; \ - /* Put the median to its final place. */ \ - Q_SWAP(q_l, q_j); \ - /* The median is not part of the left subfile. */ \ - q_j--; \ -} while (0) - -/* Insertion sort is applied to small subfiles - this is contrary to - * Sedgewick's suggestion to run a separate insertion sort pass after - * the partitioning is done. The reason I don't like a separate pass - * is that it triggers extra comparisons, because it can't see that the - * medians are already in their final positions and need not be rechecked. - * Since I do not assume that comparisons are cheap, I also do not try - * to eliminate the (q_j > q_l) boundary check. */ -#define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \ -do { \ - Q_UINT q_i, q_j; \ - /* For each item starting with the second... */ \ - for (q_i = q_l + 1; q_i <= q_r; q_i++) \ - /* move it down the array so that the first part is sorted. */ \ - for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--) \ - Q_SWAP(q_j, q_j - 1); \ -} while (0) - -/* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to - * Q_THRESH, the algorithm performs recursive partitioning. When the size - * drops below Q_THRESH, the algorithm switches to insertion sort. - * The minimum valid value is probably 5 (with 5 items, the second and - * the middle items, the middle itself being rounded down, are distinct). */ -#define Q_THRESH 16 - -/* The main loop. */ -#define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \ -do { \ - Q_UINT q_l = 0; \ - Q_UINT q_r = (Q_N) - 1; \ - Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \ - struct { Q_UINT q_l, q_r; } \ - /* On 32-bit platforms, to sort a "char[3GB+]" array, \ - * it may take full 32 stack frames. On 64-bit CPUs, \ - * though, the address space is limited to 48 bits. \ - * The usage is further reduced if Q_N has a 32-bit type. */ \ - q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \ - while (1) { \ - if (q_r - q_l + 1 >= Q_THRESH) { \ - Q_UINT q_i, q_j; \ - Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \ - /* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \ - * Dealing with them depends on which one is bigger. */ \ - if (q_j - q_l >= q_r - q_i) \ - Q_SUBFILES(q_l, q_j, q_i, q_r); \ - else \ - Q_SUBFILES(q_i, q_r, q_l, q_j); \ - } \ - else { \ - Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \ - /* Pop subfiles from the stack, until it gets empty. */ \ - if (q_sp == 0) break; \ - q_sp--; \ - q_l = q_st[q_sp].q_l; \ - q_r = q_st[q_sp].q_r; \ - } \ - } \ -} while (0) - -/* The missing part: dealing with subfiles. - * Assumes that the first subfile is not smaller than the second. */ -#define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \ -do { \ - /* If the second subfile is only a single element, it needs \ - * no further processing. The first subfile will be processed \ - * on the next iteration (both subfiles cannot be only a single \ - * element, due to Q_THRESH). */ \ - if (q_l2 == q_r2) { \ - q_l = q_l1; \ - q_r = q_r1; \ - } \ - else { \ - /* Otherwise, both subfiles need processing. \ - * Push the larger subfile onto the stack. */ \ - q_st[q_sp].q_l = q_l1; \ - q_st[q_sp].q_r = q_r1; \ - q_sp++; \ - /* Process the smaller subfile on the next iteration. */ \ - q_l = q_l2; \ - q_r = q_r2; \ - } \ -} while (0) - -/* And now, ladies and gentlemen, may I proudly present to you... */ -#define QSORT(Q_N, Q_LESS, Q_SWAP) \ -do { \ - if ((Q_N) > 1) \ - /* We could check sizeof(Q_N) and use "unsigned", but at least \ - * on x86_64, this has the performance penalty of up to 5%. */ \ - Q_LOOP(ecs_size_t, Q_N, Q_LESS, Q_SWAP); \ -} while (0) - -void ecs_qsort( - void *base, - ecs_size_t nitems, - ecs_size_t size, - int (*compar)(const void *, const void*)); - -#define ecs_qsort_t(base, nitems, T, compar) \ - ecs_qsort(base, nitems, ECS_SIZEOF(T), compar) - -#endif - /** * @file datastructures/name_index.h * @brief Data structure for resolving 64bit keys by string (name). @@ -2342,7 +2134,6 @@ void flecs_bootstrap( ecs_set_name(world, name, (char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ ecs_set_symbol(world, name, #name) - /* Bootstrap functions for other parts in the code */ void flecs_bootstrap_hierarchy(ecs_world_t *world); @@ -2353,11 +2144,15 @@ void flecs_bootstrap_hierarchy(ecs_world_t *world); /* Mark an entity as being watched. This is used to trigger automatic rematching * when entities used in system expressions change their components. */ -ecs_record_t* flecs_add_flag( +void flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag); +void flecs_record_add_flag( + ecs_record_t *record, + uint32_t flag); + ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e); @@ -2524,7 +2319,7 @@ uint64_t _flecs_ito( void flecs_entity_filter_init( ecs_world_t *world, - ecs_entity_filter_t *entity_filter, + ecs_entity_filter_t **entity_filter, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, @@ -2574,11 +2369,6 @@ int flecs_entity_compare( ecs_entity_t e2, const void *ptr2); -/* Compare function for entity ids which can be used with qsort */ -int flecs_entity_compare_qsort( - const void *e1, - const void *e2); - bool flecs_name_is_id( const char *name); @@ -2654,10 +2444,10 @@ void flecs_table_check_sanity(ecs_table_t *table) { ECS_INTERNAL_ERROR, NULL); int32_t i; - int32_t sw_offset = table->ext ? table->ext->sw_offset : 0; - int32_t sw_count = table->ext ? table->ext->sw_count : 0; - int32_t bs_offset = table->ext ? table->ext->bs_offset : 0; - int32_t bs_count = table->ext ? table->ext->bs_count : 0; + int32_t sw_offset = table->_ ? table->_->sw_offset : 0; + int32_t sw_count = table->_ ? table->_->sw_count : 0; + int32_t bs_offset = table->_ ? table->_->bs_offset : 0; + int32_t bs_count = table->_ ? table->_->bs_count : 0; int32_t type_count = table->type.count; ecs_id_t *ids = table->type.array; @@ -2703,10 +2493,10 @@ void flecs_table_check_sanity(ecs_table_t *table) { } if (sw_count) { - ecs_assert(table->ext->sw_columns != NULL, + ecs_assert(table->_->sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < sw_count; i ++) { - ecs_switch_t *sw = &table->ext->sw_columns[i]; + ecs_switch_t *sw = &table->_->sw_columns[i]; ecs_assert(ecs_vec_count(&sw->values) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion, @@ -2715,10 +2505,10 @@ void flecs_table_check_sanity(ecs_table_t *table) { } if (bs_count) { - ecs_assert(table->ext->bs_columns != NULL, + ecs_assert(table->_->bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < bs_count; i ++) { - ecs_bitset_t *bs = &table->ext->bs_columns[i]; + ecs_bitset_t *bs = &table->_->bs_columns[i]; ecs_assert(flecs_bitset_count(bs) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), @@ -2726,13 +2516,15 @@ void flecs_table_check_sanity(ecs_table_t *table) { } } - ecs_assert((table->observed_count == 0) || - (table->flags & EcsTableHasObserved), ECS_INTERNAL_ERROR, NULL); + ecs_assert((table->_->traversable_count == 0) || + (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); } #else #define flecs_table_check_sanity(table) #endif +/* Initialize the storage map for a table. A storage map is an integer array + * that maps type indices to column indices and vice versa. */ static void flecs_table_init_storage_map( ecs_world_t *world, @@ -2788,6 +2580,8 @@ void flecs_table_init_storage_map( } } +/* Set flags for type hooks so table operations can quickly check whether a + * fast or complex operation that invokes hooks is required. */ static ecs_flags32_t flecs_type_info_flags( const ecs_type_info_t *ti) @@ -2816,6 +2610,9 @@ ecs_flags32_t flecs_type_info_flags( return flags; } +/* Initialize array with cached pointers to type info. The type info array has + * an element for each table column. Multiple tables may share the same type + * info array, as long as they have the same components. */ static void flecs_table_init_type_info( ecs_world_t *world, @@ -2825,7 +2622,7 @@ void flecs_table_init_type_info( ecs_assert(table->storage_table == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->type_info == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_record_t *records = table->records; + ecs_table_record_t *records = table->_->records; int32_t i, count = table->type.count; table->type_info = flecs_walloc_n(world, ecs_type_info_t*, count); @@ -2841,6 +2638,16 @@ void flecs_table_init_type_info( } } +/* Find or create the storage table for a table. A storage table only contains + * components, no tags. A table maintains a reference to its storage table as + * this is used for sharing metadata such as the type info array. A storage + * table is found by taking a table type and removing all non-component ids. + * For example, for table + * [Position, Velocity, Npc] + * the storage table would be + * [Position, Velocity] + * assuming that Npc is a tag. + */ static void flecs_table_init_storage_table( ecs_world_t *world, @@ -2853,7 +2660,7 @@ void flecs_table_init_storage_table( ecs_type_t type = table->type; int32_t i, count = type.count; ecs_id_t *ids = type.array; - ecs_table_record_t *records = table->records; + ecs_table_record_t *records = table->_->records; ecs_id_t array[FLECS_ID_DESC_MAX]; ecs_type_t storage_ids = { .array = array }; @@ -2879,7 +2686,7 @@ void flecs_table_init_storage_table( table->storage_ids = storage_table->type.array; table->type_info = storage_table->type_info; table->flags |= storage_table->flags; - storage_table->refcount ++; + storage_table->_->refcount ++; } else if (storage_ids.count) { table->storage_table = table; table->storage_count = flecs_ito(uint16_t, count); @@ -2896,17 +2703,7 @@ void flecs_table_init_storage_table( } } -static -ecs_table_ext_t* flecs_table_ensure_ext( - ecs_world_t *world, - ecs_table_t *table) -{ - if (!table->ext) { - table->ext = flecs_calloc_t(&world->allocator, ecs_table_ext_t); - } - return table->ext; -} - +/* Initialize table column vectors */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table) @@ -2928,28 +2725,29 @@ void flecs_table_init_data( #endif } - ecs_table_ext_t *ext = table->ext; - if (ext) { - int32_t sw_count = ext->sw_count; - int32_t bs_count = ext->bs_count; + ecs_table__t *meta = table->_; + int32_t sw_count = meta->sw_count; + int32_t bs_count = meta->bs_count; - if (sw_count) { - ext->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count); - for (i = 0; i < sw_count; i ++) { - flecs_switch_init(&ext->sw_columns[i], - &world->allocator, 0); - } + if (sw_count) { + meta->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count); + for (i = 0; i < sw_count; i ++) { + flecs_switch_init(&meta->sw_columns[i], + &world->allocator, 0); } + } - if (bs_count) { - ext->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); - for (i = 0; i < bs_count; i ++) { - flecs_bitset_init(&ext->bs_columns[i]); - } + if (bs_count) { + meta->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); + for (i = 0; i < bs_count; i ++) { + flecs_bitset_init(&meta->bs_columns[i]); } } } +/* Initialize table flags. Table flags are used in lots of scenarios to quickly + * check the features of a table without having to inspect the table type. Table + * flags are typically used to early-out of potentially expensive operations. */ static void flecs_table_init_flags( ecs_world_t *world, @@ -2958,15 +2756,10 @@ void flecs_table_init_flags( ecs_id_t *ids = table->type.array; int32_t count = table->type.count; - /* Iterate components to initialize table flags */ int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; - /* As we're iterating over the table components, also set the table - * flags. These allow us to quickly determine if the table contains - * data that needs to be handled in a special way. */ - if (id <= EcsLastInternalComponentId) { table->flags |= EcsTableHasBuiltins; } @@ -3002,29 +2795,29 @@ void flecs_table_init_flags( } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { table->flags |= EcsTableHasName; } else if (r == EcsUnion) { - ecs_table_ext_t *ext = flecs_table_ensure_ext(world, table); + ecs_table__t *meta = table->_; table->flags |= EcsTableHasUnion; - if (!ext->sw_count) { - ext->sw_offset = flecs_ito(int16_t, i); + if (!meta->sw_count) { + meta->sw_offset = flecs_ito(int16_t, i); } - ext->sw_count ++; + meta->sw_count ++; } else if (r == ecs_id(EcsTarget)) { - ecs_table_ext_t *ext = flecs_table_ensure_ext(world, table); + ecs_table__t *meta = table->_; table->flags |= EcsTableHasTarget; - ext->ft_offset = flecs_ito(int16_t, i); + meta->ft_offset = flecs_ito(int16_t, i); } else if (r == ecs_id(EcsPoly)) { table->flags |= EcsTableHasBuiltins; } } else { if (ECS_HAS_ID_FLAG(id, TOGGLE)) { - ecs_table_ext_t *ext = flecs_table_ensure_ext(world, table); + ecs_table__t *meta = table->_; table->flags |= EcsTableHasToggle; - if (!ext->bs_count) { - ext->bs_offset = flecs_ito(int16_t, i); + if (!meta->bs_count) { + meta->bs_offset = flecs_ito(int16_t, i); } - ext->bs_count ++; + meta->bs_count ++; } if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { table->flags |= EcsTableHasOverrides; @@ -3034,6 +2827,7 @@ void flecs_table_init_flags( } } +/* Utility function that appends an element to the table record array */ static void flecs_table_append_to_records( ecs_world_t *world, @@ -3060,13 +2854,24 @@ void flecs_table_append_to_records( ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); } +/* Main table initialization function */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from) { + /* Make sure table->flags is initialized */ flecs_table_init_flags(world, table); + /* The following code walks the table type to discover which id records the + * table needs to register table records with. + * + * In addition to registering itself with id records for each id in the + * table type, a table also registers itself with wildcard id records. For + * example, if a table contains (Eats, Apples), it will register itself with + * wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it + * easier for wildcard queries to find the relevant tables. */ + int32_t dst_i = 0, dst_count = table->type.count; int32_t src_i = 0, src_count = 0; ecs_id_t *dst_ids = table->type.array; @@ -3075,7 +2880,7 @@ void flecs_table_init( if (from) { src_count = from->type.count; src_ids = from->type.array; - src_tr = from->records; + src_tr = from->_->records; } /* We don't know in advance how large the records array will be, so use @@ -3084,7 +2889,7 @@ void flecs_table_init( ecs_allocator_t *a = &world->allocator; ecs_vec_t *records = &world->store.records; ecs_vec_reset_t(a, records, ecs_table_record_t); - ecs_id_record_t *idr; + ecs_id_record_t *idr, *childof_idr = NULL; int32_t last_id = -1; /* Track last regular (non-pair) id */ int32_t first_pair = -1; /* Track the first pair in the table */ @@ -3196,14 +3001,20 @@ void flecs_table_init( } if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); - idr = ((ecs_id_record_t*)tr->hdr.cache)->parent; /* (R, *) */ + + ecs_id_record_t *p_idr = (ecs_id_record_t*)tr->hdr.cache; + r = ECS_PAIR_FIRST(dst_id); + if (r == EcsChildOf) { + childof_idr = p_idr; + } + + idr = p_idr->parent; /* (R, *) */ ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->column = dst_i; tr->count = 0; - r = ECS_PAIR_FIRST(dst_id); } ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); @@ -3245,7 +3056,8 @@ void flecs_table_init( } if (dst_count && !has_childof) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)world->idr_childof_0; + childof_idr = world->idr_childof_0; + tr->hdr.cache = (ecs_table_cache_t*)childof_idr; tr->column = 0; tr->count = 1; } @@ -3254,8 +3066,8 @@ void flecs_table_init( int32_t i, dst_record_count = ecs_vec_count(records); ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); - table->record_count = flecs_ito(uint16_t, dst_record_count); - table->records = dst_tr; + table->_->record_count = flecs_ito(uint16_t, dst_record_count); + table->_->records = dst_tr; /* Register & patch up records */ for (i = 0; i < dst_record_count; i ++) { @@ -3287,17 +3099,35 @@ void flecs_table_init( flecs_table_init_storage_table(world, table); flecs_table_init_data(world, table); + + if (table->flags & EcsTableHasName) { + ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL); + table->_->name_index = + flecs_id_record_name_index_ensure(world, childof_idr); + ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (table->flags & EcsTableHasOnTableCreate) { + flecs_emit(world, world, &(ecs_event_desc_t) { + .ids = &table->type, + .event = EcsOnTableCreate, + .table = table, + .flags = EcsEventTableOnly, + .observable = world + }); + } } +/* Unregister table from id records */ static void flecs_table_records_unregister( ecs_world_t *world, ecs_table_t *table) { uint64_t table_id = table->id; - int32_t i, count = table->record_count; + int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->records[i]; + ecs_table_record_t *tr = &table->_->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; ecs_id_t id = ((ecs_id_record_t*)cache)->id; @@ -3311,9 +3141,12 @@ void flecs_table_records_unregister( flecs_id_record_release(world, (ecs_id_record_t*)cache); } - flecs_wfree_n(world, ecs_table_record_t, count, table->records); + flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); } +/* Keep track for what kind of builtin events observers are registered that can + * potentially match the table. This allows code to early out of calling the + * emit function that notifies observers. */ static void flecs_table_add_trigger_flags( ecs_world_t *world, @@ -3330,9 +3163,15 @@ void flecs_table_add_trigger_flags( table->flags |= EcsTableHasOnSet; } else if (event == EcsUnSet) { table->flags |= EcsTableHasUnSet; + } else if (event == EcsOnTableFill) { + table->flags |= EcsTableHasOnTableFill; + } else if (event == EcsOnTableEmpty) { + table->flags |= EcsTableHasOnTableEmpty; } } +/* Invoke OnRemove observers for all entities in table. Useful during table + * deletion or when clearing entities from a table. */ static void flecs_table_notify_on_remove( ecs_world_t *world, @@ -3345,10 +3184,9 @@ void flecs_table_notify_on_remove( } } -/* -- Private functions -- */ - +/* Invoke type hook for entities in table */ static -void flecs_on_component_callback( +void flecs_table_invoke_hook( ecs_world_t *world, ecs_table_t *table, ecs_iter_action_t callback, @@ -3366,8 +3204,9 @@ void flecs_on_component_callback( world, table, count, row, entities, ptr, id, ti, event, callback); } +/* Construct components */ static -void flecs_ctor_component( +void flecs_table_invoke_ctor( ecs_type_info_t *ti, ecs_vec_t *column, int32_t row, @@ -3381,8 +3220,9 @@ void flecs_ctor_component( } } +/* Destruct components */ static -void flecs_dtor_component( +void flecs_table_invoke_dtor( ecs_type_info_t *ti, ecs_vec_t *column, int32_t row, @@ -3397,8 +3237,9 @@ void flecs_dtor_component( } } +/* Run hooks that get invoked when component is added to entity */ static -void flecs_run_add_hooks( +void flecs_table_invoke_add_hooks( ecs_world_t *world, ecs_table_t *table, ecs_type_info_t *ti, @@ -3412,18 +3253,19 @@ void flecs_run_add_hooks( ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (construct) { - flecs_ctor_component(ti, column, row, count); + flecs_table_invoke_ctor(ti, column, row, count); } ecs_iter_action_t on_add = ti->hooks.on_add; if (on_add) { - flecs_on_component_callback(world, table, on_add, EcsOnAdd, column, + flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, entities, id, row, count, ti); } } +/* Run hooks that get invoked when component is removed from entity */ static -void flecs_run_remove_hooks( +void flecs_table_invoke_remove_hooks( ecs_world_t *world, ecs_table_t *table, ecs_type_info_t *ti, @@ -3438,17 +3280,18 @@ void flecs_run_remove_hooks( ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { - flecs_on_component_callback(world, table, on_remove, EcsOnRemove, column, + flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, entities, id, row, count, ti); } if (dtor) { - flecs_dtor_component(ti, column, row, count); + flecs_table_invoke_dtor(ti, column, row, count); } } +/* Destruct all components and/or delete all entities in table in range */ static -void flecs_dtor_all_components( +void flecs_table_dtor_all( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, @@ -3468,7 +3311,7 @@ void flecs_dtor_all_components( (void)records; - if (is_delete && table->observed_count) { + if (is_delete && table->_->traversable_count) { /* If table contains monitored entities with traversable relationships, * make sure to invalidate observer cache */ flecs_emit_propagate_invalidate(world, table, row, count); @@ -3477,7 +3320,7 @@ void flecs_dtor_all_components( /* If table has components with destructors, iterate component columns */ if (table->flags & EcsTableHasDtors) { /* Throw up a lock just to be sure */ - table->lock = true; + table->_->lock = true; /* Run on_remove callbacks first before destructing components */ for (c = 0; c < ids_count; c++) { @@ -3485,14 +3328,14 @@ void flecs_dtor_all_components( ecs_type_info_t *ti = table->type_info[c]; ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { - flecs_on_component_callback(world, table, on_remove, EcsOnRemove, + flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entities[row], ids[c], row, count, ti); } } /* Destruct components */ for (c = 0; c < ids_count; c++) { - flecs_dtor_component(table->type_info[c], &data->columns[c], + flecs_table_invoke_dtor(table->type_info[c], &data->columns[c], row, count); } @@ -3526,7 +3369,7 @@ void flecs_dtor_all_components( } } - table->lock = false; + table->_->lock = false; /* If table does not have destructors, just update entity index */ } else if (update_entity_index) { @@ -3558,6 +3401,7 @@ void flecs_dtor_all_components( } } +/* Cleanup table storage */ static void flecs_table_fini_data( ecs_world_t *world, @@ -3568,7 +3412,7 @@ void flecs_table_fini_data( bool is_delete, bool deactivate) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); if (!data) { return; @@ -3580,7 +3424,7 @@ void flecs_table_fini_data( int32_t count = flecs_table_data_count(data); if (count) { - flecs_dtor_all_components(world, table, data, 0, count, + flecs_table_dtor_all(world, table, data, 0, count, update_entity_index, is_delete); } @@ -3602,27 +3446,25 @@ void flecs_table_fini_data( data->columns = NULL; } - ecs_table_ext_t *ext = table->ext; - if (ext) { - ecs_switch_t *sw_columns = ext->sw_columns; - if (sw_columns) { - int32_t c, column_count = ext->sw_count; - for (c = 0; c < column_count; c ++) { - flecs_switch_fini(&sw_columns[c]); - } - flecs_wfree_n(world, ecs_switch_t, column_count, sw_columns); - ext->sw_columns = NULL; + ecs_table__t *meta = table->_; + ecs_switch_t *sw_columns = meta->sw_columns; + if (sw_columns) { + int32_t c, column_count = meta->sw_count; + for (c = 0; c < column_count; c ++) { + flecs_switch_fini(&sw_columns[c]); } + flecs_wfree_n(world, ecs_switch_t, column_count, sw_columns); + meta->sw_columns = NULL; + } - ecs_bitset_t *bs_columns = ext->bs_columns; - if (bs_columns) { - int32_t c, column_count = ext->bs_count; - for (c = 0; c < column_count; c ++) { - flecs_bitset_fini(&bs_columns[c]); - } - flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); - ext->bs_columns = NULL; + ecs_bitset_t *bs_columns = meta->bs_columns; + if (bs_columns) { + int32_t c, column_count = meta->bs_count; + for (c = 0; c < column_count; c ++) { + flecs_bitset_fini(&bs_columns[c]); } + flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); + meta->bs_columns = NULL; } ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); @@ -3632,8 +3474,8 @@ void flecs_table_fini_data( flecs_table_set_empty(world, table); } - table->observed_count = 0; - table->flags &= ~EcsTableHasObserved; + table->_->traversable_count = 0; + table->flags &= ~EcsTableHasTraversable; } /* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ @@ -3685,22 +3527,24 @@ void flecs_table_free( ecs_table_t *table) { bool is_root = table == &world->store.root; - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), ECS_INTERNAL_ERROR, NULL); (void)world; - ecs_assert(table->refcount == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->_->refcount == 0, ECS_INTERNAL_ERROR, NULL); if (!is_root && !(world->flags & EcsWorldQuit)) { - flecs_emit(world, world, &(ecs_event_desc_t) { - .ids = &table->type, - .event = EcsOnTableDelete, - .table = table, - .flags = EcsEventTableOnly, - .observable = world - }); + if (table->flags & EcsTableHasOnTableDelete) { + flecs_emit(world, world, &(ecs_event_desc_t) { + .ids = &table->type, + .event = EcsOnTableDelete, + .table = table, + .flags = EcsEventTableOnly, + .observable = world + }); + } } if (ecs_should_log_2()) { @@ -3724,14 +3568,14 @@ void flecs_table_free( .count = table->type.count }; - flecs_hashmap_remove(&world->store.table_map, &ids, ecs_table_t*); + flecs_hashmap_remove_w_hash( + &world->store.table_map, &ids, ecs_table_t*, table->_->hash); } flecs_wfree_n(world, int32_t, table->storage_count + 1, table->dirty_state); flecs_wfree_n(world, int32_t, table->storage_count + table->type.count, table->storage_map); flecs_table_records_unregister(world, table); - flecs_free_t(&world->allocator, ecs_table_ext_t, table->ext); ecs_table_t *storage_table = table->storage_table; if (storage_table == table) { @@ -3745,7 +3589,7 @@ void flecs_table_free( /* Update counters */ world->info.table_count --; - world->info.table_record_count -= table->record_count; + world->info.table_record_count -= table->_->record_count; world->info.table_storage_count -= table->storage_count; world->info.table_delete_total ++; @@ -3755,6 +3599,8 @@ void flecs_table_free( world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex); } + flecs_free_t(&world->allocator, ecs_table__t, table->_); + if (!(world->flags & EcsWorldFini)) { ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); flecs_table_free_type(world, table); @@ -3764,26 +3610,31 @@ void flecs_table_free( ecs_log_pop_2(); } +/* Increase refcount of table. A table will not be freed until its refcount + * reaches zero. Refcounting is primarily used to prevent storage tables from + * being freed while they are still being referred to. + * Tables do not form cyclical dependencies. */ void flecs_table_claim( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); - table->refcount ++; + ecs_assert(table->_->refcount > 0, ECS_INTERNAL_ERROR, NULL); + table->_->refcount ++; (void)world; } +/* Decrease refcount of table */ bool flecs_table_release( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->_->refcount > 0, ECS_INTERNAL_ERROR, NULL); - if (--table->refcount == 0) { + if (--table->_->refcount == 0) { flecs_table_free(world, table); return true; } @@ -3805,23 +3656,29 @@ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_clear_edges(world, table); } -void flecs_table_observer_add( +/* Keep track of number of traversable entities in table. A traversable entity + * is an entity used as target in a pair with a traversable relationship. The + * traversable count and flag are used by code to early out of mechanisms like + * event propagation and recursive cleanup. */ +void flecs_table_traversable_add( ecs_table_t *table, int32_t value) { - int32_t result = table->observed_count += value; + int32_t result = table->_->traversable_count += value; ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); if (result == 0) { - table->flags &= ~EcsTableHasObserved; + table->flags &= ~EcsTableHasTraversable; } else if (result == value) { - table->flags |= EcsTableHasObserved; + table->flags |= EcsTableHasTraversable; } } +/* Mark table column dirty. This usually happens as the result of a set + * operation, or iteration of a query with [out] fields. */ static void flecs_table_mark_table_dirty( ecs_world_t *world, @@ -3834,12 +3691,13 @@ void flecs_table_mark_table_dirty( } } +/* Mark table component dirty */ void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (table->dirty_state) { @@ -3849,6 +3707,26 @@ void flecs_table_mark_dirty( } } +/* Get (or create) dirty state of table. Used by queries for change tracking */ +int32_t* flecs_table_get_dirty_state( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (!table->dirty_state) { + int32_t column_count = table->storage_count; + table->dirty_state = flecs_alloc_n(&world->allocator, + int32_t, column_count + 1); + ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + for (int i = 0; i < column_count + 1; i ++) { + table->dirty_state[i] = 1; + } + } + return table->dirty_state; +} + +/* Table move logic for switch (union relationship) column */ static void flecs_table_move_switch_columns( ecs_table_t *dst_table, @@ -3858,26 +3736,26 @@ void flecs_table_move_switch_columns( int32_t count, bool clear) { - ecs_table_ext_t *dst_ext = dst_table->ext; - ecs_table_ext_t *src_ext = src_table->ext; - if (!dst_ext && !src_ext) { + ecs_table__t *dst_meta = dst_table->_; + ecs_table__t *src_meta = src_table->_; + if (!dst_meta && !src_meta) { return; } - int32_t i_old = 0, src_column_count = src_ext ? src_ext->sw_count : 0; - int32_t i_new = 0, dst_column_count = dst_ext ? dst_ext->sw_count : 0; + int32_t i_old = 0, src_column_count = src_meta ? src_meta->sw_count : 0; + int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->sw_count : 0; if (!src_column_count && !dst_column_count) { return; } - ecs_switch_t *src_columns = src_ext ? src_ext->sw_columns : NULL; - ecs_switch_t *dst_columns = dst_ext ? dst_ext->sw_columns : NULL; + ecs_switch_t *src_columns = src_meta ? src_meta->sw_columns : NULL; + ecs_switch_t *dst_columns = dst_meta ? dst_meta->sw_columns : NULL; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; - int32_t offset_new = dst_ext ? dst_ext->sw_offset : 0; - int32_t offset_old = src_ext ? src_ext->sw_offset : 0; + int32_t offset_new = dst_meta ? dst_meta->sw_offset : 0; + int32_t offset_old = src_meta ? src_meta->sw_offset : 0; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; @@ -3923,6 +3801,7 @@ void flecs_table_move_switch_columns( } } +/* Table move logic for bitset (toggle component) column */ static void flecs_table_move_bitset_columns( ecs_table_t *dst_table, @@ -3932,27 +3811,27 @@ void flecs_table_move_bitset_columns( int32_t count, bool clear) { - ecs_table_ext_t *dst_ext = dst_table->ext; - ecs_table_ext_t *src_ext = src_table->ext; - if (!dst_ext && !src_ext) { + ecs_table__t *dst_meta = dst_table->_; + ecs_table__t *src_meta = src_table->_; + if (!dst_meta && !src_meta) { return; } - int32_t i_old = 0, src_column_count = src_ext ? src_ext->bs_count : 0; - int32_t i_new = 0, dst_column_count = dst_ext ? dst_ext->bs_count : 0; + int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; + int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; if (!src_column_count && !dst_column_count) { return; } - ecs_bitset_t *src_columns = src_ext ? src_ext->bs_columns : NULL; - ecs_bitset_t *dst_columns = dst_ext ? dst_ext->bs_columns : NULL; + ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; + ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; - int32_t offset_new = dst_ext ? dst_ext->bs_offset : 0; - int32_t offset_old = src_ext ? src_ext->bs_offset : 0; + int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; + int32_t offset_old = src_meta ? src_meta->bs_offset : 0; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; @@ -3998,6 +3877,8 @@ void flecs_table_move_bitset_columns( } } +/* Grow table column. When a column needs to be reallocated this function takes + * care of correctly invoking ctor/move/dtor hooks. */ static void* flecs_table_grow_column( ecs_world_t *world, @@ -4069,6 +3950,7 @@ void* flecs_table_grow_column( return result; } +/* Grow all data structures in a table */ static int32_t flecs_table_grow_data( ecs_world_t *world, @@ -4115,28 +3997,26 @@ int32_t flecs_table_grow_data( ecs_type_info_t *ti = type_info[i]; flecs_table_grow_column(world, column, ti, to_add, size, true); ecs_assert(columns[i].size == size, ECS_INTERNAL_ERROR, NULL); - flecs_run_add_hooks(world, table, ti, column, e, table->type.array[i], + flecs_table_invoke_add_hooks(world, table, ti, column, e, table->type.array[i], cur_count, to_add, false); } - ecs_table_ext_t *ext = table->ext; - if (ext) { - int32_t sw_count = ext->sw_count; - int32_t bs_count = ext->bs_count; - ecs_switch_t *sw_columns = ext->sw_columns; - ecs_bitset_t *bs_columns = ext->bs_columns; + ecs_table__t *meta = table->_; + int32_t sw_count = meta->sw_count; + int32_t bs_count = meta->bs_count; + ecs_switch_t *sw_columns = meta->sw_columns; + ecs_bitset_t *bs_columns = meta->bs_columns; - /* Add elements to each switch column */ - for (i = 0; i < sw_count; i ++) { - ecs_switch_t *sw = &sw_columns[i]; - flecs_switch_addn(sw, to_add); - } + /* Add elements to each switch column */ + for (i = 0; i < sw_count; i ++) { + ecs_switch_t *sw = &sw_columns[i]; + flecs_switch_addn(sw, to_add); + } - /* Add elements to each bitset column */ - for (i = 0; i < bs_count; i ++) { - ecs_bitset_t *bs = &bs_columns[i]; - flecs_bitset_addn(bs, to_add); - } + /* Add elements to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, to_add); } /* If the table is monitored indicate that there has been a change */ @@ -4150,6 +4030,7 @@ int32_t flecs_table_grow_data( return cur_count; } +/* Append operation for tables that don't have any complex logic */ static void flecs_table_fast_append( ecs_world_t *world, @@ -4166,6 +4047,7 @@ void flecs_table_fast_append( } } +/* Append entity to table */ int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, @@ -4175,7 +4057,7 @@ int32_t flecs_table_append( bool on_add) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(!(table->flags & EcsTableHasTarget), ECS_INVALID_OPERATION, NULL); @@ -4231,7 +4113,7 @@ int32_t flecs_table_append( ecs_iter_action_t on_add_hook; if (on_add && (on_add_hook = ti->hooks.on_add)) { - flecs_on_component_callback(world, table, on_add_hook, EcsOnAdd, column, + flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, &entities[count], table->storage_ids[i], count, 1, ti); } @@ -4241,26 +4123,24 @@ int32_t flecs_table_append( data->entities.count, ECS_INTERNAL_ERROR, NULL); } - ecs_table_ext_t *ext = table->ext; - if (ext) { - int32_t sw_count = ext->sw_count; - int32_t bs_count = ext->bs_count; - ecs_switch_t *sw_columns = ext->sw_columns; - ecs_bitset_t *bs_columns = ext->bs_columns; + ecs_table__t *meta = table->_; + int32_t sw_count = meta->sw_count; + int32_t bs_count = meta->bs_count; + ecs_switch_t *sw_columns = meta->sw_columns; + ecs_bitset_t *bs_columns = meta->bs_columns; - /* Add element to each switch column */ - for (i = 0; i < sw_count; i ++) { - ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = &sw_columns[i]; - flecs_switch_add(sw); - } + /* Add element to each switch column */ + for (i = 0; i < sw_count; i ++) { + ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &sw_columns[i]; + flecs_switch_add(sw); + } - /* Add element to each bitset column */ - for (i = 0; i < bs_count; i ++) { - ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &bs_columns[i]; - flecs_bitset_addn(bs, 1); - } + /* Add element to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, 1); } /* If this is the first entity in this table, signal queries so that the @@ -4274,6 +4154,7 @@ int32_t flecs_table_append( return count; } +/* Delete last operation for tables that don't have any complex logic */ static void flecs_table_fast_delete_last( ecs_vec_t *columns, @@ -4285,6 +4166,7 @@ void flecs_table_fast_delete_last( } } +/* Delete operation for tables that don't have any complex logic */ static void flecs_table_fast_delete( ecs_type_info_t **type_info, @@ -4300,6 +4182,7 @@ void flecs_table_fast_delete( } } +/* Delete entity from table */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, @@ -4308,7 +4191,7 @@ void flecs_table_delete( { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(!(table->flags & EcsTableHasTarget), ECS_INVALID_OPERATION, NULL); @@ -4380,7 +4263,7 @@ void flecs_table_delete( /* If table has component destructors, invoke */ if (destruct && (table->flags & EcsTableHasDtors)) { for (i = 0; i < column_count; i ++) { - flecs_run_remove_hooks(world, table, type_info[i], &columns[i], + flecs_table_invoke_remove_hooks(world, table, type_info[i], &columns[i], &entity_to_delete, ids[i], index, 1, true); } } @@ -4400,7 +4283,7 @@ void flecs_table_delete( ecs_iter_action_t on_remove = ti->hooks.on_remove; if (destruct && on_remove) { - flecs_on_component_callback(world, table, on_remove, EcsOnRemove, + flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entity_to_delete, ids[i], index, 1, ti); } @@ -4418,26 +4301,25 @@ void flecs_table_delete( } } - ecs_table_ext_t *ext = table->ext; - if (ext) { - /* Remove elements from switch columns */ - ecs_switch_t *sw_columns = ext->sw_columns; - int32_t sw_count = ext->sw_count; - for (i = 0; i < sw_count; i ++) { - flecs_switch_remove(&sw_columns[i], index); - } + /* Remove elements from switch columns */ + ecs_table__t *meta = table->_; + ecs_switch_t *sw_columns = meta->sw_columns; + int32_t sw_count = meta->sw_count; + for (i = 0; i < sw_count; i ++) { + flecs_switch_remove(&sw_columns[i], index); + } - /* Remove elements from bitset columns */ - ecs_bitset_t *bs_columns = ext->bs_columns; - int32_t bs_count = ext->bs_count; - for (i = 0; i < bs_count; i ++) { - flecs_bitset_remove(&bs_columns[i], index); - } + /* Remove elements from bitset columns */ + ecs_bitset_t *bs_columns = meta->bs_columns; + int32_t bs_count = meta->bs_count; + for (i = 0; i < bs_count; i ++) { + flecs_bitset_remove(&bs_columns[i], index); } flecs_table_check_sanity(table); } +/* Move operation for tables that don't have any complex logic */ static void flecs_table_fast_move( ecs_table_t *dst_table, @@ -4474,6 +4356,7 @@ void flecs_table_fast_move( } } +/* Move entity from src to dst table */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, @@ -4486,8 +4369,8 @@ void flecs_table_move( { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); @@ -4565,11 +4448,11 @@ void flecs_table_move( } } else { if (dst_id < src_id) { - flecs_run_add_hooks(world, dst_table, dst_type_info[i_new], + flecs_table_invoke_add_hooks(world, dst_table, dst_type_info[i_new], &dst_columns[i_new], &dst_entity, dst_id, dst_index, 1, construct); } else { - flecs_run_remove_hooks(world, src_table, src_type_info[i_old], + flecs_table_invoke_remove_hooks(world, src_table, src_type_info[i_old], &src_columns[i_old], &src_entity, src_id, src_index, 1, use_move_dtor); } @@ -4580,13 +4463,13 @@ void flecs_table_move( } for (; (i_new < dst_column_count); i_new ++) { - flecs_run_add_hooks(world, dst_table, dst_type_info[i_new], + flecs_table_invoke_add_hooks(world, dst_table, dst_type_info[i_new], &dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1, construct); } for (; (i_old < src_column_count); i_old ++) { - flecs_run_remove_hooks(world, src_table, src_type_info[i_old], + flecs_table_invoke_remove_hooks(world, src_table, src_type_info[i_old], &src_columns[i_old], &src_entity, src_ids[i_old], src_index, 1, use_move_dtor); } @@ -4595,6 +4478,7 @@ void flecs_table_move( flecs_table_check_sanity(src_table); } +/* Append n entities to table */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, @@ -4602,7 +4486,7 @@ int32_t flecs_table_appendn( int32_t to_add, const ecs_entity_t *ids) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); int32_t cur_count = flecs_table_data_count(data); @@ -4613,6 +4497,7 @@ int32_t flecs_table_appendn( return result; } +/* Set allocated table size */ void flecs_table_set_size( ecs_world_t *world, ecs_table_t *table, @@ -4620,7 +4505,7 @@ void flecs_table_set_size( int32_t size) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); @@ -4632,12 +4517,13 @@ void flecs_table_set_size( } } +/* Shrink table storage to fit number of entities */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); (void)world; flecs_table_check_sanity(table); @@ -4658,24 +4544,26 @@ bool flecs_table_shrink( return has_payload; } +/* Return number of entities in table */ int32_t flecs_table_data_count( const ecs_data_t *data) { return data ? data->entities.count : 0; } +/* Swap operation for switch (union relationship) columns */ static void flecs_table_swap_switch_columns( ecs_table_t *table, int32_t row_1, int32_t row_2) { - int32_t i = 0, column_count = table->ext->sw_count; + int32_t i = 0, column_count = table->_->sw_count; if (!column_count) { return; } - ecs_switch_t *columns = table->ext->sw_columns; + ecs_switch_t *columns = table->_->sw_columns; for (i = 0; i < column_count; i ++) { ecs_switch_t *sw = &columns[i]; @@ -4683,18 +4571,19 @@ void flecs_table_swap_switch_columns( } } +/* Swap operation for bitset (toggle component) columns */ static void flecs_table_swap_bitset_columns( ecs_table_t *table, int32_t row_1, int32_t row_2) { - int32_t i = 0, column_count = table->ext->bs_count; + int32_t i = 0, column_count = table->_->bs_count; if (!column_count) { return; } - ecs_bitset_t *columns = table->ext->bs_columns; + ecs_bitset_t *columns = table->_->bs_columns; for (i = 0; i < column_count; i ++) { ecs_bitset_t *bs = &columns[i]; @@ -4702,6 +4591,7 @@ void flecs_table_swap_bitset_columns( } } +/* Swap two rows in a table. Used for table sorting. */ void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, @@ -4710,7 +4600,7 @@ void flecs_table_swap( { (void)world; - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); @@ -4746,10 +4636,8 @@ void flecs_table_swap( records[row_1] = record_ptr_2; records[row_2] = record_ptr_1; - if (table->ext) { - flecs_table_swap_switch_columns(table, row_1, row_2); - flecs_table_swap_bitset_columns(table, row_1, row_2); - } + flecs_table_swap_switch_columns(table, row_1, row_2); + flecs_table_swap_bitset_columns(table, row_1, row_2); ecs_vec_t *columns = table->data.columns; if (!columns) { @@ -4788,8 +4676,9 @@ void flecs_table_swap( flecs_table_check_sanity(table); } +/* Merge data from one table column into other table column */ static -void flecs_merge_column( +void flecs_table_merge_column( ecs_world_t *world, ecs_vec_t *dst, ecs_vec_t *src, @@ -4841,8 +4730,9 @@ void flecs_merge_column( } } +/* Merge storage of two tables. */ static -void flecs_merge_table_data( +void flecs_table_merge_data( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table, @@ -4869,7 +4759,7 @@ void flecs_merge_table_data( } /* Merge entities */ - flecs_merge_column(world, &dst_data->entities, &src_data->entities, + flecs_table_merge_column(world, &dst_data->entities, &src_data->entities, ECS_SIZEOF(ecs_entity_t), 0, NULL); ecs_assert(dst_data->entities.count == src_count + dst_count, ECS_INTERNAL_ERROR, NULL); @@ -4877,7 +4767,7 @@ void flecs_merge_table_data( ecs_allocator_t *a = &world->allocator; /* Merge record pointers */ - flecs_merge_column(world, &dst_data->records, &src_data->records, + flecs_table_merge_column(world, &dst_data->records, &src_data->records, ECS_SIZEOF(ecs_record_t*), 0, NULL); for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { @@ -4888,7 +4778,7 @@ void flecs_merge_table_data( ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); if (dst_id == src_id) { - flecs_merge_column(world, &dst[i_new], &src[i_old], size, column_size, dst_ti); + flecs_table_merge_column(world, &dst[i_new], &src[i_old], size, column_size, dst_ti); flecs_table_mark_table_dirty(world, dst_table, i_new + 1); ecs_assert(dst[i_new].size == dst_data->entities.size, ECS_INTERNAL_ERROR, NULL); @@ -4900,13 +4790,13 @@ void flecs_merge_table_data( ecs_vec_t *column = &dst[i_new]; ecs_vec_set_size(a, column, size, column_size); ecs_vec_set_count(a, column, size, src_count + dst_count); - flecs_ctor_component(dst_ti, column, dst_count, src_count); + flecs_table_invoke_ctor(dst_ti, column, dst_count, src_count); i_new ++; } else if (dst_id > src_id) { /* Old column does not occur in new table, destruct */ ecs_vec_t *column = &src[i_old]; ecs_type_info_t *ti = src_type_info[i_old]; - flecs_dtor_component(ti, column, 0, src_count); + flecs_table_invoke_dtor(ti, column, 0, src_count); ecs_vec_fini(a, column, ti->size); i_old ++; } @@ -4923,14 +4813,14 @@ void flecs_merge_table_data( ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_set_size(a, column, size, column_size); ecs_vec_set_count(a, column, size, src_count + dst_count); - flecs_ctor_component(ti, column, dst_count, src_count); + flecs_table_invoke_ctor(ti, column, dst_count, src_count); } /* Destruct remaining columns */ for (; i_old < src_column_count; i_old ++) { ecs_vec_t *column = &src[i_old]; ecs_type_info_t *ti = src_type_info[i_old]; - flecs_dtor_component(ti, column, 0, src_count); + flecs_table_invoke_dtor(ti, column, 0, src_count); ecs_vec_fini(a, column, ti->size); } @@ -4938,13 +4828,9 @@ void flecs_merge_table_data( flecs_table_mark_table_dirty(world, dst_table, 0); } -int32_t ecs_table_count( - const ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return flecs_table_data_count(&table->data); -} - +/* Merge source table into destination table. This typically happens as result + * of a bulk operation, like when a component is removed from all entities in + * the source table (like for the Remove OnDelete policy). */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *dst_table, @@ -4953,7 +4839,7 @@ void flecs_table_merge( ecs_data_t *src_data) { ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); @@ -4966,7 +4852,7 @@ void flecs_table_merge( flecs_table_check_sanity(src_table); return; } else { - ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL); } /* If there is no data to merge, drop out */ @@ -5006,7 +4892,7 @@ void flecs_table_merge( if (move_data) { *dst_data = *src_data; } else { - flecs_merge_table_data(world, dst_table, src_table, src_count, dst_count, + flecs_table_merge_data(world, dst_table, src_table, src_count, dst_count, src_data, dst_data); } @@ -5016,15 +4902,16 @@ void flecs_table_merge( } flecs_table_set_empty(world, src_table); - flecs_table_observer_add(dst_table, src_table->observed_count); - flecs_table_observer_add(src_table, -src_table->observed_count); - ecs_assert(src_table->observed_count == 0, ECS_INTERNAL_ERROR, NULL); + flecs_table_traversable_add(dst_table, src_table->_->traversable_count); + flecs_table_traversable_add(src_table, -src_table->_->traversable_count); + ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); } flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); } +/* Replace data with other data. Used by snapshots to restore previous state. */ void flecs_table_replace_data( ecs_world_t *world, ecs_table_t *table, @@ -5033,7 +4920,7 @@ void flecs_table_replace_data( int32_t prev_count = 0; ecs_data_t *table_data = &table->data; ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); @@ -5058,24 +4945,7 @@ void flecs_table_replace_data( flecs_table_check_sanity(table); } -int32_t* flecs_table_get_dirty_state( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!table->dirty_state) { - int32_t column_count = table->storage_count; - table->dirty_state = flecs_alloc_n(&world->allocator, - int32_t, column_count + 1); - ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - for (int i = 0; i < column_count + 1; i ++) { - table->dirty_state[i] = 1; - } - } - return table->dirty_state; -} - +/* Internal mechanism for propagating information to tables */ void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, @@ -5094,13 +4964,15 @@ void flecs_table_notify( } } +/* -- Public API -- */ + void ecs_table_lock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { - table->lock ++; + table->_->lock ++; } } } @@ -5111,8 +4983,8 @@ void ecs_table_unlock( { if (table) { if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { - table->lock --; - ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); + table->_->lock --; + ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, NULL); } } } @@ -5141,6 +5013,13 @@ ecs_vec_t* ecs_table_column_for_id( return NULL; } +int32_t ecs_table_count( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_table_data_count(&table->data); +} + const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { @@ -5187,35 +5066,16 @@ int32_t flecs_table_column_to_union_index( const ecs_table_t *table, int32_t column) { - if (table->ext) { - int32_t sw_count = table->ext->sw_count; - if (sw_count) { - int32_t sw_offset = table->ext->sw_offset; - if (column >= sw_offset && column < (sw_offset + sw_count)){ - return column - sw_offset; - } + int32_t sw_count = table->_->sw_count; + if (sw_count) { + int32_t sw_offset = table->_->sw_offset; + if (column >= sw_offset && column < (sw_offset + sw_count)){ + return column - sw_offset; } } return -1; } -ecs_record_t* ecs_record_find( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_record_t *r = flecs_entities_get(world, entity); - if (r) { - return r; - } -error: - return NULL; -} - void* ecs_table_get_column( const ecs_table_t *table, int32_t index, @@ -5330,6 +5190,21 @@ error: return -1; } +void ecs_table_swap_rows( + ecs_world_t* world, + ecs_table_t* table, + int32_t row_1, + int32_t row_2) +{ + flecs_table_swap(world, table, row_1, row_2); +} + +int32_t flecs_table_observed_count( + const ecs_table_t *table) +{ + return table->_->traversable_count; +} + void* ecs_record_get_column( const ecs_record_t *r, int32_t column, @@ -5351,19 +5226,21 @@ error: return NULL; } -void ecs_table_swap_rows( - ecs_world_t* world, - ecs_table_t* table, - int32_t row_1, - int32_t row_2) +ecs_record_t* ecs_record_find( + const ecs_world_t *world, + ecs_entity_t entity) { - flecs_table_swap(world, table, row_1, row_2); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); -int32_t flecs_table_observed_count( - const ecs_table_t *table) -{ - return table->observed_count; + world = ecs_get_world(world); + + ecs_record_t *r = flecs_entities_get(world, entity); + if (r) { + return r; + } +error: + return NULL; } /** @@ -5935,7 +5812,7 @@ void flecs_instantiate_children( * instances don't all end up with the Union property. */ continue; } - ecs_table_record_t *tr = &child_table->records[i]; + ecs_table_record_t *tr = &child_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdDontInherit) { continue; @@ -6035,13 +5912,13 @@ void flecs_instantiate_children( /* If children have union relationships, initialize */ if (has_union) { - ecs_table_ext_t *ext = child_table->ext; - ecs_assert(ext != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(i_table->ext != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t u, u_count = ext->sw_count; + ecs_table__t *meta = child_table->_; + ecs_assert(meta != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(i_table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t u, u_count = meta->sw_count; for (u = 0; u < u_count; u ++) { - ecs_switch_t *src_sw = &ext->sw_columns[i]; - ecs_switch_t *dst_sw = &i_table->ext->sw_columns[i]; + ecs_switch_t *src_sw = &meta->sw_columns[i]; + ecs_switch_t *dst_sw = &i_table->_->sw_columns[i]; ecs_vec_t *v_src_values = flecs_switch_values(src_sw); ecs_vec_t *v_dst_values = flecs_switch_values(dst_sw); uint64_t *src_values = ecs_vec_first(v_src_values); @@ -6121,9 +5998,9 @@ void flecs_set_union( const ecs_table_record_t *tr = flecs_id_record_get_table( idr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->ext != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t column = tr->column - table->ext->sw_offset; - ecs_switch_t *sw = &table->ext->sw_columns[column]; + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t column = tr->column - table->_->sw_offset; + ecs_switch_t *sw = &table->_->sw_columns[column]; ecs_entity_t union_case = 0; union_case = ECS_PAIR_SECOND(id); @@ -6154,7 +6031,7 @@ void flecs_notify_on_add( flecs_set_union(world, table, row, count, added); } - if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|EcsTableHasObserved)) { + if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|EcsTableHasTraversable)) { flecs_emit(world, world, &(ecs_event_desc_t){ .event = EcsOnAdd, .ids = added, @@ -6181,7 +6058,7 @@ void flecs_notify_on_remove( ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); if (removed->count && (table->flags & - (EcsTableHasOnRemove|EcsTableHasUnSet|EcsTableHasIsA|EcsTableHasObserved))) + (EcsTableHasOnRemove|EcsTableHasUnSet|EcsTableHasIsA|EcsTableHasTraversable))) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnRemove, @@ -6195,6 +6072,57 @@ void flecs_notify_on_remove( } } +static +void flecs_update_name_index( + ecs_world_t *world, + ecs_table_t *src, + ecs_table_t *dst, + int32_t offset, + int32_t count) +{ + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + if (!(dst->flags & EcsTableHasName)) { + /* If destination table doesn't have a name, we don't need to update the + * name index. Even if the src table had a name, the on_remove hook for + * EcsIdentifier will remove the entity from the index. */ + return; + } + + ecs_hashmap_t *src_index = src->_->name_index; + ecs_hashmap_t *dst_index = dst->_->name_index; + if ((src_index == dst_index) || (!src_index && !dst_index)) { + /* If the name index didn't change, the entity still has the same parent + * so nothing needs to be done. */ + return; + } + + EcsIdentifier *names = ecs_table_get_pair(world, + dst, EcsIdentifier, EcsName, offset); + ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i; + ecs_entity_t *entities = ecs_vec_get_t( + &dst->data.entities, ecs_entity_t, offset); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + EcsIdentifier *name = &names[i]; + + uint64_t index_hash = name->index_hash; + if (index_hash) { + flecs_name_index_remove(src_index, e, index_hash); + } + const char *name_str = name->value; + if (name_str) { + ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); + + flecs_name_index_ensure( + dst_index, e, name_str, name->length, name->hash); + name->index = dst_index; + } + } +} + static ecs_record_t* flecs_new_entity( ecs_world_t *world, @@ -6229,7 +6157,7 @@ void flecs_move_entity( { ecs_table_t *src_table = record->table; int32_t src_row = ECS_RECORD_TO_ROW(record->row); - + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table->type.count > 0, ECS_INTERNAL_ERROR, NULL); @@ -6261,6 +6189,8 @@ void flecs_move_entity( flecs_notify_on_add( world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags); + flecs_update_name_index(world, src_table, dst_table, dst_row, 1); + error: return; } @@ -6331,12 +6261,10 @@ void flecs_commit( &diff->added, &diff->removed); ecs_table_t *src_table = NULL; - uint32_t row_flags = 0; - int observed = 0; + int is_trav = 0; if (record) { src_table = record->table; - row_flags = record->row & ECS_ROW_FLAGS_MASK; - observed = (row_flags & EcsEntityIsTraversable) != 0; + is_trav = (record->row & EcsEntityIsTraversable) != 0; } if (src_table == dst_table) { @@ -6353,7 +6281,7 @@ void flecs_commit( if (src_table) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_table_observer_add(dst_table, observed); + flecs_table_traversable_add(dst_table, is_trav); if (dst_table->type.count) { flecs_move_entity(world, entity, record, dst_table, diff, @@ -6363,9 +6291,9 @@ void flecs_commit( record->table = NULL; } - flecs_table_observer_add(src_table, -observed); + flecs_table_traversable_add(src_table, -is_trav); } else { - flecs_table_observer_add(dst_table, observed); + flecs_table_traversable_add(dst_table, is_trav); if (dst_table->type.count) { flecs_new_entity(world, entity, record, dst_table, diff, construct, evt_flags); @@ -6377,7 +6305,7 @@ void flecs_commit( * ensures that systems that rely on components from containers or prefabs * update the matched tables when the application adds or removes a * component from, for example, a container. */ - if (row_flags) { + if (is_trav) { flecs_update_component_monitors(world, &diff->added, &diff->removed); } @@ -6457,8 +6385,10 @@ const ecs_entity_t* flecs_bulk_new( ecs_entity_t id = component_ids->array[c_i]; const ecs_table_record_t *tr = flecs_table_record_get( world, storage_table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, + "id is not a component"); + ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, + "ids cannot be wildcards"); int32_t index = tr->column; ecs_type_info_t *ti = table->type_info[index]; @@ -6690,27 +6620,30 @@ void flecs_notify_on_set( } } -ecs_record_t* flecs_add_flag( - ecs_world_t *world, - ecs_entity_t entity, +void flecs_record_add_flag( + ecs_record_t *record, uint32_t flag) { - ecs_record_t *record = flecs_entities_get(world, entity); - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - if (flag == EcsEntityIsTraversable) { if (!(record->row & flag)) { ecs_table_t *table = record->table; if (table) { - flecs_table_observer_add(table, 1); + flecs_table_traversable_add(table, 1); } } } record->row |= flag; - - return record; } +void flecs_add_flag( + ecs_world_t *world, + ecs_entity_t entity, + uint32_t flag) +{ + ecs_record_t *record = flecs_entities_get_any(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(record, flag); +} /* -- Public functions -- */ @@ -6723,6 +6656,7 @@ bool ecs_commit( const ecs_type_t *removed) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); ecs_table_t *src_table = NULL; if (!record) { @@ -6736,10 +6670,12 @@ bool ecs_commit( diff.added = *added; } if (removed) { - diff.added = *removed; + diff.removed = *removed; } - + + ecs_defer_begin(world); flecs_commit(world, entity, record, table, &diff, true, 0); + ecs_defer_end(world); return src_table != table; error: @@ -7595,7 +7531,7 @@ void ecs_clear( r->table = NULL; if (r->row & EcsEntityIsTraversable) { - flecs_table_observer_add(table, -1); + flecs_table_traversable_add(table, -1); } } @@ -7713,7 +7649,7 @@ ecs_entity_t flecs_get_delete_action( if (!result && delete_target) { /* If action is not specified and we're deleting a relationship target, * derive the action from the current record */ - ecs_table_record_t *trr = &table->records[tr->column]; + ecs_table_record_t *trr = &table->_->records[tr->column]; ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; result = ECS_ID_ON_DELETE_OBJECT(idrr->flags); } @@ -7959,6 +7895,7 @@ bool flecs_on_delete_clear_ids( int32_t i, count = ecs_vec_count(&world->store.marked_ids); ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); int twice = 2; + do { for (i = 0; i < count; i ++) { /* Release normal ids before wildcard ids */ @@ -7978,22 +7915,22 @@ bool flecs_on_delete_clear_ids( flecs_id_record_release_tables(world, idr); /* Release the claim taken by flecs_marked_id_push. This may delete the - * id record as all other claims may have been released. */ + * id record as all other claims may have been released. */ int32_t rc = flecs_id_record_release(world, idr); ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); (void)rc; /* If rc is 0, the id was likely deleted by a nested delete_with call - * made by an on_remove handler/OnRemove observer */ + * made by an on_remove handler/OnRemove observer */ if (rc) { if (delete_id) { /* If id should be deleted, release initial claim. This happens when - * a component, tag, or part of a pair is deleted. */ + * a component, tag, or part of a pair is deleted. */ flecs_id_record_release(world, idr); } else { /* If id should not be deleted, unmark id record for deletion. This - * happens when all instances *of* an id are deleted, for example - * when calling ecs_remove_all or ecs_delete_with. */ + * happens when all instances *of* an id are deleted, for example + * when calling ecs_remove_all or ecs_delete_with. */ idr->flags &= ~EcsIdMarkedForDelete; } } @@ -8118,7 +8055,7 @@ void ecs_delete( if (row_flags & EcsEntityIsTraversable) { table = r->table; if (table) { - flecs_table_observer_add(table, -1); + flecs_table_traversable_add(table, -1); } } /* Merge operations before deleting entity */ @@ -8321,7 +8258,7 @@ ecs_record_t* flecs_access_begin( return NULL; } - int32_t count = ecs_os_ainc(&table->lock); + int32_t count = ecs_os_ainc(&table->_->lock); (void)count; if (write) { ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); @@ -8340,7 +8277,7 @@ void flecs_access_end( ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t count = ecs_os_adec(&r->table->lock); + int32_t count = ecs_os_adec(&r->table->_->lock); (void)count; if (write) { ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); @@ -8771,12 +8708,12 @@ void ecs_enable_id( return; } - ecs_assert(table->ext != NULL, ECS_INTERNAL_ERROR, NULL); - index -= table->ext->bs_offset; + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Data cannot be NULl, since entity is stored in the table */ - ecs_bitset_t *bs = &table->ext->bs_columns[index]; + ecs_bitset_t *bs = &table->_->bs_columns[index]; ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); @@ -8811,10 +8748,10 @@ bool ecs_is_enabled_id( return ecs_has_id(world, entity, id); } - ecs_assert(table->ext != NULL, ECS_INTERNAL_ERROR, NULL); - index -= table->ext->bs_offset; + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &table->ext->bs_columns[index]; + ecs_bitset_t *bs = &table->_->bs_columns[index]; return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: @@ -8865,9 +8802,9 @@ bool ecs_has_id( ECS_PAIR_SECOND(id) != EcsWildcard) { if (ECS_PAIR_FIRST(table->type.array[column]) == EcsUnion) { - ecs_assert(table->ext != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = &table->ext->sw_columns[ - column - table->ext->sw_offset]; + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &table->_->sw_columns[ + column - table->_->sw_offset]; int32_t row = ECS_RECORD_TO_ROW(r->row); uint64_t value = flecs_switch_get(sw, row); return value == ECS_PAIR_SECOND(id); @@ -8917,9 +8854,9 @@ ecs_entity_t ecs_get_target( wc = ecs_pair(EcsUnion, rel); tr = flecs_table_record_get(world, table, wc); if (tr) { - ecs_assert(table->ext != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = &table->ext->sw_columns[ - tr->column - table->ext->sw_offset]; + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &table->_->sw_columns[ + tr->column - table->_->sw_offset]; int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_switch_get(sw, row); @@ -9214,7 +9151,7 @@ void ecs_flatten( const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; - if (!table->observed_count) { + if (!table->_->traversable_count) { continue; } @@ -9323,7 +9260,7 @@ ecs_entity_t ecs_set_name( const char *name) { if (!entity) { - return ecs_entity_init(world, &(ecs_entity_desc_t){ + return ecs_entity(world, { .name = name }); } @@ -9686,7 +9623,8 @@ int32_t ecs_count_id( int32_t count = 0; ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { .id = id, - .src.flags = EcsSelf + .src.flags = EcsSelf, + .flags = EcsTermMatchDisabled|EcsTermMatchPrefab }); it.flags |= EcsIterNoData; @@ -11054,6 +10992,7 @@ void ecs_set_stage_count( /* Set thread_ctx to stage, as this stage might be used in a * multithreaded context */ stage->thread_ctx = (ecs_world_t*)stage; + stage->thread = 0; } } else { /* Set to NULL to prevent double frees */ @@ -13241,31 +13180,6 @@ uint64_t flecs_hash( return wyhash(data, flecs_ito(size_t, length), 0, _wyp); } -/** - * @file datastructures/qsort.c - * @brief Quicksort implementation. - */ - - -void ecs_qsort( - void *base, - ecs_size_t nitems, - ecs_size_t size, - int (*compar)(const void *, const void*)) -{ - void *tmp = ecs_os_alloca(size); /* For swap */ - - #define LESS(i, j) \ - compar(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j)) < 0 - - #define SWAP(i, j) \ - ecs_os_memcpy(tmp, ECS_ELEM(base, size, i), size),\ - ecs_os_memcpy(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j), size),\ - ecs_os_memcpy(ECS_ELEM(base, size, j), tmp, size) - - QSORT(nitems, LESS, SWAP); -} - /** * @file datastructures/bitset.c * @brief Bitset data structure. @@ -15649,7 +15563,7 @@ int flecs_entity_filter_find_smallest_term( ecs_table_t *table, ecs_entity_filter_iter_t *iter) { - ecs_assert(table->ext != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); flecs_switch_term_t *sw_terms = ecs_vec_first(&iter->entity_filter->sw_terms); int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); int32_t min = INT_MAX, index = 0; @@ -15668,12 +15582,12 @@ int flecs_entity_filter_find_smallest_term( sparse_column->signature_column_index]; /* Translate the table column index to switch column index */ - table_column_index -= table->ext->sw_offset; + table_column_index -= table->_->sw_offset; ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); /* Get the sparse column */ sw = sparse_column->sw_column = - &table->ext->sw_columns[table_column_index - 1]; + &table->_->sw_columns[table_column_index - 1]; } /* Find the smallest column */ @@ -15823,7 +15737,7 @@ int flecs_entity_filter_bitset_next( int32_t i, count = ecs_vec_count(&iter->entity_filter->bs_terms); flecs_bitset_term_t *terms = ecs_vec_first(&iter->entity_filter->bs_terms); - int32_t bs_offset = table->ext->bs_offset; + int32_t bs_offset = table->_->bs_offset; int32_t first = iter->bs_offset; int32_t last = 0; @@ -15834,7 +15748,7 @@ int flecs_entity_filter_bitset_next( if (!bs) { int32_t index = column->column_index; ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); - bs = &table->ext->bs_columns[index - bs_offset]; + bs = &table->_->bs_columns[index - bs_offset]; terms[i].bs_column = bs; } @@ -15985,7 +15899,7 @@ int32_t flecs_get_flattened_target( } if (table->flags & EcsTableHasTarget) { - int32_t col = table->storage_map[table->ext->ft_offset]; + int32_t col = table->storage_map[table->_->ft_offset]; ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); EcsTarget *next = table->data.columns[col].array; next = ECS_ELEM_T(next, EcsTarget, ECS_RECORD_TO_ROW(r->row)); @@ -15999,7 +15913,7 @@ int32_t flecs_get_flattened_target( void flecs_entity_filter_init( ecs_world_t *world, - ecs_entity_filter_t *entity_filter, + ecs_entity_filter_t **entity_filter, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, @@ -16012,16 +15926,23 @@ void flecs_entity_filter_init( ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &world->allocator; - ecs_vec_t *sw_terms = &entity_filter->sw_terms; - ecs_vec_t *bs_terms = &entity_filter->bs_terms; - ecs_vec_t *ft_terms = &entity_filter->ft_terms; + ecs_entity_filter_t ef; + ecs_os_zeromem(&ef); + ecs_vec_t *sw_terms = &ef.sw_terms; + ecs_vec_t *bs_terms = &ef.bs_terms; + ecs_vec_t *ft_terms = &ef.ft_terms; + if (*entity_filter) { + ef.sw_terms = (*entity_filter)->sw_terms; + ef.bs_terms = (*entity_filter)->bs_terms; + ef.ft_terms = (*entity_filter)->ft_terms; + } ecs_vec_reset_t(a, sw_terms, flecs_switch_term_t); ecs_vec_reset_t(a, bs_terms, flecs_bitset_term_t); ecs_vec_reset_t(a, ft_terms, flecs_flat_table_term_t); ecs_term_t *terms = filter->terms; int32_t i, term_count = filter->term_count; - entity_filter->has_filter = false; - entity_filter->flat_tree_column = -1; + bool has_filter = false; + ef.flat_tree_column = -1; /* Look for union fields */ if (table->flags & EcsTableHasUnion) { @@ -16052,7 +15973,7 @@ void flecs_entity_filter_init( el->sw_case = ECS_PAIR_SECOND(id); el->sw_column = NULL; ids[field] = id; - entity_filter->has_filter = true; + has_filter = true; } } @@ -16073,7 +15994,7 @@ void flecs_entity_filter_init( flecs_bitset_term_t); bc->column_index = bs_index; bc->bs_column = NULL; - entity_filter->has_filter = true; + has_filter = true; } } } @@ -16093,10 +16014,10 @@ void flecs_entity_filter_init( } if (terms[i].src.trav == rel) { - entity_filter->flat_tree_column = table->storage_map[column]; - ecs_assert(entity_filter->flat_tree_column != -1, + ef.flat_tree_column = table->storage_map[column]; + ecs_assert(ef.flat_tree_column != -1, ECS_INTERNAL_ERROR, NULL); - entity_filter->has_filter = true; + has_filter = true; flecs_flat_table_term_t *term = ecs_vec_append_t( a, ft_terms, flecs_flat_table_term_t); @@ -16106,12 +16027,24 @@ void flecs_entity_filter_init( } } } + + if (has_filter) { + if (!*entity_filter) { + *entity_filter = ecs_os_malloc_t(ecs_entity_filter_t); + } + ecs_assert(*entity_filter != NULL, ECS_OUT_OF_MEMORY, NULL); + **entity_filter = ef; + } } void flecs_entity_filter_fini( ecs_world_t *world, ecs_entity_filter_t *ef) { + if (!ef) { + return; + } + ecs_allocator_t *a = &world->allocator; flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); @@ -16123,6 +16056,7 @@ void flecs_entity_filter_fini( ecs_vec_fini_t(a, &ef->sw_terms, flecs_switch_term_t); ecs_vec_fini_t(a, &ef->bs_terms, flecs_bitset_term_t); ecs_vec_fini_t(a, &ef->ft_terms, flecs_flat_table_term_t); + ecs_os_free(ef); } int flecs_entity_filter_next( @@ -16815,7 +16749,7 @@ int ecs_log_last_error(void) */ /** - * @file addons/system/system.c + * @file addons/system/system.h * @brief Internal types and functions for system addon. */ @@ -16970,6 +16904,12 @@ void flecs_workers_progress( ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); +void flecs_create_worker_threads( + ecs_world_t *world); + +bool flecs_join_worker_threads( + ecs_world_t *world); + #endif @@ -17041,18 +16981,14 @@ bool flecs_is_main_thread( } /* Start threads */ -static -void flecs_start_workers( - ecs_world_t *world, - int32_t threads) +void flecs_create_worker_threads( + ecs_world_t *world) { - ecs_set_stage_count(world, threads); + ecs_poly_assert(world, ecs_world_t); + int32_t stages = ecs_get_stage_count(world); - ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); - - int32_t i; - for (i = 0; i < threads - 1; i ++) { - ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i + 1); + for (int32_t i = 1; i < stages; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); ecs_poly_assert(stage, ecs_stage_t); @@ -17066,11 +17002,37 @@ void flecs_start_workers( ecs_worker_state_t *state = ecs_os_calloc_t(ecs_worker_state_t); state->stage = stage; state->pq = pq; - stage->thread = ecs_os_thread_new(flecs_worker, state); + + ecs_assert(stage->thread == 0, ECS_INTERNAL_ERROR, NULL); + if (ecs_using_task_threads(world)) + { + /* workers are using tasks in an external task manager provided to the OS API */ + stage->thread = ecs_os_task_new(flecs_worker, state); + } + else + { + /* workers are using long-running os threads */ + stage->thread = ecs_os_thread_new(flecs_worker, state); + } ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); } } +static +void flecs_start_workers( + ecs_world_t *world, + int32_t threads) +{ + ecs_set_stage_count(world, threads); + + ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_using_task_threads(world)) + { + flecs_create_worker_threads(world); + } +} + /* Wait until all workers are running */ static void flecs_wait_for_workers( @@ -17157,12 +17119,11 @@ void flecs_signal_workers( ecs_os_cond_broadcast(world->worker_cond); ecs_os_mutex_unlock(world->sync_mutex); } - -/** Stop workers */ -static -bool ecs_stop_threads( + +bool flecs_join_worker_threads( ecs_world_t *world) { + ecs_poly_assert(world, ecs_world_t); bool threads_active = false; /* Test if threads are created. Cannot use workers_running, since this is @@ -17192,19 +17153,41 @@ bool ecs_stop_threads( /* Join all threads with main */ for (i = 1; i < count; i ++) { - ecs_os_thread_join(stages[i].thread); + if (ecs_using_task_threads(world)) + { + /* Join using the override provided */ + ecs_os_task_join(stages[i].thread); + } + else + { + ecs_os_thread_join(stages[i].thread); + } stages[i].thread = 0; } world->flags &= ~EcsWorldQuitWorkers; ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); - /* Deinitialize stages */ - ecs_set_stage_count(world, 1); - return true; } +/** Stop workers */ +static +bool ecs_stop_threads( + ecs_world_t *world) +{ + /* Join all existing worker threads */ + if (flecs_join_worker_threads(world)) + { + /* Deinitialize stages */ + ecs_set_stage_count(world, 1); + + return true; + } + + return false; +} + /* -- Private functions -- */ bool flecs_worker_begin( ecs_world_t *world, @@ -17315,17 +17298,18 @@ void flecs_workers_progress( ecs_set_scope((ecs_world_t*)stage, old_scope); } -/* -- Public functions -- */ - -void ecs_set_threads( +static +void flecs_set_threads_internal( ecs_world_t *world, - int32_t threads) + int32_t threads, + bool use_task_api) { - ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - + ecs_assert(threads <= 1 || (use_task_api ? ecs_os_has_task_support() : ecs_os_has_threading()), ECS_MISSING_OS_API, NULL); + int32_t stage_count = ecs_get_stage_count(world); - - if (stage_count != threads) { + bool worker_method_changed = (use_task_api != world->workers_use_task_api); + + if (stage_count != threads || worker_method_changed) { /* Stop existing threads */ if (stage_count > 1) { if (ecs_stop_threads(world)) { @@ -17335,6 +17319,9 @@ void ecs_set_threads( } } + /* Adopt the desired API for worker creation & join */ + world->workers_use_task_api = use_task_api; + /* Start threads if number of threads > 1 */ if (threads > 1) { world->worker_cond = ecs_os_cond_new(); @@ -17345,6 +17332,28 @@ void ecs_set_threads( } } +/* -- Public functions -- */ + +void ecs_set_threads( + ecs_world_t *world, + int32_t threads) +{ + flecs_set_threads_internal(world, threads, false /* use thread API*/); +} + +void ecs_set_task_threads( + ecs_world_t *world, + int32_t task_threads) +{ + flecs_set_threads_internal(world, task_threads, true /* use task API*/); +} + +bool ecs_using_task_threads( + ecs_world_t *world) +{ + return world->workers_use_task_api; +} + #endif /** @@ -18019,6 +18028,12 @@ bool ecs_progress( flecs_run_startup_systems(world); } + /* create any worker task threads request */ + if (ecs_using_task_threads(world)) + { + flecs_create_worker_threads(world); + } + ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); ecs_log_push_3(); const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); @@ -18028,6 +18043,12 @@ bool ecs_progress( ecs_frame_end(world); + if (ecs_using_task_threads(world)) + { + /* task threads were temporary and may now be joined */ + flecs_join_worker_threads(world); + } + return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return false; @@ -18186,7 +18207,7 @@ void FlecsPipelineImport( .move = ecs_move(EcsPipeline) }); - world->pipeline = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ + world->pipeline = ecs_pipeline(world, { .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), .query = { .filter.terms = { @@ -18216,6 +18237,7 @@ void FlecsPipelineImport( ECS_COMPONENT_DECLARE(FlecsMonitor); ECS_COMPONENT_DECLARE(EcsWorldStats); +ECS_COMPONENT_DECLARE(EcsWorldSummary); ECS_COMPONENT_DECLARE(EcsPipelineStats); ecs_entity_t EcsPeriod1s = 0; @@ -18245,6 +18267,26 @@ ECS_DTOR(EcsPipelineStats, ptr, { ecs_pipeline_stats_fini(&ptr->stats); }) +static +void UpdateWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 1); + + const ecs_world_info_t *info = ecs_get_world_info(it->world); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + summary[i].target_fps = (double)info->target_fps; + + summary[i].frame_time_last = (double)info->frame_time_total - summary[i].frame_time_total; + summary[i].system_time_last = (double)info->system_time_total - summary[i].system_time_total; + summary[i].merge_time_last = (double)info->merge_time_total - summary[i].merge_time_total; + + summary[i].frame_time_total = (double)info->frame_time_total; + summary[i].system_time_total = (double)info->system_time_total; + summary[i].merge_time_total = (double)info->merge_time_total; + } +} + static void MonitorStats(ecs_iter_t *it) { ecs_world_t *world = it->real_world; @@ -18387,7 +18429,7 @@ void flecs_stats_monitor_import( ecs_entity_t prev = ecs_set_scope(world, kind); // Called each frame, collects 60 measurements per second - ecs_system_init(world, &(ecs_system_desc_t){ + ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1s", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1s), @@ -18397,7 +18439,7 @@ void flecs_stats_monitor_import( }); // Called each second, reduces into 60 measurements per minute - ecs_entity_t mw1m = ecs_system_init(world, &(ecs_system_desc_t){ + ecs_entity_t mw1m = ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1m", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1m), @@ -18411,7 +18453,7 @@ void flecs_stats_monitor_import( }); // Called each minute, reduces into 60 measurements per hour - ecs_system_init(world, &(ecs_system_desc_t){ + ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1h", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1h), @@ -18426,7 +18468,7 @@ void flecs_stats_monitor_import( }); // Called each minute, reduces into 60 measurements per day - ecs_system_init(world, &(ecs_system_desc_t){ + ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1d", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1d), @@ -18442,7 +18484,7 @@ void flecs_stats_monitor_import( }); // Called each hour, reduces into 60 measurements per week - ecs_system_init(world, &(ecs_system_desc_t){ + ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1w", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1w), @@ -18499,6 +18541,9 @@ void FlecsMonitorImport( ECS_MODULE_DEFINE(world, FlecsMonitor); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsTimer); +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif ecs_set_name_prefix(world, "Ecs"); @@ -18508,6 +18553,35 @@ void FlecsMonitorImport( EcsPeriod1d = ecs_new_entity(world, "EcsPeriod1d"); EcsPeriod1w = ecs_new_entity(world, "EcsPeriod1w"); + ECS_COMPONENT_DEFINE(world, EcsWorldSummary); + +#ifdef FLECS_META + ecs_struct(world, { + .entity = ecs_id(EcsWorldSummary), + .members = { + { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, + { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds } + } + }); +#endif + + ecs_system(world, { + .entity = ecs_entity(world, { + .name = "UpdateWorldSummary", + .add = {ecs_dependson(EcsPreFrame)} + }), + .query.filter.terms[0] = { .id = ecs_id(EcsWorldSummary) }, + .callback = UpdateWorldSummary + }); + + ECS_SYSTEM(world, UpdateWorldSummary, EcsPreFrame, WorldSummary); + ecs_set(world, EcsWorld, EcsWorldSummary, {0}); + flecs_world_monitor_import(world); flecs_pipeline_monitor_import(world); @@ -18774,7 +18848,7 @@ void FlecsTimerImport( flecs_bootstrap_component(world, EcsRateFilter); /* Add EcsTickSource to timers and rate filters */ - ecs_system_init(world, &(ecs_system_desc_t){ + ecs_system(world, { .entity = ecs_entity(world, {.name = "AddTickSource", .add = { ecs_dependson(EcsPreFrame) }}), .query.filter.terms = { { .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn }, @@ -18785,7 +18859,7 @@ void FlecsTimerImport( }); /* Timer handling */ - ecs_system_init(world, &(ecs_system_desc_t){ + ecs_system(world, { .entity = ecs_entity(world, {.name = "ProgressTimers", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsTimer) }, @@ -18795,7 +18869,7 @@ void FlecsTimerImport( }); /* Rate filter handling */ - ecs_system_init(world, &(ecs_system_desc_t){ + ecs_system(world, { .entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, @@ -18805,7 +18879,7 @@ void FlecsTimerImport( }); /* TickSource without a timer or rate filter just increases each frame */ - ecs_system_init(world, &(ecs_system_desc_t){ + ecs_system(world, { .entity = ecs_entity(world, { .name = "ProgressTickSource", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsTickSource), .inout = EcsOut }, @@ -19274,7 +19348,7 @@ ecs_entity_t ecs_cpp_enum_constant_register( } ecs_entity_t prev = ecs_set_scope(world, parent); - id = ecs_entity_init(world, &(ecs_entity_desc_t){ + id = ecs_entity(world, { .id = id, .name = name }); @@ -19311,6 +19385,767 @@ int32_t ecs_cpp_reset_count_inc(void) { return ++flecs_reset_count; } +#ifdef FLECS_META +const ecs_member_t* ecs_cpp_last_member( + const ecs_world_t *world, + ecs_entity_t type) +{ + const EcsStruct *st = ecs_get(world, type, EcsStruct); + if (!st) { + char *type_str = ecs_get_fullpath(world, type); + ecs_err("entity '%s' is not a struct", type_str); + ecs_os_free(type_str); + return 0; + } + + ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t, + ecs_vec_count(&st->members) - 1); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + return m; +} +#endif + +#endif + +/** + * @file addons/alerts.c + * @brief Alerts addon. + */ + + +#ifdef FLECS_ALERTS + +ECS_COMPONENT_DECLARE(FlecsAlerts); + +typedef struct EcsAlert { + char *message; + ecs_map_t instances; /* Active instances for metric */ + ecs_ftime_t retain_period; /* How long to retain the alert */ + ecs_vec_t severity_filters; /* Severity filters */ + + /* Member range monitoring */ + ecs_id_t id; /* (Component) id that contains to monitor member */ + ecs_entity_t member; /* Member to monitor */ + int32_t offset; /* Offset of member in component */ + int32_t size; /* Size of component */ + ecs_primitive_kind_t kind; /* Primitive type kind */ + ecs_ref_t ranges; /* Reference to ranges component */ + int32_t var_id; /* Variable from which to obtain data (0 = $this) */ +} EcsAlert; + +typedef struct EcsAlertTimeout { + ecs_ftime_t inactive_time; /* Time the alert has been inactive */ + ecs_ftime_t expire_time; /* Expiration duration */ +} EcsAlertTimeout; + +ECS_COMPONENT_DECLARE(EcsAlertTimeout); + +static +ECS_CTOR(EcsAlert, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->instances, NULL); + ecs_vec_init_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t, 0); +}) + +static +ECS_DTOR(EcsAlert, ptr, { + ecs_os_free(ptr->message); + ecs_map_fini(&ptr->instances); + ecs_vec_fini_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t); +}) + +static +ECS_MOVE(EcsAlert, dst, src, { + ecs_os_free(dst->message); + dst->message = src->message; + src->message = NULL; + + ecs_map_fini(&dst->instances); + dst->instances = src->instances; + src->instances = (ecs_map_t){0}; + + ecs_vec_fini_t(NULL, &dst->severity_filters, ecs_alert_severity_filter_t); + dst->severity_filters = src->severity_filters; + src->severity_filters = (ecs_vec_t){0}; + + dst->retain_period = src->retain_period; + dst->id = src->id; + dst->member = src->member; + dst->offset = src->offset; + dst->size = src->size; + dst->kind = src->kind; + dst->ranges = src->ranges; + dst->var_id = src->var_id; +}) + +static +ECS_CTOR(EcsAlertsActive, ptr, { + ecs_map_init(&ptr->alerts, NULL); +}) + +static +ECS_DTOR(EcsAlertsActive, ptr, { + ecs_map_fini(&ptr->alerts); +}) + +static +ECS_MOVE(EcsAlertsActive, dst, src, { + ecs_map_fini(&dst->alerts); + dst->alerts = src->alerts; + src->alerts = (ecs_map_t){0}; +}) + +static +ECS_DTOR(EcsAlertInstance, ptr, { + ecs_os_free(ptr->message); +}) + +static +ECS_MOVE(EcsAlertInstance, dst, src, { + ecs_os_free(dst->message); + dst->message = src->message; + src->message = NULL; +}) + +static +ECS_COPY(EcsAlertInstance, dst, src, { + ecs_os_free(dst->message); + dst->message = ecs_os_strdup(src->message); +}) + +static +void flecs_alerts_add_alert_to_src( + ecs_world_t *world, + ecs_entity_t source, + ecs_entity_t alert, + ecs_entity_t alert_instance) +{ + EcsAlertsActive *active = ecs_get_mut( + world, source, EcsAlertsActive); + ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ptr[0] = alert_instance; + ecs_modified(world, source, EcsAlertsActive); +} + +static +void flecs_alerts_remove_alert_from_src( + ecs_world_t *world, + ecs_entity_t source, + ecs_entity_t alert) +{ + EcsAlertsActive *active = ecs_get_mut( + world, source, EcsAlertsActive); + ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_remove(&active->alerts, alert); + + if (!ecs_map_count(&active->alerts)) { + ecs_remove(world, source, EcsAlertsActive); + } else { + ecs_modified(world, source, EcsAlertsActive); + } +} + +static +ecs_entity_t flecs_alert_get_severity( + ecs_world_t *world, + ecs_iter_t *it, + EcsAlert *alert) +{ + int32_t i, filter_count = ecs_vec_count(&alert->severity_filters); + ecs_alert_severity_filter_t *filters = + ecs_vec_first(&alert->severity_filters); + for (i = 0; i < filter_count; i ++) { + ecs_alert_severity_filter_t *filter = &filters[i]; + if (!filter->var) { + if (ecs_table_has_id(world, it->table, filters[i].with)) { + return filters[i].severity; + } + } else { + ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index); + if (src && src != EcsWildcard) { + if (ecs_has_id(world, src, filters[i].with)) { + return filters[i].severity; + } + } + } + } + + return 0; +} + +static +ecs_entity_t flecs_alert_out_of_range_kind( + EcsAlert *alert, + const EcsMemberRanges *ranges, + const void *value_ptr) +{ + double value; + + switch(alert->kind) { + case EcsU8: value = *(uint8_t*)value_ptr; break; + case EcsU16: value = *(uint16_t*)value_ptr; break; + case EcsU32: value = *(uint32_t*)value_ptr; break; + case EcsU64: value = (double)*(uint64_t*)value_ptr; break; + case EcsI8: value = *(int8_t*)value_ptr; break; + case EcsI16: value = *(int16_t*)value_ptr; break; + case EcsI32: value = *(int32_t*)value_ptr; break; + case EcsI64: value = (double)*(int64_t*)value_ptr; break; + case EcsF32: value = (double)*(float*)value_ptr; break; + case EcsF64: value = *(double*)value_ptr; break; + default: return 0; + } + + bool has_error = ranges->error.min != ranges->error.max; + bool has_warning = ranges->warning.min != ranges->warning.max; + + if (has_error && (value < ranges->error.min || value > ranges->error.max)) { + return EcsAlertError; + } else if (has_warning && + (value < ranges->warning.min || value > ranges->warning.max)) + { + return EcsAlertWarning; + } else { + return 0; + } +} + +static +void MonitorAlerts(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsAlert *alert = ecs_field(it, EcsAlert, 1); + EcsPoly *poly = ecs_field(it, EcsPoly, 2); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t a = it->entities[i]; /* Alert entity */ + ecs_entity_t default_severity = ecs_get_target( + world, a, ecs_id(EcsAlert), 0); + ecs_rule_t *rule = poly[i].poly; + ecs_poly_assert(rule, ecs_rule_t); + + ecs_id_t member_id = alert[i].id; + const EcsMemberRanges *ranges = NULL; + if (member_id) { + ranges = ecs_ref_get(world, &alert[i].ranges, EcsMemberRanges); + } + + ecs_iter_t rit = ecs_rule_iter(world, rule); + rit.flags |= EcsIterNoData; + rit.flags |= EcsIterIsInstanced; + + while (ecs_rule_next(&rit)) { + ecs_entity_t severity = flecs_alert_get_severity( + world, &rit, &alert[i]); + if (!severity) { + severity = default_severity; + } + + const void *member_data = NULL; + ecs_entity_t member_src = 0; + if (ranges) { + if (alert[i].var_id) { + member_src = ecs_iter_get_var(&rit, alert[i].var_id); + if (!member_src || member_src == EcsWildcard) { + continue; + } + } + if (!member_src) { + member_data = ecs_table_get_id( + world, rit.table, member_id, rit.offset); + } else { + member_data = ecs_get_id(world, member_src, member_id); + } + if (!member_data) { + continue; + } + member_data = ECS_OFFSET(member_data, alert[i].offset); + } + + int32_t j, alert_src_count = rit.count; + for (j = 0; j < alert_src_count; j ++) { + ecs_entity_t src_severity = severity; + ecs_entity_t e = rit.entities[j]; + if (member_data) { + ecs_entity_t range_severity = flecs_alert_out_of_range_kind( + &alert[i], ranges, member_data); + if (!member_src) { + member_data = ECS_OFFSET(member_data, alert[i].size); + } + if (!range_severity) { + continue; + } + if (range_severity < src_severity) { + /* Range severity should not exceed alert severity */ + src_severity = range_severity; + } + } + + ecs_entity_t *aptr = ecs_map_ensure(&alert[i].instances, e); + ecs_assert(aptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (!aptr[0]) { + /* Alert does not yet exist for entity */ + ecs_entity_t ai = ecs_new_w_pair(world, EcsChildOf, a); + ecs_set(world, ai, EcsAlertInstance, { .message = NULL }); + ecs_set(world, ai, EcsMetricSource, { .entity = e }); + ecs_set(world, ai, EcsMetricValue, { .value = 0 }); + ecs_add_pair(world, ai, ecs_id(EcsAlert), src_severity); + if (alert[i].retain_period != 0) { + ecs_set(world, ai, EcsAlertTimeout, { + .inactive_time = 0, + .expire_time = alert[i].retain_period + }); + } + + ecs_defer_suspend(it->world); + flecs_alerts_add_alert_to_src(world, e, a, ai); + ecs_defer_resume(it->world); + aptr[0] = ai; + } else { + /* Make sure alert severity is up to date */ + if (ecs_vec_count(&alert[i].severity_filters) || member_data) { + ecs_entity_t cur_severity = ecs_get_target( + world, aptr[0], ecs_id(EcsAlert), 0); + if (cur_severity != src_severity) { + ecs_add_pair(world, aptr[0], ecs_id(EcsAlert), + src_severity); + } + } + } + } + } + } +} + +static +void MonitorAlertInstances(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsAlertInstance *alert_instance = ecs_field(it, EcsAlertInstance, 1); + EcsMetricSource *source = ecs_field(it, EcsMetricSource, 2); + EcsMetricValue *value = ecs_field(it, EcsMetricValue, 3); + EcsAlertTimeout *timeout = ecs_field(it, EcsAlertTimeout, 4); + + /* Get alert component from alert instance parent (the alert) */ + ecs_id_t childof_pair; + if (ecs_search(world, it->table, ecs_childof(EcsWildcard), &childof_pair) == -1) { + ecs_err("alert instances must be a child of an alert"); + return; + } + ecs_entity_t parent = ecs_pair_second(world, childof_pair); + ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, parent, EcsAlert), ECS_INVALID_OPERATION, + "alert entity does not have Alert component"); + EcsAlert *alert = ecs_get_mut(world, parent, EcsAlert); + const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery); + ecs_assert(poly != NULL, ECS_INVALID_OPERATION, + "alert entity does not have (Poly, Query) component"); + ecs_rule_t *rule = poly->poly; + ecs_poly_assert(rule, ecs_rule_t); + + ecs_id_t member_id = alert->id; + const EcsMemberRanges *ranges = NULL; + if (member_id) { + ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges); + } + + ecs_vars_t vars = {0}; + ecs_vars_init(world, &vars); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t ai = it->entities[i]; + ecs_entity_t e = source[i].entity; + + /* If source of alert is no longer alive, delete alert instance even if + * the alert has a retain period. */ + if (!ecs_is_alive(world, e)) { + ecs_delete(world, ai); + continue; + } + + /* Check if alert instance still matches rule */ + ecs_iter_t rit = ecs_rule_iter(world, rule); + rit.flags |= EcsIterNoData; + rit.flags |= EcsIterIsInstanced; + ecs_iter_set_var(&rit, 0, e); + + if (ecs_rule_next(&rit)) { + bool match = true; + + /* If alert is monitoring member range, test value against range */ + if (ranges) { + ecs_entity_t member_src = e; + if (alert->var_id) { + member_src = ecs_iter_get_var(&rit, alert->var_id); + } + + const void *member_data = ecs_get_id( + world, member_src, member_id); + if (!member_data) { + match = false; + } else { + member_data = ECS_OFFSET(member_data, alert->offset); + if (flecs_alert_out_of_range_kind( + alert, ranges, member_data) == 0) + { + match = false; + } + } + } + + if (match) { + /* Only increase alert duration if the alert was active */ + value[i].value += (double)it->delta_system_time; + + bool generate_message = alert->message; + if (generate_message) { + if (alert_instance[i].message) { + /* If a message was already generated, only regenerate if + * rule has multiple variables. Variable values could have + * changed, this ensures the message remains up to date. */ + generate_message = rit.variable_count > 1; + } + } + + if (generate_message) { + if (alert_instance[i].message) { + ecs_os_free(alert_instance[i].message); + } + + ecs_iter_to_vars(&rit, &vars, 0); + alert_instance[i].message = ecs_interpolate_string( + world, alert->message, &vars); + } + + if (timeout) { + if (timeout[i].inactive_time != 0) { + /* The alert just became active. Remove Disabled tag */ + flecs_alerts_add_alert_to_src(world, e, parent, ai); + ecs_remove_id(world, ai, EcsDisabled); + } + timeout[i].inactive_time = 0; + } + + /* Alert instance still matches rule, keep it alive */ + ecs_iter_fini(&rit); + continue; + } + + ecs_iter_fini(&rit); + } + + /* Alert instance is no longer active */ + + if (timeout) { + if (timeout[i].inactive_time == 0) { + /* The alert just became inactive. Add Disabled tag */ + flecs_alerts_remove_alert_from_src(world, e, parent); + ecs_add_id(world, ai, EcsDisabled); + } + ecs_ftime_t t = timeout[i].inactive_time; + timeout[i].inactive_time += it->delta_system_time; + if (t < timeout[i].expire_time) { + /* Alert instance no longer matches rule, but is still + * within the timeout period. Keep it alive. */ + continue; + } + } + + /* Alert instance no longer matches rule, remove it */ + flecs_alerts_remove_alert_from_src(world, e, parent); + ecs_map_remove(&alert->instances, e); + ecs_delete(world, ai); + } + + ecs_vars_fini(&vars); +} + +ecs_entity_t ecs_alert_init( + ecs_world_t *world, + const ecs_alert_desc_t *desc) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!desc->filter.entity || desc->entity == desc->filter.entity, + ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world, 0); + } + + ecs_filter_desc_t private_desc = desc->filter; + private_desc.entity = result; + + ecs_rule_t *rule = ecs_rule_init(world, &private_desc); + if (!rule) { + ecs_err("failed to create alert filter"); + return 0; + } + + const ecs_filter_t *filter = ecs_rule_get_filter(rule); + if (!(filter->flags & EcsFilterMatchThis)) { + ecs_err("alert filter must have at least one '$this' term"); + ecs_rule_fini(rule); + return 0; + } + + /* Initialize Alert component which identifiers entity as alert */ + EcsAlert *alert = ecs_get_mut(world, result, EcsAlert); + ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL); + alert->message = ecs_os_strdup(desc->message); + alert->retain_period = desc->retain_period; + + /* Initialize severity filters */ + int32_t i; + for (i = 0; i < 4; i ++) { + if (desc->severity_filters[i].with) { + if (!desc->severity_filters[i].severity) { + ecs_err("severity filter must have severity"); + goto error; + } + ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL, + &alert->severity_filters, ecs_alert_severity_filter_t); + *sf = desc->severity_filters[i]; + if (sf->var) { + sf->_var_index = ecs_rule_find_var(rule, sf->var); + if (sf->_var_index == -1) { + ecs_err("unresolved variable '%s' in alert severity filter", + sf->var); + goto error; + } + } + } + } + + /* Fetch data for member monitoring */ + if (desc->member) { + alert->member = desc->member; + if (!desc->id) { + alert->id = ecs_get_parent(world, desc->member); + if (!alert->id) { + ecs_err("ecs_alert_desc_t::member is not a member"); + goto error; + } + ecs_check(alert->id != 0, ECS_INVALID_PARAMETER, NULL); + } else { + alert->id = desc->id; + } + + ecs_id_record_t *idr = flecs_id_record_ensure(world, alert->id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + if (!idr->type_info) { + ecs_err("ecs_alert_desc_t::id must be a component"); + goto error; + } + + ecs_entity_t type = idr->type_info->component; + if (type != ecs_get_parent(world, desc->member)) { + char *type_name = ecs_get_fullpath(world, type); + ecs_err("member '%s' is not a member of '%s'", + ecs_get_name(world, desc->member), type_name); + ecs_os_free(type_name); + goto error; + } + + const EcsMember *member = ecs_get(world, alert->member, EcsMember); + if (!member) { + ecs_err("ecs_alert_desc_t::member is not a member"); + goto error; + } + if (!member->type) { + ecs_err("ecs_alert_desc_t::member must have a type"); + goto error; + } + + const EcsPrimitive *pr = ecs_get(world, member->type, EcsPrimitive); + if (!pr) { + ecs_err("ecs_alert_desc_t::member must be of a primitive type"); + goto error; + } + + if (!ecs_has(world, desc->member, EcsMemberRanges)) { + ecs_err("ecs_alert_desc_t::member must have warning/error ranges"); + goto error; + } + + int32_t var_id = 0; + if (desc->var) { + var_id = ecs_rule_find_var(rule, desc->var); + if (var_id == -1) { + ecs_err("unresolved variable '%s' in alert member", desc->var); + goto error; + } + } + + alert->offset = member->offset; + alert->size = idr->type_info->size; + alert->kind = pr->kind; + alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); + alert->var_id = var_id; + } + + ecs_modified(world, result, EcsAlert); + + /* Register alert as metric */ + ecs_add(world, result, EcsMetric); + ecs_add_pair(world, result, EcsMetric, EcsCounter); + + /* Add severity to alert */ + ecs_entity_t severity = desc->severity; + if (!severity) { + severity = EcsAlertError; + } + + ecs_add_pair(world, result, ecs_id(EcsAlert), severity); + + if (desc->doc_name) { +#ifdef FLECS_DOC + ecs_doc_set_name(world, result, desc->doc_name); +#else + ecs_err("cannot set doc_name for alert, requires FLECS_DOC addon"); + goto error; +#endif + } + + if (desc->brief) { +#ifdef FLECS_DOC + ecs_doc_set_brief(world, result, desc->brief); +#else + ecs_err("cannot set brief for alert, requires FLECS_DOC addon"); + goto error; +#endif + } + + return result; +error: + if (result) { + ecs_delete(world, result); + } + return 0; +} + +int32_t ecs_get_alert_count( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!alert || ecs_has(world, alert, EcsAlert), + ECS_INVALID_PARAMETER, NULL); + + const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); + if (!active) { + return 0; + } + + if (alert) { + return ecs_map_get(&active->alerts, alert) != NULL; + } + + return ecs_map_count(&active->alerts); +error: + return 0; +} + +ecs_entity_t ecs_get_alert( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(alert != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); + if (!active) { + return 0; + } + + ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert); + if (ptr) { + return ptr[0]; + } + +error: + return 0; +} + +void FlecsAlertsImport(ecs_world_t *world) { + ECS_MODULE_DEFINE(world, FlecsAlerts); + + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsTimer); + ECS_IMPORT(world, FlecsMetrics); +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); +#endif + + ecs_set_name_prefix(world, "Ecs"); + ECS_COMPONENT_DEFINE(world, EcsAlert); + ecs_remove_pair(world, ecs_id(EcsAlert), ecs_id(EcsIdentifier), EcsSymbol); + + ecs_set_name_prefix(world, "EcsAlert"); + ECS_COMPONENT_DEFINE(world, EcsAlertInstance); + ECS_COMPONENT_DEFINE(world, EcsAlertsActive); + ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); + + ECS_TAG_DEFINE(world, EcsAlertInfo); + ECS_TAG_DEFINE(world, EcsAlertWarning); + ECS_TAG_DEFINE(world, EcsAlertError); + ECS_TAG_DEFINE(world, EcsAlertCritical); + + ecs_add_id(world, ecs_id(EcsAlert), EcsTag); + ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive); + ecs_add_id(world, ecs_id(EcsAlertsActive), EcsPrivate); + + ecs_struct(world, { + .entity = ecs_id(EcsAlertInstance), + .members = { + { .name = "message", .type = ecs_id(ecs_string_t) } + } + }); + + ecs_set_hooks(world, EcsAlert, { + .ctor = ecs_ctor(EcsAlert), + .dtor = ecs_dtor(EcsAlert), + .move = ecs_move(EcsAlert) + }); + + ecs_set_hooks(world, EcsAlertsActive, { + .ctor = ecs_ctor(EcsAlertsActive), + .dtor = ecs_dtor(EcsAlertsActive), + .move = ecs_move(EcsAlertsActive) + }); + + ecs_set_hooks(world, EcsAlertInstance, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsAlertInstance), + .move = ecs_move(EcsAlertInstance), + .copy = ecs_copy(EcsAlertInstance) + }); + + ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query)); + ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, + flecs.metrics.Source, flecs.metrics.Value, ?EcsAlertTimeout, ?Disabled); + + ecs_system(world, { + .entity = ecs_id(MonitorAlerts), + .no_readonly = true, + .interval = 0.5 + }); + + ecs_system(world, { + .entity = ecs_id(MonitorAlertInstances), + .interval = 0.5 + }); +} + #endif /** @@ -19570,6 +20405,8 @@ void ecs_set_os_api_impl(void) { api.thread_new_ = win_thread_new; api.thread_join_ = win_thread_join; api.thread_self_ = win_thread_self; + api.task_new_ = win_thread_new; + api.task_join_ = win_thread_join; api.ainc_ = win_ainc; api.adec_ = win_adec; api.lainc_ = win_lainc; @@ -19905,6 +20742,8 @@ void ecs_set_os_api_impl(void) { api.thread_new_ = posix_thread_new; api.thread_join_ = posix_thread_join; api.thread_self_ = posix_thread_self; + api.task_new_ = posix_thread_new; + api.task_join_ = posix_thread_join; api.ainc_ = posix_ainc; api.adec_ = posix_adec; api.lainc_ = posix_lainc; @@ -20620,16 +21459,17 @@ int plecs_create_term( } if (subj) { + ecs_id_t id; if (!obj) { - ecs_add_id(world, subj, pred); - state->last_assign_id = pred; + id = term->id_flags | pred; } else { - ecs_add_pair(world, subj, pred, obj); + id = term->id_flags | ecs_pair(pred, obj); state->last_object = obj; - state->last_assign_id = ecs_pair(pred, obj); } + state->last_assign_id = id; state->last_predicate = pred; state->last_subject = subj; + ecs_add_id(world, subj, id); pred_as_subj = false; } else { @@ -22727,6 +23567,23 @@ static void flecs_metrics_on_oneof_metric(ecs_iter_t *it) { } } +/** Set doc name of metric instance to name of source entity */ +#ifdef FLECS_DOC +static void SetMetricDocName(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t src_e = src[i].entity; + const char *name = ecs_get_name(world, src_e); + if (name) { + ecs_doc_set_name(world, it->entities[i], name); + } + } +} +#endif + /** Delete metric instances for entities that are no longer alive */ static void ClearMetricInstance(ecs_iter_t *it) { ecs_world_t *world = it->world; @@ -23150,6 +24007,7 @@ ecs_entity_t ecs_metric_init( const ecs_metric_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_poly_assert(world, ecs_world_t); ecs_entity_t result = desc->entity; @@ -23329,6 +24187,10 @@ void FlecsMetricsImport(ecs_world_t *world) { ecs_add_id(world, EcsMetric, EcsOneOf); +#ifdef FLECS_DOC + ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, EcsMetricSource); +#endif + ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, [in] Source); @@ -23413,6 +24275,34 @@ bool flecs_unit_validate( #ifdef FLECS_META +static +bool flecs_type_is_number( + ecs_world_t *world, + ecs_entity_t type) +{ + const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); + if (!p) { + return false; + } + + switch(p->kind) { + case EcsChar: + case EcsU8: + case EcsU16: + case EcsU32: + case EcsU64: + case EcsI8: + case EcsI16: + case EcsI32: + case EcsI64: + case EcsF32: + case EcsF64: + return true; + default: + return false; + } +} + ecs_entity_t ecs_primitive_init( ecs_world_t *world, const ecs_primitive_desc_t *desc) @@ -23447,7 +24337,7 @@ ecs_entity_t ecs_enum_init( break; } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ + ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); @@ -23490,7 +24380,7 @@ ecs_entity_t ecs_bitmask_init( break; } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ + ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); @@ -23546,6 +24436,28 @@ ecs_entity_t ecs_vector_init( return t; } +static +bool flecs_member_range_overlaps( + const ecs_member_value_range_t *range, + const ecs_member_value_range_t *with) +{ + if (with->min == with->max) { + return false; + } + + if (range->min == range->max) { + return false; + } + + if (range->min < with->min || + range->max > with->max) + { + return true; + } + + return false; +} + ecs_entity_t ecs_struct_init( ecs_world_t *world, const ecs_struct_desc_t *desc) @@ -23571,7 +24483,7 @@ ecs_entity_t ecs_struct_init( return 0; } - ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t){ + ecs_entity_t m = ecs_entity(world, { .name = m_desc->name }); @@ -23581,6 +24493,86 @@ ecs_entity_t ecs_struct_init( .offset = m_desc->offset, .unit = m_desc->unit }); + + EcsMemberRanges *ranges = NULL; + const ecs_member_value_range_t *range = &m_desc->range; + const ecs_member_value_range_t *error = &m_desc->error_range; + const ecs_member_value_range_t *warning = &m_desc->warning_range; + if (range->min != range->max) { + ranges = ecs_get_mut(world, m, EcsMemberRanges); + if (range->min > range->max) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("member '%s' has an invalid value range [%d..%d]", + member_name, range->min, range->max); + ecs_os_free(member_name); + goto error; + } + ranges->value.min = range->min; + ranges->value.max = range->max; + } + if (error->min != error->max) { + if (error->min > error->max) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("member '%s' has an invalid error range [%d..%d]", + member_name, error->min, error->max); + ecs_os_free(member_name); + goto error; + } + if (flecs_member_range_overlaps(error, range)) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("error range of member '%s' overlaps with value range", + member_name); + ecs_os_free(member_name); + goto error; + } + if (!ranges) { + ranges = ecs_get_mut(world, m, EcsMemberRanges); + } + ranges->error.min = error->min; + ranges->error.max = error->max; + } + + if (warning->min != warning->max) { + if (warning->min > warning->max) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("member '%s' has an invalid warning range [%d..%d]", + member_name, warning->min, warning->max); + ecs_os_free(member_name); + goto error; + } + if (flecs_member_range_overlaps(warning, range)) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("warning range of member '%s' overlaps with value " + "range", member_name); + ecs_os_free(member_name); + goto error; + } + if (flecs_member_range_overlaps(warning, error)) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("warning range of member '%s' overlaps with error " + "range", member_name); + ecs_os_free(member_name); + goto error; + } + + if (!ranges) { + ranges = ecs_get_mut(world, m, EcsMemberRanges); + } + ranges->warning.min = warning->min; + ranges->warning.max = warning->max; + } + + if (ranges && !flecs_type_is_number(world, m_desc->type)) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("member '%s' has an value/error/warning range, but is not a " + "number", member_name); + ecs_os_free(member_name); + goto error; + } + + if (ranges) { + ecs_modified(world, m, EcsMemberRanges); + } } ecs_set_scope(world, old_scope); @@ -23598,6 +24590,11 @@ ecs_entity_t ecs_struct_init( } return t; +error: + if (t) { + ecs_delete(world, t); + } + return 0; } ecs_entity_t ecs_opaque_init( @@ -23731,7 +24728,7 @@ ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t k op->name = NULL; op->members = NULL; op->type = 0; - op->unit = 0; + op->member_index = 0; return op; } @@ -23889,7 +24886,8 @@ int flecs_meta_serialize_struct( ecs_member_t *member = &members[i]; cur = ecs_vec_count(ops); - flecs_meta_serialize_type(world, member->type, offset + member->offset, ops); + flecs_meta_serialize_type(world, + member->type, offset + member->offset, ops); op = flecs_meta_ops_get(ops, cur); if (!op->type) { @@ -23902,8 +24900,8 @@ int flecs_meta_serialize_struct( const char *member_name = member->name; op->name = member_name; - op->unit = member->unit; op->op_count = ecs_vec_count(ops) - cur; + op->member_index = i; flecs_name_index_ensure( member_index, flecs_ito(uint64_t, cur - first - 1), @@ -24317,7 +25315,8 @@ void flecs_set_struct_member( ecs_entity_t type, int32_t count, int32_t offset, - ecs_entity_t unit) + ecs_entity_t unit, + EcsMemberRanges *ranges) { member->member = entity; member->type = type; @@ -24330,6 +25329,16 @@ void flecs_set_struct_member( } ecs_os_strset((char**)&member->name, name); + + if (ranges) { + member->range = ranges->value; + member->error_range = ranges->error; + member->warning_range = ranges->warning; + } else { + ecs_os_zeromem(&member->range); + ecs_os_zeromem(&member->error_range); + ecs_os_zeromem(&member->warning_range); + } } static @@ -24337,7 +25346,8 @@ int flecs_add_member_to_struct( ecs_world_t *world, ecs_entity_t type, ecs_entity_t member, - EcsMember *m) + EcsMember *m, + EcsMemberRanges *ranges) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); @@ -24369,7 +25379,6 @@ int flecs_add_member_to_struct( } ecs_entity_t unit = m->unit; - if (unit) { if (!ecs_has(world, unit, EcsUnit)) { ecs_err("entity '%s' for member '%s' is not a unit", @@ -24397,8 +25406,8 @@ int flecs_add_member_to_struct( int32_t i, count = ecs_vec_count(&s->members); for (i = 0; i < count; i ++) { if (members[i].member == member) { - flecs_set_struct_member( - &members[i], member, name, m->type, m->count, m->offset, unit); + flecs_set_struct_member(&members[i], member, name, m->type, + m->count, m->offset, unit, ranges); break; } } @@ -24409,7 +25418,7 @@ int flecs_add_member_to_struct( ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); elem->name = NULL; flecs_set_struct_member(elem, member, name, m->type, - m->count, m->offset, unit); + m->count, m->offset, unit, ranges); /* Reobtain members array in case it was reallocated */ members = ecs_vec_first_t(&s->members, ecs_member_t); @@ -24727,6 +25736,8 @@ static void flecs_set_member(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMember *member = ecs_field(it, EcsMember, 1); + EcsMemberRanges *ranges = ecs_table_get_id(world, it->table, + ecs_id(EcsMemberRanges), it->offset); int i, count = it->count; for (i = 0; i < count; i ++) { @@ -24737,7 +25748,32 @@ void flecs_set_member(ecs_iter_t *it) { continue; } - flecs_add_member_to_struct(world, parent, e, &member[i]); + flecs_add_member_to_struct(world, parent, e, &member[i], + ranges ? &ranges[i] : NULL); + } +} + +static +void flecs_set_member_ranges(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMemberRanges *ranges = ecs_field(it, EcsMemberRanges, 1); + EcsMember *member = ecs_table_get_id(world, it->table, + ecs_id(EcsMember), it->offset); + if (!member) { + return; + } + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); + continue; + } + + flecs_add_member_to_struct(world, parent, e, &member[i], + &ranges[i]); } } @@ -25071,6 +26107,7 @@ void FlecsMetaImport( flecs_bootstrap_component(world, EcsEnum); flecs_bootstrap_component(world, EcsBitmask); flecs_bootstrap_component(world, EcsMember); + flecs_bootstrap_component(world, EcsMemberRanges); flecs_bootstrap_component(world, EcsStruct); flecs_bootstrap_component(world, EcsArray); flecs_bootstrap_component(world, EcsVector); @@ -25102,6 +26139,10 @@ void FlecsMetaImport( .on_set = flecs_member_on_set }); + ecs_set_hooks(world, EcsMemberRanges, { + .ctor = ecs_default_ctor + }); + ecs_set_hooks(world, EcsEnum, { .ctor = ecs_default_ctor, .move = ecs_move(EcsEnum), @@ -25133,79 +26174,85 @@ void FlecsMetaImport( /* Register triggers to finalize type information from component data */ ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ world, EcsFlecsInternals); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_primitive }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_member }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMemberRanges), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_member_ranges + }); + + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_enum }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_bitmask }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_constant }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_add_constant }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_array }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_vector }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsOpaque), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_custom_type }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_unit }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_serialized_init }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_init_default_ctor }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = { { .id = ecs_id(EcsUnit) }, { .id = EcsQuantity } @@ -25330,6 +26377,23 @@ void FlecsMetaImport( } }); + ecs_entity_t vr = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, { .name = "value_range" }), + .members = { + {.name = (char*)"min", .type = ecs_id(ecs_f64_t)}, + {.name = (char*)"max", .type = ecs_id(ecs_f64_t)} + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMemberRanges), + .members = { + { .name = (char*)"value", .type = vr}, + { .name = (char*)"warning", .type = vr}, + { .name = (char*)"error", .type = vr} + } + }); + ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsArray), .members = { @@ -25383,7 +26447,7 @@ void FlecsMetaImport( #endif /** - * @file meta/api.c + * @file meta/cursor.c * @brief API for assigning values of runtime types with reflection. */ @@ -26045,8 +27109,17 @@ ecs_entity_t ecs_meta_get_unit( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_entity_t type = scope->type; + const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); + if (!st) { + return 0; + } + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - return op->unit; + ecs_member_t *m = ecs_vec_get_t( + &st->members, ecs_member_t, op->member_index); + + return m->unit; } const char* ecs_meta_get_member( @@ -26957,7 +28030,8 @@ int flecs_expr_ser_type( const ecs_world_t *world, const ecs_vec_t *ser, const void *base, - ecs_strbuf_t *str); + ecs_strbuf_t *str, + bool is_expr); static int flecs_expr_ser_type_ops( @@ -26966,14 +28040,16 @@ int flecs_expr_ser_type_ops( int32_t op_count, const void *base, ecs_strbuf_t *str, - int32_t in_array); + int32_t in_array, + bool is_expr); static int flecs_expr_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, - ecs_strbuf_t *str); + ecs_strbuf_t *str, + bool is_expr); static ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { @@ -26986,7 +28062,8 @@ int flecs_expr_ser_primitive( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, - ecs_strbuf_t *str) + ecs_strbuf_t *str, + bool is_expr) { switch(kind) { case EcsBool: @@ -27001,9 +28078,9 @@ int flecs_expr_ser_primitive( char ch = *(char*)base; if (ch) { ecs_chresc(chbuf, *(char*)base, '"'); - ecs_strbuf_appendch(str, '"'); + if (is_expr) ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, chbuf); - ecs_strbuf_appendch(str, '"'); + if (is_expr) ecs_strbuf_appendch(str, '"'); } else { ecs_strbuf_appendch(str, '0'); } @@ -27051,18 +28128,22 @@ int flecs_expr_ser_primitive( case EcsString: { char *value = *(char**)base; if (value) { - ecs_size_t length = ecs_stresc(NULL, 0, '"', value); - if (length == ecs_os_strlen(value)) { - ecs_strbuf_appendch(str, '"'); - ecs_strbuf_appendstrn(str, value, length); - ecs_strbuf_appendch(str, '"'); + if (!is_expr) { + ecs_strbuf_appendstr(str, value); } else { - char *out = ecs_os_malloc(length + 3); - ecs_stresc(out + 1, length, '"', value); - out[0] = '"'; - out[length + 1] = '"'; - out[length + 2] = '\0'; - ecs_strbuf_appendstr_zerocpy(str, out); + ecs_size_t length = ecs_stresc(NULL, 0, '"', value); + if (length == ecs_os_strlen(value)) { + ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstrn(str, value, length); + ecs_strbuf_appendch(str, '"'); + } else { + char *out = ecs_os_malloc(length + 3); + ecs_stresc(out + 1, length, '"', value); + out[0] = '"'; + out[length + 1] = '"'; + out[length + 2] = '\0'; + ecs_strbuf_appendstr_zerocpy(str, out); + } } } else { ecs_strbuf_appendlit(str, "null"); @@ -27185,7 +28266,9 @@ int expr_ser_elements( int i; for (i = 0; i < elem_count; i ++) { ecs_strbuf_list_next(str); - if (flecs_expr_ser_type_ops(world, ops, op_count, ptr, str, is_array)) { + if (flecs_expr_ser_type_ops( + world, ops, op_count, ptr, str, is_array, true)) + { return -1; } ptr = ECS_OFFSET(ptr, elem_size); @@ -27258,7 +28341,8 @@ int flecs_expr_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, - ecs_strbuf_t *str) + ecs_strbuf_t *str, + bool is_expr) { switch(op->kind) { case EcsOpPush: @@ -27288,7 +28372,7 @@ int flecs_expr_ser_type_op( break; default: if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind), - ECS_OFFSET(ptr, op->offset), str)) + ECS_OFFSET(ptr, op->offset), str, is_expr)) { /* Unknown operation */ ecs_err("unknown serializer operation kind (%d)", op->kind); @@ -27310,7 +28394,8 @@ int flecs_expr_ser_type_ops( int32_t op_count, const void *base, ecs_strbuf_t *str, - int32_t in_array) + int32_t in_array, + bool is_expr) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; @@ -27345,7 +28430,7 @@ int flecs_expr_ser_type_ops( in_array ++; break; default: - if (flecs_expr_ser_type_op(world, op, base, str)) { + if (flecs_expr_ser_type_op(world, op, base, str, is_expr)) { goto error; } break; @@ -27363,11 +28448,12 @@ int flecs_expr_ser_type( const ecs_world_t *world, const ecs_vec_t *v_ops, const void *base, - ecs_strbuf_t *str) + ecs_strbuf_t *str, + bool is_expr) { ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(v_ops); - return flecs_expr_ser_type_ops(world, ops, count, base, str, 0); + return flecs_expr_ser_type_ops(world, ops, count, base, str, 0, is_expr); } int ecs_ptr_to_expr_buf( @@ -27385,7 +28471,7 @@ int ecs_ptr_to_expr_buf( goto error; } - if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out)) { + if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, true)) { goto error; } @@ -27409,13 +28495,52 @@ char* ecs_ptr_to_expr( return ecs_strbuf_get(&str); } +int ecs_ptr_to_str_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf_out) +{ + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (ser == NULL) { + char *path = ecs_get_fullpath(world, type); + ecs_err("cannot serialize value for type '%s'", path); + ecs_os_free(path); + goto error; + } + + if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, false)) { + goto error; + } + + return 0; +error: + return -1; +} + +char* ecs_ptr_to_str( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + int ecs_primitive_to_expr_buf( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, ecs_strbuf_t *str) { - return flecs_expr_ser_primitive(world, kind, base, str); + return flecs_expr_ser_primitive(world, kind, base, str, true); } #endif @@ -27587,7 +28712,7 @@ ecs_expr_var_t* flecs_vars_scope_lookup( } ecs_expr_var_t* ecs_vars_lookup( - ecs_vars_t *vars, + const ecs_vars_t *vars, const char *name) { return flecs_vars_scope_lookup(vars->cur, name); @@ -27596,13 +28721,15 @@ ecs_expr_var_t* ecs_vars_lookup( #endif /** - * @file expr/strutil.c + * @file expr/utils.c * @brief String parsing utilities. */ #ifdef FLECS_EXPR +#include + char* ecs_chresc( char *out, char in, @@ -27770,6 +28897,258 @@ char* ecs_astresc( return out; } +static +const char* flecs_parse_var_name( + const char *ptr, + char *token_out) +{ + char ch, *bptr = token_out; + + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } + + if (isalpha(ch) || isdigit(ch) || ch == '_') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + break; + } + } + + if (bptr == token_out) { + goto error; + } + + *bptr = '\0'; + + return ptr; +error: + return NULL; +} + +static +const char* flecs_parse_interpolated_str( + const char *ptr, + char *token_out) +{ + char ch, *bptr = token_out; + + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } + + if (ch == '\\') { + if (ptr[1] == '}') { + *bptr = '}'; + bptr ++; + ptr += 2; + continue; + } + } + + if (ch != '}') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + ptr ++; + break; + } + } + + if (bptr == token_out) { + goto error; + } + + *bptr = '\0'; + + return ptr; +error: + return NULL; +} + +char* ecs_interpolate_string( + ecs_world_t *world, + const char *str, + const ecs_vars_t *vars) +{ + char token[ECS_MAX_TOKEN_SIZE]; + ecs_strbuf_t result = ECS_STRBUF_INIT; + const char *ptr; + char ch; + + for(ptr = str; (ch = *ptr); ptr++) { + if (ch == '\\') { + ptr ++; + if (ptr[0] == '$') { + ecs_strbuf_appendch(&result, '$'); + continue; + } + if (ptr[0] == '\\') { + ecs_strbuf_appendch(&result, '\\'); + continue; + } + if (ptr[0] == '{') { + ecs_strbuf_appendch(&result, '{'); + continue; + } + if (ptr[0] == '}') { + ecs_strbuf_appendch(&result, '}'); + continue; + } + ptr --; + } + + if (ch == '$') { + ptr = flecs_parse_var_name(ptr + 1, token); + if (!ptr) { + ecs_parser_error(NULL, str, ptr - str, + "invalid variable name '%s'", ptr); + goto error; + } + + ecs_expr_var_t *var = ecs_vars_lookup(vars, token); + if (!var) { + ecs_parser_error(NULL, str, ptr - str, + "unresolved variable '%s'", token); + goto error; + } + + if (ecs_ptr_to_str_buf( + world, var->value.type, var->value.ptr, &result)) + { + goto error; + } + + ptr --; + } else if (ch == '{') { + ptr = flecs_parse_interpolated_str(ptr + 1, token); + if (!ptr) { + ecs_parser_error(NULL, str, ptr - str, + "invalid interpolated expression"); + goto error; + } + + ecs_parse_expr_desc_t expr_desc = { .vars = (ecs_vars_t*)vars }; + ecs_value_t expr_result = {0}; + if (!ecs_parse_expr(world, token, &expr_result, &expr_desc)) { + goto error; + } + + if (ecs_ptr_to_str_buf( + world, expr_result.type, expr_result.ptr, &result)) + { + goto error; + } + + ecs_value_free(world, expr_result.type, expr_result.ptr); + + ptr --; + } else { + ecs_strbuf_appendch(&result, ch); + } + } + + return ecs_strbuf_get(&result); +error: + return NULL; +} + +void ecs_iter_to_vars( + const ecs_iter_t *it, + ecs_vars_t *vars, + int offset) +{ + ecs_check(vars != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!offset || offset < it->count, ECS_INVALID_PARAMETER, NULL); + + /* Set variable for $this */ + if (it->count) { + ecs_expr_var_t *var = ecs_vars_lookup(vars, "this"); + if (!var) { + ecs_value_t v = { + .ptr = &it->entities[offset], + .type = ecs_id(ecs_entity_t) + }; + var = ecs_vars_declare_w_value(vars, "this", &v); + var->owned = false; + } else { + var->value.ptr = &it->entities[offset]; + } + } + + /* Set variables for fields */ + { + int32_t i, field_count = it->field_count; + for (i = 0; i < field_count; i ++) { + ecs_size_t size = it->sizes[i]; + if (!size) { + continue; + } + + void *ptr = it->ptrs[i]; + if (!ptr) { + continue; + } + + ptr = ECS_OFFSET(ptr, offset * size); + + char name[16]; + ecs_os_sprintf(name, "%d", i + 1); + ecs_expr_var_t *var = ecs_vars_lookup(vars, name); + if (!var) { + ecs_value_t v = { .ptr = ptr, .type = it->ids[i] }; + var = ecs_vars_declare_w_value(vars, name, &v); + var->owned = false; + } else { + ecs_check(var->value.type == it->ids[i], + ECS_INVALID_PARAMETER, NULL); + var->value.ptr = ptr; + } + } + } + + /* Set variables for query variables */ + { + int32_t i, var_count = it->variable_count; + for (i = 1 /* skip this variable */ ; i < var_count; i ++) { + ecs_entity_t *e_ptr = NULL; + ecs_var_t *query_var = &it->variables[i]; + if (query_var->entity) { + e_ptr = &query_var->entity; + } else { + ecs_table_range_t *range = &query_var->range; + if (range->count == 1) { + ecs_entity_t *entities = range->table->data.entities.array; + e_ptr = &entities[range->offset]; + } + } + if (!e_ptr) { + continue; + } + + ecs_expr_var_t *var = ecs_vars_lookup(vars, it->variable_names[i]); + if (!var) { + ecs_value_t v = { .ptr = e_ptr, .type = ecs_id(ecs_entity_t) }; + var = ecs_vars_declare_w_value(vars, it->variable_names[i], &v); + var->owned = false; + } else { + ecs_check(var->value.type == ecs_id(ecs_entity_t), + ECS_INVALID_PARAMETER, NULL); + var->value.ptr = e_ptr; + } + } + } + +error: + return; +} + #endif /** @@ -28026,10 +29405,100 @@ bool flecs_parse_is_float( return false; } +/* Attempt to resolve variable dotexpression to value (foo.bar) */ +static +ecs_value_t flecs_dotresolve_var( + ecs_world_t *world, + ecs_vars_t *vars, + char *token) +{ + char *dot = strchr(token, '.'); + if (!dot) { + return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; + } + + dot[0] = '\0'; + + const ecs_expr_var_t *var = ecs_vars_lookup(vars, token); + if (!var) { + return (ecs_value_t){0}; + } + + ecs_meta_cursor_t cur = ecs_meta_cursor( + world, var->value.type, var->value.ptr); + ecs_meta_push(&cur); + if (ecs_meta_dotmember(&cur, dot + 1) != 0) { + return (ecs_value_t){0}; + } + + return (ecs_value_t){ + .ptr = ecs_meta_get_ptr(&cur), + .type = ecs_meta_get_type(&cur) + }; +} + +static +int flecs_meta_call( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + ecs_meta_cursor_t *cur, + const char *function) +{ + ecs_entity_t type = ecs_meta_get_type(cur); + void *value_ptr = ecs_meta_get_ptr(cur); + + if (!ecs_os_strcmp(function, "parent")) { + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "parent() can only be called on entity"); + return -1; + } + + *(ecs_entity_t*)value_ptr = ecs_get_parent( + world, *(ecs_entity_t*)value_ptr); + } else if (!ecs_os_strcmp(function, "name")) { + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "name() can only be called on entity"); + return -1; + } + + char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); + *result = ecs_os_strdup(ecs_get_name(world, *(ecs_entity_t*)value_ptr)); + *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); + } else if (!ecs_os_strcmp(function, "doc_name")) { +#ifdef FLECS_DOC + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "name() can only be called on entity"); + return -1; + } + + char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); + *result = ecs_os_strdup(ecs_doc_get_name(world, *(ecs_entity_t*)value_ptr)); + *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); +#else + ecs_parser_error(name, expr, ptr - expr, + "doc_name() is not available without FLECS_DOC addon"); + return -1; +#endif + } else { + ecs_parser_error(name, expr, ptr - expr, + "unknown function '%s'", function); + return -1; + } + + return 0; +} + /* Determine the type of an expression from the first character(s). This allows * us to initialize a storage for a type if none was provided. */ static ecs_entity_t flecs_parse_discover_type( + ecs_world_t *world, const char *name, const char *expr, const char *ptr, @@ -28077,8 +29546,24 @@ ecs_entity_t flecs_parse_discover_type( if (ecs_parse_expr_token(name, expr, &ptr[1], token) == NULL) { return 0; } + const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); if (!var) { + ecs_size_t len = ecs_os_strlen(token); + if (ptr[len + 1] == '(') { + while ((len > 0) && (token[len] != '.')) { + len --; + } + if (token[len] == '.') { + token[len] = '\0'; + } + } + + ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); + if (v.type) { + return v.type; + } + ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", token); return 0; @@ -28564,6 +30049,85 @@ const char* flecs_binary_expr_parse( return ptr; } +static +const char* flecs_funccall_parse( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + char *token, + ecs_meta_cursor_t *cur, + ecs_value_t *value, + bool isvar, + const ecs_parse_expr_desc_t *desc) +{ + char *sep = strrchr(token, '.'); + if (!sep) { + ecs_parser_error(name, expr, ptr - expr, + "missing object for function call '%s'", token); + return NULL; + } + + sep[0] = '\0'; + const char *function = sep + 1; + + if (!isvar) { + if (ecs_meta_set_string(cur, token) != 0) { + goto error; + } + } else { + const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); + ecs_meta_set_value(cur, &var->value); + } + + do { + if (!function[0]) { + ecs_parser_error(name, expr, ptr - expr, + "missing function name for function call '%s'", token); + return NULL; + } + + if (flecs_meta_call(world, stack, name, expr, ptr, cur, + function) != 0) + { + goto error; + } + + ecs_entity_t type = ecs_meta_get_type(cur); + value->ptr = ecs_meta_get_ptr(cur); + value->type = type; + + ptr += 2; + if (ptr[0] != '.') { + break; + } + + ptr ++; + char *paren = strchr(ptr, '('); + if (!paren) { + break; + } + + ecs_size_t len = flecs_ito(int32_t, paren - ptr); + if (len >= ECS_MAX_TOKEN_SIZE) { + ecs_parser_error(name, expr, ptr - expr, + "token exceeds maximum token size"); + goto error; + } + + ecs_os_strncpy(token, ptr, len); + token[len] = '\0'; + function = token; + + ptr = paren; + } while (true); + + return ptr; +error: + return NULL; +} + static const char* flecs_parse_expr( ecs_world_t *world, @@ -28596,7 +30160,7 @@ const char* flecs_parse_expr( * will be initialized by a nested expression */ if (ptr[0] != '(') { ecs_entity_t type = flecs_parse_discover_type( - name, expr, ptr, value->type, desc); + world, name, expr, ptr, value->type, desc); if (!type) { return NULL; } @@ -28763,12 +30327,26 @@ const char* flecs_parse_expr( const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, &token[1]); if (!var) { - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable '%s'", token); - return NULL; + if (ptr[0] == '(') { + /* Function */ + ptr = flecs_funccall_parse(world, stack, name, expr, ptr, + &token[1], &cur, &result, true, desc); + if (!ptr) { + goto error; + } + } else { + ecs_value_t v = flecs_dotresolve_var(world, desc->vars, &token[1]); + if (!v.ptr) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s'", token); + return NULL; + } else { + ecs_meta_set_value(&cur, &v); + } + } + } else { + ecs_meta_set_value(&cur, &var->value); } - - ecs_meta_set_value(&cur, &var->value); is_lvalue = true; } else { @@ -28785,6 +30363,13 @@ const char* flecs_parse_expr( if (ecs_meta_dotmember(&cur, token) != 0) { goto error; } + } else if (ptr[0] == '(') { + /* Function */ + ptr = flecs_funccall_parse(world, stack, name, expr, ptr, + token, &cur, &result, false, desc); + if (!ptr) { + goto error; + } } else { if (ecs_meta_set_string(&cur, token) != 0) { goto error; @@ -29724,138 +31309,6 @@ error: #ifdef FLECS_UNITS -ECS_DECLARE(EcsUnitPrefixes); - -ECS_DECLARE(EcsYocto); -ECS_DECLARE(EcsZepto); -ECS_DECLARE(EcsAtto); -ECS_DECLARE(EcsFemto); -ECS_DECLARE(EcsPico); -ECS_DECLARE(EcsNano); -ECS_DECLARE(EcsMicro); -ECS_DECLARE(EcsMilli); -ECS_DECLARE(EcsCenti); -ECS_DECLARE(EcsDeci); -ECS_DECLARE(EcsDeca); -ECS_DECLARE(EcsHecto); -ECS_DECLARE(EcsKilo); -ECS_DECLARE(EcsMega); -ECS_DECLARE(EcsGiga); -ECS_DECLARE(EcsTera); -ECS_DECLARE(EcsPeta); -ECS_DECLARE(EcsExa); -ECS_DECLARE(EcsZetta); -ECS_DECLARE(EcsYotta); - -ECS_DECLARE(EcsKibi); -ECS_DECLARE(EcsMebi); -ECS_DECLARE(EcsGibi); -ECS_DECLARE(EcsTebi); -ECS_DECLARE(EcsPebi); -ECS_DECLARE(EcsExbi); -ECS_DECLARE(EcsZebi); -ECS_DECLARE(EcsYobi); - -ECS_DECLARE(EcsDuration); - ECS_DECLARE(EcsPicoSeconds); - ECS_DECLARE(EcsNanoSeconds); - ECS_DECLARE(EcsMicroSeconds); - ECS_DECLARE(EcsMilliSeconds); - ECS_DECLARE(EcsSeconds); - ECS_DECLARE(EcsMinutes); - ECS_DECLARE(EcsHours); - ECS_DECLARE(EcsDays); - -ECS_DECLARE(EcsTime); - ECS_DECLARE(EcsDate); - -ECS_DECLARE(EcsMass); - ECS_DECLARE(EcsGrams); - ECS_DECLARE(EcsKiloGrams); - -ECS_DECLARE(EcsElectricCurrent); - ECS_DECLARE(EcsAmpere); - -ECS_DECLARE(EcsAmount); - ECS_DECLARE(EcsMole); - -ECS_DECLARE(EcsLuminousIntensity); - ECS_DECLARE(EcsCandela); - -ECS_DECLARE(EcsForce); - ECS_DECLARE(EcsNewton); - -ECS_DECLARE(EcsLength); - ECS_DECLARE(EcsMeters); - ECS_DECLARE(EcsPicoMeters); - ECS_DECLARE(EcsNanoMeters); - ECS_DECLARE(EcsMicroMeters); - ECS_DECLARE(EcsMilliMeters); - ECS_DECLARE(EcsCentiMeters); - ECS_DECLARE(EcsKiloMeters); - ECS_DECLARE(EcsMiles); - ECS_DECLARE(EcsPixels); - -ECS_DECLARE(EcsPressure); - ECS_DECLARE(EcsPascal); - ECS_DECLARE(EcsBar); - -ECS_DECLARE(EcsSpeed); - ECS_DECLARE(EcsMetersPerSecond); - ECS_DECLARE(EcsKiloMetersPerSecond); - ECS_DECLARE(EcsKiloMetersPerHour); - ECS_DECLARE(EcsMilesPerHour); - -ECS_DECLARE(EcsAcceleration); - -ECS_DECLARE(EcsTemperature); - ECS_DECLARE(EcsKelvin); - ECS_DECLARE(EcsCelsius); - ECS_DECLARE(EcsFahrenheit); - -ECS_DECLARE(EcsData); - ECS_DECLARE(EcsBits); - ECS_DECLARE(EcsKiloBits); - ECS_DECLARE(EcsMegaBits); - ECS_DECLARE(EcsGigaBits); - ECS_DECLARE(EcsBytes); - ECS_DECLARE(EcsKiloBytes); - ECS_DECLARE(EcsMegaBytes); - ECS_DECLARE(EcsGigaBytes); - ECS_DECLARE(EcsKibiBytes); - ECS_DECLARE(EcsGibiBytes); - ECS_DECLARE(EcsMebiBytes); - -ECS_DECLARE(EcsDataRate); - ECS_DECLARE(EcsBitsPerSecond); - ECS_DECLARE(EcsKiloBitsPerSecond); - ECS_DECLARE(EcsMegaBitsPerSecond); - ECS_DECLARE(EcsGigaBitsPerSecond); - ECS_DECLARE(EcsBytesPerSecond); - ECS_DECLARE(EcsKiloBytesPerSecond); - ECS_DECLARE(EcsMegaBytesPerSecond); - ECS_DECLARE(EcsGigaBytesPerSecond); - -ECS_DECLARE(EcsPercentage); - -ECS_DECLARE(EcsAngle); - ECS_DECLARE(EcsRadians); - ECS_DECLARE(EcsDegrees); - -ECS_DECLARE(EcsBel); -ECS_DECLARE(EcsDeciBel); - -ECS_DECLARE(EcsFrequency); - ECS_DECLARE(EcsHertz); - ECS_DECLARE(EcsKiloHertz); - ECS_DECLARE(EcsMegaHertz); - ECS_DECLARE(EcsGigaHertz); - -ECS_DECLARE(EcsUri); - ECS_DECLARE(EcsUriHyperlink); - ECS_DECLARE(EcsUriImage); - ECS_DECLARE(EcsUriFile); - void FlecsUnitsImport( ecs_world_t *world) { @@ -29863,7 +31316,7 @@ void FlecsUnitsImport( ecs_set_name_prefix(world, "Ecs"); - EcsUnitPrefixes = ecs_entity_init(world, &(ecs_entity_desc_t){ + EcsUnitPrefixes = ecs_entity(world, { .name = "prefixes", .add = { EcsModule } }); @@ -30940,7 +32393,7 @@ ecs_snapshot_t* snapshot_create( snapshot_table(world, result, table); } } else { - for (t = 0; t < table_count; t ++) { + for (t = 1; t < table_count; t ++) { ecs_table_t *table = flecs_sparse_get_t( &world->store.tables, ecs_table_t, t); snapshot_table(world, result, table); @@ -30995,7 +32448,7 @@ void restore_unfiltered( int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables); int32_t snapshot_count = ecs_vec_count(&snapshot->tables); - for (i = 0; i <= count; i ++) { + for (i = 1; i <= count; i ++) { ecs_table_t *world_table = flecs_sparse_get_t( &world->store.tables, ecs_table_t, (uint32_t)i); @@ -31581,6 +33034,21 @@ ecs_entity_t ecs_system_init( if (desc->no_readonly) { system->no_readonly = desc->no_readonly; } + + if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { +#ifdef FLECS_TIMER + if (desc->interval != 0) { + ecs_set_interval(world, entity, desc->interval); + } + if (desc->rate != 0) { + ecs_set_rate(world, entity, desc->rate, desc->tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, entity, desc->tick_source); + } +#else + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif + } } ecs_poly_modified(world, entity, ecs_system_t); @@ -33195,6 +34663,41 @@ int ecs_entity_to_json_buf( goto error; } + if (desc && desc->serialize_alerts) { +#ifdef FLECS_ALERTS + const EcsAlertsActive *alerts = ecs_get(world, entity, EcsAlertsActive); + if (alerts) { + flecs_json_memberl(buf, "alerts"); + flecs_json_array_push(buf); + ecs_map_iter_t it = ecs_map_iter(&alerts->alerts); + while (ecs_map_next(&it)) { + flecs_json_next(buf); + flecs_json_object_push(buf); + ecs_entity_t ai = ecs_map_value(&it); + char *alert_name = ecs_get_fullpath(world, ai); + flecs_json_memberl(buf, "alert"); + flecs_json_string(buf, alert_name); + ecs_os_free(alert_name); + + ecs_entity_t severity_id = ecs_get_target( + world, ai, ecs_id(EcsAlert), 0); + const char *severity = ecs_get_name(world, severity_id); + + const EcsAlertInstance *alert = ecs_get( + world, ai, EcsAlertInstance); + if (alert) { + flecs_json_memberl(buf, "message"); + flecs_json_string(buf, alert->message); + flecs_json_memberl(buf, "severity"); + flecs_json_string(buf, severity); + } + flecs_json_object_pop(buf); + } + flecs_json_array_pop(buf); + } +#endif + } + flecs_json_object_pop(buf); return 0; @@ -33921,6 +35424,17 @@ int flecs_json_serialize_iter_result( } } + /* Add "alerts": true member if table has entities with active alerts */ +#ifdef FLECS_ALERTS + if (it->table && (ecs_id(EcsAlertsActive) != 0)) { + /* Only add field if alerts addon is imported */ + if (ecs_table_has_id(world, it->table, ecs_id(EcsAlertsActive))) { + flecs_json_memberl(buf, "alerts"); + flecs_json_true(buf); + } + } +#endif + flecs_json_object_pop(buf); return 0; @@ -34250,12 +35764,28 @@ int json_typeinfo_ser_unit( return 0; } +static +void json_typeinfo_ser_range( + ecs_strbuf_t *str, + const char *kind, + ecs_member_value_range_t *range) +{ + flecs_json_member(str, kind); + flecs_json_array_push(str); + flecs_json_next(str); + flecs_json_number(str, range->min); + flecs_json_next(str); + flecs_json_number(str, range->max); + flecs_json_array_pop(str); +} + /* Forward serialization to the different type kinds */ static int json_typeinfo_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, - ecs_strbuf_t *str) + ecs_strbuf_t *str, + const EcsStruct *st) { if (op->kind == EcsOpOpaque) { const EcsOpaque *ct = ecs_get(world, op->type, @@ -34299,14 +35829,36 @@ int json_typeinfo_ser_type_op( break; } - ecs_entity_t unit = op->unit; - if (unit) { - flecs_json_next(str); - flecs_json_next(str); + if (st) { + ecs_member_t *m = ecs_vec_get_t( + &st->members, ecs_member_t, op->member_index); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_json_object_push(str); - json_typeinfo_ser_unit(world, str, unit); - flecs_json_object_pop(str); + bool value_range = m->range.min != m->range.max; + bool error_range = m->error_range.min != m->error_range.max; + bool warning_range = m->warning_range.min != m->warning_range.max; + + ecs_entity_t unit = m->unit; + if (unit || error_range || warning_range || value_range) { + flecs_json_next(str); + flecs_json_next(str); + flecs_json_object_push(str); + + if (unit) { + json_typeinfo_ser_unit(world, str, unit); + } + if (value_range) { + json_typeinfo_ser_range(str, "range", &m->range); + } + if (error_range) { + json_typeinfo_ser_range(str, "error_range", &m->error_range); + } + if (warning_range) { + json_typeinfo_ser_range(str, "warning_range", &m->warning_range); + } + + flecs_json_object_pop(str); + } } flecs_json_array_pop(str); @@ -34322,7 +35874,8 @@ int json_typeinfo_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, - ecs_strbuf_t *str) + ecs_strbuf_t *str, + const EcsStruct *st) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; @@ -34350,7 +35903,7 @@ int json_typeinfo_ser_type_ops( flecs_json_object_pop(str); break; default: - if (json_typeinfo_ser_type_op(world, op, str)) { + if (json_typeinfo_ser_type_op(world, op, str, st)) { goto error; } break; @@ -34381,10 +35934,11 @@ int json_typeinfo_ser_type( return 0; } + const EcsStruct *st = ecs_get(world, type, EcsStruct); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(&ser->ops); - return json_typeinfo_ser_type_ops(world, ops, count, buf); + return json_typeinfo_ser_type_ops(world, ops, count, buf, st); } int ecs_type_info_to_json_buf( @@ -35610,6 +37164,7 @@ void flecs_rest_parse_json_ser_entity_params( flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "private", &desc->serialize_private); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts); } static @@ -36292,7 +37847,7 @@ void flecs_rest_reply_table_append( ecs_strbuf_list_append(reply, "\"count\":%d", ecs_table_count(table)); ecs_strbuf_list_append(reply, "\"memory\":"); flecs_rest_reply_table_append_memory(reply, table); - ecs_strbuf_list_append(reply, "\"refcount\":%d", table->refcount); + ecs_strbuf_list_append(reply, "\"refcount\":%d", table->_->refcount); ecs_strbuf_list_pop(reply, "}"); } @@ -36735,6 +38290,9 @@ typedef SOCKET ecs_http_socket_t; #include #include #include +#ifdef __FreeBSD__ +#include +#endif typedef int ecs_http_socket_t; #endif @@ -38355,6 +39913,7 @@ typedef enum { typedef struct ecs_rule_var_t { int8_t kind; /* variable kind (EcsVarEntity or EcsVarTable)*/ + bool anonymous; /* variable is anonymous */ ecs_var_id_t id; /* variable id */ ecs_var_id_t table_id; /* id to table variable, if any */ const char *name; /* variable name */ @@ -38506,12 +40065,7 @@ typedef struct ecs_rule_op_ctx_t { } is; } ecs_rule_op_ctx_t; -/* Rule compiler state */ typedef struct { - ecs_vec_t *ops; - ecs_write_flags_t written; /* Bitmask to check which variables have been written */ - ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */ - /* Labels used for control flow */ ecs_rule_lbl_t lbl_union; ecs_rule_lbl_t lbl_not; @@ -38520,6 +40074,20 @@ typedef struct { ecs_rule_lbl_t lbl_or; ecs_rule_lbl_t lbl_none; ecs_rule_lbl_t lbl_prev; /* If set, use this as default value for prev */ +} ecs_rule_compile_ctrlflow_t; + +/* Rule compiler state */ +typedef struct { + ecs_vec_t *ops; + ecs_write_flags_t written; /* Bitmask to check which variables have been written */ + ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */ + + /* Maintain control flow per scope */ + ecs_rule_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX]; + ecs_rule_compile_ctrlflow_t *cur; /* Current scope */ + + int32_t scope; /* Nesting level of query scopes */ + ecs_flags32_t scope_is_not; /* Whether scope is prefixed with not */ } ecs_rule_compile_ctx_t; /* Rule run state */ @@ -38698,8 +40266,13 @@ void flecs_rule_write_ctx( { bool is_written = flecs_rule_is_written(var_id, ctx->written); flecs_rule_write(var_id, &ctx->written); - if (!is_written && cond_write) { - flecs_rule_write(var_id, &ctx->cond_written); + if (!is_written) { + if (cond_write) { + flecs_rule_write(var_id, &ctx->cond_written); + } + if (ctx->scope != 0) { + + } } } @@ -38734,7 +40307,7 @@ bool flecs_rule_var_is_anonymous( ecs_var_id_t var_id) { ecs_rule_var_t *var = &rule->vars[var_id]; - return var->name && var->name[0] == '_'; + return var->anonymous; } static @@ -38927,6 +40500,7 @@ ecs_var_id_t flecs_rule_add_var( if (name) { flecs_name_index_init_if(var_index, NULL); flecs_name_index_ensure(var_index, var->id, name, 0, 0); + var->anonymous = name ? name[0] == '_' : false; } return var->id; @@ -38957,7 +40531,7 @@ void flecs_rule_discover_vars( ecs_term_t *terms = rule->filter.terms; int32_t i, anonymous_count = 0, count = rule->filter.term_count; - int32_t anonymous_table_count = 0; + int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; bool table_this = false, entity_before_table_this = false; /* For This table lookups during discovery. This will be overwritten after @@ -38970,6 +40544,28 @@ void flecs_rule_discover_vars( ecs_term_id_t *second = &term->second; ecs_term_id_t *src = &term->src; + if (first->id == EcsScopeOpen) { + /* Keep track of which variables are first used in scope, so that we + * can mark them as anonymous. Terms inside a scope are collapsed + * into a single result, which means that outside of the scope the + * value of those variables is undefined. */ + if (!scope) { + scoped_var_index = ecs_vec_count(vars); + } + scope ++; + continue; + } else if (first->id == EcsScopeClose) { + if (!--scope) { + /* Any new variables declared after entering a scope should be + * marked as anonymous. */ + int32_t v; + for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { + ecs_vec_get_t(vars, ecs_rule_var_t, v)->anonymous = true; + } + } + continue; + } + ecs_var_id_t first_var_id = flecs_rule_add_var_for_term_id( rule, first, vars, EcsVarEntity); if (first_var_id == EcsVarNone) { @@ -39159,18 +40755,18 @@ ecs_rule_lbl_t flecs_rule_op_insert( int32_t count = ecs_vec_count(ctx->ops); *elem = *op; if (count > 1) { - if (ctx->lbl_union == -1) { + if (ctx->cur->lbl_union == -1) { /* Variables written by previous instruction can't be written by * this instruction, except when this is a union. */ elem->written &= ~elem[-1].written; } } - if (ctx->lbl_union != -1) { - elem->prev = ctx->lbl_union; - } else if (ctx->lbl_prev != -1) { - elem->prev = ctx->lbl_prev; - ctx->lbl_prev = -1; + if (ctx->cur->lbl_union != -1) { + elem->prev = ctx->cur->lbl_union; + } else if (ctx->cur->lbl_prev != -1) { + elem->prev = ctx->cur->lbl_prev; + ctx->cur->lbl_prev = -1; } else { elem->prev = flecs_itolbl(count - 2); } @@ -39211,7 +40807,7 @@ static void flecs_rule_begin_none( ecs_rule_compile_ctx_t *ctx) { - ctx->lbl_none = flecs_itolbl(ecs_vec_count(ctx->ops)); + ctx->cur->lbl_none = flecs_itolbl(ecs_vec_count(ctx->ops)); ecs_rule_op_t jmp = {0}; jmp.kind = EcsRuleJmpCondFalse; @@ -39222,18 +40818,19 @@ static void flecs_rule_begin_not( ecs_rule_compile_ctx_t *ctx) { - ctx->lbl_not = flecs_itolbl(ecs_vec_count(ctx->ops)); + ctx->cur->lbl_not = flecs_itolbl(ecs_vec_count(ctx->ops)); } static void flecs_rule_end_not( ecs_rule_op_t *op, - ecs_rule_compile_ctx_t *ctx) + ecs_rule_compile_ctx_t *ctx, + bool update_labels) { - if (ctx->lbl_none != -1) { + if (ctx->cur->lbl_none != -1) { ecs_rule_op_t setcond = {0}; setcond.kind = EcsRuleSetCond; - setcond.other = ctx->lbl_none; + setcond.other = ctx->cur->lbl_none; flecs_rule_op_insert(&setcond, ctx); } @@ -39241,41 +40838,45 @@ void flecs_rule_end_not( ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t i, count = ecs_vec_count(ctx->ops); - for (i = ctx->lbl_not; i < count; i ++) { - ecs_rule_op_t *cur = &ops[i]; - if (flecs_rule_op_is_test[cur->kind]) { - cur->prev = flecs_itolbl(count - 1); - if (i == (count - 2)) { - cur->next = flecs_itolbl(ctx->lbl_not - 1); + if (update_labels) { + for (i = ctx->cur->lbl_not; i < count; i ++) { + ecs_rule_op_t *cur = &ops[i]; + if (flecs_rule_op_is_test[cur->kind]) { + cur->prev = flecs_itolbl(count - 1); + if (i == (count - 2)) { + cur->next = flecs_itolbl(ctx->cur->lbl_not - 1); + } } } } /* After a match was found, return to op before Not operation */ ecs_rule_op_t *not_ptr = ecs_vec_last_t(ctx->ops, ecs_rule_op_t); - not_ptr->prev = flecs_itolbl(ctx->lbl_not - 1); + not_ptr->prev = flecs_itolbl(ctx->cur->lbl_not - 1); - if (ctx->lbl_none != -1) { + if (ctx->cur->lbl_none != -1) { /* setcond */ - ops[count - 2].next = flecs_itolbl(ctx->lbl_none - 1); + ops[count - 2].next = flecs_itolbl(ctx->cur->lbl_none - 1); /* last actual instruction */ - ops[count - 3].prev = flecs_itolbl(count - 4); + if (update_labels) { + ops[count - 3].prev = flecs_itolbl(count - 4); + } /* jfalse */ - ops[ctx->lbl_none].other = + ops[ctx->cur->lbl_none].other = flecs_itolbl(count - 1); /* jump to not */ /* not */ - ops[count - 1].prev = flecs_itolbl(ctx->lbl_none - 1); + ops[count - 1].prev = flecs_itolbl(ctx->cur->lbl_none - 1); } - ctx->lbl_not = -1; - ctx->lbl_none = -1; + ctx->cur->lbl_not = -1; + ctx->cur->lbl_none = -1; } static void flecs_rule_begin_option( ecs_rule_compile_ctx_t *ctx) { - ctx->lbl_option = flecs_itolbl(ecs_vec_count(ctx->ops)); + ctx->cur->lbl_option = flecs_itolbl(ecs_vec_count(ctx->ops)); { ecs_rule_op_t new_op = {0}; @@ -39293,15 +40894,15 @@ void flecs_rule_end_option( ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t count = ecs_vec_count(ctx->ops); - ops[ctx->lbl_option].other = flecs_itolbl(count - 1); + ops[ctx->cur->lbl_option].other = flecs_itolbl(count - 1); ops[count - 2].next = flecs_itolbl(count); ecs_rule_op_t new_op = {0}; new_op.kind = EcsRuleSetCond; - new_op.other = ctx->lbl_option; + new_op.other = ctx->cur->lbl_option; flecs_rule_op_insert(&new_op, ctx); - ctx->lbl_option = -1; + ctx->cur->lbl_option = -1; } static @@ -39329,7 +40930,7 @@ void flecs_rule_begin_cond_eval( /* If this term uses conditionally set variables, insert instruction that * jumps over the term if the variables weren't set yet. */ if (cond_mask & cond_write_state) { - ctx->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); + ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); ecs_rule_op_t jmp_op = {0}; jmp_op.kind = EcsRuleJmpNotSet; @@ -39349,7 +40950,7 @@ void flecs_rule_begin_cond_eval( flecs_rule_op_insert(&jmp_op, ctx); } else { - ctx->lbl_cond_eval = -1; + ctx->cur->lbl_cond_eval = -1; } } @@ -39358,7 +40959,7 @@ void flecs_rule_end_cond_eval( ecs_rule_op_t *op, ecs_rule_compile_ctx_t *ctx) { - if (ctx->lbl_cond_eval == -1) { + if (ctx->cur->lbl_cond_eval == -1) { return; } @@ -39366,7 +40967,7 @@ void flecs_rule_end_cond_eval( ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t count = ecs_vec_count(ctx->ops); - ops[ctx->lbl_cond_eval].other = flecs_itolbl(count - 1); + ops[ctx->cur->lbl_cond_eval].other = flecs_itolbl(count - 1); ops[count - 2].next = flecs_itolbl(count); } @@ -39383,7 +40984,7 @@ static void flecs_rule_begin_or( ecs_rule_compile_ctx_t *ctx) { - ctx->lbl_or = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); + ctx->cur->lbl_or = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); flecs_rule_next_or(ctx); } @@ -39396,7 +40997,7 @@ void flecs_rule_end_or( ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t i, count = ecs_vec_count(ctx->ops); int32_t prev_or = -2; - for (i = ctx->lbl_or; i < count; i ++) { + for (i = ctx->cur->lbl_or; i < count; i ++) { if (ops[i].next == FlecsRuleOrMarker) { if (prev_or != -2) { ops[prev_or].prev = flecs_itolbl(i); @@ -39406,11 +41007,11 @@ void flecs_rule_end_or( } } - ops[count - 1].prev = flecs_itolbl(ctx->lbl_or - 1); + ops[count - 1].prev = flecs_itolbl(ctx->cur->lbl_or - 1); /* Set prev of next instruction to before the start of the OR chain */ - ctx->lbl_prev = flecs_itolbl(ctx->lbl_or - 1); - ctx->lbl_or = -1; + ctx->cur->lbl_prev = flecs_itolbl(ctx->cur->lbl_or - 1); + ctx->cur->lbl_or = -1; } static @@ -39419,7 +41020,7 @@ void flecs_rule_begin_union( { ecs_rule_op_t op = {0}; op.kind = EcsRuleUnion; - ctx->lbl_union = flecs_rule_op_insert(&op, ctx); + ctx->cur->lbl_union = flecs_rule_op_insert(&op, ctx); } static @@ -39430,7 +41031,7 @@ void flecs_rule_end_union( ecs_rule_op_t op = {0}; op.kind = EcsRuleEnd; - ctx->lbl_union = -1; + ctx->cur->lbl_union = -1; ecs_rule_lbl_t next = flecs_rule_op_insert(&op, ctx); ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); @@ -39732,6 +41333,24 @@ int flecs_rule_compile_builtin_pred( return 0; } +static +void flecs_rule_compile_push( + ecs_rule_compile_ctx_t *ctx) +{ + ctx->cur = &ctx->ctrlflow[++ ctx->scope]; + ctx->cur->lbl_union = -1; + ctx->cur->lbl_prev = -1; + ctx->cur->lbl_not = -1; + ctx->cur->lbl_none = -1; +} + +static +void flecs_rule_compile_pop( + ecs_rule_compile_ctx_t *ctx) +{ + ctx->cur = &ctx->ctrlflow[-- ctx->scope]; +} + static int flecs_rule_compile_term( ecs_world_t *world, @@ -39749,7 +41368,22 @@ int flecs_rule_compile_term( ecs_rule_op_t op = {0}; if (!term->src.id && term->src.flags & EcsIsEntity) { - /* If the term has a 0 source, don't insert operation */ + /* If the term has a 0 source, check if it's a scope open/close */ + if (term->first.id == EcsScopeOpen) { + if (term->oper == EcsNot) { + ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); + } else { + ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); + } + } else if (term->first.id == EcsScopeClose) { + flecs_rule_compile_pop(ctx); + if (ctx->scope_is_not & (ecs_flags32_t)(1ull << ctx->scope)) { + op.field_index = -1; + flecs_rule_end_not(&op, ctx, false); + } + } else { + /* Nothing to be done */ + } return 0; } @@ -39900,6 +41534,16 @@ int flecs_rule_compile_term( flecs_rule_insert_inheritance(rule, term, &op, ctx, cond_write); } + /* If previous term was ScopeOpen with a Not operator, insert operation to + * ensure that none of the results inside the scope should match. */ + if (!first_term && term[-1].first.id == EcsScopeOpen) { + if (term[-1].oper == EcsNot) { + flecs_rule_begin_none(ctx); + flecs_rule_begin_not(ctx); + } + flecs_rule_compile_push(ctx); + } + /* Handle Not, Optional, Or operators */ if (is_not) { flecs_rule_begin_not(ctx); @@ -39964,15 +41608,15 @@ int flecs_rule_compile_term( /* Restore original first id in case it got replaced with a variable */ op.first = prev_first; op.flags = prev_op_flags; - flecs_rule_end_not(&op, ctx); + flecs_rule_end_not(&op, ctx, true); } else if (term->oper == EcsOptional) { flecs_rule_end_option(&op, ctx); } else if (term->oper == EcsOr) { - if (ctx->lbl_union != -1) { + if (ctx->cur->lbl_union != -1) { flecs_rule_next_or(ctx); } else { if (first_term || term[-1].oper != EcsOr) { - if (ctx->lbl_union == -1) { + if (ctx->cur->lbl_union == -1) { flecs_rule_begin_or(ctx); } } else if (term->oper == EcsOr) { @@ -39981,7 +41625,7 @@ int flecs_rule_compile_term( } } else if (term->oper == EcsAnd) { if (!first_term && term[-1].oper == EcsOr) { - if (ctx->lbl_union != -1) { + if (ctx->cur->lbl_union != -1) { flecs_rule_end_union(ctx); } else { flecs_rule_end_or(ctx); @@ -40002,10 +41646,11 @@ int flecs_rule_compile( ecs_rule_compile_ctx_t ctx = {0}; ecs_vec_reset_t(NULL, &stage->operations, ecs_rule_op_t); ctx.ops = &stage->operations; - ctx.lbl_union = -1; - ctx.lbl_prev = -1; - ctx.lbl_not = -1; - ctx.lbl_none = -1; + ctx.cur = ctx.ctrlflow; + ctx.cur->lbl_union = -1; + ctx.cur->lbl_prev = -1; + ctx.cur->lbl_not = -1; + ctx.cur->lbl_none = -1; ecs_vec_clear(ctx.ops); /* Find all variables defined in query */ @@ -40086,6 +41731,19 @@ int flecs_rule_compile( if (!flecs_rule_var_is_anonymous(rule, var_id)) { only_anonymous = false; break; + } else { + /* Don't fetch component data for anonymous variables. Because + * not all metadata (such as it.sources) is initialized for + * anonymous variables, and because they may only be available + * as table variables (each is not guaranteed to be inserted for + * anonymous variables) the iterator may not have sufficient + * information to resolve component data. */ + for (int32_t t = 0; t < filter->term_count; t ++) { + ecs_term_t *term = &filter->terms[t]; + if (term->field_index == i) { + term->inout = EcsInOutNone; + } + } } } @@ -40571,7 +42229,7 @@ void flecs_rule_build_down_cache( while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = tr->hdr.table; - if (!table->observed_count) { + if (!table->_->traversable_count) { continue; } @@ -42557,7 +44215,8 @@ ecs_iter_t ecs_rule_iter( { ecs_iter_t it = {0}; ecs_rule_iter_t *rit = &it.priv.iter.rule; - + ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_run_aperiodic(rule->filter.world, EcsAperiodicEmptyTables); int32_t i, var_count = rule->var_count, op_count = rule->op_count; @@ -42599,6 +44258,7 @@ ecs_iter_t ecs_rule_iter( it.variable_count = rule->var_pub_count; it.variable_names = rule->var_names; +error: return it; } @@ -42788,6 +44448,8 @@ void FlecsDocImport( #define TOK_BITWISE_OR '|' #define TOK_BRACKET_OPEN '[' #define TOK_BRACKET_CLOSE ']' +#define TOK_SCOPE_OPEN '{' +#define TOK_SCOPE_CLOSE '}' #define TOK_WILDCARD '*' #define TOK_VARIABLE '$' #define TOK_PAREN_OPEN '(' @@ -43390,7 +45052,7 @@ const char* flecs_parse_term( /* If next token is the start of an identifier, it could be either a type * role, source or component identifier */ - if (flecs_valid_token_start_char(ptr[0])) { + if (flecs_valid_identifier_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { goto error; @@ -43414,6 +45076,23 @@ const char* flecs_parse_term( } else if (ptr[0] == TOK_PAREN_OPEN) { goto parse_pair; + /* Open query scope */ + } else if (ptr[0] == TOK_SCOPE_OPEN) { + term.first.id = EcsScopeOpen; + term.src.id = 0; + term.src.flags = EcsIsEntity; + term.inout = EcsInOutNone; + goto parse_done; + + /* Close query scope */ + } else if (ptr[0] == TOK_SCOPE_CLOSE) { + term.first.id = EcsScopeClose; + term.src.id = 0; + term.src.flags = EcsIsEntity; + term.inout = EcsInOutNone; + ptr = ecs_parse_ws(ptr + 1); + goto parse_done; + /* Nothing else expected here */ } else { flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); @@ -43639,14 +45318,7 @@ parse_pair_object: goto error; } - if (term.id_flags != 0) { - if (!ECS_HAS_ID_FLAG(term.id_flags, PAIR)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of role '%s' with pair", - ecs_id_flag_str(term.id_flags)); - goto error; - } - } else { + if (term.id_flags == 0) { term.id_flags = ECS_PAIR; } @@ -43701,6 +45373,10 @@ char* ecs_parse_term( ptr ++; } else if (ptr[0] == '|') { ptr += 2; + } else if (ptr[0] == '{') { + ptr ++; + } else if (ptr[0] == '}') { + /* nothing to be done */ } else { ecs_parser_error(name, expr, (ptr - expr), "invalid preceding token"); @@ -43790,9 +45466,11 @@ char* ecs_parse_term( /* Cannot combine EcsIsEntity/0 with operators other than AND */ if (term->oper != EcsAnd && ecs_term_match_0(term)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid operator for empty source"); - goto error; + if (term->first.id != EcsScopeOpen && term->first.id != EcsScopeClose) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator for empty source"); + goto error; + } } /* Automatically assign This if entity is not assigned and the set is @@ -43836,7 +45514,7 @@ error: #endif /** - * @file addons/meta.c + * @file addons/meta_c.c * @brief C utilities for meta addon. */ @@ -44527,7 +46205,7 @@ int meta_parse_struct( ecs_entity_t old_scope = ecs_set_scope(world, t); while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { - ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t){ + ecs_entity_t m = ecs_entity(world, { .name = token.name }); @@ -44583,7 +46261,7 @@ int meta_parse_constants( goto error; } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ + ecs_entity_t c = ecs_entity(world, { .name = token.name }); @@ -44911,6 +46589,8 @@ const ecs_entity_t EcsDefaultChildComponent = FLECS_HI_COMPONENT_ID + 55; const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 56; const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 57; const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 58; +const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 59; +const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 60; /* Systems */ const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 61; @@ -44955,28 +46635,174 @@ const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 99; const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 100; const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 101; const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 102; -const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 103; -const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 104; -const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 105; -const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 106; -const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 107; -const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 108; -const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 109; -const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 110; +const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 103; +const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 104; +const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 105; +const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 106; +const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 107; +const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 108; +const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 109; +const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 110; +const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 111; /* Doc module components */ -const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 111; -const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 112; -const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 113; -const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 114; -const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 115; +const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 112; +const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 113; +const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 114; +const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 115; +const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 116; /* REST module components */ -const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 116; +const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 117; /* Default lookup path */ static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; +/* Declarations for addons. Located in world.c to avoid issues during linking of + * static library */ +#ifdef FLECS_ALERTS +ECS_COMPONENT_DECLARE(EcsAlert); +ECS_COMPONENT_DECLARE(EcsAlertInstance); +ECS_COMPONENT_DECLARE(EcsAlertsActive); +ECS_TAG_DECLARE(EcsAlertInfo); +ECS_TAG_DECLARE(EcsAlertWarning); +ECS_TAG_DECLARE(EcsAlertError); +ECS_TAG_DECLARE(EcsAlertCritical); +#endif +#ifdef FLECS_UNITS +ECS_DECLARE(EcsUnitPrefixes); + +ECS_DECLARE(EcsYocto); +ECS_DECLARE(EcsZepto); +ECS_DECLARE(EcsAtto); +ECS_DECLARE(EcsFemto); +ECS_DECLARE(EcsPico); +ECS_DECLARE(EcsNano); +ECS_DECLARE(EcsMicro); +ECS_DECLARE(EcsMilli); +ECS_DECLARE(EcsCenti); +ECS_DECLARE(EcsDeci); +ECS_DECLARE(EcsDeca); +ECS_DECLARE(EcsHecto); +ECS_DECLARE(EcsKilo); +ECS_DECLARE(EcsMega); +ECS_DECLARE(EcsGiga); +ECS_DECLARE(EcsTera); +ECS_DECLARE(EcsPeta); +ECS_DECLARE(EcsExa); +ECS_DECLARE(EcsZetta); +ECS_DECLARE(EcsYotta); + +ECS_DECLARE(EcsKibi); +ECS_DECLARE(EcsMebi); +ECS_DECLARE(EcsGibi); +ECS_DECLARE(EcsTebi); +ECS_DECLARE(EcsPebi); +ECS_DECLARE(EcsExbi); +ECS_DECLARE(EcsZebi); +ECS_DECLARE(EcsYobi); + +ECS_DECLARE(EcsDuration); + ECS_DECLARE(EcsPicoSeconds); + ECS_DECLARE(EcsNanoSeconds); + ECS_DECLARE(EcsMicroSeconds); + ECS_DECLARE(EcsMilliSeconds); + ECS_DECLARE(EcsSeconds); + ECS_DECLARE(EcsMinutes); + ECS_DECLARE(EcsHours); + ECS_DECLARE(EcsDays); + +ECS_DECLARE(EcsTime); + ECS_DECLARE(EcsDate); + +ECS_DECLARE(EcsMass); + ECS_DECLARE(EcsGrams); + ECS_DECLARE(EcsKiloGrams); + +ECS_DECLARE(EcsElectricCurrent); + ECS_DECLARE(EcsAmpere); + +ECS_DECLARE(EcsAmount); + ECS_DECLARE(EcsMole); + +ECS_DECLARE(EcsLuminousIntensity); + ECS_DECLARE(EcsCandela); + +ECS_DECLARE(EcsForce); + ECS_DECLARE(EcsNewton); + +ECS_DECLARE(EcsLength); + ECS_DECLARE(EcsMeters); + ECS_DECLARE(EcsPicoMeters); + ECS_DECLARE(EcsNanoMeters); + ECS_DECLARE(EcsMicroMeters); + ECS_DECLARE(EcsMilliMeters); + ECS_DECLARE(EcsCentiMeters); + ECS_DECLARE(EcsKiloMeters); + ECS_DECLARE(EcsMiles); + ECS_DECLARE(EcsPixels); + +ECS_DECLARE(EcsPressure); + ECS_DECLARE(EcsPascal); + ECS_DECLARE(EcsBar); + +ECS_DECLARE(EcsSpeed); + ECS_DECLARE(EcsMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerHour); + ECS_DECLARE(EcsMilesPerHour); + +ECS_DECLARE(EcsAcceleration); + +ECS_DECLARE(EcsTemperature); + ECS_DECLARE(EcsKelvin); + ECS_DECLARE(EcsCelsius); + ECS_DECLARE(EcsFahrenheit); + +ECS_DECLARE(EcsData); + ECS_DECLARE(EcsBits); + ECS_DECLARE(EcsKiloBits); + ECS_DECLARE(EcsMegaBits); + ECS_DECLARE(EcsGigaBits); + ECS_DECLARE(EcsBytes); + ECS_DECLARE(EcsKiloBytes); + ECS_DECLARE(EcsMegaBytes); + ECS_DECLARE(EcsGigaBytes); + ECS_DECLARE(EcsKibiBytes); + ECS_DECLARE(EcsGibiBytes); + ECS_DECLARE(EcsMebiBytes); + +ECS_DECLARE(EcsDataRate); + ECS_DECLARE(EcsBitsPerSecond); + ECS_DECLARE(EcsKiloBitsPerSecond); + ECS_DECLARE(EcsMegaBitsPerSecond); + ECS_DECLARE(EcsGigaBitsPerSecond); + ECS_DECLARE(EcsBytesPerSecond); + ECS_DECLARE(EcsKiloBytesPerSecond); + ECS_DECLARE(EcsMegaBytesPerSecond); + ECS_DECLARE(EcsGigaBytesPerSecond); + +ECS_DECLARE(EcsPercentage); + +ECS_DECLARE(EcsAngle); + ECS_DECLARE(EcsRadians); + ECS_DECLARE(EcsDegrees); + +ECS_DECLARE(EcsBel); +ECS_DECLARE(EcsDeciBel); + +ECS_DECLARE(EcsFrequency); + ECS_DECLARE(EcsHertz); + ECS_DECLARE(EcsKiloHertz); + ECS_DECLARE(EcsMegaHertz); + ECS_DECLARE(EcsGigaHertz); + +ECS_DECLARE(EcsUri); + ECS_DECLARE(EcsUriHyperlink); + ECS_DECLARE(EcsUriImage); + ECS_DECLARE(EcsUriFile); +#endif + /* -- Private functions -- */ const ecs_stage_t* flecs_stage_from_readonly_world( @@ -45237,7 +47063,6 @@ void flecs_clean_tables( * that only exists so that there is no table with id 0 */ ecs_table_t *first = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, 0); - ecs_assert(first->id == 0, ECS_INTERNAL_ERROR, NULL); (void)first; for (i = 1; i < count; i ++) { @@ -46639,30 +48464,32 @@ void flecs_process_pending_tables( /* For each id in the table, add it to the empty/non empty list * based on its current state */ if (flecs_table_records_update_empty(table)) { - /* Only emit an event when there was a change in the - * administration. It is possible that a table ended up in the - * pending_tables list by going from empty->non-empty, but then - * became empty again. By the time we run this code, no changes - * in the administration would actually be made. */ int32_t table_count = ecs_table_count(table); - ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; - if (ecs_should_log_3()) { - ecs_dbg_3("table %u state change (%s)", (uint32_t)table->id, - table_count ? "non-empty" : "empty"); + if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { + /* Only emit an event when there was a change in the + * administration. It is possible that a table ended up in the + * pending_tables list by going from empty->non-empty, but then + * became empty again. By the time we run this code, no changes + * in the administration would actually be made. */ + ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; + if (ecs_should_log_3()) { + ecs_dbg_3("table %u state change (%s)", + (uint32_t)table->id, + table_count ? "non-empty" : "empty"); + } + + ecs_log_push_3(); + + flecs_emit(world, world, &(ecs_event_desc_t){ + .event = evt, + .table = table, + .ids = &table->type, + .observable = world, + .flags = EcsEventTableOnly + }); + + ecs_log_pop_3(); } - - ecs_log_push_3(); - - flecs_emit(world, world, &(ecs_event_desc_t){ - .event = evt, - .table = table, - .ids = &table->type, - .observable = world, - .flags = EcsEventTableOnly - }); - - ecs_log_pop_3(); - world->info.empty_table_count += (table_count == 0) * 2 - 1; } } @@ -46685,7 +48512,7 @@ void flecs_table_set_empty( ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_count(table)) { - table->generation = 0; + table->_->generation = 0; } flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, @@ -46764,7 +48591,7 @@ int32_t ecs_delete_empty_tables( ecs_table_t *table = tr->hdr.table; ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); - if (table->refcount > 1) { + if (table->_->refcount > 1) { /* Don't delete claimed tables */ continue; } @@ -46773,7 +48600,7 @@ int32_t ecs_delete_empty_tables( continue; } - uint16_t gen = ++ table->generation; + uint16_t gen = ++ table->_->generation; if (delete_generation && (gen > delete_generation)) { if (flecs_table_release(world, table)) { delete_count ++; @@ -47023,12 +48850,11 @@ int32_t flecs_event_observers_get( return count; } -bool flecs_check_observers_for_event( - const ecs_poly_t *object, +bool flecs_observers_exist( + ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event) -{ - ecs_observable_t *observable = ecs_get_observable(object); +{ ecs_event_record_t *er = flecs_event_record_get_if(observable, event); if (!er) { return false; @@ -47102,7 +48928,7 @@ void flecs_emit_propagate( } } - if (!table->observed_count) { + if (!table->_->traversable_count) { continue; } @@ -47156,7 +48982,7 @@ void flecs_emit_propagate_invalidate_tables( const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; - if (!table->observed_count) { + if (!table->_->traversable_count) { continue; } @@ -47567,7 +49393,7 @@ void flecs_emit_forward_table_up( for (i = 0; i < id_count; i ++) { ecs_id_t id = ids[i]; - ecs_table_record_t *tgt_tr = &tgt_table->records[i]; + ecs_table_record_t *tgt_tr = &tgt_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache; if (inherit && (idr->flags & EcsIdDontInherit)) { continue; @@ -47656,10 +49482,7 @@ void flecs_emit_forward_table_up( ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); } - if (it->event == EcsOnAdd || it->event == EcsOnRemove) { - /* Only OnAdd/OnRemove events can validate a cache */ - rc->current = rc->generation; - } + rc->current = rc->generation; if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); @@ -47730,7 +49553,11 @@ void flecs_emit_forward( ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); if (it->event == EcsOnAdd || it->event == EcsOnRemove) { - /* Only OnAdd/OnRemove events can validate a cache */ + /* Only OnAdd/OnRemove events can validate top-level cache, which + * is for the id for which the event is emitted. + * The reason for this is that we don't want to validate the cache + * while the administration for the mutated entity isn't up to + * date yet. */ rc->current = rc->generation; } @@ -47801,7 +49628,7 @@ void flecs_emit( const ecs_type_t *ids = desc->ids; ecs_entity_t event = desc->event; - ecs_table_t *table = desc->table; + ecs_table_t *table = desc->table, *other_table = desc->other_table; int32_t offset = desc->offset; int32_t i, r, count = desc->count; ecs_flags32_t table_flags = table->flags; @@ -47835,7 +49662,7 @@ void flecs_emit( .sizes = &sizes_cache, .columns = &columns_cache, .sources = &sources_cache, - .other_table = desc->other_table, + .other_table = other_table, .offset = offset, .count = count, .param = (void*)desc->param, @@ -47891,7 +49718,7 @@ void flecs_emit( bool propagated = false; /* Does table has observed entities */ - bool has_observed = table_flags & EcsTableHasObserved; + bool has_observed = table_flags & EcsTableHasTraversable; /* When a relationship is removed, the events reachable through that * relationship should emit UnSet events. This is part of the behavior that @@ -48086,7 +49913,7 @@ repeat_event: * lower in the tree(s). */ propagated = true; - /* The table->observed_count value indicates if the table contains any + /* The table->traversable_count value indicates if the table contains any * entities that are used as targets of traversable relationships. If the * entity/entities for which the event was generated is used as such a * target, events must be propagated downwards. */ @@ -48113,6 +49940,7 @@ repeat_event: } it.table = table; + it.other_table = other_table; it.entities = entities; it.count = count; it.offset = offset; @@ -48480,8 +50308,7 @@ ecs_entity_t flecs_term_id_get_entity( static int flecs_term_populate_id( - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) + ecs_term_t *term) { ecs_entity_t first = flecs_term_id_get_entity(&term->first); ecs_entity_t second = flecs_term_id_get_entity(&term->second); @@ -48494,19 +50321,14 @@ int flecs_term_populate_id( return -1; } - if ((second || term->second.flags == EcsIsEntity) && !role) { - role = term->id_flags = ECS_PAIR; + if ((second || term->second.flags == EcsIsEntity)) { + role = term->id_flags |= ECS_PAIR; } if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) { term->id = first | role; } else { - if (!ECS_HAS_ID_FLAG(role, PAIR)) { - flecs_filter_error(ctx, "invalid role for pair"); - return -1; - } - - term->id = ecs_pair(first, second); + term->id = ecs_pair(first, second) | role; } return 0; @@ -48883,7 +50705,7 @@ int flecs_term_finalize( } if (!term->id) { - if (flecs_term_populate_id(term, ctx)) { + if (flecs_term_populate_id(term)) { return -1; } } @@ -49298,7 +51120,7 @@ int ecs_filter_finalize( { int32_t i, term_count = f->term_count, field_count = 0; ecs_term_t *terms = f->terms; - int32_t filter_terms = 0; + int32_t filter_terms = 0, scope_nesting = 0; bool cond_set = false; ecs_filter_finalize_ctx_t ctx = {0}; @@ -49399,6 +51221,34 @@ int ecs_filter_finalize( if (term->oper == EcsOptional || term->oper == EcsNot) { cond_set = true; } + + if (term->first.id == EcsPredEq || term->first.id == EcsPredMatch || + term->first.id == EcsPredLookup) + { + f->flags |= EcsFilterHasPred; + } + + if (term->first.id == EcsScopeOpen) { + f->flags |= EcsFilterHasScopes; + scope_nesting ++; + } + if (term->first.id == EcsScopeClose) { + if (i && terms[i - 1].first.id == EcsScopeOpen) { + flecs_filter_error(&ctx, "invalid empty scope"); + return -1; + } + + f->flags |= EcsFilterHasScopes; + scope_nesting --; + } + if (scope_nesting < 0) { + flecs_filter_error(&ctx, "'}' without matching '{'"); + } + } + + if (scope_nesting != 0) { + flecs_filter_error(&ctx, "missing '}'"); + return -1; } if (term_count && (terms[term_count - 1].oper == EcsOr)) { @@ -49797,7 +51647,8 @@ static void flecs_term_str_w_strbuf( const ecs_world_t *world, const ecs_term_t *term, - ecs_strbuf_t *buf) + ecs_strbuf_t *buf, + int32_t t) { const ecs_term_id_t *src = &term->src; const ecs_term_id_t *second = &term->second; @@ -49810,6 +51661,26 @@ void flecs_term_str_w_strbuf( bool subj_set = !ecs_term_match_0(term); bool obj_set = ecs_term_id_is_set(second); + if (term->first.id == EcsScopeOpen) { + ecs_strbuf_appendlit(buf, "{"); + return; + } else if (term->first.id == EcsScopeClose) { + ecs_strbuf_appendlit(buf, "}"); + return; + } + + if (!t || !(term[-1].oper == EcsOr)) { + if (term->inout == EcsIn) { + ecs_strbuf_appendlit(buf, "[in] "); + } else if (term->inout == EcsInOut) { + ecs_strbuf_appendlit(buf, "[inout] "); + } else if (term->inout == EcsOut) { + ecs_strbuf_appendlit(buf, "[out] "); + } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { + ecs_strbuf_appendlit(buf, "[none] "); + } + } + if (term->first.flags & EcsIsEntity && term->first.id != 0) { if (ecs_has_id(world, term->first.id, EcsDontInherit)) { def_src_mask = EcsSelf; @@ -49878,7 +51749,7 @@ char* ecs_term_str( const ecs_term_t *term) { ecs_strbuf_t buf = ECS_STRBUF_INIT; - flecs_term_str_w_strbuf(world, term, &buf); + flecs_term_str_w_strbuf(world, term, &buf, 0); return ecs_strbuf_get(&buf); } @@ -49908,25 +51779,17 @@ char* flecs_filter_str( } } - if (!i || !(term[-1].oper == EcsOr)) { - if (term->inout == EcsIn) { - ecs_strbuf_appendlit(&buf, "[in] "); - } else if (term->inout == EcsInOut) { - ecs_strbuf_appendlit(&buf, "[inout] "); - } else if (term->inout == EcsOut) { - ecs_strbuf_appendlit(&buf, "[out] "); - } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { - ecs_strbuf_appendlit(&buf, "[none] "); - } - } - - flecs_term_str_w_strbuf(world, term, &buf); + flecs_term_str_w_strbuf(world, term, &buf, i); if (i != (count - 1)) { if (term->oper == EcsOr) { ecs_strbuf_appendlit(&buf, " || "); } else { - ecs_strbuf_appendlit(&buf, ", "); + if (term->first.id != EcsScopeOpen) { + if (term[1].first.id != EcsScopeClose) { + ecs_strbuf_appendlit(&buf, ", "); + } + } } } } @@ -50137,8 +52000,8 @@ bool flecs_term_match_table( } if ((column == -1) && (src->flags & EcsUp) && (table->flags & EcsTableHasTarget)) { - ecs_assert(table->ext != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t rel = ECS_PAIR_SECOND(table->type.array[table->ext->ft_offset]); + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t rel = ECS_PAIR_SECOND(table->type.array[table->_->ft_offset]); if (rel == (uint32_t)src->trav) { result = true; } @@ -50529,7 +52392,7 @@ bool flecs_term_iter_next( } table = tr->hdr.table; - if (table->observed_count) { + if (table->_->traversable_count) { iter->observed_table_count ++; } @@ -50662,7 +52525,10 @@ bool ecs_term_next( goto yield; } else { - if (!flecs_term_iter_next(world, iter, false, false)) { + if (!flecs_term_iter_next(world, iter, + (term->flags & EcsTermMatchPrefab) != 0, + (term->flags & EcsTermMatchDisabled) != 0)) + { goto done; } @@ -50763,6 +52629,8 @@ ecs_iter_t flecs_filter_iter_w_flags( { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(filter->flags & (EcsFilterHasPred|EcsFilterHasScopes)), + ECS_UNSUPPORTED, NULL); const ecs_world_t *world = ecs_get_world(stage); if (!(flags & EcsIterMatchVar)) { @@ -51501,7 +53369,7 @@ int32_t flecs_relation_depth( } /** - * @file observer.h + * @file observer.c * @brief Observer implementation. * * The observer implementation contains functions for creating, deleting and @@ -51545,6 +53413,18 @@ ecs_flags32_t flecs_id_flag_for_event( if (e == EcsUnSet) { return EcsIdHasUnSet; } + if (e == EcsOnTableFill) { + return EcsIdHasOnTableFill; + } + if (e == EcsOnTableEmpty) { + return EcsIdHasOnTableEmpty; + } + if (e == EcsOnTableCreate) { + return EcsIdHasOnTableCreate; + } + if (e == EcsOnTableDelete) { + return EcsIdHasOnTableDelete; + } return 0; } @@ -52790,7 +54670,7 @@ ecs_table_cache_hdr_t* _flecs_table_cache_next( } /** - * @file os_api.h + * @file os_api.c * @brief Operating system abstraction API. * * The OS API implements an overridable interface for implementing functions @@ -53277,6 +55157,21 @@ bool ecs_os_has_threading(void) { (ecs_os_api.thread_self_ != NULL); } +bool ecs_os_has_task_support(void) { + return + (ecs_os_api.mutex_new_ != NULL) && + (ecs_os_api.mutex_free_ != NULL) && + (ecs_os_api.mutex_lock_ != NULL) && + (ecs_os_api.mutex_unlock_ != NULL) && + (ecs_os_api.cond_new_ != NULL) && + (ecs_os_api.cond_free_ != NULL) && + (ecs_os_api.cond_wait_ != NULL) && + (ecs_os_api.cond_signal_ != NULL) && + (ecs_os_api.cond_broadcast_ != NULL) && + (ecs_os_api.task_new_ != NULL) && + (ecs_os_api.task_join_ != NULL); +} + bool ecs_os_has_time(void) { return (ecs_os_api.get_time_ != NULL) && @@ -53351,12 +55246,12 @@ void flecs_query_compute_group_id( ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (query->group_by) { - ecs_table_t *table = match->node.table; + ecs_table_t *table = match->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - match->node.group_id = flecs_query_get_group_id(query, table); + match->group_id = flecs_query_get_group_id(query, table); } else { - match->node.group_id = 0; + match->group_id = 0; } } @@ -53424,7 +55319,7 @@ uint64_t flecs_query_default_group_by( /* Find the last node of the group after which this group should be inserted */ static -ecs_query_table_node_t* flecs_query_find_group_insertion_node( +ecs_query_table_match_t* flecs_query_find_group_insertion_node( ecs_query_t *query, uint64_t group_id) { @@ -53465,47 +55360,46 @@ ecs_query_table_node_t* flecs_query_find_group_insertion_node( static void flecs_query_create_group( ecs_query_t *query, - ecs_query_table_node_t *node) + ecs_query_table_match_t *match) { - ecs_query_table_match_t *match = node->match; - uint64_t group_id = match->node.group_id; + uint64_t group_id = match->group_id; /* If query has grouping enabled & this is a new/empty group, find * the insertion point for the group */ - ecs_query_table_node_t *insert_after = flecs_query_find_group_insertion_node( + ecs_query_table_match_t *insert_after = flecs_query_find_group_insertion_node( query, group_id); if (!insert_after) { /* This group should appear first in the query list */ - ecs_query_table_node_t *query_first = query->list.first; + ecs_query_table_match_t *query_first = query->list.first; if (query_first) { /* If this is not the first match for the query, insert before it */ - node->next = query_first; - query_first->prev = node; - query->list.first = node; + match->next = query_first; + query_first->prev = match; + query->list.first = match; } else { /* If this is the first match of the query, initialize its list */ ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL); - query->list.first = node; - query->list.last = node; + query->list.first = match; + query->list.last = match; } } else { ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); /* This group should appear after another group */ - ecs_query_table_node_t *insert_before = insert_after->next; - node->prev = insert_after; - insert_after->next = node; - node->next = insert_before; + ecs_query_table_match_t *insert_before = insert_after->next; + match->prev = insert_after; + insert_after->next = match; + match->next = insert_before; if (insert_before) { - insert_before->prev = node; + insert_before->prev = match; } else { ecs_assert(query->list.last == insert_after, ECS_INTERNAL_ERROR, NULL); /* This group should appear last in the query list */ - query->list.last = node; + query->list.last = match; } } } @@ -53514,11 +55408,10 @@ void flecs_query_create_group( static ecs_query_table_list_t* flecs_query_get_node_list( ecs_query_t *query, - ecs_query_table_node_t *node) + ecs_query_table_match_t *match) { - ecs_query_table_match_t *match = node->match; if (query->group_by) { - return flecs_query_get_group(query, match->node.group_id); + return flecs_query_get_group(query, match->group_id); } else { return &query->list; } @@ -53528,11 +55421,10 @@ ecs_query_table_list_t* flecs_query_get_node_list( static ecs_query_table_list_t* flecs_query_ensure_node_list( ecs_query_t *query, - ecs_query_table_node_t *node) + ecs_query_table_match_t *match) { - ecs_query_table_match_t *match = node->match; if (query->group_by) { - return flecs_query_ensure_group(query, match->node.group_id); + return flecs_query_ensure_group(query, match->group_id); } else { return &query->list; } @@ -53542,28 +55434,28 @@ ecs_query_table_list_t* flecs_query_ensure_node_list( static void flecs_query_remove_table_node( ecs_query_t *query, - ecs_query_table_node_t *node) + ecs_query_table_match_t *match) { - ecs_query_table_node_t *prev = node->prev; - ecs_query_table_node_t *next = node->next; + ecs_query_table_match_t *prev = match->prev; + ecs_query_table_match_t *next = match->next; - ecs_assert(prev != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != node, ECS_INTERNAL_ERROR, NULL); + ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); - ecs_query_table_list_t *list = flecs_query_get_node_list(query, node); + ecs_query_table_list_t *list = flecs_query_get_node_list(query, match); if (!list || !list->first) { - /* If list contains no nodes, the node must be empty */ + /* If list contains no matches, the match must be empty */ ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); return; } - ecs_assert(prev != NULL || query->list.first == node, + ecs_assert(prev != NULL || query->list.first == match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != NULL || query->list.last == node, + ecs_assert(next != NULL || query->list.last == match, ECS_INTERNAL_ERROR, NULL); if (prev) { @@ -53577,16 +55469,15 @@ void flecs_query_remove_table_node( list->info.table_count --; if (query->group_by) { - ecs_query_table_match_t *match = node->match; - uint64_t group_id = match->node.group_id; + uint64_t group_id = match->group_id; /* Make sure query.list is updated if this is the first or last group */ - if (query->list.first == node) { + if (query->list.first == match) { ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); query->list.first = next; prev = next; } - if (query->list.last == node) { + if (query->list.last == match) { ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); query->list.last = prev; next = prev; @@ -53597,17 +55488,17 @@ void flecs_query_remove_table_node( list->info.match_count ++; /* Make sure group list only contains nodes that belong to the group */ - if (prev && prev->match->node.group_id != group_id) { + if (prev && prev->group_id != group_id) { /* The previous node belonged to another group */ prev = next; } - if (next && next->match->node.group_id != group_id) { + if (next && next->group_id != group_id) { /* The next node belonged to another group */ next = prev; } /* Do check again, in case both prev & next belonged to another group */ - if ((!prev && !next) || (prev && prev->match->node.group_id != group_id)) { + if ((!prev && !next) || (prev && prev->group_id != group_id)) { /* There are no more matches left in this group */ flecs_query_remove_group(query, group_id); list = NULL; @@ -53615,16 +55506,16 @@ void flecs_query_remove_table_node( } if (list) { - if (list->first == node) { + if (list->first == match) { list->first = next; } - if (list->last == node) { + if (list->last == match) { list->last = prev; } } - node->prev = NULL; - node->next = NULL; + match->prev = NULL; + match->next = NULL; query->match_count ++; } @@ -53633,10 +55524,10 @@ void flecs_query_remove_table_node( static void flecs_query_insert_table_node( ecs_query_t *query, - ecs_query_table_node_t *node) + ecs_query_table_match_t *match) { /* Node should not be part of an existing list */ - ecs_assert(node->prev == NULL && node->next == NULL, + ecs_assert(match->prev == NULL && match->next == NULL, ECS_INTERNAL_ERROR, NULL); /* If this is the first match, activate system */ @@ -53644,42 +55535,42 @@ void flecs_query_insert_table_node( ecs_remove_id(query->filter.world, query->filter.entity, EcsEmpty); } - flecs_query_compute_group_id(query, node->match); + flecs_query_compute_group_id(query, match); - ecs_query_table_list_t *list = flecs_query_ensure_node_list(query, node); + ecs_query_table_list_t *list = flecs_query_ensure_node_list(query, match); if (list->last) { ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_table_node_t *last = list->last; - ecs_query_table_node_t *last_next = last->next; + ecs_query_table_match_t *last = list->last; + ecs_query_table_match_t *last_next = last->next; - node->prev = last; - node->next = last_next; - last->next = node; + match->prev = last; + match->next = last_next; + last->next = match; if (last_next) { - last_next->prev = node; + last_next->prev = match; } - list->last = node; + list->last = match; if (query->group_by) { /* Make sure to update query list if this is the last group */ if (query->list.last == last) { - query->list.last = node; + query->list.last = match; } } } else { ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - list->first = node; - list->last = node; + list->first = match; + list->last = match; if (query->group_by) { /* Initialize group with its first node */ - flecs_query_create_group(query, node); + flecs_query_create_group(query, match); } } @@ -53691,12 +55582,12 @@ void flecs_query_insert_table_node( query->list.info.table_count ++; query->match_count ++; - ecs_assert(node->prev != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(node->next != node, ECS_INTERNAL_ERROR, NULL); + ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last == node, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); @@ -53710,9 +55601,7 @@ ecs_query_table_match_t* flecs_query_cache_add( { ecs_query_table_match_t *result = flecs_bcalloc(&world->allocators.query_table_match); - ecs_query_table_node_t *node = &result->node; - node->match = result; if (!elem->first) { elem->first = result; elem->last = result; @@ -53744,7 +55633,7 @@ void flecs_query_get_dirty_state( int32_t column = -1; if (!subject) { - table = match->node.table; + table = match->table; column = match->storage_columns[term]; } else { table = ecs_get_table(world, subject); @@ -53816,11 +55705,13 @@ bool flecs_query_get_match_monitor( monitor[t + 1] = 0; } + /* If matched table needs entity filter, make sure to test fields that could * be matched by flattened parents. */ - if (match->entity_filter.flat_tree_column != -1) { - int32_t *fields = ecs_vec_first(&match->entity_filter.ft_terms); - int32_t field_count = ecs_vec_count(&match->entity_filter.ft_terms); + ecs_entity_filter_t *ef = match->entity_filter; + if (ef && ef->flat_tree_column != -1) { + int32_t *fields = ecs_vec_first(&ef->ft_terms); + int32_t field_count = ecs_vec_count(&ef->ft_terms); for (i = 0; i < field_count; i ++) { monitor[fields[i] + 1] = 0; } @@ -53849,7 +55740,7 @@ void flecs_query_sync_match_monitor( } int32_t *monitor = match->monitor; - ecs_table_t *table = match->node.table; + ecs_table_t *table = match->table; if (table) { int32_t *dirty_state = flecs_table_get_dirty_state( query->filter.world, table); @@ -53873,8 +55764,8 @@ void flecs_query_sync_match_monitor( monitor[t + 1] = cur.dirty_state[cur.column + 1]; } - ecs_entity_filter_t *ef = &match->entity_filter; - if (ef->flat_tree_column != -1) { + ecs_entity_filter_t *ef = match->entity_filter; + if (ef && ef->flat_tree_column != -1) { flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); int32_t field_count = ecs_vec_count(&ef->ft_terms); for (i = 0; i < field_count; i ++) { @@ -53909,7 +55800,7 @@ bool flecs_query_check_match_monitor_term( return false; } - ecs_table_t *table = match->node.table; + ecs_table_t *table = match->table; if (table) { int32_t *dirty_state = flecs_table_get_dirty_state( query->filter.world, table); @@ -53942,7 +55833,7 @@ bool flecs_query_check_match_monitor( } int32_t *monitor = match->monitor; - ecs_table_t *table = match->node.table; + ecs_table_t *table = match->table; int32_t *dirty_state = NULL; if (table) { dirty_state = flecs_table_get_dirty_state( @@ -54012,7 +55903,7 @@ bool flecs_query_check_match_monitor( } if (has_flat) { - ecs_entity_filter_t *ef = &match->entity_filter; + ecs_entity_filter_t *ef = match->entity_filter; flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; int32_t cur_tgt = ent_it->target_count - 1; @@ -54038,9 +55929,9 @@ bool flecs_query_check_table_monitor( ecs_query_table_t *table, int32_t term) { - ecs_query_table_node_t *cur, *end = table->last->node.next; + ecs_query_table_match_t *cur, *end = table->last->next; - for (cur = &table->first->node; cur != end; cur = cur->next) { + for (cur = table->first; cur != end; cur = cur->next) { ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; if (term == -1) { if (flecs_query_check_match_monitor(query, match, NULL)) { @@ -54077,7 +55968,7 @@ static void flecs_query_init_query_monitors( ecs_query_t *query) { - ecs_query_table_node_t *cur = query->list.first; + ecs_query_table_match_t *cur = query->list.first; /* Ensure each match has a monitor */ for (; cur != NULL; cur = cur->next) { @@ -54137,7 +56028,7 @@ ecs_query_table_match_t* flecs_query_add_table_match( /* Add match for table. One table can have more than one match, if * the query contains wildcards. */ ecs_query_table_match_t *qm = flecs_query_cache_add(query->filter.world, qt); - qm->node.table = table; + qm->table = table; qm->columns = flecs_balloc(&query->allocators.columns); qm->storage_columns = flecs_balloc(&query->allocators.columns); @@ -54146,7 +56037,7 @@ ecs_query_table_match_t* flecs_query_add_table_match( /* Insert match to iteration list if table is not empty */ if (!table || ecs_table_count(table) != 0) { - flecs_query_insert_table_node(query, &qm->node); + flecs_query_insert_table_node(query, qm); } return qm; @@ -54193,7 +56084,7 @@ void flecs_query_set_table_match( flecs_entity_filter_init(world, &qm->entity_filter, filter, table, qm->ids, qm->columns); - if (qm->entity_filter.has_filter) { + if (qm->entity_filter) { query->flags &= ~EcsQueryTrivialIter; } if (table->flags & EcsTableHasUnion) { @@ -54401,16 +56292,15 @@ void flecs_query_build_sorted_table_range( return; } - ecs_vec_init_if_t(&query->table_slices, ecs_query_table_node_t); + ecs_vec_init_if_t(&query->table_slices, ecs_query_table_match_t); int32_t to_sort = 0; int32_t order_by_term = query->order_by_term; sort_helper_t *helper = flecs_alloc_n( &world->allocator, sort_helper_t, table_count); - ecs_query_table_node_t *cur, *end = list->last->next; + ecs_query_table_match_t *cur, *end = list->last->next; for (cur = list->first; cur != end; cur = cur->next) { - ecs_query_table_match_t *match = cur->match; - ecs_table_t *table = match->node.table; + ecs_table_t *table = cur->table; ecs_data_t *data = &table->data; ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); @@ -54418,7 +56308,7 @@ void flecs_query_build_sorted_table_range( if (id) { const ecs_term_t *term = &query->filter.terms[order_by_term]; int32_t field = term->field_index; - int32_t column = match->columns[field]; + int32_t column = cur->columns[field]; ecs_size_t size = query->filter.sizes[field]; ecs_assert(column != 0, ECS_INTERNAL_ERROR, NULL); if (column >= 0) { @@ -54428,7 +56318,7 @@ void flecs_query_build_sorted_table_range( helper[to_sort].elem_size = size; helper[to_sort].shared = false; } else { - ecs_entity_t src = match->sources[field]; + ecs_entity_t src = cur->sources[field]; ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); @@ -54456,7 +56346,7 @@ void flecs_query_build_sorted_table_range( helper[to_sort].shared = false; } - helper[to_sort].match = match; + helper[to_sort].match = cur; helper[to_sort].entities = ecs_vec_first(&data->entities); helper[to_sort].row = 0; helper[to_sort].count = ecs_table_count(table); @@ -54499,10 +56389,10 @@ void flecs_query_build_sorted_table_range( } sort_helper_t *cur_helper = &helper[min]; - if (!cur || cur->match != cur_helper->match) { + if (!cur || cur->columns != cur_helper->match->columns) { cur = ecs_vec_append_t(NULL, &query->table_slices, - ecs_query_table_node_t); - cur->match = cur_helper->match; + ecs_query_table_match_t); + *cur = *(cur_helper->match); cur->offset = cur_helper->row; cur->count = 1; } else { @@ -54515,7 +56405,7 @@ void flecs_query_build_sorted_table_range( /* Iterate through the vector of slices to set the prev/next ptrs. This * can't be done while building the vector, as reallocs may occur */ int32_t i, count = ecs_vec_count(&query->table_slices); - ecs_query_table_node_t *nodes = ecs_vec_first(&query->table_slices); + ecs_query_table_match_t *nodes = ecs_vec_first(&query->table_slices); for (i = 0; i < count; i ++) { nodes[i].prev = &nodes[i - 1]; nodes[i].next = &nodes[i + 1]; @@ -54535,20 +56425,18 @@ void flecs_query_build_sorted_tables( if (query->group_by) { /* Populate sorted node list in grouping order */ - ecs_query_table_node_t *cur = query->list.first; + ecs_query_table_match_t *cur = query->list.first; if (cur) { do { /* Find list for current group */ - ecs_query_table_match_t *match = cur->match; - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - uint64_t group_id = match->node.group_id; + uint64_t group_id = cur->group_id; ecs_query_table_list_t *list = ecs_map_get_deref( &query->groups, ecs_query_table_list_t, group_id); ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); /* Sort tables in current group */ flecs_query_build_sorted_table_range(query, list); - + /* Find next group to sort */ cur = list->last->next; } while (cur); @@ -54763,11 +56651,11 @@ void flecs_query_update_table( ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); - flecs_query_remove_table_node(query, &cur->node); + flecs_query_remove_table_node(query, cur); } else { ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); - flecs_query_insert_table_node(query, &cur->node); + flecs_query_insert_table_node(query, cur); } } } @@ -54836,11 +56724,11 @@ void flecs_query_table_match_free( flecs_bfree(&query->allocators.monitors, cur->monitor); } if (!elem->hdr.empty) { - flecs_query_remove_table_node(query, &cur->node); + flecs_query_remove_table_node(query, cur); } ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t); - flecs_entity_filter_fini(world, &cur->entity_filter); + flecs_entity_filter_fini(world, cur->entity_filter); next = cur->next_match; @@ -54939,10 +56827,10 @@ void flecs_query_rematch_tables( flecs_query_set_table_match(world, query, qm, table, &it); if (table && ecs_table_count(table) && query->group_by) { - if (flecs_query_get_group_id(query, table) != qm->node.group_id) { + if (flecs_query_get_group_id(query, table) != qm->group_id) { /* Update table group */ - flecs_query_remove_table_node(query, &qm->node); - flecs_query_insert_table_node(query, &qm->node); + flecs_query_remove_table_node(query, qm); + flecs_query_insert_table_node(query, qm); } } } @@ -55065,12 +56953,14 @@ void flecs_query_order_by( query->order_by_term = order_by_term; query->sort_table = action; - ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_node_t); + ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); flecs_query_sort_tables(world, query); if (!query->table_slices.array) { flecs_query_build_sorted_tables(query); } + + query->flags &= ~EcsQueryTrivialIter; error: return; } @@ -55265,7 +57155,7 @@ void flecs_query_fini( ecs_map_fini(&query->groups); ecs_vec_fini_t(NULL, &query->subqueries, ecs_query_t*); - ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_node_t); + ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); ecs_filter_fini(&query->filter); flecs_query_allocators_fini(query); @@ -55522,7 +57412,6 @@ ecs_iter_t ecs_query_iter( ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, field_count); } ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, field_count); - // ecs_os_memcpy_n(result.sizes, fit.sizes, ecs_size_t, field_count); ecs_os_memcpy_n(result.columns, fit.columns, int32_t, field_count); ecs_os_memcpy_n(result.sources, fit.sources, int32_t, field_count); } @@ -55561,7 +57450,7 @@ void ecs_query_set_group( return; } - ecs_query_table_node_t *first = node->first; + ecs_query_table_match_t *first = node->first; if (first) { qit->node = node->first; qit->last = node->last->next; @@ -55604,7 +57493,7 @@ void flecs_query_mark_columns_dirty( ecs_query_t *query, ecs_query_table_match_t *qm) { - ecs_table_t *table = qm->node.table; + ecs_table_t *table = qm->table; if (!table) { return; } @@ -55643,17 +57532,17 @@ bool ecs_query_next_table( flecs_iter_validate(it); ecs_query_iter_t *iter = &it->priv.iter.query; - ecs_query_table_node_t *node = iter->node; + ecs_query_table_match_t *node = iter->node; ecs_query_t *query = iter->query; - ecs_query_table_node_t *prev = iter->prev; + ecs_query_table_match_t *prev = iter->prev; if (prev) { if (query->flags & EcsQueryHasMonitor) { - flecs_query_sync_match_monitor(query, prev->match); + flecs_query_sync_match_monitor(query, prev); } if (query->flags & EcsQueryHasOutColumns) { if (it->count) { - flecs_query_mark_columns_dirty(query, prev->match); + flecs_query_mark_columns_dirty(query, prev); } } } @@ -55676,25 +57565,20 @@ error: static void flecs_query_populate_trivial( ecs_iter_t *it, - ecs_query_table_node_t *node) -{ - ecs_query_table_match_t *match = node->match; - ecs_table_t *table = match->node.table; + ecs_query_table_match_t *match) +{; + ecs_table_t *table = match->table; + int32_t count = ecs_table_count(table); it->ids = match->ids; it->sources = match->sources; it->columns = match->columns; - it->group_id = match->node.group_id; + it->group_id = match->group_id; it->instance_count = 0; - it->offset = node->offset; - it->count = node->count; + it->offset = 0; + it->count = count; it->references = ecs_vec_first(&match->refs); - if (!it->count) { - it->count = ecs_table_count(table); - ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); - } - if (!it->references) { ecs_data_t *data = &table->data; if (!(it->flags & EcsIterNoData)) { @@ -55713,16 +57597,15 @@ void flecs_query_populate_trivial( } it->ptrs[i] = ecs_vec_get(&data->columns[column], - it->sizes[i], it->offset); + it->sizes[i], 0); } } it->frame_offset += it->table ? ecs_table_count(it->table) : 0; it->table = table; - it->entities = ecs_vec_get_t(&data->entities, ecs_entity_t, it->offset); + it->entities = ecs_vec_first(&data->entities); } else { - flecs_iter_populate_data(it->real_world, it, table, it->offset, - it->count, it->ptrs); + flecs_iter_populate_data(it->real_world, it, table, 0, count, it->ptrs); } } @@ -55737,15 +57620,14 @@ int ecs_query_populate( ecs_query_iter_t *iter = &it->priv.iter.query; ecs_query_t *query = iter->query; - ecs_query_table_node_t *node = iter->prev; - ecs_assert(node != NULL, ECS_INVALID_OPERATION, NULL); + ecs_query_table_match_t *match = iter->prev; + ecs_assert(match != NULL, ECS_INVALID_OPERATION, NULL); if (query->flags & EcsQueryTrivialIter) { - flecs_query_populate_trivial(it, node); + flecs_query_populate_trivial(it, match); return EcsIterNextYield; } - ecs_query_table_match_t *match = node->match; - ecs_table_t *table = match->node.table; + ecs_table_t *table = match->table; ecs_world_t *world = query->filter.world; const ecs_filter_t *filter = &query->filter; ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; @@ -55772,15 +57654,15 @@ repeat: } if (table) { - range->offset = node->offset; - range->count = node->count; + range->offset = match->offset; + range->count = match->count; if (!range->count) { range->count = ecs_table_count(table); ecs_assert(range->count != 0, ECS_INTERNAL_ERROR, NULL); } - if (match->entity_filter.has_filter) { - ent_it->entity_filter = &match->entity_filter; + if (match->entity_filter) { + ent_it->entity_filter = match->entity_filter; ent_it->columns = match->columns; ent_it->range.table = table; ent_it->it = it; @@ -55790,7 +57672,7 @@ repeat: } } - it->group_id = match->node.group_id; + it->group_id = match->group_id; } else { range->offset = 0; range->count = 0; @@ -55843,14 +57725,14 @@ bool ecs_query_next_instanced( ecs_query_t *query = iter->query; ecs_flags32_t flags = query->flags; - ecs_query_table_node_t *prev, *next, *node = iter->node, *last = iter->last; + ecs_query_table_match_t *prev, *next, *cur = iter->node, *last = iter->last; if ((prev = iter->prev)) { /* Match has been iterated, update monitor for change tracking */ if (flags & EcsQueryHasMonitor) { - flecs_query_sync_match_monitor(query, prev->match); + flecs_query_sync_match_monitor(query, prev); } if (flags & EcsQueryHasOutColumns) { - flecs_query_mark_columns_dirty(query, prev->match); + flecs_query_mark_columns_dirty(query, prev); } } @@ -55860,23 +57742,23 @@ bool ecs_query_next_instanced( /* Trivial iteration: each entry in the cache is a full match and ids are * only matched on $this or through traversal starting from $this. */ if (flags & EcsQueryTrivialIter) { - if (node == last) { + if (cur == last) { goto done; } - iter->node = node->next; - iter->prev = node; - flecs_query_populate_trivial(it, node); + iter->node = cur->next; + iter->prev = cur; + flecs_query_populate_trivial(it, cur); return true; } /* Non-trivial iteration: query matches with static sources, or matches with * tables that require per-entity filtering. */ - for (; node != last; node = next) { - next = node->next; - iter->prev = node; + for (; cur != last; cur = next) { + next = cur->next; + iter->prev = cur; switch(ecs_query_populate(it, false)) { case EcsIterNext: iter->node = next; continue; - case EcsIterYield: next = node; /* fall through */ + case EcsIterYield: next = cur; /* fall through */ case EcsIterNextYield: goto yield; default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } @@ -55889,7 +57771,7 @@ done: error: yield: iter->node = next; - iter->prev = node; + iter->prev = cur; return true; } @@ -56397,9 +58279,12 @@ ecs_graph_edge_t* flecs_table_ensure_hi_edge( ecs_graph_edges_t *edges, ecs_id_t id) { - ecs_map_init_w_params_if(&edges->hi, &world->allocators.ptr); + if (!edges->hi) { + edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t); + ecs_map_init_w_params(edges->hi, &world->allocators.ptr); + } - ecs_graph_edge_t **r = ecs_map_ensure_ref(&edges->hi, ecs_graph_edge_t, id); + ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id); ecs_graph_edge_t *edge = r[0]; if (edge) { return edge; @@ -56478,9 +58363,9 @@ void flecs_table_remove_edge( ecs_graph_edge_t *edge) { ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_map_is_init(&edges->hi), ECS_INTERNAL_ERROR, NULL); + ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_disconnect_edge(world, id, edge); - ecs_map_remove(&edges->hi, id); + ecs_map_remove(edges->hi, id); } static @@ -56488,7 +58373,7 @@ void flecs_table_init_edges( ecs_graph_edges_t *edges) { edges->lo = NULL; - ecs_os_zeromem(&edges->hi); + edges->hi = NULL; } static @@ -56505,9 +58390,9 @@ bool flecs_table_records_update_empty( bool result = false; bool is_empty = ecs_table_count(table) == 0; - int32_t i, count = table->record_count; + int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->records[i]; + ecs_table_record_t *tr = &table->_->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; result |= ecs_table_cache_set_empty(cache, table, is_empty); } @@ -56524,9 +58409,9 @@ void flecs_init_table( table->type_info = NULL; table->flags = 0; table->dirty_state = NULL; - table->lock = 0; - table->refcount = 1; - table->generation = 0; + table->_->lock = 0; + table->_->refcount = 1; + table->_->generation = 0; flecs_table_init_node(&table->node); @@ -56542,7 +58427,9 @@ ecs_table_t *flecs_create_table( { ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - + result->_ = flecs_calloc_t(&world->allocator, ecs_table__t); + ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL); + result->id = flecs_sparse_last_id(&world->store.tables); result->type = *type; @@ -56561,20 +58448,13 @@ ecs_table_t *flecs_create_table( /* Set keyvalue to one that has the same lifecycle as the table */ *(ecs_type_t*)table_elem.key = result->type; + result->_->hash = table_elem.hash; flecs_init_table(world, result, prev); - flecs_emit(world, world, &(ecs_event_desc_t) { - .ids = &result->type, - .event = EcsOnTableCreate, - .table = result, - .flags = EcsEventTableOnly, - .observable = world - }); - /* Update counters */ world->info.table_count ++; - world->info.table_record_count += result->record_count; + world->info.table_record_count += result->_->record_count; world->info.table_storage_count += result->storage_count; world->info.empty_table_count ++; world->info.table_create_total ++; @@ -56768,7 +58648,7 @@ void flecs_add_overrides_for_base( if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { flecs_type_add(world, dst_type, id & ~ECS_OVERRIDE); } else { - ecs_table_record_t *tr = &base_table->records[i]; + ecs_table_record_t *tr = &base_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdAlwaysOverride) { flecs_type_add(world, dst_type, id); @@ -57115,7 +58995,7 @@ void flecs_init_root_table( ecs_poly_assert(world, ecs_world_t); world->store.root.type = (ecs_type_t){0}; - + world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); flecs_init_table(world, &world->store.root, NULL); /* Ensure table indices start at 1, as 0 is reserved for the root */ @@ -57137,8 +59017,8 @@ void flecs_table_clear_edges( ecs_graph_node_t *table_node = &table->node; ecs_graph_edges_t *node_add = &table_node->add; ecs_graph_edges_t *node_remove = &table_node->remove; - ecs_map_t *add_hi = &node_add->hi; - ecs_map_t *remove_hi = &node_remove->hi; + ecs_map_t *add_hi = node_add->hi; + ecs_map_t *remove_hi = node_remove->hi; ecs_graph_edge_hdr_t *node_refs = &table_node->refs; /* Cleanup outgoing edges */ @@ -57185,8 +59065,12 @@ void flecs_table_clear_edges( ecs_map_fini(add_hi); ecs_map_fini(remove_hi); + flecs_free_t(&world->allocator, ecs_map_t, add_hi); + flecs_free_t(&world->allocator, ecs_map_t, remove_hi); table_node->add.lo = NULL; table_node->remove.lo = NULL; + table_node->add.hi = NULL; + table_node->remove.hi = NULL; ecs_log_pop_1(); } @@ -57459,8 +59343,8 @@ has_data: has_union: { /* Edge case: if column is a switch we should return the vector with case * identifiers. Will be replaced in the future with pluggable storage */ - ecs_assert(table->ext != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = &table->ext->sw_columns[u_index]; + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &table->_->sw_columns[u_index]; data = ecs_vec_first(flecs_switch_values(sw)); goto has_data; } @@ -58468,15 +60352,6 @@ int flecs_entity_compare( return (e1 > e2) - (e1 < e2); } -int flecs_entity_compare_qsort( - const void *e1, - const void *e2) -{ - ecs_entity_t v1 = *(ecs_entity_t*)e1; - ecs_entity_t v2 = *(ecs_entity_t*)e2; - return flecs_entity_compare(v1, NULL, v2, NULL); -} - uint64_t flecs_string_hash( const void *ptr) { @@ -59189,7 +61064,7 @@ void flecs_register_symmetric(ecs_iter_t *it) { /* Create observer that adds the reverse relationship when R(X, Y) is * added, or remove the reverse relationship when R(X, Y) is removed. */ - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .entity = ecs_entity(world, {.add = {ecs_childof(r)}}), .filter.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, .callback = flecs_on_symmetric_add_remove, @@ -59238,78 +61113,6 @@ void flecs_ensure_module_tag(ecs_iter_t *it) { } } -/* -- Triggers for keeping hashed ids in sync -- */ - -static -void flecs_on_parent_change(ecs_iter_t *it) { - ecs_world_t *world = it->world; - ecs_table_t *other_table = it->other_table, *table = it->table; - - if (!(table->flags & EcsTableHasName)) { - return; - } - - ecs_id_t to_pair = it->event_id; - ecs_id_t from_pair = ecs_childof(0); - - /* Find the other ChildOf relationship */ - ecs_search(it->real_world, other_table, - ecs_pair(EcsChildOf, EcsWildcard), &from_pair); - - bool other_has_name = other_table && ((other_table->flags & EcsTableHasName) != 0); - bool to_has_name = true, from_has_name = other_has_name; - if (it->event == EcsOnRemove) { - if (from_pair != ecs_childof(0)) { - /* Because ChildOf is an exclusive relationship, events always come - * in OnAdd/OnRemove pairs (add for the new, remove for the old - * parent). We only need one of those events, so filter out the - * OnRemove events except for the case where a parent is removed and - * not replaced with another parent. */ - return; - } - - ecs_id_t temp = from_pair; - from_pair = to_pair; - to_pair = temp; - - to_has_name = other_has_name; - from_has_name = true; - } - - ecs_hashmap_t *from_index = 0; - if (from_has_name) { - from_index = flecs_id_name_index_get(world, from_pair); - } - ecs_hashmap_t *to_index = NULL; - if (to_has_name) { - to_index = flecs_id_name_index_ensure(world, to_pair); - } - - EcsIdentifier *names = ecs_table_get_pair(it->real_world, - table, EcsIdentifier, EcsName, it->offset); - ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - EcsIdentifier *name = &names[i]; - - uint64_t index_hash = name->index_hash; - if (from_index && index_hash) { - flecs_name_index_remove(from_index, e, index_hash); - } - const char *name_str = name->value; - if (to_index && name_str) { - ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); - - flecs_name_index_ensure( - to_index, e, name_str, name->length, name->hash); - name->index = to_index; - } - } -} - - /* -- Iterable mixins -- */ static @@ -59346,8 +61149,6 @@ void flecs_bootstrap_builtin( ecs_record_t *record = flecs_entities_ensure(world, entity); record->table = table; - ecs_defer_begin(world); - int32_t index = flecs_table_append(world, table, entity, record, false, false); record->row = ECS_ROW_TO_RECORD(index, 0); @@ -59360,11 +61161,14 @@ void flecs_bootstrap_builtin( ecs_size_t name_length = symbol_length - 3; EcsIdentifier *name_col = ecs_vec_first(&columns[1]); + uint64_t name_hash = flecs_hash(name, name_length); name_col[index].value = ecs_os_strdup(name); name_col[index].length = name_length; - name_col[index].hash = flecs_hash(name, name_length); + name_col[index].hash = name_hash; name_col[index].index_hash = 0; - name_col[index].index = NULL; + name_col[index].index = table->_->name_index; + flecs_name_index_ensure( + table->_->name_index, entity, name, name_length, name_hash); EcsIdentifier *symbol_col = ecs_vec_first(&columns[2]); symbol_col[index].value = ecs_os_strdup(symbol); @@ -59372,8 +61176,6 @@ void flecs_bootstrap_builtin( symbol_col[index].hash = flecs_hash(symbol, symbol_length); symbol_col[index].index_hash = 0; symbol_col[index].index = NULL; - - ecs_defer_end(world); } /** Initialize component table. This table is manually constructed to bootstrap @@ -59412,7 +61214,7 @@ ecs_table_t* flecs_bootstrap_component_table( ecs_pair(EcsChildOf, EcsFlecsCore), ecs_pair(EcsOnDelete, EcsPanic) }; - + ecs_type_t array = { .array = ids, .count = 6 @@ -59485,7 +61287,7 @@ void flecs_bootstrap( ecs_ensure(world, EcsAny); ecs_ensure(world, EcsTag); - /* Bootstrap builtin components */ + /* Register type information for builtin components */ flecs_type_info_init(world, EcsComponent, { .ctor = ecs_default_ctor, .on_set = flecs_on_component, @@ -59509,22 +61311,25 @@ void flecs_bootstrap( }); flecs_type_info_init(world, EcsIterable, { 0 }); - flecs_type_info_init(world, EcsTarget, { 0 }); - /* Cache often used id records */ + /* Create and cache often used id records on world */ flecs_init_id_records(world); - /* Create table for initial components */ + /* Create table for builtin components. This table temporarily stores the + * entities associated with builtin components, until they get moved to + * other tables once properties are added (see below) */ ecs_table_t *table = flecs_bootstrap_component_table(world); assert(table != NULL); + /* Bootstrap builtin components */ flecs_bootstrap_builtin_t(world, table, EcsIdentifier); flecs_bootstrap_builtin_t(world, table, EcsComponent); flecs_bootstrap_builtin_t(world, table, EcsIterable); flecs_bootstrap_builtin_t(world, table, EcsPoly); flecs_bootstrap_builtin_t(world, table, EcsTarget); + /* Initialize default entity id range */ world->info.last_component_id = EcsFirstUserComponentId; flecs_entities_max_id(world) = EcsFirstUserEntityId; world->info.min_id = 0; @@ -59534,7 +61339,8 @@ void flecs_bootstrap( ecs_set(world, EcsOnAdd, EcsIterable, { .init = flecs_on_event_iterable_init }); ecs_set(world, EcsOnSet, EcsIterable, { .init = flecs_on_event_iterable_init }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + /* Register observer for tag property before adding EcsTag */ + ecs_observer(world, { .entity = ecs_entity(world, {.add = { ecs_childof(EcsFlecsInternals)}}), .filter.terms[0] = { .id = EcsTag, .src.flags = EcsSelf }, .events = {EcsOnAdd, EcsOnRemove}, @@ -59615,6 +61421,8 @@ void flecs_bootstrap( flecs_bootstrap_tag(world, EcsPredEq); flecs_bootstrap_tag(world, EcsPredMatch); flecs_bootstrap_tag(world, EcsPredLookup); + flecs_bootstrap_tag(world, EcsScopeOpen); + flecs_bootstrap_tag(world, EcsScopeClose); /* Builtin relationships */ flecs_bootstrap_tag(world, EcsIsA); @@ -59666,23 +61474,16 @@ void flecs_bootstrap( .src.flags = EcsSelf }; - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = { - { .id = ecs_pair(EcsChildOf, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = { EcsOnAdd, EcsOnRemove }, - .yield_existing = true, - .callback = flecs_on_parent_change - }); - - ecs_observer_init(world, &(ecs_observer_desc_t){ + /* Register observers for components/relationship properties. Most observers + * set flags on an id record when a property is added to a component, which + * allows for quick property testing in various operations. */ + ecs_observer(world, { .filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_final }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = { { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, match_prefab @@ -59691,7 +61492,7 @@ void flecs_bootstrap( .callback = flecs_register_on_delete }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = { { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, match_prefab @@ -59700,7 +61501,7 @@ void flecs_bootstrap( .callback = flecs_register_on_delete_object }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = { { .id = EcsTraversable, .src.flags = EcsSelf }, match_prefab @@ -59709,31 +61510,31 @@ void flecs_bootstrap( .callback = flecs_register_traversable }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, + .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_exclusive }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_symmetric }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_dont_inherit }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = {{ .id = EcsAlwaysOverride, .src.flags = EcsSelf } }, .events = {EcsOnAdd}, .callback = flecs_register_always_override }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = { { .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, match_prefab @@ -59742,7 +61543,7 @@ void flecs_bootstrap( .callback = flecs_register_with }); - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_union @@ -59750,7 +61551,7 @@ void flecs_bootstrap( /* Entities used as slot are marked as exclusive to ensure a slot can always * only point to a single entity. */ - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = { { .id = ecs_pair(EcsSlotOf, EcsWildcard), .src.flags = EcsSelf }, match_prefab @@ -59761,7 +61562,7 @@ void flecs_bootstrap( /* Define observer to make sure that adding a module to a child entity also * adds it to the parent. */ - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .filter.terms = {{ .id = EcsModule, .src.flags = EcsSelf }, match_prefab}, .events = {EcsOnAdd}, .callback = flecs_ensure_module_tag @@ -60028,7 +61829,7 @@ void flecs_on_set_symbol(ecs_iter_t *it) { } void flecs_bootstrap_hierarchy(ecs_world_t *world) { - ecs_observer_init(world, &(ecs_observer_desc_t){ + ecs_observer(world, { .entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}), .filter.terms[0] = { .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), @@ -60642,21 +62443,24 @@ ecs_id_record_t* flecs_id_record_new( idr->reachable.current = -1; bool is_wildcard = ecs_id_is_wildcard(id); + bool is_pair = ECS_IS_PAIR(id); ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; - if (ECS_HAS_ID_FLAG(id, PAIR)) { - rel = ecs_pair_first(world, id); + if (is_pair) { + // rel = ecs_pair_first(world, id); + rel = ECS_PAIR_FIRST(id); ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); /* Relationship object can be 0, as tables without a ChildOf * relationship are added to the (ChildOf, 0) id record */ tgt = ECS_PAIR_SECOND(id); + +#ifdef FLECS_DEBUG + /* Check constraints */ if (tgt) { tgt = ecs_get_alive(world, tgt); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); } - - /* Check constraints */ if (tgt && !ecs_id_is_wildcard(tgt)) { /* Check if target of relationship satisfies OneOf property */ ecs_entity_t oneof = flecs_get_oneof(world, rel); @@ -60672,8 +62476,9 @@ ecs_id_record_t* flecs_id_record_new( (void)is_final; } } +#endif - if (!is_wildcard && ECS_IS_PAIR(id) && (rel != EcsFlag)) { + if (!is_wildcard && (rel != EcsFlag)) { /* Inherit flags from (relationship, *) record */ ecs_id_record_t *idr_r = flecs_id_record_ensure( world, ecs_pair(rel, EcsWildcard)); @@ -60694,12 +62499,11 @@ ecs_id_record_t* flecs_id_record_new( } } else { rel = id & ECS_COMPONENT_MASK; - rel = ecs_get_alive(world, rel); ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); } /* Initialize type info if id is not a tag */ - if (!is_wildcard && (!role || ECS_IS_PAIR(id))) { + if (!is_wildcard && (!role || is_pair)) { if (!(idr->flags & EcsIdTag)) { const ecs_type_info_t *ti = flecs_type_info_get(world, rel); if (!ti && tgt) { @@ -60717,31 +62521,28 @@ ecs_id_record_t* flecs_id_record_new( flecs_add_flag(world, rel, EcsEntityIsId); if (tgt) { /* Flag for OnDeleteTarget policies */ - flecs_add_flag(world, tgt, EcsEntityIsTarget); + ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt); + ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(tgt_r, EcsEntityIsTarget); if (idr->flags & EcsIdTraversable) { /* Flag used to determine if object should be traversed when * propagating events or with super/subset queries */ - ecs_record_t *r = flecs_add_flag( - world, tgt, EcsEntityIsTraversable); + flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); /* Add reference to (*, tgt) id record to entity record */ - r->idr = idr_t; + tgt_r->idr = idr_t; } } - /* Flags for events */ - if (flecs_check_observers_for_event(world, id, EcsOnAdd)) { - idr->flags |= EcsIdHasOnAdd; - } - if (flecs_check_observers_for_event(world, id, EcsOnRemove)) { - idr->flags |= EcsIdHasOnRemove; - } - if (flecs_check_observers_for_event(world, id, EcsOnSet)) { - idr->flags |= EcsIdHasOnSet; - } - if (flecs_check_observers_for_event(world, id, EcsUnSet)) { - idr->flags |= EcsIdHasUnSet; - } + ecs_observable_t *o = &world->observable; + idr->flags |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; + idr->flags |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; + idr->flags |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; + idr->flags |= flecs_observers_exist(o, id, EcsUnSet) * EcsIdHasUnSet; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; if (ecs_should_log_1()) { char *id_str = ecs_id_str(world, id); @@ -60761,7 +62562,7 @@ ecs_id_record_t* flecs_id_record_new( world->info.tag_id_count ++; } - if (ECS_IS_PAIR(id)) { + if (is_pair) { world->info.pair_id_count ++; } } else { @@ -60769,8 +62570,10 @@ ecs_id_record_t* flecs_id_record_new( } return idr; +#ifdef FLECS_DEBUG error: return NULL; +#endif } static @@ -60795,20 +62598,18 @@ void flecs_id_record_free( flecs_id_record_assert_empty(idr); - if (!(world->flags & EcsWorldQuit)) { - /* Id is still in use by a filter, query, rule or observer */ - ecs_assert((idr->keep_alive == 0), - ECS_ID_IN_USE, "cannot delete id that is queried for"); - } + /* Id is still in use by a filter, query, rule or observer */ + ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0), + ECS_ID_IN_USE, "cannot delete id that is queried for"); if (ECS_IS_PAIR(id)) { - ecs_entity_t rel = ecs_pair_first(world, id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ECS_PAIR_SECOND(id); if (!ecs_id_is_wildcard(id)) { if (ECS_PAIR_FIRST(id) != EcsFlag) { /* If id is not a wildcard, remove it from the wildcard lists */ flecs_remove_id_elem(idr, ecs_pair(rel, EcsWildcard)); - flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, obj)); + flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, tgt)); } } else { ecs_log_push_2(); @@ -60988,7 +62789,7 @@ void flecs_id_record_release_tables( /* Check if the only thing keeping the next table alive is our * claim. If so, move to the next record before releasing */ if (next) { - if (next->refcount == 1) { + if (next->_->refcount == 1) { it.next = it.next->next; } @@ -61024,6 +62825,18 @@ bool flecs_id_record_set_type_info( return changed; } +ecs_hashmap_t* flecs_id_record_name_index_ensure( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + ecs_hashmap_t *map = idr->name_index; + if (!map) { + map = idr->name_index = flecs_name_index_new(world, &world->allocator); + } + + return map; +} + ecs_hashmap_t* flecs_id_name_index_ensure( ecs_world_t *world, ecs_id_t id) @@ -61033,12 +62846,7 @@ ecs_hashmap_t* flecs_id_name_index_ensure( ecs_id_record_t *idr = flecs_id_record_get(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_hashmap_t *map = idr->name_index; - if (!map) { - map = idr->name_index = flecs_name_index_new(world, &world->allocator); - } - - return map; + return flecs_id_record_name_index_ensure(world, idr); } ecs_hashmap_t* flecs_id_name_index_get( diff --git a/code/vendors/flecs/flecs.h b/code/vendors/flecs/flecs.h index f5973b1..e28ea82 100644 --- a/code/vendors/flecs/flecs.h +++ b/code/vendors/flecs/flecs.h @@ -151,6 +151,7 @@ #define FLECS_STATS /**< Access runtime statistics */ #define FLECS_MONITOR /**< Track runtime statistics periodically */ #define FLECS_METRICS /**< Expose component data as statistics */ +#define FLECS_ALERTS /**< Monitor conditions for errors */ #define FLECS_SYSTEM /**< System support */ #define FLECS_PIPELINE /**< Pipeline support */ #define FLECS_TIMER /**< Timer support */ @@ -244,6 +245,10 @@ * Maximum number of query variables per query */ #define FLECS_VARIABLE_COUNT_MAX (64) +/** \def FLECS_QUERY_SCOPE_NESTING_MAX + * Maximum nesting depth of query scopes */ +#define FLECS_QUERY_SCOPE_NESTING_MAX (8) + /** @} */ /** @@ -333,10 +338,16 @@ extern "C" { #define EcsIdHasOnRemove (1u << 17) #define EcsIdHasOnSet (1u << 18) #define EcsIdHasUnSet (1u << 19) +#define EcsIdHasOnTableFill (1u << 20) +#define EcsIdHasOnTableEmpty (1u << 21) +#define EcsIdHasOnTableCreate (1u << 22) +#define EcsIdHasOnTableDelete (1u << 23) #define EcsIdEventMask\ - (EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet|EcsIdHasUnSet) + (EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet|EcsIdHasUnSet|\ + EcsIdHasOnTableFill|EcsIdHasOnTableEmpty|EcsIdHasOnTableCreate|\ + EcsIdHasOnTableDelete) -#define EcsIdMarkedForDelete (1u << 30) +#define EcsIdMarkedForDelete (1u << 30) /* Utilities for converting from flags to delete policies and vice versa */ #define ECS_ID_ON_DELETE(flags) \ @@ -385,7 +396,8 @@ extern "C" { #define EcsFilterPopulate (1u << 9u) /* Populate data, ignore non-matching fields */ #define EcsFilterHasCondSet (1u << 10u) /* Does filter have conditionally set fields */ #define EcsFilterUnresolvedByName (1u << 11u) /* Use by-name matching for unresolved entity identifiers */ - +#define EcsFilterHasPred (1u << 12u) /* Filter has equality predicates */ +#define EcsFilterHasScopes (1u << 13u) /* Filter has query scopes */ //////////////////////////////////////////////////////////////////////////////// //// Table flags (used by ecs_table_t::flags) @@ -411,9 +423,13 @@ extern "C" { #define EcsTableHasOnRemove (1u << 17u) #define EcsTableHasOnSet (1u << 18u) #define EcsTableHasUnSet (1u << 19u) +#define EcsTableHasOnTableFill (1u << 20u) +#define EcsTableHasOnTableEmpty (1u << 21u) +#define EcsTableHasOnTableCreate (1u << 22u) +#define EcsTableHasOnTableDelete (1u << 23u) -#define EcsTableHasObserved (1u << 20u) -#define EcsTableHasTarget (1u << 21u) +#define EcsTableHasTraversable (1u << 25u) +#define EcsTableHasTarget (1u << 26u) #define EcsTableMarkedForDelete (1u << 30u) @@ -1929,6 +1945,16 @@ void* (*ecs_os_api_thread_join_t)( typedef ecs_os_thread_id_t (*ecs_os_api_thread_self_t)(void); +/* Tasks */ +typedef +ecs_os_thread_t (*ecs_os_api_task_new_t)( + ecs_os_thread_callback_t callback, + void *param); + +typedef +void* (*ecs_os_api_task_join_t)( + ecs_os_thread_t thread); + /* Atomic increment / decrement */ typedef int32_t (*ecs_os_api_ainc_t)( @@ -2046,6 +2072,10 @@ typedef struct ecs_os_api_t { ecs_os_api_thread_join_t thread_join_; ecs_os_api_thread_self_t thread_self_; + /* Tasks */ + ecs_os_api_thread_new_t task_new_; + ecs_os_api_thread_join_t task_join_; + /* Atomic incremenet / decrement */ ecs_os_api_ainc_t ainc_; ecs_os_api_ainc_t adec_; @@ -2228,6 +2258,10 @@ void ecs_os_set_api_defaults(void); #define ecs_os_thread_join(thread) ecs_os_api.thread_join_(thread) #define ecs_os_thread_self() ecs_os_api.thread_self_() +/* Tasks */ +#define ecs_os_task_new(callback, param) ecs_os_api.task_new_(callback, param) +#define ecs_os_task_join(thread) ecs_os_api.task_join_(thread) + /* Atomic increment / decrement */ #define ecs_os_ainc(value) ecs_os_api.ainc_(value) #define ecs_os_adec(value) ecs_os_api.adec_(value) @@ -2332,6 +2366,10 @@ bool ecs_os_has_heap(void); FLECS_API bool ecs_os_has_threading(void); +/** Are task functions available? */ +FLECS_API +bool ecs_os_has_task_support(void); + /** Are time functions available? */ FLECS_API bool ecs_os_has_time(void); @@ -2666,13 +2704,17 @@ typedef enum ecs_oper_kind_t { #define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsParent) /* Term flags discovered & set during filter creation. */ -#define EcsTermMatchAny (1 << 0) -#define EcsTermMatchAnySrc (1 << 1) -#define EcsTermSrcFirstEq (1 << 2) -#define EcsTermSrcSecondEq (1 << 3) -#define EcsTermTransitive (1 << 4) -#define EcsTermReflexive (1 << 5) -#define EcsTermIdInherited (1 << 6) +#define EcsTermMatchAny (1u << 0) +#define EcsTermMatchAnySrc (1u << 1) +#define EcsTermSrcFirstEq (1u << 2) +#define EcsTermSrcSecondEq (1u << 3) +#define EcsTermTransitive (1u << 4) +#define EcsTermReflexive (1u << 5) +#define EcsTermIdInherited (1u << 6) + +/* Term flags used for term iteration */ +#define EcsTermMatchDisabled (1u << 7) +#define EcsTermMatchPrefab (1u << 8) /** Type that describes a single identifier in a term */ typedef struct ecs_term_id_t { @@ -2881,7 +2923,7 @@ typedef struct ecs_data_t ecs_data_t; typedef struct ecs_switch_t ecs_switch_t; /* Cached query table data */ -typedef struct ecs_query_table_node_t ecs_query_table_node_t; +typedef struct ecs_query_table_match_t ecs_query_table_match_t; //////////////////////////////////////////////////////////////////////////////// //// Non-opaque types @@ -3012,7 +3054,7 @@ typedef struct ecs_filter_iter_t { /** Query-iterator specific data */ typedef struct ecs_query_iter_t { ecs_query_t *query; - ecs_query_table_node_t *node, *prev, *last; + ecs_query_table_match_t *node, *prev, *last; int32_t sparse_smallest; int32_t sparse_first; int32_t bitset_first; @@ -3301,8 +3343,8 @@ void _flecs_hashmap_init( ecs_compare_action_t compare, ecs_allocator_t *allocator); -#define flecs_hashmap_init(hm, K, V, compare, hash, allocator)\ - _flecs_hashmap_init(hm, ECS_SIZEOF(K), ECS_SIZEOF(V), compare, hash, allocator) +#define flecs_hashmap_init(hm, K, V, hash, compare, allocator)\ + _flecs_hashmap_init(hm, ECS_SIZEOF(K), ECS_SIZEOF(V), hash, compare, allocator) FLECS_DBG_API void flecs_hashmap_fini( @@ -4040,6 +4082,10 @@ FLECS_API extern const ecs_entity_t EcsPredEq; FLECS_API extern const ecs_entity_t EcsPredMatch; FLECS_API extern const ecs_entity_t EcsPredLookup; +/* Builtin marker entities for opening/closing query scopes */ +FLECS_API extern const ecs_entity_t EcsScopeOpen; +FLECS_API extern const ecs_entity_t EcsScopeClose; + /** Tag used to indicate query is empty */ FLECS_API extern const ecs_entity_t EcsEmpty; @@ -4610,6 +4656,10 @@ bool ecs_enable_range_check( ecs_world_t *world, bool enable); +/** Get the largest issued entity id (not counting generation). + * + * @param world The world. + */ FLECS_API ecs_entity_t ecs_get_max_id( const ecs_world_t *world); @@ -9719,6 +9769,7 @@ int ecs_app_set_frame_action( #endif // FLECS_APP #endif + #ifdef FLECS_HTTP #ifdef FLECS_NO_HTTP #error "FLECS_NO_HTTP failed: HTTP is required by other addons" @@ -9964,6 +10015,7 @@ const char* ecs_http_get_param( #endif // FLECS_HTTP #endif + #ifdef FLECS_REST #ifdef FLECS_NO_REST #error "FLECS_NO_REST failed: REST is required by other addons" @@ -10079,6 +10131,7 @@ void FlecsRestImport( #endif #endif + #ifdef FLECS_TIMER #ifdef FLECS_NO_TIMER #error "FLECS_NO_TIMER failed: TIMER is required by other addons" @@ -10330,6 +10383,7 @@ void FlecsTimerImport( #endif #endif + #ifdef FLECS_PIPELINE #ifdef FLECS_NO_PIPELINE #error "FLECS_NO_PIPELINE failed: PIPELINE is required by other addons" @@ -10517,12 +10571,37 @@ void ecs_run_pipeline( * Setting this value to a value higher than 1 will start as many threads and * will cause systems to evenly distribute matched entities across threads. The * operation may be called multiple times to reconfigure the number of threads - * used, but never while running a system / pipeline. */ + * used, but never while running a system / pipeline. + * Calling ecs_set_threads will also end the use of task threads setup with + * ecs_set_task_threads and vice-versa */ FLECS_API void ecs_set_threads( ecs_world_t *world, int32_t threads); +/** Set number of worker task threads. + * ecs_set_task_threads is similar to ecs_set_threads, except threads are treated + * as short-lived tasks and will be created and joined around each update of the world. + * Creation and joining of these tasks will use the os_api_t tasks APIs rather than the + * the standard thread API functions, although they may be the same if desired. + * This function is useful for multithreading world updates using an external + * asynchronous job system rather than long running threads by providing the APIs + * to create tasks for your job system and then wait on their conclusion. + * The operation may be called multiple times to reconfigure the number of task threads + * used, but never while running a system / pipeline. + * Calling ecs_set_task_threads will also end the use of threads setup with + * ecs_set_threads and vice-versa */ + +FLECS_API +void ecs_set_task_threads( + ecs_world_t *world, + int32_t task_threads); + +/** Returns true if task thread use have been requested. */ +FLECS_API +bool ecs_using_task_threads( + ecs_world_t *world); + //////////////////////////////////////////////////////////////////////////////// //// Module //////////////////////////////////////////////////////////////////////////////// @@ -10542,6 +10621,7 @@ void FlecsPipelineImport( #endif #endif + #ifdef FLECS_SYSTEM #ifdef FLECS_NO_SYSTEM #error "FLECS_NO_SYSTEM failed: SYSTEM is required by other addons" @@ -10840,6 +10920,7 @@ void FlecsSystemImport( #endif #endif + #ifdef FLECS_STATS #ifdef FLECS_NO_STATS #error "FLECS_NO_STATS failed: STATS is required by other addons" @@ -11272,6 +11353,7 @@ void ecs_metric_copy( #endif #endif + #ifdef FLECS_METRICS #ifdef FLECS_NO_METRICS #error "FLECS_NO_METRICS failed: METRICS is required by other addons" @@ -11287,9 +11369,8 @@ void ecs_metric_copy( #ifdef FLECS_METRICS /** - * @defgroup c_addons_monitor Monitor - * @brief * The metrics module extracts metrics from components and makes them - * available through a unified component interface. + * @defgroup c_addons_metrics Metrics + * @brief Collect user-defined metrics from ECS data. * * \ingroup c_addons * @{ @@ -11349,30 +11430,71 @@ typedef struct EcsMetricSource { } EcsMetricSource; typedef struct ecs_metric_desc_t { - /* Entity associated with metric */ + int32_t _canary; + + /** Entity associated with metric */ ecs_entity_t entity; - /* Entity associated with member that stores metric value. Must not be set + /** Entity associated with member that stores metric value. Must not be set * at the same time as id. Cannot be combined with EcsCounterId. */ ecs_entity_t member; - /* Tracks whether entities have the specified component id. Must not be set + /** Tracks whether entities have the specified component id. Must not be set * at the same time as member. */ ecs_id_t id; - /* If id is a (R, *) wildcard and relationship R has the OneOf property, the + /** If id is a (R, *) wildcard and relationship R has the OneOf property, * setting this value to true will track individual targets. * If the kind is EcsCountId and the id is a (R, *) wildcard, this value * will create a metric per target. */ bool targets; - /* Must be EcsGauge, EcsCounter, EcsCounterIncrement or EcsCounterId */ + /** Must be EcsGauge, EcsCounter, EcsCounterIncrement or EcsCounterId */ ecs_entity_t kind; - /* Description of metric. Will only be set if FLECS_DOC addon is enabled */ + /** Description of metric. Will only be set if FLECS_DOC addon is enabled */ const char *brief; } ecs_metric_desc_t; +/** Create a new metric. + * Metrics are entities that store values measured from a range of different + * properties in the ECS storage. Metrics provide a single unified interface to + * discovering and reading these values, which can be useful for monitoring + * utilities, or for debugging. + * + * Examples of properties that can be measured by metrics are: + * - Component member values + * - How long an entity has had a specific component + * - How long an entity has had a specific target for a relationship + * - How many entities have a specific component + * + * Metrics can either be created as a "gauge" or "counter". A gauge is a metric + * that represents the value of something at a specific point in time, for + * example "velocity". A counter metric represents a value that is monotonically + * increasing, for example "miles driven". + * + * There are three different kinds of counter metric kinds: + * - EcsCounter + * When combined with a member, this will store the actual value of the member + * in the metric. This is useful for values that are already counters, such as + * a MilesDriven component. + * This kind creates a metric per entity that has the member/id. + * + * - EcsCounterIncrement + * When combined with a member, this will increment the value of the metric by + * the value of the member * delta_time. This is useful for values that are + * not counters, such as a Velocity component. + * This kind creates a metric per entity that has the member. + * + * - EcsCounterId + * This metric kind will count the number of entities with a specific + * (component) id. This kind creates a single metric instance for regular ids, + * and a metric instance per target for wildcard ids when targets is set. + * + * @param world The world. + * @param desc Metric description. + * @return The metric entity. + */ FLECS_API ecs_entity_t ecs_metric_init( ecs_world_t *world, @@ -11405,12 +11527,226 @@ void FlecsMetricsImport( #endif #endif + +#ifdef FLECS_ALERTS +#ifdef FLECS_NO_ALERTS +#error "FLECS_NO_ALERTS failed: ALERTS is required by other addons" +#endif +/** + * @file addons/alerts.h + * @brief Alerts module. + * + * The alerts module enables applications to register alerts for when certain + * conditions are met. Alerts are registered as queries, and automatically + * become active when entities match the alert query. + */ + +#ifdef FLECS_ALERTS + +/** + * @defgroup c_addons_alerts Alerts + * @brief Create alerts from monitoring queries. + * + * \ingroup c_addons + * @{ + */ + +#ifndef FLECS_ALERTS_H +#define FLECS_ALERTS_H + +#ifndef FLECS_RULES +#define FLECS_RULES +#endif + +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_ALERT_MAX_SEVERITY_FILTERS (4) + +/* Module id */ +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsAlerts); + +/* Module components */ + +/** Tag added to alert, and used as first element of alert severity pair */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlert); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlertInstance); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlertsActive); + +/* Alert severity tags */ +FLECS_API extern ECS_TAG_DECLARE(EcsAlertInfo); +FLECS_API extern ECS_TAG_DECLARE(EcsAlertWarning); +FLECS_API extern ECS_TAG_DECLARE(EcsAlertError); +FLECS_API extern ECS_TAG_DECLARE(EcsAlertCritical); + +/** Alert information. Added to each alert instance */ +typedef struct EcsAlertInstance { + char *message; +} EcsAlertInstance; + +/** Map with active alerts for entity. */ +typedef struct EcsAlertsActive { + ecs_map_t alerts; +} EcsAlertsActive; + +typedef struct ecs_alert_severity_filter_t { + ecs_entity_t severity; /* Severity kind */ + ecs_id_t with; /* Component to match */ + const char *var; /* Variable to match component on. Do not include the + * '$' character. Leave to NULL for $this. */ + int32_t _var_index; /* Index of variable in filter (do not set) */ +} ecs_alert_severity_filter_t; + +typedef struct ecs_alert_desc_t { + int32_t _canary; + + /** Entity associated with alert */ + ecs_entity_t entity; + + /** Alert query. An alert will be created for each entity that matches the + * specified query. The query must have at least one term that uses the + * $this variable (default). */ + ecs_filter_desc_t filter; + + /** Template for alert message. This string is used to generate the alert + * message and may refer to variables in the query result. The format for + * the template expressions is as specified by ecs_interpolate_string. + * + * Examples: + * "$this has Position but not Velocity" + * "$this has a parent entity $parent without Position" + */ + const char *message; + + /** User friendly name. Will only be set if FLECS_DOC addon is enabled. */ + const char *doc_name; + + /** Description of alert. Will only be set if FLECS_DOC addon is enabled */ + const char *brief; + + /** Metric kind. Must be EcsAlertInfo, EcsAlertWarning, EcsAlertError or + * EcsAlertCritical. Defaults to EcsAlertError. */ + ecs_entity_t severity; + + /** Severity filters can be used to assign different severities to the same + * alert. This prevents having to create multiple alerts, and allows + * entities to transition between severities without resetting the + * alert duration (optional). */ + ecs_alert_severity_filter_t severity_filters[ECS_ALERT_MAX_SEVERITY_FILTERS]; + + /** The retain period specifies how long an alert must be inactive before it + * is cleared. This makes it easier to track noisy alerts. While an alert is + * inactive its duration won't increase. + * When the retain period is 0, the alert will clear immediately after it no + * longer matches the alert query. */ + ecs_ftime_t retain_period; + + /** Alert when member value is out of range. Uses the warning/error ranges + * assigned to the member in the MemberRanges component (optional). */ + ecs_entity_t member; + + /** (Component) id of member to monitor. If left to 0 this will be set to + * the parent entity of the member (optional). */ + ecs_id_t id; + + /** Variable from which to fetch the member (optional). When left to NULL + * 'id' will be obtained from $this. */ + const char *var; +} ecs_alert_desc_t; + +/** Create a new alert. + * An alert is a query that is evaluated periodically and creates alert + * instances for each entity that matches the query. Alerts can be used to + * automate detection of errors in an application. + * + * Alerts are automatically cleared when a query is no longer true for an alert + * instance. At most one alert instance will be created per matched entity. + * + * Alert instances have three components: + * - AlertInstance: contains the alert message for the instance + * - MetricSource: contains the entity that triggered the alert + * - MetricValue: contains how long the alert has been active + * + * Alerts reuse components from the metrics addon so that alert instances can be + * tracked and discovered as metrics. Just like metrics, alert instances are + * created as children of the alert. + * + * When an entity has active alerts, it will have the EcsAlertsActive component + * which contains a map with active alerts for the entity. This component + * will be automatically removed once all alerts are cleared for the entity. + * + * @param world The world. + * @param desc Alert description. + * @return The alert entity. + */ +FLECS_API +ecs_entity_t ecs_alert_init( + ecs_world_t *world, + const ecs_alert_desc_t *desc); + +#define ecs_alert(world, ...)\ + ecs_alert_init(world, &(ecs_alert_desc_t)__VA_ARGS__) + +/** Return number of active alerts for entity. + * When a valid alert entity is specified for the alert parameter, the operation + * will return whether the specified alert is active for the entity. When no + * alert is specified, the operation will return the total number of active + * alerts for the entity. + * + * @param world The world. + * @param entity The entity. + * @param alert The alert to test for (optional). + * @return The number of active alerts for the entity. + */ +FLECS_API +int32_t ecs_get_alert_count( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert); + +/** Return alert instance for specified alert. + * This operation returns the alert instance for the specified alert. If the + * alert is not active for the entity, the operation will return 0. + * + * @param world The world. + * @param entity The entity. + * @param alert The alert to test for. + * @return The alert instance for the specified alert. + */ +FLECS_API +ecs_entity_t ecs_get_alert( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert); + +/* Module import */ +FLECS_API +void FlecsAlertsImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + #ifdef FLECS_MONITOR #ifdef FLECS_NO_MONITOR #error "FLECS_NO_MONITOR failed: MONITOR is required by other addons" #endif /** - * @file addons/doc.h + * @file addons/monitor.h * @brief Doc module. * * The monitor module automatically tracks statistics from the stats addon and @@ -11444,6 +11780,7 @@ extern "C" { FLECS_API extern ECS_COMPONENT_DECLARE(FlecsMonitor); FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldStats); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldSummary); FLECS_API extern ECS_COMPONENT_DECLARE(EcsPipelineStats); FLECS_API extern ecs_entity_t EcsPeriod1s; @@ -11467,6 +11804,21 @@ typedef struct { ecs_pipeline_stats_t stats; } EcsPipelineStats; +typedef struct { + /* Target FPS */ + double target_fps; /**< Target FPS */ + + /* Total time */ + double frame_time_total; /**< Total time spent processing a frame */ + double system_time_total; /**< Total time spent in systems */ + double merge_time_total; /**< Total time spent in merges */ + + /* Last frame time */ + double frame_time_last; /**< Time spent processing a frame */ + double system_time_last; /**< Time spent in systems */ + double merge_time_last; /**< Time spent in merges */ +} EcsWorldSummary; + /* Module import */ FLECS_API void FlecsMonitorImport( @@ -11483,6 +11835,7 @@ void FlecsMonitorImport( #endif #endif + #ifdef FLECS_COREDOC #ifdef FLECS_NO_COREDOC #error "FLECS_NO_COREDOC failed: COREDOC is required by other addons" @@ -11537,6 +11890,7 @@ void FlecsCoreDocImport( #endif #endif + #ifdef FLECS_DOC #ifdef FLECS_NO_DOC #error "FLECS_NO_DOC failed: DOC is required by other addons" @@ -11723,6 +12077,7 @@ void FlecsDocImport( #endif #endif + #ifdef FLECS_JSON #ifdef FLECS_NO_JSON #error "FLECS_NO_JSON failed: JSON is required by other addons" @@ -11760,9 +12115,11 @@ extern "C" { /** Used with ecs_ptr_from_json, ecs_entity_from_json. */ typedef struct ecs_from_json_desc_t { - const char *name; /* Name of expression (used for logging) */ - const char *expr; /* Full expression (used for logging) */ + const char *name; /**< Name of expression (used for logging) */ + const char *expr; /**< Full expression (used for logging) */ + /** Callback that allows for specifying a custom lookup function. The + * default behavior uses ecs_lookup_fullpath */ ecs_entity_t (*lookup_action)( const ecs_world_t*, const char *value, @@ -11930,10 +12287,11 @@ typedef struct ecs_entity_to_json_desc_t { bool serialize_hidden; /**< Serialize ids hidden by override */ bool serialize_values; /**< Serialize component values */ bool serialize_type_info; /**< Serialize type info (requires serialize_values) */ + bool serialize_alerts; /**< Serialize active alerts for entity */ } ecs_entity_to_json_desc_t; #define ECS_ENTITY_TO_JSON_INIT (ecs_entity_to_json_desc_t){true, false,\ - false, false, false, false, false, true, false, false, false, false } + false, false, false, false, false, true, false, false, false, false, false } /** Serialize entity into JSON string. * This creates a JSON object with the entity's (path) name, which components @@ -12036,8 +12394,8 @@ int ecs_iter_to_json_buf( /** Used with ecs_iter_to_json. */ typedef struct ecs_world_to_json_desc_t { - bool serialize_builtin; /* Exclude flecs modules & contents */ - bool serialize_modules; /* Exclude modules & contents */ + bool serialize_builtin; /**< Exclude flecs modules & contents */ + bool serialize_modules; /**< Exclude modules & contents */ } ecs_world_to_json_desc_t; /** Serialize world into JSON string. @@ -12084,6 +12442,7 @@ int ecs_world_to_json_buf( #endif #endif + #if defined(FLECS_EXPR) || defined(FLECS_META_C) #ifndef FLECS_META #define FLECS_META @@ -12439,6 +12798,7 @@ void FlecsUnitsImport( #endif #endif + #ifdef FLECS_META #ifdef FLECS_NO_META #error "FLECS_NO_META failed: META is required by other addons" @@ -12553,6 +12913,7 @@ FLECS_API extern const ecs_entity_t ecs_id(EcsPrimitive); FLECS_API extern const ecs_entity_t ecs_id(EcsEnum); FLECS_API extern const ecs_entity_t ecs_id(EcsBitmask); FLECS_API extern const ecs_entity_t ecs_id(EcsMember); +FLECS_API extern const ecs_entity_t ecs_id(EcsMemberRanges); FLECS_API extern const ecs_entity_t ecs_id(EcsStruct); FLECS_API extern const ecs_entity_t ecs_id(EcsArray); FLECS_API extern const ecs_entity_t ecs_id(EcsVector); @@ -12581,7 +12942,7 @@ FLECS_API extern const ecs_entity_t ecs_id(ecs_f64_t); FLECS_API extern const ecs_entity_t ecs_id(ecs_string_t); FLECS_API extern const ecs_entity_t ecs_id(ecs_entity_t); -/** Type kinds supported by reflection type system */ +/** Type kinds supported by meta addon */ typedef enum ecs_type_kind_t { EcsPrimitiveType, EcsBitmaskType, @@ -12602,6 +12963,7 @@ typedef struct EcsMetaType { ecs_size_t alignment; /**< Computed alignment */ } EcsMetaType; +/** Primitive type kinds supported by meta addon */ typedef enum ecs_primitive_kind_t { EcsBool = 1, EcsChar, @@ -12623,10 +12985,12 @@ typedef enum ecs_primitive_kind_t { EcsPrimitiveKindLast = EcsEntity } ecs_primitive_kind_t; +/** Component added to primitive types */ typedef struct EcsPrimitive { ecs_primitive_kind_t kind; } EcsPrimitive; +/** Component added to member entities */ typedef struct EcsMember { ecs_entity_t type; int32_t count; @@ -12634,6 +12998,19 @@ typedef struct EcsMember { int32_t offset; } EcsMember; +/** Type expressing a range for a member value */ +typedef struct ecs_member_value_range_t { + double min; + double max; +} ecs_member_value_range_t; + +/** Component added to member entities to express valid value ranges */ +typedef struct EcsMemberRanges { + ecs_member_value_range_t value; + ecs_member_value_range_t warning; + ecs_member_value_range_t error; +} EcsMemberRanges; + /** Element type of members vector in EcsStruct */ typedef struct ecs_member_t { /** Must be set when used with ecs_struct_desc_t */ @@ -12648,11 +13025,25 @@ typedef struct ecs_member_t { * type entity is also a unit */ ecs_entity_t unit; + /** Numerical range that specifies which values member can assume. This + * range may be used by UI elements such as a progress bar or slider. The + * value of a member should not exceed this range. */ + ecs_member_value_range_t range; + + /** Numerical range outside of which the value represents an error. This + * range may be used by UI elements to style a value. */ + ecs_member_value_range_t error_range; + + /** Numerical range outside of which the value represents an warning. This + * range may be used by UI elements to style a value. */ + ecs_member_value_range_t warning_range; + /** Should not be set by ecs_struct_desc_t */ ecs_size_t size; ecs_entity_t member; } ecs_member_t; +/** Component added to struct type entities */ typedef struct EcsStruct { /** Populated from child entities with Member component */ ecs_vec_t members; /* vector */ @@ -12669,6 +13060,7 @@ typedef struct ecs_enum_constant_t { ecs_entity_t constant; } ecs_enum_constant_t; +/** Component added to enum type entities */ typedef struct EcsEnum { /** Populated from child entities with Constant component */ ecs_map_t constants; /* map */ @@ -12685,18 +13077,21 @@ typedef struct ecs_bitmask_constant_t { ecs_entity_t constant; } ecs_bitmask_constant_t; +/** Component added to bitmask type entities */ typedef struct EcsBitmask { /* Populated from child entities with Constant component */ ecs_map_t constants; /* map */ } EcsBitmask; +/** Component added to array type entities */ typedef struct EcsArray { - ecs_entity_t type; - int32_t count; + ecs_entity_t type; /**< Element type */ + int32_t count; /**< Number of elements */ } EcsArray; +/** Component added to vector type entities */ typedef struct EcsVector { - ecs_entity_t type; + ecs_entity_t type; /**< Element type */ } EcsVector; @@ -12709,13 +13104,13 @@ typedef struct ecs_serializer_t { /* Serialize value */ int (*value)( const struct ecs_serializer_t *ser, /**< Serializer */ - ecs_entity_t type, /**< Type of the value to serialize */ - const void *value); /**< Pointer to the value to serialize */ + ecs_entity_t type, /**< Type of the value to serialize */ + const void *value); /**< Pointer to the value to serialize */ /* Serialize member */ int (*member)( const struct ecs_serializer_t *ser, /**< Serializer */ - const char *member); /**< Member name */ + const char *member); /**< Member name */ const ecs_world_t *world; void *ctx; @@ -12906,8 +13301,8 @@ typedef struct ecs_meta_type_op_t { const char *name; /**< Name of value (only used for struct members) */ int32_t op_count; /**< Number of operations until next field or end */ ecs_size_t size; /**< Size of type of operation */ - ecs_entity_t type; - ecs_entity_t unit; + ecs_entity_t type; /**< Type entity */ + int32_t member_index; /**< Index of member in struct */ ecs_hashmap_t *members; /**< string -> member index (structs only) */ } ecs_meta_type_op_t; @@ -12931,7 +13326,7 @@ typedef struct ecs_meta_scope_t { const EcsComponent *comp; /**< Pointer to component, in case size/alignment is needed */ const EcsOpaque *opaque; /**< Opaque type interface */ - ecs_vec_t *vector; /**< Current vector, in case a vector is iterated */ + ecs_vec_t *vector; /**< Current vector, in case a vector is iterated */ ecs_hashmap_t *members; /**< string -> member index */ bool is_collection; /**< Is the scope iterating elements? */ bool is_inline_array; /**< Is the scope iterating an inline array? */ @@ -13340,6 +13735,7 @@ void FlecsMetaImport( #endif #endif + #ifdef FLECS_EXPR #ifdef FLECS_NO_EXPR #error "FLECS_NO_EXPR failed: EXPR is required by other addons" @@ -13517,7 +13913,7 @@ ecs_expr_var_t* ecs_vars_declare_w_value( /** Lookup variable in scope and parent scopes */ FLECS_API ecs_expr_var_t* ecs_vars_lookup( - ecs_vars_t *vars, + const ecs_vars_t *vars, const char *name); /** Used with ecs_parse_expr. */ @@ -13568,7 +13964,7 @@ char* ecs_ptr_to_expr( ecs_entity_t type, const void *data); -/** Serialize value into string buffer. +/** Serialize value into expression buffer. * Same as ecs_ptr_to_expr, but serializes to an ecs_strbuf_t instance. * * @param world The world. @@ -13584,6 +13980,39 @@ int ecs_ptr_to_expr_buf( const void *data, ecs_strbuf_t *buf); +/** Similar as ecs_ptr_to_expr, but serializes values to string. + * Whereas the output of ecs_ptr_to_expr is a valid expression, the output of + * ecs_ptr_to_str is a string representation of the value. In most cases the + * output of the two operations is the same, but there are some differences: + * - Strings are not quoted + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @return String with result, or NULL if failed. + */ +FLECS_API +char* ecs_ptr_to_str( + const ecs_world_t *world, + ecs_entity_t type, + const void *data); + +/** Serialize value into string buffer. + * Same as ecs_ptr_to_str, but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param buf The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_ptr_to_str_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + ecs_strbuf_t *buf); + /** Serialize primitive value into string buffer. * Serializes a primitive value to an ecs_strbuf_t instance. This operation can * be reused by other serializers to avoid having to write boilerplate code that @@ -13619,6 +14048,62 @@ const char *ecs_parse_expr_token( const char *ptr, char *token); +/** Evaluate interpolated expressions in string. + * This operation evaluates expressions in a string, and replaces them with + * their evaluated result. Supported expression formats are: + * - $variable_name + * - {expression} + * + * The $, { and } characters can be escaped with a backslash (\). + * + * @param world The world. + * @param str The string to evaluate. + * @param vars The variables to use for evaluation. + */ +FLECS_API +char* ecs_interpolate_string( + ecs_world_t *world, + const char *str, + const ecs_vars_t *vars); + +/** Convert iterator to vars + * This operation converts an iterator to a variable array. This allows for + * using iterator results in expressions. The operation only converts a + * single result at a time, and does not progress the iterator. + * + * Iterator fields with data will be made available as variables with as name + * the field index (e.g. "$1"). The operation does not check if reflection data + * is registered for a field type. If no reflection data is registered for the + * type, using the field variable in expressions will fail. + * + * Field variables will only contain single elements, even if the iterator + * returns component arrays. The offset parameter can be used to specify which + * element in the component arrays to return. The offset parameter must be + * smaller than it->count. + * + * The operation will create a variable for query variables that contain a + * single entity. + * + * The operation will attempt to use existing variables. If a variable does not + * yet exist, the operation will create it. If an existing variable exists with + * a mismatching type, the operation will fail. + * + * Accessing variables after progressing the iterator or after the iterator is + * destroyed will result in undefined behavior. + * + * If vars contains a variable that is not present in the iterator, the variable + * will not be modified. + * + * @param it The iterator to convert to variables. + * @param vars The variables to write to. + * @param offset The offset to the current element. + */ +FLECS_API +void ecs_iter_to_vars( + const ecs_iter_t *it, + ecs_vars_t *vars, + int offset); + /** @} */ #ifdef __cplusplus @@ -13630,6 +14115,7 @@ const char *ecs_parse_expr_token( #endif #endif + #ifdef FLECS_META_C #ifdef FLECS_NO_META_C #error "FLECS_NO_META_C failed: META_C is required by other addons" @@ -13792,6 +14278,7 @@ int ecs_meta_from_desc( #endif // FLECS_META_C #endif + #ifdef FLECS_PLECS #ifdef FLECS_NO_PLECS #error "FLECS_NO_PLECS failed: PLECS is required by other addons" @@ -13940,6 +14427,7 @@ void FlecsScriptImport( #endif #endif + #ifdef FLECS_RULES #ifdef FLECS_NO_RULES #error "FLECS_NO_RULES failed: RULES is required by other addons" @@ -14179,6 +14667,7 @@ const char* ecs_rule_parse_vars( #endif // FLECS_RULES #endif + #ifdef FLECS_SNAPSHOT #ifdef FLECS_NO_SNAPSHOT #error "FLECS_NO_SNAPSHOT failed: SNAPSHOT is required by other addons" @@ -14292,6 +14781,7 @@ void ecs_snapshot_free( #endif #endif + #ifdef FLECS_PARSER #ifdef FLECS_NO_PARSER #error "FLECS_NO_PARSER failed: PARSER is required by other addons" @@ -14424,6 +14914,7 @@ char* ecs_parse_term( #endif // FLECS_PARSER #endif + #ifdef FLECS_OS_API_IMPL #ifdef FLECS_NO_OS_API_IMPL #error "FLECS_NO_OS_API_IMPL failed: OS_API_IMPL is required by other addons" @@ -14464,6 +14955,7 @@ void ecs_set_os_api_impl(void); #endif // FLECS_OS_API_IMPL #endif + #ifdef FLECS_MODULE #ifdef FLECS_NO_MODULE #error "FLECS_NO_MODULE failed: MODULE is required by other addons" @@ -14721,6 +15213,13 @@ int32_t ecs_cpp_reset_count_get(void); FLECS_API int32_t ecs_cpp_reset_count_inc(void); +#ifdef FLECS_META +FLECS_API +const ecs_member_t* ecs_cpp_last_member( + const ecs_world_t *world, + ecs_entity_t type); +#endif + #ifdef __cplusplus } #endif @@ -14921,6 +15420,10 @@ static const flecs::entity_t PredEq = EcsPredEq; static const flecs::entity_t PredMatch = EcsPredMatch; static const flecs::entity_t PredLookup = EcsPredLookup; +/* Builtin marker entities for query scopes */ +static const flecs::entity_t ScopeOpen = EcsScopeOpen; +static const flecs::entity_t ScopeClose = EcsScopeClose; + /** @} */ } @@ -16482,6 +16985,7 @@ using Primitive = EcsPrimitive; using Enum = EcsEnum; using Bitmask = EcsBitmask; using Member = EcsMember; +using MemberRanges = EcsMemberRanges; using Struct = EcsStruct; using Array = EcsArray; using Vector = EcsVector; @@ -17410,6 +17914,48 @@ struct metrics { } +#endif +#ifdef FLECS_ALERTS +/** + * @file addons/cpp/mixins/alerts/decl.hpp + * @brief Alert declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_alerts Alerts + * @brief Alert implementation. + * + * \ingroup cpp_addons + * @{ + */ + +/** Module */ +struct alerts { + using AlertsActive = EcsAlertsActive; + using Instance = EcsAlertInstance; + + struct Alert { }; + struct Info { }; + struct Warning { }; + struct Error { }; + + alerts(flecs::world& world); +}; + +template +struct alert; + +template +struct alert_builder; + +/** @} */ + +} + #endif #ifdef FLECS_JSON /** @@ -18940,6 +19486,13 @@ struct world { */ void remove(flecs::entity_t first, flecs::entity_t second) const; + /** Iterate entities in root of world + * Accepts a callback with the following signature: + * void(*)(flecs::entity e); + */ + template + void children(Func&& f) const; + /** Get singleton entity for type. */ template @@ -19621,6 +20174,16 @@ void set_threads(int32_t threads) const; */ int32_t get_threads() const; +/** Set number of task threads. + * @see ecs_set_task_threads + */ +void set_task_threads(int32_t task_threads) const; + +/** Returns true if task thread use has been requested. + * @see ecs_using_task_threads + */ +bool using_task_threads() const; + /** @} */ # endif @@ -19911,6 +20474,17 @@ flecs::app_builder app() { template flecs::metric_builder metric(Args &&... args) const; +# endif +# ifdef FLECS_ALERTS + +/** Create alert. + * + * \ingroup cpp_addons_alerts + * \memberof flecs::world + */ +template +flecs::alert_builder alert(Args &&... args) const; + # endif public: @@ -20622,6 +21196,14 @@ struct entity_view : public id { */ template void children(flecs::entity_t rel, Func&& func) const { + /* When the entity is a wildcard, this would attempt to query for all + * entities with (ChildOf, *) or (ChildOf, _) instead of querying for + * the children of the wildcard entity. */ + if (m_id == flecs::Wildcard || m_id == flecs::Any) { + /* This is correct, wildcard entities don't have children */ + return; + } + flecs::world world(m_world); ecs_term_t terms[2]; @@ -20630,18 +21212,20 @@ struct entity_view : public id { f.term_count = 2; ecs_filter_desc_t desc = {}; - desc.terms[0].id = ecs_pair(rel, m_id); + desc.terms[0].first.id = rel; + desc.terms[0].second.id = m_id; + desc.terms[0].second.flags = EcsIsEntity; desc.terms[1].id = flecs::Prefab; desc.terms[1].oper = EcsOptional; desc.storage = &f; - ecs_filter_init(m_world, &desc); + if (ecs_filter_init(m_world, &desc) != nullptr) { + ecs_iter_t it = ecs_filter_iter(m_world, &f); + while (ecs_filter_next(&it)) { + _::each_invoker(FLECS_MOV(func)).invoke(&it); + } - ecs_iter_t it = ecs_filter_iter(m_world, &f); - while (ecs_filter_next(&it)) { - _::each_invoker(FLECS_MOV(func)).invoke(&it); + ecs_filter_fini(&f); } - - ecs_filter_fini(&f); } /** Iterate children for entity. @@ -21200,6 +21784,22 @@ const char* doc_color() { return ecs_doc_get_color(m_world, m_id); } +# endif +# ifdef FLECS_ALERTS +/** + * @file addons/cpp/mixins/alerts/entity_view.inl + * @brief Alerts entity mixin. + */ + +/** Return number of alerts for entity. + * + * \memberof flecs::entity_view + * \ingroup cpp_addons_alerts + */ +int32_t alert_count(flecs::entity_t alert = 0) const { + return ecs_get_alert_count(m_world, m_id, alert); +} + # endif /** @@ -22154,6 +22754,11 @@ struct entity_builder : entity_view { return to_base(); } + /** Return world scoped to entity */ + scoped_world scope() const { + return scoped_world(m_world, m_id); + } + /* Set the entity name. */ Self& set_name(const char *name) { @@ -24107,6 +24712,55 @@ untyped_component& bit(const char *name, uint32_t value) { return *this; } +/** Add member value range */ +untyped_component& range(double min, double max) { + const flecs::member_t *m = ecs_cpp_last_member(m_world, m_id); + if (!m) { + return *this; + } + + flecs::world w(m_world); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = me.get_mut(); + mr->value.min = min; + mr->value.max = max; + me.modified(); + return *this; +} + +/** Add member warning range */ +untyped_component& warning_range(double min, double max) { + const flecs::member_t *m = ecs_cpp_last_member(m_world, m_id); + if (!m) { + return *this; + } + + flecs::world w(m_world); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = me.get_mut(); + mr->warning.min = min; + mr->warning.max = max; + me.modified(); + return *this; +} + +/** Add member error range */ +untyped_component& error_range(double min, double max) { + const flecs::member_t *m = ecs_cpp_last_member(m_world, m_id); + if (!m) { + return *this; + } + + flecs::world w(m_world); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = me.get_mut(); + mr->error.min = min; + mr->error.max = max; + me.modified(); + return *this; +} + + /** @} */ # endif @@ -25997,6 +26651,15 @@ struct filter_builder_i : term_builder_i { return this->term().read(); } + /* Scope_open/scope_close shorthand notation. */ + Base& scope_open() { + return this->with(flecs::ScopeOpen).entity(0); + } + + Base& scope_close() { + return this->with(flecs::ScopeClose).entity(0); + } + /* Term notation for more complex query features */ Base& term() { @@ -27737,6 +28400,14 @@ inline int32_t world::get_threads() const { return ecs_get_stage_count(m_world); } +inline void world::set_task_threads(int32_t task_threads) const { + ecs_set_task_threads(m_world, task_threads); +} + +inline bool world::using_task_threads() const { + return ecs_using_task_threads(m_world); +} + } #endif @@ -28647,27 +29318,20 @@ inline flecs::metric_builder world::metric(Args &&... args) const { } template -inline untyped_component& untyped_component::metric(flecs::entity_t parent, const char *brief, const char *metric_name) { +inline untyped_component& untyped_component::metric( + flecs::entity_t parent, + const char *brief, + const char *metric_name) +{ flecs::world w(m_world); - flecs::entity e = w.entity(m_id); - const flecs::Struct *s = e.get(); - if (!s) { - flecs::log::err("can't register metric, component '%s' is not a struct", - e.path().c_str()); - return *this; - } - - ecs_member_t *m = ecs_vec_get_t(&s->members, ecs_member_t, - ecs_vec_count(&s->members) - 1); - ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); - - flecs::entity me = e.lookup(m->name); - if (!me) { - flecs::log::err("can't find member '%s' in component '%s' for metric", - m->name, e.path().c_str()); + flecs::entity e(m_world, m_id); + + const flecs::member_t *m = ecs_cpp_last_member(w, e); + if (!m) { return *this; } + flecs::entity me = w.entity(m->member); flecs::entity metric_entity = me; if (parent) { const char *component_name = e.name(); @@ -28692,6 +29356,256 @@ inline untyped_component& untyped_component::metric(flecs::entity_t parent, cons } +#endif +#ifdef FLECS_ALERTS +/** + * @file addons/cpp/mixins/alerts/impl.hpp + * @brief Alerts module implementation. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/alerts/builder.hpp + * @brief Alert builder. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/alerts/builder_i.hpp + * @brief Alert builder interface. + */ + +#pragma once + + +namespace flecs { + +/** Alert builder interface. + * + * \ingroup cpp_addons_alerts + */ +template +struct alert_builder_i : filter_builder_i { +private: + using BaseClass = filter_builder_i; + +public: + alert_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) { } + + alert_builder_i(ecs_alert_desc_t *desc, int32_t term_index = 0) + : BaseClass(&desc->filter, term_index) + , m_desc(desc) { } + + /** Alert message. + * + * @see ecs_alert_desc_t::message + */ + Base& message(const char *message) { + m_desc->message = message; + return *this; + } + + /** Set brief description for alert. + * + * @see ecs_alert_desc_t::brief + */ + Base& brief(const char *brief) { + m_desc->brief = brief; + return *this; + } + + /** Set doc name for alert. + * + * @see ecs_alert_desc_t::doc_name + */ + Base& doc_name(const char *doc_name) { + m_desc->doc_name = doc_name; + return *this; + } + + /** Set severity of alert (default is Error) + * + * @see ecs_alert_desc_t::severity + */ + Base& severity(flecs::entity_t kind) { + m_desc->severity = kind; + return *this; + } + + /* Set retain period of alert. + * + * @see ecs_alert_desc_t::retain_period + */ + Base& retain_period(ecs_ftime_t period) { + m_desc->retain_period = period; + return *this; + } + + /** Set severity of alert (default is Error) + * + * @see ecs_alert_desc_t::severity + */ + template + Base& severity() { + return severity(_::cpp_type::id(world_v())); + } + + /** Add severity filter */ + Base& severity_filter(flecs::entity_t kind, flecs::id_t with, const char *var = nullptr) { + ecs_assert(severity_filter_count < ECS_ALERT_MAX_SEVERITY_FILTERS, + ECS_INVALID_PARAMETER, "Maxium number of severity filters reached"); + + ecs_alert_severity_filter_t *filter = + &m_desc->severity_filters[severity_filter_count ++]; + + filter->severity = kind; + filter->with = with; + filter->var = var; + return *this; + } + + /** Add severity filter */ + template + Base& severity_filter(flecs::id_t with, const char *var = nullptr) { + return severity_filter(_::cpp_type::id(world_v()), with, var); + } + + /** Add severity filter */ + template ::value > = 0> + Base& severity_filter(const char *var = nullptr) { + return severity_filter(_::cpp_type::id(world_v()), + _::cpp_type::id(world_v()), var); + } + + /** Add severity filter */ + template ::value > = 0 > + Base& severity_filter(T with, const char *var = nullptr) { + flecs::world w(world_v()); + flecs::entity constant = w.to_entity(with); + return severity_filter(_::cpp_type::id(world_v()), + w.pair(constant), var); + } + + /** Set member to create an alert for out of range values */ + Base& member(flecs::entity_t m) { + m_desc->member = m; + return *this; + } + + /** Set (component) id for member (optional). If .member() is set and id + * is not set, the id will default to the member parent. */ + Base& id(flecs::id_t id) { + m_desc->id = id; + return *this; + } + + /** Set member to create an alert for out of range values */ + template + Base& member(const char *m, const char *v = nullptr) { + flecs::entity_t id = _::cpp_type::id(world_v()); + flecs::entity_t mid = ecs_lookup_path_w_sep( + world_v(), id, m, "::", "::", false); + ecs_assert(m != 0, ECS_INVALID_PARAMETER, NULL); + m_desc->var = v; + return this->member(mid); + } + + /** Set source variable for member (optional, defaults to $this) */ + Base& var(const char *v) { + m_desc->var = v; + return *this; + } + +protected: + virtual flecs::world_t* world_v() = 0; + +private: + operator Base&() { + return *static_cast(this); + } + + ecs_alert_desc_t *m_desc; + int32_t severity_filter_count = 0; +}; + +} + + +namespace flecs { +namespace _ { + template + using alert_builder_base = builder< + alert, ecs_alert_desc_t, alert_builder, + alert_builder_i, Components ...>; +} + +/** Alert builder. + * + * \ingroup cpp_addons_alerts + */ +template +struct alert_builder final : _::alert_builder_base { + alert_builder(flecs::world_t* world, const char *name = nullptr) + : _::alert_builder_base(world) + { + _::sig(world).populate(this); + if (name != nullptr) { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::", + entity_desc.root_sep = "::", + this->m_desc.entity = ecs_entity_init(world, &entity_desc); + } + } +}; + +} + + +namespace flecs { + +template +struct alert final : entity +{ + using entity::entity; + + explicit alert() { + m_id = 0; + m_world = nullptr; + } + + explicit alert(flecs::world_t *world, ecs_alert_desc_t *desc) + { + m_world = world; + m_id = ecs_alert_init(world, desc); + + if (desc->filter.terms_buffer) { + ecs_os_free(desc->filter.terms_buffer); + } + } +}; + +inline alerts::alerts(flecs::world& world) { + /* Import C module */ + FlecsAlertsImport(world); + + world.entity("::flecs::alerts::Alert"); + world.entity("::flecs::alerts::Info"); + world.entity("::flecs::alerts::Warning"); + world.entity("::flecs::alerts::Error"); +} + +template +inline flecs::alert_builder world::alert(Args &&... args) const { + return flecs::alert_builder(m_world, FLECS_FWD(args)...); +} + +} + #endif /** @@ -28793,10 +29707,6 @@ namespace flecs { inline void world::init_builtin_components() { - component("flecs::core::Component"); - component("flecs::core::Identifier"); - component("flecs::core::Poly"); - # ifdef FLECS_SYSTEM _::system_init(*this); # endif @@ -28979,6 +29889,11 @@ inline void world::remove(flecs::entity_t first, flecs::entity_t second) const { e.remove(first, second); } +template +inline void world::children(Func&& f) const { + this->entity(0).children(FLECS_FWD(f)); +} + template inline flecs::entity world::singleton() const { return flecs::entity(m_world, _::cpp_type::id(m_world));