diff --git a/README.md b/README.md index 9f6d61ef..f59ba437 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Flecs Explorer Web-based UI for monitoring Flecs applications, trying out queries & learning ECS -Screen Shot 2021-11-07 at 10 20 51 PM +Screen Shot 2022-06-01 at 3 42 48 PM A live version of the explorer is running @ https://flecs.dev/explorer @@ -72,6 +72,35 @@ docker run --name=explorer -p 80:80 --restart=unless-stopped -d sandermertens/fl You can now go to http://localhost to open the explorer. +### Statistics +The explorer can visualize statistics from Flecs applications. Statistics collection is disabled by default as it is not free. To enable it, import the `flecs.monitor` module: + +In C: +```c +ECS_IMPORT(world, FlecsMonitor); +``` + +In C++: +```c +world.import(); +``` + +Note that the statistics collection systems run as part of the default Flecs pipeline. If your application does not use systems, manually runs systems or uses a custom pipeline statistics collection will not run. To make sure the collection systems are ran, call this once in the main loop of your game: + +In C: +```c +ecs_progress(world, 0); +``` + +In C++ +```c +world.progress(); +``` + +You should now be able to see statistics in the explorer, which should look like this: + +Screen Shot 2022-06-04 at 1 14 49 AM + ### URL options The following options can be added to the URL: diff --git a/deps/flecs.c b/deps/flecs.c index 8f3777d6..ac297385 100644 --- a/deps/flecs.c +++ b/deps/flecs.c @@ -1,9 +1,19 @@ #include "flecs.h" +/** + * @file private_api.h + * @brief Private functions. + */ + #ifndef FLECS_PRIVATE_H #define FLECS_PRIVATE_H -#ifndef FLECS_TYPES_PRIVATE_H -#define FLECS_TYPES_PRIVATE_H +/** + * @file private_api.h + * @brief Private types. + */ + +#ifndef FLECS_PRIVATE_TYPES_H +#define FLECS_PRIVATE_TYPES_H #ifndef __MACH__ #ifndef _POSIX_C_SOURCE @@ -29,10 +39,6 @@ #ifndef FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_INDEX_H -#ifdef __cplusplus -extern "C" { -#endif - #define ecs_eis(world) (&((world)->store.entity_index)) #define ecs_eis_get(world, entity) flecs_sparse_get(ecs_eis(world), ecs_record_t, entity) #define ecs_eis_get_any(world, entity) flecs_sparse_get_any(ecs_eis(world), ecs_record_t, entity) @@ -52,10 +58,6 @@ extern "C" { #define ecs_eis_free(world) flecs_sparse_free(ecs_eis(world)) #define ecs_eis_memory(world, allocd, used) flecs_sparse_memory(ecs_eis(world), allocd, used) -#ifdef __cplusplus -} -#endif - #endif /** @@ -167,31 +169,42 @@ void flecs_bitset_swap( #define FLECS_SWITCH_LIST_H -typedef struct flecs_switch_header_t { +typedef struct ecs_switch_header_t { int32_t element; /* First element for value */ int32_t count; /* Number of elements for value */ -} flecs_switch_header_t; +} ecs_switch_header_t; -typedef struct flecs_switch_node_t { +typedef struct ecs_switch_node_t { int32_t next; /* Next node in list */ int32_t prev; /* Prev node in list */ -} flecs_switch_node_t; +} ecs_switch_node_t; struct ecs_switch_t { - uint64_t min; /* Minimum value the switch can store */ - uint64_t max; /* Maximum value the switch can store */ - flecs_switch_header_t *headers; /* Array with headers, indexed by value */ - ecs_vector_t *nodes; /* Vector with nodes, of type flecs_switch_node_t */ + // uint64_t min; /* Minimum value the switch can store */ + // uint64_t max; /* Maximum value the switch can store */ + // ecs_switch_header_t *headers; /* Array with headers, indexed by value */ + + ecs_map_t headers; + ecs_vector_t *nodes; /* Vector with nodes, of type ecs_switch_node_t */ ecs_vector_t *values; /* Vector with values, of type uint64_t */ }; +/** Init new switch. */ +FLECS_DBG_API +void flecs_switch_init( + ecs_switch_t* sw, + int32_t elements); + /** Create new switch. */ FLECS_DBG_API ecs_switch_t* flecs_switch_new( - uint64_t min, - uint64_t max, int32_t elements); +/** Fini switch. */ +FLECS_DBG_API +void flecs_switch_fini( + ecs_switch_t *sw); + /** Free switch. */ FLECS_DBG_API void flecs_switch_free( @@ -364,60 +377,26 @@ typedef struct ecs_table_event_t { /** A component column. */ struct ecs_column_t { - ecs_vector_t *data; /* Column data */ + void *array; + int32_t count; + int32_t size; }; -/** A switch column. */ -typedef struct ecs_sw_column_t { - ecs_switch_t *data; /* Column data */ - ecs_table_t *type; /* Table with switch type */ -} ecs_sw_column_t; - -/** A bitset column. */ -typedef struct ecs_bs_column_t { - ecs_bitset_t data; /* Column data */ -} ecs_bs_column_t; - /** Stage-specific component data */ struct ecs_data_t { - ecs_vector_t *entities; /* Entity identifiers */ - ecs_vector_t *record_ptrs; /* Ptrs to records in main entity index */ + ecs_column_t entities; /* Entity identifiers */ + ecs_column_t records; /* Ptrs to records in main entity index */ ecs_column_t *columns; /* Component columns */ - ecs_sw_column_t *sw_columns; /* Switch columns */ - ecs_bs_column_t *bs_columns; /* Bitset columns */ + ecs_switch_t *sw_columns; /* Switch columns */ + ecs_bitset_t *bs_columns; /* Bitset columns */ }; -/** Flags for quickly checking for special properties of a table. */ -#define EcsTableHasBuiltins 1u /* Does table have builtin components */ -#define EcsTableIsPrefab 2u /* Does the table store prefabs */ -#define EcsTableHasIsA 4u /* Does the table have IsA relation */ -#define EcsTableHasChildOf 8u /* Does the table type ChildOf relation */ -#define EcsTableHasPairs 16u /* Does the table type have pairs */ -#define EcsTableHasModule 32u /* Does the table have module data */ -#define EcsTableIsDisabled 128u /* Does the table type has EcsDisabled */ -#define EcsTableHasCtors 256u -#define EcsTableHasDtors 512u -#define EcsTableHasCopy 1024u -#define EcsTableHasMove 2048u -#define EcsTableHasOnAdd 4096u -#define EcsTableHasOnRemove 8192u -#define EcsTableHasOnSet 16384u -#define EcsTableHasUnSet 32768u -#define EcsTableHasSwitch 65536u -#define EcsTableHasDisabled 131072u - -/* Composite constants */ -#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors) -#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasSwitch | EcsTableHasDisabled) -#define EcsTableHasAddActions (EcsTableHasIsA | EcsTableHasSwitch | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet) -#define EcsTableHasRemoveActions (EcsTableHasIsA | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet) - /** Cache of added/removed components for non-trivial edges between tables */ typedef struct ecs_table_diff_t { - ecs_ids_t added; /* Components added between tables */ - ecs_ids_t removed; /* Components removed between tables */ - ecs_ids_t on_set; /* OnSet from exposing/adding base components */ - ecs_ids_t un_set; /* UnSet from hiding/removing base components */ + ecs_type_t added; /* Components added between tables */ + ecs_type_t removed; /* Components removed between tables */ + ecs_type_t on_set; /* OnSet from exposing/adding base components */ + ecs_type_t un_set; /* UnSet from hiding/removing base components */ } ecs_table_diff_t; /** Edge linked list (used to keep track of incoming edges) */ @@ -459,29 +438,28 @@ 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 (non-zero sized) components */ + 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 w/type without tags */ - ecs_id_t *storage_ids; /* Storage ids (prevent indirection) */ - int32_t *storage_map; /* Map type <-> storage type - * - 0..count(T): type -> storage_type - * - count(T)..count(S): storage_type -> type + 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_graph_node_t node; /* Graph node */ - ecs_data_t storage; /* Component storage */ - ecs_type_info_t *type_info; /* Cached type info */ + 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 */ - - int16_t sw_column_count; - int16_t sw_column_offset; - int16_t bs_column_count; - int16_t bs_column_offset; - int32_t alloc_count; /* Increases when columns are reallocd */ + int16_t sw_count; + int16_t sw_offset; + int16_t bs_count; + int16_t bs_offset; + int32_t refcount; /* Increased when used as storage table */ int16_t lock; /* Prevents modifications */ uint16_t record_count; /* Table record count including wildcards */ @@ -511,14 +489,14 @@ typedef struct ecs_table_cache_t { /* Sparse query column */ typedef struct flecs_switch_term_t { - ecs_sw_column_t *sw_column; + ecs_switch_t *sw_column; ecs_entity_t sw_case; int32_t signature_column_index; } flecs_switch_term_t; /* Bitset query column */ typedef struct flecs_bitset_term_t { - ecs_bs_column_t *bs_column; + ecs_bitset_t *bs_column; int32_t column_index; } flecs_bitset_term_t; @@ -574,12 +552,6 @@ typedef struct ecs_query_table_list_t { int32_t count; } ecs_query_table_list_t; -#define EcsQueryHasRefs (64) /* Does query have references */ -#define EcsQueryIsSubquery (256) /* Is query a subquery */ -#define EcsQueryIsOrphaned (512) /* Is subquery orphaned */ -#define EcsQueryHasOutColumns (1024) /* Does query have out columns */ -#define EcsQueryHasMonitor (4096) /* Does query track changes */ - /* Query event type for notifying queries of world events */ typedef enum ecs_query_eventkind_t { EcsQueryTableMatch, @@ -639,7 +611,6 @@ struct ecs_query_t { 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 */ - bool constraints_satisfied; /* Are all term constraints satisfied */ /* Mixins */ ecs_world_t *world; @@ -717,6 +688,7 @@ struct ecs_stage_t { /* Are operations deferred? */ int32_t defer; ecs_vector_t *defer_queue; + bool defer_suspend; ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ ecs_world_t *world; /* Reference to world */ @@ -748,30 +720,6 @@ typedef struct ecs_monitor_set_t { bool is_dirty; /* Should monitors be evaluated? */ } ecs_monitor_set_t; -/* Payload for table index which returns all tables for a given component, with - * the column of the component in the table. */ -typedef struct ecs_table_record_t { - ecs_table_cache_hdr_t hdr; - ecs_id_t id; - int32_t column; - int32_t count; -} ecs_table_record_t; - -/* Payload for id index which contains all datastructures for an id. */ -struct ecs_id_record_t { - /* Cache with all tables that contain the id. Must be first member. */ - ecs_table_cache_t cache; /* table_cache */ - - /* Flags for id */ - ecs_flags32_t flags; - - /* Name lookup index (currently only used for ChildOf pairs) */ - ecs_hashmap_t *name_index; - - /* Cached pointer to type info for id */ - const ecs_type_info_t *type_info; -}; - typedef struct ecs_store_t { /* Entity lookup */ ecs_sparse_t entity_index; /* sparse */ @@ -780,23 +728,17 @@ typedef struct ecs_store_t { 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; /* Table edge cache */ ecs_graph_edge_hdr_t *first_free; -} ecs_store_t; -/** Supporting type to store looked up or derived entity data */ -typedef struct ecs_entity_info_t { - ecs_record_t *record; /* Main stage record in entity index */ - ecs_table_t *table; /* Table. Not set if entity is empty */ - ecs_data_t *data; /* Stage-specific table columns */ - int32_t row; /* Row in table */ - uint32_t row_flags; /* Row flags (used to track observables) */ -} ecs_entity_info_t; + /* Records cache */ + ecs_vector_t *records; +} ecs_store_t; /** Supporting type to store looked up component data in specific table */ typedef struct ecs_column_info_t { @@ -820,8 +762,12 @@ struct ecs_world_t { ecs_map_t id_index; /* map */ ecs_sparse_t *type_info; /* sparse */ - /* Cached handle to (IsA, *) */ + /* -- Cached handle to id records -- */ + ecs_id_record_t *idr_wildcard; + ecs_id_record_t *idr_wildcard_wildcard; + ecs_id_record_t *idr_any; ecs_id_record_t *idr_isa_wildcard; + ecs_id_record_t *idr_childof_0; /* -- Mixins -- */ ecs_world_t *self; @@ -945,10 +891,6 @@ struct ecs_world_t { #ifndef FLECS_TABLE_CACHE_H_ #define FLECS_TABLE_CACHE_H_ -#ifdef __cplusplus -extern "C" { -#endif - void ecs_table_cache_init( ecs_table_cache_t *cache); @@ -960,6 +902,11 @@ void ecs_table_cache_insert( const ecs_table_t *table, ecs_table_cache_hdr_t *result); +void ecs_table_cache_replace( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem); + void* ecs_table_cache_remove( ecs_table_cache_t *cache, const ecs_table_t *table, @@ -984,477 +931,176 @@ void ecs_table_cache_fini_delete_all( ecs_world_t *world, ecs_table_cache_t *cache); -#ifdef __cplusplus -} -#endif - -#endif - -/* 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) +bool flecs_table_cache_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out); -/* 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) +bool flecs_table_cache_empty_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out); -void ecs_qsort( - void *base, - ecs_size_t nitems, - ecs_size_t size, - int (*compar)(const void *, const void*)); +ecs_table_cache_hdr_t* _flecs_table_cache_next( + ecs_table_cache_iter_t *it); -#define ecs_qsort_t(base, nitems, T, compar) \ - ecs_qsort(base, nitems, ECS_SIZEOF(T), compar) +#define flecs_table_cache_next(it, T)\ + (ECS_CAST(T*, _flecs_table_cache_next(it))) #endif /** - * @file name_index.h - * @brief Data structure used for id <-> name lookups. + * @file id_record.h + * @brief Index for looking up tables by (component) id. */ -#ifndef FLECS_NAME_INDEX_H -#define FLECS_NAME_INDEX_H - -void flecs_name_index_init( - ecs_hashmap_t *hm); - -ecs_hashmap_t* flecs_name_index_new(void); - -void flecs_name_index_fini( - ecs_hashmap_t *map); - -void flecs_name_index_free( - ecs_hashmap_t *map); - -ecs_hashed_string_t flecs_get_hashed_string( - const char *name, - ecs_size_t length, - uint64_t hash); - -const uint64_t* flecs_name_index_find_ptr( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash); - -uint64_t flecs_name_index_find( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash); - -void flecs_name_index_ensure( - ecs_hashmap_t *map, - uint64_t id, - const char *name, - ecs_size_t length, - uint64_t hash); - -void flecs_name_index_remove( - ecs_hashmap_t *map, - uint64_t id, - uint64_t hash); - -void flecs_name_index_update_name( - ecs_hashmap_t *map, - uint64_t e, - uint64_t hash, - const char *name); - -#endif +#ifndef FLECS_ID_RECORD_H +#define FLECS_ID_RECORD_H +/* Payload for id cache */ +typedef struct ecs_table_record_t { + ecs_table_cache_hdr_t hdr; /* Table cache header */ + int32_t column; /* First column where id occurs in table */ + int32_t count; /* Number of times id occurs in table */ +} ecs_table_record_t; -//////////////////////////////////////////////////////////////////////////////// -//// Core bootstrap functions -//////////////////////////////////////////////////////////////////////////////// +/* Linked list of id records */ +typedef struct ecs_id_record_elem_t { + struct ecs_id_record_t *prev, *next; +} ecs_id_record_elem_t; -/* Bootstrap world */ -void flecs_bootstrap( - ecs_world_t *world); - -#define flecs_bootstrap_component(world, id)\ - ecs_component_init(world, &(ecs_component_desc_t){\ - .entity = {\ - .entity = ecs_id(id),\ - .name = #id,\ - .symbol = #id\ - },\ - .size = sizeof(id),\ - .alignment = ECS_ALIGNOF(id)\ - }); +/* Payload for id index which contains all datastructures for an id. */ +struct ecs_id_record_t { + /* Cache with all tables that contain the id. Must be first member. */ + ecs_table_cache_t cache; /* table_cache */ -#define flecs_bootstrap_tag(world, name)\ - ecs_add_id(world, name, EcsFinal);\ - ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ - ecs_set(world, name, EcsComponent, {.size = 0});\ - ecs_set_name(world, name, (char*)&#name[ecs_os_strlen(world->name_prefix)]);\ - ecs_set_symbol(world, name, #name) + /* Flags for id */ + ecs_flags32_t flags; + /* Name lookup index (currently only used for ChildOf pairs) */ + ecs_hashmap_t *name_index; -/* Bootstrap functions for other parts in the code */ -void flecs_bootstrap_hierarchy(ecs_world_t *world); + /* Cached pointer to type info for id, if id contains data. */ + const ecs_type_info_t *type_info; -//////////////////////////////////////////////////////////////////////////////// -//// Entity API -//////////////////////////////////////////////////////////////////////////////// + /* Id of record */ + ecs_id_t id; -/* Mark an entity as being watched. This is used to trigger automatic rematching - * when entities used in system expressions change their components. */ -void flecs_add_flag( - ecs_world_t *world, - ecs_entity_t entity, - uint32_t flag); + /* Parent id record. For pair records the parent is the (R, *) record. */ + ecs_id_record_t *parent; + + /* 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 acyclic; /* (*, O) with only acyclic relations */ +}; -/* Obtain entity info */ -bool flecs_get_info( +/* Get id record for id */ +ecs_id_record_t* flecs_get_id_record( const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_info_t *info); - -void flecs_register_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name); - -void flecs_unregister_name( - ecs_world_t *world, - ecs_entity_t entity); - - -//////////////////////////////////////////////////////////////////////////////// -//// World API -//////////////////////////////////////////////////////////////////////////////// - -/* Get current stage */ -ecs_stage_t* flecs_stage_from_world( - ecs_world_t **world_ptr); - -/* Get current thread-specific stage from readonly world */ -const ecs_stage_t* flecs_stage_from_readonly_world( - const ecs_world_t *world); + ecs_id_t id); -/* Get component callbacks */ -const ecs_type_info_t *flecs_get_type_info( +/* Get id record for id for searching. + * Same as flecs_get_id_record, but replaces (R, *) with (Union, R) if R is a + * union relationship. */ +ecs_id_record_t* flecs_get_query_id_record( const ecs_world_t *world, - ecs_entity_t component); - -/* Get or create component callbacks */ -ecs_type_info_t* flecs_ensure_type_info( - ecs_world_t *world, - ecs_entity_t component); - -void flecs_init_type_info( - ecs_world_t *world, - ecs_entity_t component, - ecs_size_t size, - ecs_size_t alignment); - -#define flecs_init_type_info_t(world, T)\ - flecs_init_type_info(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T)) - -void flecs_eval_component_monitors( - ecs_world_t *world); - -void flecs_monitor_mark_dirty( - ecs_world_t *world, - ecs_entity_t id); - -void flecs_monitor_register( - ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query); + ecs_id_t id); -void flecs_monitor_unregister( +/* Ensure id record for id */ +ecs_id_record_t* flecs_ensure_id_record( ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query); + ecs_id_t id); -void flecs_notify_tables( +/* Clear all tables in id record. */ +void flecs_clear_id_record( ecs_world_t *world, ecs_id_t id, - ecs_table_event_t *event); - -void flecs_notify_queries( - ecs_world_t *world, - ecs_query_event_t *event); - -void flecs_register_table( - ecs_world_t *world, - ecs_table_t *table); - -void flecs_unregister_table( - ecs_world_t *world, - ecs_table_t *table); - -void flecs_table_set_empty( - ecs_world_t *world, - ecs_table_t *table); + ecs_id_record_t *idr); -ecs_id_record_t* flecs_ensure_id_record( +/* Remove id record if empty. If id record only contains empty tables, they will + * be cleaned up. Id record removal may be deleted if one or more empty tables + * are referenced as storage table by other tables. */ +void flecs_remove_id_record( ecs_world_t *world, - ecs_id_t id); + ecs_id_t id, + ecs_id_record_t *idr); +/* Register table for id record */ void flecs_register_for_id_record( ecs_world_t *world, ecs_id_t id, const ecs_table_t *table, ecs_table_record_t *tr); -ecs_id_record_t* flecs_get_id_record( - const ecs_world_t *world, - ecs_id_t id); +/* Set (component) type info for id record */ +bool flecs_set_type_info_for_id_record( + ecs_world_t *world, + ecs_id_record_t *idr, + const ecs_type_info_t *ti); +/* Ensure id record has name index */ ecs_hashmap_t* flecs_ensure_id_name_index( ecs_world_t *world, ecs_id_t id); +/* Get name index for id record */ ecs_hashmap_t* flecs_get_id_name_index( const ecs_world_t *world, ecs_id_t id); +/* Find table record for id */ ecs_table_record_t* flecs_get_table_record( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id); +/* Find table record for id record */ const ecs_table_record_t* flecs_id_record_table( ecs_id_record_t *idr, ecs_table_t *table); -void flecs_process_pending_tables( - const ecs_world_t *world); - +/* Return table iterator for id */ ecs_id_record_t* flecs_table_iter( ecs_world_t *world, ecs_id_t id, ecs_table_cache_iter_t *out); +/* Return empty table iterator for id */ ecs_id_record_t* flecs_empty_table_iter( ecs_world_t *world, ecs_id_t id, ecs_table_cache_iter_t *out); -bool flecs_table_cache_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out); - -bool flecs_table_cache_empty_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out); +/* Cleanup all id records in world */ +void flecs_fini_id_records( + ecs_world_t *world); -ecs_table_cache_hdr_t* _flecs_table_cache_next( - ecs_table_cache_iter_t *it); +#endif -#define flecs_table_cache_next(it, T)\ - (ECS_CAST(T*, _flecs_table_cache_next(it))) +/** + * @file observable.h + * @brief Functions for sending events. + */ -void flecs_clear_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr); +#ifndef FLECS_OBSERVABLE_H +#define FLECS_OBSERVABLE_H -void flecs_remove_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr); +void flecs_observable_init( + ecs_observable_t *observable); -void flecs_name_index_erase( - ecs_hashmap_t *map, - ecs_entity_t entity, - uint64_t hash); +void flecs_observable_fini( + ecs_observable_t *observable); void flecs_triggers_notify( ecs_iter_t *it, ecs_observable_t *observable, - ecs_ids_t *ids, + const ecs_type_t *ids, ecs_entity_t event); void flecs_set_triggers_notify( ecs_iter_t *it, ecs_observable_t *observable, - ecs_ids_t *ids, + const ecs_type_t *ids, ecs_entity_t event, ecs_id_t set_id); @@ -1471,192 +1117,179 @@ void flecs_observer_fini( ecs_world_t *world, ecs_observer_t *observer); -/* Suspend/resume readonly state. To fully support implicit registration of - * components, it should be possible to register components while the world is - * in readonly mode. It is not uncommon that a component is used first from - * within a system, which are often ran while in readonly mode. - * - * Suspending readonly mode is only allowed when the world is not multithreaded. - * When a world is multithreaded, it is not safe to (even temporarily) leave - * readonly mode, so a multithreaded application should always explicitly - * register components in advance. - * - * These operations also suspend deferred mode. - */ -typedef struct { - bool is_readonly; - bool is_deferred; - int32_t defer_count; - ecs_entity_t scope; - ecs_entity_t with; - ecs_vector_t *defer_queue; - ecs_stage_t *stage; -} ecs_suspend_readonly_state_t; - -ecs_world_t* flecs_suspend_readonly( - const ecs_world_t *world, - ecs_suspend_readonly_state_t *state); - -void flecs_resume_readonly( - ecs_world_t *world, - ecs_suspend_readonly_state_t *state); - void flecs_emit( ecs_world_t *world, ecs_world_t *stage, ecs_event_desc_t *desc); -ecs_entity_t flecs_get_oneof( - const ecs_world_t *world, - ecs_entity_t e); +#endif -//////////////////////////////////////////////////////////////////////////////// -//// Stage API -//////////////////////////////////////////////////////////////////////////////// +/** + * @file storage.h + * @brief Storage API + */ -/* Initialize stage data structures */ -void flecs_stage_init( - ecs_world_t *world, - ecs_stage_t *stage); +#ifndef FLECS_STORAGE_H +#define FLECS_STORAGE_H -/* Deinitialize stage */ -void flecs_stage_deinit( - ecs_world_t *world, - ecs_stage_t *stage); +void ecs_storage_init( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count); -/* Post-frame merge actions */ -void flecs_stage_merge_post_frame( - ecs_world_t *world, - ecs_stage_t *stage); +#define ecs_storage_init_t(storage, T, elem_count) \ + ecs_storage_init(storage, ECS_SIZEOF(T), elem_count) -/* Delete table from stage */ -void flecs_delete_table( - ecs_world_t *world, - ecs_table_t *table); +void ecs_storage_fini( + ecs_column_t *storage); +int32_t ecs_storage_count( + ecs_column_t *storage); -//////////////////////////////////////////////////////////////////////////////// -//// Defer API -//////////////////////////////////////////////////////////////////////////////// +int32_t ecs_storage_size( + ecs_column_t *storage); -bool flecs_defer_none( - ecs_world_t *world, - ecs_stage_t *stage); +void* ecs_storage_get( + ecs_column_t *storage, + ecs_size_t size, + int32_t index); -bool flecs_defer_modified( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t component); +#define ecs_storage_get_t(storage, T, index) \ + ECS_CAST(T*, ecs_storage_get(storage, ECS_SIZEOF(T), index)) -bool flecs_defer_new( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id); +void* ecs_storage_first( + ecs_column_t *storage); -bool flecs_defer_clone( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t src, - bool clone_value); +#define ecs_storage_first_t(storage, T) \ + ECS_CAST(T*, ecs_storage_first(storage)) -bool flecs_defer_bulk_new( - ecs_world_t *world, - ecs_stage_t *stage, - int32_t count, - ecs_id_t id, - const ecs_entity_t **ids_out); +void* ecs_storage_last( + ecs_column_t *storage, + ecs_size_t size); -bool flecs_defer_delete( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity); +#define ecs_storage_last_t(storage, T) \ + ECS_CAST(T*, ecs_storage_last(storage, ECS_SIZEOF(T))) -bool flecs_defer_clear( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity); +void* ecs_storage_append( + ecs_column_t *storage, + ecs_size_t size); -bool flecs_defer_on_delete_action( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_id_t id, - ecs_entity_t action); +#define ecs_storage_append_t(storage, T) \ + ECS_CAST(T*, ecs_storage_append(storage, ECS_SIZEOF(T))) -bool flecs_defer_enable( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t component, - bool enable); +void ecs_storage_remove( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem); -bool flecs_defer_add( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id); +#define ecs_storage_remove_t(storage, T) \ + ECS_CAST(T*, ecs_storage_remove(storage, ECS_SIZEOF(T))) -bool flecs_defer_remove( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id); +void ecs_storage_remove_last( + ecs_column_t *storage); -bool flecs_defer_set( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_defer_op_kind_t op_kind, - ecs_entity_t entity, - ecs_entity_t component, +ecs_column_t ecs_storage_copy( + ecs_column_t *storage, + ecs_size_t size); + +#define ecs_storage_copy_t(storage, T) \ + ecs_storage_copy(storage, ECS_SIZEOF(T)) + +void ecs_storage_reclaim( + ecs_column_t *storage, + ecs_size_t size); + +#define ecs_storage_reclaim_t(storage, T) \ + ecs_storage_reclaim(storage, ECS_SIZEOF(T)) + +void ecs_storage_set_size( + ecs_column_t *storage, ecs_size_t size, - const void *value, - void **value_out, - bool *is_added); + int32_t elem_count); -bool flecs_defer_flush( - ecs_world_t *world, - ecs_stage_t *stage); +#define ecs_storage_set_size_t(storage, T, elem_count) \ + ecs_storage_set_size(storage, ECS_SIZEOF(T), elem_count) -bool flecs_defer_purge( - ecs_world_t *world, - ecs_stage_t *stage); +void ecs_storage_set_count( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count); +#define ecs_storage_set_count_t(storage, T, elem_count) \ + ecs_storage_set_count(storage, ECS_SIZEOF(T), elem_count) -//////////////////////////////////////////////////////////////////////////////// -//// Notifications -//////////////////////////////////////////////////////////////////////////////// +void* ecs_storage_grow( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count); -void flecs_notify_on_remove( +#define ecs_storage_grow_t(storage, T, elem_count) \ + ecs_storage_grow(storage, ECS_SIZEOF(T), elem_count) + +#endif + +/** + * @file iter.h + * @brief Iterator utilities. + */ + +#ifndef FLECS_ITER_H +#define FLECS_ITER_H + +void flecs_iter_init( + ecs_iter_t *it, + ecs_flags8_t fields); + +void flecs_iter_validate( + ecs_iter_t *it); + +void flecs_iter_populate_data( ecs_world_t *world, + ecs_iter_t *it, ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, + int32_t offset, int32_t count, - ecs_table_diff_t *diff); + void **ptrs, + ecs_size_t *sizes); -void flecs_notify_on_set( +bool flecs_iter_next_row( + ecs_iter_t *it); + +bool flecs_iter_next_instanced( + ecs_iter_t *it, + bool result); + +#endif + +/** + * @file table.h + * @brief Table functions. + */ + +#ifndef FLECS_TABLE_H +#define FLECS_TABLE_H + +/* Init table */ +void flecs_table_init( ecs_world_t *world, ecs_table_t *table, - int32_t row, - int32_t count, - ecs_ids_t *ids, - bool owned); + ecs_table_t *from); +/** Copy type. */ +ecs_type_t flecs_type_copy( + const ecs_type_t *src); -//////////////////////////////////////////////////////////////////////////////// -//// Table API -//////////////////////////////////////////////////////////////////////////////// +/** Free type. */ +void flecs_type_free( + ecs_type_t *type); /** Find or create table for a set of components */ ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, - const ecs_ids_t *type); + ecs_type_t *type); /* Initialize columns for data */ void flecs_table_init_data( - ecs_world_t *world, ecs_table_t *table); /* Clear all entities from a table. */ @@ -1688,7 +1321,6 @@ int32_t flecs_table_data_count( int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, ecs_entity_t entity, ecs_record_t *record, bool construct); @@ -1697,7 +1329,6 @@ int32_t flecs_table_append( void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, int32_t index, bool destruct); @@ -1726,10 +1357,8 @@ void flecs_table_move( ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *new_table, - ecs_data_t *new_data, int32_t new_index, ecs_table_t *old_table, - ecs_data_t *old_data, int32_t old_index, bool construct); @@ -1818,15 +1447,6 @@ void flecs_table_mark_dirty( ecs_table_t *table, ecs_entity_t component); -const EcsComponent* flecs_component_from_id( - const ecs_world_t *world, - ecs_entity_t e); - -int32_t flecs_table_switch_from_case( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_entity_t add); - void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, @@ -1845,70 +1465,19 @@ ecs_column_t *ecs_table_column_for_id( const ecs_table_t *table, ecs_id_t id); - -//////////////////////////////////////////////////////////////////////////////// -//// Query API -//////////////////////////////////////////////////////////////////////////////// - -/* Match table with term */ -bool flecs_term_match_table( - ecs_world_t *world, - const ecs_term_t *term, +int32_t flecs_table_column_to_union_index( const ecs_table_t *table, - ecs_type_t type, - ecs_id_t *id_out, - int32_t *column_out, - ecs_entity_t *subject_out, - int32_t *match_indices, - bool first, - ecs_flags32_t iter_flags); - -/* Match table with filter */ -bool flecs_filter_match_table( - ecs_world_t *world, - const ecs_filter_t *filter, - const ecs_table_t *table, - ecs_id_t *ids, - int32_t *columns, - ecs_entity_t *subjects, - int32_t *match_indices, - int32_t *matches_left, - bool first, - int32_t skip_term, - ecs_flags32_t iter_flags); - -void flecs_query_notify( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event); - -void flecs_iter_init( - ecs_iter_t *it, - ecs_flags8_t fields); - -void flecs_iter_validate( - ecs_iter_t *it); - -void flecs_iter_populate_data( - ecs_world_t *world, - ecs_iter_t *it, - ecs_table_t *table, - int32_t offset, - int32_t count, - void **ptrs, - ecs_size_t *sizes); - -bool flecs_iter_next_row( - ecs_iter_t *it); + int32_t column); -bool flecs_iter_next_instanced( - ecs_iter_t *it, - bool result); +#endif +/** + * @file poly.h + * @brief Functions for managing poly objects. + */ -//////////////////////////////////////////////////////////////////////////////// -//// Poly API -//////////////////////////////////////////////////////////////////////////////// +#ifndef FLECS_POLY_H +#define FLECS_POLY_H /* Initialize object header & mixins for specified type */ void* _ecs_poly_init( @@ -1964,18 +1533,598 @@ ecs_iterable_t* ecs_get_iterable( ecs_observable_t* ecs_get_observable( const ecs_poly_t *object); +#endif + +/** + * @file stage.h + * @brief Stage functions. + */ + +#ifndef FLECS_STAGE_H +#define FLECS_STAGE_H + +/* Initialize stage data structures */ +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Deinitialize stage */ +void flecs_stage_deinit( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Post-frame merge actions */ +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_none( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component); + +bool flecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id); + +bool flecs_defer_clone( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value); + +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + ecs_id_t id, + const ecs_entity_t **ids_out); + +bool flecs_defer_delete( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity); + +bool flecs_defer_clear( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity); + +bool flecs_defer_on_delete_action( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_id_t id, + ecs_entity_t action); + +bool flecs_defer_enable( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable); + +bool flecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id); + +bool flecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id); + +bool flecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_defer_op_kind_t op_kind, + ecs_entity_t entity, + ecs_entity_t component, + ecs_size_t size, + const void *value, + void **value_out, + bool *is_added); + +bool flecs_defer_flush( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage); + +#endif + +/** + * @file world.h + * @brief World utilities. + */ + +#ifndef FLECS_WORLD_H +#define FLECS_WORLD_H + +/* Get current stage */ +ecs_stage_t* flecs_stage_from_world( + ecs_world_t **world_ptr); + +/* Get current thread-specific stage from readonly world */ +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world); + +/* Get component callbacks */ +const ecs_type_info_t *flecs_get_type_info( + const ecs_world_t *world, + ecs_entity_t component); + +/* Get or create component callbacks */ +ecs_type_info_t* flecs_ensure_type_info( + ecs_world_t *world, + ecs_entity_t component); + +bool flecs_init_type_info_id( + ecs_world_t *world, + ecs_entity_t component, + ecs_size_t size, + ecs_size_t alignment, + const EcsComponentLifecycle *li); + +#define flecs_init_type_info(world, T, ...)\ + flecs_init_type_info_id(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T),\ + &(EcsComponentLifecycle)__VA_ARGS__) + +void flecs_eval_component_monitors( + ecs_world_t *world); + +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t id); + +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query); + +void flecs_monitor_unregister( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query); + +void flecs_notify_tables( + ecs_world_t *world, + ecs_id_t id, + ecs_table_event_t *event); + +void flecs_notify_queries( + ecs_world_t *world, + ecs_query_event_t *event); + +void flecs_register_table( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_unregister_table( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_table_set_empty( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_process_pending_tables( + const ecs_world_t *world); + +/* Suspend/resume readonly state. To fully support implicit registration of + * components, it should be possible to register components while the world is + * in readonly mode. It is not uncommon that a component is used first from + * within a system, which are often ran while in readonly mode. + * + * Suspending readonly mode is only allowed when the world is not multithreaded. + * When a world is multithreaded, it is not safe to (even temporarily) leave + * readonly mode, so a multithreaded application should always explicitly + * register components in advance. + * + * These operations also suspend deferred mode. + */ +typedef struct { + bool is_readonly; + bool is_deferred; + int32_t defer_count; + ecs_entity_t scope; + ecs_entity_t with; + ecs_vector_t *defer_queue; + ecs_stage_t *stage; +} ecs_suspend_readonly_state_t; + +ecs_world_t* flecs_suspend_readonly( + const ecs_world_t *world, + ecs_suspend_readonly_state_t *state); + +void flecs_resume_readonly( + ecs_world_t *world, + ecs_suspend_readonly_state_t *state); + +#endif + +/* 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 name_index.h + * @brief Data structure used for id <-> name lookups. + */ + +#ifndef FLECS_NAME_INDEX_H +#define FLECS_NAME_INDEX_H + +void flecs_name_index_init( + ecs_hashmap_t *hm); + +ecs_hashmap_t* flecs_name_index_new(void); + +void flecs_name_index_fini( + ecs_hashmap_t *map); + +void flecs_name_index_free( + ecs_hashmap_t *map); + +ecs_hashed_string_t flecs_get_hashed_string( + const char *name, + ecs_size_t length, + uint64_t hash); + +const uint64_t* flecs_name_index_find_ptr( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash); + +uint64_t flecs_name_index_find( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash); + +void flecs_name_index_ensure( + ecs_hashmap_t *map, + uint64_t id, + const char *name, + ecs_size_t length, + uint64_t hash); + +void flecs_name_index_remove( + ecs_hashmap_t *map, + uint64_t id, + uint64_t hash); + +void flecs_name_index_update_name( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash, + const char *name); + +#endif + + //////////////////////////////////////////////////////////////////////////////// -//// Observables +//// Bootstrap API //////////////////////////////////////////////////////////////////////////////// -void flecs_observable_init( - ecs_observable_t *observable); +/* Bootstrap world */ +void flecs_bootstrap( + ecs_world_t *world); + +#define flecs_bootstrap_component(world, id)\ + ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .entity = ecs_id(id),\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + }); + +#define flecs_bootstrap_tag(world, name)\ + ecs_add_id(world, name, EcsFinal);\ + ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ + ecs_set(world, name, EcsComponent, {.size = 0});\ + ecs_set_name(world, name, (char*)&#name[ecs_os_strlen(world->name_prefix)]);\ + ecs_set_symbol(world, name, #name) -void flecs_observable_fini( - ecs_observable_t *observable); + +/* Bootstrap functions for other parts in the code */ +void flecs_bootstrap_hierarchy(ecs_world_t *world); +//////////////////////////////////////////////////////////////////////////////// +//// Entity API +//////////////////////////////////////////////////////////////////////////////// + +/* Mark an entity as being watched. This is used to trigger automatic rematching + * when entities used in system expressions change their components. */ +void flecs_add_flag( + ecs_world_t *world, + ecs_entity_t entity, + uint32_t flag); + +ecs_entity_t flecs_get_oneof( + const ecs_world_t *world, + ecs_entity_t e); + +void flecs_notify_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + ecs_table_diff_t *diff); + +void flecs_notify_on_set( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *type, + bool owned); + + +//////////////////////////////////////////////////////////////////////////////// +//// Query API +//////////////////////////////////////////////////////////////////////////////// + +/* Match table with term */ +bool flecs_term_match_table( + ecs_world_t *world, + const ecs_term_t *term, + const ecs_table_t *table, + ecs_id_t *id_out, + int32_t *column_out, + ecs_entity_t *subject_out, + int32_t *match_indices, + bool first, + ecs_flags32_t iter_flags); + +/* Match table with filter */ +bool flecs_filter_match_table( + ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_id_t *ids, + int32_t *columns, + ecs_entity_t *subjects, + int32_t *match_indices, + int32_t *matches_left, + bool first, + int32_t skip_term, + ecs_flags32_t iter_flags); + +ecs_iter_t flecs_filter_iter_w_flags( + const ecs_world_t *stage, + const ecs_filter_t *filter, + ecs_flags32_t flags); + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event); + +ecs_id_t flecs_to_public_id( + ecs_id_t id); + +ecs_id_t flecs_from_public_id( + ecs_world_t *world, + ecs_id_t id); + //////////////////////////////////////////////////////////////////////////////// //// Safe(r) integer casting //////////////////////////////////////////////////////////////////////////////// @@ -2034,6 +2183,7 @@ uint64_t _flecs_ito( #define flecs_itoi16(value) flecs_ito(int16_t, (value)) #define flecs_itoi32(value) flecs_ito(int32_t, (value)) + //////////////////////////////////////////////////////////////////////////////// //// Utilities //////////////////////////////////////////////////////////////////////////////// @@ -2065,10 +2215,6 @@ uint32_t flecs_row_to_record( uint32_t row, bool is_watched); -/* Convert type to entity array */ -ecs_ids_t flecs_type_to_ids( - ecs_type_t type); - /* Convert a symbol name to an entity name by removing the prefix */ const char* flecs_name_from_symbol( ecs_world_t *world, @@ -2124,31 +2270,31 @@ void _assert_func( #ifdef FLECS_SANITIZE static void check_table_sanity(ecs_table_t *table) { - int32_t size = ecs_vector_size(table->storage.entities); - int32_t count = ecs_vector_count(table->storage.entities); + int32_t size = ecs_storage_size(&table->data.entities); + int32_t count = ecs_storage_count(&table->data.entities); - ecs_assert(size == ecs_vector_size(table->storage.record_ptrs), + ecs_assert(size == ecs_storage_size(&table->data.records), ECS_INTERNAL_ERROR, NULL); - ecs_assert(count == ecs_vector_count(table->storage.record_ptrs), + ecs_assert(count == ecs_storage_count(&table->data.records), ECS_INTERNAL_ERROR, NULL); int32_t i; - int32_t sw_offset = table->sw_column_offset; - int32_t sw_count = table->sw_column_count; - int32_t bs_offset = table->bs_column_offset; - int32_t bs_count = table->bs_column_count; - int32_t type_count = ecs_vector_count(table->type); - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + int32_t sw_offset = table->sw_offset; + int32_t sw_count = table->sw_count; + int32_t bs_offset = table->bs_offset; + int32_t bs_count = table->bs_count; + int32_t type_count = table->type.count; + ecs_id_t *ids = table->type.array; ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); ecs_table_t *storage_table = table->storage_table; if (storage_table) { - ecs_assert(table->storage_count == ecs_vector_count(storage_table->type), + ecs_assert(table->storage_count == storage_table->type.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->storage_ids == storage_table->type.array, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_ids == ecs_vector_first( - storage_table->type, ecs_id_t), ECS_INTERNAL_ERROR, NULL); int32_t storage_count = table->storage_count; ecs_assert(type_count >= storage_count, ECS_INTERNAL_ERROR, NULL); @@ -2164,17 +2310,13 @@ void check_table_sanity(ecs_table_t *table) { } } - ecs_assert(table->storage.columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->type_info != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < storage_count; i ++) { - ecs_type_info_t *ti = &table->type_info[i]; - ecs_column_t *column = &table->storage.columns[i]; - - ecs_vector_assert_size(column->data, ti->size); - ecs_assert(size == ecs_vector_size(column->data), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(count == ecs_vector_count(column->data), - ECS_INTERNAL_ERROR, NULL); + ecs_column_t *column = &table->data.columns[i]; + ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); int32_t storage_map_id = storage_map[i + type_count]; ecs_assert(storage_map_id >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ids[storage_map_id] == storage_ids[i], @@ -2186,24 +2328,23 @@ void check_table_sanity(ecs_table_t *table) { } if (sw_count) { - ecs_assert(table->storage.sw_columns != NULL, + ecs_assert(table->data.sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < sw_count; i ++) { - ecs_sw_column_t *sw = &table->storage.sw_columns[i]; - ecs_assert(sw->data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_count(sw->data->values) == count, + ecs_switch_t *sw = &table->data.sw_columns[i]; + ecs_assert(ecs_vector_count(sw->values) == count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion, ECS_INTERNAL_ERROR, NULL); - ecs_assert((ids[i + sw_offset] & ECS_ROLE_MASK) == - ECS_SWITCH, ECS_INTERNAL_ERROR, NULL); } } if (bs_count) { - ecs_assert(table->storage.bs_columns != NULL, + ecs_assert(table->data.bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < bs_count; i ++) { - ecs_bs_column_t *bs = &table->storage.bs_columns[i]; - ecs_assert(flecs_bitset_count(&bs->data) == count, + ecs_bitset_t *bs = &table->data.bs_columns[i]; + ecs_assert(flecs_bitset_count(bs) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert((ids[i + bs_offset] & ECS_ROLE_MASK) == ECS_DISABLED, ECS_INTERNAL_ERROR, NULL); @@ -2215,7 +2356,7 @@ void check_table_sanity(ecs_table_t *table) { #endif static -void init_storage_map( +void flecs_table_init_storage_map( ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); @@ -2224,8 +2365,8 @@ void init_storage_map( } ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t t, ids_count = ecs_vector_count(type); + ecs_id_t *ids = type.array; + int32_t t, ids_count = type.count; ecs_id_t *storage_ids = table->storage_ids; int32_t s, storage_ids_count = table->storage_count; @@ -2269,7 +2410,59 @@ void init_storage_map( } static -void init_storage_table( +ecs_flags32_t flecs_type_info_flags( + const ecs_type_info_t *ti) +{ + ecs_flags32_t flags = 0; + + if (ti->lifecycle.ctor) { + flags |= EcsTableHasCtors; + } + if (ti->lifecycle.on_add) { + flags |= EcsTableHasCtors; + } + if (ti->lifecycle.dtor) { + flags |= EcsTableHasDtors; + } + if (ti->lifecycle.on_remove) { + flags |= EcsTableHasDtors; + } + if (ti->lifecycle.copy) { + flags |= EcsTableHasCopy; + } + if (ti->lifecycle.move) { + flags |= EcsTableHasMove; + } + + return flags; +} + +static +void flecs_table_init_type_info( + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + 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; + int32_t i, count = table->type.count; + table->type_info = ecs_os_calloc_n(ecs_type_info_t*, count); + + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + + /* All ids in the storage table must be components with type info */ + const ecs_type_info_t *ti = idr->type_info; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + table->flags |= flecs_type_info_flags(ti); + table->type_info[i] = (ecs_type_info_t*)ti; + } +} + +static +void flecs_table_init_storage_table( ecs_world_t *world, ecs_table_t *table) { @@ -2278,12 +2471,12 @@ void init_storage_table( } ecs_type_t type = table->type; - int32_t i, count = ecs_vector_count(type); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + int32_t i, count = type.count; + ecs_id_t *ids = type.array; ecs_table_record_t *records = table->records; ecs_id_t array[ECS_ID_CACHE_SIZE]; - ecs_ids_t storage_ids = { .array = array }; + ecs_type_t storage_ids = { .array = array }; if (count > ECS_ID_CACHE_SIZE) { storage_ids.array = ecs_os_malloc_n(ecs_id_t, count); } @@ -2291,29 +2484,27 @@ void init_storage_table( for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(idr->flags & ECS_TYPE_INFO_INITIALIZED, - ECS_INTERNAL_ERROR, NULL); + ecs_id_t id = ids[i]; - if (idr->type_info == NULL) { - ecs_assert(ecs_get_typeid(world, ids[i]) == 0, - ECS_INTERNAL_ERROR, NULL); - continue; /* not a component */ + if (idr->type_info != NULL) { + storage_ids.array[storage_ids.count ++] = id; } - - storage_ids.array[storage_ids.count ++] = ids[i]; } - + if (storage_ids.count && storage_ids.count != count) { ecs_table_t *storage_table = flecs_table_find_or_create(world, &storage_ids); table->storage_table = storage_table; table->storage_count = flecs_ito(uint16_t, storage_ids.count); - table->storage_ids = ecs_vector_first(storage_table->type, ecs_id_t); + table->storage_ids = storage_table->type.array; + table->type_info = storage_table->type_info; + table->flags |= storage_table->flags; storage_table->refcount ++; } else if (storage_ids.count) { table->storage_table = table; table->storage_count = flecs_ito(uint16_t, count); - table->storage_ids = ecs_vector_first(type, ecs_id_t); + table->storage_ids = type.array; + flecs_table_init_type_info(table); } if (storage_ids.array != array) { @@ -2321,133 +2512,341 @@ void init_storage_table( } if (!table->storage_map) { - init_storage_map(table); + flecs_table_init_storage_map(table); } } -static -ecs_flags32_t type_info_flags( - const ecs_type_info_t *ti) +void flecs_table_init_data( + ecs_table_t *table) { - ecs_flags32_t flags = 0; + int32_t sw_count = table->sw_count; + int32_t bs_count = table->bs_count; - if (ti->lifecycle.ctor) { - flags |= EcsTableHasCtors; - } - if (ti->lifecycle.on_add) { - flags |= EcsTableHasCtors; - } - if (ti->lifecycle.dtor) { - flags |= EcsTableHasDtors; + ecs_data_t *storage = &table->data; + int32_t i, count = table->storage_count; + + /* Root tables don't have columns */ + if (!count && !sw_count && !bs_count) { + storage->columns = NULL; } - if (ti->lifecycle.on_remove) { - flags |= EcsTableHasDtors; + + if (count) { + storage->columns = ecs_os_calloc_n(ecs_column_t, count); } - if (ti->lifecycle.copy) { - flags |= EcsTableHasCopy; + + if (sw_count) { + storage->sw_columns = ecs_os_calloc_n(ecs_switch_t, sw_count); + for (i = 0; i < sw_count; i ++) { + flecs_switch_init(&storage->sw_columns[i], 0); + } } - if (ti->lifecycle.move) { - flags |= EcsTableHasMove; - } - return flags; + if (bs_count) { + storage->bs_columns = ecs_os_calloc_n(ecs_bitset_t, bs_count); + for (i = 0; i < bs_count; i ++) { + flecs_bitset_init(&storage->bs_columns[i]); + } + } } static -void init_type_info( +void flecs_table_init_flags( + ecs_world_t *world, ecs_table_t *table) { - ecs_table_t *storage_table = table->storage_table; - if (!storage_table) { - return; - } + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; - if (storage_table != table) { - /* Because the storage table is guaranteed to have the same components - * (but not tags) as this table, we can share the type info cache */ - table->type_info = storage_table->type_info; - table->flags |= storage_table->flags; - return; - } + /* Iterate components to initialize table flags */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; - if (table->type_info) { - return; - } + /* 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. */ - ecs_table_record_t *records = table->records; - int32_t i, count = ecs_vector_count(table->type); - table->type_info = ecs_os_calloc_n(ecs_type_info_t, count); + if (id <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasBuiltins; + } - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(idr->flags & ECS_TYPE_INFO_INITIALIZED, - ECS_INTERNAL_ERROR, NULL); - - /* All ids in the storage table must be components with type info */ - const ecs_type_info_t *ti = idr->type_info; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - table->flags |= type_info_flags(ti); - table->type_info[i] = *ti; + if (id == EcsModule) { + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } else if (id == EcsPrefab) { + table->flags |= EcsTableIsPrefab; + } else if (id == EcsDisabled) { + table->flags |= EcsTableIsDisabled; + } else { + ecs_entity_t role = id & ECS_ROLE_MASK; + if (role == ECS_PAIR) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + + table->flags |= EcsTableHasPairs; + + if (r == EcsIsA) { + table->flags |= EcsTableHasIsA; + } else if (r == EcsChildOf) { + table->flags |= EcsTableHasChildOf; + ecs_entity_t obj = ecs_pair_second(world, id); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + + if (obj == EcsFlecs || obj == EcsFlecsCore || + ecs_has_id(world, obj, EcsModule)) + { + /* If table contains entities that are inside one of the + * builtin modules, it contains builtin entities */ + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } + } else if (r == EcsUnion) { + table->flags |= EcsTableHasUnion; + + if (!table->sw_count) { + table->sw_offset = flecs_ito(int16_t, i); + } + table->sw_count ++; + } + } else if (role == ECS_DISABLED) { + table->flags |= EcsTableHasDisabled; + + if (!table->bs_count) { + table->bs_offset = flecs_ito(int16_t, i); + } + table->bs_count ++; + } else if (role == ECS_OVERRIDE) { + table->flags |= EcsTableHasOverrides; + } + } } } -void flecs_table_init_data( +void flecs_table_init( ecs_world_t *world, - ecs_table_t *table) -{ - init_storage_table(world, table); - init_type_info(table); + ecs_table_t *table, + ecs_table_t *from) +{ + flecs_table_init_flags(world, table); + + 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; + ecs_id_t *src_ids = NULL; + ecs_table_record_t *tr = NULL, *src_tr = NULL; + if (from) { + src_count = from->type.count; + src_ids = from->type.array; + src_tr = from->records; + } + + /* We don't know in advance how large the records array will be, so use + * cached vector. This eliminates unnecessary allocations, and/or expensive + * iterations to determine how many records we need. */ + ecs_vector_t *records = world->store.records; + ecs_vector_clear(records); + ecs_id_record_t *idr; - int32_t sw_count = table->sw_column_count; - int32_t bs_count = table->bs_column_count; + int32_t last_id = -1; /* Track last regular (non-pair) id */ + int32_t first_pair = -1; /* Track the first pair in the table */ + int32_t first_role = -1; /* Track first id with role */ - ecs_data_t *storage = &table->storage; - int32_t i, count = table->storage_count; + /* Scan to find boundaries of regular ids, pairs and roles */ + for (dst_i = 0; dst_i < dst_count; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + ecs_entity_t role = dst_id & ECS_ROLE_MASK; + if (first_pair == -1 && ECS_HAS_ROLE(dst_id, PAIR)) { + first_pair = dst_i; + } + if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { + last_id = dst_i; + } else if (first_role == -1 && role != ECS_PAIR) { + first_role = dst_i; + } + } - /* Root tables don't have columns */ - if (!count && !sw_count && !bs_count) { - storage->columns = NULL; + /* The easy part: initialize a record for every id in the type */ + for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { + ecs_id_t dst_id = dst_ids[dst_i]; + ecs_id_t src_id = src_ids[src_i]; + + idr = NULL; + + if (dst_id == src_id) { + idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; + } else if (dst_id < src_id) { + idr = flecs_ensure_id_record(world, dst_id); + } + if (idr) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)idr; + tr->column = dst_i; + tr->count = 1; + } + + dst_i += dst_id <= src_id; + src_i += dst_id >= src_id; } - if (count) { - storage->columns = ecs_os_calloc_n(ecs_column_t, count); + /* Add remaining ids that the "from" table didn't have */ + for (; (dst_i < dst_count); dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + tr = ecs_vector_add(&records, ecs_table_record_t); + idr = flecs_ensure_id_record(world, dst_id); + tr->hdr.cache = (ecs_table_cache_t*)idr; + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); + tr->column = dst_i; + tr->count = 1; } - if (sw_count) { - ecs_entity_t *ids = ecs_vector_first(table->type, ecs_entity_t); - int32_t sw_offset = table->sw_column_offset; - storage->sw_columns = ecs_os_calloc_n(ecs_sw_column_t, sw_count); + /* Add records for ids with roles (used by cleanup logic) */ + if (first_role != -1) { + for (dst_i = first_role; dst_i < dst_count; dst_i ++) { + ecs_id_t id = dst_ids[dst_i]; + ecs_entity_t role = id & ECS_ROLE_MASK; + if (role != ECS_PAIR) { + id &= ECS_COMPONENT_MASK; + id = ecs_pair(id, EcsWildcard); + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)flecs_ensure_id_record( + world, id); + tr->column = dst_i; + tr->count = 1; + } + } + } + + int32_t last_pair = -1; + int32_t first_tgt_wc = -1; + int32_t tgt_wc_count = 0; + bool has_childof = table->flags & EcsTableHasChildOf; + if (first_pair != -1) { + /* Add a (Relation, *) record for each relationship. */ + ecs_entity_t r = 0; + for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + if (!ECS_HAS_ROLE(dst_id, PAIR)) { + break; /* no more pairs */ + } + if (r != ECS_PAIR_FIRST(dst_id)) { /* New relation, new record */ + tr = ecs_vector_get(records, ecs_table_record_t, dst_i); + idr = ((ecs_id_record_t*)tr->hdr.cache)->parent; /* (R, *) */ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < sw_count; i ++) { - ecs_entity_t e = ids[i + sw_offset]; - ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); - e = e & ECS_COMPONENT_MASK; - const EcsType *switch_type = ecs_get(world, e, EcsType); - ecs_assert(switch_type != NULL, - ECS_INVALID_PARAMETER, "not a switch"); - ecs_table_t *sw_table = switch_type->normalized; - ecs_type_t sw_type = sw_table->type; + tr = ecs_vector_add(&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); + tr->count ++; + } + + last_pair = dst_i; - ecs_entity_t *sw_array = ecs_vector_first(sw_type, ecs_entity_t); - int32_t sw_array_count = ecs_vector_count(sw_type); + /* Add a (*, Target) record for each relationship target. Type + * ids are sorted relation-first, so we can't simply do a single linear + * scan to find all occurrences for a target. */ - ecs_switch_t *sw = flecs_switch_new( - sw_array[0], - sw_array[sw_array_count - 1], - 0); + /* We're going to insert records from the vector into the index that + * will get patched up later. To ensure the record pointers don't get + * invalidated we need to grow the vector so that it won't realloc as + * we're adding the next set of records */ - storage->sw_columns[i].data = sw; - storage->sw_columns[i].type = sw_table; + int wildcard_count = 3; /* for *, _ and (*, *) */ + wildcard_count += dst_count && !has_childof; /* for (ChildOf, 0) */ + + ecs_vector_set_min_size(&records, ecs_table_record_t, + ecs_vector_count(records) + wildcard_count + + (last_pair - first_pair)); + + for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); + + /* To avoid a quadratic search, use the O(1) lookup that the index + * already provides. */ + idr = flecs_ensure_id_record(world, tgt_id); + tr = (ecs_table_record_t*)flecs_id_record_table(idr, table); + if (!tr) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->column = dst_i; + tr->count = 1; + + ecs_table_cache_insert(&idr->cache, table, &tr->hdr); + + if (first_tgt_wc == -1) { + first_tgt_wc = ecs_vector_count(records) - 1; + } + + tgt_wc_count ++; + } else { + tr->count ++; + } + + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); } } - if (bs_count) { - storage->bs_columns = ecs_os_calloc_n(ecs_bs_column_t, bs_count); - for (i = 0; i < bs_count; i ++) { - flecs_bitset_init(&storage->bs_columns[i].data); + /* Lastly, add records for all-wildcard ids */ + if (last_id >= 0) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; + tr->column = 0; + tr->count = last_id + 1; + } + if (last_pair - first_pair) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; + tr->column = first_pair; + tr->count = last_pair - first_pair; + } + if (dst_count) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; + tr->column = 0; + tr->count = 1; + } + if (dst_count && !has_childof) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_childof_0; + tr->column = 0; + tr->count = 1; + } + + /* Now that all records have been added, copy them to array */ + int32_t i, dst_record_count = ecs_vector_count(records); + ecs_table_record_t *dst_tr = ecs_os_memdup_n( ecs_vector_first(records, + ecs_table_record_t), ecs_table_record_t, dst_record_count); + 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 ++) { + tr = &dst_tr[i]; + idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (i >= first_tgt_wc && i < (first_tgt_wc + tgt_wc_count)) { + /* If this is a target wildcard record it has already been + * registered, but the record is now at a different location in + * memory. Patch up the linked list with the new address */ + ecs_table_cache_replace(&idr->cache, table, &tr->hdr); + } else { + /* Other records are not registered yet */ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_cache_insert(&idr->cache, table, &tr->hdr); } + + /* Initialize event flags */ + table->flags |= idr->flags & EcsIdEventMask; } + + world->store.records = records; + + flecs_table_init_storage_table(world, table); + flecs_table_init_data(table); } static @@ -2475,16 +2874,11 @@ void run_on_remove( ecs_table_t *table, ecs_data_t *data) { - int32_t count = ecs_vector_count(data->entities); + int32_t count = data->entities.count; if (count) { - ecs_ids_t removed = { - .array = ecs_vector_first(table->type, ecs_id_t), - .count = ecs_vector_count(table->type) - }; - ecs_table_diff_t diff = { - .removed = removed, - .un_set = removed + .removed = table->type, + .un_set = table->type }; flecs_notify_on_remove(world, table, NULL, 0, count, &diff); @@ -2511,13 +2905,12 @@ void on_component_callback( it.entities = entities; ecs_size_t size = ti->size; - void *ptr = ecs_vector_get_t(column->data, size, ti->alignment, row); + void *ptr = ecs_storage_get(column, size, row); flecs_iter_init(&it, flecs_iter_cache_all); it.world = world; it.real_world = world; it.table = table; - it.type = table->type; it.ptrs[0] = ptr; it.sizes[0] = size; it.ids[0] = id; @@ -2540,8 +2933,7 @@ void ctor_component( ecs_xtor_t ctor = ti->lifecycle.ctor; if (ctor) { - void *ptr = ecs_vector_get_t( - column->data, ti->size, ti->alignment, row); + void *ptr = ecs_storage_get(column, ti->size, row); ctor(ptr, count, ti); } } @@ -2579,8 +2971,7 @@ void dtor_component( ecs_xtor_t dtor = ti->lifecycle.dtor; if (dtor) { - void *ptr = ecs_vector_get_t( - column->data, ti->size, ti->alignment, row); + void *ptr = ecs_storage_get(column, ti->size, row); dtor(ptr, count, ti); } } @@ -2622,8 +3013,8 @@ void dtor_all_components( ecs_id_t *ids = table->storage_ids; int32_t ids_count = table->storage_count; - ecs_record_t **records = ecs_vector_first(data->record_ptrs, ecs_record_t*); - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_record_t **records = data->records.array; + ecs_entity_t *entities = data->entities.array; int32_t i, c, end = row + count; (void)records; @@ -2639,7 +3030,7 @@ void dtor_all_components( /* Run on_remove callbacks in bulk for improved performance */ for (c = 0; c < ids_count; c++) { ecs_column_t *column = &data->columns[c]; - ecs_type_info_t *ti = &table->type_info[c]; + ecs_type_info_t *ti = table->type_info[c]; ecs_iter_action_t on_remove = ti->lifecycle.on_remove; if (on_remove) { on_component_callback(world, table, on_remove, EcsOnRemove, @@ -2653,7 +3044,7 @@ void dtor_all_components( for (i = row; i < end; i ++) { for (c = 0; c < ids_count; c++) { ecs_column_t *column = &data->columns[c]; - dtor_component(&table->type_info[c], column, i, 1); + dtor_component(table->type_info[c], column, i, 1); } /* Update entity index after invoking destructors so that entity can @@ -2745,48 +3136,45 @@ void fini_data( } /* Sanity check */ - ecs_assert(ecs_vector_count(data->record_ptrs) == - ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + ecs_assert(data->records.count == + data->entities.count, ECS_INTERNAL_ERROR, NULL); ecs_column_t *columns = data->columns; if (columns) { int32_t c, column_count = table->storage_count; for (c = 0; c < column_count; c ++) { /* Sanity check */ - ecs_assert(!columns[c].data || (ecs_vector_count(columns[c].data) == - ecs_vector_count(data->entities)), ECS_INTERNAL_ERROR, NULL); + ecs_assert(columns[c].count == data->entities.count, + ECS_INTERNAL_ERROR, NULL); - ecs_vector_free(columns[c].data); + ecs_storage_fini(&columns[c]); } ecs_os_free(columns); data->columns = NULL; } - ecs_sw_column_t *sw_columns = data->sw_columns; + ecs_switch_t *sw_columns = data->sw_columns; if (sw_columns) { - int32_t c, column_count = table->sw_column_count; + int32_t c, column_count = table->sw_count; for (c = 0; c < column_count; c ++) { - flecs_switch_free(sw_columns[c].data); + flecs_switch_fini(&sw_columns[c]); } ecs_os_free(sw_columns); data->sw_columns = NULL; } - ecs_bs_column_t *bs_columns = data->bs_columns; + ecs_bitset_t *bs_columns = data->bs_columns; if (bs_columns) { - int32_t c, column_count = table->bs_column_count; + int32_t c, column_count = table->bs_count; for (c = 0; c < column_count; c ++) { - flecs_bitset_fini(&bs_columns[c].data); + flecs_bitset_fini(&bs_columns[c]); } ecs_os_free(bs_columns); data->bs_columns = NULL; } - ecs_vector_free(data->entities); - ecs_vector_free(data->record_ptrs); - - data->entities = NULL; - data->record_ptrs = NULL; + ecs_storage_fini(&data->entities); + ecs_storage_fini(&data->records); if (deactivate && count) { flecs_table_set_empty(world, table); @@ -2807,7 +3195,7 @@ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table) { - fini_data(world, table, &table->storage, false, true, false, true); + fini_data(world, table, &table->data, false, true, false, true); } /* Cleanup, run OnRemove, clear entity index, deactivate table */ @@ -2815,7 +3203,7 @@ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table) { - fini_data(world, table, &table->storage, true, true, false, true); + fini_data(world, table, &table->data, true, true, false, true); } /* Cleanup, run OnRemove, delete from entity index, deactivate table */ @@ -2823,7 +3211,7 @@ void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table) { - fini_data(world, table, &table->storage, true, true, true, true); + fini_data(world, table, &table->data, true, true, true, true); } /* Unset all components in table. This function is called before a table is @@ -2833,7 +3221,7 @@ void flecs_table_remove_actions( ecs_table_t *table) { (void)world; - run_on_remove(world, table, &table->storage); + run_on_remove(world, table, &table->data); } /* Free table resources. */ @@ -2859,10 +3247,10 @@ void flecs_table_free( } if (ecs_should_log_2()) { - char *expr = ecs_type_str(world, table->type); + char *expr = ecs_type_str(world, &table->type); ecs_dbg_2( - "#[green]table#[normal] [%s] #[red]deleted#[normal] with id %d // %p", - expr, table->id, table); + "#[green]table#[normal] [%s] #[red]deleted#[normal] with id %d", + expr, table->id); ecs_os_free(expr); ecs_log_push_2(); } @@ -2870,14 +3258,14 @@ void flecs_table_free( world->info.empty_table_count -= (ecs_table_count(table) == 0); /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ - fini_data(world, table, &table->storage, false, true, true, false); + fini_data(world, table, &table->data, false, true, true, false); flecs_table_clear_edges(world, table); if (!is_root) { - ecs_ids_t ids = { - .array = ecs_vector_first(table->type, ecs_id_t), - .count = ecs_vector_count(table->type) + ecs_type_t ids = { + .array = table->type.array, + .count = table->type.count }; flecs_hashmap_remove(&world->store.table_map, &ids, ecs_table_t*); @@ -2950,7 +3338,7 @@ bool flecs_table_release( void flecs_table_free_type( ecs_table_t *table) { - ecs_vector_free((ecs_vector_t*)table->type); + ecs_os_free(table->type.array); } /* Reset a table to its initial state. */ @@ -2992,40 +3380,38 @@ void flecs_table_mark_dirty( static void move_switch_columns( ecs_table_t *dst_table, - ecs_data_t *dst_data, int32_t dst_index, ecs_table_t *src_table, - ecs_data_t *src_data, int32_t src_index, int32_t count, bool clear) { - int32_t i_old = 0, src_column_count = src_table->sw_column_count; - int32_t i_new = 0, dst_column_count = dst_table->sw_column_count; + int32_t i_old = 0, src_column_count = src_table->sw_count; + int32_t i_new = 0, dst_column_count = dst_table->sw_count; if (!src_column_count && !dst_column_count) { return; } - ecs_sw_column_t *src_columns = src_data->sw_columns; - ecs_sw_column_t *dst_columns = dst_data->sw_columns; + ecs_switch_t *src_columns = src_table->data.sw_columns; + ecs_switch_t *dst_columns = dst_table->data.sw_columns; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; - int32_t offset_new = dst_table->sw_column_offset; - int32_t offset_old = src_table->sw_column_offset; + int32_t offset_new = dst_table->sw_offset; + int32_t offset_old = src_table->sw_offset; - ecs_id_t *dst_ids = ecs_vector_first(dst_type, ecs_id_t); - ecs_id_t *src_ids = ecs_vector_first(src_type, ecs_id_t); + ecs_id_t *dst_ids = dst_type.array; + ecs_id_t *src_ids = src_type.array; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_entity_t dst_id = dst_ids[i_new + offset_new]; ecs_entity_t src_id = src_ids[i_old + offset_old]; if (dst_id == src_id) { - ecs_switch_t *src_switch = src_columns[i_old].data; - ecs_switch_t *dst_switch = dst_columns[i_new].data; + ecs_switch_t *src_switch = &src_columns[i_old]; + ecs_switch_t *dst_switch = &dst_columns[i_new]; flecs_switch_ensure(dst_switch, dst_index + count); @@ -3049,7 +3435,7 @@ void move_switch_columns( /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { - ecs_switch_t *src_switch = src_columns[i_old].data; + ecs_switch_t *src_switch = &src_columns[i_old]; ecs_assert(count == flecs_switch_count(src_switch), ECS_INTERNAL_ERROR, NULL); flecs_switch_clear(src_switch); @@ -3060,40 +3446,38 @@ void move_switch_columns( static void move_bitset_columns( ecs_table_t *dst_table, - ecs_data_t *dst_data, int32_t dst_index, ecs_table_t *src_table, - ecs_data_t *src_data, int32_t src_index, int32_t count, bool clear) { - int32_t i_old = 0, src_column_count = src_table->bs_column_count; - int32_t i_new = 0, dst_column_count = dst_table->bs_column_count; + int32_t i_old = 0, src_column_count = src_table->bs_count; + int32_t i_new = 0, dst_column_count = dst_table->bs_count; if (!src_column_count && !dst_column_count) { return; } - ecs_bs_column_t *src_columns = src_data->bs_columns; - ecs_bs_column_t *dst_columns = dst_data->bs_columns; + ecs_bitset_t *src_columns = src_table->data.bs_columns; + ecs_bitset_t *dst_columns = dst_table->data.bs_columns; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; - int32_t offset_new = dst_table->bs_column_offset; - int32_t offset_old = src_table->bs_column_offset; + int32_t offset_new = dst_table->bs_offset; + int32_t offset_old = src_table->bs_offset; - ecs_entity_t *dst_components = ecs_vector_first(dst_type, ecs_entity_t); - ecs_entity_t *src_components = ecs_vector_first(src_type, ecs_entity_t); + ecs_id_t *dst_components = dst_type.array; + ecs_id_t *src_components = src_type.array; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_entity_t dst_component = dst_components[i_new + offset_new]; - ecs_entity_t src_component = src_components[i_old + offset_old]; + ecs_id_t dst_component = dst_components[i_new + offset_new]; + ecs_id_t src_component = src_components[i_old + offset_old]; if (dst_component == src_component) { - ecs_bitset_t *src_bs = &src_columns[i_old].data; - ecs_bitset_t *dst_bs = &dst_columns[i_new].data; + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_bitset_t *dst_bs = &dst_columns[i_new]; flecs_bitset_ensure(dst_bs, dst_index + count); @@ -3117,7 +3501,7 @@ void move_bitset_columns( /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { - ecs_bitset_t *src_bs = &src_columns[i_old].data; + ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); @@ -3136,11 +3520,9 @@ void* grow_column( ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vector_t *vec = column->data; int32_t size = ti->size; - int32_t alignment = ti->alignment; - int32_t count = ecs_vector_count(vec); - int32_t src_size = ecs_vector_size(vec); + int32_t count = column->count; + int32_t src_size = column->size; int32_t dst_count = count + to_add; bool can_realloc = dst_size != src_size; void *result = NULL; @@ -3150,39 +3532,39 @@ void* grow_column( /* If the array could possibly realloc and the component has a move action * defined, move old elements manually */ ecs_move_t move_ctor; - if (count && can_realloc && (move_ctor = ti->lifecycle.move_ctor)) - { + if (count && can_realloc && (move_ctor = ti->lifecycle.move_ctor)) { ecs_xtor_t ctor = ti->lifecycle.ctor; ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); /* Create vector */ - ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, dst_size); - ecs_vector_set_count_t(&dst_vec, size, alignment, dst_count); + ecs_column_t dst; + ecs_storage_init(&dst, size, dst_size); + dst.count = dst_count; - void *src_buffer = ecs_vector_first_t(vec, size, alignment); - void *dst_buffer = ecs_vector_first_t(dst_vec, size, alignment); + void *src_buffer = column->array; + void *dst_buffer = dst.array; /* Move (and construct) existing elements to new vector */ move_ctor(dst_buffer, src_buffer, count, ti); if (construct) { /* Construct new element(s) */ - result = ECS_OFFSET(dst_buffer, size * count); + result = ECS_ELEM(dst_buffer, size, count); ctor(result, to_add, ti); } /* Free old vector */ - ecs_vector_free(vec); + ecs_storage_fini(column); - column->data = dst_vec; + *column = dst; } else { /* If array won't realloc or has no move, simply add new elements */ if (can_realloc) { - ecs_vector_set_size_t(&vec, size, alignment, dst_size); + ecs_storage_set_size(column, size, dst_size); } - result = ecs_vector_addn_t(&vec, size, alignment, to_add); + result = ecs_storage_grow(column, size, to_add); ecs_xtor_t ctor; if (construct && (ctor = ti->lifecycle.ctor)) { @@ -3190,12 +3572,9 @@ void* grow_column( * constructor, construct */ ctor(result, to_add, ti); } - - column->data = vec; } - ecs_assert(ecs_vector_size(column->data) == dst_size, - ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); return result; } @@ -3214,25 +3593,25 @@ int32_t grow_data( int32_t cur_count = flecs_table_data_count(data); int32_t column_count = table->storage_count; - int32_t sw_column_count = table->sw_column_count; - int32_t bs_column_count = table->bs_column_count; + int32_t sw_count = table->sw_count; + int32_t bs_count = table->bs_count; ecs_column_t *columns = data->columns; - ecs_sw_column_t *sw_columns = data->sw_columns; - ecs_bs_column_t *bs_columns = data->bs_columns; + ecs_switch_t *sw_columns = data->sw_columns; + ecs_bitset_t *bs_columns = data->bs_columns; /* Add record to record ptr array */ - ecs_vector_set_size(&data->record_ptrs, ecs_record_t*, size); - ecs_record_t **r = ecs_vector_addn(&data->record_ptrs, ecs_record_t*, to_add); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_vector_size(data->record_ptrs) > size) { - size = ecs_vector_size(data->record_ptrs); + ecs_storage_set_size_t(&data->records, ecs_record_t*, size); + ecs_record_t **r = ecs_storage_last_t(&data->records, ecs_record_t*) + 1; + data->records.count += to_add; + if (data->records.size > size) { + size = data->records.size; } /* Add entity to column with entity ids */ - ecs_vector_set_size(&data->entities, ecs_entity_t, size); - ecs_entity_t *e = ecs_vector_addn(&data->entities, ecs_entity_t, to_add); - ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_size(data->entities) == size, ECS_INTERNAL_ERROR, NULL); + ecs_storage_set_size_t(&data->entities, ecs_entity_t, size); + ecs_entity_t *e = ecs_storage_last_t(&data->entities, ecs_entity_t) + 1; + data->entities.count += to_add; + ecs_assert(data->entities.size == size, ECS_INTERNAL_ERROR, NULL); /* Initialize entity ids and record ptrs */ int32_t i; @@ -3246,24 +3625,23 @@ int32_t grow_data( ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); /* Add elements to each column array */ - ecs_type_info_t *type_info = table->type_info; + ecs_type_info_t **type_info = table->type_info; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = &type_info[i]; + ecs_type_info_t *ti = type_info[i]; grow_column(column, ti, to_add, size, true); - ecs_assert(ecs_vector_size(columns[i].data) == size, - ECS_INTERNAL_ERROR, NULL); + ecs_assert(columns[i].size == size, ECS_INTERNAL_ERROR, NULL); } /* Add elements to each switch column */ - for (i = 0; i < sw_column_count; i ++) { - ecs_switch_t *sw = sw_columns[i].data; + 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_column_count; i ++) { - ecs_bitset_t *bs = &bs_columns[i].data; + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, to_add); } @@ -3274,59 +3652,51 @@ int32_t grow_data( flecs_table_set_empty(world, table); } - table->alloc_count ++; - /* Return index of first added entity */ return cur_count; } static void fast_append( - ecs_type_info_t *type_info, + ecs_type_info_t **type_info, ecs_column_t *columns, int32_t count) { /* Add elements to each column array */ int32_t i; for (i = 0; i < count; i ++) { - ecs_type_info_t *ti = &type_info[i]; + ecs_type_info_t *ti = type_info[i]; ecs_column_t *column = &columns[i]; - ecs_vector_add_t(&column->data, ti->size, ti->alignment); + ecs_storage_append(column, ti->size); } } int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, ecs_entity_t entity, ecs_record_t *record, bool construct) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); check_table_sanity(table); /* Get count & size before growing entities array. This tells us whether the * arrays will realloc */ - int32_t count = ecs_vector_count(data->entities); - int32_t size = ecs_vector_size(data->entities); + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; int32_t column_count = table->storage_count; - ecs_column_t *columns = table->storage.columns; + ecs_column_t *columns = table->data.columns; /* Grow buffer with entity ids, set new element to new entity */ - ecs_entity_t *e = ecs_vector_add(&data->entities, ecs_entity_t); + ecs_entity_t *e = ecs_storage_append_t(&data->entities, ecs_entity_t); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - *e = entity; - - /* Keep track of alloc count. This allows references to check if cached - * pointers need to be updated. */ - table->alloc_count += (count == size); + *e = entity; /* Add record ptr to array with record ptrs */ - ecs_record_t **r = ecs_vector_add(&data->record_ptrs, ecs_record_t*); + ecs_record_t **r = ecs_storage_append_t(&data->records, ecs_record_t*); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); *r = record; @@ -3334,7 +3704,7 @@ int32_t flecs_table_append( mark_table_dirty(world, table, 0); ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_type_info_t *type_info = table->type_info; + ecs_type_info_t **type_info = table->type_info; /* Fast path: no switch columns, no lifecycle actions */ if (!(table->flags & EcsTableIsComplex)) { @@ -3345,22 +3715,22 @@ int32_t flecs_table_append( return count; } - int32_t sw_column_count = table->sw_column_count; - int32_t bs_column_count = table->bs_column_count; - ecs_sw_column_t *sw_columns = data->sw_columns; - ecs_bs_column_t *bs_columns = data->bs_columns; - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t sw_count = table->sw_count; + int32_t bs_count = table->bs_count; + ecs_switch_t *sw_columns = data->sw_columns; + ecs_bitset_t *bs_columns = data->bs_columns; + ecs_entity_t *entities = data->entities.array; /* Reobtain size to ensure that the columns have the same size as the * entities and record vectors. This keeps reasoning about when allocations * occur easier. */ - size = ecs_vector_size(data->entities); + int32_t size = data->entities.size; /* Grow component arrays with 1 element */ int32_t i; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = &type_info[i]; + ecs_type_info_t *ti = type_info[i]; grow_column(column, ti, 1, size, construct); ecs_iter_action_t on_add = ti->lifecycle.on_add; @@ -3369,23 +3739,23 @@ int32_t flecs_table_append( &entities[count], table->storage_ids[i], count, 1, ti); } - ecs_assert(ecs_vector_size(columns[i].data) == - ecs_vector_size(data->entities), ECS_INTERNAL_ERROR, NULL); - ecs_assert( ecs_vector_count(columns[i].data) == - ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + ecs_assert(columns[i].size == + data->entities.size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(columns[i].count == + data->entities.count, ECS_INTERNAL_ERROR, NULL); } /* Add element to each switch column */ - for (i = 0; i < sw_column_count; i ++) { + for (i = 0; i < sw_count; i ++) { ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = sw_columns[i].data; + ecs_switch_t *sw = &sw_columns[i]; flecs_switch_add(sw); } /* Add element to each bitset column */ - for (i = 0; i < bs_column_count; i ++) { + for (i = 0; i < bs_count; i ++) { ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &bs_columns[i].data; + ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, 1); } @@ -3407,62 +3777,58 @@ void fast_delete_last( { int i; for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_vector_remove_last(column->data); + ecs_storage_remove_last(&columns[i]); } } static void fast_delete( - ecs_type_info_t *type_info, + ecs_type_info_t **type_info, ecs_column_t *columns, int32_t column_count, int32_t index) { int i; for (i = 0; i < column_count; i ++) { - ecs_type_info_t *ti = &type_info[i]; + ecs_type_info_t *ti = type_info[i]; ecs_column_t *column = &columns[i]; - ecs_vector_remove_t(column->data, ti->size, ti->alignment, index); + ecs_storage_remove(column, ti->size, index); } } void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, int32_t index, bool destruct) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); check_table_sanity(table); - ecs_vector_t *v_entities = data->entities; - int32_t count = ecs_vector_count(v_entities); + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); count --; ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); /* Move last entity id to index */ - ecs_entity_t *entities = ecs_vector_first(v_entities, ecs_entity_t); + ecs_entity_t *entities = data->entities.array; ecs_entity_t entity_to_move = entities[count]; ecs_entity_t entity_to_delete = entities[index]; entities[index] = entity_to_move; - ecs_vector_remove_last(v_entities); + ecs_storage_remove_last(&data->entities); /* Move last record ptr to index */ - ecs_vector_t *v_records = data->record_ptrs; - ecs_assert(count < ecs_vector_count(v_records), ECS_INTERNAL_ERROR, NULL); + ecs_assert(count < data->records.count, ECS_INTERNAL_ERROR, NULL); - ecs_record_t **records = ecs_vector_first(v_records, ecs_record_t*); + ecs_record_t **records = data->records.array; ecs_record_t *record_to_move = records[count]; records[index] = record_to_move; - ecs_vector_remove_last(v_records); + ecs_storage_remove_last(&data->records); /* Update record of moved entity in entity index */ if (index != count) { @@ -3483,7 +3849,7 @@ void flecs_table_delete( } /* Destruct component data */ - ecs_type_info_t *type_info = table->type_info; + ecs_type_info_t **type_info = table->type_info; ecs_column_t *columns = data->columns; int32_t column_count = table->storage_count; int32_t i; @@ -3508,7 +3874,7 @@ void flecs_table_delete( /* If table has component destructors, invoke */ if (destruct && (table->flags & EcsTableHasDtors)) { for (i = 0; i < column_count; i ++) { - remove_component(world, table, &type_info[i], &columns[i], + remove_component(world, table, type_info[i], &columns[i], &entity_to_delete, ids[i], index, 1); } } @@ -3521,12 +3887,10 @@ void flecs_table_delete( if (destruct && (table->flags & (EcsTableHasDtors | EcsTableHasMove))) { for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = &type_info[i]; + ecs_type_info_t *ti = type_info[i]; ecs_size_t size = ti->size; - ecs_size_t align = ti->alignment; - ecs_vector_t *vec = column->data; - void *dst = ecs_vector_get_t(vec, size, align, index); - void *src = ecs_vector_last_t(vec, size, align); + void *dst = ecs_storage_get(column, size, index); + void *src = ecs_storage_last(column, size); ecs_iter_action_t on_remove = ti->lifecycle.on_remove; if (on_remove) { @@ -3541,7 +3905,7 @@ void flecs_table_delete( ecs_os_memcpy(dst, src, size); } - ecs_vector_remove_last(vec); + ecs_storage_remove_last(column); } } else { fast_delete(type_info, columns, column_count, index); @@ -3549,17 +3913,17 @@ void flecs_table_delete( } /* Remove elements from switch columns */ - ecs_sw_column_t *sw_columns = data->sw_columns; - int32_t sw_column_count = table->sw_column_count; - for (i = 0; i < sw_column_count; i ++) { - flecs_switch_remove(sw_columns[i].data, index); + ecs_switch_t *sw_columns = data->sw_columns; + int32_t sw_count = table->sw_count; + for (i = 0; i < sw_count; i ++) { + flecs_switch_remove(&sw_columns[i], index); } /* Remove elements from bitset columns */ - ecs_bs_column_t *bs_columns = data->bs_columns; - int32_t bs_column_count = table->bs_column_count; - for (i = 0; i < bs_column_count; i ++) { - flecs_bitset_remove(&bs_columns[i].data, index); + ecs_bitset_t *bs_columns = data->bs_columns; + int32_t bs_count = table->bs_count; + for (i = 0; i < bs_count; i ++) { + flecs_bitset_remove(&bs_columns[i], index); } check_table_sanity(table); @@ -3568,10 +3932,8 @@ void flecs_table_delete( static void fast_move( ecs_table_t *dst_table, - ecs_data_t *dst_data, int32_t dst_index, ecs_table_t *src_table, - ecs_data_t *src_data, int32_t src_index) { int32_t i_new = 0, dst_column_count = dst_table->storage_count; @@ -3579,10 +3941,10 @@ void fast_move( ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; - ecs_column_t *src_columns = src_data->columns; - ecs_column_t *dst_columns = dst_data->columns; + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; - ecs_type_info_t *dst_type_info = dst_table->type_info; + ecs_type_info_t **dst_type_info = dst_table->type_info; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new]; @@ -3591,16 +3953,10 @@ void fast_move( if (dst_id == src_id) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; - ecs_type_info_t *ti = &dst_type_info[i_new]; + ecs_type_info_t *ti = dst_type_info[i_new]; int32_t size = ti->size; - int32_t alignment = ti->alignment; - void *dst = ecs_vector_get_t( - dst_column->data, size, alignment, dst_index); - void *src = ecs_vector_get_t( - src_column->data, size, alignment, src_index); - - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + void *dst = ecs_storage_get(dst_column, size, dst_index); + void *src = ecs_storage_get(src_column, size, src_index); ecs_os_memcpy(dst, src, size); } @@ -3614,10 +3970,8 @@ void flecs_table_move( ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *dst_table, - ecs_data_t *dst_data, int32_t dst_index, ecs_table_t *src_table, - ecs_data_t *src_data, int32_t src_index, bool construct) { @@ -3629,37 +3983,31 @@ void flecs_table_move( ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src_data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst_data != NULL, ECS_INTERNAL_ERROR, NULL); - check_table_sanity(dst_table); check_table_sanity(src_table); if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { - fast_move(dst_table, dst_data, dst_index, src_table, src_data, - src_index); + fast_move(dst_table, dst_index, src_table, src_index); check_table_sanity(dst_table); check_table_sanity(src_table); return; } - move_switch_columns(dst_table, dst_data, dst_index, src_table, src_data, - src_index, 1, false); - move_bitset_columns(dst_table, dst_data, dst_index, src_table, src_data, - src_index, 1, false); + move_switch_columns(dst_table, dst_index, src_table, src_index, 1, false); + move_bitset_columns(dst_table, dst_index, src_table, src_index, 1, false); bool same_entity = dst_entity == src_entity; - ecs_type_info_t *dst_type_info = dst_table->type_info; - ecs_type_info_t *src_type_info = src_table->type_info; + ecs_type_info_t **dst_type_info = dst_table->type_info; + ecs_type_info_t **src_type_info = src_table->type_info; int32_t i_new = 0, dst_column_count = dst_table->storage_count; int32_t i_old = 0, src_column_count = src_table->storage_count; ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; - ecs_column_t *src_columns = src_data->columns; - ecs_column_t *dst_columns = dst_data->columns; + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new]; @@ -3668,18 +4016,12 @@ void flecs_table_move( if (dst_id == src_id) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; - ecs_type_info_t *ti = &dst_type_info[i_new]; + ecs_type_info_t *ti = dst_type_info[i_new]; int32_t size = ti->size; - int32_t alignment = ti->alignment; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *dst = ecs_vector_get_t( - dst_column->data, size, alignment, dst_index); - void *src = ecs_vector_get_t( - src_column->data, size, alignment, src_index); - - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + void *dst = ecs_storage_get(dst_column, size, dst_index); + void *src = ecs_storage_get(src_column, size, src_index); if (same_entity) { ecs_move_t move = ti->lifecycle.ctor_move_dtor; @@ -3700,12 +4042,12 @@ void flecs_table_move( } else { if (dst_id < src_id) { if (construct) { - add_component(world, dst_table, &dst_type_info[i_new], + add_component(world, dst_table, dst_type_info[i_new], &dst_columns[i_new], &dst_entity, dst_id, dst_index, 1); } } else { - remove_component(world, src_table, &src_type_info[i_old], + remove_component(world, src_table, src_type_info[i_old], &src_columns[i_old], &src_entity, src_id, src_index, 1); } @@ -3717,13 +4059,13 @@ void flecs_table_move( if (construct) { for (; (i_new < dst_column_count); i_new ++) { - add_component(world, dst_table, &dst_type_info[i_new], + add_component(world, dst_table, dst_type_info[i_new], &dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1); } } for (; (i_old < src_column_count); i_old ++) { - remove_component(world, src_table, &src_type_info[i_old], + remove_component(world, src_table, src_type_info[i_old], &src_columns[i_old], &src_entity, src_ids[i_old], src_index, 1); } @@ -3777,31 +4119,28 @@ bool flecs_table_shrink( ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); (void)world; - check_table_sanity(table); - ecs_data_t *data = &table->storage; - bool has_payload = data->entities != NULL; - ecs_vector_reclaim(&data->entities, ecs_entity_t); - ecs_vector_reclaim(&data->record_ptrs, ecs_record_t*); + ecs_data_t *data = &table->data; + bool has_payload = data->entities.array != NULL; + ecs_storage_reclaim_t(&data->entities, ecs_entity_t); + ecs_storage_reclaim_t(&data->records, ecs_record_t*); int32_t i, count = table->storage_count; - ecs_type_info_t *type_info = table->type_info; + ecs_type_info_t **type_info = table->type_info; for (i = 0; i < count; i ++) { ecs_column_t *column = &data->columns[i]; - ecs_type_info_t *ti = &type_info[i]; - ecs_vector_reclaim_t(&column->data, ti->size, ti->alignment); + ecs_type_info_t *ti = type_info[i]; + ecs_storage_reclaim(column, ti->size); } - table->alloc_count ++; - return has_payload; } int32_t flecs_table_data_count( const ecs_data_t *data) { - return data ? ecs_vector_count(data->entities) : 0; + return data ? data->entities.count : 0; } static @@ -3811,15 +4150,15 @@ void swap_switch_columns( int32_t row_1, int32_t row_2) { - int32_t i = 0, column_count = table->sw_column_count; + int32_t i = 0, column_count = table->sw_count; if (!column_count) { return; } - ecs_sw_column_t *columns = data->sw_columns; + ecs_switch_t *columns = data->sw_columns; for (i = 0; i < column_count; i ++) { - ecs_switch_t *sw = columns[i].data; + ecs_switch_t *sw = &columns[i]; flecs_switch_swap(sw, row_1, row_2); } } @@ -3831,15 +4170,15 @@ void swap_bitset_columns( int32_t row_1, int32_t row_2) { - int32_t i = 0, column_count = table->bs_column_count; + int32_t i = 0, column_count = table->bs_count; if (!column_count) { return; } - ecs_bs_column_t *columns = data->bs_columns; + ecs_bitset_t *columns = data->bs_columns; for (i = 0; i < column_count; i ++) { - ecs_bitset_t *bs = &columns[i].data; + ecs_bitset_t *bs = &columns[i]; flecs_bitset_swap(bs, row_1, row_2); } } @@ -3867,13 +4206,13 @@ void flecs_table_swap( /* If the table is monitored indicate that there has been a change */ mark_table_dirty(world, table, 0); - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_entity_t *entities = data->entities.array; ecs_entity_t e1 = entities[row_1]; ecs_entity_t e2 = entities[row_2]; - ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); - ecs_record_t *record_ptr_1 = record_ptrs[row_1]; - ecs_record_t *record_ptr_2 = record_ptrs[row_2]; + ecs_record_t **records = data->records.array; + ecs_record_t *record_ptr_1 = records[row_1]; + ecs_record_t *record_ptr_2 = records[row_2]; ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); @@ -3887,8 +4226,8 @@ void flecs_table_swap( entities[row_2] = e1; record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); - record_ptrs[row_1] = record_ptr_2; - record_ptrs[row_2] = record_ptr_1; + records[row_1] = record_ptr_2; + records[row_2] = record_ptr_1; swap_switch_columns(table, data, row_1, row_2); swap_bitset_columns(table, data, row_1, row_2); @@ -3899,22 +4238,20 @@ void flecs_table_swap( return; } - ecs_type_info_t *type_info = table->type_info; + ecs_type_info_t **type_info = table->type_info; /* Swap columns */ int32_t i, column_count = table->storage_count; for (i = 0; i < column_count; i ++) { - ecs_type_info_t *ti = &type_info[i]; + ecs_type_info_t *ti = type_info[i]; int32_t size = ti->size; - int32_t alignment = ti->alignment; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *ptr = ecs_vector_first_t(columns[i].data, size, alignment); + void *ptr = columns[i].array; void *tmp = ecs_os_alloca(size); - void *el_1 = ECS_OFFSET(ptr, size * row_1); - void *el_2 = ECS_OFFSET(ptr, size * row_2); + void *el_1 = ECS_ELEM(ptr, size, row_1); + void *el_2 = ECS_ELEM(ptr, size, row_2); ecs_os_memcpy(tmp, el_1, size); ecs_os_memcpy(el_1, el_2, size); @@ -3925,89 +4262,52 @@ void flecs_table_swap( } static -void merge_vector( - ecs_vector_t **dst_out, - ecs_vector_t *src, - int16_t size, - int16_t alignment) -{ - ecs_vector_t *dst = *dst_out; - int32_t dst_count = ecs_vector_count(dst); - - if (!dst_count) { - if (dst) { - ecs_vector_free(dst); - } - - *dst_out = src; - - /* If the new table is not empty, copy the contents from the - * src into the dst. */ - } else { - int32_t src_count = ecs_vector_count(src); - ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); - - void *dst_ptr = ecs_vector_first_t(dst, size, alignment); - void *src_ptr = ecs_vector_first_t(src, size, alignment); - - dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); - - ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); - - ecs_vector_free(src); - *dst_out = dst; - } -} - -static -void merge_column( - ecs_type_info_t *ti, +void flecs_merge_column( + ecs_column_t *dst, + ecs_column_t *src, int32_t size, - int32_t alignment, - ecs_data_t *data, - int32_t column_id, - ecs_vector_t *src) + ecs_type_info_t *ti) { - ecs_column_t *column = &data->columns[column_id]; - ecs_vector_t *dst = column->data; - int32_t dst_count = ecs_vector_count(dst); + int32_t dst_count = dst->count; if (!dst_count) { - if (dst) { - ecs_vector_free(dst); - } - - column->data = src; + ecs_storage_fini(dst); + *dst = *src; + src->array = NULL; + src->count = 0; + src->size = 0; /* If the new table is not empty, copy the contents from the * src into the dst. */ } else { - int32_t src_count = ecs_vector_count(src); - ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); - column->data = dst; + int32_t src_count = src->count; + ecs_storage_set_count(dst, size, dst_count + src_count); /* Construct new values */ - ctor_component(ti, column, dst_count, src_count); + if (ti) { + ctor_component(ti, dst, dst_count, src_count); + } - void *dst_ptr = ecs_vector_first_t(dst, size, alignment); - void *src_ptr = ecs_vector_first_t(src, size, alignment); - - dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); + void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); + void *src_ptr = src->array; /* Move values into column */ - ecs_move_t move = ti->lifecycle.move; + ecs_move_t move = NULL; + if (ti) { + move = ti->lifecycle.move; + } if (move) { move(dst_ptr, src_ptr, src_count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); } - ecs_vector_free(src); + ecs_storage_fini(src); } } static -void merge_table_data( +void flecs_merge_table_data( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table, @@ -4021,107 +4321,74 @@ void merge_table_data( ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; - ecs_type_info_t *dst_type_info = dst_table->type_info; - ecs_type_info_t *src_type_info = src_table->type_info; + ecs_type_info_t **dst_type_info = dst_table->type_info; + ecs_type_info_t **src_type_info = src_table->type_info; - ecs_column_t *src_columns = src_data->columns; - ecs_column_t *dst_columns = dst_data->columns; + ecs_column_t *src = src_data->columns; + ecs_column_t *dst = dst_data->columns; - if (!dst_columns && !dst_data->entities) { - dst_columns = dst_data->columns; - } - - ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!dst_column_count || dst, ECS_INTERNAL_ERROR, NULL); if (!src_count) { return; } /* Merge entities */ - merge_vector(&dst_data->entities, src_data->entities, ECS_SIZEOF(ecs_entity_t), - ECS_ALIGNOF(ecs_entity_t)); - src_data->entities = NULL; - ecs_assert(ecs_vector_count(dst_data->entities) == src_count + dst_count, + flecs_merge_column(&dst_data->entities, &src_data->entities, + ECS_SIZEOF(ecs_entity_t), NULL); + ecs_assert(dst_data->entities.count == src_count + dst_count, ECS_INTERNAL_ERROR, NULL); - /* Merge entity index record pointers */ - merge_vector(&dst_data->record_ptrs, src_data->record_ptrs, - ECS_SIZEOF(ecs_record_t*), ECS_ALIGNOF(ecs_record_t*)); - src_data->record_ptrs = NULL; + /* Merge record pointers */ + flecs_merge_column(&dst_data->records, &src_data->records, + ECS_SIZEOF(ecs_record_t*), 0); for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_id_t dst_id = dst_ids[i_new]; ecs_id_t src_id = src_ids[i_old]; - ecs_type_info_t *dst_ti = &dst_type_info[i_new]; + ecs_type_info_t *dst_ti = dst_type_info[i_new]; int32_t size = dst_ti->size; - int32_t alignment = dst_ti->alignment; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); if (dst_id == src_id) { - merge_column(dst_ti, size, alignment, dst_data, - i_new, src_columns[i_old].data); - src_columns[i_old].data = NULL; - - /* Mark component column as dirty */ + flecs_merge_column(&dst[i_new], &src[i_old], size, dst_ti); mark_table_dirty(world, dst_table, i_new + 1); i_new ++; i_old ++; } else if (dst_id < src_id) { - /* New column does not occur in old table, make sure vector is large - * enough. */ - ecs_column_t *column = &dst_columns[i_new]; - ecs_vector_set_count_t(&column->data, size, alignment, - src_count + dst_count); - - /* Construct new values */ + /* New column, make sure vector is large enough. */ + ecs_column_t *column = &dst[i_new]; + ecs_storage_set_count(column, size, src_count + dst_count); ctor_component(dst_ti, column, 0, src_count + dst_count); - i_new ++; } else if (dst_id > src_id) { - ecs_column_t *column = &src_columns[i_old]; - - /* Destruct old values */ - dtor_component(&src_type_info[i_old], column, 0, src_count); - - /* Old column does not occur in new table, remove */ - ecs_vector_free(column->data); - column->data = NULL; - + /* Old column does not occur in new table, destruct */ + ecs_column_t *column = &src[i_old]; + dtor_component(src_type_info[i_old], column, 0, src_count); + ecs_storage_fini(column); i_old ++; } } - move_switch_columns(dst_table, dst_data, dst_count, src_table, src_data, 0, - src_count, true); - move_bitset_columns(dst_table, dst_data, dst_count, src_table, src_data, 0, - src_count, true); + move_switch_columns(dst_table, dst_count, src_table, 0, src_count, true); + move_bitset_columns(dst_table, dst_count, src_table, 0, src_count, true); /* Initialize remaining columns */ for (; i_new < dst_column_count; i_new ++) { - ecs_column_t *column = &dst_columns[i_new]; - ecs_type_info_t *ti = &dst_type_info[i_new]; - int32_t size = ti->size; - int32_t alignment = ti->alignment; - + ecs_column_t *column = &dst[i_new]; + ecs_type_info_t *ti = dst_type_info[i_new]; + int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - ecs_vector_set_count_t(&column->data, size, alignment, - src_count + dst_count); - - /* Construct new values */ + ecs_storage_set_count(column, size, src_count + dst_count); ctor_component(ti, column, 0, src_count + dst_count); } - /* Destroy remaining columns */ + /* Destruct remaining columns */ for (; i_old < src_column_count; i_old ++) { - ecs_column_t *column = &src_columns[i_old]; - - /* Destruct old values */ - dtor_component(&src_type_info[i_old], column, 0, src_count); - - /* Old column does not occur in new table, remove */ - ecs_vector_free(column->data); - column->data = NULL; + ecs_column_t *column = &src[i_old]; + dtor_component(src_type_info[i_old], column, 0, src_count); + ecs_storage_fini(column); } /* Mark entity column as dirty */ @@ -4132,7 +4399,7 @@ int32_t ecs_table_count( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return flecs_table_data_count(&table->storage); + return flecs_table_data_count(&table->data); } void flecs_table_merge( @@ -4165,18 +4432,16 @@ void flecs_table_merge( } if (!dst_data) { - dst_data = &dst_table->storage; + dst_data = &dst_table->data; if (dst_table == src_table) { move_data = true; } } - ecs_entity_t *src_entities = ecs_vector_first(src_data->entities, ecs_entity_t); - int32_t src_count = ecs_vector_count(src_data->entities); - int32_t dst_count = ecs_vector_count(dst_data->entities); - - ecs_record_t **src_records = ecs_vector_first( - src_data->record_ptrs, ecs_record_t*); + ecs_entity_t *src_entities = src_data->entities.array; + int32_t src_count = src_data->entities.count; + int32_t dst_count = dst_data->entities.count; + ecs_record_t **src_records = src_data->records.array; /* First, update entity index so old entities point to new type */ int32_t i; @@ -4198,12 +4463,10 @@ void flecs_table_merge( if (move_data) { *dst_data = *src_data; } else { - merge_table_data(world, dst_table, src_table, src_count, dst_count, + flecs_merge_table_data(world, dst_table, src_table, src_count, dst_count, src_data, dst_data); } - dst_table->alloc_count ++; - if (src_count) { if (!dst_count) { flecs_table_set_empty(world, dst_table); @@ -4221,20 +4484,20 @@ void flecs_table_replace_data( ecs_data_t *data) { int32_t prev_count = 0; - ecs_data_t *table_data = &table->storage; + 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); check_table_sanity(table); - prev_count = ecs_vector_count(table_data->entities); + prev_count = table_data->entities.count; run_on_remove(world, table, table_data); flecs_table_clear_data(world, table, table_data); if (data) { - table->storage = *data; + table->data = *data; } else { - flecs_table_init_data(world, table); + flecs_table_init_data(table); } int32_t count = ecs_table_count(table); @@ -4245,8 +4508,6 @@ void flecs_table_replace_data( flecs_table_set_empty(world, table); } - table->alloc_count ++; - check_table_sanity(table); } @@ -4334,17 +4595,17 @@ ecs_column_t* ecs_table_column_for_id( ecs_table_record_t *tr = flecs_get_table_record(world, storage_table, id); if (tr) { - return &table->storage.columns[tr->column]; + return &table->data.columns[tr->column]; } return NULL; } -ecs_type_t ecs_table_get_type( +const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { if (table) { - return table->type; + return &table->type; } else { return NULL; } @@ -4361,8 +4622,7 @@ int32_t ecs_table_type_to_storage_index( int32_t index) { ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < ecs_vector_count(table->type), - ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); int32_t *storage_map = table->storage_map; if (storage_map) { return storage_map[index]; @@ -4377,12 +4637,26 @@ int32_t ecs_table_storage_to_type_index( { ecs_check(index < table->storage_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t offset = ecs_vector_count(table->type); + int32_t offset = table->type.count; return table->storage_map[offset + index]; error: return -1; } +int32_t flecs_table_column_to_union_index( + const ecs_table_t *table, + int32_t column) +{ + 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) @@ -4409,15 +4683,14 @@ void* ecs_record_get_column( ecs_table_t *table = r->table; ecs_check(column < table->storage_count, ECS_INVALID_PARAMETER, NULL); - ecs_type_info_t *ti = &table->type_info[column]; - ecs_column_t *c = &table->storage.columns[column]; + ecs_type_info_t *ti = table->type_info[column]; + ecs_column_t *c = &table->data.columns[column]; ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == ti->size, ECS_INVALID_PARAMETER, NULL); - return ecs_vector_get_t(c->data, ti->size, ti->alignment, - ECS_RECORD_TO_ROW(r->row)); + return ecs_storage_get(c, ti->size, ECS_RECORD_TO_ROW(r->row)); error: return NULL; } @@ -4606,6 +4879,169 @@ const ecs_world_t* ecs_get_world( } +void ecs_storage_init( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count) +{ + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + storage->array = NULL; + storage->count = 0; + if (elem_count) { + storage->array = ecs_os_malloc(size * elem_count); + } + storage->size = elem_count; +} + +void ecs_storage_fini( + ecs_column_t *storage) +{ + ecs_os_free(storage->array); + storage->array = NULL; + storage->count = 0; + storage->size = 0; +} + +int32_t ecs_storage_count( + ecs_column_t *storage) +{ + return storage->count; +} + +int32_t ecs_storage_size( + ecs_column_t *storage) +{ + return storage->size; +} + +void* ecs_storage_get( + ecs_column_t *storage, + ecs_size_t size, + int32_t index) +{ + ecs_assert(index < storage->count, ECS_OUT_OF_RANGE, NULL); + return ECS_ELEM(storage->array, size, index); +} + +void* ecs_storage_last( + ecs_column_t *storage, + ecs_size_t size) +{ + return ECS_ELEM(storage->array, size, storage->count - 1); +} + +void* ecs_storage_first( + ecs_column_t *storage) +{ + return storage->array; +} + +void* ecs_storage_append( + ecs_column_t *storage, + ecs_size_t size) +{ + int32_t count = storage->count; + if (storage->size == count) { + ecs_storage_set_size(storage, size, count + 1); + } + storage->count = count + 1; + return ECS_ELEM(storage->array, size, count); +} + +void ecs_storage_remove( + ecs_column_t *storage, + ecs_size_t size, + int32_t index) +{ + ecs_assert(index < storage->count, ECS_OUT_OF_RANGE, NULL); + if (index == --storage->count) { + return; + } + + ecs_os_memcpy( + ECS_ELEM(storage->array, size, index), + ECS_ELEM(storage->array, size, storage->count), + size); +} + +void ecs_storage_remove_last( + ecs_column_t *storage) +{ + storage->count --; +} + +ecs_column_t ecs_storage_copy( + ecs_column_t *storage, + ecs_size_t size) +{ + return (ecs_column_t) { + .count = storage->count, + .size = storage->size, + .array = ecs_os_memdup(storage->array, storage->size * size) + }; +} + +void ecs_storage_reclaim( + ecs_column_t *storage, + ecs_size_t size) +{ + int32_t count = storage->count; + if (count < storage->size) { + if (count) { + storage->array = ecs_os_realloc(storage->array, size * count); + storage->size = count; + } else { + ecs_storage_fini(storage); + } + } +} + +void ecs_storage_set_size( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count) +{ + if (storage->size != elem_count) { + if (elem_count < storage->count) { + elem_count = storage->count; + } + + elem_count = flecs_next_pow_of_2(elem_count); + if (elem_count < 2) { + elem_count = 2; + } + if (elem_count != storage->size) { + storage->array = ecs_os_realloc(storage->array, size * elem_count); + storage->size = elem_count; + } + } +} + +void ecs_storage_set_count( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count) +{ + if (storage->count != elem_count) { + if (storage->size < elem_count) { + ecs_storage_set_size(storage, size, elem_count); + } + + storage->count = elem_count; + } +} + +void* ecs_storage_grow( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count) +{ + ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); + int32_t count = storage->count; + ecs_storage_set_count(storage, size, count + elem_count); + return ECS_ELEM(storage->array, size, count); +} + #include static @@ -4613,7 +5049,6 @@ void flecs_notify_on_add( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, - ecs_data_t *data, int32_t row, int32_t count, ecs_table_diff_t *diff, @@ -4624,7 +5059,7 @@ const ecs_entity_t* new_w_data( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, - ecs_ids_t *component_ids, + ecs_type_t *component_ids, int32_t count, void **c_info, bool move, @@ -4638,17 +5073,9 @@ void* get_component_w_index( int32_t row) { ecs_check(column_index < table->storage_count, ECS_NOT_A_COMPONENT, NULL); - - ecs_type_info_t *ti = &table->type_info[column_index]; - ecs_column_t *column = &table->storage.columns[column_index]; - - /* If size is 0, component does not have a value. This is likely caused by - * an application trying to call ecs_get with a tag. */ - int32_t size = ti->size; - ecs_check(size != 0, ECS_INVALID_PARAMETER, NULL); - - void *ptr = ecs_vector_first_t(column->data, size, ti->alignment); - return ECS_OFFSET(ptr, size * row); + ecs_type_info_t *ti = table->type_info[column_index]; + ecs_column_t *column = &table->data.columns[column_index]; + return ecs_storage_get(column, ti->size, row);; error: return NULL; } @@ -4710,7 +5137,7 @@ void* get_base_component( ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + ecs_id_t *ids = type.array; int32_t i = tr_isa->column, end = tr_isa->count + tr_isa->column; void *ptr = NULL; @@ -4752,41 +5179,6 @@ void* get_base_component( return NULL; } -/* Utility to compute actual row from row in record */ -static -void set_row_info( - ecs_entity_info_t *info, - uint32_t row) -{ - info->row = ECS_RECORD_TO_ROW(row); - info->row_flags = ECS_RECORD_TO_ROW_FLAGS(row); -} - -/* Utility to set info from main stage record */ -static -void set_info_from_record( - ecs_entity_info_t *info, - ecs_record_t *record) -{ - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - - info->record = record; - - ecs_table_t *table = record->table; - - set_row_info(info, record->row); - - info->table = table; - if (!info->table) { - return; - } - - info->data = &table->storage; - - ecs_assert(ecs_vector_count(table->storage.entities) > info->row, - ECS_INTERNAL_ERROR, NULL); -} - static const ecs_type_info_t *get_c_info( ecs_world_t *world, @@ -4800,42 +5192,59 @@ const ecs_type_info_t *get_c_info( } } +/* Utilities for creating a diff struct on the fly between two arbitrary tables. + * This is temporary code that will eventually be replaced by a cache that + * stores the diff between two archetypes. */ + +typedef struct { + ecs_type_t type; + ecs_size_t size; +} ecs_type_buffer_t; + +typedef struct { + ecs_type_buffer_t added; + ecs_type_buffer_t removed; + ecs_type_buffer_t on_set; + ecs_type_buffer_t un_set; +} ecs_diff_buffer_t; + static void ids_merge( - ecs_ids_t *ids, - ecs_ids_t *add) + ecs_type_buffer_t *ids, + ecs_type_t *add) { if (!add || !add->count) { return; } - int32_t new_count = ids->count + add->count; + int32_t new_count = ids->type.count + add->count; if (new_count >= ids->size) { ids->size = flecs_next_pow_of_2(new_count); - ecs_id_t *arr = ecs_os_malloc(ids->size * ECS_SIZEOF(ecs_id_t)); - ecs_os_memcpy_n(arr, ids->array, ecs_id_t, ids->count); + ecs_id_t *arr = ecs_os_malloc_n(ecs_id_t, ids->size); + ecs_os_memcpy_n(arr, ids->type.array, ecs_id_t, ids->type.count); - if (ids->count >= ECS_ID_CACHE_SIZE) { - ecs_os_free(ids->array); + if (ids->type.count >= ECS_ID_CACHE_SIZE) { + ecs_os_free(ids->type.array); } - ids->array = arr; + ids->type.array = arr; } - ecs_os_memcpy_n(&ids->array[ids->count], add->array, ecs_id_t, add->count); - ids->count += add->count; + ecs_os_memcpy_n(&ids->type.array[ids->type.count], + add->array, ecs_id_t, add->count); + ids->type.count += add->count; } -#define ECS_TABLE_DIFF_INIT {\ - .added = {.array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}, .size = ECS_ID_CACHE_SIZE},\ - .removed = {.array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}, .size = ECS_ID_CACHE_SIZE},\ - .on_set = {.array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}, .size = ECS_ID_CACHE_SIZE},\ - .un_set = {.array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}, .size = ECS_ID_CACHE_SIZE},\ +#define ECS_DIFF_INIT {\ + .added = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ + .removed = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ + .on_set = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ + .un_set = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ } static void diff_append( - ecs_table_diff_t *dst, + ecs_diff_buffer_t *dst, ecs_table_diff_t *src) { ids_merge(&dst->added, &src->added); @@ -4846,28 +5255,40 @@ void diff_append( static void diff_free( - ecs_table_diff_t *diff) + ecs_diff_buffer_t *diff) { - if (diff->added.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->added.array); + if (diff->added.type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(diff->added.type.array); } - if (diff->removed.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->removed.array); + if (diff->removed.type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(diff->removed.type.array); } - if (diff->on_set.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->on_set.array); + if (diff->on_set.type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(diff->on_set.type.array); } - if (diff->un_set.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->un_set.array); + if (diff->un_set.type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(diff->un_set.type.array); } } +static +ecs_table_diff_t diff_to_table_diff( + ecs_diff_buffer_t *diff) +{ + return (ecs_table_diff_t){ + .added = diff->added.type, + .removed = diff->removed.type, + .on_set = diff->on_set.type, + .un_set = diff->un_set.type + }; +} + static ecs_table_t* table_append( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, - ecs_table_diff_t *diff) + ecs_diff_buffer_t *diff) { ecs_table_diff_t temp_diff; table = flecs_table_traverse_add(world, table, &id, &temp_diff); @@ -4886,7 +5307,7 @@ void notify( int32_t row, int32_t count, ecs_entity_t event, - ecs_ids_t *ids, + ecs_type_t *ids, ecs_entity_t relation) { flecs_emit(world, world, &(ecs_event_desc_t) { @@ -4906,7 +5327,6 @@ void instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, - ecs_data_t *data, int32_t row, int32_t count); @@ -4915,7 +5335,6 @@ void instantiate_children( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, - ecs_data_t *data, int32_t row, int32_t count, ecs_table_t *child_table) @@ -4925,15 +5344,15 @@ void instantiate_children( } ecs_type_t type = child_table->type; - ecs_data_t *child_data = &child_table->storage; + ecs_data_t *child_data = &child_table->data; - ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); - int32_t type_count = ecs_vector_count(type); + ecs_entity_t *ids = type.array; + int32_t type_count = type.count; /* Instantiate child table for each instance */ /* Create component array for creating the table */ - ecs_ids_t components = { + ecs_type_t components = { .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) }; @@ -4959,10 +5378,8 @@ void instantiate_children( int32_t storage_index = ecs_table_type_to_storage_index(child_table, i); if (storage_index != -1) { - ecs_type_info_t *ti = &child_table->type_info[storage_index]; ecs_column_t *column = &child_data->columns[storage_index]; - component_data[pos] = ecs_vector_first_t( - column->data, ti->size, ti->alignment); + component_data[pos] = ecs_storage_first(column); } else { component_data[pos] = NULL; } @@ -4984,12 +5401,12 @@ void instantiate_children( components.count = pos; /* Instantiate the prefab child table for each new instance */ - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - int32_t child_count = ecs_vector_count(child_data->entities); + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); + int32_t child_count = ecs_storage_count(&child_data->entities); for (i = row; i < count + row; i ++) { ecs_entity_t instance = entities[i]; - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; ecs_table_t *i_table = NULL; /* Replace ChildOf element in the component array with instance id */ @@ -5001,14 +5418,13 @@ void instantiate_children( } ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_count(i_table->type) == components.count, + ecs_assert(i_table->type.count == components.count, ECS_INTERNAL_ERROR, NULL); /* The instance is trying to instantiate from a base that is also * its parent. This would cause the hierarchy to instantiate itself * which would cause infinite recursion. */ - ecs_entity_t *children = ecs_vector_first( - child_data->entities, ecs_entity_t); + ecs_entity_t *children = ecs_storage_first(&child_data->entities); #ifdef FLECS_DEBUG for (j = 0; j < child_count; j ++) { @@ -5022,16 +5438,16 @@ void instantiate_children( #endif /* Create children */ - int32_t child_row; + int32_t child_row; + ecs_table_diff_t table_diff = diff_to_table_diff(&diff); new_w_data(world, i_table, NULL, &components, child_count, - component_data, false, &child_row, &diff); + component_data, false, &child_row, &table_diff); diff_free(&diff); /* If prefab child table has children itself, recursively instantiate */ - ecs_data_t *i_data = &i_table->storage; for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; - instantiate(world, child, i_table, i_data, child_row + j, 1); + instantiate(world, child, i_table, child_row + j, 1); } } error: @@ -5043,7 +5459,6 @@ void instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, - ecs_data_t *data, int32_t row, int32_t count) { @@ -5058,7 +5473,7 @@ void instantiate( const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { instantiate_children( - world, base, table, data, row, count, tr->hdr.table); + world, base, table, row, count, tr->hdr.table); } } } @@ -5070,7 +5485,6 @@ bool override_component( ecs_type_t type, ecs_table_t *table, ecs_table_t *other_table, - ecs_data_t *data, const ecs_type_info_t *ti, ecs_column_t *column, int32_t row, @@ -5084,7 +5498,6 @@ bool override_from_base( ecs_entity_t component, ecs_table_t *table, ecs_table_t *other_table, - ecs_data_t *data, const ecs_type_info_t *ti, ecs_column_t *column, int32_t row, @@ -5093,21 +5506,17 @@ bool override_from_base( { ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - ecs_entity_info_t base_info; - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - - if (!flecs_get_info(world, base, &base_info) || !base_info.table) { + ecs_record_t *r = ecs_eis_get(world, base); + ecs_table_t *base_table; + if (!r || !(base_table = r->table)) { return false; } void *base_ptr = get_component( - world, base_info.table, base_info.row, component); + world, base_table, ECS_RECORD_TO_ROW(r->row), component); if (base_ptr) { - int32_t data_size = ti->size; - void *data_ptr = ecs_vector_get_t( - column->data, data_size, ti->alignment, row); - - int32_t index; + int32_t index, data_size = ti->size; + void *data_ptr = ecs_storage_get(column, data_size, row); ecs_copy_t copy = ti->lifecycle.copy; if (copy) { @@ -5122,7 +5531,7 @@ bool override_from_base( } } - ecs_ids_t ids = { + ecs_type_t ids = { .array = (ecs_id_t[]){ component }, .count = 1 }; @@ -5153,9 +5562,9 @@ bool override_from_base( return true; } else { /* If component not found on base, check if base itself inherits */ - ecs_type_t base_type = base_info.table->type; + ecs_type_t base_type = base_table->type; return override_component(world, component, base_type, table, - other_table, data, ti, column, row, count, notify_on_set); + other_table, ti, column, row, count, notify_on_set); } } @@ -5166,15 +5575,14 @@ bool override_component( ecs_type_t type, ecs_table_t *table, ecs_table_t *other_table, - ecs_data_t *data, const ecs_type_info_t *ti, ecs_column_t *column, int32_t row, int32_t count, bool notify_on_set) { - ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t); - int32_t i, type_count = ecs_vector_count(type); + ecs_entity_t *type_array = type.array; + int32_t i, type_count = type.count; /* Walk prefabs */ i = type_count - 1; @@ -5187,8 +5595,7 @@ bool override_component( if (ECS_HAS_RELATION(e, EcsIsA)) { if (override_from_base(world, ecs_pair_second(world, e), component, - table, other_table, data, ti, column, row, count, - notify_on_set)) + table, other_table, ti, column, row, count, notify_on_set)) { return true; } @@ -5203,15 +5610,12 @@ void components_override( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, - ecs_data_t *data, int32_t row, int32_t count, - ecs_ids_t *added, + ecs_type_t *added, bool notify_on_set) { - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_column_t *columns = data->columns; + ecs_column_t *columns = table->data.columns; ecs_type_t type = table->type; ecs_table_t *storage_table = table->storage_table; @@ -5235,7 +5639,7 @@ void components_override( * which would call instantiate multiple times for the same * level in the hierarchy. */ world->stage.base = base; - instantiate(world, base, table, data, row, count); + instantiate(world, base, table, row, count); world->stage.base = 0; } } @@ -5251,7 +5655,7 @@ void components_override( } ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & ECS_ID_DONT_INHERIT) { + if (idr->flags & EcsIdDontInherit) { continue; } @@ -5261,7 +5665,7 @@ void components_override( } ecs_column_t *column = &columns[tr->column]; - override_component(world, id, type, table, other_table, data, ti, + override_component(world, id, type, table, other_table, ti, column, row, count, notify_on_set); } error: @@ -5269,175 +5673,165 @@ void components_override( } static -void set_switch( +void flecs_set_union( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, int32_t row, int32_t count, - ecs_ids_t *entities, + ecs_type_t *ids, bool reset) { - ecs_entity_t *array = entities->array; - int32_t i, comp_count = entities->count; - - for (i = 0; i < comp_count; i ++) { - ecs_entity_t e = array[i]; + ecs_id_t *array = ids->array; + int32_t i, id_count = ids->count; - if (ECS_HAS_ROLE(e, CASE)) { - e = e & ECS_COMPONENT_MASK; + for (i = 0; i < id_count; i ++) { + ecs_id_t id = array[i]; + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_id_record_t *idr = flecs_get_id_record(world, + ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + if (!idr) { + continue; + } - ecs_entity_t sw_case = 0; + const ecs_table_record_t *tr = flecs_id_record_table( + idr, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t column = tr->column - table->sw_offset; + ecs_switch_t *sw = &table->data.sw_columns[column]; + ecs_entity_t union_case = 0; if (!reset) { - sw_case = e; - ecs_assert(sw_case != 0, ECS_INTERNAL_ERROR, NULL); + union_case = ECS_PAIR_SECOND(id); } - int32_t sw_index = flecs_table_switch_from_case(world, table, e); - ecs_assert(sw_index != -1, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = data->sw_columns[sw_index].data; - ecs_assert(sw != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t r; for (r = 0; r < count; r ++) { - flecs_switch_set(sw, row + r, sw_case); + flecs_switch_set(sw, row + r, union_case); } } } } static -void ecs_components_switch( +void flecs_add_remove_union( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, int32_t row, int32_t count, - ecs_ids_t *added, - ecs_ids_t *removed) + ecs_type_t *added, + ecs_type_t *removed) { if (added) { - set_switch(world, table, data, row, count, added, false); + flecs_set_union(world, table, row, count, added, false); } if (removed) { - set_switch(world, table, data, row, count, removed, true); + flecs_set_union(world, table, row, count, removed, true); } } static -int32_t new_entity( +ecs_record_t* new_entity( ecs_world_t *world, ecs_entity_t entity, - ecs_entity_info_t *info, + ecs_record_t *record, ecs_table_t *new_table, ecs_table_diff_t *diff, bool construct, bool notify_on_set) { - ecs_record_t *record = info->record; - ecs_data_t *new_data = &new_table->storage; int32_t new_row; if (!record) { record = ecs_eis_ensure(world, entity); } - new_row = flecs_table_append( - world, new_table, new_data, entity, record, construct); + new_row = flecs_table_append(world, new_table, entity, record, construct); record->table = new_table; record->row = ECS_ROW_TO_RECORD(new_row, record->row & ECS_ROW_FLAGS_MASK); - ecs_assert(ecs_vector_count(new_data[0].entities) > new_row, + ecs_data_t *new_data = &new_table->data; + ecs_assert(ecs_storage_count(&new_data[0].entities) > new_row, ECS_INTERNAL_ERROR, NULL); + (void)new_data; + + ecs_flags32_t flags = new_table->flags; - if (new_table->flags & EcsTableHasAddActions) { + if (flags & EcsTableHasAddActions) { flecs_notify_on_add( - world, new_table, NULL, new_data, new_row, 1, diff, notify_on_set); + world, new_table, NULL, new_row, 1, diff, notify_on_set); } - info->data = new_data; - - return new_row; + return record; } static -int32_t move_entity( +void move_entity( ecs_world_t *world, ecs_entity_t entity, - ecs_entity_info_t *info, - ecs_table_t *src_table, - ecs_data_t *src_data, - int32_t src_row, + ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool construct, bool notify_on_set) -{ - ecs_data_t *dst_data = &dst_table->storage; - ecs_assert(src_data != dst_data, ECS_INTERNAL_ERROR, NULL); +{ + 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_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_count(src_data->entities) > src_row, + ecs_assert(ecs_storage_count(&src_table->data.entities) > src_row, ECS_INTERNAL_ERROR, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record == ecs_eis_get(world, entity), ECS_INTERNAL_ERROR, NULL); - ecs_record_t *record = info->record; - ecs_assert(!record || record == ecs_eis_get(world, entity), - ECS_INTERNAL_ERROR, NULL); - - int32_t dst_row = flecs_table_append(world, dst_table, dst_data, entity, + int32_t dst_row = flecs_table_append(world, dst_table, entity, record, false); - ecs_assert(ecs_vector_count(src_data->entities) > src_row, - ECS_INTERNAL_ERROR, NULL); - /* Copy entity & components from src_table to dst_table */ - if (src_table->type) { - flecs_notify_on_remove( - world, src_table, dst_table, src_row, 1, diff); + if (src_table->type.count) { + flecs_notify_on_remove(world, src_table, dst_table, + ECS_RECORD_TO_ROW(src_row), 1, diff); - flecs_table_move(world, entity, entity, dst_table, dst_data, dst_row, - src_table, src_data, src_row, construct); + flecs_table_move(world, entity, entity, dst_table, dst_row, + src_table, src_row, construct); } /* Update entity index & delete old data after running remove actions */ record->table = dst_table; record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); - flecs_table_delete(world, src_table, src_data, src_row, false); + flecs_table_delete(world, src_table, src_row, false); /* If components were added, invoke add actions */ + ecs_flags32_t dst_flags = dst_table->flags; if (src_table != dst_table || diff->added.count) { - if (diff->added.count && (dst_table->flags & EcsTableHasAddActions)) { - flecs_notify_on_add(world, dst_table, src_table, dst_data, - dst_row, 1, diff, notify_on_set); + if (diff->added.count && (dst_flags & EcsTableHasAddActions)) { + flecs_notify_on_add(world, dst_table, src_table, dst_row, 1, diff, + notify_on_set); } } - info->data = dst_data; - return dst_row; error: - return -1; + return; } static void delete_entity( ecs_world_t *world, - ecs_table_t *src_table, - ecs_data_t *src_data, - int32_t src_row, + ecs_record_t *record, ecs_table_diff_t *diff) -{ - if (src_table) { - /* Invoke remove actions before deleting */ - if (src_table->flags & EcsTableHasRemoveActions) { - flecs_notify_on_remove(world, src_table, NULL, src_row, 1, diff); - } +{ + ecs_table_t *table = record->table; + int32_t row = ECS_RECORD_TO_ROW(record->row); + + /* Invoke remove actions before deleting */ + if (table->flags & EcsTableHasRemoveActions) { + flecs_notify_on_remove(world, table, NULL, row, 1, diff); } - flecs_table_delete(world, src_table, src_data, src_row, true); + flecs_table_delete(world, table, row, true); } /* Updating component monitors is a relatively expensive operation that only @@ -5449,15 +5843,15 @@ void delete_entity( static void update_component_monitor_w_array( ecs_world_t *world, - ecs_ids_t *entities) + ecs_type_t *ids) { - if (!entities) { + if (!ids) { return; } int i; - for (i = 0; i < entities->count; i ++) { - ecs_entity_t id = entities->array[i]; + for (i = 0; i < ids->count; i ++) { + ecs_entity_t id = ids->array[i]; if (ECS_HAS_ROLE(id, PAIR)) { flecs_monitor_mark_dirty(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); @@ -5470,8 +5864,8 @@ void update_component_monitor_w_array( static void update_component_monitors( ecs_world_t *world, - ecs_ids_t *added, - ecs_ids_t *removed) + ecs_type_t *added, + ecs_type_t *removed) { update_component_monitor_w_array(world, added); update_component_monitor_w_array(world, removed); @@ -5481,7 +5875,7 @@ static void commit( ecs_world_t *world, ecs_entity_t entity, - ecs_entity_info_t *info, + ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool construct, @@ -5489,42 +5883,36 @@ void commit( { ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *src_table = info->table; + ecs_table_t *src_table = record ? record->table : NULL; if (src_table == dst_table) { /* If source and destination table are the same no action is needed * * However, if a component was added in the process of traversing a * table, this suggests that a case switch could have occured. */ if (((diff->added.count) || (diff->removed.count)) && - src_table && src_table->flags & EcsTableHasSwitch) + src_table && src_table->flags & EcsTableHasUnion) { - ecs_components_switch( - world, src_table, info->data, info->row, 1, - &diff->added, &diff->removed); + flecs_add_remove_union(world, src_table, + ECS_RECORD_TO_ROW(record->row), 1, + &diff->added, &diff->removed); } return; } if (src_table) { - ecs_data_t *src_data = info->data; ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - if (dst_table->type) { - info->row = move_entity(world, entity, info, src_table, - src_data, info->row, dst_table, diff, construct, notify_on_set); - info->table = dst_table; + if (dst_table->type.count) { + move_entity(world, entity, record, dst_table, diff, + construct, notify_on_set); } else { - delete_entity(world, src_table, src_data, info->row, diff); - - ecs_eis_set(world, entity, &(ecs_record_t){ - NULL, info->row_flags - }); + delete_entity(world, record, diff); + record->table = NULL; } } else { - if (dst_table->type) { - info->row = new_entity( - world, entity, info, dst_table, diff, construct, notify_on_set); - info->table = dst_table; + if (dst_table->type.count) { + record = new_entity(world, entity, record, dst_table, diff, + construct, notify_on_set); } } @@ -5533,11 +5921,11 @@ void 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 (info->row_flags) { + if (record->row & ECS_ROW_FLAGS_MASK) { update_component_monitors(world, &diff->added, &diff->removed); } - if ((!src_table || !src_table->type) && world->range_check_enabled) { + if ((!src_table || !src_table->type.count) && world->range_check_enabled) { ecs_check(!world->info.max_id || entity <= world->info.max_id, ECS_OUT_OF_RANGE, 0); ecs_check(entity >= world->info.min_id, @@ -5551,19 +5939,18 @@ static void new( ecs_world_t *world, ecs_entity_t entity, - ecs_ids_t *to_add) + ecs_type_t *to_add) { - ecs_entity_info_t info = {0}; int32_t i, count = to_add->count; ecs_table_t *table = &world->store.root; - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; for (i = 0; i < count; i ++) { table = table_append(world, table, to_add->array[i], &diff); } - new_entity(world, entity, &info, table, &diff, true, true); - + ecs_table_diff_t table_diff = diff_to_table_diff(&diff); + new_entity(world, entity, NULL, table, &table_diff, true, true); diff_free(&diff); } @@ -5572,7 +5959,7 @@ const ecs_entity_t* new_w_data( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, - ecs_ids_t *component_ids, + ecs_type_t *component_ids, int32_t count, void **component_data, bool is_move, @@ -5590,26 +5977,25 @@ const ecs_entity_t* new_w_data( } ecs_type_t type = table->type; - if (!type) { + if (!type.count) { return entities; } - ecs_ids_t component_array = { 0 }; + ecs_type_t component_array = { 0 }; if (!component_ids) { component_ids = &component_array; - component_array.array = ecs_vector_first(type, ecs_entity_t); - component_array.count = ecs_vector_count(type); + component_array.array = type.array; + component_array.count = type.count; } - ecs_data_t *data = &table->storage; + ecs_data_t *data = &table->data; int32_t row = flecs_table_appendn(world, table, data, count, entities); /* Update entity index. */ int i; - ecs_record_t **record_ptrs = ecs_vector_first( - data->record_ptrs, ecs_record_t*); + ecs_record_t **records = ecs_storage_first(&data->records); for (i = 0; i < count; i ++) { - record_ptrs[row + i] = ecs_eis_set(world, entities[i], + records[row + i] = ecs_eis_set(world, entities[i], &(ecs_record_t){ .table = table, .row = ECS_ROW_TO_RECORD(row + i, 0) @@ -5618,7 +6004,7 @@ const ecs_entity_t* new_w_data( flecs_defer_none(world, &world->stage); - flecs_notify_on_add(world, table, NULL, data, row, count, diff, + flecs_notify_on_add(world, table, NULL, row, count, diff, component_data == NULL); if (component_data) { @@ -5641,14 +6027,11 @@ const ecs_entity_t* new_w_data( ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t index = tr->column; - ecs_type_info_t *ti = &table->type_info[index]; - ecs_column_t *column = &table->storage.columns[index]; + ecs_type_info_t *ti = table->type_info[index]; + ecs_column_t *column = &table->data.columns[index]; int32_t size = ti->size; - int32_t alignment = ti->alignment; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - - void *ptr = ecs_vector_first_t(column->data, size, alignment); - ptr = ECS_OFFSET(ptr, size * row); + void *ptr = ecs_storage_get(column, size, row); ecs_copy_t copy; ecs_move_t move; @@ -5680,20 +6063,24 @@ const ecs_entity_t* new_w_data( } static -void add_id_w_info( +void add_id_w_record( ecs_world_t *world, ecs_entity_t entity, - ecs_entity_info_t *info, + ecs_record_t *record, ecs_id_t id, bool construct) { ecs_table_diff_t diff; - ecs_table_t *src_table = info->table; + ecs_table_t *src_table = NULL; + if (record) { + src_table = record->table; + } + ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); - commit(world, entity, info, dst_table, &diff, construct, + commit(world, entity, record, dst_table, &diff, construct, false); /* notify_on_set = false, this function is only called from * functions that are about to set the component. */ } @@ -5710,15 +6097,13 @@ void add_id( return; } - ecs_entity_info_t info; - flecs_get_info(world, entity, &info); - + ecs_record_t *r = ecs_eis_ensure(world, entity); ecs_table_diff_t diff; - ecs_table_t *src_table = info.table; + ecs_table_t *src_table = r->table; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); - commit(world, entity, &info, dst_table, &diff, true, true); + commit(world, entity, r, dst_table, &diff, true, true); flecs_defer_flush(world, stage); } @@ -5735,16 +6120,19 @@ void remove_id( return; } - ecs_entity_info_t info; - flecs_get_info(world, entity, &info); + ecs_record_t *r = ecs_eis_get(world, entity); + ecs_table_t *src_table = NULL; + if (!r || !(src_table = r->table)) { + goto done; /* Nothing to remove */ + } ecs_table_diff_t diff; - ecs_table_t *src_table = info.table; ecs_table_t *dst_table = flecs_table_traverse_remove( world, src_table, &id, &diff); - commit(world, entity, &info, dst_table, &diff, true, true); + commit(world, entity, r, dst_table, &diff, true, true); +done: flecs_defer_flush(world, stage); } @@ -5752,43 +6140,46 @@ static void *get_mutable( ecs_world_t *world, ecs_entity_t entity, - ecs_entity_t component, - ecs_entity_info_t *info, + ecs_entity_t id, + ecs_record_t *r, bool *is_added) { + void *dst = NULL; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(component != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check((component & ECS_COMPONENT_MASK) == component || - ECS_HAS_ROLE(component, PAIR), ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check((id & ECS_COMPONENT_MASK) == id || + ECS_HAS_ROLE(id, PAIR), ECS_INVALID_PARAMETER, NULL); - void *dst = NULL; - if (flecs_get_info(world, entity, info) && info->table) { - dst = get_component(world, info->table, info->row, component); + if (r->table) { + dst = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } if (!dst) { - ecs_table_t *table = info->table; - add_id_w_info(world, entity, info, component, true); - flecs_get_info(world, entity, info); - - ecs_assert(info->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(info->table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); - dst = get_component(world, info->table, info->row, component); + /* If entity didn't have component yet, add it */ + add_id_w_record(world, entity, r, id, true); - if (is_added) { - *is_added = table != info->table; - } + /* Flush commands so the pointer we're fetching is stable */ + ecs_defer_end(world); + ecs_defer_begin(world); + + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); + dst = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); - return dst; - } else { if (is_added) { - *is_added = false; + *is_added = true; } return dst; } + + if (is_added) { + *is_added = false; + } error: - return NULL; + return dst; } /* -- Private functions -- */ @@ -5797,7 +6188,6 @@ void flecs_notify_on_add( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, - ecs_data_t *data, int32_t row, int32_t count, ecs_table_diff_t *diff, @@ -5807,13 +6197,12 @@ void flecs_notify_on_add( if (diff->added.count) { if (table->flags & EcsTableHasIsA) { - components_override(world, table, other_table, data, row, count, + components_override(world, table, other_table, row, count, &diff->added, run_on_set); } - if (table->flags & EcsTableHasSwitch) { - ecs_components_switch( - world, table, data, row, count, &diff->added, NULL); + if (table->flags & EcsTableHasUnion) { + flecs_add_remove_union(world, table, row, count, &diff->added, NULL); } if (table->flags & EcsTableHasOnAdd) { @@ -5863,19 +6252,18 @@ void flecs_notify_on_set( ecs_table_t *table, int32_t row, int32_t count, - ecs_ids_t *ids, + ecs_type_t *ids, bool owned) { - ecs_data_t *data = &table->storage; + ecs_data_t *data = &table->data; - ecs_entity_t *entities = ecs_vector_get(data->entities, ecs_entity_t, row); + ecs_entity_t *entities = ecs_storage_get_t( + &data->entities, ecs_entity_t, row); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row < ecs_vector_count(data->entities), - ECS_INTERNAL_ERROR, NULL); - ecs_assert((row + count) <= ecs_vector_count(data->entities), + ecs_assert((row + count) <= ecs_storage_count(&data->entities), ECS_INTERNAL_ERROR, NULL); - ecs_ids_t local_ids; + ecs_type_t local_ids; if (!ids) { local_ids.array = table->storage_ids; local_ids.count = table->storage_count; @@ -5892,12 +6280,12 @@ void flecs_notify_on_set( ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t column = tr->column; - const ecs_type_info_t *ti = &table->type_info[column]; + const ecs_type_info_t *ti = table->type_info[column]; ecs_iter_action_t on_set = ti->lifecycle.on_set; if (on_set) { - ecs_column_t *c = &table->storage.columns[column]; + ecs_column_t *c = &table->data.columns[column]; ecs_size_t size = ti->size; - void *ptr = ecs_vector_get_t(c->data, size, ti->alignment, row); + void *ptr = ecs_storage_get(c, size, row); ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_iter_t it = {.term_count = 1}; @@ -5907,7 +6295,6 @@ void flecs_notify_on_set( it.world = world; it.real_world = world; it.table = table; - it.type = table->type; it.ptrs[0] = ptr; it.sizes[0] = size; it.ids[0] = id; @@ -5927,31 +6314,6 @@ void flecs_notify_on_set( } } -bool flecs_get_info( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_info_t *info) -{ - info->table = NULL; - info->record = NULL; - info->data = NULL; - info->row_flags = 0; - - if (entity & ECS_ROLE) { - return false; - } - - ecs_record_t *record = ecs_eis_get(world, entity); - - if (!record) { - return false; - } - - set_info_from_record(info, record); - - return true; -} - uint32_t flecs_record_to_row( uint32_t row, bool *is_watched_out) @@ -5964,25 +6326,14 @@ uint32_t flecs_row_to_record( uint32_t row, bool is_watched) { - return row | (ECS_FLAG_OBSERVED * is_watched); -} - -ecs_ids_t flecs_type_to_ids( - ecs_type_t type) -{ - return (ecs_ids_t){ - .array = ecs_vector_first(type, ecs_entity_t), - .count = ecs_vector_count(type) - }; + return row | (EcsEntityObserved * is_watched); } void flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag) -{ - (void)world; - +{ ecs_record_t *record = ecs_eis_get(world, entity); if (!record) { ecs_record_t new_record = {.row = flag, .table = NULL}; @@ -6000,8 +6351,8 @@ bool ecs_commit( ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, - ecs_ids_t *added, - ecs_ids_t *removed) + const ecs_type_t *added, + const ecs_type_t *removed) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); @@ -6011,12 +6362,9 @@ bool ecs_commit( src_table = record->table; } - ecs_entity_info_t info = {0}; - if (record) { - set_info_from_record(&info, record); - } + ecs_table_diff_t diff; + ecs_os_zeromem(&diff); - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; if (added) { diff.added = *added; } @@ -6024,7 +6372,7 @@ bool ecs_commit( diff.added = *removed; } - commit(world, entity, &info, table, &diff, true, true); + commit(world, entity, record, table, &diff, true, true); return src_table != table; error: @@ -6131,7 +6479,7 @@ ecs_entity_t ecs_new_w_id( ecs_entity_t entity = ecs_new_id(world); ecs_id_t ids[3]; - ecs_ids_t to_add = { .array = ids, .count = 0 }; + ecs_type_t to_add = { .array = ids, .count = 0 }; if (id) { ids[to_add.count ++] = id; @@ -6182,7 +6530,7 @@ ecs_table_t *traverse_from_expr( ecs_table_t *table, const char *name, const char *expr, - ecs_table_diff_t *diff, + ecs_diff_buffer_t *diff, bool replace_and, bool *error) { @@ -6222,12 +6570,6 @@ ecs_table_t *traverse_from_expr( return NULL; } - if (term.role == ECS_CASE) { - table = table_append(world, table, - ECS_SWITCH | ECS_PAIR_FIRST(term.id), diff); - term.id = ECS_CASE | ECS_PAIR_SECOND(term.id); - } - if (term.oper == EcsAnd || !replace_and) { /* Regular AND expression */ table = table_append(world, table, term.id, diff); @@ -6243,8 +6585,8 @@ ecs_table_t *traverse_from_expr( return NULL; } - ecs_id_t *ids = ecs_vector_first(t->normalized->type, ecs_id_t); - int32_t i, count = ecs_vector_count(t->normalized->type); + ecs_id_t *ids = t->normalized->type.array; + int32_t i, count =t->normalized->type.count; for (i = 0; i < count; i ++) { table = table_append(world, table, ids[i], diff); } @@ -6309,8 +6651,8 @@ void defer_from_expr( return; } - ecs_id_t *ids = ecs_vector_first(t->normalized->type, ecs_id_t); - int32_t i, count = ecs_vector_count(t->normalized->type); + ecs_id_t *ids = t->normalized->type.array; + int32_t i, count = t->normalized->type.count; for (i = 0; i < count; i ++) { if (is_add) { ecs_add_id(world, entity, ids[i]); @@ -6343,16 +6685,17 @@ int traverse_add( const char *root_sep = desc->root_sep; /* Find existing table */ - ecs_entity_info_t info = {0}; ecs_table_t *src_table = NULL, *table = NULL; + ecs_record_t *r = NULL; if (!new_entity) { - if (flecs_get_info(world, result, &info)) { - table = info.table; + r = ecs_eis_get(world, result); + if (r) { + table = r->table; } } /* Find destination table */ - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ @@ -6410,7 +6753,8 @@ int traverse_add( /* Commit entity to destination table */ if (src_table != table) { ecs_defer_begin(world); - commit(world, result, &info, table, &diff, true, true); + ecs_table_diff_t table_diff = diff_to_table_diff(&diff); + commit(world, result, r, table, &table_diff, true, true); ecs_defer_end(world); } @@ -6607,7 +6951,7 @@ ecs_entity_t ecs_entity_init( result = ecs_new_id(world); } new_entity = true; - ecs_assert(ecs_get_type(world, result) == NULL, + ecs_assert(ecs_get_type(world, result) == NULL, ECS_INTERNAL_ERROR, NULL); } } else { @@ -6673,9 +7017,9 @@ const ecs_entity_t* ecs_bulk_init( } } - ecs_ids_t ids; + ecs_type_t ids; ecs_table_t *table = desc->table; - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; if (!table) { int32_t i = 0; ecs_id_t id; @@ -6687,14 +7031,14 @@ const ecs_entity_t* ecs_bulk_init( ids.array = (ecs_id_t*)desc->ids; ids.count = i; } else { - diff.added.array = ecs_vector_first(table->type, ecs_id_t); - diff.added.count = ecs_vector_count(table->type); - - ids = diff.added; + diff.added.type.array = table->type.array; + diff.added.type.count = table->type.count; + ids = diff.added.type; } - new_w_data( - world, table, entities, &ids, count, desc->data, true, NULL, &diff); + ecs_table_diff_t table_diff = diff_to_table_diff(&diff); + new_w_data(world, table, entities, &ids, count, desc->data, true, NULL, + &table_diff); if (!sparse_count) { return entities; @@ -6759,7 +7103,7 @@ ecs_entity_t ecs_component_init( } /* Ensure components cannot be deleted */ - ecs_add_pair(world, result, EcsOnDelete, EcsThrow); + ecs_add_pair(world, result, EcsOnDelete, EcsPanic); flecs_resume_readonly(world, &readonly_state); @@ -6785,7 +7129,8 @@ ecs_entity_t ecs_type_init( } ecs_table_t *table = NULL, *normalized = NULL; - ecs_table_diff_t temp_diff, diff = ECS_TABLE_DIFF_INIT; + ecs_table_diff_t temp_diff; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; /* Find destination table (and type) */ @@ -6824,10 +7169,10 @@ ecs_entity_t ecs_type_init( diff_free(&diff); - ecs_type_t type = NULL; + ecs_type_t *type = NULL; if (table) { - type = table->type; + type = &table->type; } bool add = false; @@ -6868,13 +7213,14 @@ const ecs_entity_t* ecs_bulk_new_w_id( } ecs_table_t *table = &world->store.root; - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; if (id) { table = table_append(world, table, id, &diff); } - ids = new_w_data(world, table, NULL, NULL, count, NULL, false, NULL, &diff); + ecs_table_diff_t td = diff_to_table_diff(&diff); + ids = new_w_data(world, table, NULL, NULL, count, NULL, false, NULL, &td); flecs_defer_flush(world, stage); return ids; @@ -6894,21 +7240,20 @@ void ecs_clear( return; } - ecs_entity_info_t info; - info.table = NULL; - - flecs_get_info(world, entity, &info); + ecs_record_t *r = ecs_eis_get(world, entity); + if (!r) { + return; /* Nothing to clear */ + } - ecs_table_t *table = info.table; + ecs_table_t *table = r->table; if (table) { ecs_table_diff_t diff = { - .removed = flecs_type_to_ids(table->type), - .un_set = { table->storage_ids, table->storage_count, 0 } + .removed = table->type, + .un_set = { table->storage_ids, table->storage_count } }; - delete_entity(world, table, &table->storage, info.row, &diff); - info.record->table = NULL; - info.record->row = 0; + delete_entity(world, r, &diff); + r->table = NULL; } flecs_defer_flush(world, stage); @@ -6942,9 +7287,10 @@ void remove_from_table( int32_t column, int32_t column_count) { - ecs_table_diff_t temp_diff, diff = ECS_TABLE_DIFF_INIT; + ecs_table_diff_t temp_diff; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; ecs_table_t *dst_table = src_table; - ecs_id_t *ids = ecs_vector_first(src_table->type, ecs_id_t); + ecs_id_t *ids = src_table->type.array; /* If id is pair but the column pointed to is not a pair, the record is * pointing to an instance of the id that has a (non-PAIR) role. */ @@ -6954,7 +7300,7 @@ void remove_from_table( ECS_INTERNAL_ERROR, NULL); bool is_wildcard = ecs_id_is_wildcard(id); - int32_t i, count = ecs_vector_count(src_table->type), removed_count = 0; + int32_t i, count = src_table->type.count, removed_count = 0; ecs_entity_t entity = ECS_PAIR_FIRST(id); for (i = column; i < count; i ++) { @@ -6980,7 +7326,7 @@ void remove_from_table( ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!dst_table->type) { + if (!dst_table->type.count) { ecs_dbg_3("clear entities from table %u", (uint32_t)src_table->id); /* If this removes all components, clear table */ flecs_table_clear_entities(world, src_table); @@ -6990,14 +7336,15 @@ void remove_from_table( (uint32_t)dst_table->id); /* Otherwise, merge table into dst_table */ if (dst_table != src_table) { - ecs_data_t *src_data = &src_table->storage; + ecs_data_t *src_data = &src_table->data; int32_t src_count = ecs_table_count(src_table); - if (diff.removed.count) { + if (diff.removed.type.count) { + ecs_table_diff_t td = diff_to_table_diff(&diff); flecs_notify_on_remove(world, src_table, NULL, - 0, src_count, &diff); + 0, src_count, &td); } flecs_table_merge(world, dst_table, src_table, - &dst_table->storage, src_data); + &dst_table->data, src_data); } } @@ -7009,14 +7356,12 @@ void delete_objects( ecs_world_t *world, ecs_table_t *table) { - ecs_data_t *data = &table->storage; + ecs_data_t *data = &table->data; if (data) { - ecs_entity_t *entities = ecs_vector_first( - data->entities, ecs_entity_t); - ecs_record_t **records = ecs_vector_first( - data->record_ptrs, ecs_record_t*); + ecs_entity_t *entities = ecs_storage_first(&data->entities); + ecs_record_t **records = ecs_storage_first(&data->records); - int32_t i, count = ecs_vector_count(data->entities); + int32_t i, count = ecs_storage_count(&data->entities); for (i = 0; i < count; i ++) { ecs_record_t *r = records[i]; @@ -7043,7 +7388,7 @@ void on_delete_object_action( ecs_entity_t action) { ecs_table_cache_iter_t it; - ecs_id_record_t *idr = flecs_get_id_record(world, id); + ecs_id_record_t *idrr, *idr = flecs_get_id_record(world, id); if (idr) { bool deleted; @@ -7052,7 +7397,7 @@ void on_delete_object_action( deleted = false; /* Make sure records are in the right list (empty/non-empty) */ - ecs_force_aperiodic(world); + ecs_run_aperiodic(world, EcsAperiodicEmptyTableEvents); if (!flecs_table_cache_iter(&idr->cache, &it)) { continue; @@ -7060,7 +7405,7 @@ void on_delete_object_action( /* First move entities to tables without the id (action = Remove) or * delete entities with id (action = Delete) */ - const ecs_table_record_t *tr; + const ecs_table_record_t *trr, *tr; if ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { do { ecs_table_t *table = tr->hdr.table; @@ -7076,27 +7421,14 @@ void on_delete_object_action( * could happen if store contains cyclic relationships */ flecs_table_claim(world, table); - /* If store contains cyclic relationships it's possible that - * a table we were about to cleanup already got emptied */ - ecs_id_t *rel_id = ecs_vector_get( - table->type, ecs_id_t, tr->column); - ecs_assert(rel_id != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_entity_t rel = ECS_PAIR_FIRST(*rel_id); - /* delete_object_action should be invoked for pairs */ - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - /* Initialize with original value in case action = 0 */ ecs_entity_t cur_action = action; /* Find delete action for relation */ if (!cur_action) { - ecs_id_record_t *idrr = flecs_get_id_record( - world, rel); - if (idrr) { - cur_action = - ECS_ID_ON_DELETE_OBJECT(idrr->flags); - } + trr = &table->records[tr->column]; + idrr = (ecs_id_record_t*)trr->hdr.cache; + cur_action = ECS_ID_ON_DELETE_OBJECT(idrr->flags); } if (!cur_action || cur_action == EcsRemove) { @@ -7108,10 +7440,14 @@ void on_delete_object_action( /* Delete actions can cause cyclic cleanup, which * requires special attention */ deleted = true; - } else if (cur_action == EcsThrow) { + } else if (cur_action == EcsPanic) { throw_invalid_delete(world, id); } + /* Merge operations before proceeding */ + ecs_defer_end(world); + ecs_defer_begin(world); + ecs_log_pop_3(); /* It is possible that the current record has been moved to @@ -7171,7 +7507,7 @@ void on_delete_id_action( action = ECS_ID_ON_DELETE(idr->flags); } - if (action == EcsThrow) { + if (action == EcsPanic) { throw_invalid_delete(world, id); } @@ -7232,11 +7568,11 @@ void on_delete_any_w_entity( uint32_t flags) { /* Make sure any references to the entity are cleaned up */ - if (flags & ECS_FLAG_OBSERVED_ID) { + if (flags & EcsEntityObservedId) { on_delete_action(world, e, action); on_delete_action(world, ecs_pair(e, EcsWildcard), action); } - if (flags & ECS_FLAG_OBSERVED_OBJECT) { + if (flags & EcsEntityObservedObject) { on_delete_action(world, ecs_pair(EcsWildcard, e), action); } } @@ -7282,28 +7618,26 @@ void ecs_delete( ecs_record_t *r = flecs_sparse_get( ecs_eis(world), ecs_record_t, entity); if (r) { - ecs_entity_info_t info = {0}; - set_info_from_record(&info, r); - - ecs_table_t *table = info.table; + ecs_table_t *table = r->table; uint64_t table_id = 0; if (table) { table_id = table->id; } - if (info.row_flags) { + ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); + if (row_flags) { /* Prevent infinite recursion in case of cyclic delete actions */ r->row &= ECS_ROW_MASK; /* Ensure that the store contains no dangling references to the * deleted entity (as a component, or as part of a relation) */ - on_delete_any_w_entity(world, entity, 0, info.row_flags); + on_delete_any_w_entity(world, entity, 0, row_flags); - /* Refetch data. In case of circular relations, the entity may have - * moved to a different table. */ - set_info_from_record(&info, r); + /* Merge operations before deleting entity */ + ecs_defer_end(world); + ecs_defer_begin(world); - table = info.table; + table = r->table; if (table) { table_id = table->id; } else { @@ -7311,7 +7645,7 @@ void ecs_delete( } if (r->table) { - ecs_ids_t to_remove = flecs_type_to_ids(r->table->type); + ecs_type_t to_remove = r->table->type; update_component_monitors(world, NULL, &to_remove); } } @@ -7322,11 +7656,11 @@ void ecs_delete( * as delete actions could have deleted the table already. */ if (table_id && flecs_sparse_is_alive(&world->store.tables, table_id)) { ecs_table_diff_t diff = { - .removed = flecs_type_to_ids(table->type), - .un_set = { table->storage_ids, table->storage_count, 0 } + .removed = table->type, + .un_set = { table->storage_ids, table->storage_count } }; - delete_entity(world, table, info.data, info.row, &diff); + delete_entity(world, r, &diff); r->table = NULL; } @@ -7395,26 +7729,22 @@ ecs_entity_t ecs_clone( return dst; } - ecs_entity_info_t src_info; - bool found = flecs_get_info(world, src, &src_info); - ecs_table_t *src_table = src_info.table; - - if (!found || !src_table) { + ecs_record_t *src_r = ecs_eis_get(world, src); + ecs_table_t *src_table; + if (!src_r || !(src_table = src_r->table)) { goto done; } ecs_type_t src_type = src_table->type; - ecs_table_diff_t diff = {.added = flecs_type_to_ids(src_type)}; - - ecs_entity_info_t dst_info = {0}; - dst_info.row = new_entity(world, dst, &dst_info, src_table, &diff, - true, true); + ecs_table_diff_t diff = { .added = src_type }; + ecs_record_t *dst_r = new_entity(world, dst, NULL, src_table, &diff, true, true); + int32_t row = ECS_RECORD_TO_ROW(dst_r->row); if (copy_value) { - flecs_table_move(world, dst, src, src_table, dst_info.data, - dst_info.row, src_table, src_info.data, src_info.row, true); + flecs_table_move(world, dst, src, src_table, + row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); - flecs_notify_on_set(world, src_table, dst_info.row, 1, NULL, true); + flecs_notify_on_set(world, src_table, row, 1, NULL, true); } done: @@ -7471,62 +7801,6 @@ const void* ecs_get_id( return NULL; } -const void* ecs_get_ref_id( - const ecs_world_t *world, - ecs_ref_t *ref, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!entity || !ref->entity || entity == ref->entity, - ECS_INVALID_PARAMETER, NULL); - ecs_check(!id || !ref->component || id == ref->component, - ECS_INVALID_PARAMETER, NULL); - ecs_record_t *record = ref->record; - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - - entity |= ref->entity; - - if (!record) { - record = ecs_eis_get(world, entity); - } - - if (!record || !record->table) { - goto error; - } - - ecs_table_t *table = record->table; - - if (ref->record == record && - ref->table == table && - ref->row == record->row && - ref->alloc_count == table->alloc_count) - { - return ref->ptr; - } - - id |= ref->component; - - uint32_t row = record->row; - ref->entity = entity; - ref->component = id; - ref->table = table; - ref->row = row; - ref->alloc_count = table->alloc_count; - - if (table && id) { - ref->ptr = get_component(world, table, ECS_RECORD_TO_ROW(row), id); - } - - ref->record = record; - return ref->ptr; -error: - return NULL; -} - void* ecs_get_mut_id( ecs_world_t *world, ecs_entity_t entity, @@ -7546,40 +7820,77 @@ void* ecs_get_mut_id( return result; } - ecs_entity_info_t info; - result = get_mutable(world, entity, id, &info, is_added); + ecs_record_t *r = ecs_eis_ensure(world, entity); + result = get_mutable(world, entity, id, r, is_added); ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); - /* Store table so we can quickly check if returned pointer is still valid */ - ecs_table_t *table = info.record->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_defer_flush(world, stage); + + return result; +error: + return NULL; +} - /* Keep track of alloc count of table, since even if the entity has not - * moved, other entities could have been added to the table which could - * reallocate arrays. Also store the row, as the entity could have - * reallocated. */ - int32_t alloc_count = table->alloc_count; - uint32_t row = info.record->row; +ecs_ref_t ecs_ref_init_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - flecs_defer_flush(world, stage); + world = ecs_get_world(world); - /* Ensure that after flushing, the pointer is still valid. Flushing may - * trigger callbacks, which could do anything with the entity */ - if (table != info.record->table || - alloc_count != info.record->table->alloc_count || - row != info.record->row) - { - if (flecs_get_info(world, entity, &info) && info.table) { - result = get_component(world, info.table, info.row, id); - } else { - /* A trigger has removed the component we just added. This is not - * allowed, an application should always be able to assume that - * get_mut returns a valid pointer. */ - ecs_throw(ECS_INVALID_OPERATION, NULL); - } + ecs_record_t *record = ecs_eis_get(world, entity); + ecs_check(record != NULL, ECS_INVALID_PARAMETER, + "cannot create ref for empty entity"); + + ecs_ref_t result = { + .entity = entity, + .id = id, + .record = record + }; + + ecs_table_t *table = record->table; + if (table) { + result.tr = flecs_get_table_record(world, table->storage_table, id); } return result; +error: + return (ecs_ref_t){0}; +} + +void* ecs_ref_get_id( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id == ref->id, ECS_INVALID_PARAMETER, NULL); + + ecs_record_t *r = ref->record; + ecs_table_t *table = r->table; + if (!table) { + return NULL; + } + + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + + ecs_table_record_t *tr = ref->tr; + if (!tr || tr->hdr.table != table) { + tr = ref->tr = flecs_get_table_record(world, table->storage_table, id); + if (!tr) { + return NULL; + } + } + + return get_component_w_index(table, tr->column, row); error: return NULL; } @@ -7603,11 +7914,10 @@ void* ecs_emplace_id( return result; } - ecs_entity_info_t info = {0}; - flecs_get_info(world, entity, &info); - add_id_w_info(world, entity, &info, id, false /* Add without ctor */); + ecs_record_t *r = ecs_eis_ensure(world, entity); + add_id_w_record(world, entity, r, id, false /* Add without ctor */); - void *ptr = get_component(world, info.table, info.row, id); + void *ptr = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_flush(world, stage); @@ -7638,20 +7948,19 @@ void ecs_modified_id( * operations are being deferred. */ ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); - ecs_entity_info_t info = {0}; - if (flecs_get_info(world, entity, &info)) { - ecs_ids_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set(world, info.table, info.row, 1, &ids, true); - } + ecs_record_t *r = ecs_eis_get(world, entity); + ecs_table_t *table = r->table; + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); - flecs_table_mark_dirty(world, info.table, id); + flecs_table_mark_dirty(world, table, id); flecs_defer_flush(world, stage); error: return; } static -ecs_entity_t assign_ptr_w_id( +ecs_entity_t set_ptr_w_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, @@ -7676,9 +7985,8 @@ ecs_entity_t assign_ptr_w_id( return entity; } - ecs_entity_info_t info; - - void *dst = get_mutable(world, entity, id, &info, NULL); + ecs_record_t *r = ecs_eis_ensure(world, entity); + void *dst = get_mutable(world, entity, id, r, NULL); ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); if (ptr) { @@ -7707,11 +8015,12 @@ ecs_entity_t assign_ptr_w_id( memset(dst, 0, size); } - flecs_table_mark_dirty(world, info.table, id); + flecs_table_mark_dirty(world, r->table, id); if (notify) { - ecs_ids_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set(world, info.table, info.row, 1, &ids, true); + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set( + world, r->table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } flecs_defer_flush(world, stage); @@ -7733,47 +8042,12 @@ ecs_entity_t ecs_set_id( ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); /* Safe to cast away const: function won't modify if move arg is false */ - return assign_ptr_w_id( + return set_ptr_w_id( world, entity, id, size, (void*)ptr, false, true); error: return 0; } -ecs_entity_t ecs_get_case( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t sw_id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, sw_id), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_entity_info_t info; - ecs_table_t *table; - if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { - return 0; - } - - sw_id = sw_id | ECS_SWITCH; - - int32_t index = ecs_search(world, table, sw_id, 0); - if (index == -1) { - return 0; - } - - index -= table->sw_column_offset; - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - - /* Data cannot be NULl, since entity is stored in the table */ - ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = info.data->sw_columns[index].data; - return flecs_switch_get(sw, info.row); -error: - return 0; -} - void ecs_enable_component_w_id( ecs_world_t *world, ecs_entity_t entity, @@ -7795,12 +8069,10 @@ void ecs_enable_component_w_id( stage->defer --; } - ecs_entity_info_t info; - flecs_get_info(world, entity, &info); - + ecs_record_t *r = ecs_eis_ensure(world, entity); ecs_entity_t bs_id = (id & ECS_COMPONENT_MASK) | ECS_DISABLED; - ecs_table_t *table = info.table; + ecs_table_t *table = r->table; int32_t index = -1; if (table) { index = ecs_search(world, table, bs_id, 0); @@ -7812,15 +8084,14 @@ void ecs_enable_component_w_id( return; } - index -= table->bs_column_offset; + index -= table->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Data cannot be NULl, since entity is stored in the table */ - ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &info.data->bs_columns[index].data; + ecs_bitset_t *bs = &table->data.bs_columns[index]; ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_bitset_set(bs, info.row, enable); + flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); error: return; } @@ -7837,9 +8108,9 @@ bool ecs_is_component_enabled_w_id( /* Make sure we're not working with a stage */ world = ecs_get_world(world); - ecs_entity_info_t info; + ecs_record_t *r = ecs_eis_get(world, entity); ecs_table_t *table; - if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { + if (!r || !(table = r->table)) { return false; } @@ -7851,14 +8122,11 @@ bool ecs_is_component_enabled_w_id( return ecs_has_id(world, entity, id); } - index -= table->bs_column_offset; + index -= table->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &table->data.bs_columns[index]; - /* Data cannot be NULl, since entity is stored in the table */ - ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &info.data->bs_columns[index].data; - - return flecs_bitset_get(bs, info.row); + return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: return false; } @@ -7875,42 +8143,33 @@ bool ecs_has_id( /* Make sure we're not working with a stage */ world = ecs_get_world(world); - if (ECS_HAS_ROLE(id, CASE)) { - ecs_entity_info_t info; - ecs_table_t *table; - if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { - return false; - } - - int32_t index = flecs_table_switch_from_case(world, table, id); - if (index == -1) { - /* If table has IsA relationship, traverse */ - if (table->flags & EcsTableHasIsA) { - int32_t oi = 0; - ecs_entity_t b = 0; - while ((b = ecs_get_object(world, entity, EcsIsA, oi ++))) { - return ecs_has_id(world, b, id); - } - } - return false; - } + ecs_record_t *r = ecs_eis_get(world, entity); + ecs_table_t *table; + if (!r || !(table = r->table)) { + return false; + } - ecs_assert(index < table->sw_column_count, ECS_INTERNAL_ERROR, NULL); - - ecs_data_t *data = info.data; - ecs_switch_t *sw = data->sw_columns[index].data; - ecs_entity_t value = flecs_switch_get(sw, info.row); + ecs_table_record_t *tr = NULL; + int32_t column = ecs_search_relation( + world, table, 0, id, EcsIsA, 0, 0, 0, 0, 0, &tr); + if (column == -1) { + return false; + } - return value == (id & ECS_COMPONENT_MASK); - } else { - ecs_table_t *table = ecs_get_table(world, entity); - if (!table) { - return false; + table = tr->hdr.table; + if ((table->flags & EcsTableHasUnion) && ECS_HAS_ROLE(id, PAIR) && + ECS_PAIR_SECOND(id) != EcsWildcard) + { + if (ECS_PAIR_FIRST(table->type.array[column]) == EcsUnion) { + ecs_switch_t *sw = &table->data.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); } - - return ecs_search_relation( - world, table, 0, id, EcsIsA, 0, 0, 0, 0, 0, 0) != -1; } + + return true; error: return false; } @@ -7927,14 +8186,26 @@ ecs_entity_t ecs_get_object( world = ecs_get_world(world); - ecs_table_t *table = ecs_get_table(world, entity); - if (!table) { + ecs_record_t *r = ecs_eis_get(world, entity); + ecs_table_t *table; + if (!r || !(table = r->table)) { return 0; } ecs_id_t wc = ecs_pair(rel, EcsWildcard); ecs_table_record_t *tr = flecs_get_table_record(world, table, wc); if (!tr) { + if (table->flags & EcsTableHasUnion) { + wc = ecs_pair(EcsUnion, rel); + tr = flecs_get_table_record(world, table, wc); + if (tr) { + ecs_switch_t *sw = &table->data.sw_columns[ + tr->column - table->sw_offset]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_switch_get(sw, row); + + } + } return 0; } @@ -7942,7 +8213,7 @@ ecs_entity_t ecs_get_object( return 0; } - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + ecs_id_t *ids = table->type.array; return ecs_pair_second(world, ids[tr->column + index]); error: return 0; @@ -7964,8 +8235,8 @@ ecs_entity_t ecs_get_object_for_id( return 0; } } else { - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - int32_t i, count = ecs_vector_count(table->type); + ecs_id_t *ids = table->type.array; + int32_t i, count = table->type.count; for (i = 0; i < count; i ++) { ecs_id_t ent = ids[i]; @@ -8217,7 +8488,7 @@ void ecs_ensure_id( ecs_world_t *world, ecs_id_t id) { - if (ECS_HAS_ROLE(id, PAIR) || ECS_HAS_ROLE(id, CASE)) { + if (ECS_HAS_ROLE(id, PAIR)) { ecs_entity_t r = ECS_PAIR_FIRST(id); ecs_entity_t o = ECS_PAIR_SECOND(id); @@ -8281,75 +8552,58 @@ ecs_table_t* ecs_get_storage_table( return NULL; } -ecs_type_t ecs_get_type( +const ecs_type_t* ecs_get_type( const ecs_world_t *world, ecs_entity_t entity) { ecs_table_t *table = ecs_get_table(world, entity); if (table) { - return table->type; + return &table->type; } return NULL; } -ecs_entity_t ecs_get_typeid( +const ecs_type_info_t* ecs_get_type_info( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - /* Hardcode components used in bootstrap */ - if (id == ecs_id(EcsComponent)) { - return id; - } else if (id == ecs_id(EcsIdentifier)) { - return id; - } else if (ECS_PAIR_FIRST(id) == ecs_id(EcsIdentifier)) { - return ecs_id(EcsIdentifier); - } else if (ECS_PAIR_FIRST(id) == EcsChildOf) { - return 0; - } else if (ECS_PAIR_FIRST(id) == EcsOnDelete) { - return 0; - } - - if (ECS_HAS_ROLE(id, PAIR)) { - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - - ecs_entity_t rel = ecs_get_alive(world, ECS_PAIR_FIRST(id)); - - /* If relation is marked as a tag, it never has data. Return relation */ - if (ecs_has_id(world, rel, EcsTag)) { - return 0; - } + world = ecs_get_world(world); - const EcsComponent *ptr = ecs_get(world, rel, EcsComponent); - if (ptr && ptr->size != 0) { - return rel; + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr && ECS_HAS_ROLE(id, PAIR)) { + idr = flecs_get_id_record(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); + if (!idr || !idr->type_info) { + idr = NULL; } - - ecs_entity_t obj = ECS_PAIR_SECOND(id); - if (obj) { - obj = ecs_get_alive(world, obj); - ptr = ecs_get(world, obj, EcsComponent); - - if (ptr && ptr->size != 0) { - return obj; + if (!idr) { + idr = flecs_get_id_record(world, + ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); + if (!idr || !idr->type_info) { + idr = NULL; } } + } - /* Neither relation nor object have data */ - return 0; - } else if (id & ECS_ROLE_MASK) { - return 0; - } else { - const EcsComponent *ptr = ecs_get(world, id, EcsComponent); - if (!ptr || !ptr->size) { - return 0; - } + if (idr) { + return idr->type_info; } +error: + return NULL; +} - return id; +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + return ti->component; + } error: return 0; } @@ -8420,9 +8674,12 @@ void ecs_enable( const EcsType *type_ptr = ecs_get(world, entity, EcsType); if (type_ptr) { /* If entity is a type, disable all entities in the type */ - ecs_vector_each(type_ptr->normalized->type, ecs_entity_t, e, { - ecs_enable(world, *e, enabled); - }); + const ecs_type_t *type = &type_ptr->normalized->type; + ecs_id_t *ids = type->array; + int32_t i, count = type->count; + for (i = 0; i < count; i ++) { + ecs_enable(world, ids[i], enabled); + } } else { if (enabled) { ecs_remove_id(world, entity, EcsDisabled); @@ -8452,6 +8709,37 @@ bool ecs_defer_end( return false; } +FLECS_API +void ecs_defer_suspend( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer_suspend == false, ECS_INVALID_OPERATION, NULL); + stage->defer_suspend = true; +error: + return; +} + +/** Resume deferring. + * See ecs_defer_suspend. + * + * @param world The world. + */ +FLECS_API +void ecs_defer_resume( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer_suspend == true, ECS_INVALID_OPERATION, NULL); + stage->defer_suspend = false; +error: + return; +} + const char* ecs_role_str( ecs_entity_t entity) { @@ -8473,12 +8761,6 @@ const char* ecs_role_str( if (ECS_HAS_ROLE(entity, NOT)) { return "NOT"; } else - if (ECS_HAS_ROLE(entity, SWITCH)) { - return "SWITCH"; - } else - if (ECS_HAS_ROLE(entity, CASE)) { - return "CASE"; - } else if (ECS_HAS_ROLE(entity, OVERRIDE)) { return "OVERRIDE"; } else { @@ -8500,7 +8782,7 @@ void ecs_id_str_buf( ecs_strbuf_appendch(buf, '|'); } - if (ECS_HAS_ROLE(id, PAIR) || ECS_HAS_ROLE(id, CASE)) { + if (ECS_HAS_ROLE(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t obj = ECS_PAIR_SECOND(id); @@ -8535,18 +8817,17 @@ char* ecs_id_str( return ecs_strbuf_get(&buf); } - char* ecs_type_str( const ecs_world_t *world, - ecs_type_t type) + const ecs_type_t *type) { if (!type) { return ecs_os_strdup(""); } ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); - int32_t i, count = ecs_vector_count(type); + ecs_entity_t *ids = type->array; + int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_entity_t id = ids[i]; @@ -8566,6 +8847,13 @@ char* ecs_type_str( return ecs_strbuf_get(&buf); } +char* ecs_table_str( + const ecs_world_t *world, + ecs_table_t *table) +{ + return ecs_type_str(world, &table->type); +} + static void flush_bulk_new( ecs_world_t *world, @@ -8590,10 +8878,9 @@ void free_value( void *value, int32_t count) { - ecs_entity_t real_id = ecs_get_typeid(world, id); - const ecs_type_info_t *ti = flecs_get_type_info(world, real_id); + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->lifecycle.dtor; - if (dtor) { ecs_size_t size = ti->size; void *ptr; @@ -8649,14 +8936,15 @@ bool remove_invalid( ecs_entity_t obj = ecs_pair_second(world, id); if (!obj || !is_entity_valid(world, obj)) { /* Check the relation's policy for deleted objects */ - ecs_id_record_t *idr = flecs_get_id_record(world, rel); + ecs_id_record_t *idr = flecs_get_id_record(world, + ecs_pair(rel, EcsWildcard)); if (idr) { ecs_entity_t action = ECS_ID_ON_DELETE_OBJECT(idr->flags); if (action == EcsDelete) { /* Entity should be deleted, don't bother checking * other ids */ return false; - } else if (action == EcsThrow) { + } else if (action == EcsPanic) { /* If policy is throw this object should not have * been deleted */ throw_invalid_delete(world, id); @@ -8691,6 +8979,10 @@ bool flecs_defer_flush( ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + if (stage->defer_suspend) { + return false; + } + if (!--stage->defer) { /* Set to NULL. Processing deferred commands can cause additional * commands to get enqueued (as result of reactive systems). Make sure @@ -8741,12 +9033,12 @@ bool flecs_defer_flush( ecs_clone(world, e, op->id, op->is._1.clone_value); break; case EcsOpSet: - assign_ptr_w_id(world, e, + set_ptr_w_id(world, e, op->id, flecs_itosize(op->is._1.size), op->is._1.value, true, true); break; case EcsOpMut: - assign_ptr_w_id(world, e, + set_ptr_w_id(world, e, op->id, flecs_itosize(op->is._1.size), op->is._1.value, true, false); break; @@ -8848,6 +9140,7 @@ bool defer_add_remove( ecs_entity_t entity, ecs_id_t id) { + if (stage->defer_suspend) return false; if (stage->defer) { if (!id) { return true; @@ -8942,6 +9235,7 @@ bool flecs_defer_none( ecs_stage_t *stage) { (void)world; + if (stage->defer_suspend) return false; return (++ stage->defer) == 1; } @@ -8952,6 +9246,7 @@ bool flecs_defer_modified( ecs_id_t id) { (void)world; + if (stage->defer_suspend) return false; if (stage->defer) { ecs_defer_op_t *op = new_defer_op(stage); op->kind = EcsOpModified; @@ -8973,6 +9268,7 @@ bool flecs_defer_clone( bool clone_value) { (void)world; + if (stage->defer_suspend) return false; if (stage->defer) { ecs_defer_op_t *op = new_defer_op(stage); op->kind = EcsOpClone; @@ -8993,6 +9289,7 @@ bool flecs_defer_delete( ecs_entity_t entity) { (void)world; + if (stage->defer_suspend) return false; if (stage->defer) { ecs_defer_op_t *op = new_defer_op(stage); op->kind = EcsOpDelete; @@ -9011,6 +9308,7 @@ bool flecs_defer_clear( ecs_entity_t entity) { (void)world; + if (stage->defer_suspend) return false; if (stage->defer) { ecs_defer_op_t *op = new_defer_op(stage); op->kind = EcsOpClear; @@ -9030,6 +9328,7 @@ bool flecs_defer_on_delete_action( ecs_entity_t action) { (void)world; + if (stage->defer_suspend) return false; if (stage->defer) { ecs_defer_op_t *op = new_defer_op(stage); op->kind = EcsOpOnDeleteAction; @@ -9051,6 +9350,7 @@ bool flecs_defer_enable( bool enable) { (void)world; + if (stage->defer_suspend) return false; if (stage->defer) { ecs_defer_op_t *op = new_defer_op(stage); op->kind = enable ? EcsOpEnable : EcsOpDisable; @@ -9070,6 +9370,7 @@ bool flecs_defer_bulk_new( ecs_id_t id, const ecs_entity_t **ids_out) { + if (stage->defer_suspend) return false; if (stage->defer) { ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); world->bulk_new_count ++; @@ -9135,12 +9436,14 @@ bool flecs_defer_set( void **value_out, bool *is_added) { + if (stage->defer_suspend) return false; if (stage->defer) { world->set_count ++; if (!size) { - const EcsComponent *cptr = flecs_component_from_id(world, id); - ecs_check(cptr != NULL, ECS_INVALID_PARAMETER, NULL); - size = cptr->size; + ecs_id_record_t *idr = flecs_ensure_id_record(world, id); + ecs_check(idr != NULL && idr->type_info != NULL, + ECS_INVALID_PARAMETER, NULL); + size = idr->type_info->size; } ecs_defer_op_t *op = new_defer_op(stage); @@ -9347,8 +9650,6 @@ bool ecs_staging_begin( * allowed to enqueue commands from stages */ world->is_readonly = true; - ecs_dbg_3("staging: begin"); - return is_readonly; } @@ -10827,8 +11128,8 @@ ecs_sparse_iter_t _flecs_sparse_iter( #ifdef FLECS_SANITIZE static void verify_nodes( - flecs_switch_header_t *hdr, - flecs_switch_node_t *nodes) + ecs_switch_header_t *hdr, + ecs_switch_node_t *nodes) { if (!hdr) { return; @@ -10849,7 +11150,7 @@ void verify_nodes( #endif static -flecs_switch_header_t *get_header( +ecs_switch_header_t *get_header( const ecs_switch_t *sw, uint64_t value) { @@ -10857,21 +11158,32 @@ flecs_switch_header_t *get_header( return NULL; } - value = (uint32_t)value; + return ecs_map_get(&sw->headers, ecs_switch_header_t, value); +} - ecs_assert(value >= sw->min, ECS_INTERNAL_ERROR, NULL); - ecs_assert(value <= sw->max, ECS_INTERNAL_ERROR, NULL); +static +ecs_switch_header_t *ensure_header( + ecs_switch_t *sw, + uint64_t value) +{ + if (value == 0) { + return NULL; + } - uint64_t index = value - sw->min; + ecs_switch_header_t *node = get_header(sw, value); + if (!node) { + node = ecs_map_ensure(&sw->headers, ecs_switch_header_t, value); + node->element = -1; + } - return &sw->headers[index]; + return node; } static void remove_node( - flecs_switch_header_t *hdr, - flecs_switch_node_t *nodes, - flecs_switch_node_t *node, + ecs_switch_header_t *hdr, + ecs_switch_node_t *nodes, + ecs_switch_node_t *node, int32_t element) { ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); @@ -10885,7 +11197,7 @@ void remove_node( /* If this is not the first node, update the previous node to the * removed node's next ptr */ ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); - flecs_switch_node_t *prev_node = &nodes[node->prev]; + ecs_switch_node_t *prev_node = &nodes[node->prev]; prev_node->next = node->next; } @@ -10895,7 +11207,7 @@ void remove_node( ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); /* If this is not the last node, update the next node to point to the * removed node's prev ptr */ - flecs_switch_node_t *next_node = &nodes[next]; + ecs_switch_node_t *next_node = &nodes[next]; next_node->prev = node->prev; } @@ -10904,42 +11216,33 @@ void remove_node( ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); } -ecs_switch_t* flecs_switch_new( - uint64_t min, - uint64_t max, +void flecs_switch_init( + ecs_switch_t *sw, int32_t elements) { - ecs_assert(min <= max, ECS_INVALID_PARAMETER, NULL); + ecs_map_init(&sw->headers, ecs_switch_header_t, 1); + sw->nodes = ecs_vector_new(ecs_switch_node_t, elements); + sw->values = ecs_vector_new(uint64_t, elements); - /* Min must be larger than 0, as 0 is an invalid entity id, and should - * therefore never occur as case id */ - ecs_assert(min > 0, ECS_INVALID_PARAMETER, NULL); - - ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t)); - result->min = (uint32_t)min; - result->max = (uint32_t)max; - - int32_t count = (int32_t)(max - min) + 1; - result->headers = ecs_os_calloc(ECS_SIZEOF(flecs_switch_header_t) * count); - result->nodes = ecs_vector_new(flecs_switch_node_t, elements); - result->values = ecs_vector_new(uint64_t, elements); - - int64_t i; - for (i = 0; i < count; i ++) { - result->headers[i].element = -1; - result->headers[i].count = 0; - } - - flecs_switch_node_t *nodes = ecs_vector_first( - result->nodes, flecs_switch_node_t); + ecs_switch_node_t *nodes = ecs_vector_first( + sw->nodes, ecs_switch_node_t); uint64_t *values = ecs_vector_first( - result->values, uint64_t); + sw->values, uint64_t); + int i; for (i = 0; i < elements; i ++) { nodes[i].prev = -1; nodes[i].next = -1; values[i] = 0; } +} + +ecs_switch_t* flecs_switch_new( + int32_t elements) +{ + ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t)); + + flecs_switch_init(result, elements); return result; } @@ -10947,30 +11250,33 @@ ecs_switch_t* flecs_switch_new( void flecs_switch_clear( ecs_switch_t *sw) { - int32_t count = (int32_t)(sw->max - sw->min) + 1; - for (int i = 0; i < count; i ++) { - sw->headers[i].element = -1; - sw->headers[i].count = 0; - } + ecs_map_clear(&sw->headers); ecs_vector_free(sw->nodes); ecs_vector_free(sw->values); sw->nodes = NULL; sw->values = NULL; } -void flecs_switch_free( +void flecs_switch_fini( ecs_switch_t *sw) { - ecs_os_free(sw->headers); + // ecs_os_free(sw->headers); + ecs_map_fini(&sw->headers); ecs_vector_free(sw->nodes); ecs_vector_free(sw->values); +} + +void flecs_switch_free( + ecs_switch_t *sw) +{ + flecs_switch_fini(sw); ecs_os_free(sw); } void flecs_switch_add( ecs_switch_t *sw) { - flecs_switch_node_t *node = ecs_vector_add(&sw->nodes, flecs_switch_node_t); + ecs_switch_node_t *node = ecs_vector_add(&sw->nodes, ecs_switch_node_t); uint64_t *value = ecs_vector_add(&sw->values, uint64_t); node->prev = -1; node->next = -1; @@ -10986,15 +11292,15 @@ void flecs_switch_set_count( return; } - ecs_vector_set_count(&sw->nodes, flecs_switch_node_t, count); + ecs_vector_set_count(&sw->nodes, ecs_switch_node_t, count); ecs_vector_set_count(&sw->values, uint64_t, count); - flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); uint64_t *values = ecs_vector_first(sw->values, uint64_t); int32_t i; for (i = old_count; i < count; i ++) { - flecs_switch_node_t *node = &nodes[i]; + ecs_switch_node_t *node = &nodes[i]; node->prev = -1; node->next = -1; values[i] = 0; @@ -11047,11 +11353,11 @@ void flecs_switch_set( return; } - flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); - flecs_switch_node_t *node = &nodes[element]; + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); + ecs_switch_node_t *node = &nodes[element]; - flecs_switch_header_t *cur_hdr = get_header(sw, cur_value); - flecs_switch_header_t *dst_hdr = get_header(sw, value); + ecs_switch_header_t *dst_hdr = ensure_header(sw, value); + ecs_switch_header_t *cur_hdr = get_header(sw, cur_value); verify_nodes(cur_hdr, nodes); verify_nodes(dst_hdr, nodes); @@ -11075,7 +11381,7 @@ void flecs_switch_set( int32_t first = dst_hdr->element; if (first != -1) { ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); - flecs_switch_node_t *first_node = &nodes[first]; + ecs_switch_node_t *first_node = &nodes[first]; first_node->prev = element; } @@ -11094,12 +11400,12 @@ void flecs_switch_remove( uint64_t *values = ecs_vector_first(sw->values, uint64_t); uint64_t value = values[element]; - flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); - flecs_switch_node_t *node = &nodes[element]; + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); + ecs_switch_node_t *node = &nodes[element]; /* If node is currently assigned to a case, remove it from the list */ if (value != 0) { - flecs_switch_header_t *hdr = get_header(sw, value); + ecs_switch_header_t *hdr = get_header(sw, value); ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); verify_nodes(hdr, nodes); @@ -11108,18 +11414,18 @@ void flecs_switch_remove( int32_t last_elem = ecs_vector_count(sw->nodes) - 1; if (last_elem != element) { - flecs_switch_node_t *last = ecs_vector_last(sw->nodes, flecs_switch_node_t); + ecs_switch_node_t *last = ecs_vector_last(sw->nodes, ecs_switch_node_t); int32_t next = last->next, prev = last->prev; if (next != -1) { - flecs_switch_node_t *n = &nodes[next]; + ecs_switch_node_t *n = &nodes[next]; n->prev = element; } if (prev != -1) { - flecs_switch_node_t *n = &nodes[prev]; + ecs_switch_node_t *n = &nodes[prev]; n->next = element; } else { - flecs_switch_header_t *hdr = get_header(sw, values[last_elem]); + ecs_switch_header_t *hdr = get_header(sw, values[last_elem]); if (hdr && hdr->element != -1) { ecs_assert(hdr->element == last_elem, ECS_INTERNAL_ERROR, NULL); @@ -11129,7 +11435,7 @@ void flecs_switch_remove( } /* Remove element from arrays */ - ecs_vector_remove(sw->nodes, flecs_switch_node_t, element); + ecs_vector_remove(sw->nodes, ecs_switch_node_t, element); ecs_vector_remove(sw->values, uint64_t, element); } @@ -11156,7 +11462,7 @@ int32_t flecs_switch_case_count( const ecs_switch_t *sw, uint64_t value) { - flecs_switch_header_t *hdr = get_header(sw, value); + ecs_switch_header_t *hdr = get_header(sw, value); if (!hdr) { return 0; } @@ -11181,11 +11487,11 @@ int32_t flecs_switch_first( uint64_t value) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert((uint32_t)value <= sw->max, ECS_INVALID_PARAMETER, NULL); - ecs_assert((uint32_t)value >= sw->min, ECS_INVALID_PARAMETER, NULL); - flecs_switch_header_t *hdr = get_header(sw, value); - ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_switch_header_t *hdr = get_header(sw, value); + if (!hdr) { + return -1; + } return hdr->element; } @@ -11198,8 +11504,8 @@ int32_t flecs_switch_next( ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); - flecs_switch_node_t *nodes = ecs_vector_first( - sw->nodes, flecs_switch_node_t); + ecs_switch_node_t *nodes = ecs_vector_first( + sw->nodes, ecs_switch_node_t); return nodes[element].next; } @@ -12599,11 +12905,66 @@ int32_t ecs_strbuf_written( /* The ratio used to determine whether the map should rehash. If * (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */ -#define LOAD_FACTOR (1.5f) +#define LOAD_FACTOR (1.2f) #define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) #define GET_ELEM(array, elem_size, index) \ ECS_OFFSET(array, (elem_size) * (index)) +static +ecs_block_allocator_chunk_header_t *ecs_balloc_block( + ecs_block_allocator_t *allocator) +{ + ecs_block_allocator_chunk_header_t *first_chunk = + ecs_os_malloc(allocator->block_size); + ecs_block_allocator_block_t *block = + ecs_os_malloc_t(ecs_block_allocator_block_t); + + block->memory = first_chunk; + if (!allocator->block_tail) { + ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0); + block->next = NULL; + allocator->block_head = block; + allocator->block_tail = block; + } else { + block->next = NULL; + allocator->block_tail->next = block; + allocator->block_tail = block; + } + + ecs_block_allocator_chunk_header_t *chunk = first_chunk; + int32_t i, end; + for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { + chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); + chunk = chunk->next; + } + + chunk->next = NULL; + return first_chunk; +} + +static +void *ecs_balloc( + ecs_block_allocator_t *allocator) +{ + if (!allocator->head) { + allocator->head = ecs_balloc_block(allocator); + } + + void *result = allocator->head; + allocator->head = allocator->head->next; + return result; +} + +static +void ecs_bfree( + ecs_block_allocator_t *allocator, + void *memory) +{ + ecs_block_allocator_chunk_header_t *chunk = memory; + chunk->next = allocator->head; + allocator->head = chunk; +} + static uint8_t ecs_log2(uint32_t v) { static const uint8_t log2table[32] = @@ -12637,13 +12998,10 @@ uint8_t get_bucket_shift ( /* Get bucket index for provided map key */ static int32_t get_bucket_index( - const ecs_map_t *map, uint16_t bucket_shift, ecs_map_key_t key) { ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(map->bucket_shift == bucket_shift, ECS_INTERNAL_ERROR, NULL); - (void)map; return (int32_t)((11400714819323198485ull * key) >> bucket_shift); } @@ -12653,9 +13011,9 @@ ecs_bucket_t* get_bucket( const ecs_map_t *map, ecs_map_key_t key) { - ecs_assert(map->bucket_shift == get_bucket_shift(map->bucket_count), + ecs_assert(map->bucket_shift == get_bucket_shift(map->bucket_count), ECS_INTERNAL_ERROR, NULL); - int32_t bucket_id = get_bucket_index(map, map->bucket_shift, key); + int32_t bucket_id = get_bucket_index(map->bucket_shift, key); ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); return &map->buckets[bucket_id]; } @@ -12673,25 +13031,27 @@ void ensure_buckets( } if (new_count && new_count > bucket_count) { - map->buckets = ecs_os_realloc(map->buckets, new_count * ECS_SIZEOF(ecs_bucket_t)); + map->buckets = ecs_os_realloc_n(map->buckets, ecs_bucket_t, new_count); + map->buckets_end = ECS_ELEM_T(map->buckets, ecs_bucket_t, new_count); + map->bucket_count = new_count; map->bucket_shift = get_bucket_shift(new_count); - ecs_os_memset( - ECS_OFFSET(map->buckets, bucket_count * ECS_SIZEOF(ecs_bucket_t)), - 0, (new_count - bucket_count) * ECS_SIZEOF(ecs_bucket_t)); + ecs_os_memset_n(ECS_ELEM_T(map->buckets, ecs_bucket_t, bucket_count), + 0, ecs_bucket_t, (new_count - bucket_count)); } } /* Free contents of bucket */ static void clear_bucket( - ecs_bucket_t *bucket) + ecs_block_allocator_t *allocator, + ecs_bucket_entry_t *bucket) { - ecs_os_free(bucket->keys); - ecs_os_free(bucket->payload); - bucket->keys = NULL; - bucket->payload = NULL; - bucket->count = 0; + while(bucket) { + ecs_bucket_entry_t *next = bucket->next; + ecs_bfree(allocator, bucket); + bucket = next; + } } /* Clear all buckets */ @@ -12699,96 +13059,71 @@ static void clear_buckets( ecs_map_t *map) { - ecs_bucket_t *buckets = map->buckets; int32_t i, count = map->bucket_count; for (i = 0; i < count; i ++) { - clear_bucket(&buckets[i]); + clear_bucket(&map->allocator, map->buckets[i].first); } - ecs_os_free(buckets); + ecs_os_free(map->buckets); map->buckets = NULL; map->bucket_count = 0; } -/* Find or create bucket for specified key */ -static -ecs_bucket_t* ensure_bucket( - ecs_map_t *map, - ecs_map_key_t key) -{ - ecs_assert(map->bucket_count >= 2, ECS_INTERNAL_ERROR, NULL); - int32_t bucket_id = get_bucket_index(map, map->bucket_shift, key); - ecs_assert(bucket_id >= 0, ECS_INTERNAL_ERROR, NULL); - return &map->buckets[bucket_id]; -} - /* Add element to bucket */ static -int32_t add_to_bucket( +void* add_to_bucket( + ecs_block_allocator_t *allocator, ecs_bucket_t *bucket, ecs_size_t elem_size, ecs_map_key_t key, const void *payload) { - int32_t index = bucket->count ++; - int32_t bucket_count = index + 1; - - bucket->keys = ecs_os_realloc(bucket->keys, KEY_SIZE * bucket_count); - bucket->keys[index] = key; - - if (elem_size) { - bucket->payload = ecs_os_realloc(bucket->payload, elem_size * bucket_count); - if (payload) { - void *elem = GET_ELEM(bucket->payload, elem_size, index); - ecs_os_memcpy(elem, payload, elem_size); - } - } else { - bucket->payload = NULL; + ecs_bucket_entry_t *new_entry = ecs_balloc(allocator); + new_entry->key = key; + new_entry->next = bucket->first; + bucket->first = new_entry; + void *new_payload = ECS_OFFSET(&new_entry->key, ECS_SIZEOF(ecs_map_key_t)); + if (elem_size && payload) { + ecs_os_memcpy(new_payload, payload, elem_size); } - - return index; + return new_payload; } /* Remove element from bucket */ static -void remove_from_bucket( +bool remove_from_bucket( + ecs_block_allocator_t *allocator, ecs_bucket_t *bucket, - ecs_size_t elem_size, - ecs_map_key_t key, - int32_t index) + ecs_map_key_t key) { - (void)key; - - ecs_assert(bucket->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(index < bucket->count, ECS_INTERNAL_ERROR, NULL); - - int32_t bucket_count = -- bucket->count; - - if (index != bucket->count) { - ecs_assert(key == bucket->keys[index], ECS_INTERNAL_ERROR, NULL); - bucket->keys[index] = bucket->keys[bucket_count]; - - ecs_map_key_t *elem = GET_ELEM(bucket->payload, elem_size, index); - ecs_map_key_t *last_elem = GET_ELEM(bucket->payload, elem_size, bucket->count); - - ecs_os_memcpy(elem, last_elem, elem_size); + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + ecs_bucket_entry_t **next_holder = &bucket->first; + while(*next_holder != entry) { + next_holder = &(*next_holder)->next; + } + *next_holder = entry->next; + ecs_bfree(allocator, entry); + return true; + } } + + return false; } /* Get payload pointer for key from bucket */ static void* get_from_bucket( ecs_bucket_t *bucket, - ecs_map_key_t key, - ecs_size_t elem_size) + ecs_map_key_t key) { - ecs_map_key_t *keys = bucket->keys; - int32_t i, count = bucket->count; - - for (i = 0; i < count; i ++) { - if (keys[i] == key) { - return GET_ELEM(bucket->payload, elem_size, i); + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + return ECS_OFFSET(&entry->key, ECS_SIZEOF(ecs_map_key_t)); } } + return NULL; } @@ -12800,45 +13135,32 @@ void rehash( { ecs_assert(bucket_count != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); - - ensure_buckets(map, bucket_count); - - ecs_bucket_t *buckets = map->buckets; - ecs_assert(buckets != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_size_t elem_size = map->elem_size; - uint16_t bucket_shift = map->bucket_shift; - int32_t bucket_id; - - /* Iterate backwards as elements could otherwise be moved to existing - * buckets which could temporarily cause the number of elements in a - * bucket to exceed BUCKET_COUNT. */ - for (bucket_id = bucket_count - 1; bucket_id >= 0; bucket_id --) { - ecs_bucket_t *bucket = &buckets[bucket_id]; - - int i, count = bucket->count; - ecs_map_key_t *key_array = bucket->keys; - void *payload_array = bucket->payload; - - for (i = 0; i < count; i ++) { - ecs_map_key_t key = key_array[i]; - void *elem = GET_ELEM(payload_array, elem_size, i); - int32_t new_bucket_id = get_bucket_index(map, bucket_shift, key); - - if (new_bucket_id != bucket_id) { - ecs_bucket_t *new_bucket = &buckets[new_bucket_id]; - - add_to_bucket(new_bucket, elem_size, key, elem); - remove_from_bucket(bucket, elem_size, key, i); - - count --; - i --; - } - } - - if (!bucket->count) { - clear_bucket(bucket); + + int32_t old_count = map->bucket_count; + ecs_bucket_t *old_buckets = map->buckets; + + int32_t new_count = flecs_next_pow_of_2(bucket_count); + map->bucket_count = new_count; + map->bucket_shift = get_bucket_shift(new_count); + map->buckets = ecs_os_calloc_n(ecs_bucket_t, new_count); + map->buckets_end = ECS_ELEM_T(map->buckets, ecs_bucket_t, new_count); + + /* Remap old bucket entries to new buckets */ + int32_t index; + for (index = 0; index < old_count; ++index) { + ecs_bucket_entry_t* entry; + for (entry = old_buckets[index].first; entry;) { + ecs_bucket_entry_t* next = entry->next; + int32_t bucket_index = get_bucket_index( + map->bucket_shift, entry->key); + ecs_bucket_t *bucket = &map->buckets[bucket_index]; + entry->next = bucket->first; + bucket->first = entry; + entry = next; } } + + ecs_os_free(old_buckets); } void _ecs_map_init( @@ -12850,6 +13172,23 @@ void _ecs_map_init( result->count = 0; result->elem_size = (int16_t)elem_size; + + int32_t entry_size = elem_size + ECS_SIZEOF(ecs_bucket_entry_t); + int32_t balloc_min_chunk_size = ECS_MAX(entry_size, + ECS_SIZEOF(ecs_block_allocator_chunk_header_t)); + uint32_t alignment = 16; + uint32_t alignment_mask = alignment - 1u; + + /* Align balloc_min_chunk_size up to alignment. */ + result->allocator.chunk_size = (int32_t)(((uint32_t)balloc_min_chunk_size + + alignment_mask) & ~alignment_mask); + result->allocator.chunks_per_block = + ECS_MAX(4096 / result->allocator.chunk_size, 1); + result->allocator.block_size = result->allocator.chunks_per_block * + result->allocator.chunk_size; + result->allocator.head = NULL; + result->allocator.block_head = NULL; + result->allocator.block_tail = NULL; ensure_buckets(result, get_bucket_count(element_count)); } @@ -12876,7 +13215,18 @@ void ecs_map_fini( ecs_map_t *map) { ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); - clear_buckets(map); + ecs_block_allocator_block_t *block; + for (block = map->allocator.block_head; block; ){ + ecs_block_allocator_block_t *next = block->next; + ecs_os_free(block->memory); + ecs_os_free(block); + block = next; + } + map->allocator.block_head = NULL; + ecs_os_free(map->buckets); + map->buckets = NULL; + map->buckets_end = NULL; + map->bucket_count = 0; ecs_assert(!ecs_map_is_initialized(map), ECS_INTERNAL_ERROR, NULL); } @@ -12902,12 +13252,9 @@ void* _ecs_map_get( ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return NULL; - } + ecs_bucket_t *bucket = get_bucket(map, key); - return get_from_bucket(bucket, key, elem_size); + return get_from_bucket(bucket, key); } void* _ecs_map_get_ptr( @@ -12931,12 +13278,9 @@ bool ecs_map_has( return false; } - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return false; - } + ecs_bucket_t *bucket = get_bucket(map, key); - return get_from_bucket(bucket, key, 0) != NULL; + return get_from_bucket(bucket, key) != NULL; } void* _ecs_map_ensure( @@ -12965,23 +13309,23 @@ void* _ecs_map_set( ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - ecs_bucket_t *bucket = ensure_bucket(map, key); - ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bucket_t *bucket = get_bucket(map, key); - void *elem = get_from_bucket(bucket, key, elem_size); + void *elem = get_from_bucket(bucket, key); if (!elem) { - int32_t index = add_to_bucket(bucket, elem_size, key, payload); + void *added_data = add_to_bucket( + &map->allocator, bucket, elem_size, key, payload); int32_t map_count = ++map->count; int32_t target_bucket_count = get_bucket_count(map_count); int32_t map_bucket_count = map->bucket_count; if (target_bucket_count > map_bucket_count) { rehash(map, target_bucket_count); - bucket = ensure_bucket(map, key); - return get_from_bucket(bucket, key, elem_size); - } else { - return GET_ELEM(bucket->payload, elem_size, index); - } + bucket = get_bucket(map, key); + added_data = get_from_bucket(bucket, key); + ecs_assert(added_data != NULL, ECS_INVALID_PARAMETER, NULL); + } + return added_data; } else { if (payload) { ecs_os_memcpy(elem, payload, elem_size); @@ -12996,19 +13340,11 @@ int32_t ecs_map_remove( { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return map->count; - } - - int32_t i, bucket_count = bucket->count; - for (i = 0; i < bucket_count; i ++) { - if (bucket->keys[i] == key) { - remove_from_bucket(bucket, map->elem_size, key, i); - return --map->count; - } + ecs_bucket_t *bucket = get_bucket(map, key); + if (remove_from_bucket(&map->allocator, bucket, key)) { + return --map->count; } - + return map->count; } @@ -13039,8 +13375,7 @@ ecs_map_iter_t ecs_map_iter( return (ecs_map_iter_t){ .map = map, .bucket = NULL, - .bucket_index = 0, - .element_index = 0 + .entry = NULL }; } @@ -13049,47 +13384,51 @@ void* _ecs_map_next( ecs_size_t elem_size, ecs_map_key_t *key_out) { + (void)elem_size; const ecs_map_t *map = iter->map; if (!ecs_map_is_initialized(map)) { return NULL; } - + if (iter->bucket == map->buckets_end) { + return NULL; + } + ecs_assert(!elem_size || elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - - ecs_bucket_t *bucket = iter->bucket; - int32_t element_index = iter->element_index; - elem_size = map->elem_size; - do { - if (!bucket) { - int32_t bucket_index = iter->bucket_index; - ecs_bucket_t *buckets = map->buckets; - if (bucket_index < map->bucket_count) { - bucket = &buckets[bucket_index]; - iter->bucket = bucket; - - element_index = 0; - iter->element_index = 0; - } else { - return NULL; + ecs_bucket_entry_t *entry = NULL; + + if (!iter->bucket) { + for (iter->bucket = map->buckets; + iter->bucket != map->buckets_end; + ++iter->bucket) + { + if (iter->bucket->first) { + entry = iter->bucket->first; + break; } } - - if (element_index < bucket->count) { - iter->element_index = element_index + 1; - break; - } else { - bucket = NULL; - iter->bucket_index ++; + if (iter->bucket == map->buckets_end) { + return NULL; } - } while (true); + } else if ((entry = iter->entry) == NULL) { + do { + ++iter->bucket; + if (iter->bucket == map->buckets_end) { + return NULL; + } + } while(!iter->bucket->first); + entry = iter->bucket->first; + } if (key_out) { - *key_out = bucket->keys[element_index]; + ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); + *key_out = entry->key; } - return GET_ELEM(bucket->payload, elem_size, element_index); + iter->entry = entry->next; + + return ECS_OFFSET(&entry->key, ECS_SIZEOF(ecs_map_key_t)); } void* _ecs_map_next_ptr( @@ -13160,17 +13499,18 @@ void ecs_map_memory( *used = map->count * map->elem_size; } + // TODO: something something block allocator if (allocd) { *allocd += ECS_SIZEOF(ecs_map_t); - - int i, bucket_count = map->bucket_count; - for (i = 0; i < bucket_count; i ++) { - ecs_bucket_t *bucket = &map->buckets[i]; - *allocd += KEY_SIZE * bucket->count; - *allocd += map->elem_size * bucket->count; + *allocd += ECS_SIZEOF(ecs_bucket_entry_t*) * map->bucket_count; + int32_t index; + int32_t entry_size = map->elem_size + ECS_SIZEOF(ecs_bucket_entry_t); + for (index = 0; index < map->bucket_count; ++index) { + ecs_bucket_entry_t *entry; + for (entry = map->buckets[index].first; entry; entry = entry->next){ + *allocd += entry_size; + } } - - *allocd += ECS_SIZEOF(ecs_bucket_t) * bucket_count; } } @@ -13807,6 +14147,7 @@ const char* ecs_strerror( ECS_ERR_STR(ECS_MODULE_UNDEFINED); ECS_ERR_STR(ECS_MISSING_SYMBOL); ECS_ERR_STR(ECS_ALREADY_IN_USE); + ECS_ERR_STR(ECS_CYCLE_DETECTED); ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); ECS_ERR_STR(ECS_COLUMN_IS_SHARED); @@ -14454,21 +14795,21 @@ uint64_t group_by_phase( ecs_assert(pt != NULL, ECS_INTERNAL_ERROR, NULL); /* Find tag in system that belongs to pipeline */ - ecs_type_t type = ecs_table_get_type(table); - ecs_id_t *sys_comps = ecs_vector_first(type, ecs_id_t); - int32_t c, t, count = ecs_vector_count(type); + const ecs_type_t *type = ecs_table_get_type(table); + ecs_id_t *sys_comps = type->array; + int32_t c, t, count = type->count; - ecs_type_t pipeline_type = NULL; + const ecs_type_t *pipeline_type = NULL; if (pt->normalized) { - pipeline_type = pt->normalized->type; + pipeline_type = &pt->normalized->type; } - if (!pipeline_type) { + if (!pipeline_type || !pipeline_type->count) { return 0; } - ecs_id_t *tags = ecs_vector_first(pipeline_type, ecs_id_t); - int32_t tag_count = ecs_vector_count(pipeline_type); + ecs_id_t *tags = pipeline_type->array; + int32_t tag_count = pipeline_type->count; ecs_id_t result = 0; @@ -14788,45 +15129,54 @@ bool build_pipeline( op = ecs_vector_first(ops, ecs_pipeline_op_t); int32_t i, ran_since_merge = 0, op_index = 0; - ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); + if (!op) { + ecs_dbg("#[green]pipeline#[reset] is empty"); + return true; + } else { - /* Add schedule to debug tracing */ - ecs_dbg("#[green]pipeline#[reset] rebuild:"); - ecs_log_push_1(); + ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", - op->multi_threaded, !op->no_staging); - ecs_log_push_1(); - - it = ecs_query_iter(world, pq->query); - while (ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); - for (i = 0; i < it.count; i ++) { - if (ecs_should_log_1()) { - char *path = ecs_get_fullpath(world, it.entities[i]); - ecs_dbg("#[green]system#[reset] %s", path); - ecs_os_free(path); - } + /* Add schedule to debug tracing */ + ecs_dbg("#[green]pipeline#[reset] rebuild:"); + ecs_log_push_1(); - ran_since_merge ++; - if (ran_since_merge == op[op_index].count) { - ecs_dbg("#[magenta]merge#[reset]"); - ecs_log_pop_1(); - ran_since_merge = 0; - op_index ++; - if (op_index < ecs_vector_count(ops)) { - ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", - op[op_index].multi_threaded, !op[op_index].no_staging); + ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", + op->multi_threaded, !op->no_staging); + ecs_log_push_1(); + + it = ecs_query_iter(world, pq->query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + for (i = 0; i < it.count; i ++) { + if (ecs_should_log_1()) { + char *path = ecs_get_fullpath(world, it.entities[i]); + ecs_dbg("#[green]system#[reset] %s", path); + ecs_os_free(path); + } + + ran_since_merge ++; + if (ran_since_merge == op[op_index].count) { + ecs_dbg("#[magenta]merge#[reset]"); + ecs_log_pop_1(); + ran_since_merge = 0; + op_index ++; + if (op_index < ecs_vector_count(ops)) { + ecs_dbg( + "#[green]schedule#[reset]: " + "threading: %d, staging: %d:", + op[op_index].multi_threaded, + !op[op_index].no_staging); + } + ecs_log_push_1(); } - ecs_log_push_1(); - } - if (sys[i].last_frame == (world->info.frame_count_total + 1)) { - last_system = it.entities[i]; + if (sys[i].last_frame == (world->info.frame_count_total + 1)) { + last_system = it.entities[i]; - /* Can't break from loop yet. It's possible that previously - * inactive systems that ran before the last ran system are now - * active. */ + /* Can't break from loop yet. It's possible that previously + * inactive systems that ran before the last ran system are + * now active. */ + } } } } @@ -14896,7 +15246,7 @@ bool ecs_pipeline_update( * notify appropriate queries so caches are up to date. This includes the * pipeline query. */ if (start_of_frame) { - ecs_force_aperiodic(world); + ecs_run_aperiodic(world, 0); } bool added = false; @@ -15006,18 +15356,18 @@ static void add_pipeline_tags_to_sig( ecs_world_t *world, ecs_term_t *terms, - ecs_type_t type) + const ecs_type_t *type) { (void)world; - int32_t i, count = ecs_vector_count(type); - ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = type->count; + ecs_id_t *ids = type->array; for (i = 0; i < count; i ++) { terms[i] = (ecs_term_t){ .inout = EcsIn, .oper = EcsOr, - .pred.entity = entities[i], + .pred.entity = ids[i], .subj = { .entity = EcsThis, .set.mask = EcsSelf | EcsSuperSet @@ -15036,12 +15386,12 @@ ecs_query_t* build_pipeline_query( const EcsType *type_ptr = ecs_get(world, pipeline, EcsType); ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_type_t type = NULL; + const ecs_type_t *type = NULL; if (type_ptr->normalized) { - type = type_ptr->normalized->type; + type = &type_ptr->normalized->type; } - int32_t type_count = ecs_vector_count(type); + int32_t type_count = type ? type->count : 0; int32_t term_count = 1; if (not_inactive) { @@ -15073,7 +15423,9 @@ ecs_query_t* build_pipeline_query( }; } - add_pipeline_tags_to_sig(world, &terms[term_count], type); + if (type) { + add_pipeline_tags_to_sig(world, &terms[term_count], type); + } ecs_query_t *result = ecs_query_init(world, &(ecs_query_desc_t){ .filter = { @@ -15855,7 +16207,8 @@ ecs_entity_t ecs_cpp_component_register( const char *name, const char *symbol, ecs_size_t size, - ecs_size_t alignment) + ecs_size_t alignment, + bool implicit_name) { (void)size; (void)alignment; @@ -15912,7 +16265,7 @@ ecs_entity_t ecs_cpp_component_register( /* If no entity is found, lookup symbol to check if the component was * registered under a different name. */ - } else { + } else if (!implicit_name) { ent = ecs_lookup_symbol(world, symbol, false); ecs_assert(ent == 0, ECS_INCONSISTENT_COMPONENT_ID, symbol); } @@ -15931,14 +16284,23 @@ ecs_entity_t ecs_cpp_component_register_explicit( size_t alignment, bool is_component) { + char *existing_name = NULL; + // If an explicit id is provided, it is possible that the symbol and // name differ from the actual type, as the application may alias // one type to another. if (!id) { if (!name) { - // If no name was provided, retrieve the name implicitly from - // the name_helper class. - name = ecs_cpp_trim_module(world, type_name); + // If no name was provided first check if a type with the provided + // symbol was already registered. + id = ecs_lookup_symbol(world, symbol, false); + if (id) { + existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); + name = existing_name; + } else { + // If type is not yet known, derive from type name + name = ecs_cpp_trim_module(world, type_name); + } } } else { // If an explicit id is provided but it has no name, inherit @@ -15972,6 +16334,8 @@ ecs_entity_t ecs_cpp_component_register_explicit( ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(existing_name); + return entity; } @@ -16039,7 +16403,7 @@ int32_t ecs_cpp_reset_count_inc(void) { #ifdef FLECS_OS_API_IMPL -#ifdef ECS_TARGET_MSVC +#ifdef ECS_TARGET_WINDOWS #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif @@ -18369,11 +18733,11 @@ ecs_entity_t reg_get_entity( int32_t offset = regs[r].range.offset; ecs_assert(regs[r].range.count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_data_t *data = &table_reg_get(rule, regs, r).table->storage; + ecs_data_t *data = &table_reg_get(rule, regs, r).table->data; ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_entity_t *entities = ecs_storage_first(&data->entities); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(offset < ecs_vector_count(data->entities), + ecs_assert(offset < ecs_storage_count(&data->entities), ECS_INTERNAL_ERROR, NULL); ecs_check(ecs_is_valid(rule->world, entities[offset]), ECS_INVALID_PARAMETER, NULL); @@ -18462,7 +18826,7 @@ void reg_set_range( if (rule->vars[r].kind == EcsRuleVarKindEntity) { ecs_check(range->count == 1, ECS_INTERNAL_ERROR, NULL); regs[r].range = *range; - regs[r].entity = ecs_vector_get(range->table->storage.entities, + regs[r].entity = ecs_storage_get_t(&range->table->data.entities, ecs_entity_t, range->offset)[0]; } else { regs[r].range = *range; @@ -18653,8 +19017,8 @@ void reify_variables( (void)vars; ecs_var_t *regs = get_registers(it, op); - ecs_entity_t *elem = ecs_vector_get(type, ecs_entity_t, column); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *elem = &type.array[column]; int32_t obj_var = filter->lo_var; int32_t pred_var = filter->hi_var; @@ -20680,7 +21044,8 @@ ecs_iter_t ecs_rule_iter( result.terms = rule->filter.terms; result.next = ecs_rule_next; result.fini = ecs_rule_iter_free; - result.flags |= EcsIterIsFilter * (rule->filter.filter == true); + ECS_BIT_COND(result.flags, EcsIterIsFilter, + ECS_BIT_IS_SET(rule->filter.flags, EcsFilterIsFilter)); flecs_iter_init(&result, flecs_iter_cache_ids | @@ -20715,8 +21080,8 @@ int32_t find_next_same_var( (void)pattern; /* Keep scanning for an id where rel and obj are the same */ - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + ecs_id_t *ids = type.array; + int32_t i, count = type.count; for (i = column + 1; i < count; i ++) { ecs_id_t id = ids[i]; if (!ECS_HAS_ROLE(id, PAIR)) { @@ -20818,8 +21183,8 @@ ecs_id_t rule_get_column( ecs_type_t type, int32_t column) { - ecs_id_t *comp = ecs_vector_get(type, ecs_id_t, column); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL); + ecs_id_t *comp = &type.array[column]; return *comp; } @@ -21110,8 +21475,7 @@ bool eval_subset( /* Table must have at least row elements */ ecs_assert(row_count > row, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); /* The entity used to find the next table set */ @@ -21155,8 +21519,7 @@ bool eval_subset( table_reg_set(rule, regs, r, table); yield: - set_term_vars(rule, regs, op->term, ecs_vector_get(frame->table->type, - ecs_id_t, frame->column)[0]); + set_term_vars(rule, regs, op->term, frame->table->type.array[frame->column]); return true; } @@ -21497,8 +21860,7 @@ bool eval_each( count += offset; } - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); /* If this is is not a redo, start from row 0, otherwise go to the @@ -21873,8 +22235,8 @@ void populate_iterator( * column can be correctly resolved */ ecs_table_t *t = regs[var->id].range.table; if (t) { - iter->subjects[i] = ecs_vector_first( - t->storage.entities, ecs_entity_t)[0]; + iter->subjects[i] = ecs_storage_first_t( + &t->data.entities, ecs_entity_t)[0]; } else { /* Can happen if term is optional */ iter->subjects[i] = 0; @@ -23412,6 +23774,7 @@ int add_constant_to_enum( ecs_map_iter_t it = ecs_map_iter(ptr->constants); ecs_enum_constant_t *c; ecs_map_key_t key; + while ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) { if (c->constant == e) { ecs_os_free((char*)c->name); @@ -26140,7 +26503,7 @@ void ecs_gauge_reduce( return; } -void ecs_get_world_stats( +void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *s) { @@ -26216,8 +26579,8 @@ void ecs_get_world_stats( /* Singleton tables are tables that have just one entity that also has * itself in the table type. */ if (entity_count == 1) { - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); + ecs_entity_t *entities = ecs_storage_first_t( + &table->data.entities, ecs_entity_t); if (ecs_search(world, table, entities[0], 0)) { singleton_table_count ++; } @@ -26250,7 +26613,7 @@ void ecs_get_world_stats( return; } -void ecs_get_query_stats( +void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *s) @@ -26272,7 +26635,7 @@ void ecs_get_query_stats( } #ifdef FLECS_SYSTEM -bool ecs_get_system_stats( +bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *s) @@ -26288,7 +26651,7 @@ bool ecs_get_system_stats( return false; } - ecs_get_query_stats(world, ptr->query, &s->query_stats); + ecs_query_stats_get(world, ptr->query, &s->query_stats); int32_t t = s->query_stats.t; record_counter(&s->time_spent, t, ptr->time_spent); @@ -26323,7 +26686,7 @@ ecs_system_stats_t* get_system_stats( return NULL; } -bool ecs_get_pipeline_stats( +bool ecs_pipeline_stats_get( ecs_world_t *stage, ecs_entity_t pipeline, ecs_pipeline_stats_t *s) @@ -26410,7 +26773,7 @@ bool ecs_get_pipeline_stats( for (i = 0; i < it.count; i ++) { ecs_system_stats_t *sys_stats = get_system_stats( s->system_stats, it.entities[i]); - ecs_get_system_stats(world, it.entities[i], sys_stats); + ecs_system_stats_get(world, it.entities[i], sys_stats); } } @@ -26428,7 +26791,7 @@ void ecs_pipeline_stats_fini( #endif -void ecs_dump_world_stats( +void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *s) { @@ -27506,7 +27869,7 @@ struct ecs_snapshot_t { /** Small footprint data structure for storing data associated with a table. */ typedef struct ecs_table_leaf_t { ecs_table_t *table; - ecs_vector_t *type; + ecs_type_t type; ecs_data_t *data; } ecs_table_leaf_t; @@ -27524,39 +27887,31 @@ ecs_data_t* duplicate_data( result->columns = ecs_os_memdup_n( main_data->columns, ecs_column_t, column_count); - /* Copy entities */ - result->entities = ecs_vector_copy(main_data->entities, ecs_entity_t); - - /* Copy record ptrs */ - result->record_ptrs = ecs_vector_copy( - main_data->record_ptrs, ecs_record_t*); - - ecs_size_t to_alloc = ecs_vector_size(result->entities); + /* Copy entities and records */ + result->entities = ecs_storage_copy_t(&main_data->entities, ecs_entity_t); + result->records = ecs_storage_copy_t(&main_data->records, ecs_record_t*); /* Copy each column */ for (i = 0; i < column_count; i ++) { ecs_column_t *column = &result->columns[i]; - ecs_type_info_t *ti = &table->type_info[i]; + ecs_type_info_t *ti = table->type_info[i]; int32_t size = ti->size; - int32_t alignment = ti->alignment; ecs_copy_t copy = ti->lifecycle.copy; if (copy) { - int32_t count = ecs_vector_count(column->data); - ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, to_alloc); - ecs_vector_set_count_t(&dst_vec, size, alignment, count); - void *dst_ptr = ecs_vector_first_t(dst_vec, size, alignment); - + ecs_column_t dst = ecs_storage_copy(column, size); + int32_t count = ecs_storage_count(column); + void *dst_ptr = ecs_storage_first(&dst); + void *src_ptr = ecs_storage_first(column); + ecs_xtor_t ctor = ti->lifecycle.ctor; if (ctor) { ctor(dst_ptr, count, ti); } - void *src_ptr = ecs_vector_first_t(column->data, size, alignment); copy(dst_ptr, src_ptr, count, ti); - - column->data = dst_vec; + *column = dst; } else { - column->data = ecs_vector_copy_t(column->data, size, alignment); + *column = ecs_storage_copy(column, size); } } @@ -27577,8 +27932,8 @@ void snapshot_table( ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL); l->table = table; - l->type = ecs_vector_copy(table->type, ecs_id_t); - l->data = duplicate_data(table, &table->storage); + l->type = flecs_type_copy(&table->type); + l->data = duplicate_data(table, &table->data); } static @@ -27591,7 +27946,7 @@ ecs_snapshot_t* snapshot_create( ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_force_aperiodic((ecs_world_t*)world); + ecs_run_aperiodic((ecs_world_t*)world, 0); result->world = (ecs_world_t*)world; @@ -27697,12 +28052,8 @@ void restore_unfiltered( /* If the world table no longer exists but the snapshot table does, * reinsert it */ if (!world_table && snapshot_table) { - ecs_ids_t type = { - .array = ecs_vector_first(snapshot_table->type, ecs_id_t), - .count = ecs_vector_count(snapshot_table->type) - }; - - ecs_table_t *table = flecs_table_find_or_create(world, &type); + ecs_table_t *table = flecs_table_find_or_create(world, + &snapshot_table->type); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (snapshot_table->data) { @@ -27719,8 +28070,8 @@ void restore_unfiltered( world, world_table, snapshot_table->data); } else { flecs_table_clear_data( - world, world_table, &world_table->storage); - flecs_table_init_data(world, world_table); + world, world_table, &world_table->data); + flecs_table_init_data(world_table); } /* If the snapshot table doesn't exist, this table was created after the @@ -27732,7 +28083,7 @@ void restore_unfiltered( * restored in a different table. Therefore first clear the data * from the table (which doesn't invoke triggers), and then delete * the table. */ - flecs_table_clear_data(world, world_table, &world_table->storage); + flecs_table_clear_data(world, world_table, &world_table->data); flecs_delete_table(world, world_table); /* If there is no world & snapshot table, nothing needs to be done */ @@ -27740,7 +28091,7 @@ void restore_unfiltered( if (snapshot_table) { ecs_os_free(snapshot_table->data); - ecs_os_free(snapshot_table->type); + flecs_type_free(&snapshot_table->type); } } @@ -27782,20 +28133,20 @@ void restore_filtered( ecs_data_t *data = snapshot_table->data; if (!data) { - ecs_vector_free(snapshot_table->type); + flecs_type_free(&snapshot_table->type); continue; } /* Delete entity from storage first, so that when we restore it to the * current table we can be sure that there won't be any duplicates */ - int32_t i, entity_count = ecs_vector_count(data->entities); - ecs_entity_t *entities = ecs_vector_first( - snapshot_table->data->entities, ecs_entity_t); + int32_t i, entity_count = ecs_storage_count(&data->entities); + ecs_entity_t *entities = ecs_storage_first( + &snapshot_table->data->entities); for (i = 0; i < entity_count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *r = ecs_eis_get(world, e); if (r && r->table) { - flecs_table_delete(world, r->table, &r->table->storage, + flecs_table_delete(world, r->table, ECS_RECORD_TO_ROW(r->row), true); } else { /* Make sure that the entity has the same generation count */ @@ -27807,7 +28158,7 @@ void restore_filtered( int32_t old_count = ecs_table_count(snapshot_table->table); int32_t new_count = flecs_table_data_count(snapshot_table->data); - flecs_table_merge(world, table, table, &table->storage, snapshot_table->data); + flecs_table_merge(world, table, table, &table->data, snapshot_table->data); /* Run OnSet systems for merged entities */ if (new_count) { @@ -27817,7 +28168,7 @@ void restore_filtered( ecs_os_free(snapshot_table->data->columns); ecs_os_free(snapshot_table->data); - ecs_vector_free(snapshot_table->type); + flecs_type_free(&snapshot_table->type); } } @@ -27826,7 +28177,7 @@ void ecs_snapshot_restore( ecs_world_t *world, ecs_snapshot_t *snapshot) { - ecs_force_aperiodic(world); + ecs_run_aperiodic(world, 0); if (snapshot->entity_index) { /* Unfiltered snapshots have a copy of the entity index which is @@ -27876,7 +28227,7 @@ bool ecs_snapshot_next( it->table = table; it->count = ecs_table_count(table); if (data) { - it->entities = ecs_vector_first(data->entities, ecs_entity_t); + it->entities = ecs_storage_first(&data->entities); } else { it->entities = NULL; } @@ -27912,7 +28263,7 @@ void ecs_snapshot_free( flecs_table_clear_data(snapshot->world, table, data); ecs_os_free(data); } - ecs_vector_free(snapshot_table->type); + flecs_type_free(&snapshot_table->type); } } @@ -28400,7 +28751,7 @@ ecs_entity_t ecs_system_init( system->binding_ctx = desc->binding_ctx; } if (desc->query.filter.instanced) { - system->query->filter.instanced = true; + ECS_BIT_SET(system->query->filter.flags, EcsFilterIsInstanced); } if (desc->multi_threaded) { system->multi_threaded = desc->multi_threaded; @@ -29353,9 +29704,14 @@ int append_type( ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { - ecs_type_t type = ecs_get_type(world, ent); - const ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + const ecs_id_t *ids = NULL; + int32_t i, count = 0; + + const ecs_type_t *type = ecs_get_type(world, ent); + if (type) { + ids = type->array; + count = type->count; + } flecs_json_member(buf, "ids"); flecs_json_array_push(buf); @@ -29415,9 +29771,13 @@ int append_base( ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { - ecs_type_t type = ecs_get_type(world, ent); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + const ecs_type_t *type = ecs_get_type(world, ent); + ecs_id_t *ids = NULL; + int32_t i, count = 0; + if (type) { + ids = type->array; + count = type->count; + } for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; @@ -29491,9 +29851,13 @@ int ecs_entity_to_json_buf( } #endif - ecs_type_t type = ecs_get_type(world, entity); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + const ecs_type_t *type = ecs_get_type(world, entity); + ecs_id_t *ids = NULL; + int32_t i, count = 0; + if (type) { + ids = type->array; + count = type->count; + } if (!desc || desc->serialize_base) { if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { @@ -30849,7 +31213,7 @@ void FlecsCoreDocImport( ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); ecs_doc_set_brief(world, EcsRemove, "Remove relation cleanup property"); ecs_doc_set_brief(world, EcsDelete, "Delete relation cleanup property"); - ecs_doc_set_brief(world, EcsThrow, "Throw relation cleanup property"); + ecs_doc_set_brief(world, EcsPanic, "Panic relation cleanup property"); ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relation"); ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relation"); ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); @@ -30870,7 +31234,7 @@ void FlecsCoreDocImport( ecs_doc_set_link(world, EcsOnDeleteObject, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsThrow, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relation"); ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relation"); @@ -32463,10 +32827,6 @@ ecs_entity_t parse_role( return ECS_XOR; } else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { return ECS_NOT; - } else if (!ecs_os_strcmp(token, TOK_ROLE_SWITCH)) { - return ECS_SWITCH; - } else if (!ecs_os_strcmp(token, TOK_ROLE_CASE)) { - return ECS_CASE; } else if (!ecs_os_strcmp(token, TOK_OVERRIDE)) { return ECS_OVERRIDE; } else if (!ecs_os_strcmp(token, TOK_ROLE_DISABLED)) { @@ -33000,7 +33360,7 @@ const char* parse_term( } if (term.role != 0) { - if (term.role != ECS_PAIR && term.role != ECS_CASE) { + if (term.role != ECS_PAIR) { ecs_parser_error(name, expr, (ptr - expr), "invalid combination of role '%s' with pair", ecs_role_str(term.role)); @@ -34118,8 +34478,6 @@ int ecs_app_set_frame_action( /* Roles */ -const ecs_id_t ECS_CASE = (ECS_ROLE | (0x7Cull << 56)); -const ecs_id_t ECS_SWITCH = (ECS_ROLE | (0x7Bull << 56)); const ecs_id_t ECS_PAIR = (ECS_ROLE | (0x7Aull << 56)); const ecs_id_t ECS_OVERRIDE = (ECS_ROLE | (0x75ull << 56)); const ecs_id_t ECS_DISABLED = (ECS_ROLE | (0x74ull << 56)); @@ -34180,10 +34538,11 @@ const ecs_entity_t EcsSymmetric = ECS_HI_COMPONENT_ID + 16; const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 17; const ecs_entity_t EcsDontInherit = ECS_HI_COMPONENT_ID + 18; const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 19; -const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 20; -const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 21; -const ecs_entity_t EcsWith = ECS_HI_COMPONENT_ID + 22; -const ecs_entity_t EcsOneOf = ECS_HI_COMPONENT_ID + 23; +const ecs_entity_t EcsUnion = ECS_HI_COMPONENT_ID + 20; +const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 21; +const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 22; +const ecs_entity_t EcsWith = ECS_HI_COMPONENT_ID + 23; +const ecs_entity_t EcsOneOf = ECS_HI_COMPONENT_ID + 24; /* Builtin relations */ const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 25; @@ -34213,7 +34572,7 @@ const ecs_entity_t EcsOnDeleteObject = ECS_HI_COMPONENT_ID + 43; /* Actions */ const ecs_entity_t EcsRemove = ECS_HI_COMPONENT_ID + 50; const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; -const ecs_entity_t EcsThrow = ECS_HI_COMPONENT_ID + 52; +const ecs_entity_t EcsPanic = ECS_HI_COMPONENT_ID + 52; /* Misc */ const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; @@ -34371,7 +34730,7 @@ void flecs_resume_readonly( if (state->is_readonly || state->is_deferred) { ecs_dbg_3("resuming readonly mode"); - ecs_force_aperiodic(world); + ecs_run_aperiodic(world, 0); /* Restore readonly state / defer count */ world->is_readonly = state->is_readonly; @@ -34570,6 +34929,7 @@ void fini_store(ecs_world_t *world) { flecs_table_release(world, &world->store.root); flecs_sparse_clear(&world->store.entity_index); flecs_hashmap_fini(&world->store.table_map); + ecs_vector_free(world->store.records); ecs_graph_edge_hdr_t *cur, *next = world->store.first_free; while ((cur = next)) { @@ -34719,7 +35079,7 @@ ecs_world_t *ecs_mini(void) { log_addons(); #ifdef FLECS_SANITIZE - ecs_trace("sanitize build, rebuild witohut FLECS_SANITIZE for (much) " + ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " "improved performance"); #elif defined(FLECS_DEBUG) ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for improved " @@ -34999,7 +35359,7 @@ void default_move_w_dtor(void *dst_ptr, void *src_ptr, void ecs_set_component_actions_w_id( ecs_world_t *world, ecs_entity_t component, - EcsComponentLifecycle *lifecycle) + const EcsComponentLifecycle *lifecycle) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); @@ -35240,192 +35600,6 @@ ecs_entity_t flecs_get_oneof( } } -static -ecs_id_record_t* new_id_record( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = ecs_os_calloc_t(ecs_id_record_t); - ecs_table_cache_init(&idr->cache); - - ecs_entity_t rel = 0, obj = 0; - if (ECS_HAS_ROLE(id, PAIR)) { - rel = ecs_pair_first(world, id); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - - /* Relation object can be 0, as tables without a ChildOf relation are - * added to the (ChildOf, 0) id record */ - obj = ECS_PAIR_SECOND(id); - if (obj) { - obj = ecs_get_alive(world, obj); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - } - - /* If id is a pair, inherit flags from relation id record */ - ecs_id_record_t *idr_r = flecs_get_id_record( - world, ECS_PAIR_FIRST(id)); - if (idr_r) { - idr->flags = (idr_r->flags & ~ECS_TYPE_INFO_INITIALIZED); - } - - /* Check constraints */ - if (obj && !ecs_id_is_wildcard(obj)) { - ecs_entity_t oneof = flecs_get_oneof(world, rel); - ecs_check( !oneof || ecs_has_pair(world, obj, EcsChildOf, oneof), - ECS_CONSTRAINT_VIOLATED, NULL); - (void)oneof; - } - - } else { - rel = id & ECS_COMPONENT_MASK; - rel = ecs_get_alive(world, rel); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - } - - /* Mark entities that are used as component/pair ids. When a tracked - * entity is deleted, cleanup policies are applied so that the store - * won't contain any tables with deleted ids. */ - - /* Flag for OnDelete policies */ - flecs_add_flag(world, rel, ECS_FLAG_OBSERVED_ID); - if (obj) { - /* Flag for OnDeleteObject policies */ - flecs_add_flag(world, obj, ECS_FLAG_OBSERVED_OBJECT); - if (ecs_has_id(world, rel, EcsAcyclic)) { - /* Flag used to determine if object should be traversed when - * propagating events or with super/subset queries */ - flecs_add_flag(world, obj, ECS_FLAG_OBSERVED_ACYCLIC); - } - } - - if (ecs_should_log_1()) { - char *id_str = ecs_id_str(world, id); - ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); - ecs_os_free(id_str); - } - - /* Update counters */ - world->info.id_create_total ++; - - if (!ecs_id_is_wildcard(id)) { - world->info.id_count ++; - - /* if id is component, attaching type info will update counters */ - world->info.tag_id_count ++; - - if (ECS_HAS_ROLE(id, PAIR)) { - world->info.pair_id_count ++; - } - } else { - world->info.wildcard_id_count ++; - } - - return idr; -error: - return NULL; -} - - -/* Cleanup id index */ -static -bool free_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_poly_assert(world, ecs_world_t); - (void)id; - - /* Force the empty table administration to be consistent if the non-empty - * list of the id record has elements */ - if (ecs_table_cache_count(&idr->cache)) { - ecs_force_aperiodic(world); - } - - /* If there are still tables in the non-empty list they're really not empty. - * We can't free the record yet. */ - if (ecs_table_cache_count(&idr->cache)) { - return false; - } - - /* If id record contains no more empty tables, free it */ - if (ecs_table_cache_empty_count(&idr->cache) == 0) { - if (ecs_should_log_1()) { - char *id_str = ecs_id_str(world, id); - ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str); - ecs_os_free(id_str); - } - - /* Update counters */ - world->info.id_delete_total ++; - - if (!ecs_id_is_wildcard(id)) { - world->info.id_count --; - - if (ECS_HAS_ROLE(id, PAIR)) { - world->info.pair_id_count --; - } - - if (idr->type_info) { - world->info.component_id_count --; - } else { - world->info.tag_id_count --; - } - } else { - world->info.wildcard_id_count --; - } - - ecs_table_cache_fini(&idr->cache); - flecs_name_index_free(idr->name_index); - ecs_os_free(idr); - return true; - } - - /* Delete empty tables */ - ecs_table_cache_iter_t cache_it; - flecs_table_cache_empty_iter(&idr->cache, &cache_it); - - const ecs_table_record_t *tr; - int32_t unreleased_count = 0; - while ((tr = flecs_table_cache_next(&cache_it, ecs_table_record_t))) { - if (!flecs_table_release(world, tr->hdr.table)) { - /* Releasing the table did not free it, which means that something - * is keeping the table alive. Cleanup of the id record will happen - * when the last reference(s) to the table are released */ - unreleased_count ++; - } - } - if (unreleased_count) { - return false; - } - - /* If all tables were deleted for this id record, the last deleted table - * should have removed the record from the world. */ - ecs_assert(flecs_get_id_record(world, id) == NULL, - ECS_INTERNAL_ERROR, NULL); - - return true; -} - -static -void fini_id_index( - ecs_world_t *world) -{ - ecs_map_iter_t it = ecs_map_iter(&world->id_index); - ecs_id_record_t *idr; - ecs_map_key_t key; - while ((idr = ecs_map_next_ptr(&it, ecs_id_record_t*, &key))) { - free_id_record(world, key, idr); - } - - ecs_map_fini(&world->id_index); - flecs_sparse_free(world->pending_tables); - flecs_sparse_free(world->pending_buffer); -} - /* Cleanup misc structures */ static void fini_misc( @@ -35448,6 +35622,10 @@ int ecs_fini( world->is_fini = true; + /* Run fini actions (simple callbacks ran when world is deleted) before + * destroying the storage */ + fini_actions(world); + /* Operations invoked during UnSet/OnRemove/destructors are deferred and * will be discarded after world cleanup */ ecs_defer_begin(world); @@ -35455,10 +35633,6 @@ int ecs_fini( /* Run UnSet/OnRemove actions for components while the store is still * unmodified by cleanup. */ fini_unset_tables(world); - - /* Run fini actions (simple callbacks ran when world is deleted) before - * destroying the storage */ - fini_actions(world); /* This will destroy all entities and components. After this point no more * user code is executed. */ @@ -35487,7 +35661,7 @@ int ecs_fini( fini_observers(world); - fini_id_index(world); + flecs_fini_id_records(world); flecs_observable_fini(&world->observable); @@ -35719,6 +35893,7 @@ ecs_type_info_t* flecs_ensure_type_info( ti_mut = flecs_sparse_ensure( world->type_info, ecs_type_info_t, component); ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); + ti_mut->component = component; } else { ti_mut = (ecs_type_info_t*)ti; } @@ -35726,18 +35901,76 @@ ecs_type_info_t* flecs_ensure_type_info( return ti_mut; } -void flecs_init_type_info( +static +void flecs_remove_type_info( + ecs_world_t *world, + ecs_entity_t component) +{ + flecs_sparse_remove(world->type_info, component); +} + +bool flecs_init_type_info_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, - ecs_size_t alignment) + ecs_size_t alignment, + const EcsComponentLifecycle *li) { - ecs_type_info_t *ti = flecs_ensure_type_info(world, component); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ti->size == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ti->alignment == 0, ECS_INTERNAL_ERROR, NULL); - ti->size = size; - ti->alignment = alignment; + bool changed = false; + + ecs_eis_ensure(world, component); + + ecs_type_info_t *ti = NULL; + if (!size || !alignment) { + ecs_assert(size == 0 && alignment == 0, + ECS_INVALID_COMPONENT_SIZE, NULL); + ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + flecs_remove_type_info(world, component); + } else { + ti = flecs_ensure_type_info(world, component); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + changed |= ti->size != size; + changed |= ti->alignment != alignment; + ti->size = size; + ti->alignment = alignment; + if (li) { + ecs_set_component_actions_w_id(world, component, li); + } + } + + /* Set type info for id record of component */ + ecs_id_record_t *idr = flecs_ensure_id_record(world, component); + changed |= flecs_set_type_info_for_id_record(world, idr, ti); + bool is_tag = idr->flags & EcsIdTag; + + /* All id records with component as relation inherit type info */ + idr = flecs_ensure_id_record(world, ecs_pair(component, EcsWildcard)); + do { + if (is_tag) { + changed |= flecs_set_type_info_for_id_record(world, idr, NULL); + } else if (ti) { + changed |= flecs_set_type_info_for_id_record(world, idr, ti); + } else if ((idr->type_info != NULL) && + (idr->type_info->component == component)) + { + changed |= flecs_set_type_info_for_id_record(world, idr, NULL); + } + } while ((idr = idr->first.next)); + + /* All non-tag id records with component as object inherit type info, + * if relation doesn't have type info */ + idr = flecs_ensure_id_record(world, ecs_pair(EcsWildcard, component)); + do { + if (!(idr->flags & EcsIdTag) && !idr->type_info) { + changed |= flecs_set_type_info_for_id_record(world, idr, ti); + } + } while ((idr = idr->first.next)); + + /* Type info of (*, component) should always point to component */ + ecs_assert(flecs_get_id_record(world, ecs_pair(EcsWildcard, component))-> + type_info == ti, ECS_INTERNAL_ERROR, NULL); + + return changed; } static @@ -35855,7 +36088,7 @@ FLECS_FLOAT ecs_frame_begin( /* Keep track of total scaled time passed in world */ world->info.world_time_total += world->info.delta_time; - ecs_force_aperiodic(world); + ecs_run_aperiodic(world, 0); return world->info.delta_time; error: @@ -35978,20 +36211,14 @@ void flecs_process_pending_tables( * 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_ids_t ids = { - .array = ecs_vector_first(table->type, ecs_id_t), - .count = ecs_vector_count(table->type) - }; - int32_t table_count = ecs_table_count(table); - flecs_emit(world, world, &(ecs_event_desc_t) { .event = table_count ? EcsOnTableFill : EcsOnTableEmpty , .table = table, - .ids = &ids, + .ids = &table->type, .observable = world, .table_event = true }); @@ -36021,137 +36248,6 @@ void flecs_table_set_empty( (uint32_t)table->id)[0] = table; } -ecs_id_record_t* flecs_ensure_id_record( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t **idr_ptr = ecs_map_ensure(&world->id_index, - ecs_id_record_t*, ecs_strip_generation(id)); - ecs_id_record_t *idr = idr_ptr[0]; - if (!idr) { - idr_ptr[0] = idr = new_id_record(world, id); - } - - return idr; -} - -void flecs_register_for_id_record( - ecs_world_t *world, - ecs_id_t id, - const ecs_table_t *table, - ecs_table_record_t *tr) -{ - ecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t *idr = flecs_ensure_id_record(world, id); - if (!idr) { - return; - } - - ecs_table_cache_insert(&idr->cache, table, &tr->hdr); - - /* When id record is used by table, make sure type info is initialized */ - if (!(idr->flags & ECS_TYPE_INFO_INITIALIZED)) { - ecs_entity_t type = ecs_get_typeid(world, id); - if (type) { - idr->type_info = flecs_get_type_info(world, type); - ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - - world->info.tag_id_count --; - world->info.component_id_count ++; - } - idr->flags |= ECS_TYPE_INFO_INITIALIZED; - } -} - -ecs_id_record_t* flecs_get_id_record( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); - - return ecs_map_get_ptr(&world->id_index, ecs_id_record_t*, - ecs_strip_generation(id)); -} - -ecs_hashmap_t* flecs_ensure_id_name_index( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t *idr = flecs_get_id_record(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(); - } - - return map; -} - -ecs_hashmap_t* flecs_get_id_name_index( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; - } - - return idr->name_index; -} - -ecs_table_record_t* flecs_get_table_record( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t* idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; - } - - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); -} - -void flecs_remove_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr) -{ - ecs_poly_assert(world, ecs_world_t); - - /* Free id record resources */ - if (free_id_record(world, id, idr)) { - /* Remove record from world index */ - ecs_map_remove(&world->id_index, ecs_strip_generation(id)); - } -} - -void flecs_clear_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr) -{ - ecs_poly_assert(world, ecs_world_t); - - if (world->is_fini) { - return; - } - - ecs_table_cache_fini_delete_all(world, &idr->cache); - - flecs_remove_id_record(world, id, idr); -} - bool ecs_id_in_use( ecs_world_t *world, ecs_id_t id) @@ -36164,59 +36260,18 @@ bool ecs_id_in_use( (ecs_table_cache_empty_count(&idr->cache) != 0); } -const ecs_table_record_t* flecs_id_record_table( - ecs_id_record_t *idr, - ecs_table_t *table) -{ - if (!idr) { - return NULL; - } - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); -} - -ecs_id_record_t* flecs_table_iter( - ecs_world_t *world, - ecs_id_t id, - ecs_table_cache_iter_t *out) -{ - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; - } - - flecs_process_pending_tables(world); - ecs_assert( flecs_sparse_count(world->pending_tables) == 0, - ECS_INTERNAL_ERROR, NULL); - - flecs_table_cache_iter(&idr->cache, out); - return idr; -} - -ecs_id_record_t* flecs_empty_table_iter( +void ecs_run_aperiodic( ecs_world_t *world, - ecs_id_t id, - ecs_table_cache_iter_t *out) -{ - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; - } - - flecs_process_pending_tables(world); - ecs_assert( flecs_sparse_count(world->pending_tables) == 0, - ECS_INTERNAL_ERROR, NULL); - - flecs_table_cache_empty_iter(&idr->cache, out); - return idr; -} - -void ecs_force_aperiodic( - ecs_world_t *world) + ecs_flags32_t flags) { ecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); - flecs_eval_component_monitors(world); + if (!flags || (flags & EcsAperiodicEmptyTableEvents)) { + flecs_process_pending_tables(world); + } + if (!flags || (flags & EcsAperiodicComponentMonitors)) { + flecs_eval_component_monitors(world); + } } int32_t ecs_delete_empty_tables( @@ -36260,7 +36315,7 @@ int32_t ecs_delete_empty_tables( continue; } - if (ecs_vector_count(table->type) < min_id_count) { + if (table->type.count < min_id_count) { continue; } @@ -36326,51 +36381,52 @@ void notify_subset( ecs_observable_t *observable, ecs_entity_t entity, ecs_entity_t event, - ecs_ids_t *ids) + const ecs_type_t *ids) { ecs_id_t pair = ecs_pair(EcsWildcard, entity); - ecs_table_cache_iter_t idt; - ecs_id_record_t *idr = flecs_table_iter(world, pair, &idt); + ecs_id_record_t *idr = flecs_get_id_record(world, pair); if (!idr) { return; } - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - ecs_id_t id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; - ecs_entity_t rel = ECS_PAIR_FIRST(id); + /* Iterate acyclic relations */ + ecs_id_record_t *cur = idr; + while ((cur = cur->acyclic.next)) { + flecs_process_pending_tables(world); - if (ecs_is_valid(world, rel) && !ecs_has_id(world, rel, EcsAcyclic)) { - /* Only notify for acyclic relations */ - continue; + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_iter(&cur->cache, &idt)) { + return; } - int32_t e, entity_count = ecs_table_count(table); - it->table = table; - it->type = table->type; - it->other_table = NULL; - it->offset = 0; - it->count = entity_count; + ecs_entity_t rel = ECS_PAIR_FIRST(cur->id); + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + + int32_t e, entity_count = ecs_table_count(table); + it->table = table; + it->other_table = NULL; + it->offset = 0; + it->count = entity_count; - /* Treat as new event as this could trigger observers again for - * different tables. */ - world->event_id ++; + /* Treat as new event as this could trigger observers again for + * different tables. */ + world->event_id ++; - flecs_set_triggers_notify(it, observable, ids, event, - ecs_pair(rel, EcsWildcard)); + flecs_set_triggers_notify(it, observable, ids, event, + ecs_pair(rel, EcsWildcard)); - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - ecs_record_t **records = ecs_vector_first( - table->storage.record_ptrs, ecs_record_t*); + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); + ecs_record_t **records = ecs_storage_first(&table->data.records); - for (e = 0; e < entity_count; e ++) { - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); - if (flags & ECS_FLAG_OBSERVED_ACYCLIC) { - /* Only notify for entities that are used in pairs with - * acyclic relations */ - notify_subset(world, it, observable, entities[e], event, ids); + for (e = 0; e < entity_count; e ++) { + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); + if (flags & EcsEntityObservedAcyclic) { + /* Only notify for entities that are used in pairs with + * acyclic relations */ + notify_subset(world, it, observable, entities[e], event, ids); + } } } } @@ -36390,7 +36446,7 @@ void flecs_emit( ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_ids_t *ids = desc->ids; + const ecs_type_t *ids = desc->ids; ecs_entity_t event = desc->event; ecs_table_t *table = desc->table; int32_t row = desc->offset; @@ -36405,7 +36461,6 @@ void flecs_emit( .world = stage, .real_world = world, .table = table, - .type = table->type, .term_count = 1, .other_table = desc->other_table, .offset = row, @@ -36427,8 +36482,8 @@ void flecs_emit( } if (count && !desc->table_event) { - ecs_record_t **recs = ecs_vector_get( - table->storage.record_ptrs, ecs_record_t*, row); + ecs_record_t **recs = ecs_storage_get_t( + &table->data.records, ecs_record_t*, row); for (i = 0; i < count; i ++) { ecs_record_t *r = recs[i]; @@ -36439,10 +36494,9 @@ void flecs_emit( } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(recs[i]->row); - if (flags & ECS_FLAG_OBSERVED_ACYCLIC) { - notify_subset(world, &it, observable, ecs_vector_first( - table->storage.entities, ecs_entity_t)[row + i], - event, ids); + if (flags & EcsEntityObservedAcyclic) { + notify_subset(world, &it, observable, ecs_storage_first_t( + &table->data.entities, ecs_entity_t)[row + i], event, ids); } } } @@ -36629,7 +36683,7 @@ int finalize_term_var( if ((identifier->set.mask == EcsNothing) && (identifier->var != EcsVarDefault)) { - term_error(world, term, name, "Invalid Nothing with entity"); + term_error(world, term, name, "invalid Nothing with entity"); return -1; } @@ -36888,7 +36942,7 @@ int finalize_term_id( term->id = pred | role; } else { if (role) { - if (role && role != ECS_PAIR && role != ECS_CASE) { + if (role && role != ECS_PAIR) { term_error(world, term, name, "invalid role for pair"); return -1; } @@ -36926,7 +36980,7 @@ int populate_from_term_id( term->role = role; - if (ECS_HAS_ROLE(term->id, PAIR) || ECS_HAS_ROLE(term->id, CASE)) { + if (ECS_HAS_ROLE(term->id, PAIR)) { pred = ECS_PAIR_FIRST(term->id); obj = ECS_PAIR_SECOND(term->id); @@ -36991,18 +37045,12 @@ int verify_term_consistency( ecs_id_t id = term->id; bool wildcard = pred == EcsWildcard || obj == EcsWildcard; - if (obj && (!role || (role != ECS_PAIR && role != ECS_CASE))) { + if (obj && (!role || (role != ECS_PAIR))) { term_error(world, term, name, "invalid role for term with pair (expected ECS_PAIR)"); return -1; } - if (role == ECS_CASE && !obj) { - term_error(world, term, name, - "missing object for term with ECS_CASE role"); - return -1; - } - if (!pred) { term_error(world, term, name, "missing predicate for term"); return -1; @@ -37013,12 +37061,12 @@ int verify_term_consistency( return -1; } - if (obj && !ECS_HAS_ROLE(id, PAIR) && !ECS_HAS_ROLE(id, CASE)) { + if (obj && !ECS_HAS_ROLE(id, PAIR)) { term_error(world, term, name, "term has object but id is not a pair"); return -1; } - if (ECS_HAS_ROLE(id, PAIR) || ECS_HAS_ROLE(id, CASE)) { + if (ECS_HAS_ROLE(id, PAIR)) { if (!wildcard) { role = ECS_ROLE_MASK & id; if (id != (role | ecs_entity_t_comb( @@ -37083,6 +37131,32 @@ int verify_term_consistency( return 0; } +ecs_id_t flecs_to_public_id( + ecs_id_t id) +{ + if (ECS_PAIR_FIRST(id) == EcsUnion) { + return ecs_pair(ECS_PAIR_SECOND(id), EcsWildcard); + } else { + return id; + } +} + +ecs_id_t flecs_from_public_id( + ecs_world_t *world, + ecs_id_t id) +{ + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_id_record_t *idr = flecs_ensure_id_record(world, + ecs_pair(first, EcsWildcard)); + if (idr->flags & EcsIdUnion) { + return ecs_pair(EcsUnion, first); + } + } + + return id; +} + bool ecs_identifier_is_0( const char *id) { @@ -37165,15 +37239,20 @@ bool ecs_id_is_pair( bool ecs_id_is_wildcard( ecs_id_t id) { - return - (id == EcsWildcard) || (ECS_HAS_ROLE(id, PAIR) && ( - (ECS_PAIR_FIRST(id) == EcsWildcard) || - (ECS_PAIR_SECOND(id) == EcsWildcard) - )) || - (id == EcsAny) || (ECS_HAS_ROLE(id, PAIR) && ( - (ECS_PAIR_FIRST(id) == EcsAny) || - (ECS_PAIR_SECOND(id) == EcsAny) - )); + if ((id == EcsWildcard) || (id == EcsAny)) { + return true; + } + + bool is_pair = ECS_HAS_ROLE(id, PAIR); + if (!is_pair) { + return false; + } + + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); + + return (first == EcsWildcard) || (second == EcsWildcard) || + (first == EcsAny) || (second == EcsAny); } bool ecs_id_is_valid( @@ -37367,22 +37446,22 @@ int ecs_filter_finalize( prev_or = is_or; if (term->subj.entity == EcsThis) { - f->match_this = true; + ECS_BIT_SET(f->flags, EcsFilterMatchThis); if (term->subj.set.mask != EcsSelf) { - f->match_only_this = false; + ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); } } else { - f->match_only_this = false; + ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); } if (term->id == EcsPrefab) { - f->match_prefab = true; + ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); } if (term->id == EcsDisabled) { - f->match_disabled = true; + ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); } - if (f->filter) { + if (ECS_BIT_IS_SET(f->flags, EcsFilterIsFilter)) { term->inout = EcsInOutFilter; } @@ -37391,14 +37470,14 @@ int ecs_filter_finalize( } if (term->oper != EcsNot || term->subj.entity != EcsThis) { - f->match_anything = false; + ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); } } f->term_count_actual = actual_count; if (filter_terms == term_count) { - f->filter = true; + ECS_BIT_SET(f->flags, EcsFilterIsFilter); } return 0; @@ -37406,7 +37485,7 @@ int ecs_filter_finalize( /* Implementation for iterable mixin */ static -void filter_iter_init( +void flecs_filter_iter_init( const ecs_world_t *world, const ecs_poly_t *poly, ecs_iter_t *iter, @@ -37445,10 +37524,10 @@ int ecs_filter_init( /* Temporarily set the fields to the values provided in desc, until the * filter has been validated. */ f.name = (char*)name; - f.filter = desc->filter; - f.instanced = desc->instanced; - f.match_empty_tables = desc->match_empty_tables; - f.match_anything = true; + ECS_BIT_COND(f.flags, EcsFilterIsFilter, desc->filter); + ECS_BIT_COND(f.flags, EcsFilterIsInstanced, desc->instanced); + ECS_BIT_COND(f.flags, EcsFilterMatchEmptyTables, desc->match_empty_tables); + ECS_BIT_SET(f.flags, EcsFilterMatchAnything); if (terms) { term_count = desc->terms_buffer_count; @@ -37558,7 +37637,7 @@ int ecs_filter_init( ecs_assert(filter_out->term_count == f.term_count, ECS_INTERNAL_ERROR, NULL); - filter_out->iterable.init = filter_iter_init; + filter_out->iterable.init = flecs_filter_iter_init; return 0; error: @@ -37806,7 +37885,7 @@ int32_t ecs_filter_find_this_var( { ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); - if (filter->match_this) { + if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { /* Filters currently only support the This variable at index 0. Only * return 0 if filter actually has terms for the This variable. */ return 0; @@ -37838,34 +37917,11 @@ bool is_any_pair( return false; } -static -ecs_id_t actual_match_id( - ecs_id_t id) -{ - /* Table types don't store CASE, so replace it with corresponding SWITCH */ - if (ECS_HAS_ROLE(id, CASE)) { - return ECS_SWITCH | ECS_PAIR_FIRST(id); - } - - /* If the id is a pair and contains Any wildcards, replace them with * */ - if (ECS_HAS_ROLE(id, PAIR)) { - if (ECS_PAIR_FIRST(id) == EcsAny) { - id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); - } - if (ECS_PAIR_SECOND(id) == EcsAny) { - id = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); - } - } - - return id; -} - static bool flecs_n_term_match_table( ecs_world_t *world, const ecs_term_t *term, const ecs_table_t *table, - ecs_type_t type, ecs_id_t *id_out, int32_t *column_out, ecs_entity_t *subject_out, @@ -37881,14 +37937,14 @@ bool flecs_n_term_match_table( const EcsType *term_type = ecs_get(world, type_id, EcsType); ecs_check(term_type != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_id_t *ids = ecs_vector_first(term_type->normalized->type, ecs_id_t); - int32_t i, count = ecs_vector_count(term_type->normalized->type); + ecs_id_t *ids = term_type->normalized->type.array; + int32_t i, count = term_type->normalized->type.count; ecs_term_t temp = *term; temp.oper = EcsAnd; for (i = 0; i < count; i ++) { temp.id = ids[i]; - bool result = flecs_term_match_table(world, &temp, table, type, id_out, + bool result = flecs_term_match_table(world, &temp, table, id_out, 0, subject_out, match_index_out, first, iter_flags); if (!result && oper == EcsAndFrom) { return false; @@ -37916,7 +37972,6 @@ bool flecs_term_match_table( ecs_world_t *world, const ecs_term_t *term, const ecs_table_t *table, - ecs_type_t type, ecs_id_t *id_out, int32_t *column_out, ecs_entity_t *subject_out, @@ -37927,7 +37982,6 @@ bool flecs_term_match_table( const ecs_term_id_t *subj = &term->subj; ecs_oper_kind_t oper = term->oper; const ecs_table_t *match_table = table; - ecs_type_t match_type = type; ecs_id_t id = term->id; ecs_entity_t subj_entity = subj->entity; @@ -37937,7 +37991,7 @@ bool flecs_term_match_table( } if (oper == EcsAndFrom || oper == EcsOrFrom) { - return flecs_n_term_match_table(world, term, table, type, id_out, column_out, + return flecs_n_term_match_table(world, term, table, id_out, column_out, subject_out, match_index_out, first, iter_flags); } @@ -37950,7 +38004,6 @@ bool flecs_term_match_table( match_table = ecs_get_table(world, subj_entity); if (match_table) { - match_type = match_table->type; } else if (oper != EcsOptional) { return false; } @@ -37959,7 +38012,7 @@ bool flecs_term_match_table( ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } - if (!match_type) { + if (!match_table) { return false; } @@ -37984,7 +38037,7 @@ bool flecs_term_match_table( bool is_any = is_any_pair(id); column = ecs_search_relation(world, match_table, - column, actual_match_id(id), subj->set.relation, subj->set.min_depth, + column, id, subj->set.relation, subj->set.min_depth, subj->set.max_depth, &source, id_out, 0, &tr); if (tr && match_index_out) { @@ -38057,11 +38110,6 @@ bool flecs_filter_match_table( ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, ECS_INTERNAL_ERROR, NULL); - ecs_type_t type = NULL; - if (table) { - type = table->type; - } - ecs_term_t *terms = filter->terms; int32_t i, count = filter->term_count; @@ -38081,7 +38129,6 @@ bool flecs_filter_match_table( ecs_term_id_t *subj = &term->subj; ecs_oper_kind_t oper = term->oper; const ecs_table_t *match_table = table; - ecs_type_t match_type = type; int32_t t_i = term->index; if (!is_or && oper == EcsOr) { @@ -38105,20 +38152,19 @@ bool flecs_filter_match_table( if (subj_entity != EcsThis) { match_table = ecs_get_table(world, subj_entity); - if (match_table) { - match_type = match_table->type; - } else { - match_type = NULL; - } } else { + if (ECS_BIT_IS_SET(iter_flags, EcsIterIgnoreThis)) { + or_result = true; + continue; + } + /* If filter contains This terms, table must be provided */ ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } int32_t match_index = 0; - bool result = flecs_term_match_table(world, term, match_table, - match_type, + bool result = flecs_term_match_table(world, term, match_table, ids ? &ids[t_i] : NULL, columns ? &columns[t_i] : NULL, subjects ? &subjects[t_i] : NULL, @@ -38210,8 +38256,7 @@ void term_iter_init( iter->term = *term; if (subj->set.mask == EcsDefaultSet || subj->set.mask & EcsSelf) { - iter->self_index = flecs_get_id_record(world, - actual_match_id(term->id)); + iter->self_index = flecs_get_query_id_record(world, term->id); } if (subj->set.mask & EcsSuperSet) { @@ -38235,7 +38280,6 @@ ecs_iter_t ecs_term_iter( { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term->id != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); @@ -38302,7 +38346,7 @@ ecs_iter_t ecs_term_chain_iter( } static -const ecs_table_record_t *next_table( +const ecs_table_record_t *flecs_term_iter_next_table( ecs_term_iter_t *iter) { ecs_id_record_t *idr = iter->cur; @@ -38323,7 +38367,7 @@ const ecs_table_record_t *next_table( } static -bool iter_find_superset( +bool flecs_term_iter_find_superset( ecs_world_t *world, ecs_table_t *table, ecs_term_t *term, @@ -38351,7 +38395,7 @@ bool iter_find_superset( } static -bool term_iter_next( +bool flecs_term_iter_next( ecs_world_t *world, ecs_term_iter_t *iter, bool match_prefab, @@ -38372,19 +38416,18 @@ bool term_iter_next( world, table, iter->last_column + 1, term->id, 0); iter->column = iter->last_column + 1; if (iter->last_column >= 0) { - iter->id = ecs_vector_get( - table->type, ecs_id_t, iter->last_column)[0]; + iter->id = table->type.array[iter->last_column]; } } } if (!table) { - if (!(tr = next_table(iter))) { + if (!(tr = flecs_term_iter_next_table(iter))) { if (iter->cur != iter->set_index && iter->set_index != NULL) { iter->cur = iter->set_index; flecs_table_cache_iter(&iter->set_index->cache, &iter->it); iter->index = 0; - tr = next_table(iter); + tr = flecs_term_iter_next_table(iter); } if (!tr) { @@ -38410,7 +38453,7 @@ bool term_iter_next( iter->cur_match = 0; iter->last_column = tr->column; iter->column = tr->column + 1; - iter->id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; + iter->id = flecs_to_public_id(table->type.array[tr->column]); } if (iter->cur == iter->set_index) { @@ -38422,11 +38465,16 @@ bool term_iter_next( } } - if (!iter_find_superset( + if (!flecs_term_iter_find_superset( world, table, term, &source, &iter->id, &iter->column)) { continue; } + + /* The tr->count field refers to the number of relation instances, + * not to the number of matches. Superset terms can only yield a + * single match. */ + iter->match_count = 1; } break; @@ -38438,7 +38486,7 @@ bool term_iter_next( } static -bool term_iter_set_table( +bool flecs_term_iter_set_table( ecs_world_t *world, ecs_term_iter_t *iter, ecs_table_t *table) @@ -38451,7 +38499,7 @@ bool term_iter_set_table( iter->match_count = tr->count; iter->last_column = tr->column; iter->column = tr->column + 1; - iter->id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; + iter->id = flecs_to_public_id(table->type.array[tr->column]); } } @@ -38459,8 +38507,8 @@ bool term_iter_set_table( idr = iter->set_index; if (idr) { tr = ecs_table_cache_get(&idr->cache, table); - if (!iter_find_superset(world, table, &iter->term, &iter->subject, - &iter->id, &iter->column)) + if (!flecs_term_iter_find_superset(world, table, &iter->term, + &iter->subject, &iter->id, &iter->column)) { return false; } @@ -38515,14 +38563,14 @@ bool ecs_term_next( } table = chain_it->table; - match = flecs_term_match_table(world, term, table, table->type, + match = flecs_term_match_table(world, term, table, it->ids, it->columns, it->subjects, it->match_indices, true, it->flags); } while (!match); goto yield; } else { - if (!term_iter_next(world, iter, false, false)) { + if (!flecs_term_iter_next(world, iter, false, false)) { goto done; } @@ -38544,7 +38592,7 @@ bool ecs_term_next( } static -const ecs_filter_t* init_filter_iter( +const ecs_filter_t* flecs_init_filter_iter( const ecs_world_t *world, ecs_iter_t *it, const ecs_filter_t *filter) @@ -38558,10 +38606,6 @@ const ecs_filter_t* init_filter_iter( iter->filter.terms = iter->filter.term_cache; } - int filter_invalid = ecs_filter_finalize(world, &iter->filter); - ecs_assert(!filter_invalid, ECS_INTERNAL_ERROR, NULL); - (void)filter_invalid; - ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, ECS_INTERNAL_ERROR, NULL); } else { @@ -38600,8 +38644,7 @@ int32_t ecs_filter_pivot_term( continue; } - ecs_id_record_t *idr = flecs_get_id_record(world, - actual_match_id(id)); + ecs_id_record_t *idr = flecs_get_query_id_record(world, id); if (!idr) { /* If one of the terms does not match with any data, iterator * should not return anything */ @@ -38627,9 +38670,10 @@ int32_t ecs_filter_pivot_term( return -2; } -ecs_iter_t ecs_filter_iter( +ecs_iter_t flecs_filter_iter_w_flags( const ecs_world_t *stage, - const ecs_filter_t *filter) + const ecs_filter_t *filter, + ecs_flags32_t flags) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); @@ -38637,29 +38681,31 @@ ecs_iter_t ecs_filter_iter( flecs_process_pending_tables(world); - bool instanced = filter ? filter->instanced : false; - ecs_iter_t it = { .real_world = (ecs_world_t*)world, .world = (ecs_world_t*)stage, .terms = filter ? filter->terms : NULL, .next = ecs_filter_next, - .flags = instanced ? EcsIterIsInstanced : 0 + .flags = flags }; ecs_filter_iter_t *iter = &it.priv.iter.filter; + iter->pivot_term = -1; - filter = init_filter_iter(world, &it, filter); + filter = flecs_init_filter_iter(world, &it, filter); + ECS_BIT_COND(it.flags, EcsIterIsInstanced, + ECS_BIT_IS_SET(filter->flags, EcsFilterIsInstanced)); /* Find term that represents smallest superset */ - if (filter->match_this) { + if (ECS_BIT_IS_SET(flags, EcsIterIgnoreThis)) { + term_iter_init_no_data(&iter->term_iter); + } else if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { ecs_term_t *terms = filter->terms; int32_t pivot_term = -1; ecs_check(terms != NULL, ECS_INVALID_PARAMETER, NULL); - iter->kind = EcsIterEvalTables; - pivot_term = ecs_filter_pivot_term(world, filter); + iter->kind = EcsIterEvalTables; iter->pivot_term = pivot_term; if (pivot_term == -2) { @@ -38669,20 +38715,18 @@ ecs_iter_t ecs_filter_iter( /* No terms meet the criteria to be a pivot term, evaluate filter * against all tables */ term_iter_init_wildcard(world, &iter->term_iter, - filter->match_empty_tables); + ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); } else { ecs_assert(pivot_term >= 0, ECS_INTERNAL_ERROR, NULL); term_iter_init(world, &terms[pivot_term], &iter->term_iter, - filter->match_empty_tables); + ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); } } else { - if (!filter->match_anything) { - iter->kind = EcsIterEvalCondition; + if (!ECS_BIT_IS_SET(filter->flags, EcsFilterMatchAnything)) { term_iter_init_no_data(&iter->term_iter); } else { iter->kind = EcsIterEvalNone; } - iter->pivot_term = -1; } if (filter->terms == filter->term_cache) { @@ -38692,11 +38736,10 @@ ecs_iter_t ecs_filter_iter( iter->filter.terms = NULL; } - if (filter->filter) { - ECS_BIT_SET(it.flags, EcsIterIsFilter); - } + ECS_BIT_COND(it.flags, EcsIterIsFilter, + ECS_BIT_IS_SET(filter->flags, EcsFilterIsFilter)); - if (filter->match_this) { + if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { /* Make space for one variable if the filter has terms for This var */ it.variable_count = 1; @@ -38711,6 +38754,13 @@ ecs_iter_t ecs_filter_iter( return (ecs_iter_t){ 0 }; } +ecs_iter_t ecs_filter_iter( + const ecs_world_t *stage, + const ecs_filter_t *filter) +{ + return flecs_filter_iter_w_flags(stage, filter, 0); +} + ecs_iter_t ecs_filter_chain_iter( const ecs_iter_t *chain_it, const ecs_filter_t *filter) @@ -38726,7 +38776,7 @@ ecs_iter_t ecs_filter_chain_iter( flecs_iter_init(&it, flecs_iter_cache_all); ecs_filter_iter_t *iter = &it.priv.iter.filter; - init_filter_iter(it.world, &it, filter); + flecs_init_filter_iter(it.world, &it, filter); iter->kind = EcsIterEvalChain; @@ -38825,7 +38875,7 @@ bool ecs_filter_next_instanced( if (first) { if (kind != EcsIterEvalCondition) { /* Check if this variable was constrained */ - if (this_table != NULL) { + if (this_table != NULL) { /* If this is the first match of a new result and the * previous result was equal to the value of a * constrained var, there's nothing left to iterate */ @@ -38835,7 +38885,9 @@ bool ecs_filter_next_instanced( /* If table doesn't match term iterator, it doesn't * match filter. */ - if (!term_iter_set_table(world, term_iter, this_table)){ + if (!flecs_term_iter_set_table( + world, term_iter, this_table)) + { goto done; } @@ -38846,8 +38898,11 @@ bool ecs_filter_next_instanced( /* If This variable is not constrained, iterate as usual */ } else { /* Find new match, starting with the leading term */ - if (!term_iter_next(world, term_iter, - filter->match_prefab, filter->match_disabled)) + if (!flecs_term_iter_next(world, term_iter, + ECS_BIT_IS_SET(filter->flags, + EcsFilterMatchPrefab), + ECS_BIT_IS_SET(filter->flags, + EcsFilterMatchDisabled))) { goto done; } @@ -38922,6 +38977,9 @@ bool ecs_filter_next_instanced( } } + /* If matches_left > 0 we should've found at least one match */ + ecs_assert(i >= 0, ECS_INTERNAL_ERROR, NULL); + /* Progress first term to next match (must be at least one) */ int32_t column = it->columns[i]; if (column < 0) { @@ -38932,15 +38990,14 @@ bool ecs_filter_next_instanced( it->columns[i] = column + 1; flecs_term_match_table(world, &filter->terms[i], table, - table->type, &it->ids[i], &it->columns[i], &it->subjects[i], + &it->ids[i], &it->columns[i], &it->subjects[i], &it->match_indices[i], false, it->flags); /* Reset remaining terms (if any) to first match */ for (j = i + 1; j < count; j ++) { flecs_term_match_table(world, &filter->terms[j], table, - table->type, &it->ids[j], &it->columns[j], - &it->subjects[j], &it->match_indices[j], true, - it->flags); + &it->ids[j], &it->columns[j], &it->subjects[j], + &it->match_indices[j], true, it->flags); } } @@ -38978,7 +39035,9 @@ int32_t type_search( if (tr) { int32_t r = tr->column; if (tr_out) tr_out[0] = tr; - if (id_out) id_out[0] = ids[r]; + if (id_out) { + id_out[0] = flecs_to_public_id(ids[r]); + } return r; } @@ -38997,12 +39056,13 @@ int32_t type_offset_search( ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!ECS_HAS_ROLE(id, CASE), ECS_INVALID_PARAMETER, NULL); while (offset < count) { ecs_id_t type_id = ids[offset ++]; if (ecs_id_match(type_id, id)) { - if (id_out) id_out[0] = type_id; + if (id_out) { + id_out[0] = flecs_to_public_id(type_id); + } return offset - 1; } } @@ -39017,10 +39077,10 @@ bool type_can_inherit_id( const ecs_id_record_t *idr, ecs_id_t id) { - if (idr->flags & ECS_ID_DONT_INHERIT) { + if (idr->flags & EcsIdDontInherit) { return false; } - if (idr->flags & ECS_ID_EXCLUSIVE) { + if (idr->flags & EcsIdExclusive) { if (ECS_HAS_ROLE(id, PAIR)) { ecs_entity_t er = ECS_PAIR_FIRST(id); if (flecs_get_table_record( @@ -39050,8 +39110,8 @@ int32_t type_search_relation( ecs_table_record_t **tr_out) { ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t count = ecs_vector_count(type); + ecs_id_t *ids = type.array; + int32_t count = type.count; if (min_depth <= 0) { if (offset) { @@ -39103,6 +39163,8 @@ int32_t type_search_relation( ecs_table_t *obj_table = rec->table; if (obj_table) { + ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); + r = type_search_relation(world, obj_table, 0, id, idr, rel, idr_r, min_depth - 1, max_depth - 1, subject_out, id_out, depth_out, tr_out); @@ -39157,10 +39219,7 @@ int32_t ecs_search_relation( ecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - bool is_case = ECS_HAS_ROLE(id, CASE); - id = is_case * (ECS_SWITCH | ECS_PAIR_FIRST(id)) + !is_case * id; - - ecs_id_record_t *idr = flecs_get_id_record(world, id); + ecs_id_record_t *idr = flecs_get_query_id_record(world, id); if (!idr) { return -1; } @@ -39185,13 +39244,13 @@ int32_t ecs_search( ecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_id_record_t *idr = flecs_get_id_record(world, id); + ecs_id_record_t *idr = flecs_get_query_id_record(world, id); if (!idr) { return -1; } ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + ecs_id_t *ids = type.array; return type_search(table, idr, ids, id_out, NULL); } @@ -39211,8 +39270,8 @@ int32_t ecs_search_offset( ecs_poly_assert(world, ecs_world_t); ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t count = ecs_vector_count(type); + ecs_id_t *ids = type.array; + int32_t count = type.count; return type_offset_search(offset, id, ids, count, id_out); } @@ -39294,7 +39353,9 @@ bool observer_run(ecs_iter_t *it) { ecs_iter_t user_it = *it; user_it.term_count = o->filter.term_count_actual; user_it.terms = o->filter.terms; - user_it.flags = o->filter.filter ? EcsIterIsFilter : 0; + user_it.flags = 0; + ECS_BIT_COND(user_it.flags, EcsIterIsFilter, + ECS_BIT_IS_SET(o->filter.flags, EcsFilterIsFilter)); user_it.ids = NULL; user_it.columns = NULL; user_it.subjects = NULL; @@ -39547,11 +39608,20 @@ ecs_entity_t ecs_observer_init( .callback = observer_run_callback, .ctx = observer, .binding_ctx = desc->binding_ctx, - .match_prefab = observer->filter.match_prefab, - .match_disabled = observer->filter.match_disabled, + .match_prefab = ECS_BIT_IS_SET(filter->flags, EcsFilterMatchPrefab), + .match_disabled = ECS_BIT_IS_SET(filter->flags, EcsFilterMatchDisabled), .last_event_id = &observer->last_event_id }; + bool optional_only = true; + for (i = 0; i < filter->term_count; i ++) { + if (filter->terms[i].oper != EcsOptional) { + if (filter->terms[i].subj.entity == EcsThis) { + optional_only = false; + } + } + } + for (i = 0; i < filter->term_count; i ++) { tdesc.term = filter->terms[i]; ecs_oper_kind_t oper = tdesc.term.oper; @@ -39581,9 +39651,8 @@ ecs_entity_t ecs_observer_init( /* AndFrom & OrFrom terms insert multiple triggers */ if (oper == EcsAndFrom || oper == EcsOrFrom) { const EcsType *type = ecs_get(world, id, EcsType); - int32_t ti, ti_count = ecs_vector_count(type->normalized->type); - ecs_id_t *ti_ids = ecs_vector_first( - type->normalized->type, ecs_id_t); + int32_t ti, ti_count = type->normalized->type.count; + ecs_id_t *ti_ids = type->normalized->type.array; /* Correct operator will be applied when a trigger occurs, and * the observer is evaluated on the trigger source */ @@ -39592,6 +39661,7 @@ ecs_entity_t ecs_observer_init( tdesc.term.pred.name = NULL; tdesc.term.pred.entity = ti_ids[ti]; tdesc.term.id = ti_ids[ti]; + ecs_entity_t t = ecs_vector_add(&observer->triggers, ecs_entity_t)[0] = ecs_trigger_init(world, &tdesc); if (!t) { @@ -39601,11 +39671,24 @@ ecs_entity_t ecs_observer_init( continue; } + /* If observer only contains optional terms, match everything */ + if (optional_only) { + tdesc.term.id = EcsAny; + tdesc.term.pred.entity = EcsAny; + tdesc.term.subj.entity = EcsThis; + tdesc.term.subj.var = EcsVarIsVariable; + tdesc.term.obj.entity = 0; + } + ecs_entity_t t = ecs_vector_add(&observer->triggers, ecs_entity_t) [0] = ecs_trigger_init(world, &tdesc); if (!t) { goto error; } + + if (optional_only) { + break; + } } if (desc->entity.name) { @@ -39760,7 +39843,7 @@ void ecs_table_cache_init( ecs_table_cache_t *cache) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_init(&cache->index, ecs_table_cache_hdr_t*, 0); + ecs_map_init(&cache->index, ecs_table_cache_hdr_t*, 4); } void ecs_table_cache_fini( @@ -39809,6 +39892,46 @@ void ecs_table_cache_insert( ECS_INTERNAL_ERROR, NULL); } +void ecs_table_cache_replace( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t **oldptr = ecs_map_get(&cache->index, + ecs_table_cache_hdr_t*, table->id); + ecs_assert(oldptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *old = *oldptr; + ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; + if (prev) { + ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); + prev->next = elem; + } + if (next) { + ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); + next->prev = elem; + } + + if (cache->empty_tables.first == old) { + cache->empty_tables.first = elem; + } + if (cache->empty_tables.last == old) { + cache->empty_tables.last = elem; + } + if (cache->tables.first == old) { + cache->tables.first = elem; + } + if (cache->tables.last == old) { + cache->tables.last = elem; + } + + *oldptr = elem; + elem->prev = prev; + elem->next = next; +} + void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table) @@ -41058,14 +41181,13 @@ ecs_vector_t* add_ref( ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); } - *ref = (ecs_ref_t){0}; - ref->entity = entity; - if (size) { - ref->component = component; - ecs_get_ref_id(world, ref, entity, component); + *ref = ecs_ref_init_id(world, entity, component); } else { - ref->component = 0; + *ref = (ecs_ref_t){ + .entity = entity, + .id = 0 + }; } query->flags |= EcsQueryHasRefs; @@ -41103,15 +41225,13 @@ static void set_table_match( ecs_world_t *world, ecs_query_t *query, - ecs_query_table_t *qt, ecs_query_table_match_t *qm, ecs_table_t *table, ecs_iter_t *it) -{ - (void)qt; - +{ ecs_filter_t *filter = &query->filter; int32_t i, term_count = filter->term_count; + int32_t term_count_actual = filter->term_count_actual; ecs_term_t *terms = filter->terms; /* Reset resources in case this is an existing record */ @@ -41128,60 +41248,92 @@ void set_table_match( qm->references = NULL; } - ecs_os_memcpy_n(qm->columns, it->columns, int32_t, term_count); - ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, term_count); - ecs_os_memcpy_n(qm->subjects, it->subjects, ecs_entity_t, term_count); - ecs_os_memcpy_n(qm->sizes, it->sizes, ecs_size_t, term_count); - - /* Initialize switch/case terms */ - for (i = 0; i < term_count; i ++) { - ecs_id_t id = terms[i].id; - if (ECS_HAS_ROLE(id, CASE) && terms[i].subj.set.mask != EcsNothing) { - flecs_switch_term_t *sc = ecs_vector_add( - &qm->sparse_columns, flecs_switch_term_t); - sc->signature_column_index = i; - sc->sw_case = ecs_pair_second(world, id); - sc->sw_column = NULL; - qm->ids[i] = id; - } - } + ecs_os_memcpy_n(qm->columns, it->columns, int32_t, term_count_actual); + ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, term_count_actual); + ecs_os_memcpy_n(qm->subjects, it->subjects, ecs_entity_t, term_count_actual); + ecs_os_memcpy_n(qm->sizes, it->sizes, ecs_size_t, term_count_actual); - /* Look for disabled terms */ + /* Look for union & disabled terms */ if (table) { + if (table->flags & EcsTableHasUnion) { + for (i = 0; i < term_count; i ++) { + if (terms[i].subj.set.mask == EcsNothing) { + continue; + } + + ecs_id_t id = terms[i].id; + if (ECS_HAS_ROLE(id, PAIR) && ECS_PAIR_SECOND(id) == EcsWildcard) { + continue; + } + + int32_t actual_index = terms[i].index; + int32_t column = it->columns[actual_index]; + if (column <= 0) { + continue; + } + + ecs_id_t table_id = table->type.array[column - 1]; + if (ECS_PAIR_FIRST(table_id) != EcsUnion) { + continue; + } + + flecs_switch_term_t *sc = ecs_vector_add( + &qm->sparse_columns, flecs_switch_term_t); + sc->signature_column_index = actual_index; + sc->sw_case = ECS_PAIR_SECOND(id); + sc->sw_column = NULL; + qm->ids[actual_index] = id; + } + } if (table->flags & EcsTableHasDisabled) { for (i = 0; i < term_count; i ++) { - if (terms[i].subj.set.mask != EcsNothing) { - ecs_id_t id = it->ids[i]; - ecs_id_t bs_id = ECS_DISABLED | (id & ECS_COMPONENT_MASK); - int32_t bs_index = ecs_search(world, table, bs_id, 0); - if (bs_index != -1) { - flecs_bitset_term_t *bc = ecs_vector_add( - &qm->bitset_columns, flecs_bitset_term_t); - bc->column_index = bs_index; - bc->bs_column = NULL; - } + if (terms[i].subj.set.mask == EcsNothing) { + continue; + } + + int32_t actual_index = terms[i].index; + ecs_id_t id = it->ids[actual_index]; + ecs_id_t bs_id = ECS_DISABLED | (id & ECS_COMPONENT_MASK); + int32_t bs_index = ecs_search(world, table, bs_id, 0); + if (bs_index != -1) { + flecs_bitset_term_t *bc = ecs_vector_add( + &qm->bitset_columns, flecs_bitset_term_t); + bc->column_index = bs_index; + bc->bs_column = NULL; } } } } - /* Add references for non-This terms */ - if (!filter->match_only_this) { + /* Add references for substituted terms */ + if (!ECS_BIT_IS_SET(filter->flags, EcsFilterMatchOnlyThis)) { ecs_vector_t *refs = NULL; for (i = 0; i < term_count; i ++) { - ecs_entity_t src = it->subjects[i]; + ecs_term_t *term = &terms[i]; + if (term->subj.entity != EcsThis) { + /* non-This terms are set during iteration */ + continue; + } + + int32_t actual_index = terms[i].index; + ecs_entity_t src = it->subjects[actual_index]; ecs_size_t size = 0; if (it->sizes) { - size = it->sizes[i]; + size = it->sizes[actual_index]; } if (src) { - ecs_term_t *term = &terms[i]; - ecs_id_t id = it->ids[i]; - refs = add_ref(world, query, refs, term, id, src, size); + ecs_id_t id = it->ids[actual_index]; + ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); + + if (id) { + ecs_assert(ecs_id_is_valid(world, id), + ECS_INTERNAL_ERROR, NULL); + refs = add_ref(world, query, refs, term, id, src, size); - /* Use column index to bind term and ref */ - if (qm->columns[i] != 0) { - qm->columns[i] = -ecs_vector_count(refs); + /* Use column index to bind term and ref */ + if (qm->columns[actual_index] != 0) { + qm->columns[actual_index] = -ecs_vector_count(refs); + } } } } @@ -41192,8 +41344,6 @@ void set_table_match( ecs_vector_free(refs); } } - - ecs_assert(table == qt->hdr.table, ECS_INTERNAL_ERROR, NULL); } /** Populate query cache with tables */ @@ -41219,7 +41369,7 @@ void match_tables( } ecs_query_table_match_t *qm = add_table_match(query, qt, table); - set_table_match(world, query, qt, qm, table, &it); + set_table_match(world, query, qm, table, &it); } } @@ -41255,7 +41405,7 @@ bool match_table( } ecs_query_table_match_t *qm = add_table_match(query, qt, table); - set_table_match(world, query, qt, qm, table, &it); + set_table_match(world, query, qm, table, &it); } return qt != NULL; @@ -41340,8 +41490,8 @@ void sort_table( int32_t column_index, ecs_order_by_action_t compare) { - ecs_data_t *data = &table->storage; - if (!data->entities) { + ecs_data_t *data = &table->data; + if (!ecs_storage_count(&data->entities)) { /* Nothing to sort */ return; } @@ -41351,15 +41501,15 @@ void sort_table( return; } - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_entity_t *entities = ecs_storage_first(&data->entities); void *ptr = NULL; int32_t size = 0; if (column_index != -1) { - ecs_type_info_t *ti = &table->type_info[column_index]; + ecs_type_info_t *ti = table->type_info[column_index]; ecs_column_t *column = &data->columns[column_index]; size = ti->size; - ptr = ecs_vector_first_t(column->data, size, ti->alignment); + ptr = ecs_storage_first(column); } qsort_array(world, table, data, entities, ptr, size, 0, count - 1, compare); @@ -41421,24 +41571,20 @@ void build_sorted_table_range( for (cur = list->first; cur != end; cur = cur->next) { ecs_query_table_match_t *match = cur->match; ecs_table_t *table = match->table; - ecs_data_t *data = &table->storage; - ecs_vector_t *entities; + ecs_data_t *data = &table->data; ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); - entities = data->entities; - int32_t index = -1; if (id) { index = ecs_search(world, table->storage_table, id, 0); } if (index != -1) { - ecs_type_info_t *ti = &table->type_info[index]; + ecs_type_info_t *ti = table->type_info[index]; ecs_column_t *column = &data->columns[index]; int32_t size = ti->size; - int32_t align = ti->alignment; - helper[to_sort].ptr = ecs_vector_first_t(column->data, size, align); + helper[to_sort].ptr = ecs_storage_first(column); helper[to_sort].elem_size = size; helper[to_sort].shared = false; } else if (id) { @@ -41464,7 +41610,7 @@ void build_sorted_table_range( } helper[to_sort].match = match; - helper[to_sort].entities = ecs_vector_first(entities, ecs_entity_t); + helper[to_sort].entities = ecs_storage_first(&data->entities); helper[to_sort].row = 0; helper[to_sort].count = ecs_table_count(table); to_sort ++; @@ -41764,7 +41910,7 @@ void process_signature( if (subj->entity && subj->entity != EcsThis && subj->set.mask == EcsSelf) { - flecs_add_flag(world, term->subj.entity, ECS_FLAG_OBSERVED); + flecs_add_flag(world, term->subj.entity, EcsEntityObserved); } } @@ -41894,42 +42040,6 @@ void query_table_free( ecs_os_free(elem); } -static -bool satisfy_constraints( - ecs_world_t *world, - const ecs_filter_t *filter) -{ - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; - - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; - ecs_oper_kind_t oper = term->oper; - - if (oper == EcsOptional) { - continue; - } - - if (subj->entity != EcsThis && subj->entity) { - ecs_table_t *table = ecs_get_table(world, subj->entity); - if (!table) { - goto no_match; - } - - if (!flecs_term_match_table(world, term, table, table->type, NULL, - NULL, NULL, NULL, true, 0)) - { - goto no_match; - } - } - } - - return true; -no_match: - return false; -} - static void unmatch_table( ecs_query_t *query, @@ -41995,7 +42105,7 @@ void rematch_tables( qm = add_table_match(query, qt, table); } - set_table_match(world, query, qt, qm, table, &it); + set_table_match(world, query, qm, table, &it); if (table && ecs_table_count(table) && query->group_by) { /* If grouping is enabled, make sure match is in the right place */ @@ -42025,11 +42135,7 @@ void rematch_tables( unmatch_table(query, qt->hdr.table); } } - } - - /* Enable/disable system if constraints are (not) met. If the system is - * already dis/enabled this operation has no side effects. */ - query->constraints_satisfied = satisfy_constraints(world, &query->filter); + } } static @@ -42155,7 +42261,7 @@ void query_iter_init( static void query_on_event( ecs_iter_t *it) -{ +{ /* Because this is the observer::run callback, checking if this is event is * already handled is not done for us. */ ecs_world_t *world = it->world; @@ -42201,7 +42307,7 @@ ecs_query_t* ecs_query_init( * in the right empty/non-empty list. This ensures the query won't miss * empty/non-empty events for tables that are currently out of sync, but * change back to being in sync before processing pending events. */ - ecs_force_aperiodic(world); + ecs_run_aperiodic(world, EcsAperiodicEmptyTableEvents); result = flecs_sparse_add(world->queries, ecs_query_t); ecs_poly_init(result, ecs_query_t); @@ -42295,8 +42401,6 @@ ecs_query_t* ecs_query_init( world, result, desc->order_by_component, desc->order_by); } - result->constraints_satisfied = satisfy_constraints(world, &result->filter); - return result; error: if (result) { @@ -42394,20 +42498,25 @@ ecs_iter_t ecs_query_iter( ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); - query->constraints_satisfied = satisfy_constraints(query->world, &query->filter); - - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); + ecs_world_t *world = query->world; + ecs_poly_assert(world, ecs_world_t); + /* Process table events to ensure that the list of iterated tables doesn't + * contain empty tables. */ flecs_process_pending_tables(world); + /* If query has order_by, apply sort */ sort_tables(world, query); + /* If monitors changed, do query rematching */ if (!world->is_readonly && query->flags & EcsQueryHasRefs) { flecs_eval_component_monitors(world); } query->prev_match_count = query->match_count; + /* Prepare iterator */ + int32_t table_count; if (query->table_slices) { table_count = ecs_vector_count(query->table_slices); @@ -42425,12 +42534,10 @@ ecs_iter_t ecs_query_iter( } ecs_flags32_t flags = 0; - if (query->filter.filter) { - ECS_BIT_SET(flags, EcsIterIsFilter); - } - if (query->filter.instanced) { - ECS_BIT_SET(flags, EcsIterIsInstanced); - } + ECS_BIT_COND(flags, EcsIterIsFilter, ECS_BIT_IS_SET(query->filter.flags, + EcsFilterIsFilter)); + ECS_BIT_COND(flags, EcsIterIsInstanced, ECS_BIT_IS_SET(query->filter.flags, + EcsFilterIsInstanced)); ecs_iter_t result = { .real_world = world, @@ -42443,13 +42550,46 @@ ecs_iter_t ecs_query_iter( .next = ecs_query_next, }; - /* Query populates the iterator with arrays from the cache, ensure they - * don't get overwritten by flecs_iter_validate */ - flecs_iter_init(&result, flecs_iter_cache_ptrs); + ecs_filter_t *filter = &query->filter; + if (filter->flags & EcsFilterMatchOnlyThis) { + /* When the query only matches This terms, we can reuse the storage from + * the cache to populate the iterator */ + flecs_iter_init(&result, flecs_iter_cache_ptrs); + } else { + /* Check if non-This terms (like singleton terms) still match */ + ecs_iter_t fit = flecs_filter_iter_w_flags( + world, &query->filter, EcsIterIgnoreThis); + if (!ecs_filter_next(&fit)) { + /* No match, so return nothing */ + ecs_iter_fini(&result); + goto noresults; + } + + /* Initialize iterator with private storage for ids, ptrs, sizes and + * columns so we have a place to store the non-This data */ + flecs_iter_init(&result, flecs_iter_cache_ptrs | flecs_iter_cache_ids | + flecs_iter_cache_columns | flecs_iter_cache_sizes); + + /* Copy the data */ + int32_t term_count = filter->term_count_actual; + if (term_count) { + if (result.ptrs) { + ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, term_count); + } + ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, term_count); + ecs_os_memcpy_n(result.sizes, fit.sizes, ecs_size_t, term_count); + ecs_os_memcpy_n(result.columns, fit.columns, int32_t, term_count); + } + ecs_iter_fini(&fit); + } return result; error: - return (ecs_iter_t){ 0 }; +noresults: + return (ecs_iter_t) { + .flags = EcsIterNoResults, + .next = ecs_query_next + }; } static @@ -42468,26 +42608,25 @@ int find_smallest_column( flecs_switch_term_t *sparse_column = &sparse_column_array[i]; /* Pointer to the switch column struct of the table */ - ecs_sw_column_t *sc = sparse_column->sw_column; + ecs_switch_t *sw = sparse_column->sw_column; /* If the sparse column pointer hadn't been retrieved yet, do it now */ - if (!sc) { + if (!sw) { /* Get the table column index from the signature column index */ int32_t table_column_index = table_data->columns[ sparse_column->signature_column_index]; /* Translate the table column index to switch column index */ - table_column_index -= table->sw_column_offset; + table_column_index -= table->sw_offset; ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); /* Get the sparse column */ - ecs_data_t *data = &table->storage; - sc = sparse_column->sw_column = + ecs_data_t *data = &table->data; + sw = sparse_column->sw_column = &data->sw_columns[table_column_index - 1]; } /* Find the smallest column */ - ecs_switch_t *sw = sc->data; int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); if (case_count < min) { min = case_count; @@ -42526,7 +42665,7 @@ int sparse_column_next( flecs_switch_term_t *columns = ecs_vector_first( sparse_columns, flecs_switch_term_t); flecs_switch_term_t *column = &columns[sparse_smallest]; - ecs_switch_t *sw, *sw_smallest = column->sw_column->data; + ecs_switch_t *sw, *sw_smallest = column->sw_column; ecs_entity_t case_smallest = column->sw_case; /* Find next entity to iterate in sparse column */ @@ -42564,7 +42703,7 @@ int sparse_column_next( } column = &columns[i]; - sw = column->sw_column->data; + sw = column->sw_column; if (flecs_switch_get(sw, first) != column->sw_case) { first = flecs_switch_next(sw_smallest, first); @@ -42646,23 +42785,22 @@ int bitset_column_next( int32_t i, count = ecs_vector_count(bitset_columns); flecs_bitset_term_t *columns = ecs_vector_first( bitset_columns, flecs_bitset_term_t); - int32_t bs_offset = table->bs_column_offset; + int32_t bs_offset = table->bs_offset; int32_t first = iter->bitset_first; int32_t last = 0; for (i = 0; i < count; i ++) { flecs_bitset_term_t *column = &columns[i]; - ecs_bs_column_t *bs_column = columns[i].bs_column; + ecs_bitset_t *bs = columns[i].bs_column; - if (!bs_column) { + if (!bs) { int32_t index = column->column_index; ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); - bs_column = &table->storage.bs_columns[index - bs_offset]; - columns[i].bs_column = bs_column; + bs = &table->data.bs_columns[index - bs_offset]; + columns[i].bs_column = bs; } - ecs_bitset_t *bs = &bs_column->data; int32_t bs_elem_count = bs->count; int32_t bs_block = first >> 6; int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; @@ -42839,19 +42977,21 @@ bool ecs_query_next_instanced( ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + if (ECS_BIT_IS_SET(it->flags, EcsIterNoResults)) { + goto done; + } + + ECS_BIT_SET(it->flags, EcsIterIsValid); + ecs_query_iter_t *iter = &it->priv.iter.query; ecs_query_t *query = iter->query; ecs_world_t *world = query->world; ecs_flags32_t flags = query->flags; - (void)world; - - ECS_BIT_SET(it->flags, EcsIterIsValid); + const ecs_filter_t *filter = &query->filter; + bool only_this = filter->flags & EcsFilterMatchOnlyThis; ecs_poly_assert(world, ecs_world_t); - - if (!query->constraints_satisfied) { - goto done; - } + (void)world; query_iter_cursor_t cur; ecs_query_table_node_t *node, *next, *prev; @@ -42938,10 +43078,28 @@ bool ecs_query_next_instanced( cur.first = 0; } - it->ids = match->ids; - it->columns = match->columns; + if (only_this) { + /* If query has only This terms, reuse cache storage */ + it->ids = match->ids; + it->columns = match->columns; + it->sizes = match->sizes; + } else { + /* If query has non-This terms make sure not to overwrite them */ + int32_t t, term_count = filter->term_count; + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &filter->terms[t]; + if (term->subj.entity != EcsThis) { + continue; + } + + int32_t actual_index = term->index; + it->ids[actual_index] = match->ids[actual_index]; + it->columns[actual_index] = match->columns[actual_index]; + it->sizes[actual_index] = match->sizes[actual_index]; + } + } + it->subjects = match->subjects; - it->sizes = match->sizes; it->references = match->references; it->instance_count = 0; @@ -43041,19 +43199,20 @@ bool ecs_query_orphaned( /* Marker object used to differentiate a component vs. a tag edge */ static ecs_table_diff_t ecs_table_edge_is_component; +/* Id sequence (type) utilities */ + static -uint64_t ids_hash(const void *ptr) { - const ecs_ids_t *type = ptr; +uint64_t flecs_type_hash(const void *ptr) { + const ecs_type_t *type = ptr; ecs_id_t *ids = type->array; int32_t count = type->count; - uint64_t hash = flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); - return hash; + return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); } static -int ids_compare(const void *ptr_1, const void *ptr_2) { - const ecs_ids_t *type_1 = ptr_1; - const ecs_ids_t *type_2 = ptr_2; +int flecs_type_compare(const void *ptr_1, const void *ptr_2) { + const ecs_type_t *type_1 = ptr_1; + const ecs_type_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; @@ -43064,94 +43223,240 @@ int ids_compare(const void *ptr_1, const void *ptr_2) { const ecs_id_t *ids_1 = type_1->array; const ecs_id_t *ids_2 = type_2->array; + int result = 0; int32_t i; - for (i = 0; i < count_1; i ++) { + for (i = 0; !result && (i < count_1); i ++) { ecs_id_t id_1 = ids_1[i]; ecs_id_t id_2 = ids_2[i]; - - if (id_1 != id_2) { - return (id_1 > id_2) - (id_1 < id_2); - } + result = (id_1 > id_2) - (id_1 < id_2); } - return 0; + return result; } void flecs_table_hashmap_init(ecs_hashmap_t *hm) { - flecs_hashmap_init(hm, ecs_ids_t, ecs_table_t*, ids_hash, ids_compare); + flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, + flecs_type_hash, flecs_type_compare); } -const EcsComponent* flecs_component_from_id( - const ecs_world_t *world, - ecs_entity_t e) +/* Find location where to insert id into type */ +static +int flecs_type_find_insert( + const ecs_type_t *type, + int32_t offset, + ecs_id_t to_add) { - ecs_entity_t pair = 0; + ecs_id_t *array = type->array; + int32_t i, count = type->count; - /* If this is a pair, get the pair component from the identifier */ - if (ECS_HAS_ROLE(e, PAIR)) { - pair = e; - e = ecs_get_alive(world, ECS_PAIR_FIRST(e)); + for (i = offset; i < count; i ++) { + ecs_id_t id = array[i]; + if (id == to_add) { + return -1; + } + if (id > to_add) { + return i; + } + } + return i; +} - if (ecs_has_id(world, e, EcsTag)) { - return NULL; +/* Find location of id in type */ +static +int flecs_type_find( + const ecs_type_t *type, + ecs_id_t id) +{ + ecs_id_t *array = type->array; + int32_t i, count = type->count; + + for (i = 0; i < count; i ++) { + ecs_id_t cur = array[i]; + if (ecs_id_match(cur, id)) { + return i; + } + if (cur > id) { + return -1; } } - if (e & ECS_ROLE_MASK) { - return NULL; + return -1; +} + +/* Count number of matching ids */ +static +int flecs_type_count_matches( + const ecs_type_t *type, + ecs_id_t wildcard, + int32_t offset) +{ + ecs_id_t *array = type->array; + int32_t i = offset, count = type->count; + + for (; i < count; i ++) { + ecs_id_t cur = array[i]; + if (!ecs_id_match(cur, wildcard)) { + break; + } } - const EcsComponent *component = ecs_get(world, e, EcsComponent); - if ((!component || !component->size) && pair) { - /* If this is a pair column and the pair is not a component, use - * the component type of the component the pair is applied to. */ - e = ECS_PAIR_SECOND(pair); + return i - offset; +} + +/* Create type from source type with id */ +static +int flecs_type_new_with( + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t with) +{ + ecs_id_t *src_array = src->array; + int32_t at = flecs_type_find_insert(src, 0, with); + if (at == -1) { + return -1; + } - /* Because generations are not stored in the pair, get the currently - * alive id */ - e = ecs_get_alive(world, e); + int32_t dst_count = src->count + 1; + ecs_id_t *dst_array = ecs_os_malloc_n(ecs_id_t, dst_count); + dst->count = dst_count; + dst->array = dst_array; - /* If a pair is used with a not alive id, the pair is not valid */ - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } - component = ecs_get(world, e, EcsComponent); + int32_t remain = src->count - at; + if (remain) { + ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); } - return component; + dst_array[at] = with; + + return 0; } -/* Ensure the ids used in the columns exist */ +/* Create type from source type without ids matching wildcard */ static -int32_t ensure_columns( - ecs_world_t *world, - ecs_table_t *table) +int flecs_type_new_filtered( + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t wildcard, + int32_t at) { - int32_t i, count = ecs_vector_count(table->type); - ecs_id_t* ids = ecs_vector_first(table->type, ecs_id_t); + *dst = flecs_type_copy(src); + ecs_id_t *dst_array = dst->array; + ecs_id_t *src_array = src->array; + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } - for (i = 0; i < count; i++) { - ecs_ensure_id(world, ids[i]); + int32_t i = at + 1, w = at, count = src->count; + for (; i < count; i ++) { + ecs_id_t id = src_array[i]; + if (!ecs_id_match(id, wildcard)) { + dst_array[w] = id; + w ++; + } } - return count; + dst->count = w; + if (w != count) { + dst->array = ecs_os_realloc_n(dst->array, ecs_id_t, w); + } + + return 0; } +/* Create type from source type without id */ static -ecs_vector_t* ids_to_vector( - const ecs_ids_t *entities) +int flecs_type_new_without( + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t without) { - if (entities->count) { - ecs_vector_t *result = NULL; - ecs_vector_set_count(&result, ecs_entity_t, entities->count); - ecs_entity_t *array = ecs_vector_first(result, ecs_entity_t); - ecs_os_memcpy_n(array, entities->array, ecs_entity_t, entities->count); - return result; - } else { - return NULL; + ecs_id_t *src_array = src->array; + int32_t count = 1, at = flecs_type_find(src, without); + if (at == -1) { + return -1; + } + + int32_t src_count = src->count; + if (src_count == 1) { + dst->array = NULL; + dst->count = 0; + return 0; + } + + if (ecs_id_is_wildcard(without)) { + if (ECS_HAS_ROLE(without, PAIR)) { + ecs_entity_t r = ECS_PAIR_FIRST(without); + ecs_entity_t o = ECS_PAIR_SECOND(without); + if (r == EcsWildcard && o != EcsWildcard) { + return flecs_type_new_filtered(dst, src, without, at); + } + } + count += flecs_type_count_matches(src, without, at + 1); + } + + int32_t dst_count = src_count - count; + dst->count = dst_count; + if (!dst_count) { + dst->array = NULL; + return 0; } + + ecs_id_t *dst_array = ecs_os_malloc_n(ecs_id_t, dst_count); + dst->array = dst_array; + + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } + + int32_t remain = dst_count - at; + if (remain) { + ecs_os_memcpy_n( + &dst_array[at], &src_array[at + count], ecs_id_t, remain); + } + + return 0; +} + +/* Copy type */ +ecs_type_t flecs_type_copy( + const ecs_type_t *src) +{ + int32_t src_count = src->count; + return (ecs_type_t) { + .array = ecs_os_memdup_n(src->array, ecs_id_t, src_count), + .count = src_count + }; } +/* Free type */ +void flecs_type_free( + ecs_type_t *type) +{ + ecs_os_free(type->array); +} + +/* Add to type */ +static +void flecs_type_add( + ecs_type_t *type, + ecs_id_t add) +{ + ecs_type_t new_type; + int res = flecs_type_new_with(&new_type, type, add); + if (res != -1) { + flecs_type_free(type); + type->array = new_type.array; + type->count = new_type.count; + } +} + +/* Graph edge utilities */ + static void table_diff_free( ecs_table_diff_t *diff) @@ -43308,206 +43613,6 @@ typedef struct { int32_t count; } id_first_count_t; -static -void set_trigger_flags_for_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) -{ - /* Set flags if triggers are registered for table */ - if (flecs_check_triggers_for_event(world, id, EcsOnAdd)) { - table->flags |= EcsTableHasOnAdd; - } - if (flecs_check_triggers_for_event(world, id, EcsOnRemove)) { - table->flags |= EcsTableHasOnRemove; - } - if (flecs_check_triggers_for_event(world, id, EcsOnSet)) { - table->flags |= EcsTableHasOnSet; - } - if (flecs_check_triggers_for_event(world, id, EcsUnSet)) { - table->flags |= EcsTableHasUnSet; - } -} - -static -void register_table_for_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - int32_t column, - int32_t count, - ecs_table_record_t *tr) -{ - flecs_register_for_id_record(world, id, table, tr); - tr->column = column; - tr->count = count; - tr->id = id; - set_trigger_flags_for_id(world, table, id); - ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); -} - -static -void flecs_table_records_register( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - int32_t count = ecs_vector_count(table->type); - - if (!count) { - return; - } - - /* Count number of unique ids, pairs, relations and objects so we can figure - * out how many table records are needed for this table. */ - int32_t id_count = 0, pair_count = 0, type_flag_count = 0; - int32_t first_id = -1, first_pair = -1; - ecs_map_t relations = ECS_MAP_INIT(0), objects = ECS_MAP_INIT(0); - bool has_childof = false; - - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - ecs_entity_t rel = 0, obj = 0; - - if (ECS_HAS_ROLE(id, PAIR)) { - id_first_count_t *r; - - rel = ECS_PAIR_FIRST(id); - obj = ECS_PAIR_SECOND(id); - - if (0 == pair_count ++) { - first_pair = i; - } - - if (rel == EcsChildOf) { - has_childof = true; - } - - if (!ecs_map_is_initialized(&relations)) { - ecs_map_init(&relations, id_first_count_t, count); - ecs_map_init(&objects, id_first_count_t, count); - } - - r = ecs_map_ensure(&relations, id_first_count_t, rel); - if ((++r->count) == 1) { - r->first = i; - } - - r = ecs_map_ensure(&objects, id_first_count_t, obj); - if ((++r->count) == 1) { - r->first = i; - } - } else { - rel = id & ECS_COMPONENT_MASK; - if (rel != id) { - type_flag_count ++; - } - - if (0 == id_count ++) { - first_id = i; - } - } - } - - int32_t record_count = count + type_flag_count + (id_count != 0) + - (pair_count != 0) + ecs_map_count(&relations) + ecs_map_count(&objects) - + 1 /* for any */; - - if (!has_childof) { - record_count ++; - } - - table->records = ecs_os_calloc_n(ecs_table_record_t, record_count); - table->record_count = flecs_ito(uint16_t, record_count); - - /* First initialize records for regular (non-wildcard) ids. Make sure that - * these table records line up with ids in table type. */ - int32_t first_role_id = -1; - for (i = 0; i < count; i ++) { - register_table_for_id(world, table, ids[i], i, 1, &table->records[i]); - if (first_role_id == -1) { - ecs_entity_t role = ids[i] & ECS_ROLE_MASK; - if (role && role != ECS_PAIR) { - first_role_id = i; - } - } - } - - /* Initialize records for ids with roles */ - int32_t r = i; - if (first_role_id != -1) { - for (i = first_role_id; i < count; i ++) { - ecs_id_t id = ids[i]; - ecs_entity_t role = id & ECS_ROLE_MASK; - if (role && role != ECS_PAIR) { - id &= ECS_COMPONENT_MASK; - id = ecs_pair(id, EcsWildcard); - register_table_for_id(world, table, id, i, 1, &table->records[r]); - r ++; - - /* Keep track of how many switch/bitset columns there are */ - if (role == ECS_SWITCH) { - if (!table->sw_column_count) { - table->sw_column_offset = flecs_ito(int16_t, i); - } - table->sw_column_count ++; - } else if (role == ECS_DISABLED) { - if (!table->bs_column_count) { - table->bs_column_offset = flecs_ito(int16_t, i); - } - table->bs_column_count ++; - } - } - } - } - - /* Initialize records for relation wildcards */ - ecs_map_iter_t mit = ecs_map_iter(&relations); - id_first_count_t *elem; - uint64_t key; - while ((elem = ecs_map_next(&mit, id_first_count_t, &key))) { - ecs_id_t id = ecs_pair(key, EcsWildcard); - register_table_for_id(world, table, id, elem->first, elem->count, - &table->records[r]); - r ++; - } - - /* Initialize records for object wildcards */ - mit = ecs_map_iter(&objects); - while ((elem = ecs_map_next(&mit, id_first_count_t, &key))) { - ecs_id_t id = ecs_pair(EcsWildcard, key); - register_table_for_id(world, table, id, elem->first, elem->count, - &table->records[r]); - r ++; - } - - /* Initialize records for all wildcards ids */ - if (id_count) { - register_table_for_id(world, table, EcsWildcard, - first_id, id_count, &table->records[r]); - r ++; - } - if (pair_count) { - register_table_for_id(world, table, ecs_pair(EcsWildcard, EcsWildcard), - first_pair, pair_count, &table->records[r]); - r ++; - } - if (count) { - register_table_for_id(world, table, EcsAny, 0, 1, &table->records[r]); - r ++; - } - - /* Insert into (ChildOf, 0) (root) if table doesn't have childof */ - if (!has_childof && count) { - register_table_for_id(world, table, ecs_pair(EcsChildOf, 0), - 0, 1, &table->records[r]); - } - - ecs_map_fini(&relations); - ecs_map_fini(&objects); -} - void flecs_table_records_unregister( ecs_world_t *world, ecs_table_t *table) @@ -43516,7 +43621,7 @@ void flecs_table_records_unregister( for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; - ecs_id_t id = tr->id; + ecs_id_t id = ((ecs_id_record_t*)cache)->id; ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); @@ -43550,123 +43655,39 @@ bool flecs_table_records_update_empty( return result; } -static -void init_flags( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - int32_t count = ecs_vector_count(table->type); - - /* 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, like prefabs or - * containers */ - if (id <= EcsLastInternalComponentId) { - table->flags |= EcsTableHasBuiltins; - } - - if (id == EcsModule) { - table->flags |= EcsTableHasBuiltins; - table->flags |= EcsTableHasModule; - } - - if (id == EcsPrefab) { - table->flags |= EcsTableIsPrefab; - } - - /* If table contains disabled entities, mark it as disabled */ - if (id == EcsDisabled) { - table->flags |= EcsTableIsDisabled; - } - - /* Does the table have pairs */ - if (ECS_HAS_ROLE(id, PAIR)) { - table->flags |= EcsTableHasPairs; - } - - /* Does table have IsA relations */ - if (ECS_HAS_RELATION(id, EcsIsA)) { - table->flags |= EcsTableHasIsA; - } - - /* Does table have ChildOf relations */ - if (ECS_HAS_RELATION(id, EcsChildOf)) { - table->flags |= EcsTableHasChildOf; - } - - /* Does table have switch columns */ - if (ECS_HAS_ROLE(id, SWITCH)) { - table->flags |= EcsTableHasSwitch; - } - - /* Does table support component disabling */ - if (ECS_HAS_ROLE(id, DISABLED)) { - table->flags |= EcsTableHasDisabled; - } - - if (ECS_HAS_RELATION(id, EcsChildOf)) { - ecs_poly_assert(world, ecs_world_t); - ecs_entity_t obj = ecs_pair_second(world, id); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - - if (obj == EcsFlecs || obj == EcsFlecsCore || - ecs_has_id(world, obj, EcsModule)) - { - /* If table contains entities that are inside one of the builtin - * modules, it contains builtin entities */ - table->flags |= EcsTableHasBuiltins; - table->flags |= EcsTableHasModule; - } - } - } -} - static void init_table( ecs_world_t *world, - ecs_table_t *table) + ecs_table_t *table, + ecs_table_t *prev) { table->type_info = NULL; table->flags = 0; table->dirty_state = NULL; - table->alloc_count = 0; table->lock = 0; table->refcount = 1; table->generation = 0; - /* Ensure the component ids for the table exist */ - ensure_columns(world, table); - init_node(&table->node); - init_flags(world, table); - flecs_table_records_register(world, table); - flecs_table_init_data(world, table); + + flecs_table_init(world, table, prev); } static ecs_table_t *create_table( ecs_world_t *world, - ecs_vector_t *type, - flecs_hashmap_result_t table_elem) + ecs_type_t *type, + flecs_hashmap_result_t table_elem, + ecs_table_t *prev) { ecs_table_t *result = flecs_sparse_add(&world->store.tables, ecs_table_t); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vector_reclaim(&type, ecs_id_t); - result->id = flecs_sparse_last_id(&world->store.tables); - result->type = type; - - init_table(world, result); + result->type = *type; if (ecs_should_log_2()) { - char *expr = ecs_type_str(world, result->type); + char *expr = ecs_type_str(world, &result->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[green]created#[normal] with id %d", expr, result->id); @@ -43679,11 +43700,9 @@ ecs_table_t *create_table( *(ecs_table_t**)table_elem.value = result; /* Set keyvalue to one that has the same lifecycle as the table */ - ecs_ids_t key = { - .array = ecs_vector_first(result->type, ecs_id_t), - .count = ecs_vector_count(result->type) - }; - *(ecs_ids_t*)table_elem.key = key; + *(ecs_type_t*)table_elem.key = result->type; + + init_table(world, result, prev); flecs_notify_queries(world, &(ecs_query_event_t) { .kind = EcsQueryTableMatch, @@ -43711,158 +43730,43 @@ ecs_table_t *create_table( static ecs_table_t* find_or_create( ecs_world_t *world, - const ecs_ids_t *ids, - ecs_vector_t *type) + ecs_type_t *type, + bool own_type, + ecs_table_t *prev) { ecs_poly_assert(world, ecs_world_t); - /* Make sure array is ordered and does not contain duplicates */ - int32_t id_count = ids->count; - + int32_t id_count = type->count; if (!id_count) { return &world->store.root; } ecs_table_t *table; flecs_hashmap_result_t elem = flecs_hashmap_ensure( - &world->store.table_map, ids, ecs_table_t*); + &world->store.table_map, type, ecs_table_t*); if ((table = *(ecs_table_t**)elem.value)) { - if (type) { - ecs_vector_free(type); + if (own_type) { + flecs_type_free(type); } return table; } - if (!type) { - type = ids_to_vector(ids); - } - /* If we get here, table needs to be created which is only allowed when the * application is not currently in progress */ ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); /* If we get here, the table has not been found, so create it. */ - return create_table(world, type, elem); -} - -static -void add_id_to_ids( - ecs_vector_t **idv, - ecs_entity_t add, - ecs_entity_t r_exclusive) -{ - int32_t i, count = ecs_vector_count(idv[0]); - ecs_id_t *array = ecs_vector_first(idv[0], ecs_id_t); - - for (i = 0; i < count; i ++) { - ecs_id_t e = array[i]; - - if (e == add) { - return; - } - - if (r_exclusive && ECS_HAS_ROLE(e, PAIR)) { - if (ECS_PAIR_FIRST(e) == r_exclusive) { - array[i] = add; /* Replace */ - return; - } - } - - if (e >= add) { - if (e != add) { - ecs_id_t *ptr = ecs_vector_insert_at(idv, ecs_id_t, i); - ptr[0] = add; - return; - } - } + if (own_type) { + return create_table(world, type, elem, prev); } - ecs_id_t *ptr = ecs_vector_add(idv, ecs_id_t); - ptr[0] = add; -} - -static -void remove_id_from_ids( - ecs_type_t type, - ecs_id_t remove, - ecs_ids_t *out) -{ - int32_t count = ecs_vector_count(type); - ecs_id_t *array = ecs_vector_first(type, ecs_id_t); - int32_t i, el = 0; - - if (ecs_id_is_wildcard(remove)) { - for (i = 0; i < count; i ++) { - ecs_id_t id = array[i]; - if (!ecs_id_match(id, remove)) { - out->array[el ++] = id; - ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); - } - } - } else { - for (i = 0; i < count; i ++) { - ecs_id_t id = array[i]; - if (id != remove) { - out->array[el ++] = id; - ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); - } - } - } - - out->count = el; -} - -int32_t flecs_table_switch_from_case( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_entity_t add) -{ - ecs_type_t type = table->type; - ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); - - int32_t i, count = table->sw_column_count; - if (!count) { - return -1; - } - - add = add & ECS_COMPONENT_MASK; - - ecs_sw_column_t *sw_columns = NULL; - - if ((sw_columns = table->storage.sw_columns)) { - /* Fast path, we can get the switch type from the column data */ - for (i = 0; i < count; i ++) { - ecs_table_t *sw_type = sw_columns[i].type; - if (ecs_search(world, sw_type, add, 0) != -1) { - return i; - } - } - } else { - /* Slow path, table is empty, so we'll have to get the switch types by - * actually inspecting the switch type entities. */ - for (i = 0; i < count; i ++) { - ecs_entity_t e = array[i + table->sw_column_offset]; - ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); - e = e & ECS_COMPONENT_MASK; - - const EcsType *type_ptr = ecs_get(world, e, EcsType); - ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ecs_search(world, type_ptr->normalized, add, 0) != -1) { - return i; - } - } - } - - /* If a table was not found, this is an invalid switch case */ - ecs_abort(ECS_TYPE_INVALID_CASE, NULL); - - return -1; + ecs_type_t copy = flecs_type_copy(type); + return create_table(world, ©, elem, prev); } static void ids_append( - ecs_ids_t *ids, + ecs_type_t *ids, ecs_id_t id) { ids->array = ecs_os_realloc_n(ids->array, ecs_id_t, ids->count + 1); @@ -43874,8 +43778,8 @@ void diff_insert_isa( ecs_world_t *world, ecs_table_t *table, ecs_table_diff_t *base_diff, - ecs_ids_t *append_to, - ecs_ids_t *append_from, + ecs_type_t *append_to, + ecs_type_t *append_from, ecs_id_t add) { ecs_entity_t base = ecs_pair_second(world, add); @@ -43884,13 +43788,13 @@ void diff_insert_isa( return; } - ecs_type_t base_type = base_table->type, type = table->type; + ecs_type_t base_type = base_table->type; ecs_table_t *table_wo_base = base_table; /* If the table does not have a component from the base, it should * trigger an OnSet */ - ecs_id_t *ids = ecs_vector_first(base_type, ecs_id_t); - int32_t j, i, count = ecs_vector_count(base_type); + ecs_id_t *ids = base_type.array; + int32_t j, i, count = base_type.count; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; @@ -43907,7 +43811,7 @@ void diff_insert_isa( ecs_id_t base_id = append_from->array[j]; /* We still have to make sure the id isn't overridden by the * current table */ - if (!type || ecs_search(world, table, base_id, NULL) == -1) { + if (ecs_search(world, table, base_id, NULL) == -1) { ids_append(append_to, base_id); } } @@ -43924,7 +43828,7 @@ void diff_insert_isa( continue; } - if (!type || ecs_search(world, table, id, NULL) == -1) { + if (ecs_search(world, table, id, NULL) == -1) { ids_append(append_to, id); } } @@ -44021,10 +43925,10 @@ void compute_table_diff( ecs_type_t node_type = node->type; ecs_type_t next_type = next->type; - ecs_id_t *ids_node = ecs_vector_first(node_type, ecs_id_t); - ecs_id_t *ids_next = ecs_vector_first(next_type, ecs_id_t); - int32_t i_node = 0, node_count = ecs_vector_count(node_type); - int32_t i_next = 0, next_count = ecs_vector_count(next_type); + ecs_id_t *ids_node = node_type.array; + ecs_id_t *ids_next = next_type.array; + int32_t i_node = 0, node_count = node_type.count; + int32_t i_next = 0, next_count = next_type.count; int32_t added_count = 0; int32_t removed_count = 0; bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA) && @@ -44070,12 +43974,10 @@ void compute_table_diff( if (added_count) { diff->added.array = ecs_os_malloc_n(ecs_id_t, added_count); diff->added.count = 0; - diff->added.size = added_count; } if (removed_count) { diff->removed.array = ecs_os_malloc_n(ecs_id_t, removed_count); diff->removed.count = 0; - diff->removed.size = removed_count; } for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { @@ -44104,144 +44006,166 @@ void compute_table_diff( } static -void add_with_ids_to_ids( +void flecs_add_overrides_for_base( ecs_world_t *world, - ecs_vector_t **idv, - ecs_entity_t r, - ecs_entity_t o) + ecs_type_t *dst_type, + ecs_id_t pair) { - /* Check if component/relation has With pairs, which contain ids - * that need to be added to the table. */ - ecs_table_t *id_table = ecs_get_table(world, r); - if (!id_table) { + ecs_entity_t base = ecs_pair_second(world, pair); + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *base_table = ecs_get_table(world, base); + if (!base_table) { return; } - ecs_table_record_t *tr = flecs_get_table_record(world, id_table, - ecs_pair(EcsWith, EcsWildcard)); - if (tr) { - int32_t i, with_count = tr->count; - int32_t start = tr->column; - int32_t end = start + with_count; - ecs_id_t *id_ids = ecs_vector_first(id_table->type, ecs_id_t); + ecs_id_t *ids = base_table->type.array; - for (i = start; i < end; i ++) { - ecs_assert(ECS_PAIR_FIRST(id_ids[i]) == EcsWith, - ECS_INTERNAL_ERROR, NULL); - ecs_id_t id_r = ECS_PAIR_SECOND(id_ids[i]); - ecs_id_t id = id_r; - if (o) { - id = ecs_pair(id_r, o); + ecs_flags32_t flags = base_table->flags; + if (flags & EcsTableHasOverrides) { + int32_t i, count = base_table->type.count; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ECS_HAS_ROLE(id, OVERRIDE)) { + flecs_type_add(dst_type, id & ECS_COMPONENT_MASK); } + } + } - /* Always make sure vector has room for one more */ - add_id_to_ids(idv, id, 0); - - /* Add recursively in case id also has With pairs */ - add_with_ids_to_ids(world, idv, id_r, o); + if (flags & EcsTableHasIsA) { + const ecs_table_record_t *tr = flecs_id_record_table( + world->idr_isa_wildcard, base_table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = tr->column, end = i + tr->count; + for (; i != end; i ++) { + flecs_add_overrides_for_base(world, dst_type, ids[i]); } } } static -ecs_table_t* find_or_create_table_with_id( +void flecs_add_with_property( ecs_world_t *world, - ecs_table_t *node, - ecs_entity_t id) + ecs_id_record_t *idr_with_wildcard, + ecs_type_t *dst_type, + ecs_entity_t r, + ecs_entity_t o) { - /* If table has one or more switches and this is a case, return self */ - if (ECS_HAS_ROLE(id, CASE)) { - ecs_assert((node->flags & EcsTableHasSwitch) != 0, - ECS_TYPE_INVALID_CASE, NULL); - return node; - } else { - ecs_type_t type = node->type; - ecs_entity_t r_exclusive = 0; - ecs_entity_t r = 0, o = 0, re = 0; + r = ecs_get_alive(world, r); - if (ECS_HAS_ROLE(id, PAIR)) { - r = ECS_PAIR_FIRST(id); - o = ECS_PAIR_SECOND(id); - re = ecs_get_alive(world, r); - if (re && ecs_has_id(world, re, EcsExclusive)) { - r_exclusive = (uint32_t)re; - } - } else { - r = id & ECS_COMPONENT_MASK; - re = ecs_get_alive(world, r); - } + /* Check if component/relation has With pairs, which contain ids + * that need to be added to the table. */ + ecs_table_t *table = ecs_get_table(world, r); + if (!table) { + return; + } + + const ecs_table_record_t *tr = flecs_id_record_table( + idr_with_wildcard, table); + if (tr) { + int32_t i = tr->column, end = i + tr->count; + ecs_id_t *ids = table->type.array; - ecs_vector_t *idv = ecs_vector_copy(type, ecs_id_t); - add_id_to_ids(&idv, id, r_exclusive); - if (re) { - add_with_ids_to_ids(world, &idv, re, o); - } + for (; i < end; i ++) { + ecs_id_t id = ids[i]; + ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); + ecs_id_t ra = ECS_PAIR_SECOND(id); + ecs_id_t a = ra; + if (o) { + a = ecs_pair(ra, o); + } - ecs_ids_t ids = { - .array = ecs_vector_first(idv, ecs_id_t), - .count = ecs_vector_count(idv) - }; + flecs_type_add(dst_type, a); - return find_or_create(world, &ids, idv); + flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); + } } + } static -ecs_table_t* find_or_create_table_without_id( +ecs_table_t* flecs_find_table_with( ecs_world_t *world, ecs_table_t *node, - ecs_entity_t id) -{ - /* If table has one or more switches and this is a case, return self */ - if (ECS_HAS_ROLE(id, CASE)) { - ecs_assert((node->flags & EcsTableHasSwitch) != 0, - ECS_TYPE_INVALID_CASE, NULL); - return node; + ecs_entity_t with) +{ + ecs_ensure_id(world, with); + + ecs_id_record_t *idr = NULL; + ecs_entity_t r = 0, o = 0; + if (ECS_HAS_ROLE(with, PAIR)) { + r = ECS_PAIR_FIRST(with); + o = ECS_PAIR_SECOND(with); + idr = flecs_ensure_id_record(world, ecs_pair(r, EcsWildcard)); + if (idr->flags & EcsIdUnion) { + ecs_type_t dst_type; + ecs_id_t union_id = ecs_pair(EcsUnion, r); + int res = flecs_type_new_with(&dst_type, &node->type, union_id); + if (res == -1) { + return node; + } + return find_or_create(world, &dst_type, true, node);; + } else if (idr->flags & EcsIdExclusive) { + /* Relationship is exclusive, check if table already has it */ + const ecs_table_record_t *tr = flecs_id_record_table(idr, node); + if (tr) { + /* Table already has an instance of the relationship, create + * a new id sequence with the existing id replaced */ + ecs_type_t dst_type = flecs_type_copy(&node->type); + dst_type.array[tr->column] = with; + return find_or_create(world, &dst_type, true, node); + } + } } else { - ecs_type_t type = node->type; - int32_t count = ecs_vector_count(type); + idr = flecs_ensure_id_record(world, with); + r = with; + } - ecs_ids_t ids = { - .array = ecs_os_alloca_n(ecs_id_t, count), - .count = count - }; + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_with(&dst_type, &node->type, with); + if (res == -1) { + return node; /* Current table already has id */ + } - remove_id_from_ids(type, id, &ids); + if (r == EcsIsA) { + /* If adding a prefab, check if prefab has overrides */ + flecs_add_overrides_for_base(world, &dst_type, with); + } - return flecs_table_find_or_create(world, &ids);; + if (idr->flags & EcsIdWith) { + ecs_id_record_t *idr_with_wildcard = flecs_get_id_record(world, + ecs_pair(EcsWith, EcsWildcard)); + /* If id has With property, add targets to type */ + flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o); } + + return find_or_create(world, &dst_type, true, node); } static -ecs_table_t* find_or_create_table_with_isa( +ecs_table_t* flecs_find_table_without( ecs_world_t *world, ecs_table_t *node, - ecs_entity_t base) + ecs_entity_t without) { - ecs_type_t base_type = ecs_get_type(world, base); - ecs_id_t *ids = ecs_vector_first(base_type, ecs_id_t); - int32_t i, count = ecs_vector_count(base_type); - - /* Start from back, as roles have high ids */ - for (i = count - 1; i >= 0; i --) { - ecs_id_t id = ids[i]; - if (!(id & ECS_ROLE_MASK)) { /* early out if we found everything */ - break; - } - - if (ECS_HAS_RELATION(id, EcsIsA)) { - ecs_entity_t base_of_base = ecs_pair_second(world, id); - node = find_or_create_table_with_isa(world, node, base_of_base); + if (ECS_HAS_ROLE(without, PAIR)) { + ecs_entity_t r = 0; + ecs_id_record_t *idr = NULL; + r = ECS_PAIR_FIRST(without); + idr = flecs_get_id_record(world, ecs_pair(r, EcsWildcard)); + if (idr && idr->flags & EcsIdUnion) { + without = ecs_pair(EcsUnion, r); } + } - if (ECS_HAS_ROLE(id, OVERRIDE)) { - /* Override found, add it to table */ - id &= ECS_COMPONENT_MASK; - node = flecs_table_traverse_add(world, node, &id, NULL); - } + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_without(&dst_type, &node->type, without); + if (res == -1) { + return node; /* Current table does not have id */ } - return node; + return find_or_create(world, &dst_type, true, node); } static @@ -44263,7 +44187,7 @@ void init_edge( } static -void init_add_edge( +void flecs_init_edge_for_add( ecs_world_t *world, ecs_table_t *table, ecs_graph_edge_t *edge, @@ -44292,7 +44216,7 @@ void init_add_edge( } static -void init_remove_edge( +void flecs_init_edge_for_remove( ecs_world_t *world, ecs_table_t *table, ecs_graph_edge_t *edge, @@ -44321,34 +44245,29 @@ void init_remove_edge( } static -ecs_table_t* find_or_create_table_without( +ecs_table_t* flecs_create_edge_for_remove( ecs_world_t *world, ecs_table_t *node, ecs_graph_edge_t *edge, ecs_id_t id) { - ecs_table_t *to = find_or_create_table_without_id(world, node, id); + ecs_table_t *to = flecs_find_table_without(world, node, id); - init_remove_edge(world, node, edge, id, to); + flecs_init_edge_for_remove(world, node, edge, id, to); return to; } static -ecs_table_t* find_or_create_table_with( +ecs_table_t* flecs_create_edge_for_add( ecs_world_t *world, ecs_table_t *node, ecs_graph_edge_t *edge, ecs_id_t id) { - ecs_table_t *to = find_or_create_table_with_id(world, node, id); + ecs_table_t *to = flecs_find_table_with(world, node, id); - if (ECS_HAS_ROLE(id, PAIR) && ECS_PAIR_FIRST(id) == EcsIsA) { - ecs_entity_t base = ecs_pair_second(world, id); - to = find_or_create_table_with_isa(world, to, base); - } - - init_add_edge(world, node, edge, id, to); + flecs_init_edge_for_add(world, node, edge, id, to); return to; } @@ -44364,10 +44283,6 @@ void populate_diff( ecs_table_diff_t *diff = edge->diff; if (diff && diff != &ecs_table_edge_is_component) { - ecs_assert(!add_ptr || !ECS_HAS_ROLE(add_ptr[0], CASE), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!remove_ptr || !ECS_HAS_ROLE(remove_ptr[0], CASE), - ECS_INTERNAL_ERROR, NULL); *out = *diff; } else { out->on_set.count = 0; @@ -44415,7 +44330,7 @@ ecs_table_t* flecs_table_traverse_remove( ecs_table_t *to = edge->to; if (!to) { - to = find_or_create_table_without(world, node, edge, id); + to = flecs_create_edge_for_remove(world, node, edge, id); ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } @@ -44446,7 +44361,7 @@ ecs_table_t* flecs_table_traverse_add( ecs_table_t *to = edge->to; if (!to) { - to = find_or_create_table_with(world, node, edge, id); + to = flecs_create_edge_for_add(world, node, edge, id); ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } @@ -44460,24 +44375,20 @@ ecs_table_t* flecs_table_traverse_add( ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, - const ecs_ids_t *ids) + ecs_type_t *type) { ecs_poly_assert(world, ecs_world_t); - return find_or_create(world, ids, NULL); + return find_or_create(world, type, false, NULL); } void flecs_init_root_table( ecs_world_t *world) { - ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(world, ecs_world_t); - ecs_ids_t entities = { - .array = NULL, - .count = 0 - }; + world->store.root.type = (ecs_type_t){0}; - world->store.root.type = ids_to_vector(&entities); - init_table(world, &world->store.root); + init_table(world, &world->store.root, NULL); /* Ensure table indices start at 1, as 0 is reserved for the root */ uint64_t new_id = flecs_sparse_new_id(&world->store.tables); @@ -44669,19 +44580,20 @@ ecs_size_t iter_get_size_for_id( ecs_world_t *world, ecs_id_t id) { - if (ECS_HAS_ROLE(id, SWITCH)) { - return ECS_SIZEOF(ecs_entity_t); + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return 0; } - ecs_entity_t type_id = ecs_get_typeid(world, id); - if (!type_id) { - return 0; + if (idr->flags & EcsIdUnion) { + return ECS_SIZEOF(ecs_entity_t); } - const ecs_type_info_t *ti = flecs_get_type_info(world, type_id); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + if (idr->type_info) { + return idr->type_info->size; + } - return ti->size; + return 0; } static @@ -44695,10 +44607,9 @@ bool flecs_iter_populate_term_data( { bool is_shared = false; ecs_table_t *table; - ecs_vector_t *vec; + void *data; ecs_size_t size = 0; - ecs_size_t align; - int32_t row; + int32_t row, u_index; if (!column) { /* Term has no data. This includes terms that have Not operators. */ @@ -44722,23 +44633,34 @@ bool flecs_iter_populate_term_data( /* Data is not from This */ if (it->references) { - /* Iterator provides cached references for non-This terms */ - ecs_ref_t *ref = &it->references[-column - 1]; - if (ptr_out) { - ptr_out[0] = (void*)ecs_get_ref_id( - world, ref, ref->entity, ref->component); - } + /* The reference array is used only for components matched on a + * table (vs. individual entities). Remaining components should be + * assigned outside of this function */ + if (it->terms[t].subj.entity == EcsThis) { + + /* Iterator provides cached references for non-This terms */ + ecs_ref_t *ref = &it->references[-column - 1]; + if (ptr_out) { + if (ref->id) { + ptr_out[0] = (void*)ecs_ref_get_id(world, ref, ref->id); + } else { + ptr_out[0] = NULL; + } + } - if (!ref->component) { - is_shared = false; + if (!ref->id) { + is_shared = false; + } + + /* If cached references were provided, the code that populated + * the iterator also had a chance to cache sizes, so size array + * should already have been assigned. This saves us from having + * to do additional lookups to find the component size. */ + ecs_assert(size_out == NULL, ECS_INTERNAL_ERROR, NULL); + return is_shared; } - /* If cached references were provided, the code that populated - * the iterator also had a chance to cache sizes, so size array - * should already have been assigned. This saves us from having - * to do additional lookups to find the component size. */ - ecs_assert(size_out == NULL, ECS_INTERNAL_ERROR, NULL); - return is_shared; + return true; } else { ecs_entity_t subj = it->subjects[t]; ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); @@ -44756,28 +44678,20 @@ bool flecs_iter_populate_term_data( ecs_table_record_t *tr; if (!s_table || !(tr = flecs_get_table_record(world, s_table, id))){ - /* The entity has no components or the id is not a component */ - ecs_id_t term_id = it->terms[t].id; - if (ECS_HAS_ROLE(term_id, SWITCH) || ECS_HAS_ROLE(term_id, CASE)) { - /* Edge case: if this is a switch. find switch column in - * actual table, as its not in the storage table */ - tr = flecs_get_table_record(world, table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - column = tr->column + 1; - goto has_switch; - } else { - goto no_data; + u_index = flecs_table_column_to_union_index(table, -column - 1); + if (u_index != -1) { + goto has_union; } + goto no_data; } /* We now have row and column, so we can get the storage for the id * which gives us the pointer and size */ column = tr->column; - ecs_type_info_t *ti = &table->type_info[column]; - ecs_column_t *s = &table->storage.columns[column]; + ecs_type_info_t *ti = table->type_info[column]; + ecs_column_t *s = &table->data.columns[column]; size = ti->size; - align = ti->alignment; - vec = s->data; + data = ecs_storage_first(s); /* Fallthrough to has_data */ } } else { @@ -44788,22 +44702,22 @@ bool flecs_iter_populate_term_data( } row = it->offset; - + + int32_t storage_column = ecs_table_type_to_storage_index( table, column - 1); if (storage_column == -1) { - ecs_id_t id = it->terms[t].id; - if (ECS_HAS_ROLE(id, SWITCH) || ECS_HAS_ROLE(id, CASE)) { - goto has_switch; + u_index = flecs_table_column_to_union_index(table, column - 1); + if (u_index != -1) { + goto has_union; } goto no_data; } - ecs_type_info_t *ti = &table->type_info[storage_column]; - ecs_column_t *s = &table->storage.columns[storage_column]; + ecs_type_info_t *ti = table->type_info[storage_column]; + ecs_column_t *s = &table->data.columns[storage_column]; size = ti->size; - align = ti->alignment; - vec = s->data; + data = ecs_storage_first(s); if (!table || !ecs_table_count(table)) { goto no_data; @@ -44813,18 +44727,16 @@ bool flecs_iter_populate_term_data( } has_data: - if (ptr_out) ptr_out[0] = ecs_vector_get_t(vec, size, align, row); + if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); if (size_out) size_out[0] = size; return is_shared; -has_switch: { +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_switch_t *sw = table->storage.sw_columns[ - (column - 1) - table->sw_column_offset].data; - vec = flecs_switch_values(sw); + ecs_switch_t *sw = &table->data.sw_columns[u_index]; + data = ecs_vector_first(flecs_switch_values(sw), ecs_entity_t); size = ECS_SIZEOF(ecs_entity_t); - align = ECS_ALIGNOF(ecs_entity_t); goto has_data; } @@ -44852,13 +44764,12 @@ void flecs_iter_populate_data( it->count = count; if (table) { - it->type = it->table->type; if (!count) { count = it->count = ecs_table_count(table); } if (count) { - it->entities = ecs_vector_get( - table->storage.entities, ecs_entity_t, offset); + it->entities = ecs_storage_get_t( + &table->data.entities, ecs_entity_t, offset); } else { it->entities = NULL; } @@ -45086,14 +44997,13 @@ void* ecs_iter_column_w_size( return NULL; } - ecs_type_info_t *ti = &table->type_info[storage_index]; + ecs_type_info_t *ti = table->type_info[storage_index]; ecs_check(!size || (ecs_size_t)size == ti->size, ECS_INVALID_PARAMETER, NULL); + (void)ti; - ecs_column_t *column = &table->storage.columns[storage_index]; - int32_t alignment = ti->alignment; - return ecs_vector_get_t(column->data, flecs_uto(int32_t, size), alignment, - it->offset); + ecs_column_t *column = &table->data.columns[storage_index]; + return ecs_storage_get(column, flecs_uto(int32_t, size), it->offset); error: return NULL; } @@ -45111,7 +45021,7 @@ size_t ecs_iter_column_size( return 0; } - ecs_type_info_t *ti = &table->type_info[storage_index]; + ecs_type_info_t *ti = table->type_info[storage_index]; return flecs_ito(size_t, ti->size); error: return 0; @@ -45226,6 +45136,10 @@ bool ecs_iter_is_true( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->flags, EcsIterIsFilter); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + bool result = ecs_iter_next(it); if (result) { ecs_iter_fini(it); @@ -45251,7 +45165,7 @@ ecs_entity_t ecs_iter_get_var( if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { ecs_assert(ecs_table_count(table) > var->range.offset, ECS_INTERNAL_ERROR, NULL); - e = ecs_vector_get(table->storage.entities, ecs_entity_t, + e = ecs_storage_get_t(&table->data.entities, ecs_entity_t, var->range.offset)[0]; } } @@ -45413,8 +45327,8 @@ void ecs_iter_set_var_as_range( if (range->count == 1) { ecs_table_t *table = range->table; - var->entity = ecs_vector_get( - table->storage.entities, ecs_entity_t, range->offset)[0]; + var->entity = ecs_storage_get_t( + &table->data.entities, ecs_entity_t, range->offset)[0]; } else { var->entity = 0; } @@ -45704,7 +45618,7 @@ int32_t count_events( } static -ecs_entity_t get_actual_event( +ecs_entity_t flecs_trigger_get_actual_event( ecs_trigger_t *trigger, ecs_entity_t event) { @@ -45744,6 +45658,25 @@ ecs_event_id_record_t* ensure_event_id_record( return idt[0]; } +static +ecs_flags32_t id_flag_for_event( + ecs_entity_t e) +{ + if (e == EcsOnAdd) { + return EcsIdHasOnAdd; + } + if (e == EcsOnRemove) { + return EcsIdHasOnRemove; + } + if (e == EcsOnSet) { + return EcsIdHasOnSet; + } + if (e == EcsUnSet) { + return EcsIdHasUnSet; + } + return 0; +} + static void inc_trigger_count( ecs_world_t *world, @@ -45763,6 +45696,14 @@ void inc_trigger_count( .kind = EcsTableTriggersForId, .event = event }); + + ecs_flags32_t flags = id_flag_for_event(event); + if (flags) { + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (idr) { + idr->flags |= flags; + } + } } else if (result == 0) { /* Ditto, but the reverse */ flecs_notify_tables(world, id, &(ecs_table_event_t){ @@ -45770,6 +45711,14 @@ void inc_trigger_count( .event = event }); + ecs_flags32_t flags = id_flag_for_event(event); + if (flags) { + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (idr) { + idr->flags &= ~flags; + } + } + /* Remove admin for id for event */ if (!ecs_map_is_initialized(&idt->triggers) && !ecs_map_is_initialized(&idt->set_triggers)) @@ -45790,11 +45739,12 @@ void register_trigger_for_id( { ecs_sparse_t *events = observable->events; ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = trigger->term.id; + ecs_id_t term_id = trigger->register_id; int i; for (i = 0; i < trigger->event_count; i ++) { - ecs_entity_t event = get_actual_event(trigger, trigger->events[i]); + ecs_entity_t event = flecs_trigger_get_actual_event( + trigger, trigger->events[i]); /* Get triggers for event */ ecs_event_record_t *evt = flecs_sparse_ensure( @@ -45831,13 +45781,14 @@ void register_trigger( ecs_trigger_t *trigger) { ecs_term_t *term = &trigger->term; + ecs_id_t id = trigger->register_id; if (term->subj.set.mask & EcsSelf) { if (term->subj.entity == EcsThis) { - register_trigger_for_id(world, observable, trigger, term->id, + register_trigger_for_id(world, observable, trigger, id, offsetof(ecs_event_id_record_t, triggers)); } else { - register_trigger_for_id(world, observable, trigger, term->id, + register_trigger_for_id(world, observable, trigger, id, offsetof(ecs_event_id_record_t, entity_triggers)); } } @@ -45847,19 +45798,6 @@ void register_trigger( register_trigger_for_id(world, observable, trigger, pair, offsetof(ecs_event_id_record_t, set_triggers)); } - - if (ECS_HAS_ROLE(term->id, SWITCH)) { - ecs_entity_t sw = term->id & ECS_COMPONENT_MASK; - ecs_id_t sw_case = ecs_case(sw, EcsWildcard); - register_trigger_for_id(world, observable, trigger, sw_case, - offsetof(ecs_event_id_record_t, triggers)); - } - - if (ECS_HAS_ROLE(term->id, CASE)) { - ecs_entity_t sw = ECS_PAIR_FIRST(term->id); - register_trigger_for_id(world, observable, trigger, ECS_SWITCH | sw, - offsetof(ecs_event_id_record_t, triggers)); - } } static @@ -45872,11 +45810,12 @@ void unregister_trigger_for_id( { ecs_sparse_t *events = observable->events; ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = trigger->term.id; + ecs_id_t term_id = trigger->register_id; int i; for (i = 0; i < trigger->event_count; i ++) { - ecs_entity_t event = get_actual_event(trigger, trigger->events[i]); + ecs_entity_t event = flecs_trigger_get_actual_event( + trigger, trigger->events[i]); /* Get triggers for event */ ecs_event_record_t *evt = flecs_sparse_get( @@ -45918,13 +45857,14 @@ void unregister_trigger( ecs_trigger_t *trigger) { ecs_term_t *term = &trigger->term; + ecs_id_t id = trigger->register_id; if (term->subj.set.mask & EcsSelf) { if (term->subj.entity == EcsThis) { - unregister_trigger_for_id(world, observable, trigger, term->id, + unregister_trigger_for_id(world, observable, trigger, id, offsetof(ecs_event_id_record_t, triggers)); } else { - unregister_trigger_for_id(world, observable, trigger, term->id, + unregister_trigger_for_id(world, observable, trigger, id, offsetof(ecs_event_id_record_t, entity_triggers)); } } @@ -45934,19 +45874,6 @@ void unregister_trigger( unregister_trigger_for_id(world, observable, trigger, pair, offsetof(ecs_event_id_record_t, set_triggers)); } - - if (ECS_HAS_ROLE(term->id, SWITCH)) { - ecs_entity_t sw = term->id & ECS_COMPONENT_MASK; - ecs_id_t sw_case = ecs_case(sw, EcsWildcard); - unregister_trigger_for_id(world, observable, trigger, sw_case, - offsetof(ecs_event_id_record_t, triggers)); - } - - if (ECS_HAS_ROLE(term->id, CASE)) { - ecs_entity_t sw = ECS_PAIR_FIRST(term->id); - unregister_trigger_for_id(world, observable, trigger, ECS_SWITCH | sw, - offsetof(ecs_event_id_record_t, triggers)); - } } static @@ -46212,10 +46139,10 @@ void notify_set_base_triggers( int32_t s_column = ecs_table_type_to_storage_index( obj_table, column); if (s_column != -1) { - ecs_column_t *c = &obj_table->storage.columns[s_column]; + ecs_column_t *c = &obj_table->data.columns[s_column]; int32_t row = ECS_RECORD_TO_ROW(obj_record->row); - ecs_type_info_t *ti = &obj_table->type_info[s_column]; - void *ptr = ecs_vector_get_t(c->data, ti->size, ti->alignment, row); + ecs_type_info_t *ti = obj_table->type_info[s_column]; + void *ptr = ecs_storage_get(c, ti->size, row); it->ptrs[0] = ptr; it->sizes[0] = ti->size; } @@ -46281,8 +46208,8 @@ void notify_set_triggers( it->count = 1; } - if (flecs_term_match_table(world, &t->term, it->table, it->type, - it->ids, it->columns, it->subjects, NULL, true, it->flags)) + if (flecs_term_match_table(world, &t->term, it->table, it->ids, + it->columns, it->subjects, NULL, true, it->flags)) { if (!it->subjects[0]) { /* Do not match owned components */ @@ -46399,7 +46326,7 @@ void trigger_yield_existing( void flecs_triggers_notify( ecs_iter_t *it, ecs_observable_t *observable, - ecs_ids_t *ids, + const ecs_type_t *ids, ecs_entity_t event) { ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); @@ -46427,22 +46354,24 @@ void flecs_triggers_notify( notify_triggers_for_id(world, evt, id, it, &iter_set); - if (role == ECS_PAIR || role == ECS_CASE) { - ecs_entity_t pred = ECS_PAIR_FIRST(id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); + if (role == ECS_PAIR) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t o = ECS_PAIR_SECOND(id); - ecs_id_t tid = role | ecs_entity_t_comb(EcsWildcard, pred); + ecs_id_t tid = ecs_pair(r, EcsWildcard); notify_triggers_for_id(world, evt, tid, it, &iter_set); - tid = role | ecs_entity_t_comb(obj, EcsWildcard); + tid = ecs_pair(EcsWildcard, o); notify_triggers_for_id(world, evt, tid, it, &iter_set); - tid = role | ecs_entity_t_comb(EcsWildcard, EcsWildcard); + tid = ecs_pair(EcsWildcard, EcsWildcard); notify_triggers_for_id(world, evt, tid, it, &iter_set); } else { notify_triggers_for_id(world, evt, EcsWildcard, it, &iter_set); } + notify_triggers_for_id(world, evt, EcsAny, it, &iter_set); + if (iter_set) { ecs_iter_fini(it); } @@ -46453,7 +46382,7 @@ void flecs_triggers_notify( void flecs_set_triggers_notify( ecs_iter_t *it, ecs_observable_t *observable, - ecs_ids_t *ids, + const ecs_type_t *ids, ecs_entity_t event, ecs_id_t set_id) { @@ -46574,6 +46503,7 @@ ecs_entity_t ecs_trigger_init( trigger->match_disabled = desc->match_disabled; trigger->instanced = desc->instanced; trigger->last_event_id = desc->last_event_id; + trigger->register_id = flecs_from_public_id(world, trigger->term.id); if (trigger->term.id == EcsPrefab) { trigger->match_prefab = true; @@ -46965,12 +46895,12 @@ void assert_relation_unused( if (world->is_fini) { return; } - if (flecs_get_id_record(world, ecs_pair(rel, EcsWildcard)) != NULL) { + if (ecs_id_in_use(world, ecs_pair(rel, EcsWildcard))) { char *r_str = ecs_get_fullpath(world, rel); char *p_str = ecs_get_fullpath(world, property); ecs_throw(ECS_ID_IN_USE, - "cannot change property '%s' to relation '%s': already in use", + "cannot change property '%s' for relation '%s': already in use", p_str, r_str); ecs_os_free(r_str); @@ -46981,6 +46911,71 @@ void assert_relation_unused( return; } +static +bool set_id_flag( + ecs_id_record_t *idr, + ecs_flags32_t flag) +{ + if (!(idr->flags & flag)) { + idr->flags |= flag; + return true; + } + return false; +} + +static +bool unset_id_flag( + ecs_id_record_t *idr, + ecs_flags32_t flag) +{ + if ((idr->flags & flag)) { + idr->flags &= ~flag; + return true; + } + return false; +} + +static +void register_id_flag_for_relation( + ecs_iter_t *it, + ecs_entity_t prop, + ecs_flags32_t flag, + ecs_flags32_t not_flag, + ecs_flags32_t entity_flag) +{ + ecs_world_t *world = it->world; + ecs_entity_t event = it->event; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + bool changed = false; + + if (event == EcsOnAdd) { + ecs_id_record_t *idr = flecs_ensure_id_record(world, e); + changed |= set_id_flag(idr, flag); + idr = flecs_ensure_id_record(world, ecs_pair(e, EcsWildcard)); + do { + changed |= set_id_flag(idr, flag); + } while ((idr = idr->first.next)); + if (entity_flag) flecs_add_flag(world, e, entity_flag); + } else if (event == EcsOnRemove) { + ecs_id_record_t *idr = flecs_get_id_record(world, e); + if (idr) changed |= unset_id_flag(idr, not_flag); + idr = flecs_get_id_record(world, ecs_pair(e, EcsWildcard)); + if (idr) { + do { + changed |= unset_id_flag(idr, not_flag); + } while ((idr = idr->first.next)); + } + } + + if (changed) { + assert_relation_unused(world, e, prop); + } + } +} + static void register_final(ecs_iter_t *it) { ecs_world_t *world = it->world; @@ -46991,7 +46986,7 @@ void register_final(ecs_iter_t *it) { if (flecs_get_id_record(world, ecs_pair(EcsIsA, e)) != NULL) { char *e_str = ecs_get_fullpath(world, e); ecs_throw(ECS_ID_IN_USE, - "cannot change property 'Final' to '%s': already inherited from", + "cannot change property 'Final' for '%s': already inherited from", e_str); ecs_os_free(e_str); error: @@ -47002,82 +46997,72 @@ void register_final(ecs_iter_t *it) { static void register_on_delete(ecs_iter_t *it) { - ecs_world_t *world = it->world; ecs_id_t id = ecs_term_id(it, 1); - ecs_entity_t event = it->event; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - assert_relation_unused(world, e, EcsOnDelete); - - if (event == EcsOnAdd) { - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - r->flags |= ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)); - } else { - ecs_id_record_t *r = flecs_get_id_record(world, e); - if (r) { - r->flags &= ~ECS_ID_ON_DELETE_MASK; - } - } - - flecs_add_flag(world, e, ECS_FLAG_OBSERVED_ID); - } + register_id_flag_for_relation(it, EcsOnDelete, + ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), + EcsIdOnDeleteMask, + EcsEntityObservedId); } static void register_on_delete_object(ecs_iter_t *it) { - ecs_world_t *world = it->world; ecs_id_t id = ecs_term_id(it, 1); - ecs_entity_t event = it->event; + register_id_flag_for_relation(it, EcsOnDeleteObject, + ECS_ID_ON_DELETE_OBJECT_FLAG(ECS_PAIR_SECOND(id)), + EcsIdOnDeleteObjectMask, + EcsEntityObservedId); +} + +static +void register_acyclic(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsAcyclic, EcsIdAcyclic, + EcsIdAcyclic, 0); +} +static +void register_tag(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsTag, EcsIdTag, ~EcsIdTag, 0); + + /* Ensure that all id records for tag have type info set to NULL */ + ecs_world_t *world = it->real_world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; - assert_relation_unused(world, e, EcsOnDeleteObject); - if (event == EcsOnAdd) { - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - r->flags |= ECS_ID_ON_DELETE_OBJECT_FLAG(ECS_PAIR_SECOND(id)); - } else { - ecs_id_record_t *r = flecs_get_id_record(world, e); - if (r) { - r->flags &= ~ECS_ID_ON_DELETE_OBJECT_MASK; - } + if (it->event == EcsOnAdd) { + ecs_id_record_t *idr = flecs_get_id_record(world, + ecs_pair(e, EcsWildcard)); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + do { + if (idr->type_info != NULL) { + assert_relation_unused(world, e, EcsTag); + } + idr->type_info = NULL; + } while ((idr = idr->first.next)); } - - flecs_add_flag(world, e, ECS_FLAG_OBSERVED_ID); - } + } } static void register_exclusive(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - assert_relation_unused(world, e, EcsExclusive); - - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - r->flags |= ECS_ID_EXCLUSIVE; - } + register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive, + EcsIdExclusive, 0); } static void register_dont_inherit(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - assert_relation_unused(world, e, EcsDontInherit); + register_id_flag_for_relation(it, EcsDontInherit, + EcsIdDontInherit, EcsIdDontInherit, 0); +} - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - r->flags |= ECS_ID_DONT_INHERIT; - } +static +void register_with(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0); +} + +static +void register_union(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0); } static @@ -47135,9 +47120,9 @@ void on_set_component(ecs_iter_t *it) { int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; - ecs_type_info_t *ti = flecs_ensure_type_info(world, e); - ti->size = c[i].size; - ti->alignment = c[i].alignment; + if (flecs_init_type_info_id(world, e, c[i].size, c[i].alignment, NULL)){ + assert_relation_unused(world, e, ecs_id(EcsComponent)); + } } } @@ -47272,17 +47257,16 @@ void _bootstrap_component( { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_column_t *columns = table->storage.columns; + ecs_column_t *columns = table->data.columns; ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = ecs_eis_ensure(world, entity); record->table = table; - int32_t index = flecs_table_append(world, table, &table->storage, - entity, record, false); + int32_t index = flecs_table_append(world, table, entity, record, false); record->row = ECS_ROW_TO_RECORD(index, 0); - EcsComponent *component = ecs_vector_first(columns[0].data, EcsComponent); + EcsComponent *component = ecs_storage_first(&columns[0]); component[index].size = size; component[index].alignment = alignment; @@ -47290,14 +47274,14 @@ void _bootstrap_component( ecs_size_t symbol_length = ecs_os_strlen(symbol); ecs_size_t name_length = symbol_length - 3; - EcsIdentifier *name_col = ecs_vector_first(columns[1].data, EcsIdentifier); + EcsIdentifier *name_col = ecs_storage_first(&columns[1]); 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].index_hash = 0; name_col[index].index = NULL; - EcsIdentifier *symbol_col = ecs_vector_first(columns[2].data, EcsIdentifier); + EcsIdentifier *symbol_col = ecs_storage_first(&columns[2]); symbol_col[index].value = ecs_os_strdup(symbol); symbol_col[index].length = symbol_length; symbol_col[index].hash = flecs_hash(symbol, symbol_length); @@ -47325,44 +47309,50 @@ ecs_table_t* bootstrap_component_table( ecs_ensure(world, EcsChildOf); ecs_ensure(world, EcsFlecsCore); ecs_ensure(world, EcsOnDelete); - ecs_ensure(world, EcsThrow); + ecs_ensure(world, EcsPanic); ecs_ensure(world, EcsWildcard); ecs_ensure(world, EcsAny); /* Before creating table, manually set flags for ChildOf/Identifier, as this * can no longer be done after they are in use. */ - ecs_id_record_t *childof_idr = flecs_ensure_id_record(world, EcsChildOf); - childof_idr->flags |= ECS_ID_ON_DELETE_OBJECT_DELETE; - childof_idr->flags |= ECS_ID_DONT_INHERIT; + ecs_id_record_t *idr = flecs_ensure_id_record(world, EcsChildOf); + idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | + EcsIdAcyclic | EcsIdTag; + idr = flecs_ensure_id_record(world, ecs_pair(EcsChildOf, EcsWildcard)); + idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | + EcsIdAcyclic | EcsIdTag | EcsIdExclusive; - ecs_id_record_t *ident_idr = flecs_ensure_id_record( - world, ecs_id(EcsIdentifier)); - ident_idr->flags |= ECS_ID_DONT_INHERIT; + idr = flecs_ensure_id_record( + world, ecs_pair(ecs_id(EcsIdentifier), EcsWildcard)); + idr->flags |= EcsIdDontInherit; - ecs_id_t entities[] = { + world->idr_childof_0 = flecs_ensure_id_record(world, + ecs_pair(EcsChildOf, 0)); + + ecs_id_t ids[] = { ecs_id(EcsComponent), EcsFinal, ecs_pair(ecs_id(EcsIdentifier), EcsName), ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), ecs_pair(EcsChildOf, EcsFlecsCore), - ecs_pair(EcsOnDelete, EcsThrow) + ecs_pair(EcsOnDelete, EcsPanic) }; - ecs_ids_t array = { - .array = entities, + ecs_type_t array = { + .array = ids, .count = 6 }; ecs_table_t *result = flecs_table_find_or_create(world, &array); - ecs_data_t *data = &result->storage; + ecs_data_t *data = &result->data; /* Preallocate enough memory for initial components */ - data->entities = ecs_vector_new(ecs_entity_t, EcsFirstUserComponentId); - data->record_ptrs = ecs_vector_new(ecs_record_t*, EcsFirstUserComponentId); + ecs_storage_init_t(&data->entities, ecs_entity_t, EcsFirstUserComponentId); + ecs_storage_init_t(&data->records, ecs_record_t, EcsFirstUserComponentId); - data->columns[0].data = ecs_vector_new(EcsComponent, EcsFirstUserComponentId); - data->columns[1].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); - data->columns[2].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); + ecs_storage_init_t(&data->columns[0], EcsComponent, EcsFirstUserComponentId); + ecs_storage_init_t(&data->columns[1], EcsIdentifier, EcsFirstUserComponentId); + ecs_storage_init_t(&data->columns[2], EcsIdentifier, EcsFirstUserComponentId); return result; } @@ -47397,22 +47387,15 @@ void flecs_bootstrap( ecs_set_name_prefix(world, "Ecs"); - /* Bootstrap type info (otherwise initialized by setting EcsComponent) */ - flecs_init_type_info_t(world, EcsComponent); - flecs_init_type_info_t(world, EcsIdentifier); - flecs_init_type_info_t(world, EcsComponentLifecycle); - flecs_init_type_info_t(world, EcsType); - flecs_init_type_info_t(world, EcsQuery); - flecs_init_type_info_t(world, EcsTrigger); - flecs_init_type_info_t(world, EcsObserver); - flecs_init_type_info_t(world, EcsIterable); - - /* Setup component lifecycle actions */ - ecs_set_component_actions(world, EcsComponent, { - .ctor = ecs_default_ctor + ecs_ensure(world, EcsWildcard); + ecs_ensure(world, EcsAny); + + /* Bootstrap builtin components */ + flecs_init_type_info(world, EcsComponent, { + .ctor = ecs_default_ctor }); - ecs_set_component_actions(world, EcsIdentifier, { + flecs_init_type_info(world, EcsIdentifier, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsIdentifier), .copy = ecs_copy(EcsIdentifier), @@ -47421,15 +47404,26 @@ void flecs_bootstrap( .on_remove = ecs_on_set(EcsIdentifier) }); - ecs_set_component_actions(world, EcsTrigger, { + flecs_init_type_info(world, EcsTrigger, { .ctor = ecs_default_ctor, .on_remove = ecs_on_remove(EcsTrigger) - }); + }); - ecs_set_component_actions(world, EcsObserver, { + flecs_init_type_info(world, EcsObserver, { .ctor = ecs_default_ctor, .on_remove = ecs_on_remove(EcsObserver) - }); + }); + + flecs_init_type_info(world, EcsComponentLifecycle, { 0 }); + flecs_init_type_info(world, EcsType, { 0 }); + flecs_init_type_info(world, EcsQuery, { 0 }); + flecs_init_type_info(world, EcsIterable, { 0 }); + + /* Cache often used id records on world */ + world->idr_wildcard = flecs_ensure_id_record(world, EcsWildcard); + world->idr_wildcard_wildcard = flecs_ensure_id_record(world, + ecs_pair(EcsWildcard, EcsWildcard)); + world->idr_any = flecs_ensure_id_record(world, EcsAny); /* Create table for initial components */ ecs_table_t *table = bootstrap_component_table(world); @@ -47450,6 +47444,24 @@ void flecs_bootstrap( world->info.min_id = 0; world->info.max_id = 0; + /* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */ + ecs_set(world, EcsOnAdd, EcsIterable, { .init = on_event_iterable_init }); + ecs_set(world, EcsOnSet, EcsIterable, { .init = on_event_iterable_init }); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_id(EcsComponent), .subj.set.mask = EcsSelf }, + .events = {EcsOnSet}, + .callback = on_set_component, + .yield_existing = true + }); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = EcsTag, .subj.set.mask = EcsSelf }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = register_tag, + .yield_existing = true + }); + /* Populate core module */ ecs_set_scope(world, EcsFlecsCore); @@ -47488,6 +47500,7 @@ void flecs_bootstrap( flecs_bootstrap_tag(world, EcsFinal); flecs_bootstrap_tag(world, EcsDontInherit); flecs_bootstrap_tag(world, EcsTag); + flecs_bootstrap_tag(world, EcsUnion); flecs_bootstrap_tag(world, EcsExclusive); flecs_bootstrap_tag(world, EcsAcyclic); flecs_bootstrap_tag(world, EcsWith); @@ -47497,7 +47510,7 @@ void flecs_bootstrap( flecs_bootstrap_tag(world, EcsOnDeleteObject); flecs_bootstrap_tag(world, EcsRemove); flecs_bootstrap_tag(world, EcsDelete); - flecs_bootstrap_tag(world, EcsThrow); + flecs_bootstrap_tag(world, EcsPanic); flecs_bootstrap_tag(world, EcsDefaultChildComponent); @@ -47513,22 +47526,11 @@ void flecs_bootstrap( bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); - /* Transitive relations are always Acyclic */ - ecs_add_pair(world, EcsTransitive, EcsWith, EcsAcyclic); - - /* Transitive relations */ - ecs_add_id(world, EcsIsA, EcsTransitive); - ecs_add_id(world, EcsIsA, EcsReflexive); - /* Tag relations (relations that should never have data) */ ecs_add_id(world, EcsIsA, EcsTag); ecs_add_id(world, EcsChildOf, EcsTag); ecs_add_id(world, EcsDefaultChildComponent, EcsTag); - - /* Acyclic relations */ - ecs_add_id(world, EcsIsA, EcsAcyclic); - ecs_add_id(world, EcsChildOf, EcsAcyclic); - ecs_add_id(world, EcsWith, EcsAcyclic); + ecs_add_id(world, EcsUnion, EcsTag); /* Exclusive properties */ ecs_add_id(world, EcsChildOf, EcsExclusive); @@ -47537,14 +47539,9 @@ void flecs_bootstrap( ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); ecs_add_id(world, EcsOneOf, EcsExclusive); - /* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */ - ecs_set(world, EcsOnAdd, EcsIterable, { .init = on_event_iterable_init }); - ecs_set(world, EcsOnSet, EcsIterable, { .init = on_event_iterable_init }); - - /* Removal of ChildOf objects (parents) deletes the subject (child) */ - ecs_add_pair(world, EcsChildOf, EcsOnDeleteObject, EcsDelete); - - /* ChildOf, Identifier, Disabled and Prefab should never be inherited */ + /* Sync properties of ChildOf and Identifier with bootstrapped flags */ + ecs_add_pair(world, EcsChildOf, EcsOnDeleteObject, EcsDelete); + ecs_add_id(world, EcsChildOf, EcsAcyclic); ecs_add_id(world, EcsChildOf, EcsDontInherit); ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit); @@ -47580,6 +47577,12 @@ void flecs_bootstrap( .callback = register_on_delete_object }); + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = EcsAcyclic, .subj.set.mask = EcsSelf }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = register_acyclic + }); + ecs_trigger_init(world, &(ecs_trigger_desc_t){ .term = {.id = EcsExclusive, .subj.set.mask = EcsSelf }, .events = {EcsOnAdd}, @@ -47598,6 +47601,18 @@ void flecs_bootstrap( .callback = register_dont_inherit }); + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(EcsWith, EcsWildcard), .subj.set.mask = EcsSelf }, + .events = {EcsOnAdd}, + .callback = register_with + }); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = EcsUnion, .subj.set.mask = EcsSelf }, + .events = {EcsOnAdd}, + .callback = register_union + }); + /* Define trigger to make sure that adding a module to a child entity also * adds it to the parent. */ ecs_trigger_init(world, &(ecs_trigger_desc_t){ @@ -47613,16 +47628,21 @@ void flecs_bootstrap( .callback = on_set_component_lifecycle }); - /* Define trigger for updating component size when it changes */ - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_id(EcsComponent), .subj.set.mask = EcsSelf }, - .events = {EcsOnSet}, - .callback = on_set_component - }); + /* Acyclic components */ + ecs_add_id(world, EcsIsA, EcsAcyclic); + ecs_add_id(world, EcsWith, EcsAcyclic); + /* DontInherit components */ ecs_add_id(world, EcsDisabled, EcsDontInherit); ecs_add_id(world, EcsPrefab, EcsDontInherit); + /* Transitive relations are always Acyclic */ + ecs_add_pair(world, EcsTransitive, EcsWith, EcsAcyclic); + + /* Transitive relations */ + ecs_add_id(world, EcsIsA, EcsTransitive); + ecs_add_id(world, EcsIsA, EcsReflexive); + /* Run bootstrap functions for other parts of the code */ flecs_bootstrap_hierarchy(world); @@ -47654,6 +47674,7 @@ bool path_append( if (ecs_is_valid(world, child)) { cur = ecs_get_object(world, child, EcsChildOf, 0); if (cur) { + ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { path_append(world, parent, cur, sep, prefix, buf); ecs_strbuf_appendstr(buf, sep); @@ -48252,3 +48273,568 @@ ecs_entity_t ecs_new_from_path_w_sep( return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } + +static +ecs_id_record_elem_t* id_record_elem( + ecs_id_record_t *head, + ecs_id_record_elem_t *list, + ecs_id_record_t *idr) +{ + return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); +} + +static +void id_record_elem_insert( + ecs_id_record_t *head, + ecs_id_record_t *idr, + ecs_id_record_elem_t *elem) +{ + ecs_id_record_elem_t *head_elem = id_record_elem(idr, elem, head); + ecs_id_record_t *cur = head_elem->next; + elem->next = cur; + elem->prev = head; + if (cur) { + ecs_id_record_elem_t *cur_elem = id_record_elem(idr, elem, cur); + cur_elem->prev = idr; + } + head_elem->next = idr; +} + +static +void id_record_elem_remove( + ecs_id_record_t *idr, + ecs_id_record_elem_t *elem) +{ + ecs_id_record_t *prev = elem->prev; + ecs_id_record_t *next = elem->next; + ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_record_elem_t *prev_elem = id_record_elem(idr, elem, prev); + prev_elem->next = next; + if (next) { + ecs_id_record_elem_t *next_elem = id_record_elem(idr, elem, next); + next_elem->prev = prev; + } +} + +static +void insert_id_elem( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_id_t wildcard, + ecs_id_record_t *widr) +{ + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + if (!widr) { + widr = flecs_ensure_id_record(world, wildcard); + } + ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + id_record_elem_insert(widr, idr, &idr->first); + } else { + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + id_record_elem_insert(widr, idr, &idr->second); + + if (idr->flags & EcsIdAcyclic) { + id_record_elem_insert(widr, idr, &idr->acyclic); + } + } +} + +static +void remove_id_elem( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_id_t wildcard) +{ + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *widr = flecs_get_id_record(world, wildcard); + if (!widr) { + return; + } + + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + id_record_elem_remove(idr, &idr->first); + } else { + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + id_record_elem_remove(idr, &idr->second); + + if (idr->flags & EcsIdAcyclic) { + id_record_elem_remove(idr, &idr->acyclic); + } + } +} + +static +ecs_id_record_t* new_id_record( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = ecs_os_calloc_t(ecs_id_record_t); + ecs_table_cache_init(&idr->cache); + + idr->id = id; + + bool is_wildcard = ecs_id_is_wildcard(id); + + ecs_entity_t rel = 0, obj = 0, role = id & ECS_ROLE_MASK; + if (role == ECS_PAIR) { + rel = ecs_pair_first(world, id); + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + + /* Relation object can be 0, as tables without a ChildOf relation are + * added to the (ChildOf, 0) id record */ + obj = ECS_PAIR_SECOND(id); + if (obj) { + obj = ecs_get_alive(world, obj); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + } + + /* Check constraints */ + if (obj && !ecs_id_is_wildcard(obj)) { + ecs_entity_t oneof = flecs_get_oneof(world, rel); + ecs_check( !oneof || ecs_has_pair(world, obj, EcsChildOf, oneof), + ECS_CONSTRAINT_VIOLATED, NULL); + (void)oneof; + } + + if (!is_wildcard) { + /* Inherit flags from (relation, *) record */ + ecs_id_record_t *idr_r = flecs_ensure_id_record( + world, ecs_pair(rel, EcsWildcard)); + idr->parent = idr_r; + idr->flags = idr_r->flags; + + /* If pair is not a wildcard, append it to wildcard lists. These + * allow for quickly enumerating all relations for an object, or all + * objecs for a relation. */ + insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); + insert_id_elem(world, idr, ecs_pair(EcsWildcard, obj), NULL); + + if (rel == EcsUnion) { + idr->flags |= EcsIdUnion; + } + } + } 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 || (role == ECS_PAIR))) { + if (!(idr->flags & EcsIdTag)) { + const ecs_type_info_t *ti = flecs_get_type_info(world, rel); + if (!ti && obj) { + ti = flecs_get_type_info(world, obj); + } + idr->type_info = ti; + } + } + + /* Mark entities that are used as component/pair ids. When a tracked + * entity is deleted, cleanup policies are applied so that the store + * won't contain any tables with deleted ids. */ + + /* Flag for OnDelete policies */ + flecs_add_flag(world, rel, EcsEntityObservedId); + if (obj) { + /* Flag for OnDeleteObject policies */ + flecs_add_flag(world, obj, EcsEntityObservedObject); + if (ecs_has_id(world, rel, EcsAcyclic)) { + /* Flag used to determine if object should be traversed when + * propagating events or with super/subset queries */ + flecs_add_flag(world, obj, EcsEntityObservedAcyclic); + } + } + + /* Flags for events */ + if (flecs_check_triggers_for_event(world, id, EcsOnAdd)) { + idr->flags |= EcsIdHasOnAdd; + } + if (flecs_check_triggers_for_event(world, id, EcsOnRemove)) { + idr->flags |= EcsIdHasOnRemove; + } + if (flecs_check_triggers_for_event(world, id, EcsOnSet)) { + idr->flags |= EcsIdHasOnSet; + } + if (flecs_check_triggers_for_event(world, id, EcsUnSet)) { + idr->flags |= EcsIdHasUnSet; + } + + if (ecs_should_log_1()) { + char *id_str = ecs_id_str(world, id); + ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); + ecs_os_free(id_str); + } + + /* Update counters */ + world->info.id_create_total ++; + + if (!is_wildcard) { + world->info.id_count ++; + + if (idr->type_info) { + world->info.component_id_count ++; + } else { + world->info.tag_id_count ++; + } + + if (ECS_HAS_ROLE(id, PAIR)) { + world->info.pair_id_count ++; + } + } else { + world->info.wildcard_id_count ++; + } + + return idr; +error: + return NULL; +} + +/* Cleanup id index */ +static +bool free_id_record( + ecs_world_t *world, + ecs_id_t id, + ecs_id_record_t *idr) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_poly_assert(world, ecs_world_t); + (void)id; + + /* Force the empty table administration to be consistent if the non-empty + * list of the id record has elements */ + if (ecs_table_cache_count(&idr->cache)) { + ecs_run_aperiodic(world, EcsAperiodicEmptyTableEvents); + } + + /* If there are still tables in the non-empty list they're really not empty. + * We can't free the record yet. */ + if (ecs_table_cache_count(&idr->cache)) { + return false; + } + + /* If id record contains no more empty tables, free it */ + if (ecs_table_cache_empty_count(&idr->cache) == 0) { + if (ecs_should_log_1()) { + char *id_str = ecs_id_str(world, id); + ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str); + ecs_os_free(id_str); + } + + if (ECS_HAS_ROLE(id, PAIR)) { + if (!ecs_id_is_wildcard(id)) { + ecs_entity_t rel = ecs_pair_first(world, id); + ecs_entity_t obj = ECS_PAIR_SECOND(id); + remove_id_elem(world, idr, ecs_pair(rel, EcsWildcard)); + remove_id_elem(world, idr, ecs_pair(EcsWildcard, obj)); + } + } + + /* Update counters */ + world->info.id_delete_total ++; + + if (!ecs_id_is_wildcard(id)) { + world->info.id_count --; + + if (ECS_HAS_ROLE(id, PAIR)) { + world->info.pair_id_count --; + } + + if (idr->type_info) { + world->info.component_id_count --; + } else { + world->info.tag_id_count --; + } + } else { + world->info.wildcard_id_count --; + } + + ecs_table_cache_fini(&idr->cache); + flecs_name_index_free(idr->name_index); + ecs_os_free(idr); + return true; + } + + /* Delete empty tables */ + ecs_table_cache_iter_t cache_it; + flecs_table_cache_empty_iter(&idr->cache, &cache_it); + + const ecs_table_record_t *tr; + int32_t unreleased_count = 0; + while ((tr = flecs_table_cache_next(&cache_it, ecs_table_record_t))) { + if (!flecs_table_release(world, tr->hdr.table)) { + /* Releasing the table did not free it, which means that something + * is keeping the table alive. Cleanup of the id record will happen + * when the last reference(s) to the table are released */ + unreleased_count ++; + } + } + if (unreleased_count) { + return false; + } + + /* If all tables were deleted for this id record, the last deleted table + * should have removed the record from the world. */ + ecs_assert(flecs_get_id_record(world, id) == NULL, + ECS_INTERNAL_ERROR, NULL); + + return true; +} + +static +ecs_id_t flecs_id_record_id( + ecs_id_t id) +{ + id = ecs_strip_generation(id); + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t o = ECS_PAIR_SECOND(id); + if (r == EcsAny) { + r = EcsWildcard; + } + if (o == EcsAny) { + o = EcsWildcard; + } + id = ecs_pair(r, o); + } + return id; +} + +ecs_id_record_t* flecs_ensure_id_record( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t **idr_ptr = ecs_map_ensure(&world->id_index, + ecs_id_record_t*, ecs_strip_generation(id)); + ecs_id_record_t *idr = idr_ptr[0]; + if (!idr) { + idr = new_id_record(world, id); + idr_ptr = ecs_map_get(&world->id_index, + ecs_id_record_t*, flecs_id_record_id(id)); + ecs_assert(idr_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + idr_ptr[0] = idr; + } + + return idr; +} + +ecs_id_record_t* flecs_get_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + return ecs_map_get_ptr(&world->id_index, ecs_id_record_t*, + flecs_id_record_id(id)); +} + +ecs_id_record_t* flecs_get_query_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + if (ECS_HAS_ROLE(id, PAIR)) { + idr = flecs_get_id_record(world, + ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + } + return idr; + } + if (ECS_HAS_ROLE(id, PAIR) && + ECS_PAIR_SECOND(id) == EcsWildcard && + (idr->flags & EcsIdUnion)) + { + idr = flecs_get_id_record(world, + ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + } + + return idr; +} + +void flecs_remove_id_record( + ecs_world_t *world, + ecs_id_t id, + ecs_id_record_t *idr) +{ + ecs_poly_assert(world, ecs_world_t); + + /* Free id record resources */ + if (free_id_record(world, id, idr)) { + /* Remove record from world index */ + ecs_map_remove(&world->id_index, flecs_id_record_id(id)); + } +} + +void flecs_clear_id_record( + ecs_world_t *world, + ecs_id_t id, + ecs_id_record_t *idr) +{ + ecs_poly_assert(world, ecs_world_t); + + if (world->is_fini) { + return; + } + + ecs_table_cache_fini_delete_all(world, &idr->cache); + + flecs_remove_id_record(world, id, idr); +} + +bool flecs_set_type_info_for_id_record( + ecs_world_t *world, + ecs_id_record_t *idr, + const ecs_type_info_t *ti) +{ + if (ti) { + if (!idr->type_info) { + world->info.tag_id_count --; + world->info.component_id_count ++; + } + } else { + if (idr->type_info) { + world->info.tag_id_count ++; + world->info.component_id_count --; + } + } + + bool changed = idr->type_info != ti; + idr->type_info = ti; + return changed; +} + +ecs_hashmap_t* flecs_ensure_id_name_index( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t *idr = flecs_get_id_record(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(); + } + + return map; +} + +ecs_hashmap_t* flecs_get_id_name_index( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + return idr->name_index; +} + +ecs_table_record_t* flecs_get_table_record( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t* idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); +} + +const ecs_table_record_t* flecs_id_record_table( + ecs_id_record_t *idr, + ecs_table_t *table) +{ + if (!idr) { + return NULL; + } + return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); +} + +void flecs_register_for_id_record( + ecs_world_t *world, + ecs_id_t id, + const ecs_table_t *table, + ecs_table_record_t *tr) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t *idr = flecs_ensure_id_record(world, id); + if (!idr) { + return; + } + + ecs_table_cache_insert(&idr->cache, table, &tr->hdr); +} + +void flecs_fini_id_records( + ecs_world_t *world) +{ + ecs_map_iter_t it = ecs_map_iter(&world->id_index); + ecs_id_record_t *idr; + ecs_map_key_t key; + while ((idr = ecs_map_next_ptr(&it, ecs_id_record_t*, &key))) { + free_id_record(world, key, idr); + } + + ecs_map_fini(&world->id_index); + flecs_sparse_free(world->pending_tables); + flecs_sparse_free(world->pending_buffer); +} + +ecs_id_record_t* flecs_table_iter( + ecs_world_t *world, + ecs_id_t id, + ecs_table_cache_iter_t *out) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + flecs_process_pending_tables(world); + ecs_assert( flecs_sparse_count(world->pending_tables) == 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_table_cache_iter(&idr->cache, out); + return idr; +} + +ecs_id_record_t* flecs_empty_table_iter( + ecs_world_t *world, + ecs_id_t id, + ecs_table_cache_iter_t *out) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + flecs_process_pending_tables(world); + ecs_assert( flecs_sparse_count(world->pending_tables) == 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_table_cache_empty_iter(&idr->cache, out); + return idr; +} + diff --git a/deps/flecs.h b/deps/flecs.h index 26541a4f..2ee0f21d 100644 --- a/deps/flecs.h +++ b/deps/flecs.h @@ -128,6 +128,152 @@ #ifndef FLECS_API_DEFINES_H #define FLECS_API_DEFINES_H +/** + * @file api_flags.h + * @brief Bitset flags used by internals. + */ + +#ifndef FLECS_API_FLAGS_H +#define FLECS_API_FLAGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Entity flags (set in upper bits of ecs_record_t::row) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsEntityObserved (1u << 31) +#define EcsEntityObservedId (1u << 30) +#define EcsEntityObservedObject (1u << 29) +#define EcsEntityObservedAcyclic (1u << 28) + + +//////////////////////////////////////////////////////////////////////////////// +//// Id flags (used by ecs_id_record_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsIdOnDeleteRemove (1u << 0) +#define EcsIdOnDeleteDelete (1u << 1) +#define EcsIdOnDeletePanic (1u << 2) +#define EcsIdOnDeleteMask\ + (EcsIdOnDeletePanic|EcsIdOnDeleteRemove|EcsIdOnDeleteDelete) + +#define EcsIdOnDeleteObjectRemove (1u << 3) +#define EcsIdOnDeleteObjectDelete (1u << 4) +#define EcsIdOnDeleteObjectPanic (1u << 5) +#define EcsIdOnDeleteObjectMask\ + (EcsIdOnDeleteObjectPanic|EcsIdOnDeleteObjectRemove|\ + EcsIdOnDeleteObjectDelete) + +#define EcsIdExclusive (1u << 6) +#define EcsIdDontInherit (1u << 7) +#define EcsIdAcyclic (1u << 8) +#define EcsIdTag (1u << 9) +#define EcsIdWith (1u << 10) +#define EcsIdUnion (1u << 11) + +#define EcsIdHasOnAdd (1u << 15) /* Same values as table flags */ +#define EcsIdHasOnRemove (1u << 16) +#define EcsIdHasOnSet (1u << 17) +#define EcsIdHasUnSet (1u << 18) +#define EcsIdEventMask\ + (EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet|EcsIdHasUnSet) + +/* Utilities for converting from flags to delete policies and vice versa */ +#define ECS_ID_ON_DELETE(flags) \ + ((ecs_entity_t[]){0, EcsRemove, EcsDelete, 0, EcsPanic}\ + [((flags) & EcsIdOnDeleteMask)]) +#define ECS_ID_ON_DELETE_OBJECT(flags) ECS_ID_ON_DELETE(flags >> 3) +#define ECS_ID_ON_DELETE_FLAG(id) (1u << ((id) - EcsRemove)) +#define ECS_ID_ON_DELETE_OBJECT_FLAG(id) (1u << (3 + ((id) - EcsRemove))) + + +//////////////////////////////////////////////////////////////////////////////// +//// Iterator flags (used by ecs_iter_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsIterIsValid (1u << 0u) /* Does iterator contain valid result */ +#define EcsIterIsFilter (1u << 1u) /* Is iterator filter (metadata only) */ +#define EcsIterIsInstanced (1u << 2u) /* Is iterator instanced */ +#define EcsIterHasShared (1u << 3u) /* Does result have shared terms */ +#define EcsIterTableOnly (1u << 4u) /* Result only populates table */ +#define EcsIterEntityOptional (1u << 5u) /* Treat terms with entity subject as optional */ +#define EcsIterNoResults (1u << 6u) /* Iterator has no results */ +#define EcsIterIgnoreThis (1u << 7u) /* Only evaluate non-this terms */ + + +//////////////////////////////////////////////////////////////////////////////// +//// Filter flags (used by ecs_filter_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsFilterMatchThis (1u << 1u) /* Has terms that match This */ +#define EcsFilterMatchOnlyThis (1u << 2u) /* Has only terms that match This */ +#define EcsFilterMatchPrefab (1u << 3u) /* Does filter match prefabs */ +#define EcsFilterMatchDisabled (1u << 4u) /* Does filter match disabled entities */ +#define EcsFilterMatchEmptyTables (1u << 5u) /* Does filter return empty tables */ +#define EcsFilterMatchAnything (1u << 6u) /* False if filter has no/only Not terms */ +#define EcsFilterIsFilter (1u << 7u) /* When true, data fields won't be populated */ +#define EcsFilterIsInstanced (1u << 8u) /* Is filter instanced (see ecs_filter_desc_t) */ + + +//////////////////////////////////////////////////////////////////////////////// +//// Table flags (used by ecs_table_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsTableHasBuiltins (1u << 1u) /* Does table have builtin components */ +#define EcsTableIsPrefab (1u << 2u) /* Does the table store prefabs */ +#define EcsTableHasIsA (1u << 3u) /* Does the table have IsA relation */ +#define EcsTableHasChildOf (1u << 4u) /* Does the table type ChildOf relation */ +#define EcsTableHasPairs (1u << 5u) /* Does the table type have pairs */ +#define EcsTableHasModule (1u << 6u) /* Does the table have module data */ +#define EcsTableIsDisabled (1u << 7u) /* Does the table type has EcsDisabled */ +#define EcsTableHasCtors (1u << 8u) +#define EcsTableHasDtors (1u << 9u) +#define EcsTableHasCopy (1u << 10u) +#define EcsTableHasMove (1u << 11u) +#define EcsTableHasUnion (1u << 12u) +#define EcsTableHasDisabled (1u << 13u) +#define EcsTableHasOverrides (1u << 14u) + +#define EcsTableHasOnAdd (1u << 15u) /* Same values as id flags */ +#define EcsTableHasOnRemove (1u << 16u) +#define EcsTableHasOnSet (1u << 17u) +#define EcsTableHasUnSet (1u << 18u) + +/* Composite table flags */ +#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors) +#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasUnion | EcsTableHasDisabled) +#define EcsTableHasAddActions (EcsTableHasIsA | EcsTableHasUnion | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet) +#define EcsTableHasRemoveActions (EcsTableHasIsA | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet) + + +//////////////////////////////////////////////////////////////////////////////// +//// Query flags (used by ecs_query_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsQueryHasRefs (1u << 1u) /* Does query have references */ +#define EcsQueryIsSubquery (1u << 2u) /* Is query a subquery */ +#define EcsQueryIsOrphaned (1u << 3u) /* Is subquery orphaned */ +#define EcsQueryHasOutColumns (1u << 4u) /* Does query have out columns */ +#define EcsQueryHasMonitor (1u << 5u) /* Does query track changes */ + + +//////////////////////////////////////////////////////////////////////////////// +//// Aperiodic action flags (used by ecs_run_aperiodic) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsAperiodicEmptyTableEvents (1u << 1u) /* Process pending empty table events */ +#define EcsAperiodicComponentMonitors (1u << 2u) /* Process component monitors */ + +#ifdef __cplusplus +} +#endif + +#endif + + #if defined(_WIN32) || defined(_MSC_VER) || defined(__MING32__) #define ECS_TARGET_WINDOWS #elif defined(__ANDROID__) @@ -309,10 +455,6 @@ typedef int32_t ecs_size_t; #define ECS_ROW_MASK (0x0FFFFFFFu) #define ECS_ROW_FLAGS_MASK (~ECS_ROW_MASK) -#define ECS_FLAG_OBSERVED (1u << 31) -#define ECS_FLAG_OBSERVED_ID (1u << 30) -#define ECS_FLAG_OBSERVED_OBJECT (1u << 29) -#define ECS_FLAG_OBSERVED_ACYCLIC (1u << 28) #define ECS_RECORD_TO_ROW(v) (ECS_CAST(int32_t, (ECS_CAST(uint32_t, v) & ECS_ROW_MASK))) #define ECS_RECORD_TO_ROW_FLAGS(v) (ECS_CAST(uint32_t, v) & ECS_ROW_FLAGS_MASK) #define ECS_ROW_TO_RECORD(row, flags) (ECS_CAST(uint32_t, (ECS_CAST(uint32_t, row) | (flags)))) @@ -338,36 +480,6 @@ typedef int32_t ecs_size_t; (ECS_HAS_PAIR_OBJECT(id, ECS_PAIR_FIRST(has_id), ECS_PAIR_SECOND(has_id)))) -//////////////////////////////////////////////////////////////////////////////// -//// Flags for quickly querying properties of component/relation id -//////////////////////////////////////////////////////////////////////////////// - -#define ECS_ID_ON_DELETE_REMOVE (1u << 0) -#define ECS_ID_ON_DELETE_DELETE (1u << 1) -#define ECS_ID_ON_DELETE_THROW (1u << 2) -#define ECS_ID_ON_DELETE_MASK\ - (ECS_ID_ON_DELETE_THROW|ECS_ID_ON_DELETE_REMOVE|ECS_ID_ON_DELETE_DELETE) - -#define ECS_ID_ON_DELETE_OBJECT_REMOVE (1u << 3) -#define ECS_ID_ON_DELETE_OBJECT_DELETE (1u << 4) -#define ECS_ID_ON_DELETE_OBJECT_THROW (1u << 5) -#define ECS_ID_ON_DELETE_OBJECT_MASK\ - (ECS_ID_ON_DELETE_OBJECT_THROW|ECS_ID_ON_DELETE_OBJECT_REMOVE|\ - ECS_ID_ON_DELETE_OBJECT_DELETE) - -#define ECS_ID_EXCLUSIVE (1u << 6) -#define ECS_ID_DONT_INHERIT (1u << 7) -#define ECS_TYPE_INFO_INITIALIZED (1u << 8) - -/* Utilities for converting from flags to delete policies and vice versa */ -#define ECS_ID_ON_DELETE(flags) \ - ((ecs_entity_t[]){0, EcsRemove, EcsDelete, 0, EcsThrow}\ - [((flags) & ECS_ID_ON_DELETE_MASK)]) -#define ECS_ID_ON_DELETE_OBJECT(flags) ECS_ID_ON_DELETE(flags >> 3) -#define ECS_ID_ON_DELETE_FLAG(id) (1u << ((id) - EcsRemove)) -#define ECS_ID_ON_DELETE_OBJECT_FLAG(id) (1u << (3 + ((id) - EcsRemove))) - - //////////////////////////////////////////////////////////////////////////////// //// Convert between C typenames and variables //////////////////////////////////////////////////////////////////////////////// @@ -388,7 +500,6 @@ typedef int32_t ecs_size_t; #define ecs_entity_t_comb(lo, hi) ((ECS_CAST(uint64_t, hi) << 32) + ECS_CAST(uint32_t, lo)) #define ecs_pair(pred, obj) (ECS_PAIR | ecs_entity_t_comb(obj, pred)) -#define ecs_case(pred, obj) (ECS_CASE | ecs_entity_t_comb(obj, pred)) /* Get object from pair with the correct (current) generation count */ #define ecs_pair_first(world, pair) ecs_get_alive(world, ECS_PAIR_FIRST(pair)) @@ -558,41 +669,6 @@ extern "C" { /* Compute the header size of the vector from a provided compile-time type */ #define ECS_VECTOR_T(T) ECS_VECTOR_U(ECS_SIZEOF(T), ECS_ALIGNOF(T)) -/* Utility macro's for creating vector on stack */ -#ifndef FLECS_NDEBUG -#define ECS_VECTOR_VALUE(T, elem_count)\ -{\ - .elem_size = (int32_t)(ECS_SIZEOF(T)),\ - .count = elem_count,\ - .size = elem_count\ -} -#else -#define ECS_VECTOR_VALUE(T, elem_count)\ -{\ - .count = elem_count,\ - .size = elem_count\ -} -#endif - -#define ECS_VECTOR_DECL(name, T, elem_count)\ -struct {\ - union {\ - ecs_vector_t vector;\ - uint64_t align;\ - } header;\ - T array[elem_count];\ -} __##name##_value = {\ - .header.vector = ECS_VECTOR_VALUE(T, elem_count)\ -};\ -const ecs_vector_t *name = (ecs_vector_t*)&__##name##_value - -#define ECS_VECTOR_IMPL(name, T, elems, elem_count)\ -ecs_os_memcpy(__##name##_value.array, elems, sizeof(T) * elem_count) - -#define ECS_VECTOR_STACK(name, T, elems, elem_count)\ -ECS_VECTOR_DECL(name, T, elem_count);\ -ECS_VECTOR_IMPL(name, T, elems, elem_count) - typedef struct ecs_vector_t ecs_vector_t; typedef int (*ecs_comparator_t)( @@ -1082,26 +1158,48 @@ extern "C" { typedef uint64_t ecs_map_key_t; /* Map type */ +typedef struct ecs_bucket_entry_t { + struct ecs_bucket_entry_t *next; + ecs_map_key_t key; + /* payload right after key. */ +} ecs_bucket_entry_t; + +typedef struct ecs_block_allocator_block_t { + void *memory; + struct ecs_block_allocator_block_t *next; +} ecs_block_allocator_block_t; + +typedef struct ecs_block_allocator_chunk_header_t { + struct ecs_block_allocator_chunk_header_t *next; +} ecs_block_allocator_chunk_header_t; + +typedef struct ecs_block_allocator_t { + ecs_block_allocator_chunk_header_t *head; + ecs_block_allocator_block_t *block_head; + ecs_block_allocator_block_t *block_tail; + int32_t chunk_size; + int32_t chunks_per_block; + int32_t block_size; +} ecs_block_allocator_t; + typedef struct ecs_bucket_t { - ecs_map_key_t *keys; /* Array with keys */ - void *payload; /* Payload array */ - int32_t count; /* Number of elements in bucket */ + ecs_bucket_entry_t *first; } ecs_bucket_t; typedef struct ecs_map_t { ecs_bucket_t *buckets; + ecs_bucket_t *buckets_end; int16_t elem_size; uint8_t bucket_shift; int32_t bucket_count; int32_t count; + ecs_block_allocator_t allocator; } ecs_map_t; typedef struct ecs_map_iter_t { const ecs_map_t *map; - struct ecs_bucket_t *bucket; - int32_t bucket_index; - int32_t element_index; - void *payload; + ecs_bucket_t *bucket; + ecs_bucket_entry_t *entry; } ecs_map_iter_t; #define ECS_MAP_INIT(T) { .elem_size = ECS_SIZEOF(T) } @@ -2023,8 +2121,11 @@ typedef uint64_t ecs_id_t; /** An entity identifier. */ typedef ecs_id_t ecs_entity_t; -/** A vector containing component identifiers used to describe a type. */ -typedef const ecs_vector_t* ecs_type_t; +/** An array with (component) ids */ +typedef struct { + ecs_id_t *array; + int32_t count; +} ecs_type_t; /** A world is the container for all ECS data and supporting features. */ typedef struct ecs_world_t ecs_world_t; @@ -2329,15 +2430,7 @@ struct ecs_filter_t { ecs_term_t term_cache[ECS_TERM_CACHE_SIZE]; /* Cache for small filters */ bool term_cache_used; - bool match_this; /* Has terms that match EcsThis */ - bool match_only_this; /* Has only terms that match EcsThis */ - bool match_prefab; /* Does filter match prefabs */ - bool match_disabled; /* Does filter match disabled entities */ - bool match_anything; /* False if filter has no/only Not terms */ - - bool filter; /* When true, data fields won't be populated */ - bool instanced; /* See ecs_filter_desc_t */ - bool match_empty_tables; /* See ecs_filter_desc_t */ + ecs_flags32_t flags; /* Filter flags */ char *name; /* Name of filter (optional) */ char *variable_names[1]; /* Array with variable names */ @@ -2372,6 +2465,7 @@ struct ecs_trigger_t { uint64_t id; /* Internal id */ int32_t *last_event_id; /* Optional pointer to observer last_event_id */ + ecs_id_t register_id; /* Id with with trigger is registered */ }; /* An observer reacts to events matching a filter */ @@ -2494,22 +2588,12 @@ typedef struct ecs_var_t { /** Cached reference. */ struct ecs_ref_t { - ecs_entity_t entity; /* Entity of the reference */ - ecs_entity_t component; /* Component of the reference */ - void *table; /* Last known table */ - uint32_t row; /* Last known location in table */ - int32_t alloc_count; /* Last known alloc count of table */ - ecs_record_t *record; /* Pointer to record, if in main stage */ - const void *ptr; /* Cached ptr */ + ecs_entity_t entity; /* Entity */ + ecs_entity_t id; /* Component id */ + struct ecs_table_record_t *tr; /* Table record for component */ + ecs_record_t *record; /* Entity index record */ }; -/** Array of entity ids that, other than a type, can live on the stack */ -typedef struct ecs_ids_t { - ecs_id_t *array; /* An array with entity ids */ - int32_t count; /* The number of entities in the array */ - int32_t size; /* The size of the array */ -} ecs_ids_t; - /* Page-iterator specific data */ typedef struct ecs_page_iter_t { int32_t offset; @@ -2554,9 +2638,9 @@ typedef struct ecs_term_iter_t { } ecs_term_iter_t; typedef enum ecs_iter_kind_t { + EcsIterEvalCondition, EcsIterEvalTables, EcsIterEvalChain, - EcsIterEvalCondition, EcsIterEvalNone } ecs_iter_kind_t; @@ -2652,27 +2736,17 @@ typedef struct ecs_iter_private_t { ecs_iter_cache_t cache; /* Inline arrays to reduce allocations */ } ecs_iter_private_t; -/* Bits for iterator flags */ -#define EcsIterIsValid (1u << 0u) /* Does iterator contain valid result */ -#define EcsIterIsFilter (1u << 1u) /* Is iterator filter (metadata only) */ -#define EcsIterIsInstanced (1u << 2u) /* Is iterator instanced */ -#define EcsIterHasShared (1u << 3u) /* Does result have shared terms */ -#define EcsIterTableOnly (1u << 4u) /* Result only populates table */ -#define EcsIterEntityOptional (1u << 5u) /* Treat terms with entity subject as optional */ - -/** Iterator. - */ +/** Iterator */ struct ecs_iter_t { /* World */ ecs_world_t *world; /* The world */ - ecs_world_t *real_world; /* Actual world. This differs from world when in staged mode */ + ecs_world_t *real_world; /* Actual world. This differs from world when in readonly mode */ /* Matched data */ ecs_entity_t *entities; /* Entity identifiers */ void **ptrs; /* Pointers to components. Array if from this, pointer if not. */ ecs_size_t *sizes; /* Component sizes */ ecs_table_t *table; /* Current table */ - ecs_type_t type; /* Current type */ ecs_table_t *other_table; /* Prev or next table when adding/removing */ ecs_id_t *ids; /* (Component) ids */ ecs_var_t *variables; /* Values of variables (if any) */ @@ -2814,6 +2888,7 @@ void ecs_default_ctor( #endif #define ECS_ELEM(ptr, size, index) ECS_OFFSET(ptr, (size) * (index)) +#define ECS_ELEM_T(o, T, index) ECS_ELEM(o, ECS_SIZEOF(T), index) /** Enable/disable bitsets */ #define ECS_BIT_SET(flags, bit) (flags) |= (bit) @@ -3610,7 +3685,7 @@ typedef struct EcsComponent { * This component allows for the creation of entities that represent a type, and * therefore the creation of named types. */ typedef struct EcsType { - ecs_type_t type; /* Preserved nested types */ + const ecs_type_t *type; /* Preserved nested types */ ecs_table_t *normalized; /* Table with union of type + nested AND types */ } EcsType; @@ -3774,12 +3849,6 @@ extern "C" { /** Role bit added to roles to differentiate between roles and generations */ #define ECS_ROLE (1ull << 63) -/** Cases are used to switch between mutually exclusive components */ -FLECS_API extern const ecs_id_t ECS_CASE; - -/** Switches allow for fast switching between mutually exclusive components */ -FLECS_API extern const ecs_id_t ECS_SWITCH; - /** The PAIR role indicates that the entity is a pair identifier. */ FLECS_API extern const ecs_id_t ECS_PAIR; @@ -3908,6 +3977,11 @@ FLECS_API extern const ecs_entity_t EcsOneOf; * when it or the relation object is a component. */ FLECS_API extern const ecs_entity_t EcsTag; +/* Tag to indicate that relation is stored as union. Union relations enable + * changing the target of a union without switching tables. Union relationships + * are also marked as exclusive. */ +FLECS_API extern const ecs_entity_t EcsUnion; + /* Tag to indicate name identifier */ FLECS_API extern const ecs_entity_t EcsName; @@ -4017,7 +4091,7 @@ FLECS_API extern const ecs_entity_t EcsDelete; /* Specifies that whenever a component/relation/object of relation is deleted an * error should be thrown. Must be combined with EcsOnDelete or * EcsOnDeleteObject. */ -FLECS_API extern const ecs_entity_t EcsThrow; +FLECS_API extern const ecs_entity_t EcsPanic; /* Used like (EcsDefaultChildComponent, Component). When added to an entity, * this informs serialization formats which component to use when a value is @@ -4166,7 +4240,7 @@ FLECS_API void ecs_set_component_actions_w_id( ecs_world_t *world, ecs_id_t id, - EcsComponentLifecycle *actions); + const EcsComponentLifecycle *actions); /** Set a world context. * This operation allows an application to register custom data with a world @@ -4384,13 +4458,17 @@ int32_t ecs_get_threads( * such as delayed triggering of events, which can be inconvenient when for * example running a test suite. * - * This operation forces runs all aperiodic actions to run. + * The flags parameter specifies which aperiodic actions to run. Specify 0 to + * run all actions. Supported flags start with 'EcsAperiodic'. Flags identify + * internal mechanisms and may change unannounced. * * @param world The world. + * @param flags The flags specifying which actions to run. */ FLECS_API -void ecs_force_aperiodic( - ecs_world_t *world); +void ecs_run_aperiodic( + ecs_world_t *world, + ecs_flags32_t flags); /** Cleanup empty tables. * This operation cleans up empty tables that meet certain conditions. Having @@ -4793,39 +4871,35 @@ const void* ecs_get_id( ecs_entity_t entity, ecs_id_t id); - -/** Get an immutable reference to a component. - * This operation is similar to ecs_get_id but it stores temporary - * information in a `ecs_ref_t` value which allows subsequent lookups to be - * faster. +/** Create a ref. + * A ref is a handle to an entity + component which caches a small amount of + * data to reduce overhead of repeatedly accessing the component. Use + * ecs_ref_get to get the component data. * * @param world The world. - * @param ref Pointer to a ecs_ref_t value. Must be initialized. * @param entity The entity. - * @param id The id of the component to get. - * @return The component pointer, NULL if the entity does not have the component. + * @param id The id of the component. + * @return The reference. */ FLECS_API -const void* ecs_get_ref_id( +ecs_ref_t ecs_ref_init_id( const ecs_world_t *world, - ecs_ref_t *ref, ecs_entity_t entity, ecs_id_t id); -/** Get case for switch. - * This operation gets the current case for the specified switch. If the current - * switch is not set for the entity, the operation will return 0. - * +/** Get component from ref. + * Get component pointer from ref. The ref must be created with ecs_ref_init. + * * @param world The world. - * @param e The entity. - * @param sw The switch for which to obtain the case. - * @return The current case for the specified switch. + * @param ref The ref. + * @param id The component id. + * @return The component pointer, NULL if the entity does not have the component. */ FLECS_API -ecs_entity_t ecs_get_case( +void* ecs_ref_get_id( const ecs_world_t *world, - ecs_entity_t e, - ecs_entity_t sw); + ecs_ref_t *ref, + ecs_id_t id); /** @} */ @@ -5054,7 +5128,7 @@ bool ecs_exists( * @return The type of the entity, NULL if the entity has no components. */ FLECS_API -ecs_type_t ecs_get_type( +const ecs_type_t* ecs_get_type( const ecs_world_t *world, ecs_entity_t entity); @@ -5082,6 +5156,20 @@ ecs_table_t* ecs_get_storage_table( const ecs_world_t *world, ecs_entity_t entity); +/** Get the type for an id. + * This function returnsthe type information for an id. The specified id can be + * any valid id. For the rules on how type information is determined based on + * id, see ecs_get_typeid. + * + * @param world The world. + * @param id The id. + * @return The type information of the id. + */ +FLECS_API +const ecs_type_info_t* ecs_get_type_info( + const ecs_world_t *world, + ecs_id_t id); + /** Get the type for an id. * This operation returns the component id for an id, if the id is associated * with a type. For a regular component with a non-zero size (an entity with the @@ -5099,7 +5187,7 @@ ecs_table_t* ecs_get_storage_table( * * @param world The world. * @param id The id. - * @return The typeid of the entity. + * @return The type id of the id. */ FLECS_API ecs_entity_t ecs_get_typeid( @@ -5258,7 +5346,20 @@ void ecs_id_str_buf( FLECS_API char* ecs_type_str( const ecs_world_t *world, - ecs_type_t type); + const ecs_type_t* type); + +/** Convert table to string. + * Same as ecs_type_str(world, ecs_table_get_type(table)). The result of this + * operation must be freed with ecs_os_free. + * + * @param world The world. + * @param table The table. + * @return The stringified table type. + */ +FLECS_API +char* ecs_table_str( + const ecs_world_t *world, + ecs_table_t *table); /** Test if an entity has an entity. * This operation returns true if the entity has the provided entity in its @@ -6272,7 +6373,7 @@ typedef struct ecs_event_desc_t { /* Component ids. Only triggers with a matching component id will be * notified. Observers are guaranteed to get notified once, even if they * match more than one id. */ - ecs_ids_t *ids; + const ecs_type_t *ids; /* The table for which to notify. */ ecs_table_t *table; @@ -6815,7 +6916,7 @@ int32_t ecs_iter_find_column( * Note that this function can be used together with iter::type to * dynamically iterate all data that the matched entities have. An application * can use the ecs_vector_count function to obtain the number of elements in a - * type. All indices from 0..ecs_vector_count(type) are valid column indices. + * type. All indices from 0..type->count are valid column indices. * * Additionally, note that this provides unprotected access to the column data. * An iterator cannot know or prevent accessing columns that are not queried for @@ -6971,6 +7072,28 @@ FLECS_API bool ecs_defer_end( ecs_world_t *world); +/** Suspend deferring but do not flush queue. + * This operation can be used to do an undeferred operation while not flushing + * the operations in the queue. + * + * An application should invoke ecs_defer_resume before ecs_defer_end is called. + * The operation may only be called when deferring is enabled. + * + * @param world The world. + */ +FLECS_API +void ecs_defer_suspend( + ecs_world_t *world); + +/** Resume deferring. + * See ecs_defer_suspend. + * + * @param world The world. + */ +FLECS_API +void ecs_defer_resume( + ecs_world_t *world); + /** Enable/disable automerging for world or stage. * When automerging is enabled, staged data will automatically be merged with * the world when staging ends. This happens at the end of progress(), at a @@ -7281,7 +7404,7 @@ int32_t ecs_search_relation_last( * @return The type of the table. */ FLECS_API -ecs_type_t ecs_table_get_type( +const ecs_type_t* ecs_table_get_type( const ecs_table_t *table); /** Get storage type for table. @@ -7415,8 +7538,8 @@ bool ecs_commit( ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, - ecs_ids_t *added, - ecs_ids_t *removed); + const ecs_type_t *added, + const ecs_type_t *removed); /** Find record for entity. */ FLECS_API @@ -7607,11 +7730,13 @@ void* ecs_record_get_column( ecs_pair(ecs_id(relation), object),\ sizeof(relation), &(relation)__VA_ARGS__) -#define ecs_set_pair_object(world, subject, relation, object, ...)\ +#define ecs_set_pair_second(world, subject, relation, object, ...)\ ecs_set_id(world, subject,\ ecs_pair(relation, ecs_id(object)),\ sizeof(object), &(object)__VA_ARGS__) +#define ecs_set_pair_object ecs_set_pair_second + #define ecs_set_override(world, entity, T, ...)\ ecs_add_id(world, entity, ECS_OVERRIDE | ecs_id(T));\ ecs_set(world, entity, T, __VA_ARGS__) @@ -7621,12 +7746,8 @@ void* ecs_record_get_column( #define ecs_emplace(world, entity, T)\ (ECS_CAST(T*, ecs_emplace_id(world, entity, ecs_id(T)))) - /* -- Get -- */ -#define ecs_get_ref(world, ref, entity, T)\ - (ECS_CAST(const T*, ecs_get_ref_id(world, ref, entity, ecs_id(T)))) - #define ecs_get(world, entity, T)\ (ECS_CAST(const T*, ecs_get_id(world, entity, ecs_id(T)))) @@ -7634,10 +7755,19 @@ void* ecs_record_get_column( (ECS_CAST(relation*, ecs_get_id(world, subject,\ ecs_pair(ecs_id(relation), object)))) -#define ecs_get_pair_object(world, subject, relation, object)\ +#define ecs_get_pair_second(world, subject, relation, object)\ (ECS_CAST(object*, ecs_get_id(world, subject,\ ecs_pair(relation, ecs_id(object))))) +#define ecs_get_pair_object ecs_get_pair_second + +/* -- Ref -- */ + +#define ecs_ref_init(world, entity, T)\ + ecs_ref_init_id(world, entity, ecs_id(T)) + +#define ecs_ref_get(world, ref, T)\ + (ECS_CAST(const T*, ecs_ref_get_id(world, ref, ecs_id(T)))) /* -- Get mut & Modified -- */ @@ -7648,10 +7778,12 @@ void* ecs_record_get_column( (ECS_CAST(relation*, ecs_get_mut_id(world, subject,\ ecs_pair(ecs_id(relation), object), is_added))) -#define ecs_get_mut_pair_object(world, subject, relation, object, is_added)\ +#define ecs_get_mut_pair_second(world, subject, relation, object, is_added)\ (ECS_CAST(object*, ecs_get_mut_id(world, subject,\ ecs_pair(relation, ecs_id(object)), is_added))) +#define ecs_get_mut_pair_object ecs_get_mut_pair_second + #define ecs_modified(world, entity, component)\ ecs_modified_id(world, entity, ecs_id(component)) @@ -8312,7 +8444,7 @@ void _ecs_parser_errorv( #endif #endif // FLECS_NDEBUG -/** Throw +/** Panic * goto error when FLECS_SOFT_ASSERT is defined, otherwise abort */ #if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) #define ecs_throw(error_code, ...) ecs_dummy_check @@ -8399,6 +8531,7 @@ int ecs_log_last_error(void); #define ECS_OPERATION_FAILED (10) #define ECS_INVALID_CONVERSION (11) #define ECS_ID_IN_USE (12) +#define ECS_CYCLE_DETECTED (13) #define ECS_INCONSISTENT_NAME (20) #define ECS_NAME_IN_USE (21) @@ -11548,7 +11681,7 @@ typedef struct ecs_world_stats_t { int32_t t; } ecs_world_stats_t; -/* Statistics for a single query (use ecs_get_query_stats) */ +/* Statistics for a single query (use ecs_query_stats_get) */ typedef struct ecs_query_stats_t { ecs_gauge_t matched_table_count; /* Number of matched non-empty tables. This is the number of tables * iterated over when evaluating a query. */ @@ -11562,7 +11695,7 @@ typedef struct ecs_query_stats_t { int32_t t; } ecs_query_stats_t; -/** Statistics for a single system (use ecs_get_system_stats) */ +/** Statistics for a single system (use ecs_system_stats_get) */ typedef struct ecs_system_stats_t { ecs_query_stats_t query_stats; ecs_counter_t time_spent; /* Time spent processing a system */ @@ -11594,7 +11727,7 @@ typedef struct ecs_pipeline_stats_t { * @param stats Out parameter for statistics. */ FLECS_API -void ecs_get_world_stats( +void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *stats); @@ -11606,7 +11739,7 @@ void ecs_get_world_stats( * @param stats The statistics to print. */ FLECS_API -void ecs_dump_world_stats( +void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *stats); @@ -11618,7 +11751,7 @@ void ecs_dump_world_stats( * @param stats Out parameter for statistics. */ FLECS_API -void ecs_get_query_stats( +void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *stats); @@ -11633,7 +11766,7 @@ void ecs_get_query_stats( * @return true if success, false if not a system. */ FLECS_API -bool ecs_get_system_stats( +bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *stats); @@ -11649,7 +11782,7 @@ bool ecs_get_system_stats( * @return true if success, false if not a pipeline. */ FLECS_API -bool ecs_get_pipeline_stats( +bool ecs_pipeline_stats_get( ecs_world_t *world, ecs_entity_t pipeline, ecs_pipeline_stats_t *stats); @@ -12227,7 +12360,8 @@ ecs_entity_t ecs_cpp_component_register( const char *name, const char *symbol, ecs_size_t size, - ecs_size_t alignment); + ecs_size_t alignment, + bool implicit_name); FLECS_API ecs_entity_t ecs_cpp_component_register_explicit( @@ -12313,7 +12447,6 @@ namespace flecs { using world_t = ecs_world_t; using id_t = ecs_id_t; -using ids_t = ecs_ids_t; using entity_t = ecs_entity_t; using type_t = ecs_type_t; using table_t = ecs_table_t; @@ -12390,8 +12523,6 @@ static const flecs::entity_t UnSet = EcsUnSet; /** Builtin roles */ static const flecs::entity_t Pair = ECS_PAIR; -static const flecs::entity_t Switch = ECS_SWITCH; -static const flecs::entity_t Case = ECS_CASE; static const flecs::entity_t Override = ECS_OVERRIDE; /* Builtin entity ids */ @@ -12407,6 +12538,7 @@ static const flecs::entity_t Reflexive = EcsReflexive; static const flecs::entity_t Final = EcsFinal; static const flecs::entity_t DontInherit = EcsDontInherit; static const flecs::entity_t Tag = EcsTag; +static const flecs::entity_t Union = EcsUnion; static const flecs::entity_t Exclusive = EcsExclusive; static const flecs::entity_t Acyclic = EcsAcyclic; static const flecs::entity_t Symmetric = EcsSymmetric; @@ -12426,7 +12558,7 @@ static const flecs::entity_t OnDelete = EcsOnDelete; static const flecs::entity_t OnDeleteObject = EcsOnDeleteObject; static const flecs::entity_t Remove = EcsRemove; static const flecs::entity_t Delete = EcsDelete; -static const flecs::entity_t Throw = EcsThrow; +static const flecs::entity_t Panic = EcsPanic; } @@ -12525,6 +12657,9 @@ using std::is_volatile; using std::is_same; using std::is_enum; +// Determine constness even if T is a pointer type +template +using is_const_p = is_const< remove_pointer_t >; // Apply cv modifiers from source type to destination type // (from: https://stackoverflow.com/questions/52559336/add-const-to-type-if-template-arg-is-const) @@ -12816,6 +12951,7 @@ struct string_view : string { } #include +#include #define FLECS_ENUM_MAX(T) _::to_constant::value #define FLECS_ENUM_MAX_COUNT (FLECS_ENUM_MAX(int) + 1) @@ -12875,6 +13011,7 @@ constexpr size_t enum_type_len() { * __PRETTY_FUNCTION__ contains the enumeration name, whereas if a value is * invalid, the string contains a number. */ #if defined(__clang__) +#if __clang_major__ < 13 || (defined(__APPLE__) && __clang_minor__ < 1) template constexpr bool enum_constant_is_valid() { return !( @@ -12883,6 +13020,13 @@ constexpr bool enum_constant_is_valid() { (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + enum_type_len() + 6 /* ', C = ' */] <= '9')); } +#else +template +constexpr bool enum_constant_is_valid() { + return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 6 /* ', E C = ' */] != '('); +} +#endif #elif defined(__GNUC__) template constexpr bool enum_constant_is_valid() { @@ -13277,16 +13421,6 @@ struct id { return ecs_id_is_wildcard(m_id); } - /* Test if id has the Switch role */ - bool is_switch() const { - return (m_id & ECS_ROLE_MASK) == flecs::Switch; - } - - /* Test if id has the Case role */ - bool is_case() const { - return (m_id & ECS_ROLE_MASK) == flecs::Case; - } - /* Test if id is entity */ bool is_entity() const { return !(m_id & ECS_ROLE_MASK); @@ -13307,6 +13441,9 @@ struct id { /* Return id without role */ flecs::entity remove_generation() const; + /* Return component type of id */ + flecs::entity type_id() const; + /* Test if id has specified role */ bool has_role(flecs::id_t role) const { return ((m_id & ECS_ROLE_MASK) == role); @@ -13513,7 +13650,7 @@ struct event_builder_base { protected: flecs::world_t *m_world; ecs_event_desc_t m_desc; - flecs::ids_t m_ids; + flecs::type_t m_ids; flecs::id_t m_ids_array[ECS_EVENT_DESC_ID_COUNT_MAX]; private: @@ -14216,7 +14353,7 @@ struct pair : _::pair_base { } type& operator*() { - return &ref_; + return ref_; } const type& operator*() const { @@ -14270,7 +14407,7 @@ using actual_type_t = typename actual_type::type; // Get type without const, *, & template struct base_type { - using type = remove_pointer_t< decay_t< actual_type_t > >; + using type = decay_t< remove_pointer_t< actual_type_t > >; }; template @@ -15449,6 +15586,22 @@ struct world { ecs_defer_end(m_world); } + /** Suspend deferring operations. + * + * @see ecs_defer_suspend + */ + void defer_suspend() const { + ecs_defer_suspend(m_world); + } + + /** Resume deferring operations. + * + * @see ecs_defer_suspend + */ + void defer_resume() const { + ecs_defer_resume(m_world); + } + /** Check if entity id exists in the world. * Ignores entity relation. * @@ -15634,6 +15787,14 @@ template void each(flecs::id_t term_id, Func&& func) const; +/** Upcast entity to a trigger. + * The provided entity must be a trigger. + * + * @param e The entity. + * @return A trigger object. + */ +flecs::trigger trigger(flecs::entity e) const; + /** Create a new trigger. * * @tparam Components The components to match on. @@ -15644,6 +15805,14 @@ template flecs::trigger_builder trigger(Args &&... args) const; +/** Upcast entity to an observer. + * The provided entity must be an observer. + * + * @param e The entity. + * @return An observer object. + */ +flecs::observer observer(flecs::entity e) const; + /** Create a new observer. * * @tparam Components The components to match on. @@ -15692,7 +15861,7 @@ flecs::type type(const char *name = nullptr) const; * @return Module entity. */ template -flecs::entity module() const; +flecs::entity module(const char *name = nullptr) const; /** Import a module. * @@ -16403,8 +16572,7 @@ struct iter { #ifndef FLECS_NDEBUG ecs_entity_t term_id = ecs_term_id(m_iter, index); - ecs_assert(term_id & ECS_PAIR || term_id & ECS_SWITCH || - term_id & ECS_CASE || + ecs_assert(term_id & ECS_PAIR || term_id == _::cpp_type::id(m_iter->world), ECS_COLUMN_TYPE_MISMATCH, NULL); #endif @@ -16462,12 +16630,10 @@ template struct ref { ref() : m_world( nullptr ) - , m_entity( 0 ) , m_ref() { } ref(world_t *world, entity_t entity) : m_world( world ) - , m_entity( entity ) , m_ref() { auto comp_id = _::cpp_type::id(world); @@ -16475,33 +16641,27 @@ struct ref { ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - ecs_get_ref_id( - m_world, &m_ref, m_entity, comp_id); + m_ref = ecs_ref_init_id(m_world, entity, comp_id); } - const T* operator->() { - const T* result = static_cast(ecs_get_ref_id( - m_world, &m_ref, m_entity, _::cpp_type::id(m_world))); + T* operator->() { + T* result = static_cast(ecs_ref_get_id( + m_world, &m_ref, _::cpp_type::id(m_world))); ecs_assert(result != NULL, ECS_INVALID_PARAMETER, NULL); return result; } - const T* get() { - if (m_entity) { - ecs_get_ref_id( - m_world, &m_ref, m_entity, _::cpp_type::id(m_world)); - } - - return static_cast(m_ref.ptr); + T* get() { + return static_cast(ecs_ref_get_id( + m_world, &m_ref, _::cpp_type::id(m_world))); } flecs::entity entity() const; private: world_t *m_world; - entity_t m_entity; flecs::ref_t m_ref; }; @@ -17010,55 +17170,6 @@ struct entity_view : public id { return owns(_::cpp_type::id(m_world)); } - /** Check if entity has the provided switch. - * - * @param sw The switch to check. - * @return True if the entity has the provided switch, false otherwise. - */ - bool has_switch(const flecs::type& sw) const; - - template - bool has_switch() const { - return ecs_has_id(m_world, m_id, - flecs::Switch | _::cpp_type::id(m_world)); - } - - /** Check if entity has the provided case. - * - * @param sw_case The case to check. - * @return True if the entity has the provided case, false otherwise. - */ - bool has_case(flecs::id_t sw_case) const { - return ecs_has_id(m_world, m_id, flecs::Case | sw_case); - } - - template - bool has_case() const { - return this->has_case(_::cpp_type::id(m_world)); - } - - /** Get case for switch. - * - * @param sw The switch for which to obtain the case. - * @return The entity representing the case. - */ - flecs::entity get_case(flecs::id_t sw) const; - - /** Get case for switch. - * - * @tparam T The switch type for which to obtain the case. - * @return The entity representing the case. - */ - template - flecs::entity get_case() const; - - /** Get case for switch. - * - * @param sw The switch for which to obtain the case. - * @return The entity representing the case. - */ - flecs::entity get_case(const flecs::type& sw) const; - /** Test if component is enabled. * * @tparam T The component to test. @@ -17492,104 +17603,20 @@ struct entity_builder : entity_view { return to_base(); } - /** Add a switch to an entity by id. - * The switch entity must be a type, that is it must have the EcsType - * component. Entities created with flecs::type are valid here. - * - * @param sw The switch entity id to add. - */ - Self& add_switch(entity_t sw) { - ecs_add_id(this->m_world, this->m_id, ECS_SWITCH | sw); - return to_base(); - } - - /** Add a switch to an entity by C++ type. - * The C++ type must be associated with a switch type. - * - * @tparam T The switch to add. - */ - template - Self& add_switch() { - ecs_add_id(this->m_world, this->m_id, - ECS_SWITCH | _::cpp_type::id()); - return to_base(); - } - - /** Add a switch to an entity. - * Any instance of flecs::type can be used as a switch. - * - * @param sw The switch to add. - */ - Self& add_switch(const flecs::type& sw); - - /** Remove a switch from an entity by id. + /** Emplace value, add owned flag. * - * @param sw The switch to remove. - */ - Self& remove_switch(entity_t sw) { - ecs_remove_id(this->m_world, this->m_id, ECS_SWITCH | sw); - return to_base(); - } - - /** Add a switch to an entity by C++ type. - * The C++ type must be associated with a switch type. - * - * @tparam T The switch to remove. - */ - template - Self& remove_switch() { - ecs_remove_id(this->m_world, this->m_id, - ECS_SWITCH | _::cpp_type::id()); - return to_base(); - } - - /** Remove a switch from an entity. - * Any instance of flecs::type can be used as a switch. - * - * @param sw The switch to remove. - */ - Self& remove_switch(const flecs::type& sw); - - /** Add a switch to an entity by id. - * The case must belong to a switch that is already added to the entity. - * - * @param sw_case The case entity id to add. + * @tparam T The component to set and for which to add the OVERRIDE flag */ - Self& add_case(entity_t sw_case) { - ecs_add_id(this->m_world, this->m_id, ECS_CASE | sw_case); - return to_base(); - } + template + Self& emplace_override(Args&&... args) { + this->override(); - /** Add a switch to an entity by id. - * The case must belong to a switch that is already added to the entity. - * - * @tparam T The case to add. - */ - template - Self& add_case() { - return this->add_case(_::cpp_type::id()); - } + flecs::emplace(this->m_world, this->m_id, + FLECS_FWD(args)...); - /** Remove a case from an entity by id. - * The case must belong to a switch that is already added to the entity. - * - * @param sw_case The case entity id to remove. - */ - Self& remove_case(entity_t sw_case) { - ecs_remove_id(this->m_world, this->m_id, ECS_CASE | sw_case); return to_base(); } - /** Remove a switch from an entity by id. - * The case must belong to a switch that is already added to the entity. - * - * @tparam T The case to remove. - */ - template - Self& remove_case() { - return this->remove_case(_::cpp_type::id()); - } - /** Add pair for enum constant. * This operation will add a pair to the entity where R is the enumeration * type, and O is the entity representing the enum constant. @@ -18499,10 +18526,15 @@ struct each_invoker : public invoker { ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) { flecs::iter it(iter); + + ECS_TABLE_LOCK(iter->world, iter->table); + for (auto row : it) { func( (ColumnType< remove_reference_t >(comps, row) .get_row())...); } + + ECS_TABLE_UNLOCK(iter->world, iter->table); } template class ColumnType, @@ -18564,7 +18596,12 @@ struct iter_invoker : invoker { size_t, Terms&, Args...) { flecs::iter it(iter); + + ECS_TABLE_LOCK(iter->world, iter->table); + func(it); + + ECS_TABLE_UNLOCK(iter->world, iter->table); } template > { return false; } + ECS_TABLE_LOCK(world, table); + invoke_callback(func, 0, ptrs); + ECS_TABLE_UNLOCK(world, table); + return true; } @@ -18698,6 +18739,7 @@ struct entity_with_invoker_impl> { flecs::world w(world); PtrArray ptrs; + ecs_table_t *table = NULL; // When not deferred take the fast path. if (!w.is_deferred()) { @@ -18706,7 +18748,6 @@ struct entity_with_invoker_impl> { // Find table for entity ecs_record_t *r = ecs_record_find(world, id); - ecs_table_t *table = NULL; if (r) { table = r->table; } @@ -18726,7 +18767,7 @@ struct entity_with_invoker_impl> { // If table is different, move entity straight to it if (table != next) { - ecs_ids_t ids; + ecs_type_t ids; ids.array = added.ptr(); ids.count = static_cast(elem); ecs_commit(world, id, r, next, &ids, NULL); @@ -18737,6 +18778,8 @@ struct entity_with_invoker_impl> { ecs_abort(ECS_INTERNAL_ERROR, NULL); } + ECS_TABLE_LOCK(world, table); + // When deferred, obtain pointers with regular get_mut } else { get_mut_ptrs(world, id, ptrs); @@ -18744,6 +18787,10 @@ struct entity_with_invoker_impl> { invoke_callback(func, 0, ptrs); + if (!w.is_deferred()) { + ECS_TABLE_UNLOCK(world, table); + } + // Call modified on each component DummyArray dummy_after ({ ( ecs_modified_id(world, id, w.id()), 0)... @@ -19507,13 +19554,12 @@ struct component : untyped_component { const char *last_elem = strrchr(n, ':'); if (last_elem) { name = last_elem + 1; - implicit_name = false; } } /* Find or register component */ id = ecs_cpp_component_register(world, id, n, _::symbol_name(), - ECS_SIZEOF(T), ECS_ALIGNOF(T)); + ECS_SIZEOF(T), ECS_ALIGNOF(T), implicit_name); /* Initialize static component data */ id = _::cpp_type::id_explicit(world, name, allow_tag, id); @@ -19601,13 +19647,18 @@ struct type_base { : m_table( t ) { } Base& add(id_t id) { + flecs::world_t *world = this->world(); + if (!m_table) { - for (auto type_id : this->vector()) { - m_table = ecs_table_add_id(world(), m_table, type_id); + const ecs_type_t *type = ecs_table_get_type(m_table); + if (type) { + for (int i = 0; i < type->count; i ++) { + m_table = ecs_table_add_id(world, m_table, type->array[i]); + } } } - m_table = ecs_table_add_id(world(), m_table, id); + m_table = ecs_table_add_id(world, m_table, id); sync_from_me(); return *this; } @@ -19669,7 +19720,7 @@ struct type_base { return flecs::string(str); } - type_t c_ptr() const { + const type_t* c_ptr() const { return ecs_table_get_type(m_table); } @@ -19693,22 +19744,26 @@ struct type_base { ecs_enable(world(), id(), false); } - flecs::vector vector() { - return flecs::vector( const_cast( - ecs_table_get_type(m_table))); - } - flecs::id get(int32_t index) { + const ecs_type_t *type = ecs_table_get_type(m_table); + if (!type || index >= type->count) { + ecs_abort(ECS_OUT_OF_RANGE, 0); + return flecs::id(world(), 0); + } const flecs::world_t *w = ecs_get_world(world()); - return flecs::id(const_cast(w), vector().get(index)); + return flecs::id(const_cast(w), type->array[index]); } size_t count() { - return vector().count(); + const ecs_type_t *type = ecs_table_get_type(m_table); + if (!type) { + return 0; + } + return static_cast(type->count); } /* Implicit conversion to type_t */ - operator type_t() const { return ecs_table_get_type(m_table); } + operator const type_t*() const { return ecs_table_get_type(m_table); } operator Base&() { return *static_cast(this); } @@ -19813,6 +19868,10 @@ inline flecs::world id::world() const { return flecs::world(m_world); } +inline flecs::entity id::type_id() const { + return flecs::entity(m_world, ecs_get_typeid(m_world, m_id)); +} + // Id mixin implementation @@ -19858,17 +19917,7 @@ namespace flecs { template flecs::entity ref::entity() const { - return flecs::entity(m_world, m_entity); -} - -template -inline Self& entity_builder::add_switch(const flecs::type& sw) { - return add_switch(sw.id()); -} - -template -inline Self& entity_builder::remove_switch(const flecs::type& sw) { - return remove_switch(sw.id()); + return flecs::entity(m_world, m_ref.entity); } template @@ -19879,23 +19928,6 @@ inline Self& entity_builder::set(const Func& func) { return to_base(); } -inline bool entity_view::has_switch(const flecs::type& type) const { - return ecs_has_id(m_world, m_id, flecs::Switch | type.id()); -} - -inline flecs::entity entity_view::get_case(const flecs::type& sw) const { - return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw.id())); -} - -inline flecs::entity entity_view::get_case(flecs::id_t sw) const { - return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw)); -} - -template -inline flecs::entity entity_view::get_case() const { - return get_case(_::cpp_type::id(m_world)); -} - template ::value > > const T* entity_view::get() const { entity_t r = _::cpp_type::id(m_world); @@ -19974,25 +20006,23 @@ inline flecs::type entity_view::type() const { template inline void entity_view::each(const Func& func) const { - const ecs_vector_t *type = ecs_get_type(m_world, m_id); + const ecs_type_t *type = ecs_get_type(m_world, m_id); if (!type) { return; } - const ecs_id_t *ids = static_cast( - _ecs_vector_first(type, ECS_VECTOR_T(ecs_id_t))); - int32_t count = ecs_vector_count(type); + const ecs_id_t *ids = type->array; + int32_t count = type->count; for (int i = 0; i < count; i ++) { ecs_id_t id = ids[i]; flecs::id ent(m_world, id); func(ent); - // Case is not stored in type, so handle separately - if ((id & ECS_ROLE_MASK) == flecs::Switch) { - ent = flecs::id( - m_world, flecs::Case | ecs_get_case( - m_world, m_id, ent.second().id())); + // Union object is not stored in type, so handle separately + if (ECS_PAIR_FIRST(id) == EcsUnion) { + ent = flecs::id(m_world, ECS_PAIR_SECOND(id), + ecs_get_object(m_world, m_id, ECS_PAIR_SECOND(id), 0)); func(ent); } } @@ -20008,7 +20038,7 @@ inline void entity_view::each(flecs::id_t pred, flecs::id_t obj, const Func& fun return; } - const ecs_vector_t *type = ecs_table_get_type(table); + const ecs_type_t *type = ecs_table_get_type(table); if (!type) { return; } @@ -20019,8 +20049,7 @@ inline void entity_view::each(flecs::id_t pred, flecs::id_t obj, const Func& fun } int32_t cur = 0; - id_t *ids = static_cast( - _ecs_vector_first(type, ECS_VECTOR_T(ecs_id_t))); + id_t *ids = type->array; while (-1 != (cur = ecs_search_offset(real_world, table, cur, pattern, 0))) { @@ -20136,7 +20165,7 @@ inline flecs::type world::type(const char *name) const { namespace flecs { namespace _ { - template ::value > = 0> + template ::value > = 0> static constexpr flecs::inout_kind_t type_to_inout() { return flecs::In; } @@ -20147,7 +20176,7 @@ namespace _ { } template ::value || is_reference::value > = 0> + is_const_p::value || is_reference::value > = 0> static constexpr flecs::inout_kind_t type_to_inout() { return flecs::InOutDefault; } @@ -21470,7 +21499,7 @@ struct query_base { char *result = ecs_filter_str(m_world, f); return flecs::string(result); } - + operator query<>() const; protected: @@ -21480,6 +21509,11 @@ struct query_base { template struct query final : query_base, iterable { +public: + flecs::world world() const { + return flecs::world(m_world); + } + private: using Terms = typename _::term_ptrs::array; @@ -21695,6 +21729,8 @@ struct trigger final : entity { using entity::entity; + explicit trigger() : entity() { } + trigger(flecs::world_t *world, ecs_trigger_desc_t *desc) : entity(world, ecs_trigger_init(world, desc)) { @@ -21714,6 +21750,9 @@ struct trigger final : entity }; // Mixin implementation +inline trigger world::trigger(flecs::entity e) const { + return flecs::trigger(m_world, e); +} template inline trigger_builder world::trigger(Args &&... args) const { @@ -21822,6 +21861,8 @@ struct observer final : entity { using entity::entity; + explicit observer() : entity() { } + observer(flecs::world_t *world, ecs_observer_desc_t *desc) : entity(world, ecs_observer_init(world, desc)) { @@ -21843,6 +21884,9 @@ struct observer final : entity }; // Mixin implementation +inline observer world::observer(flecs::entity e) const { + return flecs::observer(m_world, e); +} template inline observer_builder world::observer(Args &&... args) const { @@ -21910,8 +21954,11 @@ flecs::entity import(world& world) { } template -inline flecs::entity world::module() const { +inline flecs::entity world::module(const char *name) const { flecs::id_t result = _::cpp_type::id(m_world); + if (name) { + ecs_add_path_w_sep(m_world, result, 0, name, "::", "::"); + } ecs_set_scope(m_world, result); return flecs::entity(m_world, result); } diff --git a/etc/css/components/icon-button.css b/etc/css/components/icon-button.css index 10d6d368..3a358f2e 100644 --- a/etc/css/components/icon-button.css +++ b/etc/css/components/icon-button.css @@ -1,4 +1,4 @@ -.icon-button { +div.icon-button { display: flex; flex-direction: row; align-items: center; @@ -8,19 +8,23 @@ z-index: 1; } -.icon-button { +div.icon-button { background-color: rgba(255, 255, 255, 0); opacity: 0.6; - transition: all 0.2s ease; + transition: background-color 0.2s ease; } -.icon-button:hover { +div.icon-button:hover { background-color: rgba(255, 255, 255, 0.1); opacity: 0.9; } -.icon-button:active, -.icon-button:focus { +div.icon-button:active, +div.icon-button:focus { background-color: rgba(255, 255, 255, 0.2); opacity: 1.0; -} \ No newline at end of file +} + +div.icon-button-active { + opacity: 1.0; +} diff --git a/etc/css/components/icon.css b/etc/css/components/icon.css index 130ca554..af5b061d 100644 --- a/etc/css/components/icon.css +++ b/etc/css/components/icon.css @@ -1,4 +1,4 @@ -.icon-obj { +svg.codicon-icon { stroke: currentColor; stroke-width: 2; fill: none; @@ -11,4 +11,19 @@ -webkit-font-smoothing: antialiased; -webkit-transform-style: preserve-3d; -webkit-transform: translateZ(0); -} \ No newline at end of file +} + +svg.feather-icon { + stroke: currentColor; + stroke-width: 2; + fill: none; + overflow: visible; + z-index: 1; + + /* Preempts SVG jitter bug in Safari */ + -webkit-backface-visibility: hidden; + -webkit-perspective: 1000; + -webkit-font-smoothing: antialiased; + -webkit-transform-style: preserve-3d; + -webkit-transform: translateZ(0); +} diff --git a/etc/css/fonts.css b/etc/css/fonts.css new file mode 100644 index 00000000..b010c75f --- /dev/null +++ b/etc/css/fonts.css @@ -0,0 +1,27 @@ +@font-face { + font-family: 'Inter'; + font-weight: 100 900; + font-display: swap; + font-style: normal; + font-named-instance: 'Regular'; + src: url("../fonts/Inter-roman.var.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-weight: 100 900; + font-display: swap; + font-style: italic; + font-named-instance: 'Italic'; + src: url("../fonts/Inter-italic.var.woff2?v=3.19") format("woff2"); +} + +@font-face { + font-family: 'Roboto Mono'; + font-weight: 400; + font-display: swap; + font-style: normal; + src: url("../fonts/RobotoMono-Regular.ttf") format("truetype"); +} + + diff --git a/etc/css/split-panes.css b/etc/css/split-panes.css index 8805bbbe..93035079 100644 --- a/etc/css/split-panes.css +++ b/etc/css/split-panes.css @@ -1,16 +1,9 @@ -* { - padding: 0; - margin: 0; -} - -body { -} #app { width: 100%; } -.split-pane-container { +div.split-pane-container { width: 100%; height: calc(100vh - 41px); @@ -18,21 +11,29 @@ body { flex-direction: row; } -.split-pane { +div.split-pane { display: flex; flex-direction: column; height: 100%; } -.handle { +div.split-pane-hidden { + display: none; +} + +div.handle { height: 100%; width: 1px; } -.handle-grab-box { +div.handle-last { + margin-left: auto; +} + +div.handle-grab-box { border-radius: 3px; position: relative; - z-index: 100; + z-index: 5; left: -1px; height: 100%; width: 3px; @@ -41,7 +42,7 @@ body { transition: opacity 0.2s ease-in-out; } -.handle-grab-box:hover { +div.handle-grab-box:hover { cursor: col-resize; opacity: 0.7; } diff --git a/etc/css/style.css b/etc/css/style.css index e08aaea9..15e51f56 100644 --- a/etc/css/style.css +++ b/etc/css/style.css @@ -1,3 +1,4 @@ +@import "fonts.css"; @import "components/icon.css"; @import "components/icon-button.css"; @import "components/entity-hierarchy.css"; @@ -72,6 +73,7 @@ /* Color assignments */ --app-bg: var(--grey-950); + --app-bg-maximized: var(--steel-900); --panel-border: var(--steel-850); --panel-header-bg: var(--steel-700); --panel-header-hover-bg: var(--steel-675); @@ -90,8 +92,13 @@ --error: var(--bright-red); --light-hover-bg: var(--steel-700); - --color-acent-emphasis: var(--ui-blue); - --color-acent-emphasis-translucent: var(--ui-blue-30); + --color-accent-emphasis: var(--ui-blue); + --color-accent-emphasis-translucent: var(--ui-blue-30); + + /* Border colors */ + + --border-color-primary: var(--grey-700); + /* Text sizes */ --text-fs-xs: 10px; @@ -131,10 +138,19 @@ body { color: var(--primary-text); margin: 0px; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +a { + color: inherit; + text-decoration: inherit; } input { - -webkit-appearance: none; + appearance: none; + -webkit-appearance: none; -moz-appearance: none; outline: none; padding: 5px; @@ -144,6 +160,94 @@ input { font-family: inherit; } +input[type="text"] { + border: 1px solid var(--border-color-primary); + border-radius: var(--br-2); + background-color: transparent; + outline: 0px solid transparent; + transition: all 0.2s ease-in-out; +} + +input[type="text"]:disabled { + color: var(--secondary-text); +} + +input[type="text"]:hover { + border-color: var(--color-accent-emphasis); +} + +input[type="text"]:focus { + border-color: var(--color-accent-emphasis); + outline: 3px solid var(--color-accent-emphasis-translucent); +} + +input[type="radio"] { + background-color: transparent; + border: 1px solid var(--border-color-primary); + border-radius: 50%; + width: 12px; + height: 12px; + aspect-ratio: 1; +} + +input[type="radio"]:checked { + /* background-color: var(--color-accent-emphasis); */ +} + +input[type="radio"]:checked::before { + border-radius: 50%; + content: ''; + display: block; + background-color: white; + width: 8px; + height: 8px; + transform: translate(-50%, -50%); +} + +.input-group { + width: 100%; + display: flex; + align-items: center; + gap: 8px; +} + +.input-field { + display: flex; + flex-direction: column; + gap: 2px; + width: 100%; +} + +.input-field-row { + display: flex; + flex-direction: row; + gap: 4px; + width: 100%; +} + +.input-field-row * { + flex: 1 1 auto; + width: auto; + max-width: auto; +} + +.input-field label { + display: inline-block; + font-size: var(--text-fs-xs); + line-height: var(--text-lh-xs); + color: var(--tertiary-text); + text-transform: uppercase; + letter-spacing: 1px; + margin-left: var(--p-3); +} + +.control-row { + /* Right aligned */ + display: flex; + flex-direction: row; + justify-content: flex-end; +} + textarea { -webkit-appearance: none; -moz-appearance: none; @@ -265,26 +369,26 @@ div.close { /* High level layout */ div.top-box { - width: 100%; - height: 36px; + /* height: 40px; */ background-color: var(--panel-header-bg); border-color: var(--panel-border); border-style: solid; border-width: 0px; border-bottom-width: 1px; - padding-bottom: 4px; -} -div.top-content { display: flex; - width: 100%; - height: 100%; - border-color: var(--panel-border); - border-style: solid; - border-width: 0px; - border-bottom-width: 1px; + flex-direction: row; align-items: center; - justify-content: center; + justify-content: space-between; + + padding: var(--p-3) var(--p-4); + /* padding-left: var(--p-3); */ +} + +.bar-group { + display: flex; + flex-direction: row; + gap: 8px; } div.tree-box { @@ -350,7 +454,6 @@ div.terminal-box { } div.app-title { - font-weight: 500; font-size: var(--text-fs-lg); } @@ -380,28 +483,18 @@ span.app-title-detail-hide { } div.play-button-box { - position: absolute; - left: 10px; - top: 8px; - width: 26px; - height: 26px; + width: 20px; + height: 20px; cursor: pointer; } div.share-button-box { - position: absolute; - right: 12px; - top: 10px; cursor: pointer; } div.play-button-box img { - height: 22px; -} - -div.share-button-box img { - height: 25px; + height: 100%; } div.entity-box { @@ -1032,6 +1125,7 @@ div.ecs-filter { div.content-container { position: relative; + } div.content-container-wrapper { @@ -1058,7 +1152,7 @@ div.content-container-wrapper-overflow div.content-container div.detail-toggle { div.content-container > div.detail-toggle > div.detail-toggle-summary { background-color: var(--panel-header-bg); - transition: all 0.2s ease-in-out; + transition: opacity 0.2s ease-in-out; } div.content-container > div.detail-toggle > div.detail-toggle-summary:hover { @@ -1191,7 +1285,7 @@ table.query-results-table thead.query-results-table-header { position: sticky; top: 0px; z-index: 2; - box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.15); + box-shadow: 0px 4px 7px rgba(0, 0, 0, 0.1); } table.query-results-table thead tr, @@ -1294,3 +1388,17 @@ div.detail-toggle-divider { border-top-style: solid; border-top-width: 1px; } + +div.maximized { + position: absolute; + top: 41px; + left: 0px; + width: 100%; + height: calc(100% - 45px); + z-index: 10; + background-color: var(--app-bg-maximized); +} + +div.disable { + display: none; +} diff --git a/etc/flecs_explorer.wasm b/etc/flecs_explorer.wasm index 72049cc4..46fe88b3 100755 Binary files a/etc/flecs_explorer.wasm and b/etc/flecs_explorer.wasm differ diff --git a/etc/fonts/Inter-italic.var.woff2 b/etc/fonts/Inter-italic.var.woff2 new file mode 100644 index 00000000..b826d5af Binary files /dev/null and b/etc/fonts/Inter-italic.var.woff2 differ diff --git a/etc/fonts/Inter-roman.var.woff2 b/etc/fonts/Inter-roman.var.woff2 new file mode 100644 index 00000000..6a256a06 Binary files /dev/null and b/etc/fonts/Inter-roman.var.woff2 differ diff --git a/etc/fonts/RobotoMono-Regular.ttf b/etc/fonts/RobotoMono-Regular.ttf new file mode 100644 index 00000000..7c4ce36a Binary files /dev/null and b/etc/fonts/RobotoMono-Regular.ttf differ diff --git a/etc/index.html b/etc/index.html index 96b0622c..dcec46f1 100644 --- a/etc/index.html +++ b/etc/index.html @@ -5,81 +5,102 @@ - - - -
- - - -
-
- - +
+
+ +
+
+
+
+ + + +
- + + + + + + + + + + + - + + v-on:select_query="evt_select_query" + v-on:panel-update="evt_panel_update"/> -
- -
+ + + + + +
+ +
- - - - - - - + + + +
@@ -87,8 +108,9 @@ - diff --git a/etc/js/app.js b/etc/js/app.js index 01df4d4a..8f38652c 100644 --- a/etc/js/app.js +++ b/etc/js/app.js @@ -1,5 +1,4 @@ - // Track state of connection to remote app const ConnectionState = { Initializing: Symbol('Initializing'), @@ -7,7 +6,8 @@ const ConnectionState = { Connecting: Symbol('Connecting'), RetryConnecting: Symbol('RetryConnecting'), Remote: Symbol('Remote'), - ConnectionFailed: Symbol('ConnectionFailed') + ConnectionFailed: Symbol('ConnectionFailed'), + Disconnecting: Symbol('Disconnecting'), }; // Short initial timeout to detect remote app. Should be long enough for @@ -67,17 +67,36 @@ function getParameterByName(name, url = window.location.href) { return decodeURIComponent(results[2].replace(/\+/g, ' ')); } +function paramStr(params) { + let url_params = ""; + if (params) { + for (var k in params) { + url_params += "&" + k + "=" + params[k]; + } + } + return url_params; +} + /* - GLOBAL COMPONENT REGISTRATIOnS + GLOBAL COMPONENT REGISTRATIONS */ Vue.component('collapsible-panel', httpVueLoader('js/collapsible_panel.vue')); Vue.component('detail-toggle-alt', httpVueLoader('js/detail_toggle_alt.vue')); -// var icon_component = Vue.component('icon', httpVueLoader('js/components/icon.vue')); -// Vue.component('icon-button', httpVueLoader('js/components/button.vue')); var tooltip_component = Vue.component('tooltip', httpVueLoader('js/components/tooltip.vue')); var popover_component = Vue.component('popover', httpVueLoader('js/components/popover.vue')); Vue.component('url-popover', httpVueLoader('js/overlays/popovers/url-popover.vue')); -// var entity_hierarchy_component = Vue.component('entity-hierarchy', httpVueLoader('js/components/entity_hierarchy.vue')); +Vue.component('connection-popover', httpVueLoader('js/overlays/popovers/connection-popover.vue')); +Vue.component('connection-status', httpVueLoader('js/widgets/connection_status.vue')); +Vue.component('tabs', httpVueLoader('js/components/tabs.vue')); +Vue.component('primary-button', httpVueLoader('js/components/button.vue')); + +Vue.component('panel-menu', httpVueLoader('js/components/panel_menu.vue')); +Vue.component('panel-button', httpVueLoader('js/components/panel_button.vue')); +Vue.component('stat', httpVueLoader('js/components/stat.vue')); +Vue.component('stats-period', httpVueLoader('js/components/stats_period.vue')); +Vue.component('stat-chart', httpVueLoader('js/components/stat_chart.vue')); +Vue.component('stats-world', httpVueLoader('js/components/stats_world.vue')); +Vue.component('stats-pipeline', httpVueLoader('js/components/stats_pipeline.vue')); Vue.directive('tooltip', { bind: function (el, binding, vnode) { @@ -104,6 +123,15 @@ var app = new Vue({ el: '#app', mounted: function() { + + // Initialize title before watcher sets + document.title = this.title; + + /* + Call Sequence: + Mounted -> Ready -> Connect -> + */ + this.$nextTick(() => { flecs_explorer.then(() => { this.ready(); @@ -137,6 +165,11 @@ var app = new Vue({ if (Request.status == 0) { this.retry_count ++; + // Disconnect after the 10th try + if (this.retry_count >= 10) { + this.disconnect(); + } + // Retry if the server did not respond to request if (retry_interval) { retry_interval *= 1.3; @@ -153,9 +186,12 @@ var app = new Vue({ "ensure app is running and REST is enabled " + "(retried " + this.retry_count + " times)"); + // Attempt reconnection loop window.setTimeout(() => { - this.http_request(method, host, path, recv, err, - timeout, retry_interval); + if (this.connection != ConnectionState.Disconnecting && this.connection != ConnectionState.Local) { + this.http_request(method, host, path, recv, err, + timeout, retry_interval); + } }, retry_interval); } else { if (err) err(Request.responseText); @@ -238,14 +274,8 @@ var app = new Vue({ const reply = JSON.parse(r); recv(reply); } else if (this.is_remote()) { - let url_params = ""; - if (params) { - for (var k in params) { - url_params += "&" + k + "=" + params[k]; - } - } this.request(id, "GET", - "entity/" + path.replaceAll('.', '/') + url_params, recv, err); + "entity/" + path.replaceAll('.', '/') + paramStr(params), recv, err); } }, @@ -255,20 +285,23 @@ var app = new Vue({ const reply = JSON.parse(r); recv(reply); } else if (this.is_remote()) { - let url_params = ""; - if (params) { - for (var k in params) { - url_params += "&" + k + "=" + params[k]; - } - } this.request(id, - "GET", "query?q=" + encodeURIComponent(q) + url_params, + "GET", "query?q=" + encodeURIComponent(q) + paramStr(params), recv, err); } else { err({error: "no connection"}); } }, + request_stats: function(id, category, recv, err, params) { + if (this.is_local()) { + return "{}"; + } else if (this.is_remote()) { + this.request(id, "GET", + "stats/" + category + paramStr(params), recv, err); + } + }, + insert_code: function(code, recv, timeout) { if (this.is_local()) { if (this.parse_timer) { @@ -300,8 +333,9 @@ var app = new Vue({ } if (selected) { - this.selected_entity = selected; + this.set_entity(selected); } + if (q) { this.$refs.query.set_query(q); } @@ -318,17 +352,21 @@ var app = new Vue({ this.refresh_query(); this.refresh_entity(); this.refresh_tree(); + this.refresh_stats(); // Refresh UI periodically this.refresh_timer = window.setInterval(() => { this.refresh_query(); this.refresh_entity(); this.refresh_tree(); + this.refresh_stats(); }, REFRESH_INTERVAL); + + this.evt_panel_update(); }, ready_local() { - this.selected_entity = undefined; + this.set_entity(); const q_encoded = getParameterByName("q"); const p_encoded = getParameterByName("p"); @@ -367,6 +405,40 @@ var app = new Vue({ this.$refs.tree.update_expanded(); this.parse_interval = 150; + + this.evt_panel_update(); + }, + + disconnect() { + this.connection = ConnectionState.Disconnecting; + + // Reset application connection status + this.retry_count = 0; + + if (this.refresh_timer) { + window.clearInterval(this.refresh_timer); + } + + // Clear URL params + const url = new URL(window.location); + url.searchParams.delete("host"); + url.searchParams.delete("remote"); + url.searchParams.delete("port"); + window.history.replaceState({}, '', url); + + // Clear stored params + this.params.host = undefined; + this.params.remote = undefined; + this.params.port = undefined; + + // Reset + this.title = "Flecs"; + + + setTimeout(() => { + this.connection = ConnectionState.Local; + this.ready_local(); + }, 1) }, // Connect to a remote host @@ -499,16 +571,8 @@ var app = new Vue({ // Set inspector to entity by pathname set_entity(path) { - this.request_abort('inspector'); // Abort outstanding requests - this.entity_result = undefined; - - this.selected_entity = path; - if (!path) { - return; - } - - this.$refs.inspector.expand(); - this.refresh_entity(); + this.$refs.inspector.set_entity(path); + this.$refs.tree.set_selected_entity(path); }, set_entity_by_tree_item(item) { @@ -524,23 +588,20 @@ var app = new Vue({ }, refresh_entity() { - if (!this.selected_entity) { - return; - } - this.request_entity('inspector', this.selected_entity, (reply) => { - this.entity_error = reply.error; - if (this.entity_error === undefined) { - this.entity_result = reply; - } - }, () => { - this.entity_error = "request for entity '" + this.selected_entity + "' failed"; - }, {type_info: true, label: true, brief: true, link: true, id_labels: true, values: true}); + this.$refs.inspector.refresh(); }, refresh_tree() { this.$refs.tree.update_expanded(); }, + refresh_stats() { + if (this.$refs.stats_world) { + this.$refs.stats_world.refresh(); + this.$refs.stats_pipeline.refresh(); + } + }, + // Code changed event run_code(code, recv) { this.insert_code(code, (reply) => { @@ -565,16 +626,29 @@ var app = new Vue({ this.$refs.query.set_query(query); }, + evt_panel_update() { + this.$nextTick(() => { + if (this.$refs.panes) { + this.$refs.panes.resize(); + } + if (this.$refs.panel_menu) { + this.$refs.panel_menu.refresh(); + } + }); + }, + show_url_modal() { const query = this.$refs.query.get_query(); let plecs; let plecs_encoded; - if (this.$refs.plecs) { + if (!this.remote_mode) { plecs = this.$refs.plecs.get_code(); plecs_encoded = wq_encode(plecs); } + let entity = this.$refs.inspector.get_entity(); + const query_encoded = wq_encode(query); let sep = "?"; @@ -617,15 +691,19 @@ var app = new Vue({ sep = "&"; } - if (this.selected_entity) { - this.url += sep + "s=" + this.selected_entity; + if (entity) { + this.url += sep + "s=" + entity; sep = "&"; } - // this.$refs.url.show(); this.$refs.share_url_popover.show(); }, + + show_connection_modal() { + this.$refs.connection_popover.show(); + } }, + computed: { valid: function() { @@ -639,16 +717,21 @@ var app = new Vue({ (this.connection == ConnectionState.RetryConnecting) || this.params.remote || this.params.remote_self || this.params.host; } + + }, + + watch: { + title(new_title) { + // Watches for title data change, then updates page title + document.title = new_title; + }, }, data: { title: "Flecs", query_error: undefined, - entity_error: undefined, code_error: undefined, query_result: undefined, - entity_result: undefined, - selected_entity: undefined, selected_tree_item: undefined, url: undefined, params: {}, diff --git a/etc/js/app_title.js b/etc/js/app_title.js index b38c5d9d..aad41ee3 100644 --- a/etc/js/app_title.js +++ b/etc/js/app_title.js @@ -34,7 +34,9 @@ Vue.component('app-title', { this.connection == ConnectionState.Local || this.connection == ConnectionState.RetryConnecting) { - return this.value; + let str = this.value.replaceAll("_", " "); + str = str.charAt(0).toUpperCase() + str.slice(1); + return str; } else if (this.connection == ConnectionState.Connecting || this.connection == ConnectionState.Initializing) { diff --git a/etc/js/components/button.vue b/etc/js/components/button.vue index 6d59a9b4..8d6e1e00 100644 --- a/etc/js/components/button.vue +++ b/etc/js/components/button.vue @@ -1,53 +1,53 @@ -