You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
68068 lines
1.9 MiB
68068 lines
1.9 MiB
/** |
|
* @file bootstrap.c |
|
* @brief Bootstrap entities in the flecs.core namespace. |
|
* |
|
* Before the ECS storage can be used, core entities such first need to be |
|
* initialized. For example, components in Flecs are stored as entities in the |
|
* ECS storage itself with an EcsComponent component, but before this component |
|
* can be stored, the component itself needs to be initialized. |
|
* |
|
* The bootstrap code uses lower-level APIs to initialize the data structures. |
|
* After bootstrap is completed, regular ECS operations can be used to create |
|
* entities and components. |
|
* |
|
* The bootstrap file also includes several lifecycle hooks and observers for |
|
* builtin features, such as relationship properties and hooks for keeping the |
|
* entity name administration in sync with the (Identifier, Name) component. |
|
*/ |
|
|
|
#include "flecs.h" |
|
/** |
|
* @file private_api.h |
|
* @brief Private functions. |
|
*/ |
|
|
|
#ifndef FLECS_PRIVATE_H |
|
#define FLECS_PRIVATE_H |
|
|
|
/** |
|
* @file private_types.h |
|
* @brief Private types. |
|
*/ |
|
|
|
#ifndef FLECS_PRIVATE_TYPES_H |
|
#define FLECS_PRIVATE_TYPES_H |
|
|
|
#ifndef __MACH__ |
|
#ifndef _POSIX_C_SOURCE |
|
#define _POSIX_C_SOURCE 200809L |
|
#endif |
|
#endif |
|
|
|
#include <stdlib.h> |
|
#include <limits.h> |
|
#include <stdio.h> |
|
|
|
/** |
|
* @file datastructures/entity_index.h |
|
* @brief Entity index data structure. |
|
* |
|
* The entity index stores the table, row for an entity id. |
|
*/ |
|
|
|
#ifndef FLECS_ENTITY_INDEX_H |
|
#define FLECS_ENTITY_INDEX_H |
|
|
|
#define FLECS_ENTITY_PAGE_SIZE (1 << FLECS_ENTITY_PAGE_BITS) |
|
#define FLECS_ENTITY_PAGE_MASK (FLECS_ENTITY_PAGE_SIZE - 1) |
|
|
|
typedef struct ecs_entity_index_page_t { |
|
ecs_record_t records[FLECS_ENTITY_PAGE_SIZE]; |
|
} ecs_entity_index_page_t; |
|
|
|
typedef struct ecs_entity_index_t { |
|
ecs_vec_t dense; |
|
ecs_vec_t pages; |
|
int32_t alive_count; |
|
uint64_t max_id; |
|
ecs_block_allocator_t page_allocator; |
|
ecs_allocator_t *allocator; |
|
} ecs_entity_index_t; |
|
|
|
/** Initialize entity index. */ |
|
void flecs_entity_index_init( |
|
ecs_allocator_t *allocator, |
|
ecs_entity_index_t *index); |
|
|
|
/** Deinitialize entity index. */ |
|
void flecs_entity_index_fini( |
|
ecs_entity_index_t *index); |
|
|
|
/* Get entity (must exist/must be alive) */ |
|
ecs_record_t* flecs_entity_index_get( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Get entity (must exist/may not be alive) */ |
|
ecs_record_t* flecs_entity_index_get_any( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Get entity (may not exist/must be alive) */ |
|
ecs_record_t* flecs_entity_index_try_get( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Get entity (may not exist/may not be alive) */ |
|
ecs_record_t* flecs_entity_index_try_get_any( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/** Ensure entity exists. */ |
|
ecs_record_t* flecs_entity_index_ensure( |
|
ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Remove entity */ |
|
void flecs_entity_index_remove( |
|
ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Set generation of entity */ |
|
void flecs_entity_index_set_generation( |
|
ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Get current generation of entity */ |
|
uint64_t flecs_entity_index_get_generation( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Return whether entity is alive */ |
|
bool flecs_entity_index_is_alive( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Return whether entity is valid */ |
|
bool flecs_entity_index_is_valid( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Return whether entity exists */ |
|
bool flecs_entity_index_exists( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity); |
|
|
|
/* Create or recycle entity id */ |
|
uint64_t flecs_entity_index_new_id( |
|
ecs_entity_index_t *index); |
|
|
|
/* Bulk create or recycle new entity ids */ |
|
uint64_t* flecs_entity_index_new_ids( |
|
ecs_entity_index_t *index, |
|
int32_t count); |
|
|
|
/* Set size of index */ |
|
void flecs_entity_index_set_size( |
|
ecs_entity_index_t *index, |
|
int32_t size); |
|
|
|
/* Return number of entities in index */ |
|
int32_t flecs_entity_index_count( |
|
const ecs_entity_index_t *index); |
|
|
|
/* Return number of allocated entities in index */ |
|
int32_t flecs_entity_index_size( |
|
const ecs_entity_index_t *index); |
|
|
|
/* Return number of not alive entities in index */ |
|
int32_t flecs_entity_index_not_alive_count( |
|
const ecs_entity_index_t *index); |
|
|
|
/* Clear entity index */ |
|
void flecs_entity_index_clear( |
|
ecs_entity_index_t *index); |
|
|
|
/* Return number of alive entities in index */ |
|
const uint64_t* flecs_entity_index_ids( |
|
const ecs_entity_index_t *index); |
|
|
|
void flecs_entity_index_copy( |
|
ecs_entity_index_t *dst, |
|
const ecs_entity_index_t *src); |
|
|
|
void flecs_entity_index_restore( |
|
ecs_entity_index_t *dst, |
|
const ecs_entity_index_t *src); |
|
|
|
#define ecs_eis(world) (&((world)->store.entity_index)) |
|
#define flecs_entities_init(world) flecs_entity_index_init(&world->allocator, ecs_eis(world)) |
|
#define flecs_entities_fini(world) flecs_entity_index_fini(ecs_eis(world)) |
|
#define flecs_entities_get(world, entity) flecs_entity_index_get(ecs_eis(world), entity) |
|
#define flecs_entities_try(world, entity) flecs_entity_index_try_get(ecs_eis(world), entity) |
|
#define flecs_entities_get_any(world, entity) flecs_entity_index_get_any(ecs_eis(world), entity) |
|
#define flecs_entities_ensure(world, entity) flecs_entity_index_ensure(ecs_eis(world), entity) |
|
#define flecs_entities_remove(world, entity) flecs_entity_index_remove(ecs_eis(world), entity) |
|
#define flecs_entities_set_generation(world, entity) flecs_entity_index_set_generation(ecs_eis(world), entity) |
|
#define flecs_entities_get_generation(world, entity) flecs_entity_index_get_generation(ecs_eis(world), entity) |
|
#define flecs_entities_is_alive(world, entity) flecs_entity_index_is_alive(ecs_eis(world), entity) |
|
#define flecs_entities_is_valid(world, entity) flecs_entity_index_is_valid(ecs_eis(world), entity) |
|
#define flecs_entities_exists(world, entity) flecs_entity_index_exists(ecs_eis(world), entity) |
|
#define flecs_entities_new_id(world) flecs_entity_index_new_id(ecs_eis(world)) |
|
#define flecs_entities_new_ids(world, count) flecs_entity_index_new_ids(ecs_eis(world), count) |
|
#define flecs_entities_max_id(world) (ecs_eis(world)->max_id) |
|
#define flecs_entities_set_size(world, size) flecs_entity_index_set_size(ecs_eis(world), size) |
|
#define flecs_entities_count(world) flecs_entity_index_count(ecs_eis(world)) |
|
#define flecs_entities_size(world) flecs_entity_index_size(ecs_eis(world)) |
|
#define flecs_entities_not_alive_count(world) flecs_entity_index_not_alive_count(ecs_eis(world)) |
|
#define flecs_entities_clear(world) flecs_entity_index_clear(ecs_eis(world)) |
|
#define flecs_entities_ids(world) flecs_entity_index_ids(ecs_eis(world)) |
|
#define flecs_entities_copy(dst, src) flecs_entity_index_copy(dst, src) |
|
#define flecs_entities_restore(dst, src) flecs_entity_index_restore(dst, src) |
|
|
|
#endif |
|
|
|
/** |
|
* @file datastructures/stack_allocator.h |
|
* @brief Stack allocator. |
|
*/ |
|
|
|
#ifndef FLECS_STACK_ALLOCATOR_H |
|
#define FLECS_STACK_ALLOCATOR_H |
|
|
|
/** Stack allocator for quick allocation of small temporary values */ |
|
#define ECS_STACK_PAGE_SIZE (4096) |
|
|
|
typedef struct ecs_stack_page_t { |
|
void *data; |
|
struct ecs_stack_page_t *next; |
|
int16_t sp; |
|
uint32_t id; |
|
} ecs_stack_page_t; |
|
|
|
typedef struct ecs_stack_t { |
|
ecs_stack_page_t first; |
|
ecs_stack_page_t *tail_page; |
|
ecs_stack_cursor_t *tail_cursor; |
|
#ifdef FLECS_DEBUG |
|
int32_t cursor_count; |
|
#endif |
|
} ecs_stack_t; |
|
|
|
FLECS_DBG_API |
|
void flecs_stack_init( |
|
ecs_stack_t *stack); |
|
|
|
FLECS_DBG_API |
|
void flecs_stack_fini( |
|
ecs_stack_t *stack); |
|
|
|
FLECS_DBG_API |
|
void* flecs_stack_alloc( |
|
ecs_stack_t *stack, |
|
ecs_size_t size, |
|
ecs_size_t align); |
|
|
|
#define flecs_stack_alloc_t(stack, T)\ |
|
flecs_stack_alloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) |
|
|
|
#define flecs_stack_alloc_n(stack, T, count)\ |
|
flecs_stack_alloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) |
|
|
|
FLECS_DBG_API |
|
void* flecs_stack_calloc( |
|
ecs_stack_t *stack, |
|
ecs_size_t size, |
|
ecs_size_t align); |
|
|
|
#define flecs_stack_calloc_t(stack, T)\ |
|
flecs_stack_calloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) |
|
|
|
#define flecs_stack_calloc_n(stack, T, count)\ |
|
flecs_stack_calloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) |
|
|
|
FLECS_DBG_API |
|
void flecs_stack_free( |
|
void *ptr, |
|
ecs_size_t size); |
|
|
|
#define flecs_stack_free_t(ptr, T)\ |
|
flecs_stack_free(ptr, ECS_SIZEOF(T)) |
|
|
|
#define flecs_stack_free_n(ptr, T, count)\ |
|
flecs_stack_free(ptr, ECS_SIZEOF(T) * count) |
|
|
|
void flecs_stack_reset( |
|
ecs_stack_t *stack); |
|
|
|
FLECS_DBG_API |
|
ecs_stack_cursor_t* flecs_stack_get_cursor( |
|
ecs_stack_t *stack); |
|
|
|
FLECS_DBG_API |
|
void flecs_stack_restore_cursor( |
|
ecs_stack_t *stack, |
|
ecs_stack_cursor_t *cursor); |
|
|
|
#endif |
|
|
|
/** |
|
* @file bitset.h |
|
* @brief Bitset data structure. |
|
*/ |
|
|
|
#ifndef FLECS_BITSET_H |
|
#define FLECS_BITSET_H |
|
|
|
|
|
#ifdef __cplusplus |
|
extern "C" { |
|
#endif |
|
|
|
typedef struct ecs_bitset_t { |
|
uint64_t *data; |
|
int32_t count; |
|
ecs_size_t size; |
|
} ecs_bitset_t; |
|
|
|
/** Initialize bitset. */ |
|
FLECS_DBG_API |
|
void flecs_bitset_init( |
|
ecs_bitset_t *bs); |
|
|
|
/** Deinitialize bitset. */ |
|
FLECS_DBG_API |
|
void flecs_bitset_fini( |
|
ecs_bitset_t *bs); |
|
|
|
/** Add n elements to bitset. */ |
|
FLECS_DBG_API |
|
void flecs_bitset_addn( |
|
ecs_bitset_t *bs, |
|
int32_t count); |
|
|
|
/** Ensure element exists. */ |
|
FLECS_DBG_API |
|
void flecs_bitset_ensure( |
|
ecs_bitset_t *bs, |
|
int32_t count); |
|
|
|
/** Set element. */ |
|
FLECS_DBG_API |
|
void flecs_bitset_set( |
|
ecs_bitset_t *bs, |
|
int32_t elem, |
|
bool value); |
|
|
|
/** Get element. */ |
|
FLECS_DBG_API |
|
bool flecs_bitset_get( |
|
const ecs_bitset_t *bs, |
|
int32_t elem); |
|
|
|
/** Return number of elements. */ |
|
FLECS_DBG_API |
|
int32_t flecs_bitset_count( |
|
const ecs_bitset_t *bs); |
|
|
|
/** Remove from bitset. */ |
|
FLECS_DBG_API |
|
void flecs_bitset_remove( |
|
ecs_bitset_t *bs, |
|
int32_t elem); |
|
|
|
/** Swap values in bitset. */ |
|
FLECS_DBG_API |
|
void flecs_bitset_swap( |
|
ecs_bitset_t *bs, |
|
int32_t elem_a, |
|
int32_t elem_b); |
|
|
|
#ifdef __cplusplus |
|
} |
|
#endif |
|
|
|
#endif |
|
|
|
/** |
|
* @file switch_list.h |
|
* @brief Interleaved linked list for storing mutually exclusive values. |
|
*/ |
|
|
|
#ifndef FLECS_SWITCH_LIST_H |
|
#define FLECS_SWITCH_LIST_H |
|
|
|
|
|
typedef struct ecs_switch_header_t { |
|
int32_t element; /* First element for value */ |
|
int32_t count; /* Number of elements for value */ |
|
} ecs_switch_header_t; |
|
|
|
typedef struct ecs_switch_node_t { |
|
int32_t next; /* Next node in list */ |
|
int32_t prev; /* Prev node in list */ |
|
} ecs_switch_node_t; |
|
|
|
struct ecs_switch_t { |
|
ecs_map_t hdrs; /* map<uint64_t, ecs_switch_header_t> */ |
|
ecs_vec_t nodes; /* vec<ecs_switch_node_t> */ |
|
ecs_vec_t values; /* vec<uint64_t> */ |
|
}; |
|
|
|
/** Init new switch. */ |
|
FLECS_DBG_API |
|
void flecs_switch_init( |
|
ecs_switch_t* sw, |
|
ecs_allocator_t *allocator, |
|
int32_t elements); |
|
|
|
/** Fini switch. */ |
|
FLECS_DBG_API |
|
void flecs_switch_fini( |
|
ecs_switch_t *sw); |
|
|
|
/** Remove all values. */ |
|
FLECS_DBG_API |
|
void flecs_switch_clear( |
|
ecs_switch_t *sw); |
|
|
|
/** Add element to switch, initialize value to 0 */ |
|
FLECS_DBG_API |
|
void flecs_switch_add( |
|
ecs_switch_t *sw); |
|
|
|
/** Set number of elements in switch list */ |
|
FLECS_DBG_API |
|
void flecs_switch_set_count( |
|
ecs_switch_t *sw, |
|
int32_t count); |
|
|
|
/** Get number of elements */ |
|
FLECS_DBG_API |
|
int32_t flecs_switch_count( |
|
ecs_switch_t *sw); |
|
|
|
/** Ensure that element exists. */ |
|
FLECS_DBG_API |
|
void flecs_switch_ensure( |
|
ecs_switch_t *sw, |
|
int32_t count); |
|
|
|
/** Add n elements. */ |
|
FLECS_DBG_API |
|
void flecs_switch_addn( |
|
ecs_switch_t *sw, |
|
int32_t count); |
|
|
|
/** Set value of element. */ |
|
FLECS_DBG_API |
|
void flecs_switch_set( |
|
ecs_switch_t *sw, |
|
int32_t element, |
|
uint64_t value); |
|
|
|
/** Remove element. */ |
|
FLECS_DBG_API |
|
void flecs_switch_remove( |
|
ecs_switch_t *sw, |
|
int32_t element); |
|
|
|
/** Get value for element. */ |
|
FLECS_DBG_API |
|
uint64_t flecs_switch_get( |
|
const ecs_switch_t *sw, |
|
int32_t element); |
|
|
|
/** Swap element. */ |
|
FLECS_DBG_API |
|
void flecs_switch_swap( |
|
ecs_switch_t *sw, |
|
int32_t elem_1, |
|
int32_t elem_2); |
|
|
|
/** Get vector with all values. Use together with count(). */ |
|
FLECS_DBG_API |
|
ecs_vec_t* flecs_switch_values( |
|
const ecs_switch_t *sw); |
|
|
|
/** Return number of different values. */ |
|
FLECS_DBG_API |
|
int32_t flecs_switch_case_count( |
|
const ecs_switch_t *sw, |
|
uint64_t value); |
|
|
|
/** Return first element for value. */ |
|
FLECS_DBG_API |
|
int32_t flecs_switch_first( |
|
const ecs_switch_t *sw, |
|
uint64_t value); |
|
|
|
/** Return next element for value. Use with first(). */ |
|
FLECS_DBG_API |
|
int32_t flecs_switch_next( |
|
const ecs_switch_t *sw, |
|
int32_t elem); |
|
|
|
#ifdef __cplusplus |
|
extern "C" { |
|
#endif |
|
|
|
#ifdef __cplusplus |
|
} |
|
#endif |
|
|
|
#endif |
|
|
|
/** |
|
* @file table.h |
|
* @brief Table storage implementation. |
|
*/ |
|
|
|
#ifndef FLECS_TABLE_H |
|
#define FLECS_TABLE_H |
|
|
|
/** |
|
* @file table_graph.h |
|
* @brief Table graph types and functions. |
|
*/ |
|
|
|
#ifndef FLECS_TABLE_GRAPH_H |
|
#define FLECS_TABLE_GRAPH_H |
|
|
|
/** Cache of added/removed components for non-trivial edges between tables */ |
|
#define ECS_TABLE_DIFF_INIT { .added = {0}} |
|
|
|
/** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to |
|
* conserve memory on table edges (a type doesn't have the size field), whereas |
|
* a vec for the builder is more convenient to use & has allocator support. */ |
|
typedef struct ecs_table_diff_builder_t { |
|
ecs_vec_t added; |
|
ecs_vec_t removed; |
|
} ecs_table_diff_builder_t; |
|
|
|
typedef struct ecs_table_diff_t { |
|
ecs_type_t added; /* Components added between tables */ |
|
ecs_type_t removed; /* Components removed between tables */ |
|
} ecs_table_diff_t; |
|
|
|
/** Edge linked list (used to keep track of incoming edges) */ |
|
typedef struct ecs_graph_edge_hdr_t { |
|
struct ecs_graph_edge_hdr_t *prev; |
|
struct ecs_graph_edge_hdr_t *next; |
|
} ecs_graph_edge_hdr_t; |
|
|
|
/** Single edge. */ |
|
typedef struct ecs_graph_edge_t { |
|
ecs_graph_edge_hdr_t hdr; |
|
ecs_table_t *from; /* Edge source table */ |
|
ecs_table_t *to; /* Edge destination table */ |
|
ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */ |
|
ecs_id_t id; /* Id associated with edge */ |
|
} ecs_graph_edge_t; |
|
|
|
/* Edges to other tables. */ |
|
typedef struct ecs_graph_edges_t { |
|
ecs_graph_edge_t *lo; /* Small array optimized for low edges */ |
|
ecs_map_t *hi; /* Map for hi edges (map<id, edge_t>) */ |
|
} ecs_graph_edges_t; |
|
|
|
/* Table graph node */ |
|
typedef struct ecs_graph_node_t { |
|
/* Outgoing edges */ |
|
ecs_graph_edges_t add; |
|
ecs_graph_edges_t remove; |
|
|
|
/* Incoming edges (next = add edges, prev = remove edges) */ |
|
ecs_graph_edge_hdr_t refs; |
|
} ecs_graph_node_t; |
|
|
|
/* Find table by adding id to current table */ |
|
ecs_table_t *flecs_table_traverse_add( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t *id_ptr, |
|
ecs_table_diff_t *diff); |
|
|
|
/* Find table by removing id from current table */ |
|
ecs_table_t *flecs_table_traverse_remove( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t *id_ptr, |
|
ecs_table_diff_t *diff); |
|
|
|
/* Cleanup incoming and outgoing edges for table */ |
|
void flecs_table_clear_edges( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Table diff builder, used to build id lists that indicate the difference in |
|
* ids between two tables. */ |
|
void flecs_table_diff_builder_init( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *builder); |
|
|
|
void flecs_table_diff_builder_fini( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *builder); |
|
|
|
void flecs_table_diff_builder_clear( |
|
ecs_table_diff_builder_t *builder); |
|
|
|
void flecs_table_diff_build_append_table( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *dst, |
|
ecs_table_diff_t *src); |
|
|
|
void flecs_table_diff_build( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *builder, |
|
ecs_table_diff_t *diff, |
|
int32_t added_offset, |
|
int32_t removed_offset); |
|
|
|
void flecs_table_diff_build_noalloc( |
|
ecs_table_diff_builder_t *builder, |
|
ecs_table_diff_t *diff); |
|
|
|
#endif |
|
|
|
|
|
/* Table event type for notifying tables of world events */ |
|
typedef enum ecs_table_eventkind_t { |
|
EcsTableTriggersForId, |
|
EcsTableNoTriggersForId, |
|
} ecs_table_eventkind_t; |
|
|
|
typedef struct ecs_table_event_t { |
|
ecs_table_eventkind_t kind; |
|
|
|
/* Query event */ |
|
ecs_query_t *query; |
|
|
|
/* Component info event */ |
|
ecs_entity_t component; |
|
|
|
/* Event match */ |
|
ecs_entity_t event; |
|
|
|
/* If the number of fields gets out of hand, this can be turned into a union |
|
* but since events are very temporary objects, this works for now and makes |
|
* initializing an event a bit simpler. */ |
|
} ecs_table_event_t; |
|
|
|
/** Infrequently accessed data not stored inline in ecs_table_t */ |
|
typedef struct ecs_table__t { |
|
uint64_t hash; /* Type hash */ |
|
int32_t lock; /* Prevents modifications */ |
|
int32_t traversable_count; /* Traversable relationship targets in table */ |
|
uint16_t generation; /* Used for table cleanup */ |
|
int16_t record_count; /* Table record count including wildcards */ |
|
|
|
struct ecs_table_record_t *records; /* Array with table records */ |
|
ecs_hashmap_t *name_index; /* Cached pointer to name index */ |
|
|
|
ecs_switch_t *sw_columns; /* Switch columns */ |
|
ecs_bitset_t *bs_columns; /* Bitset columns */ |
|
int16_t sw_count; |
|
int16_t sw_offset; |
|
int16_t bs_count; |
|
int16_t bs_offset; |
|
int16_t ft_offset; |
|
} ecs_table__t; |
|
|
|
/** Table column */ |
|
typedef struct ecs_column_t { |
|
ecs_vec_t data; /* Vector with component data */ |
|
ecs_id_t id; /* Component id */ |
|
ecs_type_info_t *ti; /* Component type info */ |
|
ecs_size_t size; /* Component size */ |
|
} ecs_column_t; |
|
|
|
/** Table data */ |
|
struct ecs_data_t { |
|
ecs_vec_t entities; /* Entity ids */ |
|
ecs_column_t *columns; /* Component data */ |
|
}; |
|
|
|
/** A table is the Flecs equivalent of an archetype. Tables store all entities |
|
* with a specific set of components. Tables are automatically created when an |
|
* entity has a set of components not previously observed before. When a new |
|
* table is created, it is automatically matched with existing queries */ |
|
struct ecs_table_t { |
|
uint64_t id; /* Table id in sparse set */ |
|
ecs_flags32_t flags; /* Flags for testing table properties */ |
|
int16_t column_count; /* Number of components (excluding tags) */ |
|
ecs_type_t type; /* Vector with component ids */ |
|
|
|
ecs_data_t data; /* Component storage */ |
|
ecs_graph_node_t node; /* Graph node */ |
|
|
|
int32_t *dirty_state; /* Keep track of changes in columns */ |
|
int32_t *column_map; /* Map type index <-> column |
|
* - 0..count(T): type index -> column |
|
* - count(T)..count(C): column -> type index |
|
*/ |
|
|
|
ecs_table__t *_; /* Infrequently accessed table metadata */ |
|
}; |
|
|
|
/* Init table */ |
|
void flecs_table_init( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_table_t *from); |
|
|
|
/** Copy type. */ |
|
ecs_type_t flecs_type_copy( |
|
ecs_world_t *world, |
|
const ecs_type_t *src); |
|
|
|
/** Free type. */ |
|
void flecs_type_free( |
|
ecs_world_t *world, |
|
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, |
|
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. */ |
|
void flecs_table_clear_entities( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Reset a table to its initial state */ |
|
void flecs_table_reset( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Clear all entities from the table. Do not invoke OnRemove systems */ |
|
void flecs_table_clear_entities_silent( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Clear table data. Don't call OnRemove handlers. */ |
|
void flecs_table_clear_data( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data); |
|
|
|
/* Return number of entities in data */ |
|
int32_t flecs_table_data_count( |
|
const ecs_data_t *data); |
|
|
|
/* Add a new entry to the table for the specified entity */ |
|
int32_t flecs_table_append( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_entity_t entity, |
|
bool construct, |
|
bool on_add); |
|
|
|
/* Delete an entity from the table. */ |
|
void flecs_table_delete( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t index, |
|
bool destruct); |
|
|
|
/* Make sure table records are in correct table cache list */ |
|
bool flecs_table_records_update_empty( |
|
ecs_table_t *table); |
|
|
|
/* Move a row from one table to another */ |
|
void flecs_table_move( |
|
ecs_world_t *world, |
|
ecs_entity_t dst_entity, |
|
ecs_entity_t src_entity, |
|
ecs_table_t *new_table, |
|
int32_t new_index, |
|
ecs_table_t *old_table, |
|
int32_t old_index, |
|
bool construct); |
|
|
|
/* Grow table with specified number of records. Populate table with entities, |
|
* starting from specified entity id. */ |
|
int32_t flecs_table_appendn( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data, |
|
int32_t count, |
|
const ecs_entity_t *ids); |
|
|
|
/* Set table to a fixed size. Useful for preallocating memory in advance. */ |
|
void flecs_table_set_size( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data, |
|
int32_t count); |
|
|
|
/* Shrink table to contents */ |
|
bool flecs_table_shrink( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Get dirty state for table columns */ |
|
int32_t* flecs_table_get_dirty_state( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Initialize root table */ |
|
void flecs_init_root_table( |
|
ecs_world_t *world); |
|
|
|
/* Unset components in table */ |
|
void flecs_table_remove_actions( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Free table */ |
|
void flecs_table_free( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Free table */ |
|
void flecs_table_free_type( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
/* Replace data */ |
|
void flecs_table_replace_data( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data); |
|
|
|
/* Merge data of one table into another table */ |
|
void flecs_table_merge( |
|
ecs_world_t *world, |
|
ecs_table_t *new_table, |
|
ecs_table_t *old_table, |
|
ecs_data_t *new_data, |
|
ecs_data_t *old_data); |
|
|
|
void flecs_table_swap( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t row_1, |
|
int32_t row_2); |
|
|
|
void flecs_table_mark_dirty( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_entity_t component); |
|
|
|
void flecs_table_notify( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_table_event_t *event); |
|
|
|
void flecs_table_delete_entities( |
|
ecs_world_t *world, |
|
ecs_table_t *table); |
|
|
|
int32_t flecs_table_column_to_union_index( |
|
const ecs_table_t *table, |
|
int32_t column); |
|
|
|
/* Increase observer count of table */ |
|
void flecs_table_traversable_add( |
|
ecs_table_t *table, |
|
int32_t value); |
|
|
|
ecs_vec_t* flecs_table_entities( |
|
ecs_table_t *table); |
|
|
|
ecs_entity_t* flecs_table_entities_array( |
|
ecs_table_t *table); |
|
|
|
#endif |
|
|
|
|
|
/* Used in id records to keep track of entities used with id flags */ |
|
extern const ecs_entity_t EcsFlag; |
|
|
|
#define ECS_MAX_JOBS_PER_WORKER (16) |
|
#define ECS_MAX_DEFER_STACK (8) |
|
|
|
/* Magic number for a flecs object */ |
|
#define ECS_OBJECT_MAGIC (0x6563736f) |
|
|
|
/* Tags associated with poly for (Poly, tag) components */ |
|
#define ecs_world_t_tag invalid |
|
#define ecs_stage_t_tag invalid |
|
#define ecs_query_t_tag EcsQuery |
|
#define ecs_rule_t_tag EcsQuery |
|
#define ecs_table_t_tag invalid |
|
#define ecs_filter_t_tag EcsQuery |
|
#define ecs_observer_t_tag EcsObserver |
|
|
|
/* Mixin kinds */ |
|
typedef enum ecs_mixin_kind_t { |
|
EcsMixinWorld, |
|
EcsMixinEntity, |
|
EcsMixinObservable, |
|
EcsMixinIterable, |
|
EcsMixinDtor, |
|
EcsMixinMax |
|
} ecs_mixin_kind_t; |
|
|
|
/* The mixin array contains pointers to mixin members for different kinds of |
|
* flecs objects. This allows the API to retrieve data from an object regardless |
|
* of its type. Each mixin array is only stored once per type */ |
|
struct ecs_mixins_t { |
|
const char *type_name; /* Include name of mixin type so debug code doesn't |
|
* need to know about every object */ |
|
ecs_size_t elems[EcsMixinMax]; |
|
}; |
|
|
|
/* Mixin tables */ |
|
extern ecs_mixins_t ecs_world_t_mixins; |
|
extern ecs_mixins_t ecs_stage_t_mixins; |
|
extern ecs_mixins_t ecs_filter_t_mixins; |
|
extern ecs_mixins_t ecs_query_t_mixins; |
|
extern ecs_mixins_t ecs_trigger_t_mixins; |
|
extern ecs_mixins_t ecs_observer_t_mixins; |
|
|
|
/* Types that have no mixins */ |
|
#define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) |
|
|
|
/* Scope for flecs internals, like observers used for builtin features */ |
|
extern const ecs_entity_t EcsFlecsInternals; |
|
|
|
/** Type used for internal string hashmap */ |
|
typedef struct ecs_hashed_string_t { |
|
char *value; |
|
ecs_size_t length; |
|
uint64_t hash; |
|
} ecs_hashed_string_t; |
|
|
|
/** Must appear as first member in payload of table cache */ |
|
typedef struct ecs_table_cache_hdr_t { |
|
struct ecs_table_cache_t *cache; |
|
ecs_table_t *table; |
|
struct ecs_table_cache_hdr_t *prev, *next; |
|
bool empty; |
|
} ecs_table_cache_hdr_t; |
|
|
|
/** Linked list of tables in table cache */ |
|
typedef struct ecs_table_cache_list_t { |
|
ecs_table_cache_hdr_t *first; |
|
ecs_table_cache_hdr_t *last; |
|
int32_t count; |
|
} ecs_table_cache_list_t; |
|
|
|
/** Table cache */ |
|
typedef struct ecs_table_cache_t { |
|
ecs_map_t index; /* <table_id, T*> */ |
|
ecs_table_cache_list_t tables; |
|
ecs_table_cache_list_t empty_tables; |
|
} ecs_table_cache_t; |
|
|
|
/* Sparse query term */ |
|
typedef struct flecs_switch_term_t { |
|
ecs_switch_t *sw_column; |
|
ecs_entity_t sw_case; |
|
int32_t signature_column_index; |
|
} flecs_switch_term_t; |
|
|
|
/* Bitset query term */ |
|
typedef struct flecs_bitset_term_t { |
|
ecs_bitset_t *bs_column; |
|
int32_t column_index; |
|
} flecs_bitset_term_t; |
|
|
|
typedef struct flecs_flat_monitor_t { |
|
int32_t table_state; |
|
int32_t monitor; |
|
} flecs_flat_monitor_t; |
|
|
|
/* Flat table term */ |
|
typedef struct flecs_flat_table_term_t { |
|
int32_t field_index; /* Iterator field index */ |
|
ecs_term_t *term; |
|
ecs_vec_t monitor; |
|
} flecs_flat_table_term_t; |
|
|
|
/* Entity filter. This filters the entities of a matched table, for example when |
|
* it has disabled components or union relationships (switch). */ |
|
typedef struct ecs_entity_filter_t { |
|
ecs_vec_t sw_terms; /* Terms with switch (union) entity filter */ |
|
ecs_vec_t bs_terms; /* Terms with bitset (toggle) entity filter */ |
|
ecs_vec_t ft_terms; /* Terms with components from flattened tree */ |
|
int32_t flat_tree_column; |
|
} ecs_entity_filter_t; |
|
|
|
typedef struct ecs_entity_filter_iter_t { |
|
ecs_entity_filter_t *entity_filter; |
|
ecs_iter_t *it; |
|
int32_t *columns; |
|
ecs_table_t *prev; |
|
ecs_table_range_t range; |
|
int32_t bs_offset; |
|
int32_t sw_offset; |
|
int32_t sw_smallest; |
|
int32_t flat_tree_offset; |
|
int32_t target_count; |
|
} ecs_entity_filter_iter_t; |
|
|
|
/** Table match data. |
|
* Each table matched by the query is represented by a ecs_query_table_match_t |
|
* instance, which are linked together in a list. A table may match a query |
|
* multiple times (due to wildcard queries) with different columns being matched |
|
* by the query. */ |
|
struct ecs_query_table_match_t { |
|
ecs_query_table_match_t *next, *prev; |
|
ecs_table_t *table; /* The current table. */ |
|
int32_t offset; /* Starting point in table */ |
|
int32_t count; /* Number of entities to iterate in table */ |
|
int32_t *columns; /* Mapping from query fields to table columns */ |
|
int32_t *storage_columns; /* Mapping from query fields to storage columns */ |
|
ecs_id_t *ids; /* Resolved (component) ids for current table */ |
|
ecs_entity_t *sources; /* Subjects (sources) of ids */ |
|
ecs_vec_t refs; /* Cached components for non-this terms */ |
|
uint64_t group_id; /* Value used to organize tables in groups */ |
|
int32_t *monitor; /* Used to monitor table for changes */ |
|
ecs_entity_filter_t *entity_filter; /* Entity specific filters */ |
|
|
|
/* Next match in cache for same table (includes empty tables) */ |
|
ecs_query_table_match_t *next_match; |
|
}; |
|
|
|
/** Table record type for query table cache. A query only has one per table. */ |
|
typedef struct ecs_query_table_t { |
|
ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ |
|
ecs_query_table_match_t *first; /* List with matches for table */ |
|
ecs_query_table_match_t *last; /* Last discovered match for table */ |
|
uint64_t table_id; |
|
int32_t rematch_count; /* Track whether table was rematched */ |
|
} ecs_query_table_t; |
|
|
|
/** Points to the beginning & ending of a query group */ |
|
typedef struct ecs_query_table_list_t { |
|
ecs_query_table_match_t *first; |
|
ecs_query_table_match_t *last; |
|
ecs_query_group_info_t info; |
|
} ecs_query_table_list_t; |
|
|
|
/* Query event type for notifying queries of world events */ |
|
typedef enum ecs_query_eventkind_t { |
|
EcsQueryTableMatch, |
|
EcsQueryTableRematch, |
|
EcsQueryTableUnmatch, |
|
EcsQueryOrphan |
|
} ecs_query_eventkind_t; |
|
|
|
typedef struct ecs_query_event_t { |
|
ecs_query_eventkind_t kind; |
|
ecs_table_t *table; |
|
ecs_query_t *parent_query; |
|
} ecs_query_event_t; |
|
|
|
/* Query level block allocators have sizes that depend on query field count */ |
|
typedef struct ecs_query_allocators_t { |
|
ecs_block_allocator_t columns; |
|
ecs_block_allocator_t ids; |
|
ecs_block_allocator_t sources; |
|
ecs_block_allocator_t monitors; |
|
} ecs_query_allocators_t; |
|
|
|
/** Query that is automatically matched against tables */ |
|
struct ecs_query_t { |
|
ecs_header_t hdr; |
|
|
|
/* Query filter */ |
|
ecs_filter_t filter; |
|
|
|
/* Tables matched with query */ |
|
ecs_table_cache_t cache; |
|
|
|
/* Linked list with all matched non-empty tables, in iteration order */ |
|
ecs_query_table_list_t list; |
|
|
|
/* Contains head/tail to nodes of query groups (if group_by is used) */ |
|
ecs_map_t groups; |
|
|
|
/* Table sorting */ |
|
ecs_entity_t order_by_component; |
|
ecs_order_by_action_t order_by; |
|
ecs_sort_table_action_t sort_table; |
|
ecs_vec_t table_slices; |
|
int32_t order_by_term; |
|
|
|
/* Table grouping */ |
|
ecs_entity_t group_by_id; |
|
ecs_group_by_action_t group_by; |
|
ecs_group_create_action_t on_group_create; |
|
ecs_group_delete_action_t on_group_delete; |
|
void *group_by_ctx; |
|
ecs_ctx_free_t group_by_ctx_free; |
|
|
|
/* Subqueries */ |
|
ecs_query_t *parent; |
|
ecs_vec_t subqueries; |
|
|
|
/* Flags for query properties */ |
|
ecs_flags32_t flags; |
|
|
|
/* Monitor generation */ |
|
int32_t monitor_generation; |
|
|
|
int32_t cascade_by; /* Identify cascade term */ |
|
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 */ |
|
|
|
/* User context */ |
|
void *ctx; /* User context to pass to callback */ |
|
void *binding_ctx; /* Context to be used for language bindings */ |
|
|
|
ecs_ctx_free_t ctx_free; /** Callback to free ctx */ |
|
ecs_ctx_free_t binding_ctx_free; /** Callback to free binding_ctx */ |
|
|
|
/* Mixins */ |
|
ecs_iterable_t iterable; |
|
ecs_poly_dtor_t dtor; |
|
|
|
/* Query-level allocators */ |
|
ecs_query_allocators_t allocators; |
|
}; |
|
|
|
/** All observers for a specific (component) id */ |
|
typedef struct ecs_event_id_record_t { |
|
/* Triggers for Self */ |
|
ecs_map_t self; /* map<trigger_id, trigger_t> */ |
|
ecs_map_t self_up; /* map<trigger_id, trigger_t> */ |
|
ecs_map_t up; /* map<trigger_id, trigger_t> */ |
|
|
|
ecs_map_t observers; /* map<trigger_id, trigger_t> */ |
|
|
|
/* Triggers for SuperSet, SubSet */ |
|
ecs_map_t set_observers; /* map<trigger_id, trigger_t> */ |
|
|
|
/* Triggers for Self with non-This subject */ |
|
ecs_map_t entity_observers; /* map<trigger_id, trigger_t> */ |
|
|
|
/* Number of active observers for (component) id */ |
|
int32_t observer_count; |
|
} ecs_event_id_record_t; |
|
|
|
/* World level allocators are for operations that are not multithreaded */ |
|
typedef struct ecs_world_allocators_t { |
|
ecs_map_params_t ptr; |
|
ecs_map_params_t query_table_list; |
|
ecs_block_allocator_t query_table; |
|
ecs_block_allocator_t query_table_match; |
|
ecs_block_allocator_t graph_edge_lo; |
|
ecs_block_allocator_t graph_edge; |
|
ecs_block_allocator_t id_record; |
|
ecs_block_allocator_t id_record_chunk; |
|
ecs_block_allocator_t table_diff; |
|
ecs_block_allocator_t sparse_chunk; |
|
ecs_block_allocator_t hashmap; |
|
|
|
/* Temporary vectors used for creating table diff id sequences */ |
|
ecs_table_diff_builder_t diff_builder; |
|
} ecs_world_allocators_t; |
|
|
|
/* Stage level allocators are for operations that can be multithreaded */ |
|
typedef struct ecs_stage_allocators_t { |
|
ecs_stack_t iter_stack; |
|
ecs_stack_t deser_stack; |
|
ecs_block_allocator_t cmd_entry_chunk; |
|
} ecs_stage_allocators_t; |
|
|
|
/** Types for deferred operations */ |
|
typedef enum ecs_cmd_kind_t { |
|
EcsCmdClone, |
|
EcsCmdBulkNew, |
|
EcsCmdAdd, |
|
EcsCmdRemove, |
|
EcsCmdSet, |
|
EcsCmdEmplace, |
|
EcsCmdMut, |
|
EcsCmdModified, |
|
EcsCmdModifiedNoHook, |
|
EcsCmdAddModified, |
|
EcsCmdPath, |
|
EcsCmdDelete, |
|
EcsCmdClear, |
|
EcsCmdOnDeleteAction, |
|
EcsCmdEnable, |
|
EcsCmdDisable, |
|
EcsCmdEvent, |
|
EcsCmdSkip |
|
} ecs_cmd_kind_t; |
|
|
|
/* Entity specific metadata for command in queue */ |
|
typedef struct ecs_cmd_entry_t { |
|
int32_t first; |
|
int32_t last; /* If -1, a delete command was inserted */ |
|
} ecs_cmd_entry_t; |
|
|
|
typedef struct ecs_cmd_1_t { |
|
void *value; /* Component value (used by set / get_mut) */ |
|
ecs_size_t size; /* Size of value */ |
|
bool clone_value; /* Clone entity with value (used for clone) */ |
|
} ecs_cmd_1_t; |
|
|
|
typedef struct ecs_cmd_n_t { |
|
ecs_entity_t *entities; |
|
int32_t count; |
|
} ecs_cmd_n_t; |
|
|
|
typedef struct ecs_cmd_t { |
|
ecs_cmd_kind_t kind; /* Command kind */ |
|
int32_t next_for_entity; /* Next operation for entity */ |
|
ecs_id_t id; /* (Component) id */ |
|
ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ |
|
ecs_cmd_entry_t *entry; |
|
ecs_entity_t entity; /* Entity id */ |
|
|
|
union { |
|
ecs_cmd_1_t _1; /* Data for single entity operation */ |
|
ecs_cmd_n_t _n; /* Data for multi entity operation */ |
|
} is; |
|
} ecs_cmd_t; |
|
|
|
/* Data structures that store the command queue */ |
|
typedef struct ecs_commands_t { |
|
ecs_vec_t queue; |
|
ecs_stack_t stack; /* Temp memory used by deferred commands */ |
|
ecs_sparse_t entries; /* <entity, op_entry_t> - command batching */ |
|
} ecs_commands_t; |
|
|
|
/** A stage is a context that allows for safely using the API from multiple |
|
* threads. Stage pointers can be passed to the world argument of API |
|
* operations, which causes the operation to be ran on the stage instead of the |
|
* world. */ |
|
struct ecs_stage_t { |
|
ecs_header_t hdr; |
|
|
|
/* Unique id that identifies the stage */ |
|
int32_t id; |
|
|
|
/* Zero if not deferred, positive if deferred, negative if suspended */ |
|
int32_t defer; |
|
|
|
/* Command queue stack, for nested execution */ |
|
ecs_commands_t *cmd; |
|
ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK]; |
|
int32_t cmd_sp; |
|
|
|
/* Thread context */ |
|
ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ |
|
ecs_world_t *world; /* Reference to world */ |
|
ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ |
|
|
|
/* One-shot actions to be executed after the merge */ |
|
ecs_vec_t post_frame_actions; |
|
|
|
/* Namespacing */ |
|
ecs_entity_t scope; /* Entity of current scope */ |
|
ecs_entity_t with; /* Id to add by default to new entities */ |
|
ecs_entity_t base; /* Currently instantiated top-level base */ |
|
const ecs_entity_t *lookup_path; /* Search path used by lookup operations */ |
|
|
|
/* Properties */ |
|
bool auto_merge; /* Should this stage automatically merge? */ |
|
bool async; /* Is stage asynchronous? (write only) */ |
|
|
|
/* Thread specific allocators */ |
|
ecs_stage_allocators_t allocators; |
|
ecs_allocator_t allocator; |
|
|
|
/* Caches for rule creation */ |
|
ecs_vec_t variables; |
|
ecs_vec_t operations; |
|
}; |
|
|
|
/* Component monitor */ |
|
typedef struct ecs_monitor_t { |
|
ecs_vec_t queries; /* vector<ecs_query_t*> */ |
|
bool is_dirty; /* Should queries be rematched? */ |
|
} ecs_monitor_t; |
|
|
|
/* Component monitors */ |
|
typedef struct ecs_monitor_set_t { |
|
ecs_map_t monitors; /* map<id, ecs_monitor_t> */ |
|
bool is_dirty; /* Should monitors be evaluated? */ |
|
} ecs_monitor_set_t; |
|
|
|
/* Data stored for id marked for deletion */ |
|
typedef struct ecs_marked_id_t { |
|
ecs_id_record_t *idr; |
|
ecs_id_t id; |
|
ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ |
|
bool delete_id; |
|
} ecs_marked_id_t; |
|
|
|
typedef struct ecs_store_t { |
|
/* Entity lookup */ |
|
ecs_entity_index_t entity_index; |
|
|
|
/* Table lookup by id */ |
|
ecs_sparse_t tables; /* sparse<table_id, ecs_table_t> */ |
|
|
|
/* Table lookup by hash */ |
|
ecs_hashmap_t table_map; /* hashmap<ecs_type_t, ecs_table_t*> */ |
|
|
|
/* Root table */ |
|
ecs_table_t root; |
|
|
|
/* Records cache */ |
|
ecs_vec_t records; |
|
|
|
/* Stack of ids being deleted. */ |
|
ecs_vec_t marked_ids; /* vector<ecs_marked_ids_t> */ |
|
|
|
/* Entity ids associated with depth (for flat hierarchies) */ |
|
ecs_vec_t depth_ids; |
|
ecs_map_t entity_to_depth; /* What it says */ |
|
} ecs_store_t; |
|
|
|
/* fini actions */ |
|
typedef struct ecs_action_elem_t { |
|
ecs_fini_action_t action; |
|
void *ctx; |
|
} ecs_action_elem_t; |
|
|
|
typedef struct ecs_pipeline_state_t ecs_pipeline_state_t; |
|
|
|
/** The world stores and manages all ECS data. An application can have more than |
|
* one world, but data is not shared between worlds. */ |
|
struct ecs_world_t { |
|
ecs_header_t hdr; |
|
|
|
/* -- Type metadata -- */ |
|
ecs_id_record_t *id_index_lo; |
|
ecs_map_t id_index_hi; /* map<id, ecs_id_record_t*> */ |
|
ecs_sparse_t type_info; /* sparse<type_id, type_info_t> */ |
|
|
|
/* -- 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; |
|
ecs_id_record_t *idr_childof_wildcard; |
|
ecs_id_record_t *idr_identifier_name; |
|
|
|
/* -- Mixins -- */ |
|
ecs_world_t *self; |
|
ecs_observable_t observable; |
|
ecs_iterable_t iterable; |
|
|
|
/* Unique id per generated event used to prevent duplicate notifications */ |
|
int32_t event_id; |
|
|
|
/* Is entity range checking enabled? */ |
|
bool range_check_enabled; |
|
|
|
/* -- Data storage -- */ |
|
ecs_store_t store; |
|
|
|
/* -- Pending table event buffers -- */ |
|
ecs_sparse_t *pending_buffer; /* sparse<table_id, ecs_table_t*> */ |
|
ecs_sparse_t *pending_tables; /* sparse<table_id, ecs_table_t*> */ |
|
|
|
/* Used to track when cache needs to be updated */ |
|
ecs_monitor_set_t monitors; /* map<id, ecs_monitor_t> */ |
|
|
|
/* -- Systems -- */ |
|
ecs_entity_t pipeline; /* Current pipeline */ |
|
|
|
/* -- Identifiers -- */ |
|
ecs_hashmap_t aliases; |
|
ecs_hashmap_t symbols; |
|
|
|
/* -- Staging -- */ |
|
ecs_stage_t *stages; /* Stages */ |
|
int32_t stage_count; /* Number of stages */ |
|
|
|
/* -- Multithreading -- */ |
|
ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ |
|
ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ |
|
ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ |
|
int32_t workers_running; /* Number of threads running */ |
|
int32_t workers_waiting; /* Number of workers waiting on sync */ |
|
ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */ |
|
bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ |
|
|
|
/* -- Time management -- */ |
|
ecs_time_t world_start_time; /* Timestamp of simulation start */ |
|
ecs_time_t frame_start_time; /* Timestamp of frame start */ |
|
ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ |
|
|
|
/* -- Metrics -- */ |
|
ecs_world_info_t info; |
|
|
|
/* -- World flags -- */ |
|
ecs_flags32_t flags; |
|
|
|
/* Count that increases when component monitors change */ |
|
int32_t monitor_generation; |
|
|
|
/* -- Allocators -- */ |
|
ecs_world_allocators_t allocators; /* Static allocation sizes */ |
|
ecs_allocator_t allocator; /* Dynamic allocation sizes */ |
|
|
|
void *ctx; /* Application context */ |
|
void *binding_ctx; /* Binding-specific context */ |
|
|
|
ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ |
|
ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ |
|
|
|
ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ |
|
}; |
|
|
|
#endif |
|
|
|
/** |
|
* @file table_cache.h |
|
* @brief Data structure for fast table iteration/lookups. |
|
*/ |
|
|
|
#ifndef FLECS_TABLE_CACHE_H_ |
|
#define FLECS_TABLE_CACHE_H_ |
|
|
|
void ecs_table_cache_init( |
|
ecs_world_t *world, |
|
ecs_table_cache_t *cache); |
|
|
|
void ecs_table_cache_fini( |
|
ecs_table_cache_t *cache); |
|
|
|
void ecs_table_cache_insert( |
|
ecs_table_cache_t *cache, |
|
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, |
|
uint64_t table_id, |
|
ecs_table_cache_hdr_t *elem); |
|
|
|
void* ecs_table_cache_get( |
|
const ecs_table_cache_t *cache, |
|
const ecs_table_t *table); |
|
|
|
bool ecs_table_cache_set_empty( |
|
ecs_table_cache_t *cache, |
|
const ecs_table_t *table, |
|
bool empty); |
|
|
|
bool ecs_table_cache_is_empty( |
|
const ecs_table_cache_t *cache); |
|
|
|
#define flecs_table_cache_count(cache) (cache)->tables.count |
|
#define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count |
|
|
|
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); |
|
|
|
bool flecs_table_cache_all_iter( |
|
ecs_table_cache_t *cache, |
|
ecs_table_cache_iter_t *out); |
|
|
|
ecs_table_cache_hdr_t* flecs_table_cache_next_( |
|
ecs_table_cache_iter_t *it); |
|
|
|
#define flecs_table_cache_next(it, T)\ |
|
(ECS_CAST(T*, flecs_table_cache_next_(it))) |
|
|
|
#endif |
|
|
|
/** |
|
* @file id_index.h |
|
* @brief Index for looking up tables by (component) id. |
|
*/ |
|
|
|
#ifndef FLECS_ID_INDEX_H |
|
#define FLECS_ID_INDEX_H |
|
|
|
/* Payload for id cache */ |
|
struct ecs_table_record_t { |
|
ecs_table_cache_hdr_t hdr; /* Table cache header */ |
|
int16_t index; /* First type index where id occurs in table */ |
|
int16_t count; /* Number of times id occurs in table */ |
|
int16_t column; /* First column index where id occurs */ |
|
}; |
|
|
|
/* Linked list of id records */ |
|
typedef struct ecs_id_record_elem_t { |
|
struct ecs_id_record_t *prev, *next; |
|
} ecs_id_record_elem_t; |
|
|
|
typedef struct ecs_reachable_elem_t { |
|
const ecs_table_record_t *tr; |
|
ecs_record_t *record; |
|
ecs_entity_t src; |
|
ecs_id_t id; |
|
#ifndef NDEBUG |
|
ecs_table_t *table; |
|
#endif |
|
} ecs_reachable_elem_t; |
|
|
|
typedef struct ecs_reachable_cache_t { |
|
int32_t generation; |
|
int32_t current; |
|
ecs_vec_t ids; /* vec<reachable_elem_t> */ |
|
} ecs_reachable_cache_t; |
|
|
|
/* Payload for id index which contains all data structures 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<ecs_table_record_t> */ |
|
|
|
/* Id of record */ |
|
ecs_id_t id; |
|
|
|
/* Flags for id */ |
|
ecs_flags32_t flags; |
|
|
|
/* Cached pointer to type info for id, if id contains data. */ |
|
const ecs_type_info_t *type_info; |
|
|
|
/* Name lookup index (currently only used for ChildOf pairs) */ |
|
ecs_hashmap_t *name_index; |
|
|
|
/* Lists for all id records that match a pair wildcard. The wildcard id |
|
* record is at the head of the list. */ |
|
ecs_id_record_elem_t first; /* (R, *) */ |
|
ecs_id_record_elem_t second; /* (*, O) */ |
|
ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */ |
|
|
|
/* Parent id record. For pair records the parent is the (R, *) record. */ |
|
ecs_id_record_t *parent; |
|
|
|
/* Refcount */ |
|
int32_t refcount; |
|
|
|
/* Keep alive count. This count must be 0 when the id record is deleted. If |
|
* it is not 0, an application attempted to delete an id that was still |
|
* queried for. */ |
|
int32_t keep_alive; |
|
|
|
/* Cache invalidation counter */ |
|
ecs_reachable_cache_t reachable; |
|
}; |
|
|
|
/* Get id record for id */ |
|
ecs_id_record_t* flecs_id_record_get( |
|
const ecs_world_t *world, |
|
ecs_id_t id); |
|
|
|
/* Get id record for id for searching. |
|
* Same as flecs_id_record_get, but replaces (R, *) with (Union, R) if R is a |
|
* union relationship. */ |
|
ecs_id_record_t* flecs_query_id_record_get( |
|
const ecs_world_t *world, |
|
ecs_id_t id); |
|
|
|
/* Ensure id record for id */ |
|
ecs_id_record_t* flecs_id_record_ensure( |
|
ecs_world_t *world, |
|
ecs_id_t id); |
|
|
|
/* Increase refcount of id record */ |
|
void flecs_id_record_claim( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr); |
|
|
|
/* Decrease refcount of id record, delete if 0 */ |
|
int32_t flecs_id_record_release( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr); |
|
|
|
/* Release all empty tables in id record */ |
|
void flecs_id_record_release_tables( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr); |
|
|
|
/* Set (component) type info for id record */ |
|
bool flecs_id_record_set_type_info( |
|
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_id_name_index_ensure( |
|
ecs_world_t *world, |
|
ecs_id_t id); |
|
|
|
ecs_hashmap_t* flecs_id_record_name_index_ensure( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr); |
|
|
|
/* Get name index for id record */ |
|
ecs_hashmap_t* flecs_id_name_index_get( |
|
const ecs_world_t *world, |
|
ecs_id_t id); |
|
|
|
/* Find table record for id */ |
|
ecs_table_record_t* flecs_table_record_get( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_id_t id); |
|
|
|
/* Find table record for id record */ |
|
ecs_table_record_t* flecs_id_record_get_table( |
|
const ecs_id_record_t *idr, |
|
const ecs_table_t *table); |
|
|
|
/* Bootstrap cached id records */ |
|
void flecs_init_id_records( |
|
ecs_world_t *world); |
|
|
|
/* Cleanup all id records in world */ |
|
void flecs_fini_id_records( |
|
ecs_world_t *world); |
|
|
|
#endif |
|
|
|
/** |
|
* @file observable.h |
|
* @brief Functions for sending events. |
|
*/ |
|
|
|
#ifndef FLECS_OBSERVABLE_H |
|
#define FLECS_OBSERVABLE_H |
|
|
|
ecs_event_record_t* flecs_event_record_get( |
|
const ecs_observable_t *o, |
|
ecs_entity_t event); |
|
|
|
ecs_event_record_t* flecs_event_record_ensure( |
|
ecs_observable_t *o, |
|
ecs_entity_t event); |
|
|
|
ecs_event_id_record_t* flecs_event_id_record_get( |
|
const ecs_event_record_t *er, |
|
ecs_id_t id); |
|
|
|
ecs_event_id_record_t* flecs_event_id_record_ensure( |
|
ecs_world_t *world, |
|
ecs_event_record_t *er, |
|
ecs_id_t id); |
|
|
|
void flecs_event_id_record_remove( |
|
ecs_event_record_t *er, |
|
ecs_id_t id); |
|
|
|
void flecs_observable_init( |
|
ecs_observable_t *observable); |
|
|
|
void flecs_observable_fini( |
|
ecs_observable_t *observable); |
|
|
|
bool flecs_observers_exist( |
|
ecs_observable_t *observable, |
|
ecs_id_t id, |
|
ecs_entity_t event); |
|
|
|
void flecs_observer_fini( |
|
ecs_observer_t *observer); |
|
|
|
void flecs_emit( |
|
ecs_world_t *world, |
|
ecs_world_t *stage, |
|
ecs_event_desc_t *desc); |
|
|
|
bool flecs_default_observer_next_callback( |
|
ecs_iter_t *it); |
|
|
|
void flecs_observers_invoke( |
|
ecs_world_t *world, |
|
ecs_map_t *observers, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_entity_t trav); |
|
|
|
void flecs_emit_propagate_invalidate( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t offset, |
|
int32_t count); |
|
|
|
#endif |
|
|
|
/** |
|
* @file iter.h |
|
* @brief Iterator utilities. |
|
*/ |
|
|
|
#ifndef FLECS_ITER_H |
|
#define FLECS_ITER_H |
|
|
|
void flecs_iter_init( |
|
const ecs_world_t *world, |
|
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); |
|
|
|
bool flecs_iter_next_row( |
|
ecs_iter_t *it); |
|
|
|
bool flecs_iter_next_instanced( |
|
ecs_iter_t *it, |
|
bool result); |
|
|
|
void* flecs_iter_calloc( |
|
ecs_iter_t *it, |
|
ecs_size_t size, |
|
ecs_size_t align); |
|
|
|
#define flecs_iter_calloc_t(it, T)\ |
|
flecs_iter_calloc(it, ECS_SIZEOF(T), ECS_ALIGNOF(T)) |
|
|
|
#define flecs_iter_calloc_n(it, T, count)\ |
|
flecs_iter_calloc(it, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) |
|
|
|
void flecs_iter_free( |
|
void *ptr, |
|
ecs_size_t size); |
|
|
|
#define flecs_iter_free_t(ptr, T)\ |
|
flecs_iter_free(ptr, ECS_SIZEOF(T)) |
|
|
|
#define flecs_iter_free_n(ptr, T, count)\ |
|
flecs_iter_free(ptr, ECS_SIZEOF(T) * count) |
|
|
|
#endif |
|
|
|
/** |
|
* @file poly.h |
|
* @brief Functions for managing poly objects. |
|
*/ |
|
|
|
#ifndef FLECS_POLY_H |
|
#define FLECS_POLY_H |
|
|
|
#include <stddef.h> |
|
|
|
/* Initialize poly */ |
|
void* ecs_poly_init_( |
|
ecs_poly_t *object, |
|
int32_t kind, |
|
ecs_size_t size, |
|
ecs_mixins_t *mixins); |
|
|
|
#define ecs_poly_init(object, type)\ |
|
ecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins) |
|
|
|
/* Deinitialize object for specified type */ |
|
void ecs_poly_fini_( |
|
ecs_poly_t *object, |
|
int32_t kind); |
|
|
|
#define ecs_poly_fini(object, type)\ |
|
ecs_poly_fini_(object, type##_magic) |
|
|
|
/* Utility functions for creating an object on the heap */ |
|
#define ecs_poly_new(type)\ |
|
(type*)ecs_poly_init(ecs_os_calloc_t(type), type) |
|
|
|
#define ecs_poly_free(obj, type)\ |
|
ecs_poly_fini(obj, type);\ |
|
ecs_os_free(obj) |
|
|
|
/* Get or create poly component for an entity */ |
|
EcsPoly* ecs_poly_bind_( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag); |
|
|
|
#define ecs_poly_bind(world, entity, T) \ |
|
ecs_poly_bind_(world, entity, T##_tag) |
|
|
|
void ecs_poly_modified_( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag); |
|
|
|
#define ecs_poly_modified(world, entity, T) \ |
|
ecs_poly_modified_(world, entity, T##_tag) |
|
|
|
/* Get poly component for an entity */ |
|
const EcsPoly* ecs_poly_bind_get_( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag); |
|
|
|
#define ecs_poly_bind_get(world, entity, T) \ |
|
ecs_poly_bind_get_(world, entity, T##_tag) |
|
|
|
ecs_poly_t* ecs_poly_get_( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag); |
|
|
|
#define ecs_poly_get(world, entity, T) \ |
|
((T*)ecs_poly_get_(world, entity, T##_tag)) |
|
|
|
/* Utilities for testing/asserting an object type */ |
|
#ifndef FLECS_NDEBUG |
|
#define ecs_poly_assert(object, ty)\ |
|
do {\ |
|
ecs_assert(object != NULL, ECS_INVALID_PARAMETER, NULL);\ |
|
const ecs_header_t *hdr = (const ecs_header_t *)object;\ |
|
const char *type_name = hdr->mixins->type_name;\ |
|
ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, type_name);\ |
|
ecs_assert(hdr->type == ty##_magic, ECS_INVALID_PARAMETER, type_name);\ |
|
} while (0) |
|
#else |
|
#define ecs_poly_assert(object, ty) |
|
#endif |
|
|
|
/* Utility functions for getting a mixin from an object */ |
|
ecs_iterable_t* ecs_get_iterable( |
|
const ecs_poly_t *poly); |
|
|
|
ecs_observable_t* ecs_get_observable( |
|
const ecs_poly_t *object); |
|
|
|
ecs_poly_dtor_t* ecs_get_dtor( |
|
const ecs_poly_t *poly); |
|
|
|
#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_fini( |
|
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_cmd( |
|
ecs_stage_t *stage); |
|
|
|
bool flecs_defer_begin( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage); |
|
|
|
bool flecs_defer_modified( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_entity_t component); |
|
|
|
bool flecs_defer_clone( |
|
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_path( |
|
ecs_stage_t *stage, |
|
ecs_entity_t parent, |
|
ecs_entity_t entity, |
|
const char *name); |
|
|
|
bool flecs_defer_delete( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity); |
|
|
|
bool flecs_defer_clear( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity); |
|
|
|
bool flecs_defer_on_delete_action( |
|
ecs_stage_t *stage, |
|
ecs_id_t id, |
|
ecs_entity_t action); |
|
|
|
bool flecs_defer_enable( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_entity_t component, |
|
bool enable); |
|
|
|
bool flecs_defer_add( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_id_t id); |
|
|
|
bool flecs_defer_remove( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_id_t id); |
|
|
|
void* flecs_defer_set( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_cmd_kind_t op_kind, |
|
ecs_entity_t entity, |
|
ecs_entity_t component, |
|
ecs_size_t size, |
|
void *value); |
|
|
|
bool flecs_defer_end( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage); |
|
|
|
bool flecs_defer_purge( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage); |
|
|
|
void flecs_enqueue( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_event_desc_t *desc); |
|
|
|
void flecs_commands_push( |
|
ecs_stage_t *stage); |
|
|
|
void flecs_commands_pop( |
|
ecs_stage_t *stage); |
|
|
|
#endif |
|
|
|
/** |
|
* @file world.h |
|
* @brief World-level API. |
|
*/ |
|
|
|
#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_type_info_get( |
|
const ecs_world_t *world, |
|
ecs_entity_t component); |
|
|
|
/* Get or create component callbacks */ |
|
ecs_type_info_t* flecs_type_info_ensure( |
|
ecs_world_t *world, |
|
ecs_entity_t component); |
|
|
|
bool flecs_type_info_init_id( |
|
ecs_world_t *world, |
|
ecs_entity_t component, |
|
ecs_size_t size, |
|
ecs_size_t alignment, |
|
const ecs_type_hooks_t *li); |
|
|
|
#define flecs_type_info_init(world, T, ...)\ |
|
flecs_type_info_init_id(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T),\ |
|
&(ecs_type_hooks_t)__VA_ARGS__) |
|
|
|
void flecs_type_info_fini( |
|
ecs_type_info_t *ti); |
|
|
|
void flecs_type_info_free( |
|
ecs_world_t *world, |
|
ecs_entity_t component); |
|
|
|
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_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 ecs_suspend_readonly_state_t { |
|
bool is_readonly; |
|
bool is_deferred; |
|
int32_t defer_count; |
|
ecs_entity_t scope; |
|
ecs_entity_t with; |
|
ecs_vec_t commands; |
|
ecs_stack_t defer_stack; |
|
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); |
|
|
|
/* Convenience macro's for world allocator */ |
|
#define flecs_walloc(world, size)\ |
|
flecs_alloc(&world->allocator, size) |
|
#define flecs_walloc_n(world, T, count)\ |
|
flecs_alloc_n(&world->allocator, T, count) |
|
#define flecs_wcalloc(world, size)\ |
|
flecs_calloc(&world->allocator, size) |
|
#define flecs_wcalloc_n(world, T, count)\ |
|
flecs_calloc_n(&world->allocator, T, count) |
|
#define flecs_wfree(world, size, ptr)\ |
|
flecs_free(&world->allocator, size, ptr) |
|
#define flecs_wfree_n(world, T, count, ptr)\ |
|
flecs_free_n(&world->allocator, T, count, ptr) |
|
#define flecs_wrealloc(world, size_dst, size_src, ptr)\ |
|
flecs_realloc(&world->allocator, size_dst, size_src, ptr) |
|
#define flecs_wrealloc_n(world, T, count_dst, count_src, ptr)\ |
|
flecs_realloc_n(&world->allocator, T, count_dst, count_src, ptr) |
|
#define flecs_wdup(world, size, ptr)\ |
|
flecs_dup(&world->allocator, size, ptr) |
|
#define flecs_wdup_n(world, T, count, ptr)\ |
|
flecs_dup_n(&world->allocator, T, count, ptr) |
|
|
|
#endif |
|
|
|
/** |
|
* @file datastructures/name_index.h |
|
* @brief Data structure for resolving 64bit keys by string (name). |
|
*/ |
|
|
|
#ifndef FLECS_NAME_INDEX_H |
|
#define FLECS_NAME_INDEX_H |
|
|
|
void flecs_name_index_init( |
|
ecs_hashmap_t *hm, |
|
ecs_allocator_t *allocator); |
|
|
|
void flecs_name_index_init_if( |
|
ecs_hashmap_t *hm, |
|
ecs_allocator_t *allocator); |
|
|
|
bool flecs_name_index_is_init( |
|
const ecs_hashmap_t *hm); |
|
|
|
ecs_hashmap_t* flecs_name_index_new( |
|
ecs_world_t *world, |
|
ecs_allocator_t *allocator); |
|
|
|
void flecs_name_index_fini( |
|
ecs_hashmap_t *map); |
|
|
|
void flecs_name_index_free( |
|
ecs_hashmap_t *map); |
|
|
|
ecs_hashmap_t* flecs_name_index_copy( |
|
ecs_hashmap_t *dst); |
|
|
|
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 |
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
//// Bootstrap API |
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
/* Bootstrap world */ |
|
void flecs_bootstrap( |
|
ecs_world_t *world); |
|
|
|
#define flecs_bootstrap_component(world, id_)\ |
|
ecs_component_init(world, &(ecs_component_desc_t){\ |
|
.entity = ecs_entity(world, { .id = ecs_id(id_), .name = #id_, .symbol = #id_ }),\ |
|
.type.size = sizeof(id_),\ |
|
.type.alignment = ECS_ALIGNOF(id_)\ |
|
}); |
|
|
|
#define flecs_bootstrap_tag(world, name)\ |
|
ecs_ensure(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, (const char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ |
|
ecs_set_symbol(world, name, #name) |
|
|
|
/* Bootstrap functions for other parts in the code */ |
|
void flecs_bootstrap_hierarchy(ecs_world_t *world); |
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
//// 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); |
|
|
|
void flecs_record_add_flag( |
|
ecs_record_t *record, |
|
uint32_t flag); |
|
|
|
ecs_entity_t flecs_get_oneof( |
|
const ecs_world_t *world, |
|
ecs_entity_t e); |
|
|
|
void flecs_notify_on_remove( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_table_t *other_table, |
|
int32_t row, |
|
int32_t count, |
|
const ecs_type_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); |
|
|
|
int32_t flecs_relation_depth( |
|
const ecs_world_t *world, |
|
ecs_entity_t r, |
|
const ecs_table_t *table); |
|
|
|
void flecs_instantiate( |
|
ecs_world_t *world, |
|
ecs_entity_t base, |
|
ecs_table_t *table, |
|
int32_t row, |
|
int32_t count); |
|
|
|
void* flecs_get_base_component( |
|
const ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t id, |
|
ecs_id_record_t *table_index, |
|
int32_t recur_depth); |
|
|
|
void flecs_invoke_hook( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t count, |
|
int32_t row, |
|
ecs_entity_t *entities, |
|
void *ptr, |
|
ecs_id_t id, |
|
const ecs_type_info_t *ti, |
|
ecs_entity_t event, |
|
ecs_iter_action_t hook); |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
//// 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 *sources, |
|
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); |
|
|
|
void flecs_filter_apply_iter_flags( |
|
ecs_iter_t *it, |
|
const ecs_filter_t *filter); |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
//// Safe(r) integer casting |
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
#define FLECS_CONVERSION_ERR(T, value)\ |
|
"illegal conversion from value " #value " to type " #T |
|
|
|
#define flecs_signed_char__ (CHAR_MIN < 0) |
|
#define flecs_signed_short__ true |
|
#define flecs_signed_int__ true |
|
#define flecs_signed_long__ true |
|
#define flecs_signed_size_t__ false |
|
#define flecs_signed_int8_t__ true |
|
#define flecs_signed_int16_t__ true |
|
#define flecs_signed_int32_t__ true |
|
#define flecs_signed_int64_t__ true |
|
#define flecs_signed_intptr_t__ true |
|
#define flecs_signed_uint8_t__ false |
|
#define flecs_signed_uint16_t__ false |
|
#define flecs_signed_uint32_t__ false |
|
#define flecs_signed_uint64_t__ false |
|
#define flecs_signed_uintptr_t__ false |
|
#define flecs_signed_ecs_size_t__ true |
|
#define flecs_signed_ecs_entity_t__ false |
|
|
|
uint64_t flecs_ito_( |
|
size_t dst_size, |
|
bool dst_signed, |
|
bool lt_zero, |
|
uint64_t value, |
|
const char *err); |
|
|
|
#ifndef FLECS_NDEBUG |
|
#define flecs_ito(T, value)\ |
|
(T)flecs_ito_(\ |
|
sizeof(T),\ |
|
flecs_signed_##T##__,\ |
|
(value) < 0,\ |
|
(uint64_t)(value),\ |
|
FLECS_CONVERSION_ERR(T, (value))) |
|
|
|
#define flecs_uto(T, value)\ |
|
(T)flecs_ito_(\ |
|
sizeof(T),\ |
|
flecs_signed_##T##__,\ |
|
false,\ |
|
(uint64_t)(value),\ |
|
FLECS_CONVERSION_ERR(T, (value))) |
|
#else |
|
#define flecs_ito(T, value) (T)(value) |
|
#define flecs_uto(T, value) (T)(value) |
|
#endif |
|
|
|
#define flecs_itosize(value) flecs_ito(size_t, (value)) |
|
#define flecs_utosize(value) flecs_uto(ecs_size_t, (value)) |
|
#define flecs_itoi16(value) flecs_ito(int16_t, (value)) |
|
#define flecs_itoi32(value) flecs_ito(int32_t, (value)) |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
//// Entity filter |
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void flecs_entity_filter_init( |
|
ecs_world_t *world, |
|
ecs_entity_filter_t **entity_filter, |
|
const ecs_filter_t *filter, |
|
const ecs_table_t *table, |
|
ecs_id_t *ids, |
|
int32_t *columns); |
|
|
|
void flecs_entity_filter_fini( |
|
ecs_world_t *world, |
|
ecs_entity_filter_t *entity_filter); |
|
|
|
int flecs_entity_filter_next( |
|
ecs_entity_filter_iter_t *it); |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
//// Utilities |
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
uint64_t flecs_hash( |
|
const void *data, |
|
ecs_size_t length); |
|
|
|
uint64_t flecs_wyhash( |
|
const void *data, |
|
ecs_size_t length); |
|
|
|
/* Get next power of 2 */ |
|
int32_t flecs_next_pow_of_2( |
|
int32_t n); |
|
|
|
/* Convert 64bit value to ecs_record_t type. ecs_record_t is stored as 64bit int in the |
|
* entity index */ |
|
ecs_record_t flecs_to_row( |
|
uint64_t value); |
|
|
|
/* Get 64bit integer from ecs_record_t */ |
|
uint64_t flecs_from_row( |
|
ecs_record_t record); |
|
|
|
/* Convert a symbol name to an entity name by removing the prefix */ |
|
const char* flecs_name_from_symbol( |
|
ecs_world_t *world, |
|
const char *type_name); |
|
|
|
/* Compare function for entity ids */ |
|
int flecs_entity_compare( |
|
ecs_entity_t e1, |
|
const void *ptr1, |
|
ecs_entity_t e2, |
|
const void *ptr2); |
|
|
|
bool flecs_name_is_id( |
|
const char *name); |
|
|
|
ecs_entity_t flecs_name_to_id( |
|
const ecs_world_t *world, |
|
const char *name); |
|
|
|
/* Convert floating point to string */ |
|
char * ecs_ftoa( |
|
double f, |
|
char * buf, |
|
int precision); |
|
|
|
uint64_t flecs_string_hash( |
|
const void *ptr); |
|
|
|
void flecs_table_hashmap_init( |
|
ecs_world_t *world, |
|
ecs_hashmap_t *hm); |
|
|
|
void flecs_colorize_buf( |
|
char *msg, |
|
bool enable_colors, |
|
ecs_strbuf_t *buf); |
|
|
|
bool flecs_isident( |
|
char ch); |
|
|
|
int32_t flecs_search_w_idr( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_id_t id, |
|
ecs_id_t *id_out, |
|
ecs_id_record_t *idr); |
|
|
|
int32_t flecs_search_relation_w_idr( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
int32_t offset, |
|
ecs_id_t id, |
|
ecs_entity_t rel, |
|
ecs_flags32_t flags, |
|
ecs_entity_t *subject_out, |
|
ecs_id_t *id_out, |
|
struct ecs_table_record_t **tr_out, |
|
ecs_id_record_t *idr); |
|
|
|
bool flecs_type_can_inherit_id( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
const ecs_id_record_t *idr, |
|
ecs_id_t id); |
|
|
|
#endif |
|
|
|
|
|
/* -- Identifier Component -- */ |
|
static ECS_DTOR(EcsIdentifier, ptr, { |
|
ecs_os_strset(&ptr->value, NULL); |
|
}) |
|
|
|
static ECS_COPY(EcsIdentifier, dst, src, { |
|
ecs_os_strset(&dst->value, src->value); |
|
dst->hash = src->hash; |
|
dst->length = src->length; |
|
dst->index_hash = src->index_hash; |
|
dst->index = src->index; |
|
}) |
|
|
|
static ECS_MOVE(EcsIdentifier, dst, src, { |
|
ecs_os_strset(&dst->value, NULL); |
|
dst->value = src->value; |
|
dst->hash = src->hash; |
|
dst->length = src->length; |
|
dst->index_hash = src->index_hash; |
|
dst->index = src->index; |
|
|
|
src->value = NULL; |
|
src->hash = 0; |
|
src->index_hash = 0; |
|
src->index = 0; |
|
src->length = 0; |
|
}) |
|
|
|
static |
|
void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { |
|
EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 1); |
|
|
|
ecs_world_t *world = it->real_world; |
|
ecs_entity_t evt = it->event; |
|
ecs_id_t evt_id = it->event_id; |
|
ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */ |
|
ecs_id_t pair = ecs_childof(0); |
|
ecs_hashmap_t *index = NULL; |
|
|
|
if (kind == EcsSymbol) { |
|
index = &world->symbols; |
|
} else if (kind == EcsAlias) { |
|
index = &world->aliases; |
|
} else if (kind == EcsName) { |
|
ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); |
|
ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (evt == EcsOnSet) { |
|
index = flecs_id_name_index_ensure(world, pair); |
|
} else { |
|
index = flecs_id_name_index_get(world, pair); |
|
} |
|
} |
|
|
|
int i, count = it->count; |
|
|
|
for (i = 0; i < count; i ++) { |
|
EcsIdentifier *cur = &ptr[i]; |
|
uint64_t hash; |
|
ecs_size_t len; |
|
const char *name = cur->value; |
|
|
|
if (cur->index && cur->index != index) { |
|
/* If index doesn't match up, the value must have been copied from |
|
* another entity, so reset index & cached index hash */ |
|
cur->index = NULL; |
|
cur->index_hash = 0; |
|
} |
|
|
|
if (cur->value && (evt == EcsOnSet)) { |
|
len = cur->length = ecs_os_strlen(name); |
|
hash = cur->hash = flecs_hash(name, len); |
|
} else { |
|
len = cur->length = 0; |
|
hash = cur->hash = 0; |
|
cur->index = NULL; |
|
} |
|
|
|
if (index) { |
|
uint64_t index_hash = cur->index_hash; |
|
ecs_entity_t e = it->entities[i]; |
|
|
|
if (hash != index_hash) { |
|
if (index_hash) { |
|
flecs_name_index_remove(index, e, index_hash); |
|
} |
|
if (hash) { |
|
flecs_name_index_ensure(index, e, name, len, hash); |
|
cur->index_hash = hash; |
|
cur->index = index; |
|
} |
|
} else { |
|
/* Name didn't change, but the string could have been |
|
* reallocated. Make sure name index points to correct string */ |
|
flecs_name_index_update_name(index, e, hash, name); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
/* -- Poly component -- */ |
|
|
|
static ECS_COPY(EcsPoly, dst, src, { |
|
(void)dst; |
|
(void)src; |
|
ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); |
|
}) |
|
|
|
static ECS_MOVE(EcsPoly, dst, src, { |
|
if (dst->poly && (dst->poly != src->poly)) { |
|
ecs_poly_dtor_t *dtor = ecs_get_dtor(dst->poly); |
|
ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); |
|
dtor[0](dst->poly); |
|
} |
|
|
|
dst->poly = src->poly; |
|
src->poly = NULL; |
|
}) |
|
|
|
static ECS_DTOR(EcsPoly, ptr, { |
|
if (ptr->poly) { |
|
ecs_poly_dtor_t *dtor = ecs_get_dtor(ptr->poly); |
|
ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); |
|
dtor[0](ptr->poly); |
|
} |
|
}) |
|
|
|
|
|
/* -- Builtin triggers -- */ |
|
|
|
static |
|
void flecs_assert_relation_unused( |
|
ecs_world_t *world, |
|
ecs_entity_t rel, |
|
ecs_entity_t property) |
|
{ |
|
if (world->flags & (EcsWorldInit|EcsWorldFini)) { |
|
return; |
|
} |
|
|
|
ecs_vec_t *marked_ids = &world->store.marked_ids; |
|
int32_t i, count = ecs_vec_count(marked_ids); |
|
for (i = 0; i < count; i ++) { |
|
ecs_marked_id_t *mid = ecs_vec_get_t(marked_ids, ecs_marked_id_t, i); |
|
if (mid->id == ecs_pair(rel, EcsWildcard)) { |
|
/* If id is being cleaned up, no need to throw error as tables will |
|
* be cleaned up */ |
|
return; |
|
} |
|
} |
|
|
|
bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); |
|
if (property != EcsUnion) { |
|
in_use |= ecs_id_in_use(world, rel); |
|
} |
|
if (in_use) { |
|
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' for relationship '%s': already in use", |
|
p_str, r_str); |
|
|
|
ecs_os_free(r_str); |
|
ecs_os_free(p_str); |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
static |
|
bool flecs_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 flecs_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 flecs_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_id_record_ensure(world, e); |
|
changed |= flecs_set_id_flag(idr, flag); |
|
idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); |
|
do { |
|
changed |= flecs_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_id_record_get(world, e); |
|
if (idr) changed |= flecs_unset_id_flag(idr, not_flag); |
|
idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)); |
|
if (idr) { |
|
do { |
|
changed |= flecs_unset_id_flag(idr, not_flag); |
|
} while ((idr = idr->first.next)); |
|
} |
|
} |
|
|
|
if (changed) { |
|
flecs_assert_relation_unused(world, e, prop); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_register_final(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]; |
|
if (flecs_id_record_get(world, ecs_pair(EcsIsA, e)) != NULL) { |
|
char *e_str = ecs_get_fullpath(world, e); |
|
ecs_throw(ECS_ID_IN_USE, |
|
"cannot change property 'Final' for '%s': already inherited from", |
|
e_str); |
|
ecs_os_free(e_str); |
|
error: |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_register_on_delete(ecs_iter_t *it) { |
|
ecs_id_t id = ecs_field_id(it, 1); |
|
flecs_register_id_flag_for_relation(it, EcsOnDelete, |
|
ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), |
|
EcsIdOnDeleteMask, |
|
EcsEntityIsId); |
|
} |
|
|
|
static |
|
void flecs_register_on_delete_object(ecs_iter_t *it) { |
|
ecs_id_t id = ecs_field_id(it, 1); |
|
flecs_register_id_flag_for_relation(it, EcsOnDeleteTarget, |
|
ECS_ID_ON_DELETE_TARGET_FLAG(ECS_PAIR_SECOND(id)), |
|
EcsIdOnDeleteObjectMask, |
|
EcsEntityIsId); |
|
} |
|
|
|
static |
|
void flecs_register_traversable(ecs_iter_t *it) { |
|
flecs_register_id_flag_for_relation(it, EcsAcyclic, EcsIdTraversable, |
|
EcsIdTraversable, 0); |
|
} |
|
|
|
static |
|
void flecs_register_tag(ecs_iter_t *it) { |
|
flecs_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]; |
|
|
|
if (it->event == EcsOnAdd) { |
|
ecs_id_record_t *idr = flecs_id_record_get(world, |
|
ecs_pair(e, EcsWildcard)); |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
do { |
|
if (idr->type_info != NULL) { |
|
flecs_assert_relation_unused(world, e, EcsTag); |
|
} |
|
idr->type_info = NULL; |
|
} while ((idr = idr->first.next)); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_register_exclusive(ecs_iter_t *it) { |
|
flecs_register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive, |
|
EcsIdExclusive, 0); |
|
} |
|
|
|
static |
|
void flecs_register_dont_inherit(ecs_iter_t *it) { |
|
flecs_register_id_flag_for_relation(it, EcsDontInherit, |
|
EcsIdDontInherit, EcsIdDontInherit, 0); |
|
} |
|
|
|
static |
|
void flecs_register_always_override(ecs_iter_t *it) { |
|
flecs_register_id_flag_for_relation(it, EcsAlwaysOverride, |
|
EcsIdAlwaysOverride, EcsIdAlwaysOverride, 0); |
|
} |
|
|
|
static |
|
void flecs_register_with(ecs_iter_t *it) { |
|
flecs_register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0); |
|
} |
|
|
|
static |
|
void flecs_register_union(ecs_iter_t *it) { |
|
flecs_register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0); |
|
} |
|
|
|
static |
|
void flecs_register_slot_of(ecs_iter_t *it) { |
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_add_id(it->world, it->entities[i], EcsUnion); |
|
} |
|
} |
|
|
|
static |
|
void flecs_on_symmetric_add_remove(ecs_iter_t *it) { |
|
ecs_entity_t pair = ecs_field_id(it, 1); |
|
|
|
if (!ECS_HAS_ID_FLAG(pair, PAIR)) { |
|
/* If relationship was not added as a pair, there's nothing to do */ |
|
return; |
|
} |
|
|
|
ecs_world_t *world = it->world; |
|
ecs_entity_t rel = ECS_PAIR_FIRST(pair); |
|
ecs_entity_t obj = ecs_pair_second(world, pair); |
|
ecs_entity_t event = it->event; |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t subj = it->entities[i]; |
|
if (event == EcsOnAdd) { |
|
if (!ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { |
|
ecs_add_pair(it->world, obj, rel, subj); |
|
} |
|
} else { |
|
if (ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { |
|
ecs_remove_pair(it->world, obj, rel, subj); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_register_symmetric(ecs_iter_t *it) { |
|
ecs_world_t *world = it->real_world; |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t r = it->entities[i]; |
|
flecs_assert_relation_unused(world, r, EcsSymmetric); |
|
|
|
/* Create observer that adds the reverse relationship when R(X, Y) is |
|
* added, or remove the reverse relationship when R(X, Y) is removed. */ |
|
ecs_observer(world, { |
|
.entity = ecs_entity(world, {.add = {ecs_childof(r)}}), |
|
.filter.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, |
|
.callback = flecs_on_symmetric_add_remove, |
|
.events = {EcsOnAdd, EcsOnRemove} |
|
}); |
|
} |
|
} |
|
|
|
static |
|
void flecs_on_component(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsComponent *c = ecs_field(it, EcsComponent, 1); |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
|
|
uint32_t component_id = (uint32_t)e; /* Strip generation */ |
|
ecs_assert(component_id < ECS_MAX_COMPONENT_ID, ECS_OUT_OF_RANGE, |
|
"component id must be smaller than %u", ECS_MAX_COMPONENT_ID); |
|
(void)component_id; |
|
|
|
if (it->event == EcsOnSet) { |
|
if (flecs_type_info_init_id( |
|
world, e, c[i].size, c[i].alignment, NULL)) |
|
{ |
|
flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); |
|
} |
|
} else if (it->event == EcsOnRemove) { |
|
flecs_type_info_free(world, e); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_ensure_module_tag(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]; |
|
ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); |
|
if (parent) { |
|
ecs_add_id(world, parent, EcsModule); |
|
} |
|
} |
|
} |
|
|
|
/* -- Iterable mixins -- */ |
|
|
|
static |
|
void flecs_on_event_iterable_init( |
|
const ecs_world_t *world, |
|
const ecs_poly_t *poly, /* Observable */ |
|
ecs_iter_t *it, |
|
ecs_term_t *filter) |
|
{ |
|
ecs_iter_poly(world, poly, it, filter); |
|
it->event_id = filter->id; |
|
} |
|
|
|
/* -- Bootstrapping -- */ |
|
|
|
#define flecs_bootstrap_builtin_t(world, table, name)\ |
|
flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ |
|
ECS_ALIGNOF(name)) |
|
|
|
static |
|
void flecs_bootstrap_builtin( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_entity_t entity, |
|
const char *symbol, |
|
ecs_size_t size, |
|
ecs_size_t alignment) |
|
{ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_column_t *columns = table->data.columns; |
|
ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_record_t *record = flecs_entities_ensure(world, entity); |
|
record->table = table; |
|
|
|
int32_t index = flecs_table_append(world, table, entity, false, false); |
|
record->row = ECS_ROW_TO_RECORD(index, 0); |
|
|
|
EcsComponent *component = ecs_vec_first(&columns[0].data); |
|
component[index].size = size; |
|
component[index].alignment = alignment; |
|
|
|
const char *name = &symbol[3]; /* Strip 'Ecs' */ |
|
ecs_size_t symbol_length = ecs_os_strlen(symbol); |
|
ecs_size_t name_length = symbol_length - 3; |
|
|
|
EcsIdentifier *name_col = ecs_vec_first(&columns[1].data); |
|
uint64_t name_hash = flecs_hash(name, name_length); |
|
name_col[index].value = ecs_os_strdup(name); |
|
name_col[index].length = name_length; |
|
name_col[index].hash = name_hash; |
|
name_col[index].index_hash = 0; |
|
name_col[index].index = table->_->name_index; |
|
flecs_name_index_ensure( |
|
table->_->name_index, entity, name, name_length, name_hash); |
|
|
|
EcsIdentifier *symbol_col = ecs_vec_first(&columns[2].data); |
|
symbol_col[index].value = ecs_os_strdup(symbol); |
|
symbol_col[index].length = symbol_length; |
|
symbol_col[index].hash = flecs_hash(symbol, symbol_length); |
|
symbol_col[index].index_hash = 0; |
|
symbol_col[index].index = NULL; |
|
} |
|
|
|
/** Initialize component table. This table is manually constructed to bootstrap |
|
* flecs. After this function has been called, the builtin components can be |
|
* created. |
|
* The reason this table is constructed manually is because it requires the size |
|
* and alignment of the EcsComponent and EcsIdentifier components, which haven't |
|
* been created yet */ |
|
static |
|
ecs_table_t* flecs_bootstrap_component_table( |
|
ecs_world_t *world) |
|
{ |
|
/* 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 *idr = flecs_id_record_ensure(world, EcsChildOf); |
|
idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | |
|
EcsIdTraversable | EcsIdTag; |
|
|
|
/* Initialize id records cached on world */ |
|
world->idr_childof_wildcard = flecs_id_record_ensure(world, |
|
ecs_pair(EcsChildOf, EcsWildcard)); |
|
world->idr_childof_wildcard->flags |= EcsIdOnDeleteObjectDelete | |
|
EcsIdDontInherit | EcsIdTraversable | EcsIdTag | EcsIdExclusive; |
|
idr = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsWildcard)); |
|
idr->flags |= EcsIdDontInherit; |
|
world->idr_identifier_name = |
|
flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); |
|
world->idr_childof_0 = flecs_id_record_ensure(world, |
|
ecs_pair(EcsChildOf, 0)); |
|
|
|
ecs_id_t ids[] = { |
|
ecs_id(EcsComponent), |
|
EcsFinal, |
|
ecs_pair_t(EcsIdentifier, EcsName), |
|
ecs_pair_t(EcsIdentifier, EcsSymbol), |
|
ecs_pair(EcsChildOf, EcsFlecsCore), |
|
ecs_pair(EcsOnDelete, EcsPanic) |
|
}; |
|
|
|
ecs_type_t array = { |
|
.array = ids, |
|
.count = 6 |
|
}; |
|
|
|
ecs_table_t *result = flecs_table_find_or_create(world, &array); |
|
ecs_data_t *data = &result->data; |
|
|
|
/* Preallocate enough memory for initial components */ |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_vec_init_t(a, &data->entities, ecs_entity_t, EcsFirstUserComponentId); |
|
ecs_vec_init_t(a, &data->columns[0].data, EcsComponent, EcsFirstUserComponentId); |
|
ecs_vec_init_t(a, &data->columns[1].data, EcsIdentifier, EcsFirstUserComponentId); |
|
ecs_vec_init_t(a, &data->columns[2].data, EcsIdentifier, EcsFirstUserComponentId); |
|
|
|
return result; |
|
} |
|
|
|
static |
|
void flecs_bootstrap_entity( |
|
ecs_world_t *world, |
|
ecs_entity_t id, |
|
const char *name, |
|
ecs_entity_t parent) |
|
{ |
|
char symbol[256]; |
|
ecs_os_strcpy(symbol, "flecs.core."); |
|
ecs_os_strcat(symbol, name); |
|
|
|
ecs_ensure(world, id); |
|
ecs_add_pair(world, id, EcsChildOf, parent); |
|
ecs_set_name(world, id, name); |
|
ecs_set_symbol(world, id, symbol); |
|
|
|
ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!parent || parent == EcsFlecsCore) { |
|
ecs_assert(ecs_lookup_fullpath(world, name) == id, |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
void flecs_bootstrap( |
|
ecs_world_t *world) |
|
{ |
|
ecs_log_push(); |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
/* Ensure builtin ids are alive */ |
|
ecs_ensure(world, ecs_id(EcsComponent)); |
|
ecs_ensure(world, EcsFinal); |
|
ecs_ensure(world, ecs_id(EcsIdentifier)); |
|
ecs_ensure(world, EcsName); |
|
ecs_ensure(world, EcsSymbol); |
|
ecs_ensure(world, EcsAlias); |
|
ecs_ensure(world, EcsChildOf); |
|
ecs_ensure(world, EcsFlecs); |
|
ecs_ensure(world, EcsFlecsCore); |
|
ecs_ensure(world, EcsOnAdd); |
|
ecs_ensure(world, EcsOnRemove); |
|
ecs_ensure(world, EcsOnSet); |
|
ecs_ensure(world, EcsUnSet); |
|
ecs_ensure(world, EcsOnDelete); |
|
ecs_ensure(world, EcsPanic); |
|
ecs_ensure(world, EcsFlag); |
|
ecs_ensure(world, EcsIsA); |
|
ecs_ensure(world, EcsWildcard); |
|
ecs_ensure(world, EcsAny); |
|
ecs_ensure(world, EcsTag); |
|
|
|
/* Register type information for builtin components */ |
|
flecs_type_info_init(world, EcsComponent, { |
|
.ctor = ecs_default_ctor, |
|
.on_set = flecs_on_component, |
|
.on_remove = flecs_on_component |
|
}); |
|
|
|
flecs_type_info_init(world, EcsIdentifier, { |
|
.ctor = ecs_default_ctor, |
|
.dtor = ecs_dtor(EcsIdentifier), |
|
.copy = ecs_copy(EcsIdentifier), |
|
.move = ecs_move(EcsIdentifier), |
|
.on_set = ecs_on_set(EcsIdentifier), |
|
.on_remove = ecs_on_set(EcsIdentifier) |
|
}); |
|
|
|
flecs_type_info_init(world, EcsPoly, { |
|
.ctor = ecs_default_ctor, |
|
.copy = ecs_copy(EcsPoly), |
|
.move = ecs_move(EcsPoly), |
|
.dtor = ecs_dtor(EcsPoly) |
|
}); |
|
|
|
flecs_type_info_init(world, EcsIterable, { 0 }); |
|
flecs_type_info_init(world, EcsTarget, { 0 }); |
|
|
|
/* Create and cache often used id records on world */ |
|
flecs_init_id_records(world); |
|
|
|
/* Create table for builtin components. This table temporarily stores the |
|
* entities associated with builtin components, until they get moved to |
|
* other tables once properties are added (see below) */ |
|
ecs_table_t *table = flecs_bootstrap_component_table(world); |
|
assert(table != NULL); |
|
|
|
/* Bootstrap builtin components */ |
|
flecs_bootstrap_builtin_t(world, table, EcsIdentifier); |
|
flecs_bootstrap_builtin_t(world, table, EcsComponent); |
|
flecs_bootstrap_builtin_t(world, table, EcsIterable); |
|
flecs_bootstrap_builtin_t(world, table, EcsPoly); |
|
flecs_bootstrap_builtin_t(world, table, EcsTarget); |
|
|
|
/* Initialize default entity id range */ |
|
world->info.last_component_id = EcsFirstUserComponentId; |
|
flecs_entities_max_id(world) = EcsFirstUserEntityId; |
|
world->info.min_id = 0; |
|
world->info.max_id = 0; |
|
|
|
/* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */ |
|
ecs_set(world, EcsOnAdd, EcsIterable, { .init = flecs_on_event_iterable_init }); |
|
ecs_set(world, EcsOnSet, EcsIterable, { .init = flecs_on_event_iterable_init }); |
|
|
|
/* Register observer for tag property before adding EcsTag */ |
|
ecs_observer(world, { |
|
.entity = ecs_entity(world, {.add = { ecs_childof(EcsFlecsInternals)}}), |
|
.filter.terms[0] = { .id = EcsTag, .src.flags = EcsSelf }, |
|
.events = {EcsOnAdd, EcsOnRemove}, |
|
.callback = flecs_register_tag, |
|
.yield_existing = true |
|
}); |
|
|
|
/* Populate core module */ |
|
ecs_set_scope(world, EcsFlecsCore); |
|
|
|
flecs_bootstrap_tag(world, EcsName); |
|
flecs_bootstrap_tag(world, EcsSymbol); |
|
flecs_bootstrap_tag(world, EcsAlias); |
|
|
|
flecs_bootstrap_tag(world, EcsQuery); |
|
flecs_bootstrap_tag(world, EcsObserver); |
|
|
|
flecs_bootstrap_tag(world, EcsModule); |
|
flecs_bootstrap_tag(world, EcsPrivate); |
|
flecs_bootstrap_tag(world, EcsPrefab); |
|
flecs_bootstrap_tag(world, EcsSlotOf); |
|
flecs_bootstrap_tag(world, EcsDisabled); |
|
flecs_bootstrap_tag(world, EcsEmpty); |
|
|
|
/* Initialize builtin modules */ |
|
ecs_set_name(world, EcsFlecs, "flecs"); |
|
ecs_add_id(world, EcsFlecs, EcsModule); |
|
ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); |
|
|
|
ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); |
|
ecs_set_name(world, EcsFlecsCore, "core"); |
|
ecs_add_id(world, EcsFlecsCore, EcsModule); |
|
|
|
ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); |
|
ecs_set_name(world, EcsFlecsInternals, "internals"); |
|
ecs_add_id(world, EcsFlecsInternals, EcsModule); |
|
|
|
/* Self check */ |
|
ecs_record_t *r = flecs_entities_get(world, EcsFlecs); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(r->row & EcsEntityIsTraversable, ECS_INTERNAL_ERROR, NULL); |
|
(void)r; |
|
|
|
/* Initialize builtin entities */ |
|
flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsThis, "this", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); |
|
|
|
/* Component/relationship properties */ |
|
flecs_bootstrap_tag(world, EcsTransitive); |
|
flecs_bootstrap_tag(world, EcsReflexive); |
|
flecs_bootstrap_tag(world, EcsSymmetric); |
|
flecs_bootstrap_tag(world, EcsFinal); |
|
flecs_bootstrap_tag(world, EcsDontInherit); |
|
flecs_bootstrap_tag(world, EcsAlwaysOverride); |
|
flecs_bootstrap_tag(world, EcsTag); |
|
flecs_bootstrap_tag(world, EcsUnion); |
|
flecs_bootstrap_tag(world, EcsExclusive); |
|
flecs_bootstrap_tag(world, EcsAcyclic); |
|
flecs_bootstrap_tag(world, EcsTraversable); |
|
flecs_bootstrap_tag(world, EcsWith); |
|
flecs_bootstrap_tag(world, EcsOneOf); |
|
|
|
flecs_bootstrap_tag(world, EcsOnDelete); |
|
flecs_bootstrap_tag(world, EcsOnDeleteTarget); |
|
flecs_bootstrap_tag(world, EcsRemove); |
|
flecs_bootstrap_tag(world, EcsDelete); |
|
flecs_bootstrap_tag(world, EcsPanic); |
|
|
|
flecs_bootstrap_tag(world, EcsFlatten); |
|
flecs_bootstrap_tag(world, EcsDefaultChildComponent); |
|
|
|
/* Builtin predicates */ |
|
flecs_bootstrap_tag(world, EcsPredEq); |
|
flecs_bootstrap_tag(world, EcsPredMatch); |
|
flecs_bootstrap_tag(world, EcsPredLookup); |
|
flecs_bootstrap_tag(world, EcsScopeOpen); |
|
flecs_bootstrap_tag(world, EcsScopeClose); |
|
|
|
/* Builtin relationships */ |
|
flecs_bootstrap_tag(world, EcsIsA); |
|
flecs_bootstrap_tag(world, EcsChildOf); |
|
flecs_bootstrap_tag(world, EcsDependsOn); |
|
|
|
/* Builtin events */ |
|
flecs_bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); |
|
flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); |
|
|
|
/* Tag relationships (relationships that should never have data) */ |
|
ecs_add_id(world, EcsIsA, EcsTag); |
|
ecs_add_id(world, EcsChildOf, EcsTag); |
|
ecs_add_id(world, EcsSlotOf, EcsTag); |
|
ecs_add_id(world, EcsDependsOn, EcsTag); |
|
ecs_add_id(world, EcsFlatten, EcsTag); |
|
ecs_add_id(world, EcsDefaultChildComponent, EcsTag); |
|
ecs_add_id(world, EcsUnion, EcsTag); |
|
ecs_add_id(world, EcsFlag, EcsTag); |
|
ecs_add_id(world, EcsWith, EcsTag); |
|
|
|
/* Exclusive properties */ |
|
ecs_add_id(world, EcsChildOf, EcsExclusive); |
|
ecs_add_id(world, EcsOnDelete, EcsExclusive); |
|
ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); |
|
ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); |
|
|
|
/* Sync properties of ChildOf and Identifier with bootstrapped flags */ |
|
ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); |
|
ecs_add_id(world, EcsChildOf, EcsAcyclic); |
|
ecs_add_id(world, EcsChildOf, EcsTraversable); |
|
ecs_add_id(world, EcsChildOf, EcsDontInherit); |
|
ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit); |
|
|
|
/* Create triggers in internals scope */ |
|
ecs_set_scope(world, EcsFlecsInternals); |
|
|
|
/* Term used to also match prefabs */ |
|
ecs_term_t match_prefab = { |
|
.id = EcsPrefab, |
|
.oper = EcsOptional, |
|
.src.flags = EcsSelf |
|
}; |
|
|
|
/* Register observers for components/relationship properties. Most observers |
|
* set flags on an id record when a property is added to a component, which |
|
* allows for quick property testing in various operations. */ |
|
ecs_observer(world, { |
|
.filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab }, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_register_final |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = { |
|
{ .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, |
|
match_prefab |
|
}, |
|
.events = {EcsOnAdd, EcsOnRemove}, |
|
.callback = flecs_register_on_delete |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = { |
|
{ .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, |
|
match_prefab |
|
}, |
|
.events = {EcsOnAdd, EcsOnRemove}, |
|
.callback = flecs_register_on_delete_object |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = { |
|
{ .id = EcsTraversable, .src.flags = EcsSelf }, |
|
match_prefab |
|
}, |
|
.events = {EcsOnAdd, EcsOnRemove}, |
|
.callback = flecs_register_traversable |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, |
|
.events = {EcsOnAdd, EcsOnRemove}, |
|
.callback = flecs_register_exclusive |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_register_symmetric |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_register_dont_inherit |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = {{ .id = EcsAlwaysOverride, .src.flags = EcsSelf } }, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_register_always_override |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = { |
|
{ .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, |
|
match_prefab |
|
}, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_register_with |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab }, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_register_union |
|
}); |
|
|
|
/* Entities used as slot are marked as exclusive to ensure a slot can always |
|
* only point to a single entity. */ |
|
ecs_observer(world, { |
|
.filter.terms = { |
|
{ .id = ecs_pair(EcsSlotOf, EcsWildcard), .src.flags = EcsSelf }, |
|
match_prefab |
|
}, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_register_slot_of |
|
}); |
|
|
|
/* Define observer to make sure that adding a module to a child entity also |
|
* adds it to the parent. */ |
|
ecs_observer(world, { |
|
.filter.terms = {{ .id = EcsModule, .src.flags = EcsSelf }, match_prefab}, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_ensure_module_tag |
|
}); |
|
|
|
/* Set scope back to flecs core */ |
|
ecs_set_scope(world, EcsFlecsCore); |
|
|
|
/* Traversable relationships are always acyclic */ |
|
ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); |
|
|
|
/* Transitive relationships are always Traversable */ |
|
ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); |
|
|
|
/* DontInherit components */ |
|
ecs_add_id(world, EcsPrefab, EcsDontInherit); |
|
ecs_add_id(world, ecs_id(EcsComponent), EcsDontInherit); |
|
ecs_add_id(world, EcsOnDelete, EcsDontInherit); |
|
|
|
/* Acyclic/Traversable components */ |
|
ecs_add_id(world, EcsIsA, EcsTraversable); |
|
ecs_add_id(world, EcsDependsOn, EcsTraversable); |
|
ecs_add_id(world, EcsWith, EcsAcyclic); |
|
|
|
/* Transitive relationships */ |
|
ecs_add_id(world, EcsIsA, EcsTransitive); |
|
ecs_add_id(world, EcsIsA, EcsReflexive); |
|
|
|
/* Exclusive properties */ |
|
ecs_add_id(world, EcsSlotOf, EcsExclusive); |
|
ecs_add_id(world, EcsOneOf, EcsExclusive); |
|
ecs_add_id(world, EcsFlatten, EcsExclusive); |
|
|
|
/* Private properties */ |
|
ecs_add_id(world, ecs_id(EcsPoly), EcsPrivate); |
|
ecs_add_id(world, ecs_id(EcsIdentifier), EcsPrivate); |
|
ecs_add_id(world, EcsChildOf, EcsPrivate); |
|
ecs_add_id(world, EcsIsA, EcsPrivate); |
|
|
|
/* Run bootstrap functions for other parts of the code */ |
|
flecs_bootstrap_hierarchy(world); |
|
|
|
ecs_set_scope(world, 0); |
|
|
|
ecs_set_name_prefix(world, NULL); |
|
|
|
ecs_assert(world->idr_childof_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(world->idr_isa_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_log_pop(); |
|
} |
|
|
|
/** |
|
* @file entity.c |
|
* @brief Entity API. |
|
* |
|
* This file contains the implementation for the entity API, which includes |
|
* creating/deleting entities, adding/removing/setting components, instantiating |
|
* prefabs, and several other APIs for retrieving entity data. |
|
* |
|
* The file also contains the implementation of the command buffer, which is |
|
* located here so it can call functions private to the compilation unit. |
|
*/ |
|
|
|
#include <ctype.h> |
|
|
|
static |
|
const ecs_entity_t* flecs_bulk_new( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
const ecs_entity_t *entities, |
|
ecs_type_t *component_ids, |
|
int32_t count, |
|
void **c_info, |
|
bool move, |
|
int32_t *row_out, |
|
ecs_table_diff_t *diff); |
|
|
|
typedef struct { |
|
const ecs_type_info_t *ti; |
|
void *ptr; |
|
} flecs_component_ptr_t; |
|
|
|
static |
|
flecs_component_ptr_t flecs_get_component_w_index( |
|
ecs_table_t *table, |
|
int32_t column_index, |
|
int32_t row) |
|
{ |
|
ecs_check(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); |
|
ecs_column_t *column = &table->data.columns[column_index]; |
|
return (flecs_component_ptr_t){ |
|
.ti = column->ti, |
|
.ptr = ecs_vec_get(&column->data, column->size, row) |
|
}; |
|
error: |
|
return (flecs_component_ptr_t){0}; |
|
} |
|
|
|
static |
|
flecs_component_ptr_t flecs_get_component_ptr( |
|
const ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t row, |
|
ecs_id_t id) |
|
{ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_table_record_t *tr = flecs_table_record_get(world, table, id); |
|
if (!tr || (tr->column == -1)) { |
|
ecs_check(tr == NULL, ECS_NOT_A_COMPONENT, NULL); |
|
return (flecs_component_ptr_t){0}; |
|
} |
|
|
|
return flecs_get_component_w_index(table, tr->column, row); |
|
error: |
|
return (flecs_component_ptr_t){0}; |
|
} |
|
|
|
static |
|
void* flecs_get_component( |
|
const ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t row, |
|
ecs_id_t id) |
|
{ |
|
return flecs_get_component_ptr(world, table, row, id).ptr; |
|
} |
|
|
|
void* flecs_get_base_component( |
|
const ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t id, |
|
ecs_id_record_t *table_index, |
|
int32_t recur_depth) |
|
{ |
|
/* Cycle detected in IsA relationship */ |
|
ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Table (and thus entity) does not have component, look for base */ |
|
if (!(table->flags & EcsTableHasIsA)) { |
|
return NULL; |
|
} |
|
|
|
/* Exclude Name */ |
|
if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { |
|
return NULL; |
|
} |
|
|
|
/* Table should always be in the table index for (IsA, *), otherwise the |
|
* HasBase flag should not have been set */ |
|
ecs_table_record_t *tr_isa = flecs_id_record_get_table( |
|
world->idr_isa_wildcard, table); |
|
ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_type_t type = table->type; |
|
ecs_id_t *ids = type.array; |
|
int32_t i = tr_isa->index, end = tr_isa->count + tr_isa->index; |
|
void *ptr = NULL; |
|
|
|
do { |
|
ecs_id_t pair = ids[i ++]; |
|
ecs_entity_t base = ecs_pair_second(world, pair); |
|
|
|
ecs_record_t *r = flecs_entities_get(world, base); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
table = r->table; |
|
if (!table) { |
|
continue; |
|
} |
|
|
|
const ecs_table_record_t *tr = |
|
flecs_id_record_get_table(table_index, table); |
|
if (!tr || tr->column == -1) { |
|
ptr = flecs_get_base_component(world, table, id, table_index, |
|
recur_depth + 1); |
|
} else { |
|
int32_t row = ECS_RECORD_TO_ROW(r->row); |
|
ptr = flecs_get_component_w_index(table, tr->column, row).ptr; |
|
} |
|
} while (!ptr && (i < end)); |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
void flecs_instantiate_slot( |
|
ecs_world_t *world, |
|
ecs_entity_t base, |
|
ecs_entity_t instance, |
|
ecs_entity_t slot_of, |
|
ecs_entity_t slot, |
|
ecs_entity_t child) |
|
{ |
|
if (base == slot_of) { |
|
/* Instance inherits from slot_of, add slot to instance */ |
|
ecs_add_pair(world, instance, slot, child); |
|
} else { |
|
/* Slot is registered for other prefab, travel hierarchy |
|
* upwards to find instance that inherits from slot_of */ |
|
ecs_entity_t parent = instance; |
|
int32_t depth = 0; |
|
do { |
|
if (ecs_has_pair(world, parent, EcsIsA, slot_of)) { |
|
const char *name = ecs_get_name(world, slot); |
|
if (name == NULL) { |
|
char *slot_of_str = ecs_get_fullpath(world, slot_of); |
|
ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " |
|
"slot (slots must be named)", slot_of_str); |
|
ecs_os_free(slot_of_str); |
|
return; |
|
} |
|
|
|
/* The 'slot' variable is currently pointing to a child (or |
|
* grandchild) of the current base. Find the original slot by |
|
* looking it up under the prefab it was registered. */ |
|
if (depth == 0) { |
|
/* If the current instance is an instance of slot_of, just |
|
* lookup the slot by name, which is faster than having to |
|
* create a relative path. */ |
|
slot = ecs_lookup_child(world, slot_of, name); |
|
} else { |
|
/* If the slot is more than one level away from the slot_of |
|
* parent, use a relative path to find the slot */ |
|
char *path = ecs_get_path_w_sep(world, parent, child, ".", |
|
NULL); |
|
slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", |
|
NULL, false); |
|
ecs_os_free(path); |
|
} |
|
|
|
if (slot == 0) { |
|
char *slot_of_str = ecs_get_fullpath(world, slot_of); |
|
char *slot_str = ecs_get_fullpath(world, slot); |
|
ecs_throw(ECS_INVALID_OPERATION, |
|
"'%s' is not in hierarchy for slot '%s'", |
|
slot_of_str, slot_str); |
|
ecs_os_free(slot_of_str); |
|
ecs_os_free(slot_str); |
|
} |
|
|
|
ecs_add_pair(world, parent, slot, child); |
|
break; |
|
} |
|
|
|
depth ++; |
|
} while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); |
|
|
|
if (parent == 0) { |
|
char *slot_of_str = ecs_get_fullpath(world, slot_of); |
|
char *slot_str = ecs_get_fullpath(world, slot); |
|
ecs_throw(ECS_INVALID_OPERATION, |
|
"'%s' is not in hierarchy for slot '%s'", |
|
slot_of_str, slot_str); |
|
ecs_os_free(slot_of_str); |
|
ecs_os_free(slot_str); |
|
} |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
static |
|
ecs_table_t* flecs_find_table_add( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t id, |
|
ecs_table_diff_builder_t *diff) |
|
{ |
|
ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; |
|
table = flecs_table_traverse_add(world, table, &id, &temp_diff); |
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_table_diff_build_append_table(world, diff, &temp_diff); |
|
return table; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
ecs_table_t* flecs_find_table_remove( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t id, |
|
ecs_table_diff_builder_t *diff) |
|
{ |
|
ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; |
|
table = flecs_table_traverse_remove(world, table, &id, &temp_diff); |
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_table_diff_build_append_table(world, diff, &temp_diff); |
|
return table; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
void flecs_instantiate_children( |
|
ecs_world_t *world, |
|
ecs_entity_t base, |
|
ecs_table_t *table, |
|
int32_t row, |
|
int32_t count, |
|
ecs_table_t *child_table) |
|
{ |
|
if (!ecs_table_count(child_table)) { |
|
return; |
|
} |
|
|
|
ecs_type_t type = child_table->type; |
|
ecs_data_t *child_data = &child_table->data; |
|
|
|
ecs_entity_t slot_of = 0; |
|
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_type_t components = { |
|
.array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) |
|
}; |
|
|
|
void **component_data = ecs_os_alloca_n(void*, type_count + 1); |
|
|
|
/* Copy in component identifiers. Find the base index in the component |
|
* array, since we'll need this to replace the base with the instance id */ |
|
int j, i, childof_base_index = -1, pos = 0; |
|
for (i = 0; i < type_count; i ++) { |
|
ecs_id_t id = ids[i]; |
|
|
|
/* If id has DontInherit flag don't inherit it, except for the name |
|
* and ChildOf pairs. The name is preserved so applications can lookup |
|
* the instantiated children by name. The ChildOf pair is replaced later |
|
* with the instance parent. */ |
|
if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && |
|
ECS_PAIR_FIRST(id) != EcsChildOf) |
|
{ |
|
if (id == EcsUnion) { |
|
/* This should eventually be handled by the DontInherit property |
|
* but right now there is no way to selectively apply it to |
|
* EcsUnion itself: it would also apply to (Union, *) pairs, |
|
* which would make all union relationships uninheritable. |
|
* |
|
* The reason this is explicitly skipped is so that slot |
|
* instances don't all end up with the Union property. */ |
|
continue; |
|
} |
|
ecs_table_record_t *tr = &child_table->_->records[i]; |
|
ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; |
|
if (idr->flags & EcsIdDontInherit) { |
|
continue; |
|
} |
|
} |
|
|
|
/* If child is a slot, keep track of which parent to add it to, but |
|
* don't add slot relationship to child of instance. If this is a child |
|
* of a prefab, keep the SlotOf relationship intact. */ |
|
if (!(table->flags & EcsTableIsPrefab)) { |
|
if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { |
|
ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); |
|
slot_of = ecs_pair_second(world, id); |
|
continue; |
|
} |
|
} |
|
|
|
/* Keep track of the element that creates the ChildOf relationship with |
|
* the prefab parent. We need to replace this element to make sure the |
|
* created children point to the instance and not the prefab */ |
|
if (ECS_HAS_RELATION(id, EcsChildOf) && |
|
(ECS_PAIR_SECOND(id) == (uint32_t)base)) { |
|
childof_base_index = pos; |
|
} |
|
|
|
int32_t storage_index = ecs_table_type_to_column_index(child_table, i); |
|
if (storage_index != -1) { |
|
ecs_vec_t *column = &child_data->columns[storage_index].data; |
|
component_data[pos] = ecs_vec_first(column); |
|
} else { |
|
component_data[pos] = NULL; |
|
} |
|
|
|
components.array[pos] = id; |
|
pos ++; |
|
} |
|
|
|
/* Table must contain children of base */ |
|
ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* If children are added to a prefab, make sure they are prefabs too */ |
|
if (table->flags & EcsTableIsPrefab) { |
|
components.array[pos] = EcsPrefab; |
|
component_data[pos] = NULL; |
|
pos ++; |
|
} |
|
|
|
components.count = pos; |
|
|
|
/* Instantiate the prefab child table for each new instance */ |
|
ecs_entity_t *instances = ecs_vec_first(&table->data.entities); |
|
int32_t child_count = ecs_vec_count(&child_data->entities); |
|
bool has_union = child_table->flags & EcsTableHasUnion; |
|
|
|
for (i = row; i < count + row; i ++) { |
|
ecs_entity_t instance = instances[i]; |
|
ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; |
|
flecs_table_diff_builder_init(world, &diff); |
|
ecs_table_t *i_table = NULL; |
|
|
|
/* Replace ChildOf element in the component array with instance id */ |
|
components.array[childof_base_index] = ecs_pair(EcsChildOf, instance); |
|
|
|
/* Find or create table */ |
|
for (j = 0; j < components.count; j ++) { |
|
i_table = flecs_find_table_add( |
|
world, i_table, components.array[j], &diff); |
|
} |
|
|
|
ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
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_vec_first(&child_data->entities); |
|
|
|
#ifdef FLECS_DEBUG |
|
for (j = 0; j < child_count; j ++) { |
|
ecs_entity_t child = children[j]; |
|
ecs_check(child != instance, ECS_INVALID_PARAMETER, NULL); |
|
} |
|
#else |
|
/* Bit of boilerplate to ensure that we don't get warnings about the |
|
* error label not being used. */ |
|
ecs_check(true, ECS_INVALID_OPERATION, NULL); |
|
#endif |
|
|
|
/* Create children */ |
|
int32_t child_row; |
|
ecs_table_diff_t table_diff; |
|
flecs_table_diff_build_noalloc(&diff, &table_diff); |
|
const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, |
|
&components, child_count, component_data, false, &child_row, |
|
&table_diff); |
|
flecs_table_diff_builder_fini(world, &diff); |
|
|
|
/* If children have union relationships, initialize */ |
|
if (has_union) { |
|
ecs_table__t *meta = child_table->_; |
|
ecs_assert(meta != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(i_table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t u, u_count = meta->sw_count; |
|
for (u = 0; u < u_count; u ++) { |
|
ecs_switch_t *src_sw = &meta->sw_columns[i]; |
|
ecs_switch_t *dst_sw = &i_table->_->sw_columns[i]; |
|
ecs_vec_t *v_src_values = flecs_switch_values(src_sw); |
|
ecs_vec_t *v_dst_values = flecs_switch_values(dst_sw); |
|
uint64_t *src_values = ecs_vec_first(v_src_values); |
|
uint64_t *dst_values = ecs_vec_first(v_dst_values); |
|
for (j = 0; j < child_count; j ++) { |
|
dst_values[j] = src_values[j]; |
|
} |
|
} |
|
} |
|
|
|
/* If children are slots, add slot relationships to parent */ |
|
if (slot_of) { |
|
for (j = 0; j < child_count; j ++) { |
|
ecs_entity_t child = children[j]; |
|
ecs_entity_t i_child = i_children[j]; |
|
flecs_instantiate_slot(world, base, instance, slot_of, |
|
child, i_child); |
|
} |
|
} |
|
|
|
/* If prefab child table has children itself, recursively instantiate */ |
|
for (j = 0; j < child_count; j ++) { |
|
ecs_entity_t child = children[j]; |
|
flecs_instantiate(world, child, i_table, child_row + j, 1); |
|
} |
|
} |
|
error: |
|
return; |
|
} |
|
|
|
void flecs_instantiate( |
|
ecs_world_t *world, |
|
ecs_entity_t base, |
|
ecs_table_t *table, |
|
int32_t row, |
|
int32_t count) |
|
{ |
|
ecs_record_t *record = flecs_entities_get_any(world, base); |
|
ecs_table_t *base_table = record->table; |
|
if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { |
|
/* Don't instantiate children from base entities that aren't prefabs */ |
|
return; |
|
} |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); |
|
ecs_table_cache_iter_t it; |
|
if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { |
|
const ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
flecs_instantiate_children( |
|
world, base, table, row, count, tr->hdr.table); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_set_union( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t row, |
|
int32_t count, |
|
const ecs_type_t *ids) |
|
{ |
|
ecs_id_t *array = ids->array; |
|
int32_t i, id_count = ids->count; |
|
|
|
for (i = 0; i < id_count; i ++) { |
|
ecs_id_t id = array[i]; |
|
|
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
ecs_id_record_t *idr = flecs_id_record_get(world, |
|
ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); |
|
if (!idr) { |
|
continue; |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t column = tr->index - table->_->sw_offset; |
|
ecs_switch_t *sw = &table->_->sw_columns[column]; |
|
ecs_entity_t union_case = 0; |
|
union_case = ecs_pair_second(world, id); |
|
|
|
int32_t r; |
|
for (r = 0; r < count; r ++) { |
|
flecs_switch_set(sw, row + r, union_case); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_notify_on_add( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_table_t *other_table, |
|
int32_t row, |
|
int32_t count, |
|
const ecs_type_t *added, |
|
ecs_flags32_t flags) |
|
{ |
|
ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (added->count) { |
|
ecs_flags32_t table_flags = table->flags; |
|
|
|
if (table_flags & EcsTableHasUnion) { |
|
flecs_set_union(world, table, row, count, added); |
|
} |
|
|
|
if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|EcsTableHasTraversable)) { |
|
flecs_emit(world, world, &(ecs_event_desc_t){ |
|
.event = EcsOnAdd, |
|
.ids = added, |
|
.table = table, |
|
.other_table = other_table, |
|
.offset = row, |
|
.count = count, |
|
.observable = world, |
|
.flags = flags |
|
}); |
|
} |
|
} |
|
} |
|
|
|
void flecs_notify_on_remove( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_table_t *other_table, |
|
int32_t row, |
|
int32_t count, |
|
const ecs_type_t *removed) |
|
{ |
|
ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (removed->count && (table->flags & |
|
(EcsTableHasOnRemove|EcsTableHasUnSet|EcsTableHasIsA|EcsTableHasTraversable))) |
|
{ |
|
flecs_emit(world, world, &(ecs_event_desc_t) { |
|
.event = EcsOnRemove, |
|
.ids = removed, |
|
.table = table, |
|
.other_table = other_table, |
|
.offset = row, |
|
.count = count, |
|
.observable = world |
|
}); |
|
} |
|
} |
|
|
|
static |
|
void flecs_update_name_index( |
|
ecs_world_t *world, |
|
ecs_table_t *src, |
|
ecs_table_t *dst, |
|
int32_t offset, |
|
int32_t count) |
|
{ |
|
ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!(dst->flags & EcsTableHasName)) { |
|
/* If destination table doesn't have a name, we don't need to update the |
|
* name index. Even if the src table had a name, the on_remove hook for |
|
* EcsIdentifier will remove the entity from the index. */ |
|
return; |
|
} |
|
|
|
ecs_hashmap_t *src_index = src->_->name_index; |
|
ecs_hashmap_t *dst_index = dst->_->name_index; |
|
if ((src_index == dst_index) || (!src_index && !dst_index)) { |
|
/* If the name index didn't change, the entity still has the same parent |
|
* so nothing needs to be done. */ |
|
return; |
|
} |
|
|
|
EcsIdentifier *names = ecs_table_get_pair(world, |
|
dst, EcsIdentifier, EcsName, offset); |
|
ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t i; |
|
ecs_entity_t *entities = ecs_vec_get_t( |
|
&dst->data.entities, ecs_entity_t, offset); |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = entities[i]; |
|
EcsIdentifier *name = &names[i]; |
|
|
|
uint64_t index_hash = name->index_hash; |
|
if (index_hash) { |
|
flecs_name_index_remove(src_index, e, index_hash); |
|
} |
|
const char *name_str = name->value; |
|
if (name_str) { |
|
ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_name_index_ensure( |
|
dst_index, e, name_str, name->length, name->hash); |
|
name->index = dst_index; |
|
} |
|
} |
|
} |
|
|
|
static |
|
ecs_record_t* flecs_new_entity( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_record_t *record, |
|
ecs_table_t *table, |
|
ecs_table_diff_t *diff, |
|
bool ctor, |
|
ecs_flags32_t evt_flags) |
|
{ |
|
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t row = flecs_table_append(world, table, entity, ctor, true); |
|
record->table = table; |
|
record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); |
|
|
|
ecs_assert(ecs_vec_count(&table->data.entities) > row, |
|
ECS_INTERNAL_ERROR, NULL); |
|
flecs_notify_on_add(world, table, NULL, row, 1, &diff->added, evt_flags); |
|
|
|
return record; |
|
} |
|
|
|
static |
|
void flecs_move_entity( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_record_t *record, |
|
ecs_table_t *dst_table, |
|
ecs_table_diff_t *diff, |
|
bool ctor, |
|
ecs_flags32_t evt_flags) |
|
{ |
|
ecs_table_t *src_table = record->table; |
|
int32_t src_row = ECS_RECORD_TO_ROW(record->row); |
|
|
|
ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(src_table->type.count > 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ecs_vec_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 == flecs_entities_get(world, entity), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Append new row to destination table */ |
|
int32_t dst_row = flecs_table_append(world, dst_table, entity, |
|
false, false); |
|
|
|
/* Invoke remove actions for removed components */ |
|
flecs_notify_on_remove( |
|
world, src_table, dst_table, src_row, 1, &diff->removed); |
|
|
|
/* Copy entity & components from src_table to dst_table */ |
|
flecs_table_move(world, entity, entity, dst_table, dst_row, |
|
src_table, src_row, ctor); |
|
|
|
/* 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_row, false); |
|
flecs_notify_on_add( |
|
world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags); |
|
|
|
flecs_update_name_index(world, src_table, dst_table, dst_row, 1); |
|
|
|
error: |
|
return; |
|
} |
|
|
|
static |
|
void flecs_delete_entity( |
|
ecs_world_t *world, |
|
ecs_record_t *record, |
|
ecs_table_diff_t *diff) |
|
{ |
|
ecs_table_t *table = record->table; |
|
int32_t row = ECS_RECORD_TO_ROW(record->row); |
|
|
|
/* Invoke remove actions before deleting */ |
|
flecs_notify_on_remove(world, table, NULL, row, 1, &diff->removed); |
|
flecs_table_delete(world, table, row, true); |
|
} |
|
|
|
/* Updating component monitors is a relatively expensive operation that only |
|
* happens for entities that are monitored. The approach balances the amount of |
|
* processing between the operation on the entity vs the amount of work that |
|
* needs to be done to rematch queries, as a simple brute force approach does |
|
* not scale when there are many tables / queries. Therefore we need to do a bit |
|
* of bookkeeping that is more intelligent than simply flipping a flag */ |
|
static |
|
void flecs_update_component_monitor_w_array( |
|
ecs_world_t *world, |
|
ecs_type_t *ids) |
|
{ |
|
if (!ids) { |
|
return; |
|
} |
|
|
|
int i; |
|
for (i = 0; i < ids->count; i ++) { |
|
ecs_entity_t id = ids->array[i]; |
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
flecs_monitor_mark_dirty(world, |
|
ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); |
|
} |
|
|
|
flecs_monitor_mark_dirty(world, id); |
|
} |
|
} |
|
|
|
static |
|
void flecs_update_component_monitors( |
|
ecs_world_t *world, |
|
ecs_type_t *added, |
|
ecs_type_t *removed) |
|
{ |
|
flecs_update_component_monitor_w_array(world, added); |
|
flecs_update_component_monitor_w_array(world, removed); |
|
} |
|
|
|
static |
|
void flecs_commit( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_record_t *record, |
|
ecs_table_t *dst_table, |
|
ecs_table_diff_t *diff, |
|
bool construct, |
|
ecs_flags32_t evt_flags) |
|
{ |
|
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); |
|
flecs_journal_begin(world, EcsJournalMove, entity, |
|
&diff->added, &diff->removed); |
|
|
|
ecs_table_t *src_table = NULL; |
|
int is_trav = 0; |
|
if (record) { |
|
src_table = record->table; |
|
is_trav = (record->row & EcsEntityIsTraversable) != 0; |
|
} |
|
|
|
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 union relationship could have changed. */ |
|
if (src_table) { |
|
flecs_notify_on_add(world, src_table, src_table, |
|
ECS_RECORD_TO_ROW(record->row), 1, &diff->added, evt_flags); |
|
} |
|
flecs_journal_end(); |
|
return; |
|
} |
|
|
|
if (src_table) { |
|
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
flecs_table_traversable_add(dst_table, is_trav); |
|
|
|
if (dst_table->type.count) { |
|
flecs_move_entity(world, entity, record, dst_table, diff, |
|
construct, evt_flags); |
|
} else { |
|
flecs_delete_entity(world, record, diff); |
|
record->table = NULL; |
|
} |
|
|
|
flecs_table_traversable_add(src_table, -is_trav); |
|
} else { |
|
flecs_table_traversable_add(dst_table, is_trav); |
|
if (dst_table->type.count) { |
|
flecs_new_entity(world, entity, record, dst_table, diff, |
|
construct, evt_flags); |
|
} |
|
} |
|
|
|
/* If the entity is being watched, it is being monitored for changes and |
|
* requires rematching systems when components are added or removed. This |
|
* 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 (is_trav) { |
|
flecs_update_component_monitors(world, &diff->added, &diff->removed); |
|
} |
|
|
|
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, |
|
ECS_OUT_OF_RANGE, 0); |
|
} |
|
|
|
error: |
|
flecs_journal_end(); |
|
return; |
|
} |
|
|
|
static |
|
const ecs_entity_t* flecs_bulk_new( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
const ecs_entity_t *entities, |
|
ecs_type_t *component_ids, |
|
int32_t count, |
|
void **component_data, |
|
bool is_move, |
|
int32_t *row_out, |
|
ecs_table_diff_t *diff) |
|
{ |
|
int32_t sparse_count = 0; |
|
if (!entities) { |
|
sparse_count = flecs_entities_count(world); |
|
entities = flecs_entities_new_ids(world, count); |
|
} |
|
|
|
if (!table) { |
|
return entities; |
|
} |
|
|
|
ecs_type_t type = table->type; |
|
if (!type.count) { |
|
return entities; |
|
} |
|
|
|
ecs_type_t component_array = { 0 }; |
|
if (!component_ids) { |
|
component_ids = &component_array; |
|
component_array.array = type.array; |
|
component_array.count = type.count; |
|
} |
|
|
|
ecs_data_t *data = &table->data; |
|
int32_t row = flecs_table_appendn(world, table, data, count, entities); |
|
|
|
/* Update entity index. */ |
|
int i; |
|
for (i = 0; i < count; i ++) { |
|
ecs_record_t *r = flecs_entities_get(world, entities[i]); |
|
r->table = table; |
|
r->row = ECS_ROW_TO_RECORD(row + i, 0); |
|
} |
|
|
|
flecs_defer_begin(world, &world->stages[0]); |
|
flecs_notify_on_add(world, table, NULL, row, count, &diff->added, |
|
(component_data == NULL) ? 0 : EcsEventNoOnSet); |
|
|
|
if (component_data) { |
|
int32_t c_i; |
|
for (c_i = 0; c_i < component_ids->count; c_i ++) { |
|
void *src_ptr = component_data[c_i]; |
|
if (!src_ptr) { |
|
continue; |
|
} |
|
|
|
/* Find component in storage type */ |
|
ecs_entity_t id = component_ids->array[c_i]; |
|
const ecs_table_record_t *tr = flecs_table_record_get( |
|
world, table, id); |
|
ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, |
|
"id is not a component"); |
|
ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, |
|
"id is not a component"); |
|
ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, |
|
"ids cannot be wildcards"); |
|
|
|
int32_t index = tr->column; |
|
ecs_column_t *column = &table->data.columns[index]; |
|
ecs_type_info_t *ti = column->ti; |
|
int32_t size = column->size; |
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); |
|
void *ptr = ecs_vec_get(&column->data, size, row); |
|
|
|
ecs_copy_t copy; |
|
ecs_move_t move; |
|
if (is_move && (move = ti->hooks.move)) { |
|
move(ptr, src_ptr, count, ti); |
|
} else if (!is_move && (copy = ti->hooks.copy)) { |
|
copy(ptr, src_ptr, count, ti); |
|
} else { |
|
ecs_os_memcpy(ptr, src_ptr, size * count); |
|
} |
|
}; |
|
|
|
int32_t j, storage_count = table->column_count; |
|
for (j = 0; j < storage_count; j ++) { |
|
ecs_type_t set_type = { |
|
.array = &table->data.columns[j].id, |
|
.count = 1 |
|
}; |
|
flecs_notify_on_set(world, table, row, count, &set_type, true); |
|
} |
|
} |
|
|
|
flecs_defer_end(world, &world->stages[0]); |
|
|
|
if (row_out) { |
|
*row_out = row; |
|
} |
|
|
|
if (sparse_count) { |
|
entities = flecs_entities_ids(world); |
|
return &entities[sparse_count]; |
|
} else { |
|
return entities; |
|
} |
|
} |
|
|
|
static |
|
void flecs_add_id_w_record( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_record_t *record, |
|
ecs_id_t id, |
|
bool construct) |
|
{ |
|
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_t *src_table = record->table; |
|
ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; |
|
ecs_table_t *dst_table = flecs_table_traverse_add( |
|
world, src_table, &id, &diff); |
|
flecs_commit(world, entity, record, dst_table, &diff, construct, |
|
EcsEventNoOnSet); /* No OnSet, this function is only called from |
|
* functions that are about to set the component. */ |
|
} |
|
|
|
static |
|
void flecs_add_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
if (flecs_defer_add(stage, entity, id)) { |
|
return; |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; |
|
ecs_table_t *src_table = r->table; |
|
ecs_table_t *dst_table = flecs_table_traverse_add( |
|
world, src_table, &id, &diff); |
|
|
|
flecs_commit(world, entity, r, dst_table, &diff, true, 0); |
|
|
|
flecs_defer_end(world, stage); |
|
} |
|
|
|
static |
|
void flecs_remove_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
if (flecs_defer_remove(stage, entity, id)) { |
|
return; |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_t *src_table = r->table; |
|
ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; |
|
ecs_table_t *dst_table = flecs_table_traverse_remove( |
|
world, src_table, &id, &diff); |
|
|
|
flecs_commit(world, entity, r, dst_table, &diff, true, 0); |
|
|
|
flecs_defer_end(world, stage); |
|
} |
|
|
|
static |
|
flecs_component_ptr_t flecs_get_mut( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t id, |
|
ecs_record_t *r) |
|
{ |
|
flecs_component_ptr_t dst = {0}; |
|
|
|
ecs_poly_assert(world, ecs_world_t); |
|
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_ID_FLAG(id, PAIR), ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (r->table) { |
|
dst = flecs_get_component_ptr( |
|
world, r->table, ECS_RECORD_TO_ROW(r->row), id); |
|
if (dst.ptr) { |
|
return dst; |
|
} |
|
} |
|
|
|
/* If entity didn't have component yet, add it */ |
|
flecs_add_id_w_record(world, entity, r, id, true); |
|
|
|
/* Flush commands so the pointer we're fetching is stable */ |
|
flecs_defer_end(world, &world->stages[0]); |
|
flecs_defer_begin(world, &world->stages[0]); |
|
|
|
ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(r->table->column_count != 0, ECS_INTERNAL_ERROR, NULL); |
|
dst = flecs_get_component_ptr( |
|
world, r->table, ECS_RECORD_TO_ROW(r->row), id); |
|
error: |
|
return dst; |
|
} |
|
|
|
void flecs_invoke_hook( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t count, |
|
int32_t row, |
|
ecs_entity_t *entities, |
|
void *ptr, |
|
ecs_id_t id, |
|
const ecs_type_info_t *ti, |
|
ecs_entity_t event, |
|
ecs_iter_action_t hook) |
|
{ |
|
int32_t defer = world->stages[0].defer; |
|
if (defer < 0) { |
|
world->stages[0].defer *= -1; |
|
} |
|
|
|
ecs_iter_t it = { .field_count = 1}; |
|
it.entities = entities; |
|
|
|
flecs_iter_init(world, &it, flecs_iter_cache_all); |
|
it.world = world; |
|
it.real_world = world; |
|
it.table = table; |
|
it.ptrs[0] = ptr; |
|
it.sizes = ECS_CONST_CAST(ecs_size_t*, &ti->size); |
|
it.ids[0] = id; |
|
it.event = event; |
|
it.event_id = id; |
|
it.ctx = ti->hooks.ctx; |
|
it.binding_ctx = ti->hooks.binding_ctx; |
|
it.count = count; |
|
it.offset = row; |
|
flecs_iter_validate(&it); |
|
hook(&it); |
|
ecs_iter_fini(&it); |
|
|
|
world->stages[0].defer = defer; |
|
} |
|
|
|
void flecs_notify_on_set( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t row, |
|
int32_t count, |
|
ecs_type_t *ids, |
|
bool owned) |
|
{ |
|
ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_data_t *data = &table->data; |
|
|
|
ecs_entity_t *entities = ecs_vec_get_t( |
|
&data->entities, ecs_entity_t, row); |
|
ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert((row + count) <= ecs_vec_count(&data->entities), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (owned) { |
|
int i; |
|
for (i = 0; i < ids->count; i ++) { |
|
ecs_id_t id = ids->array[i]; |
|
const ecs_table_record_t *tr = flecs_table_record_get(world, |
|
table, id); |
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t index = tr->column; |
|
ecs_column_t *column = &table->data.columns[index]; |
|
const ecs_type_info_t *ti = column->ti; |
|
ecs_iter_action_t on_set = ti->hooks.on_set; |
|
if (on_set) { |
|
ecs_vec_t *c = &column->data; |
|
void *ptr = ecs_vec_get(c, column->size, row); |
|
flecs_invoke_hook(world, table, count, row, entities, ptr, id, |
|
ti, EcsOnSet, on_set); |
|
} |
|
} |
|
} |
|
|
|
/* Run OnSet notifications */ |
|
if (table->flags & EcsTableHasOnSet && ids->count) { |
|
flecs_emit(world, world, &(ecs_event_desc_t) { |
|
.event = EcsOnSet, |
|
.ids = ids, |
|
.table = table, |
|
.offset = row, |
|
.count = count, |
|
.observable = world |
|
}); |
|
} |
|
} |
|
|
|
void flecs_record_add_flag( |
|
ecs_record_t *record, |
|
uint32_t flag) |
|
{ |
|
if (flag == EcsEntityIsTraversable) { |
|
if (!(record->row & flag)) { |
|
ecs_table_t *table = record->table; |
|
if (table) { |
|
flecs_table_traversable_add(table, 1); |
|
} |
|
} |
|
} |
|
record->row |= flag; |
|
} |
|
|
|
void flecs_add_flag( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
uint32_t flag) |
|
{ |
|
ecs_record_t *record = flecs_entities_get_any(world, entity); |
|
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); |
|
flecs_record_add_flag(record, flag); |
|
} |
|
|
|
/* -- Public functions -- */ |
|
|
|
bool ecs_commit( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_record_t *record, |
|
ecs_table_t *table, |
|
const ecs_type_t *added, |
|
const ecs_type_t *removed) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); |
|
|
|
ecs_table_t *src_table = NULL; |
|
if (!record) { |
|
record = flecs_entities_get(world, entity); |
|
src_table = record->table; |
|
} |
|
|
|
ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; |
|
|
|
if (added) { |
|
diff.added = *added; |
|
} |
|
if (removed) { |
|
diff.removed = *removed; |
|
} |
|
|
|
ecs_defer_begin(world); |
|
flecs_commit(world, entity, record, table, &diff, true, 0); |
|
ecs_defer_end(world); |
|
|
|
return src_table != table; |
|
error: |
|
return false; |
|
} |
|
|
|
ecs_entity_t ecs_set_with( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_id_t prev = stage->with; |
|
stage->with = id; |
|
return prev; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_id_t ecs_get_with( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); |
|
return stage->with; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_new_id( |
|
ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); |
|
|
|
/* It is possible that the world passed to this function is a stage, so |
|
* make sure we have the actual world. Cast away const since this is one of |
|
* the few functions that may modify the world while it is in readonly mode, |
|
* since it is thread safe (uses atomic inc when in threading mode) */ |
|
ecs_world_t *unsafe_world = |
|
ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); |
|
|
|
ecs_entity_t entity; |
|
if (stage->async || (unsafe_world->flags & EcsWorldMultiThreaded)) { |
|
/* When using an async stage or world is in multithreading mode, make |
|
* sure OS API has threading functions initialized */ |
|
ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, NULL); |
|
|
|
/* Can't atomically increase number above max int */ |
|
ecs_assert(flecs_entities_max_id(unsafe_world) < UINT_MAX, |
|
ECS_INVALID_OPERATION, NULL); |
|
entity = (ecs_entity_t)ecs_os_ainc( |
|
(int32_t*)&flecs_entities_max_id(unsafe_world)); |
|
} else { |
|
entity = flecs_entities_new_id(unsafe_world); |
|
} |
|
|
|
ecs_assert(!unsafe_world->info.max_id || |
|
ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, |
|
ECS_OUT_OF_RANGE, NULL); |
|
|
|
flecs_journal(world, EcsJournalNew, entity, 0, 0); |
|
|
|
return entity; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_new_low_id( |
|
ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* It is possible that the world passed to this function is a stage, so |
|
* make sure we have the actual world. Cast away const since this is one of |
|
* the few functions that may modify the world while it is in readonly mode, |
|
* but only if single threaded. */ |
|
ecs_world_t *unsafe_world = |
|
ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); |
|
if (unsafe_world->flags & EcsWorldReadonly) { |
|
/* Can't issue new comp id while iterating when in multithreaded mode */ |
|
ecs_check(ecs_get_stage_count(world) <= 1, |
|
ECS_INVALID_WHILE_READONLY, NULL); |
|
} |
|
|
|
ecs_entity_t id = 0; |
|
if (unsafe_world->info.last_component_id < FLECS_HI_COMPONENT_ID) { |
|
do { |
|
id = unsafe_world->info.last_component_id ++; |
|
} while (ecs_exists(unsafe_world, id) && id <= FLECS_HI_COMPONENT_ID); |
|
} |
|
|
|
if (!id || id >= FLECS_HI_COMPONENT_ID) { |
|
/* If the low component ids are depleted, return a regular entity id */ |
|
id = ecs_new_id(unsafe_world); |
|
} else { |
|
flecs_entities_ensure(world, id); |
|
} |
|
|
|
ecs_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return id; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_new_w_id( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!id || ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_entity_t entity = ecs_new_id(world); |
|
|
|
ecs_id_t ids[3]; |
|
ecs_type_t to_add = { .array = ids, .count = 0 }; |
|
|
|
if (id) { |
|
ids[to_add.count ++] = id; |
|
} |
|
|
|
ecs_id_t with = stage->with; |
|
if (with) { |
|
ids[to_add.count ++] = with; |
|
} |
|
|
|
ecs_entity_t scope = stage->scope; |
|
if (scope) { |
|
if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { |
|
ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); |
|
} |
|
} |
|
if (to_add.count) { |
|
if (flecs_defer_add(stage, entity, to_add.array[0])) { |
|
int i; |
|
for (i = 1; i < to_add.count; i ++) { |
|
flecs_defer_add(stage, entity, to_add.array[i]); |
|
} |
|
return entity; |
|
} |
|
|
|
int32_t i, count = to_add.count; |
|
ecs_table_t *table = &world->store.root; |
|
|
|
ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; |
|
flecs_table_diff_builder_init(world, &diff); |
|
for (i = 0; i < count; i ++) { |
|
table = flecs_find_table_add( |
|
world, table, to_add.array[i], &diff); |
|
} |
|
|
|
ecs_table_diff_t table_diff; |
|
flecs_table_diff_build_noalloc(&diff, &table_diff); |
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
flecs_new_entity(world, entity, r, table, &table_diff, true, true); |
|
flecs_table_diff_builder_fini(world, &diff); |
|
} else { |
|
if (flecs_defer_cmd(stage)) { |
|
return entity; |
|
} |
|
|
|
flecs_entities_ensure(world, entity); |
|
} |
|
flecs_defer_end(world, stage); |
|
|
|
return entity; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_new_w_table( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
flecs_stage_from_world(&world); |
|
ecs_entity_t entity = ecs_new_id(world); |
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
|
|
ecs_table_diff_t table_diff = { .added = table->type }; |
|
flecs_new_entity(world, entity, r, table, &table_diff, true, true); |
|
return entity; |
|
error: |
|
return 0; |
|
} |
|
|
|
#ifdef FLECS_PARSER |
|
|
|
/* Traverse table graph by either adding or removing identifiers parsed from the |
|
* passed in expression. */ |
|
static |
|
ecs_table_t *flecs_traverse_from_expr( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
const char *name, |
|
const char *expr, |
|
ecs_table_diff_builder_t *diff, |
|
bool replace_and, |
|
bool *error) |
|
{ |
|
const char *ptr = expr; |
|
if (ptr) { |
|
ecs_term_t term = {0}; |
|
while (ptr[0] && (ptr = ecs_parse_term( |
|
world, name, expr, ptr, &term, NULL, NULL, false))) |
|
{ |
|
if (!ecs_term_is_initialized(&term)) { |
|
break; |
|
} |
|
|
|
if (!(term.first.flags & (EcsSelf|EcsUp))) { |
|
term.first.flags = EcsSelf; |
|
} |
|
if (!(term.second.flags & (EcsSelf|EcsUp))) { |
|
term.second.flags = EcsSelf; |
|
} |
|
if (!(term.src.flags & (EcsSelf|EcsUp))) { |
|
term.src.flags = EcsSelf; |
|
} |
|
|
|
if (ecs_term_finalize(world, &term)) { |
|
ecs_term_fini(&term); |
|
if (error) { |
|
*error = true; |
|
} |
|
return NULL; |
|
} |
|
|
|
if (!ecs_id_is_valid(world, term.id)) { |
|
ecs_term_fini(&term); |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid term for add expression"); |
|
return NULL; |
|
} |
|
|
|
if (term.oper == EcsAnd || !replace_and) { |
|
/* Regular AND expression */ |
|
table = flecs_find_table_add(world, table, term.id, diff); |
|
} |
|
|
|
ecs_term_fini(&term); |
|
} |
|
|
|
if (!ptr) { |
|
if (error) { |
|
*error = true; |
|
} |
|
return NULL; |
|
} |
|
} |
|
|
|
return table; |
|
} |
|
|
|
/* Add/remove components based on the parsed expression. This operation is |
|
* slower than flecs_traverse_from_expr, but safe to use from a deferred context. */ |
|
static |
|
void flecs_defer_from_expr( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *name, |
|
const char *expr, |
|
bool is_add, |
|
bool replace_and) |
|
{ |
|
const char *ptr = expr; |
|
if (ptr) { |
|
ecs_term_t term = {0}; |
|
while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term, |
|
NULL, NULL, false))) |
|
{ |
|
if (!ecs_term_is_initialized(&term)) { |
|
break; |
|
} |
|
|
|
if (ecs_term_finalize(world, &term)) { |
|
return; |
|
} |
|
|
|
if (!ecs_id_is_valid(world, term.id)) { |
|
ecs_term_fini(&term); |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid term for add expression"); |
|
return; |
|
} |
|
|
|
if (term.oper == EcsAnd || !replace_and) { |
|
/* Regular AND expression */ |
|
if (is_add) { |
|
ecs_add_id(world, entity, term.id); |
|
} else { |
|
ecs_remove_id(world, entity, term.id); |
|
} |
|
} |
|
|
|
ecs_term_fini(&term); |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
/* If operation is not deferred, add components by finding the target |
|
* table and moving the entity towards it. */ |
|
static |
|
int flecs_traverse_add( |
|
ecs_world_t *world, |
|
ecs_entity_t result, |
|
const char *name, |
|
const ecs_entity_desc_t *desc, |
|
ecs_entity_t scope, |
|
ecs_id_t with, |
|
bool flecs_new_entity, |
|
bool name_assigned) |
|
{ |
|
const char *sep = desc->sep; |
|
const char *root_sep = desc->root_sep; |
|
ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; |
|
flecs_table_diff_builder_init(world, &diff); |
|
|
|
/* Find existing table */ |
|
ecs_table_t *src_table = NULL, *table = NULL; |
|
ecs_record_t *r = flecs_entities_get(world, result); |
|
table = r->table; |
|
|
|
/* If a name is provided but not yet assigned, add the Name component */ |
|
if (name && !name_assigned) { |
|
table = flecs_find_table_add(world, table, |
|
ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); |
|
} |
|
|
|
/* Add components from the 'add' id array */ |
|
int32_t i = 0; |
|
ecs_id_t id; |
|
const ecs_id_t *ids = desc->add; |
|
while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { |
|
bool should_add = true; |
|
if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { |
|
scope = ECS_PAIR_SECOND(id); |
|
if ((!desc->id && desc->name) || (name && !name_assigned)) { |
|
/* If name is added to entity, pass scope to add_path instead |
|
* of adding it to the table. The provided name may have nested |
|
* elements, in which case the parent provided here is not the |
|
* parent the entity will end up with. */ |
|
should_add = false; |
|
} |
|
} |
|
if (should_add) { |
|
table = flecs_find_table_add(world, table, id, &diff); |
|
} |
|
} |
|
|
|
/* Find destination table */ |
|
/* 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 */ |
|
if (flecs_new_entity) { |
|
if (flecs_new_entity && scope && !name && !name_assigned) { |
|
table = flecs_find_table_add( |
|
world, table, ecs_pair(EcsChildOf, scope), &diff); |
|
} |
|
if (with) { |
|
table = flecs_find_table_add(world, table, with, &diff); |
|
} |
|
} |
|
|
|
/* Add components from the 'add_expr' expression */ |
|
if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { |
|
#ifdef FLECS_PARSER |
|
bool error = false; |
|
table = flecs_traverse_from_expr( |
|
world, table, name, desc->add_expr, &diff, true, &error); |
|
if (error) { |
|
flecs_table_diff_builder_fini(world, &diff); |
|
return -1; |
|
} |
|
#else |
|
ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); |
|
#endif |
|
} |
|
|
|
/* Commit entity to destination table */ |
|
if (src_table != table) { |
|
flecs_defer_begin(world, &world->stages[0]); |
|
ecs_table_diff_t table_diff; |
|
flecs_table_diff_build_noalloc(&diff, &table_diff); |
|
flecs_commit(world, result, r, table, &table_diff, true, 0); |
|
flecs_table_diff_builder_fini(world, &diff); |
|
flecs_defer_end(world, &world->stages[0]); |
|
} |
|
|
|
/* Set name */ |
|
if (name && !name_assigned) { |
|
ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); |
|
ecs_assert(ecs_get_name(world, result) != NULL, |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (desc->symbol && desc->symbol[0]) { |
|
const char *sym = ecs_get_symbol(world, result); |
|
if (sym) { |
|
ecs_assert(!ecs_os_strcmp(desc->symbol, sym), |
|
ECS_INCONSISTENT_NAME, desc->symbol); |
|
} else { |
|
ecs_set_symbol(world, result, desc->symbol); |
|
} |
|
} |
|
|
|
flecs_table_diff_builder_fini(world, &diff); |
|
return 0; |
|
} |
|
|
|
/* When in deferred mode, we need to add/remove components one by one using |
|
* the regular operations. */ |
|
static |
|
void flecs_deferred_add_remove( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *name, |
|
const ecs_entity_desc_t *desc, |
|
ecs_entity_t scope, |
|
ecs_id_t with, |
|
bool flecs_new_entity, |
|
bool name_assigned) |
|
{ |
|
const char *sep = desc->sep; |
|
const char *root_sep = desc->root_sep; |
|
|
|
/* 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 */ |
|
if (flecs_new_entity) { |
|
if (flecs_new_entity && scope && !name && !name_assigned) { |
|
ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); |
|
} |
|
|
|
if (with) { |
|
ecs_add_id(world, entity, with); |
|
} |
|
} |
|
|
|
/* Add components from the 'add' id array */ |
|
int32_t i = 0; |
|
ecs_id_t id; |
|
const ecs_id_t *ids = desc->add; |
|
while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { |
|
bool defer = true; |
|
if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { |
|
scope = ECS_PAIR_SECOND(id); |
|
if (name && (!desc->id || !name_assigned)) { |
|
/* New named entities are created by temporarily going out of |
|
* readonly mode to ensure no duplicates are created. */ |
|
defer = false; |
|
} |
|
} |
|
if (defer) { |
|
ecs_add_id(world, entity, id); |
|
} |
|
} |
|
|
|
/* Add components from the 'add_expr' expression */ |
|
if (desc->add_expr) { |
|
#ifdef FLECS_PARSER |
|
flecs_defer_from_expr(world, entity, name, desc->add_expr, true, true); |
|
#else |
|
ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); |
|
#endif |
|
} |
|
|
|
int32_t thread_count = ecs_get_stage_count(world); |
|
|
|
/* Set name */ |
|
if (name && !name_assigned) { |
|
ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); |
|
} |
|
|
|
/* Set symbol */ |
|
if (desc->symbol) { |
|
const char *sym = ecs_get_symbol(world, entity); |
|
if (!sym || ecs_os_strcmp(sym, desc->symbol)) { |
|
if (thread_count <= 1) { /* See above */ |
|
ecs_suspend_readonly_state_t state; |
|
ecs_world_t *real_world = flecs_suspend_readonly(world, &state); |
|
ecs_set_symbol(world, entity, desc->symbol); |
|
flecs_resume_readonly(real_world, &state); |
|
} else { |
|
ecs_set_symbol(world, entity, desc->symbol); |
|
} |
|
} |
|
} |
|
} |
|
|
|
ecs_entity_t ecs_entity_init( |
|
ecs_world_t *world, |
|
const ecs_entity_desc_t *desc) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_entity_t scope = stage->scope; |
|
ecs_id_t with = ecs_get_with(world); |
|
ecs_entity_t result = desc->id; |
|
|
|
const char *name = desc->name; |
|
const char *sep = desc->sep; |
|
if (!sep) { |
|
sep = "."; |
|
} |
|
|
|
if (name) { |
|
if (!name[0]) { |
|
name = NULL; |
|
} else if (flecs_name_is_id(name)){ |
|
ecs_entity_t id = flecs_name_to_id(world, name); |
|
if (!id) { |
|
return 0; |
|
} |
|
if (result && (id != result)) { |
|
ecs_err("name id conflicts with provided id"); |
|
return 0; |
|
} |
|
name = NULL; |
|
result = id; |
|
} |
|
} |
|
|
|
const char *root_sep = desc->root_sep; |
|
bool flecs_new_entity = false; |
|
bool name_assigned = false; |
|
|
|
/* Remove optional prefix from name. Entity names can be derived from |
|
* language identifiers, such as components (typenames) and systems |
|
* function names). Because C does not have namespaces, such identifiers |
|
* often encode the namespace as a prefix. |
|
* To ensure interoperability between C and C++ (and potentially other |
|
* languages with namespacing) the entity must be stored without this prefix |
|
* and with the proper namespace, which is what the name_prefix is for */ |
|
const char *prefix = world->info.name_prefix; |
|
if (name && prefix) { |
|
ecs_size_t len = ecs_os_strlen(prefix); |
|
if (!ecs_os_strncmp(name, prefix, len) && |
|
(isupper(name[len]) || name[len] == '_')) |
|
{ |
|
if (name[len] == '_') { |
|
name = name + len + 1; |
|
} else { |
|
name = name + len; |
|
} |
|
} |
|
} |
|
|
|
/* Find or create entity */ |
|
if (!result) { |
|
if (name) { |
|
/* If add array contains a ChildOf pair, use it as scope instead */ |
|
const ecs_id_t *ids = desc->add; |
|
ecs_id_t id; |
|
int32_t i = 0; |
|
while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { |
|
if (ECS_HAS_ID_FLAG(id, PAIR) && |
|
(ECS_PAIR_FIRST(id) == EcsChildOf)) |
|
{ |
|
scope = ECS_PAIR_SECOND(id); |
|
} |
|
} |
|
|
|
result = ecs_lookup_path_w_sep( |
|
world, scope, name, sep, root_sep, false); |
|
if (result) { |
|
name_assigned = true; |
|
} |
|
} |
|
|
|
if (!result) { |
|
if (desc->use_low_id) { |
|
result = ecs_new_low_id(world); |
|
} else { |
|
result = ecs_new_id(world); |
|
} |
|
flecs_new_entity = true; |
|
ecs_assert(ecs_get_type(world, result) == NULL, |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} else { |
|
/* Make sure provided id is either alive or revivable */ |
|
ecs_ensure(world, result); |
|
|
|
name_assigned = ecs_has_pair( |
|
world, result, ecs_id(EcsIdentifier), EcsName); |
|
if (name && name_assigned) { |
|
/* If entity has name, verify that name matches. The name provided |
|
* to the function could either have been relative to the current |
|
* scope, or fully qualified. */ |
|
char *path; |
|
ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; |
|
if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { |
|
/* Fully qualified name was provided, so make sure to |
|
* compare with fully qualified name */ |
|
path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); |
|
} else { |
|
/* Relative name was provided, so make sure to compare with |
|
* relative name */ |
|
if (!sep || sep[0]) { |
|
path = ecs_get_path_w_sep(world, scope, result, sep, ""); |
|
} else { |
|
/* Safe, only freed when sep is valid */ |
|
path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); |
|
} |
|
} |
|
if (path) { |
|
if (ecs_os_strcmp(path, name)) { |
|
/* Mismatching name */ |
|
ecs_err("existing entity '%s' is initialized with " |
|
"conflicting name '%s'", path, name); |
|
if (!sep || sep[0]) { |
|
ecs_os_free(path); |
|
} |
|
return 0; |
|
} |
|
if (!sep || sep[0]) { |
|
ecs_os_free(path); |
|
} |
|
} |
|
} |
|
} |
|
|
|
ecs_assert(name_assigned == ecs_has_pair( |
|
world, result, ecs_id(EcsIdentifier), EcsName), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (stage->defer) { |
|
flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, |
|
scope, with, flecs_new_entity, name_assigned); |
|
} else { |
|
if (flecs_traverse_add(world, result, name, desc, |
|
scope, with, flecs_new_entity, name_assigned)) |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
return result; |
|
error: |
|
return 0; |
|
} |
|
|
|
const ecs_entity_t* ecs_bulk_init( |
|
ecs_world_t *world, |
|
const ecs_bulk_desc_t *desc) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const ecs_entity_t *entities = desc->entities; |
|
int32_t count = desc->count; |
|
|
|
int32_t sparse_count = 0; |
|
if (!entities) { |
|
sparse_count = flecs_entities_count(world); |
|
entities = flecs_entities_new_ids(world, count); |
|
ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} else { |
|
int i; |
|
for (i = 0; i < count; i ++) { |
|
ecs_ensure(world, entities[i]); |
|
} |
|
} |
|
|
|
ecs_type_t ids; |
|
ecs_table_t *table = desc->table; |
|
if (!table) { |
|
ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; |
|
flecs_table_diff_builder_init(world, &diff); |
|
|
|
int32_t i = 0; |
|
ecs_id_t id; |
|
while ((id = desc->ids[i])) { |
|
table = flecs_find_table_add(world, table, id, &diff); |
|
i ++; |
|
} |
|
|
|
ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); |
|
ids.count = i; |
|
|
|
ecs_table_diff_t table_diff; |
|
flecs_table_diff_build_noalloc(&diff, &table_diff); |
|
flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, |
|
&table_diff); |
|
flecs_table_diff_builder_fini(world, &diff); |
|
} else { |
|
ecs_table_diff_t diff = { |
|
.added.array = table->type.array, |
|
.added.count = table->type.count |
|
}; |
|
ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; |
|
flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, |
|
&diff); |
|
} |
|
|
|
if (!sparse_count) { |
|
return entities; |
|
} else { |
|
/* Refetch entity ids, in case the underlying array was reallocated */ |
|
entities = flecs_entities_ids(world); |
|
return &entities[sparse_count]; |
|
} |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
void flecs_check_component( |
|
ecs_world_t *world, |
|
ecs_entity_t result, |
|
const EcsComponent *ptr, |
|
ecs_size_t size, |
|
ecs_size_t alignment) |
|
{ |
|
if (ptr->size != size) { |
|
char *path = ecs_get_fullpath(world, result); |
|
ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); |
|
ecs_os_free(path); |
|
} |
|
if (ptr->alignment != alignment) { |
|
char *path = ecs_get_fullpath(world, result); |
|
ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); |
|
ecs_os_free(path); |
|
} |
|
} |
|
|
|
ecs_entity_t ecs_component_init( |
|
ecs_world_t *world, |
|
const ecs_component_desc_t *desc) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* If existing entity is provided, check if it is already registered as a |
|
* component and matches the size/alignment. This can prevent having to |
|
* suspend readonly mode, and increases the number of scenarios in which |
|
* this function can be called in multithreaded mode. */ |
|
ecs_entity_t result = desc->entity; |
|
if (result && ecs_is_alive(world, result)) { |
|
const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); |
|
if (const_ptr) { |
|
flecs_check_component(world, result, const_ptr, |
|
desc->type.size, desc->type.alignment); |
|
return result; |
|
} |
|
} |
|
|
|
ecs_suspend_readonly_state_t readonly_state; |
|
world = flecs_suspend_readonly(world, &readonly_state); |
|
|
|
bool new_component = true; |
|
if (!result) { |
|
result = ecs_new_low_id(world); |
|
} else { |
|
ecs_ensure(world, result); |
|
new_component = ecs_has(world, result, EcsComponent); |
|
} |
|
|
|
EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent); |
|
if (!ptr->size) { |
|
ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); |
|
ptr->size = desc->type.size; |
|
ptr->alignment = desc->type.alignment; |
|
if (!new_component || ptr->size != desc->type.size) { |
|
if (!ptr->size) { |
|
ecs_trace("#[green]tag#[reset] %s created", |
|
ecs_get_name(world, result)); |
|
} else { |
|
ecs_trace("#[green]component#[reset] %s created", |
|
ecs_get_name(world, result)); |
|
} |
|
} |
|
} else { |
|
flecs_check_component(world, result, ptr, |
|
desc->type.size, desc->type.alignment); |
|
} |
|
|
|
ecs_modified(world, result, EcsComponent); |
|
|
|
if (desc->type.size && |
|
!ecs_id_in_use(world, result) && |
|
!ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) |
|
{ |
|
ecs_set_hooks_id(world, result, &desc->type.hooks); |
|
} |
|
|
|
if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { |
|
world->info.last_component_id = result + 1; |
|
} |
|
|
|
/* Ensure components cannot be deleted */ |
|
ecs_add_pair(world, result, EcsOnDelete, EcsPanic); |
|
|
|
flecs_resume_readonly(world, &readonly_state); |
|
|
|
ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); |
|
|
|
return result; |
|
error: |
|
return 0; |
|
} |
|
|
|
const ecs_entity_t* ecs_bulk_new_w_id( |
|
ecs_world_t *world, |
|
ecs_id_t id, |
|
int32_t count) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
const ecs_entity_t *ids; |
|
if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { |
|
return ids; |
|
} |
|
|
|
ecs_table_t *table = &world->store.root; |
|
ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; |
|
flecs_table_diff_builder_init(world, &diff); |
|
|
|
if (id) { |
|
table = flecs_find_table_add(world, table, id, &diff); |
|
} |
|
|
|
ecs_table_diff_t td; |
|
flecs_table_diff_build_noalloc(&diff, &td); |
|
ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); |
|
flecs_table_diff_builder_fini(world, &diff); |
|
flecs_defer_end(world, stage); |
|
|
|
return ids; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void ecs_clear( |
|
ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
if (flecs_defer_clear(stage, entity)) { |
|
return; |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_t *table = r->table; |
|
if (table) { |
|
ecs_table_diff_t diff = { |
|
.removed = table->type |
|
}; |
|
|
|
flecs_delete_entity(world, r, &diff); |
|
r->table = NULL; |
|
|
|
if (r->row & EcsEntityIsTraversable) { |
|
flecs_table_traversable_add(table, -1); |
|
} |
|
} |
|
|
|
flecs_defer_end(world, stage); |
|
error: |
|
return; |
|
} |
|
|
|
static |
|
void flecs_throw_invalid_delete( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
char *id_str = NULL; |
|
if (!(world->flags & EcsWorldQuit)) { |
|
id_str = ecs_id_str(world, id); |
|
ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); |
|
} |
|
error: |
|
ecs_os_free(id_str); |
|
} |
|
|
|
static |
|
void flecs_marked_id_push( |
|
ecs_world_t *world, |
|
ecs_id_record_t* idr, |
|
ecs_entity_t action, |
|
bool delete_id) |
|
{ |
|
ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, |
|
&world->store.marked_ids, ecs_marked_id_t); |
|
|
|
m->idr = idr; |
|
m->id = idr->id; |
|
m->action = action; |
|
m->delete_id = delete_id; |
|
|
|
flecs_id_record_claim(world, idr); |
|
} |
|
|
|
static |
|
void flecs_id_mark_for_delete( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr, |
|
ecs_entity_t action, |
|
bool delete_id); |
|
|
|
static |
|
void flecs_targets_mark_for_delete( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_id_record_t *idr; |
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities); |
|
int32_t i, count = ecs_vec_count(&table->data.entities); |
|
for (i = 0; i < count; i ++) { |
|
ecs_record_t *r = flecs_entities_get(world, entities[i]); |
|
if (!r) { |
|
continue; |
|
} |
|
|
|
/* If entity is not used as id or as relationship target, there won't |
|
* be any tables with a reference to it. */ |
|
ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; |
|
if (!(flags & (EcsEntityIsId|EcsEntityIsTarget))) { |
|
continue; |
|
} |
|
|
|
ecs_entity_t e = entities[i]; |
|
if (flags & EcsEntityIsId) { |
|
if ((idr = flecs_id_record_get(world, e))) { |
|
flecs_id_mark_for_delete(world, idr, |
|
ECS_ID_ON_DELETE(idr->flags), true); |
|
} |
|
if ((idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)))) { |
|
flecs_id_mark_for_delete(world, idr, |
|
ECS_ID_ON_DELETE(idr->flags), true); |
|
} |
|
} |
|
if (flags & EcsEntityIsTarget) { |
|
if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { |
|
flecs_id_mark_for_delete(world, idr, |
|
ECS_ID_ON_DELETE_TARGET(idr->flags), true); |
|
} |
|
if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { |
|
flecs_id_mark_for_delete(world, idr, |
|
ECS_ID_ON_DELETE_TARGET(idr->flags), true); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
bool flecs_id_is_delete_target( |
|
ecs_id_t id, |
|
ecs_entity_t action) |
|
{ |
|
if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { |
|
/* If no explicit delete action is provided, and the id we're deleting |
|
* has the form (*, Target), use OnDeleteTarget action */ |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_get_delete_action( |
|
ecs_table_t *table, |
|
ecs_table_record_t *tr, |
|
ecs_entity_t action, |
|
bool delete_target) |
|
{ |
|
ecs_entity_t result = action; |
|
if (!result && delete_target) { |
|
ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; |
|
ecs_id_t id = idr->id; |
|
|
|
/* If action is not specified and we're deleting a relationship target, |
|
* derive the action from the current record */ |
|
int32_t i = tr->index, count = tr->count; |
|
do { |
|
ecs_type_t *type = &table->type; |
|
ecs_table_record_t *trr = &table->_->records[i]; |
|
ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; |
|
result = ECS_ID_ON_DELETE_TARGET(idrr->flags); |
|
if (result == EcsDelete) { |
|
/* Delete takes precedence over Remove */ |
|
break; |
|
} |
|
|
|
if (count > 1) { |
|
/* If table contains multiple pairs for target they are not |
|
* guaranteed to occupy consecutive elements in the table's type |
|
* vector, so a linear search is needed to find matches. */ |
|
for (++ i; i < type->count; i ++) { |
|
if (ecs_id_match(type->array[i], id)) { |
|
break; |
|
} |
|
} |
|
|
|
/* We should always have as many matching ids as tr->count */ |
|
ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} while (--count); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static |
|
void flecs_update_monitors_for_delete( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
flecs_update_component_monitors(world, NULL, &(ecs_type_t){ |
|
.array = (ecs_id_t[]){id}, |
|
.count = 1 |
|
}); |
|
} |
|
|
|
static |
|
void flecs_id_mark_for_delete( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr, |
|
ecs_entity_t action, |
|
bool delete_id) |
|
{ |
|
if (idr->flags & EcsIdMarkedForDelete) { |
|
return; |
|
} |
|
|
|
idr->flags |= EcsIdMarkedForDelete; |
|
flecs_marked_id_push(world, idr, action, delete_id); |
|
|
|
ecs_id_t id = idr->id; |
|
|
|
bool delete_target = flecs_id_is_delete_target(id, action); |
|
|
|
/* Mark all tables with the id for delete */ |
|
ecs_table_cache_iter_t it; |
|
if (flecs_table_cache_iter(&idr->cache, &it)) { |
|
ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
if (table->flags & EcsTableMarkedForDelete) { |
|
continue; |
|
} |
|
|
|
ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, |
|
delete_target); |
|
|
|
/* If this is a Delete action, recursively mark ids & tables */ |
|
if (cur_action == EcsDelete) { |
|
table->flags |= EcsTableMarkedForDelete; |
|
ecs_log_push_2(); |
|
flecs_targets_mark_for_delete(world, table); |
|
ecs_log_pop_2(); |
|
} else if (cur_action == EcsPanic) { |
|
flecs_throw_invalid_delete(world, id); |
|
} |
|
} |
|
} |
|
|
|
/* Same for empty tables */ |
|
if (flecs_table_cache_empty_iter(&idr->cache, &it)) { |
|
ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
tr->hdr.table->flags |= EcsTableMarkedForDelete; |
|
} |
|
} |
|
|
|
/* Signal query cache monitors */ |
|
flecs_update_monitors_for_delete(world, id); |
|
|
|
/* If id is a wildcard pair, update cache monitors for non-wildcard ids */ |
|
if (ecs_id_is_wildcard(id)) { |
|
ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_record_t *cur = idr; |
|
if (ECS_PAIR_SECOND(id) == EcsWildcard) { |
|
while ((cur = cur->first.next)) { |
|
flecs_update_monitors_for_delete(world, cur->id); |
|
} |
|
} else { |
|
ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, |
|
ECS_INTERNAL_ERROR, NULL); |
|
while ((cur = cur->second.next)) { |
|
flecs_update_monitors_for_delete(world, cur->id); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
bool flecs_on_delete_mark( |
|
ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_entity_t action, |
|
bool delete_id) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
/* If there's no id record, there's nothing to delete */ |
|
return false; |
|
} |
|
|
|
if (!action) { |
|
/* If no explicit action is provided, derive it */ |
|
if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { |
|
/* Delete actions are determined by the component, or in the case |
|
* of a pair by the relationship. */ |
|
action = ECS_ID_ON_DELETE(idr->flags); |
|
} |
|
} |
|
|
|
if (action == EcsPanic) { |
|
/* This id is protected from deletion */ |
|
flecs_throw_invalid_delete(world, id); |
|
return false; |
|
} |
|
|
|
flecs_id_mark_for_delete(world, idr, action, delete_id); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
void flecs_remove_from_table( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_table_diff_t temp_diff; |
|
ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; |
|
flecs_table_diff_builder_init(world, &diff); |
|
ecs_table_t *dst_table = table; |
|
|
|
/* To find the dst table, remove all ids that are marked for deletion */ |
|
int32_t i, t, count = ecs_vec_count(&world->store.marked_ids); |
|
ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); |
|
const ecs_table_record_t *tr; |
|
for (i = 0; i < count; i ++) { |
|
const ecs_id_record_t *idr = ids[i].idr; |
|
|
|
if (!(tr = flecs_id_record_get_table(idr, dst_table))) { |
|
continue; |
|
} |
|
|
|
t = tr->index; |
|
|
|
do { |
|
ecs_id_t id = dst_table->type.array[t]; |
|
dst_table = flecs_table_traverse_remove( |
|
world, dst_table, &id, &temp_diff); |
|
flecs_table_diff_build_append_table(world, &diff, &temp_diff); |
|
} while (dst_table->type.count && (t = ecs_search_offset( |
|
world, dst_table, t, idr->id, NULL)) != -1); |
|
} |
|
|
|
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!dst_table->type.count) { |
|
/* If this removes all components, clear table */ |
|
flecs_table_clear_entities(world, table); |
|
} else { |
|
/* Otherwise, merge table into dst_table */ |
|
if (dst_table != table) { |
|
int32_t table_count = ecs_table_count(table); |
|
if (diff.removed.count && table_count) { |
|
ecs_log_push_3(); |
|
ecs_table_diff_t td; |
|
flecs_table_diff_build_noalloc(&diff, &td); |
|
flecs_notify_on_remove(world, table, NULL, 0, table_count, |
|
&td.removed); |
|
ecs_log_pop_3(); |
|
} |
|
|
|
flecs_table_merge(world, dst_table, table, |
|
&dst_table->data, &table->data); |
|
} |
|
} |
|
|
|
flecs_table_diff_builder_fini(world, &diff); |
|
} |
|
|
|
static |
|
bool flecs_on_delete_clear_tables( |
|
ecs_world_t *world) |
|
{ |
|
/* Iterate in reverse order so that DAGs get deleted bottom to top */ |
|
int32_t i, last = ecs_vec_count(&world->store.marked_ids), first = 0; |
|
ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); |
|
do { |
|
for (i = last - 1; i >= first; i --) { |
|
ecs_id_record_t *idr = ids[i].idr; |
|
ecs_entity_t action = ids[i].action; |
|
|
|
/* Empty all tables for id */ |
|
{ |
|
ecs_table_cache_iter_t it; |
|
if (flecs_table_cache_iter(&idr->cache, &it)) { |
|
ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
|
|
if ((action == EcsRemove) || |
|
!(table->flags & EcsTableMarkedForDelete)) |
|
{ |
|
flecs_remove_from_table(world, table); |
|
} else { |
|
ecs_dbg_3( |
|
"#[red]delete#[reset] entities from table %u", |
|
(uint32_t)table->id); |
|
flecs_table_delete_entities(world, table); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Run commands so children get notified before parent is deleted */ |
|
if (world->stages[0].defer) { |
|
flecs_defer_end(world, &world->stages[0]); |
|
flecs_defer_begin(world, &world->stages[0]); |
|
} |
|
|
|
/* User code (from triggers) could have enqueued more ids to delete, |
|
* reobtain the array in case it got reallocated */ |
|
ids = ecs_vec_first(&world->store.marked_ids); |
|
} |
|
|
|
/* Check if new ids were marked since we started */ |
|
int32_t new_last = ecs_vec_count(&world->store.marked_ids); |
|
if (new_last != last) { |
|
/* Iterate remaining ids */ |
|
ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); |
|
first = last; |
|
last = new_last; |
|
} else { |
|
break; |
|
} |
|
} while (true); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_on_delete_clear_ids( |
|
ecs_world_t *world) |
|
{ |
|
int32_t i, count = ecs_vec_count(&world->store.marked_ids); |
|
ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); |
|
int twice = 2; |
|
|
|
do { |
|
for (i = 0; i < count; i ++) { |
|
/* Release normal ids before wildcard ids */ |
|
if (ecs_id_is_wildcard(ids[i].id)) { |
|
if (twice == 2) { |
|
continue; |
|
} |
|
} else { |
|
if (twice == 1) { |
|
continue; |
|
} |
|
} |
|
|
|
ecs_id_record_t *idr = ids[i].idr; |
|
bool delete_id = ids[i].delete_id; |
|
|
|
flecs_id_record_release_tables(world, idr); |
|
|
|
/* Release the claim taken by flecs_marked_id_push. This may delete the |
|
* id record as all other claims may have been released. */ |
|
int32_t rc = flecs_id_record_release(world, idr); |
|
ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); |
|
(void)rc; |
|
|
|
/* If rc is 0, the id was likely deleted by a nested delete_with call |
|
* made by an on_remove handler/OnRemove observer */ |
|
if (rc) { |
|
if (delete_id) { |
|
/* If id should be deleted, release initial claim. This happens when |
|
* a component, tag, or part of a pair is deleted. */ |
|
flecs_id_record_release(world, idr); |
|
} else { |
|
/* If id should not be deleted, unmark id record for deletion. This |
|
* happens when all instances *of* an id are deleted, for example |
|
* when calling ecs_remove_all or ecs_delete_with. */ |
|
idr->flags &= ~EcsIdMarkedForDelete; |
|
} |
|
} |
|
} |
|
} while (-- twice); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
void flecs_on_delete( |
|
ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_entity_t action, |
|
bool delete_id) |
|
{ |
|
/* Cleanup can happen recursively. If a cleanup action is already in |
|
* progress, only append ids to the marked_ids. The topmost cleanup |
|
* frame will handle the actual cleanup. */ |
|
int32_t count = ecs_vec_count(&world->store.marked_ids); |
|
|
|
/* Make sure we're evaluating a consistent list of non-empty tables */ |
|
ecs_run_aperiodic(world, EcsAperiodicEmptyTables); |
|
|
|
/* Collect all ids that need to be deleted */ |
|
flecs_on_delete_mark(world, id, action, delete_id); |
|
|
|
/* Only perform cleanup if we're the first stack frame doing it */ |
|
if (!count && ecs_vec_count(&world->store.marked_ids)) { |
|
ecs_dbg_2("#[red]delete#[reset]"); |
|
ecs_log_push_2(); |
|
|
|
/* Empty tables with all the to be deleted ids */ |
|
flecs_on_delete_clear_tables(world); |
|
|
|
/* All marked tables are empty, ensure they're in the right list */ |
|
ecs_run_aperiodic(world, EcsAperiodicEmptyTables); |
|
|
|
/* Release remaining references to the ids */ |
|
flecs_on_delete_clear_ids(world); |
|
|
|
/* Verify deleted ids are no longer in use */ |
|
#ifdef FLECS_DEBUG |
|
ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); |
|
int32_t i; count = ecs_vec_count(&world->store.marked_ids); |
|
for (i = 0; i < count; i ++) { |
|
ecs_assert(!ecs_id_in_use(world, ids[i].id), |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
#endif |
|
ecs_assert(!ecs_id_in_use(world, id), ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Ids are deleted, clear stack */ |
|
ecs_vec_clear(&world->store.marked_ids); |
|
|
|
ecs_log_pop_2(); |
|
} |
|
} |
|
|
|
void ecs_delete_with( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { |
|
return; |
|
} |
|
|
|
flecs_on_delete(world, id, EcsDelete, false); |
|
flecs_defer_end(world, stage); |
|
|
|
flecs_journal_end(); |
|
} |
|
|
|
void ecs_remove_all( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { |
|
return; |
|
} |
|
|
|
flecs_on_delete(world, id, EcsRemove, false); |
|
flecs_defer_end(world, stage); |
|
|
|
flecs_journal_end(); |
|
} |
|
|
|
void ecs_delete( |
|
ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
if (flecs_defer_delete(stage, entity)) { |
|
return; |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_try(world, entity); |
|
if (r) { |
|
flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); |
|
|
|
ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); |
|
ecs_table_t *table; |
|
if (row_flags) { |
|
if (row_flags & EcsEntityIsTarget) { |
|
flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); |
|
flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); |
|
r->idr = NULL; |
|
} |
|
if (row_flags & EcsEntityIsId) { |
|
flecs_on_delete(world, entity, 0, true); |
|
flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); |
|
} |
|
if (row_flags & EcsEntityIsTraversable) { |
|
table = r->table; |
|
if (table) { |
|
flecs_table_traversable_add(table, -1); |
|
} |
|
} |
|
/* Merge operations before deleting entity */ |
|
flecs_defer_end(world, stage); |
|
flecs_defer_begin(world, stage); |
|
} |
|
|
|
table = r->table; |
|
|
|
if (table) { |
|
ecs_table_diff_t diff = { |
|
.removed = table->type |
|
}; |
|
|
|
flecs_delete_entity(world, r, &diff); |
|
|
|
r->row = 0; |
|
r->table = NULL; |
|
} |
|
|
|
flecs_entities_remove(world, entity); |
|
|
|
flecs_journal_end(); |
|
} |
|
|
|
flecs_defer_end(world, stage); |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_add_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
flecs_add_id(world, entity, id); |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_remove_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), |
|
ECS_INVALID_PARAMETER, NULL); |
|
flecs_remove_id(world, entity, id); |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_override_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
ecs_add_id(world, entity, ECS_OVERRIDE | id); |
|
error: |
|
return; |
|
} |
|
|
|
ecs_entity_t ecs_clone( |
|
ecs_world_t *world, |
|
ecs_entity_t dst, |
|
ecs_entity_t src, |
|
bool copy_value) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, src), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!dst || !ecs_get_table(world, dst), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
if (!dst) { |
|
dst = ecs_new_id(world); |
|
} |
|
|
|
if (flecs_defer_clone(stage, dst, src, copy_value)) { |
|
return dst; |
|
} |
|
|
|
ecs_record_t *src_r = flecs_entities_get(world, src); |
|
ecs_assert(src_r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_t *src_table = src_r->table; |
|
if (!src_table) { |
|
goto done; |
|
} |
|
|
|
ecs_table_t *dst_table = src_table; |
|
if (src_table->flags & EcsTableHasName) { |
|
dst_table = ecs_table_remove_id(world, src_table, |
|
ecs_pair_t(EcsIdentifier, EcsName)); |
|
} |
|
|
|
ecs_type_t dst_type = dst_table->type; |
|
ecs_table_diff_t diff = { .added = dst_type }; |
|
ecs_record_t *dst_r = flecs_entities_get(world, dst); |
|
flecs_new_entity(world, dst, dst_r, dst_table, &diff, true, true); |
|
int32_t row = ECS_RECORD_TO_ROW(dst_r->row); |
|
|
|
if (copy_value) { |
|
flecs_table_move(world, dst, src, dst_table, |
|
row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); |
|
int32_t i, count = dst_table->column_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_type_t type = { |
|
.array = &dst_table->data.columns[i].id, |
|
.count = 1 |
|
}; |
|
flecs_notify_on_set(world, dst_table, row, 1, &type, true); |
|
} |
|
} |
|
|
|
done: |
|
flecs_defer_end(world, stage); |
|
return dst; |
|
error: |
|
return 0; |
|
} |
|
|
|
const void* ecs_get_id( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(flecs_stage_from_readonly_world(world)->async == false, |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_table_t *table = r->table; |
|
if (!table) { |
|
return NULL; |
|
} |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
return NULL; |
|
} |
|
|
|
const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (!tr) { |
|
return flecs_get_base_component(world, table, id, idr, 0); |
|
} else { |
|
ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); |
|
} |
|
|
|
int32_t row = ECS_RECORD_TO_ROW(r->row); |
|
return flecs_get_component_w_index(table, tr->column, row).ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void* ecs_get_mut_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
if (flecs_defer_cmd(stage)) { |
|
return flecs_defer_set(world, stage, EcsCmdMut, entity, id, 0, NULL); |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
void *result = flecs_get_mut(world, entity, id, r).ptr; |
|
ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
flecs_defer_end(world, stage); |
|
return result; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void* ecs_get_mut_modified_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); |
|
|
|
return flecs_defer_set(world, stage, EcsCmdSet, entity, id, 0, NULL); |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
ecs_record_t* flecs_access_begin( |
|
ecs_world_t *stage, |
|
ecs_entity_t entity, |
|
bool write) |
|
{ |
|
ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); |
|
|
|
const ecs_world_t *world = ecs_get_world(stage); |
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_t *table; |
|
if (!(table = r->table)) { |
|
return NULL; |
|
} |
|
|
|
int32_t count = ecs_os_ainc(&table->_->lock); |
|
(void)count; |
|
if (write) { |
|
ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); |
|
} |
|
|
|
return r; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
void flecs_access_end( |
|
const ecs_record_t *r, |
|
bool write) |
|
{ |
|
ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); |
|
ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
int32_t count = ecs_os_adec(&r->table->_->lock); |
|
(void)count; |
|
if (write) { |
|
ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); |
|
} |
|
ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); |
|
|
|
error: |
|
return; |
|
} |
|
|
|
ecs_record_t* ecs_write_begin( |
|
ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
return flecs_access_begin(world, entity, true); |
|
} |
|
|
|
void ecs_write_end( |
|
ecs_record_t *r) |
|
{ |
|
flecs_access_end(r, true); |
|
} |
|
|
|
const ecs_record_t* ecs_read_begin( |
|
ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
return flecs_access_begin(world, entity, false); |
|
} |
|
|
|
void ecs_read_end( |
|
const ecs_record_t *r) |
|
{ |
|
flecs_access_end(r, false); |
|
} |
|
|
|
ecs_entity_t ecs_record_get_entity( |
|
const ecs_record_t *record) |
|
{ |
|
ecs_check(record != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_table_t *table = record->table; |
|
if (!table) { |
|
return 0; |
|
} |
|
|
|
return ecs_vec_get_t(&table->data.entities, ecs_entity_t, |
|
ECS_RECORD_TO_ROW(record->row))[0]; |
|
error: |
|
return 0; |
|
} |
|
|
|
const void* ecs_record_get_id( |
|
const ecs_world_t *stage, |
|
const ecs_record_t *r, |
|
ecs_id_t id) |
|
{ |
|
const ecs_world_t *world = ecs_get_world(stage); |
|
return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); |
|
} |
|
|
|
bool ecs_record_has_id( |
|
ecs_world_t *stage, |
|
const ecs_record_t *r, |
|
ecs_id_t id) |
|
{ |
|
const ecs_world_t *world = ecs_get_world(stage); |
|
if (r->table) { |
|
return ecs_table_has_id(world, r->table, id); |
|
} |
|
return false; |
|
} |
|
|
|
void* ecs_record_get_mut_id( |
|
ecs_world_t *stage, |
|
ecs_record_t *r, |
|
ecs_id_t id) |
|
{ |
|
const ecs_world_t *world = ecs_get_world(stage); |
|
return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); |
|
} |
|
|
|
ecs_ref_t ecs_ref_init_id( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
ecs_record_t *record = flecs_entities_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_table_record_get(world, table, id); |
|
result.table_id = table->id; |
|
} |
|
|
|
return result; |
|
error: |
|
return (ecs_ref_t){0}; |
|
} |
|
|
|
static |
|
bool flecs_ref_needs_sync( |
|
ecs_ref_t *ref, |
|
ecs_table_record_t *tr, |
|
const ecs_table_t *table) |
|
{ |
|
ecs_assert(ref != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return !tr || tr->hdr.table != table || ref->table_id != table->id; |
|
} |
|
|
|
void ecs_ref_update( |
|
const ecs_world_t *world, |
|
ecs_ref_t *ref) |
|
{ |
|
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_record_t *r = ref->record; |
|
ecs_table_t *table = r->table; |
|
if (!table) { |
|
return; |
|
} |
|
|
|
ecs_table_record_t *tr = ref->tr; |
|
if (flecs_ref_needs_sync(ref, tr, table)) { |
|
tr = ref->tr = flecs_table_record_get(world, table, ref->id); |
|
if (!tr) { |
|
return; |
|
} |
|
|
|
ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); |
|
ref->table_id = table->id; |
|
} |
|
error: |
|
return; |
|
} |
|
|
|
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 (flecs_ref_needs_sync(ref, tr, table)) { |
|
tr = ref->tr = flecs_table_record_get(world, table, id); |
|
if (!tr) { |
|
return NULL; |
|
} |
|
|
|
ref->table_id = table->id; |
|
ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
int32_t column = tr->column; |
|
ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); |
|
return flecs_get_component_w_index(table, column, row).ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void* ecs_emplace_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, |
|
"cannot emplace a component the entity already has"); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
if (flecs_defer_cmd(stage)) { |
|
return flecs_defer_set(world, stage, EcsCmdEmplace, entity, id, 0, NULL); |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); |
|
flecs_defer_end(world, stage); |
|
|
|
void *ptr = flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); |
|
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
void flecs_modified_id_if( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id, |
|
bool owned) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
if (flecs_defer_modified(stage, entity, id)) { |
|
return; |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_table_t *table = r->table; |
|
if (!flecs_table_record_get(world, table, id)) { |
|
flecs_defer_end(world, stage); |
|
return; |
|
} |
|
|
|
ecs_type_t ids = { .array = &id, .count = 1 }; |
|
flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, owned); |
|
|
|
flecs_table_mark_dirty(world, table, id); |
|
flecs_defer_end(world, stage); |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_modified_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
if (flecs_defer_modified(stage, entity, id)) { |
|
return; |
|
} |
|
|
|
/* If the entity does not have the component, calling ecs_modified is |
|
* invalid. The assert needs to happen after the defer statement, as the |
|
* entity may not have the component when this function is called while |
|
* operations are being deferred. */ |
|
ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_record_t *r = flecs_entities_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, table, id); |
|
flecs_defer_end(world, stage); |
|
error: |
|
return; |
|
} |
|
|
|
static |
|
void flecs_copy_ptr_w_id( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_id_t id, |
|
size_t size, |
|
void *ptr) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
flecs_defer_set(world, stage, EcsCmdSet, entity, id, |
|
flecs_utosize(size), ptr); |
|
return; |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); |
|
const ecs_type_info_t *ti = dst.ti; |
|
ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (ptr) { |
|
ecs_copy_t copy = ti->hooks.copy; |
|
if (copy) { |
|
copy(dst.ptr, ptr, 1, ti); |
|
} else { |
|
ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); |
|
} |
|
} else { |
|
ecs_os_memset(dst.ptr, 0, size); |
|
} |
|
|
|
flecs_table_mark_dirty(world, r->table, id); |
|
|
|
ecs_table_t *table = r->table; |
|
if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { |
|
ecs_type_t ids = { .array = &id, .count = 1 }; |
|
flecs_notify_on_set( |
|
world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); |
|
} |
|
|
|
flecs_defer_end(world, stage); |
|
error: |
|
return; |
|
} |
|
|
|
static |
|
void flecs_move_ptr_w_id( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_id_t id, |
|
size_t size, |
|
void *ptr, |
|
ecs_cmd_kind_t cmd_kind) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
flecs_defer_set(world, stage, cmd_kind, entity, id, |
|
flecs_utosize(size), ptr); |
|
return; |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); |
|
ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const ecs_type_info_t *ti = dst.ti; |
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_move_t move; |
|
if (cmd_kind != EcsCmdEmplace) { |
|
/* ctor will have happened by get_mut */ |
|
move = ti->hooks.move_dtor; |
|
} else { |
|
move = ti->hooks.ctor_move_dtor; |
|
} |
|
if (move) { |
|
move(dst.ptr, ptr, 1, ti); |
|
} else { |
|
ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); |
|
} |
|
|
|
flecs_table_mark_dirty(world, r->table, id); |
|
|
|
if (cmd_kind == EcsCmdSet) { |
|
ecs_table_t *table = r->table; |
|
if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { |
|
ecs_type_t ids = { .array = &id, .count = 1 }; |
|
flecs_notify_on_set( |
|
world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); |
|
} |
|
} |
|
|
|
flecs_defer_end(world, stage); |
|
error: |
|
return; |
|
} |
|
|
|
ecs_entity_t ecs_set_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id, |
|
size_t size, |
|
const void *ptr) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!entity || ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
if (!entity) { |
|
entity = ecs_new_id(world); |
|
ecs_entity_t scope = stage->scope; |
|
if (scope) { |
|
ecs_add_pair(world, entity, EcsChildOf, scope); |
|
} |
|
} |
|
|
|
/* Safe to cast away const: function won't modify if move arg is false */ |
|
flecs_copy_ptr_w_id(world, stage, entity, id, size, |
|
ECS_CONST_CAST(void*, ptr)); |
|
return entity; |
|
error: |
|
return 0; |
|
} |
|
|
|
void ecs_enable_id( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id, |
|
bool enable) |
|
{ |
|
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, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
if (flecs_defer_enable(stage, entity, id, enable)) { |
|
return; |
|
} else { |
|
/* Operations invoked by enable/disable should not be deferred */ |
|
stage->defer --; |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_entity_t bs_id = id | ECS_TOGGLE; |
|
|
|
ecs_table_t *table = r->table; |
|
int32_t index = -1; |
|
if (table) { |
|
index = ecs_table_get_type_index(world, table, bs_id); |
|
} |
|
|
|
if (index == -1) { |
|
ecs_add_id(world, entity, bs_id); |
|
ecs_enable_id(world, entity, id, enable); |
|
return; |
|
} |
|
|
|
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
index -= table->_->bs_offset; |
|
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Data cannot be NULl, since entity is stored in the table */ |
|
ecs_bitset_t *bs = &table->_->bs_columns[index]; |
|
ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); |
|
error: |
|
return; |
|
} |
|
|
|
bool ecs_is_enabled_id( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Make sure we're not working with a stage */ |
|
world = ecs_get_world(world); |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_t *table = r->table; |
|
if (!table) { |
|
return false; |
|
} |
|
|
|
ecs_entity_t bs_id = id | ECS_TOGGLE; |
|
int32_t index = ecs_table_get_type_index(world, table, bs_id); |
|
if (index == -1) { |
|
/* If table does not have TOGGLE column for component, component is |
|
* always enabled, if the entity has it */ |
|
return ecs_has_id(world, entity, id); |
|
} |
|
|
|
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
index -= table->_->bs_offset; |
|
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_bitset_t *bs = &table->_->bs_columns[index]; |
|
|
|
return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_has_id( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Make sure we're not working with a stage */ |
|
world = ecs_get_world(world); |
|
|
|
ecs_record_t *r = flecs_entities_get_any(world, entity); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_t *table = r->table; |
|
if (!table) { |
|
return false; |
|
} |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
int32_t column; |
|
if (idr) { |
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (tr) { |
|
return true; |
|
} |
|
} |
|
|
|
if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { |
|
return false; |
|
} |
|
|
|
ecs_table_record_t *tr; |
|
column = ecs_search_relation(world, table, 0, id, |
|
EcsIsA, 0, 0, 0, &tr); |
|
if (column == -1) { |
|
return false; |
|
} |
|
|
|
table = tr->hdr.table; |
|
if ((table->flags & EcsTableHasUnion) && ECS_HAS_ID_FLAG(id, PAIR) && |
|
ECS_PAIR_SECOND(id) != EcsWildcard) |
|
{ |
|
if (ECS_PAIR_FIRST(table->type.array[column]) == EcsUnion) { |
|
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_switch_t *sw = &table->_->sw_columns[ |
|
column - table->_->sw_offset]; |
|
int32_t row = ECS_RECORD_TO_ROW(r->row); |
|
uint64_t value = flecs_switch_get(sw, row); |
|
return value == ecs_pair_second(world, id); |
|
} |
|
} |
|
|
|
return true; |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_owns_id( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
return (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1); |
|
} |
|
|
|
ecs_entity_t ecs_get_target( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t rel, |
|
int32_t index) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_t *table = r->table; |
|
if (!table) { |
|
goto not_found; |
|
} |
|
|
|
ecs_id_t wc = ecs_pair(rel, EcsWildcard); |
|
ecs_id_record_t *idr = flecs_id_record_get(world, wc); |
|
const ecs_table_record_t *tr = NULL; |
|
if (idr) { |
|
tr = flecs_id_record_get_table(idr, table); |
|
} |
|
if (!tr) { |
|
if (table->flags & EcsTableHasUnion) { |
|
wc = ecs_pair(EcsUnion, rel); |
|
tr = flecs_table_record_get(world, table, wc); |
|
if (tr) { |
|
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_switch_t *sw = &table->_->sw_columns[ |
|
tr->index - table->_->sw_offset]; |
|
int32_t row = ECS_RECORD_TO_ROW(r->row); |
|
return flecs_switch_get(sw, row); |
|
|
|
} |
|
} |
|
|
|
if (!idr || !(idr->flags & EcsIdDontInherit)) { |
|
goto look_in_base; |
|
} else { |
|
return 0; |
|
} |
|
} else if (table->flags & EcsTableHasTarget) { |
|
EcsTarget *tf = ecs_table_get_id(world, table, |
|
ecs_pair_t(EcsTarget, rel), ECS_RECORD_TO_ROW(r->row)); |
|
if (tf) { |
|
return ecs_record_get_entity(tf->target); |
|
} |
|
} |
|
|
|
if (index >= tr->count) { |
|
index -= tr->count; |
|
goto look_in_base; |
|
} |
|
|
|
return ecs_pair_second(world, table->type.array[tr->index + index]); |
|
look_in_base: |
|
if (table->flags & EcsTableHasIsA) { |
|
ecs_table_record_t *tr_isa = flecs_id_record_get_table( |
|
world->idr_isa_wildcard, table); |
|
ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_id_t *ids = table->type.array; |
|
int32_t i = tr_isa->index, end = (i + tr_isa->count); |
|
for (; i < end; i ++) { |
|
ecs_id_t isa_pair = ids[i]; |
|
ecs_entity_t base = ecs_pair_second(world, isa_pair); |
|
ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_entity_t t = ecs_get_target(world, base, rel, index); |
|
if (t) { |
|
return t; |
|
} |
|
} |
|
} |
|
|
|
not_found: |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_get_parent( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
return ecs_get_target(world, entity, EcsChildOf, 0); |
|
} |
|
|
|
ecs_entity_t ecs_get_target_for_id( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t rel, |
|
ecs_id_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!id) { |
|
return ecs_get_target(world, entity, rel, 0); |
|
} |
|
|
|
world = ecs_get_world(world); |
|
|
|
ecs_table_t *table = ecs_get_table(world, entity); |
|
ecs_entity_t subject = 0; |
|
|
|
if (rel) { |
|
int32_t column = ecs_search_relation( |
|
world, table, 0, id, rel, 0, &subject, 0, 0); |
|
if (column == -1) { |
|
return 0; |
|
} |
|
} else { |
|
entity = 0; /* Don't return entity if id was not found */ |
|
|
|
if (table) { |
|
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]; |
|
if (ent & ECS_ID_FLAGS_MASK) { |
|
/* Skip ids with pairs, roles since 0 was provided for rel */ |
|
break; |
|
} |
|
|
|
if (ecs_has_id(world, ent, id)) { |
|
subject = ent; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (subject == 0) { |
|
return entity; |
|
} else { |
|
return subject; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
int32_t ecs_get_depth( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t rel) |
|
{ |
|
ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_table_t *table = ecs_get_table(world, entity); |
|
if (table) { |
|
return ecs_table_get_depth(world, table, rel); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_id_for_depth( |
|
ecs_world_t *world, |
|
int32_t depth) |
|
{ |
|
ecs_vec_t *depth_ids = &world->store.depth_ids; |
|
int32_t i, count = ecs_vec_count(depth_ids); |
|
for (i = count; i <= depth; i ++) { |
|
ecs_entity_t *el = ecs_vec_append_t( |
|
&world->allocator, depth_ids, ecs_entity_t); |
|
el[0] = ecs_new_w_pair(world, EcsChildOf, EcsFlecsInternals); |
|
ecs_map_val_t *v = ecs_map_ensure(&world->store.entity_to_depth, el[0]); |
|
v[0] = flecs_ito(uint64_t, i); |
|
} |
|
|
|
return ecs_vec_get_t(&world->store.depth_ids, ecs_entity_t, depth)[0]; |
|
} |
|
|
|
static |
|
int32_t flecs_depth_for_id( |
|
ecs_world_t *world, |
|
ecs_entity_t id) |
|
{ |
|
ecs_map_val_t *v = ecs_map_get(&world->store.entity_to_depth, id); |
|
ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return flecs_uto(int32_t, v[0]); |
|
} |
|
|
|
static |
|
int32_t flecs_depth_for_flat_table( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_assert(table->flags & EcsTableHasTarget, ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_t id; |
|
int32_t col = ecs_search(world, table, |
|
ecs_pair(EcsFlatten, EcsWildcard), &id); |
|
ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); |
|
(void)col; |
|
return flecs_depth_for_id(world, ECS_PAIR_SECOND(id)); |
|
} |
|
|
|
static |
|
void flecs_flatten( |
|
ecs_world_t *world, |
|
ecs_entity_t root, |
|
ecs_id_t pair, |
|
int32_t depth, |
|
const ecs_flatten_desc_t *desc) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, pair); |
|
if (!idr) { |
|
return; |
|
} |
|
|
|
ecs_entity_t depth_id = flecs_id_for_depth(world, depth); |
|
ecs_id_t root_pair = ecs_pair(EcsChildOf, root); |
|
ecs_id_t tgt_pair = ecs_pair_t(EcsTarget, EcsChildOf); |
|
ecs_id_t depth_pair = ecs_pair(EcsFlatten, depth_id); |
|
ecs_id_t name_pair = ecs_pair_t(EcsIdentifier, EcsName); |
|
|
|
ecs_entity_t rel = ECS_PAIR_FIRST(pair); |
|
ecs_table_cache_iter_t it; |
|
if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { |
|
const ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
int32_t i, count = ecs_table_count(table); |
|
bool has_tgt = table->flags & EcsTableHasTarget; |
|
flecs_emit_propagate_invalidate(world, table, 0, count); |
|
|
|
ecs_entity_t *entities = table->data.entities.array; |
|
for (i = 0; i < count; i ++) { |
|
ecs_record_t *record = flecs_entities_get(world, entities[i]); |
|
ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); |
|
if (flags & EcsEntityIsTarget) { |
|
flecs_flatten(world, root, ecs_pair(rel, entities[i]), |
|
depth + 1, desc); |
|
} |
|
} |
|
|
|
ecs_table_diff_t tmpdiff; |
|
ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; |
|
flecs_table_diff_builder_init(world, &diff); |
|
ecs_table_t *dst; |
|
|
|
dst = flecs_table_traverse_add(world, table, &root_pair, &tmpdiff); |
|
flecs_table_diff_build_append_table(world, &diff, &tmpdiff); |
|
|
|
dst = flecs_table_traverse_add(world, dst, &tgt_pair, &tmpdiff); |
|
flecs_table_diff_build_append_table(world, &diff, &tmpdiff); |
|
|
|
if (!desc->lose_depth) { |
|
if (!has_tgt) { |
|
dst = flecs_table_traverse_add(world, dst, &depth_pair, &tmpdiff); |
|
flecs_table_diff_build_append_table(world, &diff, &tmpdiff); |
|
} else { |
|
int32_t cur_depth = flecs_depth_for_flat_table(world, table); |
|
cur_depth += depth; |
|
ecs_entity_t e_depth = flecs_id_for_depth(world, cur_depth); |
|
ecs_id_t p_depth = ecs_pair(EcsFlatten, e_depth); |
|
dst = flecs_table_traverse_add(world, dst, &p_depth, &tmpdiff); |
|
flecs_table_diff_build_append_table(world, &diff, &tmpdiff); |
|
} |
|
} |
|
|
|
if (!desc->keep_names) { |
|
dst = flecs_table_traverse_remove(world, dst, &name_pair, &tmpdiff); |
|
flecs_table_diff_build_append_table(world, &diff, &tmpdiff); |
|
} |
|
|
|
int32_t dst_count = ecs_table_count(dst); |
|
|
|
ecs_table_diff_t td; |
|
flecs_table_diff_build_noalloc(&diff, &td); |
|
flecs_notify_on_remove(world, table, NULL, 0, count, &td.removed); |
|
flecs_table_merge(world, dst, table, &dst->data, &table->data); |
|
flecs_notify_on_add(world, dst, NULL, dst_count, count, |
|
&td.added, 0); |
|
flecs_table_diff_builder_fini(world, &diff); |
|
|
|
EcsTarget *fh = ecs_table_get_id(world, dst, tgt_pair, 0); |
|
ecs_assert(fh != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); |
|
int32_t remain = count; |
|
|
|
for (i = dst_count; i < (dst_count + count); i ++) { |
|
if (!has_tgt) { |
|
fh[i].target = flecs_entities_get_any(world, |
|
ECS_PAIR_SECOND(pair)); |
|
fh[i].count = remain; |
|
remain --; |
|
} |
|
ecs_assert(fh[i].target != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
} |
|
|
|
ecs_delete_with(world, pair); |
|
flecs_id_record_release(world, idr); |
|
} |
|
|
|
void ecs_flatten( |
|
ecs_world_t *world, |
|
ecs_id_t pair, |
|
const ecs_flatten_desc_t *desc) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_entity_t rel = ECS_PAIR_FIRST(pair); |
|
ecs_entity_t root = ecs_pair_second(world, pair); |
|
ecs_flatten_desc_t private_desc = {0}; |
|
if (desc) { |
|
private_desc = *desc; |
|
} |
|
|
|
ecs_run_aperiodic(world, 0); |
|
ecs_defer_begin(world); |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, pair); |
|
|
|
ecs_table_cache_iter_t it; |
|
if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { |
|
const ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
if (!table->_->traversable_count) { |
|
continue; |
|
} |
|
|
|
if (table->flags & EcsTableIsPrefab) { |
|
continue; |
|
} |
|
|
|
int32_t i, count = ecs_table_count(table); |
|
ecs_entity_t *entities = table->data.entities.array; |
|
for (i = 0; i < count; i ++) { |
|
ecs_record_t *record = flecs_entities_get(world, entities[i]); |
|
ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); |
|
if (flags & EcsEntityIsTarget) { |
|
flecs_flatten(world, root, ecs_pair(rel, entities[i]), 1, |
|
&private_desc); |
|
} |
|
} |
|
} |
|
} |
|
|
|
ecs_defer_end(world); |
|
} |
|
|
|
static |
|
const char* flecs_get_identifier( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
|
|
const EcsIdentifier *ptr = ecs_get_pair( |
|
world, entity, EcsIdentifier, tag); |
|
|
|
if (ptr) { |
|
return ptr->value; |
|
} else { |
|
return NULL; |
|
} |
|
error: |
|
return NULL; |
|
} |
|
|
|
const char* ecs_get_name( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
return flecs_get_identifier(world, entity, EcsName); |
|
} |
|
|
|
const char* ecs_get_symbol( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
world = ecs_get_world(world); |
|
if (ecs_owns_pair(world, entity, ecs_id(EcsIdentifier), EcsSymbol)) { |
|
return flecs_get_identifier(world, entity, EcsSymbol); |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_set_identifier( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag, |
|
const char *name) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!entity) { |
|
entity = ecs_new_id(world); |
|
} |
|
|
|
if (!name) { |
|
ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); |
|
return entity; |
|
} |
|
|
|
EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (tag == EcsName) { |
|
/* Insert command after get_mut, but before the name is potentially |
|
* freed. Even though the name is a const char*, it is possible that the |
|
* application passed in the existing name of the entity which could |
|
* still cause it to be freed. */ |
|
flecs_defer_path(stage, 0, entity, name); |
|
} |
|
|
|
ecs_os_strset(&ptr->value, name); |
|
ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); |
|
|
|
return entity; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_set_name( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *name) |
|
{ |
|
if (!entity) { |
|
return ecs_entity(world, { |
|
.name = name |
|
}); |
|
} |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
flecs_set_identifier(world, stage, entity, EcsName, name); |
|
|
|
return entity; |
|
} |
|
|
|
ecs_entity_t ecs_set_symbol( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *name) |
|
{ |
|
return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); |
|
} |
|
|
|
void ecs_set_alias( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *name) |
|
{ |
|
flecs_set_identifier(world, NULL, entity, EcsAlias, name); |
|
} |
|
|
|
ecs_id_t ecs_make_pair( |
|
ecs_entity_t relationship, |
|
ecs_entity_t target) |
|
{ |
|
ecs_assert(!ECS_IS_PAIR(relationship) && !ECS_IS_PAIR(target), |
|
ECS_INVALID_PARAMETER, "cannot create nested pairs"); |
|
return ecs_pair(relationship, target); |
|
} |
|
|
|
bool ecs_is_valid( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* 0 is not a valid entity id */ |
|
if (!entity) { |
|
return false; |
|
} |
|
|
|
/* Entity identifiers should not contain flag bits */ |
|
if (entity & ECS_ID_FLAGS_MASK) { |
|
return false; |
|
} |
|
|
|
/* Entities should not contain data in dead zone bits */ |
|
if (entity & ~0xFF00FFFFFFFFFFFF) { |
|
return false; |
|
} |
|
|
|
/* If entity doesn't exist in the world, the id is valid as long as the |
|
* generation is 0. Using a non-existing id with a non-zero generation |
|
* requires calling ecs_ensure first. */ |
|
if (!ecs_exists(world, entity)) { |
|
return ECS_GENERATION(entity) == 0; |
|
} |
|
|
|
/* If id exists, it must be alive (the generation count must match) */ |
|
return ecs_is_alive(world, entity); |
|
error: |
|
return false; |
|
} |
|
|
|
ecs_id_t ecs_strip_generation( |
|
ecs_entity_t e) |
|
{ |
|
/* If this is not a pair, erase the generation bits */ |
|
if (!(e & ECS_ID_FLAGS_MASK)) { |
|
e &= ~ECS_GENERATION_MASK; |
|
} |
|
|
|
return e; |
|
} |
|
|
|
bool ecs_is_alive( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
return flecs_entities_is_alive(world, entity); |
|
error: |
|
return false; |
|
} |
|
|
|
ecs_entity_t ecs_get_alive( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!entity) { |
|
return 0; |
|
} |
|
|
|
/* Make sure we're not working with a stage */ |
|
world = ecs_get_world(world); |
|
|
|
if (flecs_entities_is_alive(world, entity)) { |
|
return entity; |
|
} |
|
|
|
/* Make sure id does not have generation. This guards against accidentally |
|
* "upcasting" a not alive identifier to a alive one. */ |
|
if ((uint32_t)entity != entity) { |
|
return 0; |
|
} |
|
|
|
ecs_entity_t current = flecs_entities_get_generation(world, entity); |
|
if (!current || !flecs_entities_is_alive(world, current)) { |
|
return 0; |
|
} |
|
|
|
return current; |
|
error: |
|
return 0; |
|
} |
|
|
|
void ecs_ensure( |
|
ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Const cast is safe, function checks for threading */ |
|
world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); |
|
|
|
/* The entity index can be mutated while in staged/readonly mode, as long as |
|
* the world is not multithreaded. */ |
|
ecs_assert(!(world->flags & EcsWorldMultiThreaded), |
|
ECS_INVALID_OPERATION, NULL); |
|
|
|
/* Check if a version of the provided id is alive */ |
|
ecs_entity_t any = ecs_get_alive(world, (uint32_t)entity); |
|
if (any == entity) { |
|
/* If alive and equal to the argument, there's nothing left to do */ |
|
return; |
|
} |
|
|
|
/* If the id is currently alive but did not match the argument, fail */ |
|
ecs_check(!any, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Set generation if not alive. The sparse set checks if the provided |
|
* id matches its own generation which is necessary for alive ids. This |
|
* check would cause ecs_ensure to fail if the generation of the 'entity' |
|
* argument doesn't match with its generation. |
|
* |
|
* While this could've been addressed in the sparse set, this is a rare |
|
* scenario that can only be triggered by ecs_ensure. Implementing it here |
|
* allows the sparse set to not do this check, which is more efficient. */ |
|
flecs_entities_set_generation(world, entity); |
|
|
|
/* Ensure id exists. The underlying data structure will verify that the |
|
* generation count matches the provided one. */ |
|
flecs_entities_ensure(world, entity); |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_ensure_id( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
ecs_entity_t r = ECS_PAIR_FIRST(id); |
|
ecs_entity_t o = ECS_PAIR_SECOND(id); |
|
|
|
ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (flecs_entities_get_generation(world, r) == 0) { |
|
ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, |
|
"first element of pair is not alive"); |
|
flecs_entities_ensure(world, r); |
|
} |
|
if (flecs_entities_get_generation(world, o) == 0) { |
|
ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, |
|
"second element of pair is not alive"); |
|
flecs_entities_ensure(world, o); |
|
} |
|
} else { |
|
flecs_entities_ensure(world, id & ECS_COMPONENT_MASK); |
|
} |
|
error: |
|
return; |
|
} |
|
|
|
bool ecs_exists( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
return flecs_entities_exists(world, entity); |
|
error: |
|
return false; |
|
} |
|
|
|
ecs_table_t* ecs_get_table( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
ecs_record_t *record = flecs_entities_get(world, entity); |
|
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return record->table; |
|
error: |
|
return NULL; |
|
} |
|
|
|
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 NULL; |
|
} |
|
|
|
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); |
|
ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr && ECS_IS_PAIR(id)) { |
|
idr = flecs_id_record_get(world, |
|
ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); |
|
if (!idr || !idr->type_info) { |
|
idr = NULL; |
|
} |
|
if (!idr) { |
|
ecs_entity_t first = ecs_pair_first(world, id); |
|
if (!first || !ecs_has_id(world, first, EcsTag)) { |
|
idr = flecs_id_record_get(world, |
|
ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); |
|
if (!idr || !idr->type_info) { |
|
idr = NULL; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (idr) { |
|
return idr->type_info; |
|
} else if (!(id & ECS_ID_FLAGS_MASK)) { |
|
return flecs_type_info_get(world, id); |
|
} |
|
error: |
|
return NULL; |
|
} |
|
|
|
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) { |
|
ecs_assert(ti->component != 0, ECS_INTERNAL_ERROR, NULL); |
|
return ti->component; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
bool ecs_id_is_tag( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
if (ecs_id_is_wildcard(id)) { |
|
/* If id is a wildcard, we can't tell if it's a tag or not, except |
|
* when the relationship part of a pair has the Tag property */ |
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
if (ECS_PAIR_FIRST(id) != EcsWildcard) { |
|
ecs_entity_t rel = ecs_pair_first(world, id); |
|
if (ecs_is_valid(world, rel)) { |
|
if (ecs_has_id(world, rel, EcsTag)) { |
|
return true; |
|
} |
|
} else { |
|
/* During bootstrap it's possible that not all ids are valid |
|
* yet. Using ecs_get_typeid will ensure correct values are |
|
* returned for only those components initialized during |
|
* bootstrap, while still asserting if another invalid id |
|
* is provided. */ |
|
if (ecs_get_typeid(world, id) == 0) { |
|
return true; |
|
} |
|
} |
|
} else { |
|
/* If relationship is wildcard id is not guaranteed to be a tag */ |
|
} |
|
} |
|
} else { |
|
if (ecs_get_typeid(world, id) == 0) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool ecs_id_is_union( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
if (!ECS_IS_PAIR(id)) { |
|
return false; |
|
} else if (ECS_PAIR_FIRST(id) == EcsUnion) { |
|
return true; |
|
} else { |
|
ecs_entity_t first = ecs_pair_first(world, id); |
|
if (ecs_has_id(world, first, EcsUnion)) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
int32_t ecs_count_id( |
|
const ecs_world_t *world, |
|
ecs_entity_t id) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!id) { |
|
return 0; |
|
} |
|
|
|
int32_t count = 0; |
|
ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { |
|
.id = id, |
|
.src.flags = EcsSelf, |
|
.flags = EcsTermMatchDisabled|EcsTermMatchPrefab |
|
}); |
|
|
|
it.flags |= EcsIterNoData; |
|
it.flags |= EcsIterEvalTables; |
|
|
|
while (ecs_term_next(&it)) { |
|
count += it.count; |
|
} |
|
|
|
return count; |
|
error: |
|
return 0; |
|
} |
|
|
|
void ecs_enable( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
bool enabled) |
|
{ |
|
if (ecs_has_id(world, entity, EcsPrefab)) { |
|
/* If entity is a type, enable/disable all entities in the type */ |
|
const ecs_type_t *type = ecs_get_type(world, entity); |
|
ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_t *ids = type->array; |
|
int32_t i, count = type->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_id_t id = ids[i]; |
|
if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { |
|
continue; |
|
} |
|
ecs_enable(world, id, enabled); |
|
} |
|
} else { |
|
if (enabled) { |
|
ecs_remove_id(world, entity, EcsDisabled); |
|
} else { |
|
ecs_add_id(world, entity, EcsDisabled); |
|
} |
|
} |
|
} |
|
|
|
bool ecs_defer_begin( |
|
ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
return flecs_defer_begin(world, stage); |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_defer_end( |
|
ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
return flecs_defer_end(world, stage); |
|
error: |
|
return false; |
|
} |
|
|
|
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 > 0, ECS_INVALID_OPERATION, NULL); |
|
stage->defer = -stage->defer; |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_defer_resume( |
|
ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, NULL); |
|
stage->defer = -stage->defer; |
|
error: |
|
return; |
|
} |
|
|
|
const char* ecs_id_flag_str( |
|
ecs_entity_t entity) |
|
{ |
|
if (ECS_HAS_ID_FLAG(entity, PAIR)) { |
|
return "PAIR"; |
|
} else |
|
if (ECS_HAS_ID_FLAG(entity, TOGGLE)) { |
|
return "TOGGLE"; |
|
} else |
|
if (ECS_HAS_ID_FLAG(entity, AND)) { |
|
return "AND"; |
|
} else |
|
if (ECS_HAS_ID_FLAG(entity, OVERRIDE)) { |
|
return "OVERRIDE"; |
|
} else { |
|
return "UNKNOWN"; |
|
} |
|
} |
|
|
|
void ecs_id_str_buf( |
|
const ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
if (ECS_HAS_ID_FLAG(id, TOGGLE)) { |
|
ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); |
|
ecs_strbuf_appendch(buf, '|'); |
|
} |
|
|
|
if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { |
|
ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); |
|
ecs_strbuf_appendch(buf, '|'); |
|
} |
|
|
|
if (ECS_HAS_ID_FLAG(id, AND)) { |
|
ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); |
|
ecs_strbuf_appendch(buf, '|'); |
|
} |
|
|
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
ecs_entity_t rel = ECS_PAIR_FIRST(id); |
|
ecs_entity_t obj = ECS_PAIR_SECOND(id); |
|
|
|
ecs_entity_t e; |
|
if ((e = ecs_get_alive(world, rel))) { |
|
rel = e; |
|
} |
|
if ((e = ecs_get_alive(world, obj))) { |
|
obj = e; |
|
} |
|
|
|
ecs_strbuf_appendch(buf, '('); |
|
ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); |
|
ecs_strbuf_appendch(buf, ','); |
|
ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); |
|
ecs_strbuf_appendch(buf, ')'); |
|
} else { |
|
ecs_entity_t e = id & ECS_COMPONENT_MASK; |
|
ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
char* ecs_id_str( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
ecs_id_str_buf(world, id, &buf); |
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
static |
|
void ecs_type_str_buf( |
|
const ecs_world_t *world, |
|
const ecs_type_t *type, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_entity_t *ids = type->array; |
|
int32_t i, count = type->count; |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t id = ids[i]; |
|
|
|
if (i) { |
|
ecs_strbuf_appendch(buf, ','); |
|
ecs_strbuf_appendch(buf, ' '); |
|
} |
|
|
|
if (id == 1) { |
|
ecs_strbuf_appendlit(buf, "Component"); |
|
} else { |
|
ecs_id_str_buf(world, id, buf); |
|
} |
|
} |
|
} |
|
|
|
char* ecs_type_str( |
|
const ecs_world_t *world, |
|
const ecs_type_t *type) |
|
{ |
|
if (!type) { |
|
return ecs_os_strdup(""); |
|
} |
|
|
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
ecs_type_str_buf(world, type, &buf); |
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
char* ecs_table_str( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table) |
|
{ |
|
if (table) { |
|
return ecs_type_str(world, &table->type); |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
char* ecs_entity_str( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); |
|
|
|
ecs_strbuf_appendlit(&buf, " ["); |
|
const ecs_type_t *type = ecs_get_type(world, entity); |
|
if (type) { |
|
ecs_type_str_buf(world, type, &buf); |
|
} |
|
ecs_strbuf_appendch(&buf, ']'); |
|
|
|
return ecs_strbuf_get(&buf); |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
void flecs_flush_bulk_new( |
|
ecs_world_t *world, |
|
ecs_cmd_t *cmd) |
|
{ |
|
ecs_entity_t *entities = cmd->is._n.entities; |
|
|
|
if (cmd->id) { |
|
int i, count = cmd->is._n.count; |
|
for (i = 0; i < count; i ++) { |
|
flecs_entities_ensure(world, entities[i]); |
|
flecs_add_id(world, entities[i], cmd->id); |
|
} |
|
} |
|
|
|
ecs_os_free(entities); |
|
} |
|
|
|
static |
|
void flecs_dtor_value( |
|
ecs_world_t *world, |
|
ecs_id_t id, |
|
void *value, |
|
int32_t count) |
|
{ |
|
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->hooks.dtor; |
|
if (dtor) { |
|
ecs_size_t size = ti->size; |
|
void *ptr; |
|
int i; |
|
for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { |
|
dtor(ptr, 1, ti); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_free_cmd_event( |
|
ecs_world_t *world, |
|
ecs_event_desc_t *desc) |
|
{ |
|
if (desc->ids) { |
|
flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); |
|
} |
|
|
|
/* Safe const cast, command makes a copy of type object */ |
|
flecs_stack_free_t(ECS_CONST_CAST(ecs_type_t*, desc->ids), |
|
ecs_type_t); |
|
|
|
if (desc->param) { |
|
flecs_dtor_value(world, desc->event, |
|
/* Safe const cast, command makes copy of value */ |
|
ECS_CONST_CAST(void*, desc->param), 1); |
|
} |
|
} |
|
|
|
static |
|
void flecs_discard_cmd( |
|
ecs_world_t *world, |
|
ecs_cmd_t *cmd) |
|
{ |
|
if (cmd->kind == EcsCmdBulkNew) { |
|
ecs_os_free(cmd->is._n.entities); |
|
} else if (cmd->kind == EcsCmdEvent) { |
|
flecs_free_cmd_event(world, cmd->is._1.value); |
|
} else { |
|
ecs_assert(cmd->kind != EcsCmdEvent, ECS_INTERNAL_ERROR, NULL); |
|
void *value = cmd->is._1.value; |
|
if (value) { |
|
flecs_dtor_value(world, cmd->id, value, 1); |
|
flecs_stack_free(value, cmd->is._1.size); |
|
} |
|
} |
|
} |
|
|
|
static |
|
bool flecs_remove_invalid( |
|
ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_id_t *id_out) |
|
{ |
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
ecs_entity_t rel = ECS_PAIR_FIRST(id); |
|
if (!flecs_entities_is_valid(world, rel)) { |
|
/* After relationship is deleted we can no longer see what its |
|
* delete action was, so pretend this never happened */ |
|
*id_out = 0; |
|
return true; |
|
} else { |
|
ecs_entity_t obj = ECS_PAIR_SECOND(id); |
|
if (!flecs_entities_is_valid(world, obj)) { |
|
/* Check the relationship's policy for deleted objects */ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, |
|
ecs_pair(rel, EcsWildcard)); |
|
if (idr) { |
|
ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(idr->flags); |
|
if (action == EcsDelete) { |
|
/* Entity should be deleted, don't bother checking |
|
* other ids */ |
|
return false; |
|
} else if (action == EcsPanic) { |
|
/* If policy is throw this object should not have |
|
* been deleted */ |
|
flecs_throw_invalid_delete(world, id); |
|
} else { |
|
*id_out = 0; |
|
return true; |
|
} |
|
} else { |
|
*id_out = 0; |
|
return true; |
|
} |
|
} |
|
} |
|
} else { |
|
id &= ECS_COMPONENT_MASK; |
|
if (!flecs_entities_is_valid(world, id)) { |
|
/* After relationship is deleted we can no longer see what its |
|
* delete action was, so pretend this never happened */ |
|
*id_out = 0; |
|
return true; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
ecs_table_t* flecs_cmd_batch_add_diff( |
|
ecs_world_t *world, |
|
ecs_table_t *dst, |
|
ecs_table_t *cur, |
|
ecs_table_t *prev) |
|
{ |
|
int32_t p = 0, p_count = prev->type.count; |
|
int32_t c = 0, c_count = cur->type.count; |
|
ecs_id_t *p_ids = prev->type.array; |
|
ecs_id_t *c_ids = cur->type.array; |
|
|
|
for (; c < c_count;) { |
|
ecs_id_t c_id = c_ids[c]; |
|
ecs_id_t p_id = p_ids[p]; |
|
|
|
if (p_id < c_id) { |
|
/* Previous id no longer exists in new table, so it was removed */ |
|
dst = ecs_table_remove_id(world, dst, p_id); |
|
} |
|
if (c_id < p_id) { |
|
/* Current id didn't exist in previous table, so it was added */ |
|
dst = ecs_table_add_id(world, dst, c_id); |
|
} |
|
|
|
c += c_id <= p_id; |
|
p += p_id <= c_id; |
|
if (p == p_count) { |
|
break; |
|
} |
|
} |
|
|
|
/* Remainder */ |
|
for (; p < p_count; p ++) { |
|
ecs_id_t p_id = p_ids[p]; |
|
dst = ecs_table_remove_id(world, dst, p_id); |
|
} |
|
for (; c < c_count; c ++) { |
|
ecs_id_t c_id = c_ids[c]; |
|
dst = ecs_table_add_id(world, dst, c_id); |
|
} |
|
|
|
return dst; |
|
} |
|
|
|
static |
|
void flecs_cmd_batch_for_entity( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *diff, |
|
ecs_entity_t entity, |
|
ecs_cmd_t *cmds, |
|
int32_t start) |
|
{ |
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
ecs_table_t *table = NULL; |
|
if (r) { |
|
table = r->table; |
|
} |
|
|
|
world->info.cmd.batched_entity_count ++; |
|
|
|
ecs_table_t *start_table = table; |
|
ecs_cmd_t *cmd; |
|
int32_t next_for_entity; |
|
ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ |
|
int32_t cur = start; |
|
ecs_id_t id; |
|
bool has_set = false; |
|
|
|
do { |
|
cmd = &cmds[cur]; |
|
id = cmd->id; |
|
next_for_entity = cmd->next_for_entity; |
|
if (next_for_entity < 0) { |
|
/* First command for an entity has a negative index, flip sign */ |
|
next_for_entity *= -1; |
|
} |
|
|
|
/* Check if added id is still valid (like is the parent of a ChildOf |
|
* pair still alive), if not run cleanup actions for entity */ |
|
if (id) { |
|
if (flecs_remove_invalid(world, id, &id)) { |
|
if (!id) { |
|
/* Entity should remain alive but id should not be added */ |
|
cmd->kind = EcsCmdSkip; |
|
continue; |
|
} |
|
/* Entity should remain alive and id is still valid */ |
|
} else { |
|
/* Id was no longer valid and had a Delete policy */ |
|
cmd->kind = EcsCmdSkip; |
|
ecs_delete(world, entity); |
|
flecs_table_diff_builder_clear(diff); |
|
return; |
|
} |
|
} |
|
|
|
ecs_cmd_kind_t kind = cmd->kind; |
|
switch(kind) { |
|
case EcsCmdAddModified: |
|
/* Add is batched, but keep Modified */ |
|
cmd->kind = EcsCmdModified; |
|
|
|
/* fall through */ |
|
case EcsCmdAdd: |
|
table = flecs_find_table_add(world, table, id, diff); |
|
world->info.cmd.batched_command_count ++; |
|
break; |
|
case EcsCmdModified: |
|
if (start_table) { |
|
/* If a modified was inserted for an existing component, the value |
|
* of the component could have been changed. If this is the case, |
|
* call on_set hooks before the OnAdd/OnRemove observers are invoked |
|
* when moving the entity to a different table. |
|
* This ensures that if OnAdd/OnRemove observers access the modified |
|
* component value, the on_set hook has had the opportunity to |
|
* run first to set any computed values of the component. */ |
|
int32_t row = ECS_RECORD_TO_ROW(r->row); |
|
flecs_component_ptr_t ptr = flecs_get_component_ptr( |
|
world, start_table, row, cmd->id); |
|
if (ptr.ptr) { |
|
const ecs_type_info_t *ti = ptr.ti; |
|
ecs_iter_action_t on_set; |
|
if ((on_set = ti->hooks.on_set)) { |
|
ecs_table_t *prev_table = r->table; |
|
ecs_defer_begin(world); |
|
flecs_invoke_hook(world, start_table, 1, row, &entity, |
|
ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); |
|
ecs_defer_end(world); |
|
|
|
/* Don't run on_set hook twice, but make sure to still |
|
* invoke observers. */ |
|
cmd->kind = EcsCmdModifiedNoHook; |
|
|
|
/* If entity changed tables in hook, add difference to |
|
* destination table, so we don't lose the side effects |
|
* of the hook. */ |
|
if (r->table != prev_table) { |
|
table = flecs_cmd_batch_add_diff( |
|
world, table, r->table, prev_table); |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
case EcsCmdSet: |
|
case EcsCmdMut: |
|
table = flecs_find_table_add(world, table, id, diff); |
|
world->info.cmd.batched_command_count ++; |
|
has_set = true; |
|
break; |
|
case EcsCmdEmplace: |
|
/* Don't add for emplace, as this requires a special call to ensure |
|
* the constructor is not invoked for the component */ |
|
break; |
|
case EcsCmdRemove: |
|
table = flecs_find_table_remove(world, table, id, diff); |
|
world->info.cmd.batched_command_count ++; |
|
break; |
|
case EcsCmdClear: |
|
if (table) { |
|
ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, |
|
&diff->removed, ecs_id_t, table->type.count); |
|
ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, |
|
table->type.count); |
|
} |
|
table = &world->store.root; |
|
world->info.cmd.batched_command_count ++; |
|
break; |
|
case EcsCmdClone: |
|
case EcsCmdBulkNew: |
|
case EcsCmdPath: |
|
case EcsCmdDelete: |
|
case EcsCmdOnDeleteAction: |
|
case EcsCmdEnable: |
|
case EcsCmdDisable: |
|
case EcsCmdEvent: |
|
case EcsCmdSkip: |
|
case EcsCmdModifiedNoHook: |
|
break; |
|
} |
|
|
|
/* Add, remove and clear operations can be skipped since they have no |
|
* side effects besides adding/removing components */ |
|
if (kind == EcsCmdAdd || kind == EcsCmdRemove || kind == EcsCmdClear) { |
|
cmd->kind = EcsCmdSkip; |
|
} |
|
} while ((cur = next_for_entity)); |
|
|
|
/* Move entity to destination table in single operation */ |
|
flecs_table_diff_build_noalloc(diff, &table_diff); |
|
flecs_defer_begin(world, &world->stages[0]); |
|
flecs_commit(world, entity, r, table, &table_diff, true, 0); |
|
flecs_defer_end(world, &world->stages[0]); |
|
flecs_table_diff_builder_clear(diff); |
|
|
|
/* If the batch contains set commands, copy the component value from the |
|
* temporary command storage to the actual component storage before OnSet |
|
* observers are invoked. This ensures that for multi-component OnSet |
|
* observers all components are assigned a valid value before the observer |
|
* is invoked. |
|
* This only happens for entities that didn't have the assigned component |
|
* yet, as for entities that did have the component already the value will |
|
* have been assigned directly to the component storage. */ |
|
if (has_set) { |
|
cur = start; |
|
do { |
|
cmd = &cmds[cur]; |
|
next_for_entity = cmd->next_for_entity; |
|
if (next_for_entity < 0) { |
|
next_for_entity *= -1; |
|
} |
|
switch(cmd->kind) { |
|
case EcsCmdSet: |
|
case EcsCmdMut: { |
|
flecs_component_ptr_t ptr = {0}; |
|
if (r->table) { |
|
ptr = flecs_get_component_ptr(world, |
|
r->table, ECS_RECORD_TO_ROW(r->row), cmd->id); |
|
} |
|
|
|
/* It's possible that even though the component was set, the |
|
* command queue also contained a remove command, so before we |
|
* do anything ensure the entity actually has the component. */ |
|
if (ptr.ptr) { |
|
const ecs_type_info_t *ti = ptr.ti; |
|
ecs_move_t move = ti->hooks.move; |
|
if (move) { |
|
move(ptr.ptr, cmd->is._1.value, 1, ti); |
|
ecs_xtor_t dtor = ti->hooks.dtor; |
|
if (dtor) { |
|
dtor(cmd->is._1.value, 1, ti); |
|
cmd->is._1.value = NULL; |
|
} |
|
} else { |
|
ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); |
|
} |
|
if (cmd->kind == EcsCmdSet) { |
|
/* A set operation is add + copy + modified. We just did |
|
* the add the copy, so the only thing that's left is a |
|
* modified command, which will call the OnSet |
|
* observers. */ |
|
cmd->kind = EcsCmdModified; |
|
} else { |
|
/* If this was a get_mut, nothing's left to be done */ |
|
cmd->kind = EcsCmdSkip; |
|
} |
|
} else { |
|
/* The entity no longer has the component which means that |
|
* there was a remove command for the component in the |
|
* command queue. In that case skip the command. */ |
|
cmd->kind = EcsCmdSkip; |
|
} |
|
break; |
|
} |
|
case EcsCmdClone: |
|
case EcsCmdBulkNew: |
|
case EcsCmdAdd: |
|
case EcsCmdRemove: |
|
case EcsCmdEmplace: |
|
case EcsCmdModified: |
|
case EcsCmdModifiedNoHook: |
|
case EcsCmdAddModified: |
|
case EcsCmdPath: |
|
case EcsCmdDelete: |
|
case EcsCmdClear: |
|
case EcsCmdOnDeleteAction: |
|
case EcsCmdEnable: |
|
case EcsCmdDisable: |
|
case EcsCmdEvent: |
|
case EcsCmdSkip: |
|
break; |
|
} |
|
} while ((cur = next_for_entity)); |
|
} |
|
} |
|
|
|
/* Leave safe section. Run all deferred commands. */ |
|
bool flecs_defer_end( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_poly_assert(stage, ecs_stage_t); |
|
|
|
if (stage->defer < 0) { |
|
/* Defer suspending makes it possible to do operations on the storage |
|
* without flushing the commands in the queue */ |
|
return false; |
|
} |
|
|
|
ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!--stage->defer) { |
|
/* Test whether we're flushing to another queue or whether we're |
|
* flushing to the storage */ |
|
bool merge_to_world = false; |
|
if (ecs_poly_is(world, ecs_world_t)) { |
|
merge_to_world = world->stages[0].defer == 0; |
|
} |
|
|
|
ecs_stage_t *dst_stage = flecs_stage_from_world(&world); |
|
ecs_commands_t *commands = stage->cmd; |
|
ecs_vec_t *queue = &commands->queue; |
|
|
|
if (ecs_vec_count(queue)) { |
|
ecs_cmd_t *cmds = ecs_vec_first(queue); |
|
int32_t i, count = ecs_vec_count(queue); |
|
|
|
ecs_table_diff_builder_t diff; |
|
flecs_table_diff_builder_init(world, &diff); |
|
flecs_commands_push(stage); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_cmd_t *cmd = &cmds[i]; |
|
ecs_entity_t e = cmd->entity; |
|
bool is_alive = flecs_entities_is_alive(world, e); |
|
|
|
/* A negative index indicates the first command for an entity */ |
|
if (merge_to_world && (cmd->next_for_entity < 0)) { |
|
/* Batch commands for entity to limit archetype moves */ |
|
if (is_alive) { |
|
flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); |
|
} else { |
|
world->info.cmd.discard_count ++; |
|
} |
|
} |
|
|
|
/* Invalidate entry */ |
|
if (cmd->entry) { |
|
cmd->entry->first = -1; |
|
} |
|
|
|
/* If entity is no longer alive, this could be because the queue |
|
* contained both a delete and a subsequent add/remove/set which |
|
* should be ignored. */ |
|
ecs_cmd_kind_t kind = cmd->kind; |
|
if ((kind != EcsCmdPath) && ((kind == EcsCmdSkip) || (e && !is_alive))) { |
|
world->info.cmd.discard_count ++; |
|
flecs_discard_cmd(world, cmd); |
|
continue; |
|
} |
|
|
|
ecs_id_t id = cmd->id; |
|
|
|
switch(kind) { |
|
case EcsCmdAdd: |
|
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); |
|
if (flecs_remove_invalid(world, id, &id)) { |
|
if (id) { |
|
world->info.cmd.add_count ++; |
|
flecs_add_id(world, e, id); |
|
} else { |
|
world->info.cmd.discard_count ++; |
|
} |
|
} else { |
|
world->info.cmd.discard_count ++; |
|
ecs_delete(world, e); |
|
} |
|
break; |
|
case EcsCmdRemove: |
|
flecs_remove_id(world, e, id); |
|
world->info.cmd.remove_count ++; |
|
break; |
|
case EcsCmdClone: |
|
ecs_clone(world, e, id, cmd->is._1.clone_value); |
|
break; |
|
case EcsCmdSet: |
|
flecs_move_ptr_w_id(world, dst_stage, e, |
|
cmd->id, flecs_itosize(cmd->is._1.size), |
|
cmd->is._1.value, kind); |
|
world->info.cmd.set_count ++; |
|
break; |
|
case EcsCmdEmplace: |
|
if (merge_to_world) { |
|
ecs_emplace_id(world, e, id); |
|
} |
|
flecs_move_ptr_w_id(world, dst_stage, e, |
|
cmd->id, flecs_itosize(cmd->is._1.size), |
|
cmd->is._1.value, kind); |
|
world->info.cmd.get_mut_count ++; |
|
break; |
|
case EcsCmdMut: |
|
flecs_move_ptr_w_id(world, dst_stage, e, |
|
cmd->id, flecs_itosize(cmd->is._1.size), |
|
cmd->is._1.value, kind); |
|
world->info.cmd.get_mut_count ++; |
|
break; |
|
case EcsCmdModified: |
|
flecs_modified_id_if(world, e, id, true); |
|
world->info.cmd.modified_count ++; |
|
break; |
|
case EcsCmdModifiedNoHook: |
|
flecs_modified_id_if(world, e, id, false); |
|
world->info.cmd.modified_count ++; |
|
break; |
|
case EcsCmdAddModified: |
|
flecs_add_id(world, e, id); |
|
flecs_modified_id_if(world, e, id, true); |
|
world->info.cmd.add_count ++; |
|
world->info.cmd.modified_count ++; |
|
break; |
|
case EcsCmdDelete: { |
|
ecs_delete(world, e); |
|
world->info.cmd.delete_count ++; |
|
break; |
|
} |
|
case EcsCmdClear: |
|
ecs_clear(world, e); |
|
world->info.cmd.clear_count ++; |
|
break; |
|
case EcsCmdOnDeleteAction: |
|
ecs_defer_begin(world); |
|
flecs_on_delete(world, id, e, false); |
|
ecs_defer_end(world); |
|
world->info.cmd.other_count ++; |
|
break; |
|
case EcsCmdEnable: |
|
ecs_enable_id(world, e, id, true); |
|
world->info.cmd.other_count ++; |
|
break; |
|
case EcsCmdDisable: |
|
ecs_enable_id(world, e, id, false); |
|
world->info.cmd.other_count ++; |
|
break; |
|
case EcsCmdBulkNew: |
|
flecs_flush_bulk_new(world, cmd); |
|
world->info.cmd.other_count ++; |
|
continue; |
|
case EcsCmdPath: |
|
ecs_ensure(world, e); |
|
if (cmd->id) { |
|
ecs_add_pair(world, e, EcsChildOf, cmd->id); |
|
} |
|
ecs_set_name(world, e, cmd->is._1.value); |
|
ecs_os_free(cmd->is._1.value); |
|
cmd->is._1.value = NULL; |
|
break; |
|
case EcsCmdEvent: { |
|
ecs_event_desc_t *desc = cmd->is._1.value; |
|
ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_emit((ecs_world_t*)stage, desc); |
|
flecs_free_cmd_event(world, desc); |
|
break; |
|
} |
|
case EcsCmdSkip: |
|
break; |
|
} |
|
|
|
if (cmd->is._1.value) { |
|
flecs_stack_free(cmd->is._1.value, cmd->is._1.size); |
|
} |
|
} |
|
|
|
flecs_stack_reset(&commands->stack); |
|
ecs_vec_clear(queue); |
|
flecs_commands_pop(stage); |
|
|
|
flecs_table_diff_builder_fini(world, &diff); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/* Delete operations from queue without executing them. */ |
|
bool flecs_defer_purge( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!--stage->defer) { |
|
ecs_vec_t commands = stage->cmd->queue; |
|
|
|
if (ecs_vec_count(&commands)) { |
|
ecs_cmd_t *cmds = ecs_vec_first(&commands); |
|
int32_t i, count = ecs_vec_count(&commands); |
|
for (i = 0; i < count; i ++) { |
|
flecs_discard_cmd(world, &cmds[i]); |
|
} |
|
|
|
ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); |
|
|
|
ecs_vec_clear(&commands); |
|
flecs_stack_reset(&stage->cmd->stack); |
|
flecs_sparse_clear(&stage->cmd->entries); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
error: |
|
return false; |
|
} |
|
|
|
/** |
|
* @file entity_filter.c |
|
* @brief Filters that are applied to entities in a table. |
|
* |
|
* After a table has been matched by a query, additional filters may have to |
|
* be applied before returning entities to the application. The two scenarios |
|
* under which this happens are queries for union relationship pairs (entities |
|
* for multiple targets are stored in the same table) and toggles (components |
|
* that are enabled/disabled with a bitset). |
|
*/ |
|
|
|
|
|
static |
|
int flecs_entity_filter_find_smallest_term( |
|
ecs_table_t *table, |
|
ecs_entity_filter_iter_t *iter) |
|
{ |
|
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
flecs_switch_term_t *sw_terms = ecs_vec_first(&iter->entity_filter->sw_terms); |
|
int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); |
|
int32_t min = INT_MAX, index = 0; |
|
|
|
for (i = 0; i < count; i ++) { |
|
/* The array with sparse queries for the matched table */ |
|
flecs_switch_term_t *sparse_column = &sw_terms[i]; |
|
|
|
/* Pointer to the switch column struct of the table */ |
|
ecs_switch_t *sw = sparse_column->sw_column; |
|
|
|
/* If the sparse column pointer hadn't been retrieved yet, do it now */ |
|
if (!sw) { |
|
/* Get the table column index from the signature column index */ |
|
int32_t table_column_index = iter->columns[ |
|
sparse_column->signature_column_index]; |
|
|
|
/* Translate the table column index to switch column index */ |
|
table_column_index -= table->_->sw_offset; |
|
ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Get the sparse column */ |
|
sw = sparse_column->sw_column = |
|
&table->_->sw_columns[table_column_index - 1]; |
|
} |
|
|
|
/* Find the smallest column */ |
|
int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); |
|
if (case_count < min) { |
|
min = case_count; |
|
index = i + 1; |
|
} |
|
} |
|
|
|
return index; |
|
} |
|
|
|
static |
|
int flecs_entity_filter_switch_next( |
|
ecs_table_t *table, |
|
ecs_entity_filter_iter_t *iter, |
|
bool filter) |
|
{ |
|
bool first_iteration = false; |
|
int32_t switch_smallest; |
|
|
|
if (!(switch_smallest = iter->sw_smallest)) { |
|
switch_smallest = iter->sw_smallest = |
|
flecs_entity_filter_find_smallest_term(table, iter); |
|
first_iteration = true; |
|
} |
|
|
|
switch_smallest -= 1; |
|
|
|
flecs_switch_term_t *columns = ecs_vec_first(&iter->entity_filter->sw_terms); |
|
flecs_switch_term_t *column = &columns[switch_smallest]; |
|
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 */ |
|
int32_t first, sparse_first = iter->sw_offset; |
|
|
|
if (!filter) { |
|
if (first_iteration) { |
|
first = flecs_switch_first(sw_smallest, case_smallest); |
|
} else { |
|
first = flecs_switch_next(sw_smallest, sparse_first); |
|
} |
|
} else { |
|
int32_t cur_first = iter->range.offset, cur_count = iter->range.count; |
|
first = cur_first; |
|
while (flecs_switch_get(sw_smallest, first) != case_smallest) { |
|
first ++; |
|
if (first >= (cur_first + cur_count)) { |
|
first = -1; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (first == -1) { |
|
goto done; |
|
} |
|
|
|
/* Check if entity matches with other sparse columns, if any */ |
|
int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); |
|
do { |
|
for (i = 0; i < count; i ++) { |
|
if (i == switch_smallest) { |
|
/* Already validated this one */ |
|
continue; |
|
} |
|
|
|
column = &columns[i]; |
|
sw = column->sw_column; |
|
|
|
if (flecs_switch_get(sw, first) != column->sw_case) { |
|
first = flecs_switch_next(sw_smallest, first); |
|
if (first == -1) { |
|
goto done; |
|
} |
|
} |
|
} |
|
} while (i != count); |
|
|
|
iter->range.offset = iter->sw_offset = first; |
|
iter->range.count = 1; |
|
|
|
return 0; |
|
done: |
|
/* Iterated all elements in the sparse list, we should move to the |
|
* next matched table. */ |
|
iter->sw_smallest = 0; |
|
iter->sw_offset = 0; |
|
|
|
return -1; |
|
} |
|
|
|
#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) |
|
|
|
static |
|
int flecs_entity_filter_bitset_next( |
|
ecs_table_t *table, |
|
ecs_entity_filter_iter_t *iter) |
|
{ |
|
/* Precomputed single-bit test */ |
|
static const uint64_t bitmask[64] = { |
|
(uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, |
|
(uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, |
|
(uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, |
|
(uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, |
|
(uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, |
|
(uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, |
|
(uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, |
|
(uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, |
|
(uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, |
|
(uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, |
|
(uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, |
|
(uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, |
|
(uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, |
|
(uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, |
|
(uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, |
|
(uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 |
|
}; |
|
|
|
/* Precomputed test to verify if remainder of block is set (or not) */ |
|
static const uint64_t bitmask_remain[64] = { |
|
BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), |
|
BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), |
|
BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), |
|
BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), |
|
BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), |
|
BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), |
|
BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), |
|
BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), |
|
BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), |
|
BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), |
|
BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), |
|
BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), |
|
BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), |
|
BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), |
|
BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), |
|
BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), |
|
BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), |
|
BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), |
|
BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), |
|
BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), |
|
BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), |
|
BS_MAX - (BS_MAX >> 1) |
|
}; |
|
|
|
int32_t i, count = ecs_vec_count(&iter->entity_filter->bs_terms); |
|
flecs_bitset_term_t *terms = ecs_vec_first(&iter->entity_filter->bs_terms); |
|
int32_t bs_offset = table->_->bs_offset; |
|
int32_t first = iter->bs_offset; |
|
int32_t last = 0; |
|
|
|
for (i = 0; i < count; i ++) { |
|
flecs_bitset_term_t *column = &terms[i]; |
|
ecs_bitset_t *bs = terms[i].bs_column; |
|
|
|
if (!bs) { |
|
int32_t index = column->column_index; |
|
ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); |
|
bs = &table->_->bs_columns[index - bs_offset]; |
|
terms[i].bs_column = bs; |
|
} |
|
|
|
int32_t bs_elem_count = bs->count; |
|
int32_t bs_block = first >> 6; |
|
int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; |
|
|
|
if (bs_block >= bs_block_count) { |
|
goto done; |
|
} |
|
|
|
uint64_t *data = bs->data; |
|
int32_t bs_start = first & 0x3F; |
|
|
|
/* Step 1: find the first non-empty block */ |
|
uint64_t v = data[bs_block]; |
|
uint64_t remain = bitmask_remain[bs_start]; |
|
while (!(v & remain)) { |
|
/* If no elements are remaining, move to next block */ |
|
if ((++bs_block) >= bs_block_count) { |
|
/* No non-empty blocks left */ |
|
goto done; |
|
} |
|
|
|
bs_start = 0; |
|
remain = BS_MAX; /* Test the full block */ |
|
v = data[bs_block]; |
|
} |
|
|
|
/* Step 2: find the first non-empty element in the block */ |
|
while (!(v & bitmask[bs_start])) { |
|
bs_start ++; |
|
|
|
/* Block was not empty, so bs_start must be smaller than 64 */ |
|
ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
/* Step 3: Find number of contiguous enabled elements after start */ |
|
int32_t bs_end = bs_start, bs_block_end = bs_block; |
|
|
|
remain = bitmask_remain[bs_end]; |
|
while ((v & remain) == remain) { |
|
bs_end = 0; |
|
bs_block_end ++; |
|
|
|
if (bs_block_end == bs_block_count) { |
|
break; |
|
} |
|
|
|
v = data[bs_block_end]; |
|
remain = BS_MAX; /* Test the full block */ |
|
} |
|
|
|
/* Step 4: find remainder of enabled elements in current block */ |
|
if (bs_block_end != bs_block_count) { |
|
while ((v & bitmask[bs_end])) { |
|
bs_end ++; |
|
} |
|
} |
|
|
|
/* Block was not 100% occupied, so bs_start must be smaller than 64 */ |
|
ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Step 5: translate to element start/end and make sure that each column |
|
* range is a subset of the previous one. */ |
|
first = bs_block * 64 + bs_start; |
|
int32_t cur_last = bs_block_end * 64 + bs_end; |
|
|
|
/* No enabled elements found in table */ |
|
if (first == cur_last) { |
|
goto done; |
|
} |
|
|
|
/* If multiple bitsets are evaluated, make sure each subsequent range |
|
* is equal or a subset of the previous range */ |
|
if (i) { |
|
/* If the first element of a subsequent bitset is larger than the |
|
* previous last value, start over. */ |
|
if (first >= last) { |
|
i = -1; |
|
continue; |
|
} |
|
|
|
/* Make sure the last element of the range doesn't exceed the last |
|
* element of the previous range. */ |
|
if (cur_last > last) { |
|
cur_last = last; |
|
} |
|
} |
|
|
|
last = cur_last; |
|
int32_t elem_count = last - first; |
|
|
|
/* Make sure last element doesn't exceed total number of elements in |
|
* the table */ |
|
if (elem_count > (bs_elem_count - first)) { |
|
elem_count = (bs_elem_count - first); |
|
if (!elem_count) { |
|
iter->bs_offset = 0; |
|
goto done; |
|
} |
|
} |
|
|
|
iter->range.offset = first; |
|
iter->range.count = elem_count; |
|
iter->bs_offset = first; |
|
} |
|
|
|
/* Keep track of last processed element for iteration */ |
|
iter->bs_offset = last; |
|
|
|
return 0; |
|
done: |
|
iter->sw_smallest = 0; |
|
iter->sw_offset = 0; |
|
return -1; |
|
} |
|
|
|
#undef BS_MAX |
|
|
|
static |
|
int32_t flecs_get_flattened_target( |
|
ecs_world_t *world, |
|
EcsTarget *cur, |
|
ecs_entity_t rel, |
|
ecs_id_t id, |
|
ecs_entity_t *src_out, |
|
ecs_table_record_t **tr_out) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
return -1; |
|
} |
|
|
|
ecs_record_t *r = cur->target; |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_t *table = r->table; |
|
if (!table) { |
|
return -1; |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (tr) { |
|
*src_out = ecs_record_get_entity(r); |
|
*tr_out = tr; |
|
return tr->index; |
|
} |
|
|
|
if (table->flags & EcsTableHasTarget) { |
|
int32_t col = table->column_map[table->_->ft_offset]; |
|
ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); |
|
EcsTarget *next = table->data.columns[col].data.array; |
|
next = ECS_ELEM_T(next, EcsTarget, ECS_RECORD_TO_ROW(r->row)); |
|
return flecs_get_flattened_target( |
|
world, next, rel, id, src_out, tr_out); |
|
} |
|
|
|
return ecs_search_relation( |
|
world, table, 0, id, rel, EcsSelf|EcsUp, src_out, NULL, tr_out); |
|
} |
|
|
|
void flecs_entity_filter_init( |
|
ecs_world_t *world, |
|
ecs_entity_filter_t **entity_filter, |
|
const ecs_filter_t *filter, |
|
const ecs_table_t *table, |
|
ecs_id_t *ids, |
|
int32_t *columns) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(entity_filter != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_entity_filter_t ef; |
|
ecs_os_zeromem(&ef); |
|
ecs_vec_t *sw_terms = &ef.sw_terms; |
|
ecs_vec_t *bs_terms = &ef.bs_terms; |
|
ecs_vec_t *ft_terms = &ef.ft_terms; |
|
if (*entity_filter) { |
|
ef.sw_terms = (*entity_filter)->sw_terms; |
|
ef.bs_terms = (*entity_filter)->bs_terms; |
|
ef.ft_terms = (*entity_filter)->ft_terms; |
|
} |
|
ecs_vec_reset_t(a, sw_terms, flecs_switch_term_t); |
|
ecs_vec_reset_t(a, bs_terms, flecs_bitset_term_t); |
|
ecs_vec_reset_t(a, ft_terms, flecs_flat_table_term_t); |
|
ecs_term_t *terms = filter->terms; |
|
int32_t i, term_count = filter->term_count; |
|
bool has_filter = false; |
|
ef.flat_tree_column = -1; |
|
|
|
/* Look for union fields */ |
|
if (table->flags & EcsTableHasUnion) { |
|
for (i = 0; i < term_count; i ++) { |
|
if (ecs_term_match_0(&terms[i])) { |
|
continue; |
|
} |
|
|
|
ecs_id_t id = terms[i].id; |
|
if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) == EcsWildcard) { |
|
continue; |
|
} |
|
|
|
int32_t field = terms[i].field_index; |
|
int32_t column = columns[field]; |
|
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 *el = ecs_vec_append_t(a, sw_terms, |
|
flecs_switch_term_t); |
|
el->signature_column_index = field; |
|
el->sw_case = ecs_pair_second(world, id); |
|
el->sw_column = NULL; |
|
ids[field] = id; |
|
has_filter = true; |
|
} |
|
} |
|
|
|
/* Look for disabled fields */ |
|
if (table->flags & EcsTableHasToggle) { |
|
for (i = 0; i < term_count; i ++) { |
|
if (ecs_term_match_0(&terms[i])) { |
|
continue; |
|
} |
|
|
|
int32_t field = terms[i].field_index; |
|
ecs_id_t id = ids[field]; |
|
ecs_id_t bs_id = ECS_TOGGLE | id; |
|
int32_t bs_index = ecs_table_get_type_index(world, table, bs_id); |
|
|
|
if (bs_index != -1) { |
|
flecs_bitset_term_t *bc = ecs_vec_append_t(a, bs_terms, |
|
flecs_bitset_term_t); |
|
bc->column_index = bs_index; |
|
bc->bs_column = NULL; |
|
has_filter = true; |
|
} |
|
} |
|
} |
|
|
|
/* Look for flattened fields */ |
|
if (table->flags & EcsTableHasTarget) { |
|
const ecs_table_record_t *tr = flecs_table_record_get(world, table, |
|
ecs_pair_t(EcsTarget, EcsWildcard)); |
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t column = tr->index; |
|
ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); |
|
ecs_entity_t rel = ecs_pair_second(world, table->type.array[column]); |
|
|
|
for (i = 0; i < term_count; i ++) { |
|
if (ecs_term_match_0(&terms[i])) { |
|
continue; |
|
} |
|
|
|
if (terms[i].src.trav == rel) { |
|
ef.flat_tree_column = table->column_map[column]; |
|
ecs_assert(ef.flat_tree_column != -1, |
|
ECS_INTERNAL_ERROR, NULL); |
|
has_filter = true; |
|
|
|
flecs_flat_table_term_t *term = ecs_vec_append_t( |
|
a, ft_terms, flecs_flat_table_term_t); |
|
term->field_index = terms[i].field_index; |
|
term->term = &terms[i]; |
|
ecs_os_zeromem(&term->monitor); |
|
} |
|
} |
|
} |
|
|
|
if (has_filter) { |
|
if (!*entity_filter) { |
|
*entity_filter = ecs_os_malloc_t(ecs_entity_filter_t); |
|
} |
|
ecs_assert(*entity_filter != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
**entity_filter = ef; |
|
} |
|
} |
|
|
|
void flecs_entity_filter_fini( |
|
ecs_world_t *world, |
|
ecs_entity_filter_t *ef) |
|
{ |
|
if (!ef) { |
|
return; |
|
} |
|
|
|
ecs_allocator_t *a = &world->allocator; |
|
|
|
flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); |
|
int32_t i, term_count = ecs_vec_count(&ef->ft_terms); |
|
for (i = 0; i < term_count; i ++) { |
|
ecs_vec_fini_t(NULL, &fields[i].monitor, flecs_flat_monitor_t); |
|
} |
|
|
|
ecs_vec_fini_t(a, &ef->sw_terms, flecs_switch_term_t); |
|
ecs_vec_fini_t(a, &ef->bs_terms, flecs_bitset_term_t); |
|
ecs_vec_fini_t(a, &ef->ft_terms, flecs_flat_table_term_t); |
|
ecs_os_free(ef); |
|
} |
|
|
|
int flecs_entity_filter_next( |
|
ecs_entity_filter_iter_t *it) |
|
{ |
|
ecs_table_t *table = it->range.table; |
|
flecs_switch_term_t *sw_terms = ecs_vec_first(&it->entity_filter->sw_terms); |
|
flecs_bitset_term_t *bs_terms = ecs_vec_first(&it->entity_filter->bs_terms); |
|
ecs_entity_filter_t *ef = it->entity_filter; |
|
int32_t flat_tree_column = ef->flat_tree_column; |
|
ecs_table_range_t *range = &it->range; |
|
int32_t range_end = range->offset + range->count; |
|
int result = EcsIterNext; |
|
bool found = false; |
|
|
|
do { |
|
found = false; |
|
|
|
if (bs_terms) { |
|
if (flecs_entity_filter_bitset_next(table, it) == -1) { |
|
/* No more enabled components for table */ |
|
it->bs_offset = 0; |
|
break; |
|
} else { |
|
result = EcsIterYield; |
|
found = true; |
|
} |
|
} |
|
|
|
if (sw_terms) { |
|
if (flecs_entity_filter_switch_next(table, it, found) == -1) { |
|
/* No more elements in sparse column */ |
|
if (found) { |
|
/* Try again */ |
|
result = EcsIterNext; |
|
found = false; |
|
} else { |
|
/* Nothing found */ |
|
it->bs_offset = 0; |
|
break; |
|
} |
|
} else { |
|
result = EcsIterYield; |
|
found = true; |
|
it->bs_offset = range->offset + range->count; |
|
} |
|
} |
|
|
|
if (flat_tree_column != -1) { |
|
bool first_for_table = it->prev != table; |
|
ecs_iter_t *iter = it->it; |
|
ecs_world_t *world = iter->real_world; |
|
EcsTarget *ft = table->data.columns[flat_tree_column].data.array; |
|
int32_t ft_offset; |
|
int32_t ft_count; |
|
|
|
if (first_for_table) { |
|
ft_offset = it->flat_tree_offset = range->offset; |
|
it->target_count = 1; |
|
} else { |
|
it->flat_tree_offset += ft[it->flat_tree_offset].count; |
|
ft_offset = it->flat_tree_offset; |
|
it->target_count ++; |
|
} |
|
|
|
ecs_assert(ft_offset < ecs_table_count(table), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
EcsTarget *cur = &ft[ft_offset]; |
|
ft_count = cur->count; |
|
bool is_last = (ft_offset + ft_count) >= range_end; |
|
|
|
int32_t i, field_count = ecs_vec_count(&ef->ft_terms); |
|
flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); |
|
for (i = 0; i < field_count; i ++) { |
|
flecs_flat_table_term_t *field = &fields[i]; |
|
ecs_vec_init_if_t(&field->monitor, flecs_flat_monitor_t); |
|
int32_t field_index = field->field_index; |
|
ecs_id_t id = it->it->ids[field_index]; |
|
ecs_id_t flat_pair = table->type.array[flat_tree_column]; |
|
ecs_entity_t rel = ECS_PAIR_FIRST(flat_pair); |
|
ecs_entity_t tgt; |
|
ecs_table_record_t *tr; |
|
int32_t tgt_col = flecs_get_flattened_target( |
|
world, cur, rel, id, &tgt, &tr); |
|
if (tgt_col != -1) { |
|
iter->sources[field_index] = tgt; |
|
iter->columns[field_index] = /* encode flattened field */ |
|
-(iter->field_count + tgt_col + 1); |
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Keep track of maximum value encountered in target table |
|
* dirty state so this doesn't have to be recomputed when |
|
* synchronizing the query monitor. */ |
|
ecs_vec_set_min_count_zeromem_t(NULL, &field->monitor, |
|
flecs_flat_monitor_t, it->target_count); |
|
ecs_table_t *tgt_table = tr->hdr.table; |
|
int32_t *ds = flecs_table_get_dirty_state(world, tgt_table); |
|
ecs_assert(ds != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_vec_get_t(&field->monitor, flecs_flat_monitor_t, |
|
it->target_count - 1)->table_state = ds[tgt_col + 1]; |
|
} else { |
|
if (field->term->oper == EcsOptional) { |
|
iter->columns[field_index] = 0; |
|
iter->ptrs[field_index] = NULL; |
|
} else { |
|
it->prev = NULL; |
|
break; |
|
} |
|
} |
|
} |
|
if (i != field_count) { |
|
if (is_last) { |
|
break; |
|
} |
|
} else { |
|
found = true; |
|
if ((ft_offset + ft_count) == range_end) { |
|
result = EcsIterNextYield; |
|
} else { |
|
result = EcsIterYield; |
|
} |
|
} |
|
|
|
range->offset = ft_offset; |
|
range->count = ft_count; |
|
it->prev = table; |
|
} |
|
} while (!found); |
|
|
|
it->prev = table; |
|
|
|
if (!found) { |
|
return EcsIterNext; |
|
} else { |
|
return result; |
|
} |
|
} |
|
|
|
/** |
|
* @file entity_name.c |
|
* @brief Functions for working with named entities. |
|
*/ |
|
|
|
#include <ctype.h> |
|
|
|
#define ECS_NAME_BUFFER_LENGTH (64) |
|
|
|
static |
|
bool flecs_path_append( |
|
const ecs_world_t *world, |
|
ecs_entity_t parent, |
|
ecs_entity_t child, |
|
const char *sep, |
|
const char *prefix, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_entity_t cur = 0; |
|
const char *name = NULL; |
|
ecs_size_t name_len = 0; |
|
|
|
if (child && ecs_is_alive(world, child)) { |
|
cur = ecs_get_target(world, child, EcsChildOf, 0); |
|
if (cur) { |
|
ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); |
|
if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { |
|
flecs_path_append(world, parent, cur, sep, prefix, buf); |
|
if (!sep[1]) { |
|
ecs_strbuf_appendch(buf, sep[0]); |
|
} else { |
|
ecs_strbuf_appendstr(buf, sep); |
|
} |
|
} |
|
} else if (prefix && prefix[0]) { |
|
if (!prefix[1]) { |
|
ecs_strbuf_appendch(buf, prefix[0]); |
|
} else { |
|
ecs_strbuf_appendstr(buf, prefix); |
|
} |
|
} |
|
|
|
const EcsIdentifier *id = ecs_get_pair( |
|
world, child, EcsIdentifier, EcsName); |
|
if (id) { |
|
name = id->value; |
|
name_len = id->length; |
|
} |
|
} |
|
|
|
if (name) { |
|
ecs_strbuf_appendstrn(buf, name, name_len); |
|
} else { |
|
ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); |
|
} |
|
|
|
return cur != 0; |
|
} |
|
|
|
bool flecs_name_is_id( |
|
const char *name) |
|
{ |
|
ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!isdigit(name[0])) { |
|
return false; |
|
} |
|
|
|
ecs_size_t i, length = ecs_os_strlen(name); |
|
for (i = 1; i < length; i ++) { |
|
char ch = name[i]; |
|
|
|
if (!isdigit(ch)) { |
|
break; |
|
} |
|
} |
|
|
|
return i >= length; |
|
} |
|
|
|
ecs_entity_t flecs_name_to_id( |
|
const ecs_world_t *world, |
|
const char *name) |
|
{ |
|
int64_t result = atoll(name); |
|
ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_entity_t alive = ecs_get_alive(world, (ecs_entity_t)result); |
|
if (alive) { |
|
return alive; |
|
} else { |
|
if ((uint32_t)result == (uint64_t)result) { |
|
return (ecs_entity_t)result; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_get_builtin( |
|
const char *name) |
|
{ |
|
if (name[0] == '.' && name[1] == '\0') { |
|
return EcsThis; |
|
} else if (name[0] == '*' && name[1] == '\0') { |
|
return EcsWildcard; |
|
} else if (name[0] == '_' && name[1] == '\0') { |
|
return EcsAny; |
|
} else if (name[0] == '$' && name[1] == '\0') { |
|
return EcsVariable; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
bool flecs_is_sep( |
|
const char **ptr, |
|
const char *sep) |
|
{ |
|
ecs_size_t len = ecs_os_strlen(sep); |
|
|
|
if (!ecs_os_strncmp(*ptr, sep, len)) { |
|
*ptr += len; |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
const char* flecs_path_elem( |
|
const char *path, |
|
const char *sep, |
|
int32_t *len) |
|
{ |
|
const char *ptr; |
|
char ch; |
|
int32_t template_nesting = 0; |
|
int32_t count = 0; |
|
|
|
for (ptr = path; (ch = *ptr); ptr ++) { |
|
if (ch == '<') { |
|
template_nesting ++; |
|
} else if (ch == '>') { |
|
template_nesting --; |
|
} |
|
|
|
ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); |
|
|
|
if (!template_nesting && flecs_is_sep(&ptr, sep)) { |
|
break; |
|
} |
|
|
|
count ++; |
|
} |
|
|
|
if (len) { |
|
*len = count; |
|
} |
|
|
|
if (count) { |
|
return ptr; |
|
} else { |
|
return NULL; |
|
} |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
bool flecs_is_root_path( |
|
const char *path, |
|
const char *prefix) |
|
{ |
|
if (prefix) { |
|
return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_get_parent_from_path( |
|
const ecs_world_t *world, |
|
ecs_entity_t parent, |
|
const char **path_ptr, |
|
const char *prefix, |
|
bool new_entity) |
|
{ |
|
bool start_from_root = false; |
|
const char *path = *path_ptr; |
|
|
|
if (flecs_is_root_path(path, prefix)) { |
|
path += ecs_os_strlen(prefix); |
|
parent = 0; |
|
start_from_root = true; |
|
} |
|
|
|
if (!start_from_root && !parent && new_entity) { |
|
parent = ecs_get_scope(world); |
|
} |
|
|
|
*path_ptr = path; |
|
|
|
return parent; |
|
} |
|
|
|
static |
|
void flecs_on_set_symbol(ecs_iter_t *it) { |
|
EcsIdentifier *n = ecs_field(it, EcsIdentifier, 1); |
|
ecs_world_t *world = it->world; |
|
|
|
int i; |
|
for (i = 0; i < it->count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
flecs_name_index_ensure( |
|
&world->symbols, e, n[i].value, n[i].length, n[i].hash); |
|
} |
|
} |
|
|
|
void flecs_bootstrap_hierarchy(ecs_world_t *world) { |
|
ecs_observer(world, { |
|
.entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}), |
|
.filter.terms[0] = { |
|
.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), |
|
.src.flags = EcsSelf |
|
}, |
|
.callback = flecs_on_set_symbol, |
|
.events = {EcsOnSet}, |
|
.yield_existing = true |
|
}); |
|
} |
|
|
|
|
|
/* Public functions */ |
|
|
|
void ecs_get_path_w_sep_buf( |
|
const ecs_world_t *world, |
|
ecs_entity_t parent, |
|
ecs_entity_t child, |
|
const char *sep, |
|
const char *prefix, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
if (child == EcsWildcard) { |
|
ecs_strbuf_appendch(buf, '*'); |
|
return; |
|
} |
|
if (child == EcsAny) { |
|
ecs_strbuf_appendch(buf, '_'); |
|
return; |
|
} |
|
|
|
if (!sep) { |
|
sep = "."; |
|
} |
|
|
|
if (!child || parent != child) { |
|
flecs_path_append(world, parent, child, sep, prefix, buf); |
|
} else { |
|
ecs_strbuf_appendstrn(buf, "", 0); |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
char* ecs_get_path_w_sep( |
|
const ecs_world_t *world, |
|
ecs_entity_t parent, |
|
ecs_entity_t child, |
|
const char *sep, |
|
const char *prefix) |
|
{ |
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); |
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
ecs_entity_t ecs_lookup_child( |
|
const ecs_world_t *world, |
|
ecs_entity_t parent, |
|
const char *name) |
|
{ |
|
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
world = ecs_get_world(world); |
|
|
|
if (flecs_name_is_id(name)) { |
|
ecs_entity_t result = flecs_name_to_id(world, name); |
|
if (result && ecs_is_alive(world, result)) { |
|
if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { |
|
return 0; |
|
} |
|
return result; |
|
} |
|
} |
|
|
|
ecs_id_t pair = ecs_childof(parent); |
|
ecs_hashmap_t *index = flecs_id_name_index_get(world, pair); |
|
if (index) { |
|
return flecs_name_index_find(index, name, 0, 0); |
|
} else { |
|
return 0; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_lookup( |
|
const ecs_world_t *world, |
|
const char *name) |
|
{ |
|
if (!name) { |
|
return 0; |
|
} |
|
|
|
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
world = ecs_get_world(world); |
|
|
|
ecs_entity_t e = flecs_get_builtin(name); |
|
if (e) { |
|
return e; |
|
} |
|
|
|
if (flecs_name_is_id(name)) { |
|
return flecs_name_to_id(world, name); |
|
} |
|
|
|
e = flecs_name_index_find(&world->aliases, name, 0, 0); |
|
if (e) { |
|
return e; |
|
} |
|
|
|
return ecs_lookup_child(world, 0, name); |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_lookup_symbol( |
|
const ecs_world_t *world, |
|
const char *name, |
|
bool lookup_as_path, |
|
bool recursive) |
|
{ |
|
if (!name) { |
|
return 0; |
|
} |
|
|
|
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
world = ecs_get_world(world); |
|
|
|
ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0); |
|
if (e) { |
|
return e; |
|
} |
|
|
|
if (lookup_as_path) { |
|
return ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); |
|
} |
|
|
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_lookup_path_w_sep( |
|
const ecs_world_t *world, |
|
ecs_entity_t parent, |
|
const char *path, |
|
const char *sep, |
|
const char *prefix, |
|
bool recursive) |
|
{ |
|
if (!path) { |
|
return 0; |
|
} |
|
|
|
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
const ecs_world_t *stage = world; |
|
world = ecs_get_world(world); |
|
|
|
ecs_entity_t e = flecs_get_builtin(path); |
|
if (e) { |
|
return e; |
|
} |
|
|
|
e = flecs_name_index_find(&world->aliases, path, 0, 0); |
|
if (e) { |
|
return e; |
|
} |
|
|
|
char buff[ECS_NAME_BUFFER_LENGTH]; |
|
const char *ptr, *ptr_start; |
|
char *elem = buff; |
|
int32_t len, size = ECS_NAME_BUFFER_LENGTH; |
|
ecs_entity_t cur; |
|
bool lookup_path_search = false; |
|
|
|
const ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); |
|
const ecs_entity_t *lookup_path_cur = lookup_path; |
|
while (lookup_path_cur && *lookup_path_cur) { |
|
lookup_path_cur ++; |
|
} |
|
|
|
if (!sep) { |
|
sep = "."; |
|
} |
|
|
|
parent = flecs_get_parent_from_path(stage, parent, &path, prefix, true); |
|
|
|
if (!sep[0]) { |
|
return ecs_lookup_child(world, parent, path); |
|
} |
|
|
|
retry: |
|
cur = parent; |
|
ptr_start = ptr = path; |
|
|
|
while ((ptr = flecs_path_elem(ptr, sep, &len))) { |
|
if (len < size) { |
|
ecs_os_memcpy(elem, ptr_start, len); |
|
} else { |
|
if (size == ECS_NAME_BUFFER_LENGTH) { |
|
elem = NULL; |
|
} |
|
|
|
elem = ecs_os_realloc(elem, len + 1); |
|
ecs_os_memcpy(elem, ptr_start, len); |
|
size = len + 1; |
|
} |
|
|
|
elem[len] = '\0'; |
|
ptr_start = ptr; |
|
|
|
cur = ecs_lookup_child(world, cur, elem); |
|
if (!cur) { |
|
goto tail; |
|
} |
|
} |
|
|
|
tail: |
|
if (!cur && recursive) { |
|
if (!lookup_path_search) { |
|
if (parent) { |
|
parent = ecs_get_target(world, parent, EcsChildOf, 0); |
|
goto retry; |
|
} else { |
|
lookup_path_search = true; |
|
} |
|
} |
|
|
|
if (lookup_path_search) { |
|
if (lookup_path_cur != lookup_path) { |
|
lookup_path_cur --; |
|
parent = lookup_path_cur[0]; |
|
goto retry; |
|
} |
|
} |
|
} |
|
|
|
if (elem != buff) { |
|
ecs_os_free(elem); |
|
} |
|
|
|
return cur; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_set_scope( |
|
ecs_world_t *world, |
|
ecs_entity_t scope) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
ecs_entity_t cur = stage->scope; |
|
stage->scope = scope; |
|
|
|
return cur; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_get_scope( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); |
|
return stage->scope; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t* ecs_set_lookup_path( |
|
ecs_world_t *world, |
|
const ecs_entity_t *lookup_path) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
/* Safe: application owns lookup path */ |
|
ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); |
|
stage->lookup_path = lookup_path; |
|
|
|
return cur; |
|
error: |
|
return NULL; |
|
} |
|
|
|
ecs_entity_t* ecs_get_lookup_path( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); |
|
/* Safe: application owns lookup path */ |
|
return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); |
|
error: |
|
return NULL; |
|
} |
|
|
|
const char* ecs_set_name_prefix( |
|
ecs_world_t *world, |
|
const char *prefix) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
const char *old_prefix = world->info.name_prefix; |
|
world->info.name_prefix = prefix; |
|
return old_prefix; |
|
} |
|
|
|
static |
|
void flecs_add_path( |
|
ecs_world_t *world, |
|
bool defer_suspend, |
|
ecs_entity_t parent, |
|
ecs_entity_t entity, |
|
const char *name) |
|
{ |
|
ecs_suspend_readonly_state_t srs; |
|
ecs_world_t *real_world = NULL; |
|
if (defer_suspend) { |
|
real_world = flecs_suspend_readonly(world, &srs); |
|
ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (parent) { |
|
ecs_add_pair(world, entity, EcsChildOf, parent); |
|
} |
|
|
|
ecs_set_name(world, entity, name); |
|
|
|
if (defer_suspend) { |
|
flecs_resume_readonly(real_world, &srs); |
|
flecs_defer_path((ecs_stage_t*)world, parent, entity, name); |
|
} |
|
} |
|
|
|
ecs_entity_t ecs_add_path_w_sep( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t parent, |
|
const char *path, |
|
const char *sep, |
|
const char *prefix) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!sep) { |
|
sep = "."; |
|
} |
|
|
|
if (!path) { |
|
if (!entity) { |
|
entity = ecs_new_id(world); |
|
} |
|
|
|
if (parent) { |
|
ecs_add_pair(world, entity, EcsChildOf, entity); |
|
} |
|
|
|
return entity; |
|
} |
|
|
|
bool root_path = flecs_is_root_path(path, prefix); |
|
parent = flecs_get_parent_from_path(world, parent, &path, prefix, !entity); |
|
|
|
char buff[ECS_NAME_BUFFER_LENGTH]; |
|
const char *ptr = path; |
|
const char *ptr_start = path; |
|
char *elem = buff; |
|
int32_t len, size = ECS_NAME_BUFFER_LENGTH; |
|
|
|
/* If we're in deferred/readonly mode suspend it, so that the name index is |
|
* immediately updated. Without this, we could create multiple entities for |
|
* the same name in a single command queue. */ |
|
bool suspend_defer = ecs_poly_is(world, ecs_stage_t) && |
|
(ecs_get_stage_count(world) <= 1); |
|
ecs_entity_t cur = parent; |
|
char *name = NULL; |
|
|
|
if (sep[0]) { |
|
while ((ptr = flecs_path_elem(ptr, sep, &len))) { |
|
if (len < size) { |
|
ecs_os_memcpy(elem, ptr_start, len); |
|
} else { |
|
if (size == ECS_NAME_BUFFER_LENGTH) { |
|
elem = NULL; |
|
} |
|
|
|
elem = ecs_os_realloc(elem, len + 1); |
|
ecs_os_memcpy(elem, ptr_start, len); |
|
size = len + 1; |
|
} |
|
|
|
elem[len] = '\0'; |
|
ptr_start = ptr; |
|
|
|
ecs_entity_t e = ecs_lookup_child(world, cur, elem); |
|
if (!e) { |
|
if (name) { |
|
ecs_os_free(name); |
|
} |
|
|
|
name = ecs_os_strdup(elem); |
|
|
|
/* If this is the last entity in the path, use the provided id */ |
|
bool last_elem = false; |
|
if (!flecs_path_elem(ptr, sep, NULL)) { |
|
e = entity; |
|
last_elem = true; |
|
} |
|
|
|
if (!e) { |
|
if (last_elem) { |
|
ecs_entity_t prev = ecs_set_scope(world, 0); |
|
e = ecs_new(world, 0); |
|
ecs_set_scope(world, prev); |
|
} else { |
|
e = ecs_new_id(world); |
|
} |
|
} |
|
|
|
if (!cur && last_elem && root_path) { |
|
ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); |
|
} |
|
|
|
flecs_add_path(world, suspend_defer, cur, e, name); |
|
} |
|
|
|
cur = e; |
|
} |
|
|
|
if (entity && (cur != entity)) { |
|
ecs_throw(ECS_ALREADY_DEFINED, name); |
|
} |
|
|
|
if (name) { |
|
ecs_os_free(name); |
|
} |
|
|
|
if (elem != buff) { |
|
ecs_os_free(elem); |
|
} |
|
} else { |
|
flecs_add_path(world, suspend_defer, parent, entity, path); |
|
} |
|
|
|
return cur; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_new_from_path_w_sep( |
|
ecs_world_t *world, |
|
ecs_entity_t parent, |
|
const char *path, |
|
const char *sep, |
|
const char *prefix) |
|
{ |
|
return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); |
|
} |
|
|
|
/** |
|
* @file filter.c |
|
* @brief Uncached query implementation. |
|
* |
|
* Uncached queries (filters) are stateless objects that do not cache their |
|
* results. This file contains the creation and validation of uncached queries |
|
* and code for query iteration. |
|
* |
|
* There file contains the implementation for term queries and filters. Term |
|
* queries are uncached queries that only apply to a single term. Filters are |
|
* uncached queries that support multiple terms. Filters are built on top of |
|
* term queries: before iteration a filter will first find a "pivot" term (the |
|
* term with the smallest number of elements), and create a term iterator for |
|
* it. The output of that term iterator is then evaluated against the rest of |
|
* the terms of the filter. |
|
* |
|
* Cached queries and observers are built using filters. |
|
*/ |
|
|
|
#include <ctype.h> |
|
|
|
ecs_filter_t ECS_FILTER_INIT = { .hdr = { .magic = ecs_filter_t_magic }}; |
|
|
|
/* Helper type for passing around context required for error messages */ |
|
typedef struct { |
|
const ecs_world_t *world; |
|
ecs_filter_t *filter; |
|
ecs_term_t *term; |
|
int32_t term_index; |
|
} ecs_filter_finalize_ctx_t; |
|
|
|
static |
|
char* flecs_filter_str( |
|
const ecs_world_t *world, |
|
const ecs_filter_t *filter, |
|
const ecs_filter_finalize_ctx_t *ctx, |
|
int32_t *term_start_out); |
|
|
|
static |
|
void flecs_filter_error( |
|
const ecs_filter_finalize_ctx_t *ctx, |
|
const char *fmt, |
|
...) |
|
{ |
|
va_list args; |
|
va_start(args, fmt); |
|
|
|
int32_t term_start = 0; |
|
|
|
char *expr = NULL; |
|
if (ctx->filter) { |
|
expr = flecs_filter_str(ctx->world, ctx->filter, ctx, &term_start); |
|
} else { |
|
expr = ecs_term_str(ctx->world, ctx->term); |
|
} |
|
const char *name = NULL; |
|
if (ctx->filter && ctx->filter->entity) { |
|
name = ecs_get_name(ctx->filter->world, ctx->filter->entity); |
|
} |
|
ecs_parser_errorv(name, expr, term_start, fmt, args); |
|
ecs_os_free(expr); |
|
|
|
va_end(args); |
|
} |
|
|
|
static |
|
int flecs_term_id_finalize_flags( |
|
ecs_term_id_t *term_id, |
|
ecs_filter_finalize_ctx_t *ctx) |
|
{ |
|
if ((term_id->flags & EcsIsEntity) && (term_id->flags & EcsIsVariable)) { |
|
flecs_filter_error(ctx, "cannot set both IsEntity and IsVariable"); |
|
return -1; |
|
} |
|
|
|
if (!(term_id->flags & (EcsIsEntity|EcsIsVariable|EcsIsName))) { |
|
if (term_id->id || term_id->name) { |
|
if (term_id->id == EcsThis || |
|
term_id->id == EcsWildcard || |
|
term_id->id == EcsAny || |
|
term_id->id == EcsVariable) |
|
{ |
|
/* Builtin variable ids default to variable */ |
|
term_id->flags |= EcsIsVariable; |
|
} else { |
|
term_id->flags |= EcsIsEntity; |
|
} |
|
} |
|
} |
|
|
|
if (term_id->flags & EcsParent) { |
|
term_id->flags |= EcsUp; |
|
term_id->trav = EcsChildOf; |
|
} |
|
|
|
if ((term_id->flags & EcsCascade) && !(term_id->flags & (EcsUp|EcsDown))) { |
|
term_id->flags |= EcsUp; |
|
} |
|
|
|
if ((term_id->flags & (EcsUp|EcsDown)) && !term_id->trav) { |
|
term_id->trav = EcsIsA; |
|
} |
|
|
|
if (term_id->trav && !(term_id->flags & EcsTraverseFlags)) { |
|
term_id->flags |= EcsUp; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_term_id_lookup( |
|
const ecs_world_t *world, |
|
ecs_entity_t scope, |
|
ecs_term_id_t *term_id, |
|
bool free_name, |
|
ecs_filter_finalize_ctx_t *ctx) |
|
{ |
|
const char *name = term_id->name; |
|
if (!name) { |
|
return 0; |
|
} |
|
|
|
if (term_id->flags & EcsIsVariable) { |
|
if (!ecs_os_strcmp(name, "This") || !ecs_os_strcmp(name, "this")) { |
|
term_id->id = EcsThis; |
|
if (free_name) { |
|
/* Safe, if free_name is true the filter owns the name */ |
|
ecs_os_free(ECS_CONST_CAST(char*, term_id->name)); |
|
} |
|
term_id->name = NULL; |
|
} |
|
return 0; |
|
} else if (term_id->flags & EcsIsName) { |
|
return 0; |
|
} |
|
|
|
ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (ecs_identifier_is_0(name)) { |
|
if (term_id->id) { |
|
flecs_filter_error(ctx, "name '0' does not match entity id"); |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
ecs_entity_t e = ecs_lookup_symbol(world, name, true, true); |
|
if (scope && !e) { |
|
e = ecs_lookup_child(world, scope, name); |
|
} |
|
|
|
if (!e) { |
|
if (ctx->filter && (ctx->filter->flags & EcsFilterUnresolvedByName)) { |
|
term_id->flags |= EcsIsName; |
|
term_id->flags &= ~EcsIsEntity; |
|
} else { |
|
flecs_filter_error(ctx, "unresolved identifier '%s'", name); |
|
return -1; |
|
} |
|
} |
|
|
|
if (term_id->id && term_id->id != e) { |
|
char *e_str = ecs_get_fullpath(world, term_id->id); |
|
flecs_filter_error(ctx, "name '%s' does not match term.id '%s'", |
|
name, e_str); |
|
ecs_os_free(e_str); |
|
return -1; |
|
} |
|
|
|
term_id->id = e; |
|
|
|
if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || |
|
!ecs_os_strcmp(name, "$")) |
|
{ |
|
term_id->flags &= ~EcsIsEntity; |
|
term_id->flags |= EcsIsVariable; |
|
} |
|
|
|
/* Check if looked up id is alive (relevant for numerical ids) */ |
|
if (!(term_id->flags & EcsIsName)) { |
|
if (!ecs_is_alive(world, term_id->id)) { |
|
flecs_filter_error(ctx, "identifier '%s' is not alive", term_id->name); |
|
return -1; |
|
} |
|
|
|
if (free_name) { |
|
/* Safe, if free_name is true, the filter owns the name */ |
|
ecs_os_free(ECS_CONST_CAST(char*, name)); |
|
} |
|
|
|
term_id->name = NULL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_term_ids_finalize( |
|
const ecs_world_t *world, |
|
ecs_term_t *term, |
|
ecs_filter_finalize_ctx_t *ctx) |
|
{ |
|
ecs_term_id_t *src = &term->src; |
|
ecs_term_id_t *first = &term->first; |
|
ecs_term_id_t *second = &term->second; |
|
|
|
/* Include inherited components (like from prefabs) by default for src */ |
|
if (!(src->flags & EcsTraverseFlags)) { |
|
src->flags |= EcsSelf | EcsUp; |
|
} |
|
|
|
/* Include subsets for component by default, to support inheritance */ |
|
if (!(first->flags & EcsTraverseFlags)) { |
|
first->flags |= EcsSelf; |
|
if (first->id && first->flags & EcsIsEntity) { |
|
if (flecs_id_record_get(world, ecs_pair(EcsIsA, first->id))) { |
|
first->flags |= EcsDown; |
|
} |
|
} |
|
} |
|
|
|
/* Traverse Self by default for pair target */ |
|
if (!(second->flags & EcsTraverseFlags)) { |
|
second->flags |= EcsSelf; |
|
} |
|
|
|
/* Source defaults to This */ |
|
if ((src->id == 0) && (src->name == NULL) && !(src->flags & EcsIsEntity)) { |
|
src->id = EcsThis; |
|
src->flags |= EcsIsVariable; |
|
} |
|
|
|
/* Initialize term identifier flags */ |
|
if (flecs_term_id_finalize_flags(src, ctx)) { |
|
return -1; |
|
} |
|
if (flecs_term_id_finalize_flags(first, ctx)) { |
|
return -1; |
|
} |
|
|
|
if (flecs_term_id_finalize_flags(second, ctx)) { |
|
return -1; |
|
} |
|
|
|
/* Lookup term identifiers by name */ |
|
if (flecs_term_id_lookup(world, 0, src, term->move, ctx)) { |
|
return -1; |
|
} |
|
if (flecs_term_id_lookup(world, 0, first, term->move, ctx)) { |
|
return -1; |
|
} |
|
|
|
ecs_entity_t first_id = 0; |
|
ecs_entity_t oneof = 0; |
|
if (first->flags & EcsIsEntity) { |
|
first_id = first->id; |
|
|
|
/* If first element of pair has OneOf property, lookup second element of |
|
* pair in the value of the OneOf property */ |
|
oneof = flecs_get_oneof(world, first_id); |
|
} |
|
|
|
if (flecs_term_id_lookup(world, oneof, &term->second, term->move, ctx)) { |
|
return -1; |
|
} |
|
|
|
/* If source is 0, reset traversal flags */ |
|
if (src->id == 0 && src->flags & EcsIsEntity) { |
|
src->flags &= ~EcsTraverseFlags; |
|
src->trav = 0; |
|
} |
|
/* If second is 0, reset traversal flags */ |
|
if (second->id == 0 && second->flags & EcsIsEntity) { |
|
second->flags &= ~EcsTraverseFlags; |
|
second->trav = 0; |
|
} |
|
|
|
/* If source is wildcard, term won't return any data */ |
|
if ((src->flags & EcsIsVariable) && ecs_id_is_wildcard(src->id)) { |
|
term->inout |= EcsInOutNone; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_term_id_get_entity( |
|
const ecs_term_id_t *term_id) |
|
{ |
|
if (term_id->flags & EcsIsEntity) { |
|
return term_id->id; /* Id is known */ |
|
} else if (term_id->flags & EcsIsVariable) { |
|
/* Return wildcard for variables, as they aren't known yet */ |
|
if (term_id->id != EcsAny) { |
|
/* Any variable should not use wildcard, as this would return all |
|
* ids matching a wildcard, whereas Any returns the first match */ |
|
return EcsWildcard; |
|
} else { |
|
return EcsAny; |
|
} |
|
} else { |
|
return 0; /* Term id is uninitialized */ |
|
} |
|
} |
|
|
|
static |
|
int flecs_term_populate_id( |
|
ecs_term_t *term) |
|
{ |
|
ecs_entity_t first = flecs_term_id_get_entity(&term->first); |
|
ecs_entity_t second = flecs_term_id_get_entity(&term->second); |
|
ecs_id_t role = term->id_flags; |
|
|
|
if (first & ECS_ID_FLAGS_MASK) { |
|
return -1; |
|
} |
|
if (second & ECS_ID_FLAGS_MASK) { |
|
return -1; |
|
} |
|
|
|
if ((second || term->second.flags == EcsIsEntity)) { |
|
role = term->id_flags |= ECS_PAIR; |
|
} |
|
|
|
if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) { |
|
term->id = first | role; |
|
} else { |
|
term->id = ecs_pair(first, second) | role; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_term_populate_from_id( |
|
const ecs_world_t *world, |
|
ecs_term_t *term, |
|
ecs_filter_finalize_ctx_t *ctx) |
|
{ |
|
ecs_entity_t first = 0; |
|
ecs_entity_t second = 0; |
|
ecs_id_t role = term->id & ECS_ID_FLAGS_MASK; |
|
|
|
if (!role && term->id_flags) { |
|
role = term->id_flags; |
|
term->id |= role; |
|
} |
|
|
|
if (term->id_flags && term->id_flags != role) { |
|
flecs_filter_error(ctx, "mismatch between term.id & term.id_flags"); |
|
return -1; |
|
} |
|
|
|
term->id_flags = role; |
|
|
|
if (ECS_HAS_ID_FLAG(term->id, PAIR)) { |
|
first = ECS_PAIR_FIRST(term->id); |
|
second = ECS_PAIR_SECOND(term->id); |
|
|
|
if (!first) { |
|
flecs_filter_error(ctx, "missing first element in term.id"); |
|
return -1; |
|
} |
|
if (!second) { |
|
if (first != EcsChildOf) { |
|
flecs_filter_error(ctx, "missing second element in term.id"); |
|
return -1; |
|
} else { |
|
/* (ChildOf, 0) is allowed so filter can be used to efficiently |
|
* query for root entities */ |
|
} |
|
} |
|
} else { |
|
first = term->id & ECS_COMPONENT_MASK; |
|
if (!first) { |
|
flecs_filter_error(ctx, "missing first element in term.id"); |
|
return -1; |
|
} |
|
} |
|
|
|
ecs_entity_t term_first = flecs_term_id_get_entity(&term->first); |
|
if (term_first) { |
|
if ((uint32_t)term_first != (uint32_t)first) { |
|
flecs_filter_error(ctx, "mismatch between term.id and term.first"); |
|
return -1; |
|
} |
|
} else { |
|
if (!(term->first.id = ecs_get_alive(world, first))) { |
|
term->first.id = first; |
|
} |
|
} |
|
|
|
ecs_entity_t term_second = flecs_term_id_get_entity(&term->second); |
|
if (term_second) { |
|
if ((uint32_t)term_second != second) { |
|
flecs_filter_error(ctx, "mismatch between term.id and term.second"); |
|
return -1; |
|
} |
|
} else if (second) { |
|
if (!(term->second.id = ecs_get_alive(world, second))) { |
|
term->second.id = second; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_term_verify_eq_pred( |
|
const ecs_term_t *term, |
|
ecs_filter_finalize_ctx_t *ctx) |
|
{ |
|
ecs_entity_t first_id = term->first.id; |
|
const ecs_term_id_t *second = &term->second; |
|
const ecs_term_id_t *src = &term->src; |
|
|
|
if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { |
|
flecs_filter_error(ctx, "invalid operator combination"); |
|
goto error; |
|
} |
|
|
|
if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { |
|
flecs_filter_error(ctx, "both sides of operator cannot be a name"); |
|
goto error; |
|
} |
|
|
|
if ((src->flags & EcsIsEntity) && (second->flags & EcsIsEntity)) { |
|
flecs_filter_error(ctx, "both sides of operator cannot be an entity"); |
|
goto error; |
|
} |
|
|
|
if (!(src->flags & EcsIsVariable)) { |
|
flecs_filter_error(ctx, "left-hand of operator must be a variable"); |
|
goto error; |
|
} |
|
|
|
if (first_id == EcsPredMatch && !(second->flags & EcsIsName)) { |
|
flecs_filter_error(ctx, "right-hand of match operator must be a string"); |
|
goto error; |
|
} |
|
|
|
if ((src->flags & EcsIsVariable) && (second->flags & EcsIsVariable)) { |
|
if (src->id && src->id == second->id) { |
|
flecs_filter_error(ctx, "both sides of operator are equal"); |
|
goto error; |
|
} |
|
if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { |
|
flecs_filter_error(ctx, "both sides of operator are equal"); |
|
goto error; |
|
} |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int flecs_term_verify( |
|
const ecs_world_t *world, |
|
const ecs_term_t *term, |
|
ecs_filter_finalize_ctx_t *ctx) |
|
{ |
|
const ecs_term_id_t *first = &term->first; |
|
const ecs_term_id_t *second = &term->second; |
|
const ecs_term_id_t *src = &term->src; |
|
ecs_entity_t first_id = 0, second_id = 0; |
|
ecs_id_t role = term->id_flags; |
|
ecs_id_t id = term->id; |
|
|
|
if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { |
|
flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); |
|
return -1; |
|
} |
|
|
|
if (first->flags & EcsIsEntity) { |
|
first_id = first->id; |
|
} |
|
if (second->flags & EcsIsEntity) { |
|
second_id = second->id; |
|
} |
|
|
|
if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { |
|
return flecs_term_verify_eq_pred(term, ctx); |
|
} |
|
|
|
if (role != (id & ECS_ID_FLAGS_MASK)) { |
|
flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); |
|
return -1; |
|
} |
|
|
|
if (ecs_term_id_is_set(second) && !ECS_HAS_ID_FLAG(role, PAIR)) { |
|
flecs_filter_error(ctx, "expected PAIR flag for term with pair"); |
|
return -1; |
|
} else if (!ecs_term_id_is_set(second) && ECS_HAS_ID_FLAG(role, PAIR)) { |
|
if (first_id != EcsChildOf) { |
|
flecs_filter_error(ctx, "unexpected PAIR flag for term without pair"); |
|
return -1; |
|
} else { |
|
/* Exception is made for ChildOf so we can use (ChildOf, 0) to match |
|
* all entities in the root */ |
|
} |
|
} |
|
|
|
if (!ecs_term_id_is_set(src)) { |
|
flecs_filter_error(ctx, "term.src is not initialized"); |
|
return -1; |
|
} |
|
|
|
if (!ecs_term_id_is_set(first)) { |
|
flecs_filter_error(ctx, "term.first is not initialized"); |
|
return -1; |
|
} |
|
|
|
if (ECS_HAS_ID_FLAG(role, PAIR)) { |
|
if (!ECS_PAIR_FIRST(id)) { |
|
flecs_filter_error(ctx, "invalid 0 for first element in pair id"); |
|
return -1; |
|
} |
|
if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { |
|
flecs_filter_error(ctx, "invalid 0 for second element in pair id"); |
|
return -1; |
|
} |
|
|
|
if ((first->flags & EcsIsEntity) && |
|
(ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) |
|
{ |
|
flecs_filter_error(ctx, "mismatch between term.id and term.first"); |
|
return -1; |
|
} |
|
if ((first->flags & EcsIsVariable) && |
|
!ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) |
|
{ |
|
char *id_str = ecs_id_str(world, id); |
|
flecs_filter_error(ctx, |
|
"expected wildcard for variable term.first (got %s)", id_str); |
|
ecs_os_free(id_str); |
|
return -1; |
|
} |
|
|
|
if ((second->flags & EcsIsEntity) && |
|
(ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) |
|
{ |
|
flecs_filter_error(ctx, "mismatch between term.id and term.second"); |
|
return -1; |
|
} |
|
if ((second->flags & EcsIsVariable) && |
|
!ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) |
|
{ |
|
char *id_str = ecs_id_str(world, id); |
|
flecs_filter_error(ctx, |
|
"expected wildcard for variable term.second (got %s)", id_str); |
|
ecs_os_free(id_str); |
|
return -1; |
|
} |
|
} else { |
|
ecs_entity_t component = id & ECS_COMPONENT_MASK; |
|
if (!component) { |
|
flecs_filter_error(ctx, "missing component id"); |
|
return -1; |
|
} |
|
if ((first->flags & EcsIsEntity) && |
|
(ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) |
|
{ |
|
flecs_filter_error(ctx, "mismatch between term.id and term.first"); |
|
return -1; |
|
} |
|
if ((first->flags & EcsIsVariable) && !ecs_id_is_wildcard(component)) { |
|
char *id_str = ecs_id_str(world, id); |
|
flecs_filter_error(ctx, |
|
"expected wildcard for variable term.first (got %s)", id_str); |
|
ecs_os_free(id_str); |
|
return -1; |
|
} |
|
} |
|
|
|
if (first_id) { |
|
if (ecs_term_id_is_set(second)) { |
|
ecs_flags32_t mask = EcsIsEntity | EcsIsVariable; |
|
if ((src->flags & mask) == (second->flags & mask)) { |
|
bool is_same = false; |
|
if (src->flags & EcsIsEntity) { |
|
is_same = src->id == second->id; |
|
} else if (src->name && second->name) { |
|
is_same = !ecs_os_strcmp(src->name, second->name); |
|
} |
|
|
|
if (is_same && ecs_has_id(world, first_id, EcsAcyclic) |
|
&& !(term->flags & EcsTermReflexive)) |
|
{ |
|
char *pred_str = ecs_get_fullpath(world, term->first.id); |
|
flecs_filter_error(ctx, "term with acyclic relationship" |
|
" '%s' cannot have same subject and object", |
|
pred_str); |
|
ecs_os_free(pred_str); |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
if (second_id && !ecs_id_is_wildcard(second_id)) { |
|
ecs_entity_t oneof = flecs_get_oneof(world, first_id); |
|
if (oneof) { |
|
if (!ecs_has_pair(world, second_id, EcsChildOf, oneof)) { |
|
char *second_str = ecs_get_fullpath(world, second_id); |
|
char *oneof_str = ecs_get_fullpath(world, oneof); |
|
char *id_str = ecs_id_str(world, term->id); |
|
flecs_filter_error(ctx, |
|
"invalid target '%s' for %s: must be child of '%s'", |
|
second_str, id_str, oneof_str); |
|
ecs_os_free(second_str); |
|
ecs_os_free(oneof_str); |
|
ecs_os_free(id_str); |
|
return -1; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (term->src.trav) { |
|
if (!ecs_has_id(world, term->src.trav, EcsTraversable)) { |
|
char *r_str = ecs_get_fullpath(world, term->src.trav); |
|
flecs_filter_error(ctx, |
|
"cannot traverse non-traversable relationship '%s'", r_str); |
|
ecs_os_free(r_str); |
|
return -1; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_term_finalize( |
|
const ecs_world_t *world, |
|
ecs_term_t *term, |
|
ecs_filter_finalize_ctx_t *ctx) |
|
{ |
|
ctx->term = term; |
|
|
|
ecs_term_id_t *src = &term->src; |
|
ecs_term_id_t *first = &term->first; |
|
ecs_term_id_t *second = &term->second; |
|
ecs_flags32_t first_flags = first->flags; |
|
ecs_flags32_t src_flags = src->flags; |
|
ecs_flags32_t second_flags = second->flags; |
|
|
|
if (term->id) { |
|
if (flecs_term_populate_from_id(world, term, ctx)) { |
|
return -1; |
|
} |
|
} |
|
|
|
if (flecs_term_ids_finalize(world, term, ctx)) { |
|
return -1; |
|
} |
|
|
|
if ((first->flags & EcsIsVariable) && (term->first.id == EcsAny)) { |
|
term->flags |= EcsTermMatchAny; |
|
} |
|
if ((second->flags & EcsIsVariable) && (term->second.id == EcsAny)) { |
|
term->flags |= EcsTermMatchAny; |
|
} |
|
if ((src->flags & EcsIsVariable) && (term->src.id == EcsAny)) { |
|
term->flags |= EcsTermMatchAnySrc; |
|
} |
|
|
|
/* If EcsVariable is used by itself, assign to predicate (singleton) */ |
|
if ((src->id == EcsVariable) && (src->flags & EcsIsVariable)) { |
|
src->id = first->id; |
|
src->flags &= ~(EcsIsVariable | EcsIsEntity); |
|
src->flags |= first->flags & (EcsIsVariable | EcsIsEntity); |
|
} |
|
if ((second->id == EcsVariable) && (second->flags & EcsIsVariable)) { |
|
second->id = first->id; |
|
second->flags &= ~(EcsIsVariable | EcsIsEntity); |
|
second->flags |= first->flags & (EcsIsVariable | EcsIsEntity); |
|
} |
|
|
|
ecs_flags32_t mask = EcsIsEntity | EcsIsVariable; |
|
if ((src->flags & mask) == (second->flags & mask)) { |
|
bool is_same = false; |
|
if (src->flags & EcsIsEntity) { |
|
is_same = src->id == second->id; |
|
} else if (src->name && second->name) { |
|
is_same = !ecs_os_strcmp(src->name, second->name); |
|
} |
|
if (is_same) { |
|
term->flags |= EcsTermSrcSecondEq; |
|
} |
|
} |
|
if ((src->flags & mask) == (first->flags & mask)) { |
|
bool is_same = false; |
|
if (src->flags & EcsIsEntity) { |
|
is_same = src->id == first->id; |
|
} else if (src->name && first->name) { |
|
is_same = !ecs_os_strcmp(src->name, first->name); |
|
} |
|
if (is_same) { |
|
term->flags |= EcsTermSrcFirstEq; |
|
} |
|
} |
|
|
|
if (!term->id) { |
|
if (flecs_term_populate_id(term)) { |
|
return -1; |
|
} |
|
} |
|
|
|
/* If term queries for !(ChildOf, _), translate it to the builtin |
|
* (ChildOf, 0) index which is a cheaper way to find root entities */ |
|
if (term->oper == EcsNot && term->id == ecs_pair(EcsChildOf, EcsAny)) { |
|
/* Only if the source is not EcsAny */ |
|
if (!(term->src.id == EcsAny && (term->src.flags & EcsIsVariable))) { |
|
term->oper = EcsAnd; |
|
term->id = ecs_pair(EcsChildOf, 0); |
|
second->id = 0; |
|
second->flags |= EcsIsEntity; |
|
second->flags &= ~EcsIsVariable; |
|
} |
|
} |
|
|
|
ecs_entity_t first_id = 0; |
|
if (first->flags & EcsIsEntity) { |
|
first_id = first->id; |
|
} |
|
|
|
term->idr = flecs_query_id_record_get(world, term->id); |
|
ecs_flags32_t id_flags = term->idr ? term->idr->flags : 0; |
|
|
|
if (first_id) { |
|
ecs_entity_t first_trav = first->trav; |
|
|
|
/* If component is inherited from, set correct traversal flags */ |
|
ecs_flags32_t first_trav_flags = first_flags & EcsTraverseFlags; |
|
if (!first_trav && first_trav_flags != EcsSelf) { |
|
/* Inheritance uses IsA by default, but can use any relationship */ |
|
first_trav = EcsIsA; |
|
} |
|
|
|
ecs_record_t *trav_record = NULL; |
|
ecs_table_t *trav_table = NULL; |
|
if (first_trav) { |
|
trav_record = flecs_entities_get(world, first_trav); |
|
trav_table = trav_record ? trav_record->table : NULL; |
|
if (first_trav != EcsIsA) { |
|
if (!trav_table || !ecs_table_has_id(world, trav_table, EcsTraversable)) { |
|
flecs_filter_error(ctx, "first.trav is not traversable"); |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
/* Only enable inheritance for ids which are inherited from at the time |
|
* of filter creation. To force component inheritance to be evaluated, |
|
* an application can explicitly set traversal flags. */ |
|
if ((first_trav_flags & EcsDown) || |
|
flecs_id_record_get(world, ecs_pair(first_trav, first->id))) |
|
{ |
|
if (first_trav_flags == EcsSelf) { |
|
flecs_filter_error(ctx, "first.trav specified with self"); |
|
return -1; |
|
} |
|
|
|
if (!first_trav_flags || (first_trav_flags & EcsDown)) { |
|
term->flags |= EcsTermIdInherited; |
|
first->trav = first_trav; |
|
if (!first_trav_flags) { |
|
first->flags &= ~EcsTraverseFlags; |
|
first->flags |= EcsDown; |
|
ecs_assert(trav_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if ((first_trav == EcsIsA) || ecs_table_has_id( |
|
world, trav_table, EcsReflexive)) |
|
{ |
|
first->flags |= EcsSelf; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Don't traverse ids that cannot be inherited */ |
|
if ((id_flags & EcsIdDontInherit) && (src->trav == EcsIsA)) { |
|
if (src_flags & (EcsUp | EcsDown)) { |
|
flecs_filter_error(ctx, |
|
"traversing not allowed for id that can't be inherited"); |
|
return -1; |
|
} |
|
src->flags &= ~(EcsUp | EcsDown); |
|
src->trav = 0; |
|
} |
|
|
|
/* If component id is final, don't attempt component inheritance */ |
|
ecs_record_t *first_record = flecs_entities_get(world, first_id); |
|
ecs_table_t *first_table = first_record ? first_record->table : NULL; |
|
if (first_table) { |
|
if (ecs_table_has_id(world, first_table, EcsFinal)) { |
|
if (first_flags & EcsDown) { |
|
flecs_filter_error(ctx, "final id cannot be traversed down"); |
|
return -1; |
|
} |
|
} |
|
|
|
/* Add traversal flags for transitive relationships */ |
|
if (!(second_flags & EcsTraverseFlags) && ecs_term_id_is_set(second)) { |
|
if (!((src->flags & EcsIsVariable) && (src->id == EcsAny))) { |
|
if (!((second->flags & EcsIsVariable) && (second->id == EcsAny))) { |
|
if (ecs_table_has_id(world, first_table, EcsTransitive)) { |
|
second->flags |= EcsSelf|EcsUp|EcsTraverseAll; |
|
second->trav = first_id; |
|
term->flags |= EcsTermTransitive; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (ecs_table_has_id(world, first_table, EcsReflexive)) { |
|
term->flags |= EcsTermReflexive; |
|
} |
|
} |
|
} |
|
|
|
if (first->id == EcsVariable) { |
|
flecs_filter_error(ctx, "invalid $ for term.first"); |
|
return -1; |
|
} |
|
|
|
if (term->id_flags & ECS_AND) { |
|
term->oper = EcsAndFrom; |
|
term->id &= ECS_COMPONENT_MASK; |
|
term->id_flags = 0; |
|
} |
|
|
|
if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { |
|
if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { |
|
flecs_filter_error(ctx, |
|
"invalid inout value for AndFrom/OrFrom/NotFrom term"); |
|
return -1; |
|
} |
|
} |
|
|
|
/* Is term trivial */ |
|
bool trivial_term = true; |
|
if (term->oper != EcsAnd) { |
|
trivial_term = false; |
|
} |
|
if (ecs_id_is_wildcard(term->id)) { |
|
if (!(term->idr && term->idr->flags & EcsIdExclusive)) { |
|
trivial_term = false; |
|
} |
|
if (first->flags & EcsIsVariable) { |
|
if (!ecs_id_is_wildcard(first->id) || first->id == EcsAny) { |
|
trivial_term = false; |
|
} |
|
} |
|
if (second->flags & EcsIsVariable) { |
|
if (!ecs_id_is_wildcard(second->id) || second->id == EcsAny) { |
|
trivial_term = false; |
|
} |
|
} |
|
} |
|
if (!ecs_term_match_this(term)) { |
|
trivial_term = false; |
|
} |
|
if (term->flags & EcsTermIdInherited) { |
|
trivial_term = false; |
|
} |
|
if (src->trav && src->trav != EcsIsA) { |
|
trivial_term = false; |
|
} |
|
if (first->trav && first->trav != EcsIsA) { |
|
trivial_term = false; |
|
} |
|
if (second->trav && second->trav != EcsIsA) { |
|
trivial_term = false; |
|
} |
|
if (!(src->flags & EcsSelf)) { |
|
trivial_term = false; |
|
} |
|
if (((term->first.id == EcsPredEq) || (term->first.id == EcsPredMatch)) && |
|
(term->first.flags & EcsIsEntity)) |
|
{ |
|
trivial_term = false; |
|
} |
|
|
|
if (trivial_term) { |
|
ECS_BIT_SET(term->flags, EcsTermIsTrivial); |
|
} |
|
|
|
if (flecs_term_verify(world, term, ctx)) { |
|
return -1; |
|
} |
|
|
|
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_ID_FLAG(id, PAIR)) { |
|
ecs_entity_t first = ECS_PAIR_FIRST(id); |
|
ecs_id_record_t *idr = flecs_id_record_ensure(world, |
|
ecs_pair(first, EcsWildcard)); |
|
if (idr->flags & EcsIdUnion) { |
|
return ecs_pair(EcsUnion, first); |
|
} |
|
} |
|
|
|
return id; |
|
} |
|
|
|
bool ecs_identifier_is_0( |
|
const char *id) |
|
{ |
|
return id[0] == '0' && !id[1]; |
|
} |
|
|
|
bool ecs_id_match( |
|
ecs_id_t id, |
|
ecs_id_t pattern) |
|
{ |
|
if (id == pattern) { |
|
return true; |
|
} |
|
|
|
if (ECS_HAS_ID_FLAG(pattern, PAIR)) { |
|
if (!ECS_HAS_ID_FLAG(id, PAIR)) { |
|
return false; |
|
} |
|
|
|
ecs_entity_t id_rel = ECS_PAIR_FIRST(id); |
|
ecs_entity_t id_obj = ECS_PAIR_SECOND(id); |
|
ecs_entity_t pattern_rel = ECS_PAIR_FIRST(pattern); |
|
ecs_entity_t pattern_obj = ECS_PAIR_SECOND(pattern); |
|
|
|
ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_check(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (pattern_rel == EcsWildcard) { |
|
if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { |
|
return true; |
|
} |
|
} else if (pattern_rel == EcsFlag) { |
|
/* Used for internals, helps to keep track of which ids are used in |
|
* pairs that have additional flags (like OVERRIDE and TOGGLE) */ |
|
if (ECS_HAS_ID_FLAG(id, PAIR) && !ECS_IS_PAIR(id)) { |
|
if (ECS_PAIR_FIRST(id) == pattern_obj) { |
|
return true; |
|
} |
|
if (ECS_PAIR_SECOND(id) == pattern_obj) { |
|
return true; |
|
} |
|
} |
|
} else if (pattern_obj == EcsWildcard) { |
|
if (pattern_rel == id_rel) { |
|
return true; |
|
} |
|
} |
|
} else { |
|
if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { |
|
return false; |
|
} |
|
|
|
if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { |
|
return true; |
|
} |
|
} |
|
|
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_id_is_pair( |
|
ecs_id_t id) |
|
{ |
|
return ECS_HAS_ID_FLAG(id, PAIR); |
|
} |
|
|
|
bool ecs_id_is_wildcard( |
|
ecs_id_t id) |
|
{ |
|
if ((id == EcsWildcard) || (id == EcsAny)) { |
|
return true; |
|
} |
|
|
|
bool is_pair = ECS_IS_PAIR(id); |
|
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( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
if (!id) { |
|
return false; |
|
} |
|
if (ecs_id_is_wildcard(id)) { |
|
return false; |
|
} |
|
|
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
if (!ECS_PAIR_FIRST(id)) { |
|
return false; |
|
} |
|
if (!ECS_PAIR_SECOND(id)) { |
|
return false; |
|
} |
|
} else if (id & ECS_ID_FLAGS_MASK) { |
|
if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
ecs_flags32_t ecs_id_get_flags( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (idr) { |
|
return idr->flags; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
bool ecs_term_id_is_set( |
|
const ecs_term_id_t *id) |
|
{ |
|
return id->id != 0 || id->name != NULL || id->flags & EcsIsEntity; |
|
} |
|
|
|
bool ecs_term_is_initialized( |
|
const ecs_term_t *term) |
|
{ |
|
return term->id != 0 || ecs_term_id_is_set(&term->first); |
|
} |
|
|
|
bool ecs_term_match_this( |
|
const ecs_term_t *term) |
|
{ |
|
return (term->src.flags & EcsIsVariable) && (term->src.id == EcsThis); |
|
} |
|
|
|
bool ecs_term_match_0( |
|
const ecs_term_t *term) |
|
{ |
|
return (term->src.id == 0) && (term->src.flags & EcsIsEntity); |
|
} |
|
|
|
int ecs_term_finalize( |
|
const ecs_world_t *world, |
|
ecs_term_t *term) |
|
{ |
|
ecs_filter_finalize_ctx_t ctx = {0}; |
|
ctx.world = world; |
|
ctx.term = term; |
|
return flecs_term_finalize(world, term, &ctx); |
|
} |
|
|
|
ecs_term_t ecs_term_copy( |
|
const ecs_term_t *src) |
|
{ |
|
ecs_term_t dst = *src; |
|
dst.name = ecs_os_strdup(src->name); |
|
dst.first.name = ecs_os_strdup(src->first.name); |
|
dst.src.name = ecs_os_strdup(src->src.name); |
|
dst.second.name = ecs_os_strdup(src->second.name); |
|
return dst; |
|
} |
|
|
|
ecs_term_t ecs_term_move( |
|
ecs_term_t *src) |
|
{ |
|
if (src->move) { |
|
ecs_term_t dst = *src; |
|
src->name = NULL; |
|
src->first.name = NULL; |
|
src->src.name = NULL; |
|
src->second.name = NULL; |
|
dst.move = false; |
|
return dst; |
|
} else { |
|
ecs_term_t dst = ecs_term_copy(src); |
|
dst.move = false; |
|
return dst; |
|
} |
|
} |
|
|
|
void ecs_term_fini( |
|
ecs_term_t *term) |
|
{ |
|
/* Safe, values are owned by term */ |
|
ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); |
|
ecs_os_free(ECS_CONST_CAST(char*, term->src.name)); |
|
ecs_os_free(ECS_CONST_CAST(char*, term->second.name)); |
|
ecs_os_free(term->name); |
|
|
|
term->first.name = NULL; |
|
term->src.name = NULL; |
|
term->second.name = NULL; |
|
term->name = NULL; |
|
} |
|
|
|
static |
|
ecs_term_t* flecs_filter_or_other_type( |
|
ecs_filter_t *f, |
|
int32_t t) |
|
{ |
|
ecs_term_t *term = &f->terms[t]; |
|
ecs_term_t *first = NULL; |
|
while (t--) { |
|
if (f->terms[t].oper != EcsOr) { |
|
break; |
|
} |
|
first = &f->terms[t]; |
|
} |
|
|
|
if (first) { |
|
ecs_world_t *world = f->world; |
|
const ecs_type_info_t *first_type; |
|
if (first->idr) { |
|
first_type = first->idr->type_info; |
|
} else { |
|
first_type = ecs_get_type_info(world, first->id); |
|
} |
|
const ecs_type_info_t *term_type; |
|
if (term->idr) { |
|
term_type = term->idr->type_info; |
|
} else { |
|
term_type = ecs_get_type_info(world, term->id); |
|
} |
|
|
|
if (first_type == term_type) { |
|
return NULL; |
|
} |
|
return first; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
int ecs_filter_finalize( |
|
const ecs_world_t *world, |
|
ecs_filter_t *f) |
|
{ |
|
int32_t i, term_count = f->term_count, field_count = 0; |
|
ecs_term_t *terms = f->terms; |
|
int32_t filter_terms = 0, scope_nesting = 0; |
|
bool cond_set = false; |
|
|
|
ecs_filter_finalize_ctx_t ctx = {0}; |
|
ctx.world = world; |
|
ctx.filter = f; |
|
|
|
f->flags |= EcsFilterMatchOnlyThis; |
|
|
|
for (i = 0; i < term_count; i ++) { |
|
bool filter_term = false; |
|
ecs_term_t *term = &terms[i]; |
|
ctx.term_index = i; |
|
if (flecs_term_finalize(world, term, &ctx)) { |
|
return -1; |
|
} |
|
|
|
if (i && term[-1].oper == EcsOr) { |
|
if (term[-1].src.id != term->src.id) { |
|
flecs_filter_error(&ctx, "mismatching src.id for OR terms"); |
|
return -1; |
|
} |
|
if (term->oper != EcsOr && term->oper != EcsAnd) { |
|
flecs_filter_error(&ctx, |
|
"term after OR operator must use AND operator"); |
|
return -1; |
|
} |
|
} else { |
|
field_count ++; |
|
} |
|
|
|
term->field_index = field_count - 1; |
|
|
|
if (ecs_id_is_wildcard(term->id)) { |
|
f->flags |= EcsFilterHasWildcards; |
|
} |
|
|
|
if (ecs_term_match_this(term)) { |
|
ECS_BIT_SET(f->flags, EcsFilterMatchThis); |
|
} else { |
|
ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); |
|
} |
|
|
|
if (term->id == EcsPrefab) { |
|
ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); |
|
} |
|
if (term->id == EcsDisabled && (term->src.flags & EcsSelf)) { |
|
ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); |
|
} |
|
|
|
if (ECS_BIT_IS_SET(f->flags, EcsFilterNoData)) { |
|
term->inout = EcsInOutNone; |
|
} |
|
|
|
if (term->oper == EcsNot && term->inout == EcsInOutDefault) { |
|
term->inout = EcsInOutNone; |
|
} |
|
|
|
if ((term->id == EcsWildcard) || (term->id == |
|
ecs_pair(EcsWildcard, EcsWildcard))) |
|
{ |
|
/* If term type is unknown beforehand, default the inout type to |
|
* none. This prevents accidentally requesting lots of components, |
|
* which can put stress on serializer code. */ |
|
if (term->inout == EcsInOutDefault) { |
|
term->inout = EcsInOutNone; |
|
} |
|
} |
|
|
|
if (term->inout == EcsInOutNone) { |
|
filter_term = true; |
|
} else if (term->idr) { |
|
if (!term->idr->type_info && !(term->idr->flags & EcsIdUnion)) { |
|
filter_term = true; |
|
} |
|
} else if (!ecs_id_is_union(world, term->id)) { |
|
/* Union ids aren't filters because they return their target |
|
* as component value with type ecs_entity_t */ |
|
if (ecs_id_is_tag(world, term->id)) { |
|
filter_term = true; |
|
} else if (ECS_PAIR_SECOND(term->id) == EcsWildcard) { |
|
/* If the second element of a pair is a wildcard and the first |
|
* element is not a type, we can't know in advance what the |
|
* type of the term is, so it can't provide data. */ |
|
if (!ecs_get_type_info(world, ecs_pair_first(world, term->id))) { |
|
filter_term = true; |
|
} |
|
} |
|
} |
|
|
|
if (!filter_term) { |
|
if (term->oper == EcsOr || (i && term[-1].oper == EcsOr)) { |
|
ecs_term_t *first = flecs_filter_or_other_type(f, i); |
|
if (first) { |
|
if (first == &term[-1]) { |
|
filter_terms ++; |
|
} |
|
filter_term = true; |
|
} |
|
} |
|
} |
|
|
|
if (filter_term) { |
|
filter_terms ++; |
|
term->flags |= EcsTermNoData; |
|
} else { |
|
f->data_fields |= (1llu << term->field_index); |
|
} |
|
|
|
if (term->oper != EcsNot || !ecs_term_match_this(term)) { |
|
ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); |
|
} |
|
|
|
if (term->idr) { |
|
if (ecs_os_has_threading()) { |
|
ecs_os_ainc(&term->idr->keep_alive); |
|
} else { |
|
term->idr->keep_alive ++; |
|
} |
|
} |
|
|
|
if (term->oper == EcsOptional || term->oper == EcsNot) { |
|
cond_set = true; |
|
} |
|
|
|
if (term->first.id == EcsPredEq || term->first.id == EcsPredMatch || |
|
term->first.id == EcsPredLookup) |
|
{ |
|
f->flags |= EcsFilterHasPred; |
|
} |
|
|
|
if (term->first.id == EcsScopeOpen) { |
|
f->flags |= EcsFilterHasScopes; |
|
scope_nesting ++; |
|
} |
|
|
|
if (term->first.id == EcsScopeClose) { |
|
if (i && terms[i - 1].first.id == EcsScopeOpen) { |
|
flecs_filter_error(&ctx, "invalid empty scope"); |
|
return -1; |
|
} |
|
|
|
f->flags |= EcsFilterHasScopes; |
|
scope_nesting --; |
|
} |
|
if (scope_nesting < 0) { |
|
flecs_filter_error(&ctx, "'}' without matching '{'"); |
|
} |
|
} |
|
|
|
if (scope_nesting != 0) { |
|
flecs_filter_error(&ctx, "missing '}'"); |
|
return -1; |
|
} |
|
|
|
if (term_count && (terms[term_count - 1].oper == EcsOr)) { |
|
flecs_filter_error(&ctx, "last term of filter can't have OR operator"); |
|
return -1; |
|
} |
|
|
|
f->field_count = flecs_ito(int8_t, field_count); |
|
|
|
if (field_count) { |
|
for (i = 0; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
ecs_id_record_t *idr = term->idr; |
|
int32_t field = term->field_index; |
|
f->ids[field] = term->id; |
|
|
|
if (term->oper == EcsOr || (i && (term[-1].oper == EcsOr))) { |
|
if (flecs_filter_or_other_type(f, i)) { |
|
f->sizes[field] = 0; |
|
f->ids[field] = 0; |
|
continue; |
|
} |
|
} |
|
|
|
if (idr) { |
|
if (!ECS_IS_PAIR(idr->id) || ECS_PAIR_FIRST(idr->id) != EcsWildcard) { |
|
if (idr->flags & EcsIdUnion) { |
|
f->sizes[field] = ECS_SIZEOF(ecs_entity_t); |
|
#ifdef FLECS_META |
|
f->ids[field] = ecs_id(ecs_entity_t); |
|
#endif |
|
} else if (idr->type_info) { |
|
f->sizes[field] = idr->type_info->size; |
|
f->ids[field] = idr->id; |
|
} |
|
} |
|
} else { |
|
bool is_union = false; |
|
if (ECS_IS_PAIR(term->id)) { |
|
ecs_entity_t first = ecs_pair_first(world, term->id); |
|
if (ecs_has_id(world, first, EcsUnion)) { |
|
is_union = true; |
|
} |
|
} |
|
if (is_union) { |
|
f->sizes[field] = ECS_SIZEOF(ecs_entity_t); |
|
#ifdef FLECS_META |
|
f->ids[field] = ecs_id(ecs_entity_t); |
|
#endif |
|
} else { |
|
const ecs_type_info_t *ti = ecs_get_type_info( |
|
world, term->id); |
|
if (ti) { |
|
f->sizes[field] = ti->size; |
|
f->ids[field] = term->id; |
|
} |
|
} |
|
} |
|
} |
|
} else { |
|
f->sizes = NULL; |
|
f->ids = NULL; |
|
} |
|
|
|
ecs_assert(filter_terms <= term_count, ECS_INTERNAL_ERROR, NULL); |
|
if (filter_terms == term_count) { |
|
ECS_BIT_SET(f->flags, EcsFilterNoData); |
|
} |
|
|
|
ECS_BIT_COND(f->flags, EcsFilterHasCondSet, cond_set); |
|
|
|
/* Check if this is a trivial filter */ |
|
if ((f->flags & EcsFilterMatchOnlyThis)) { |
|
if (!(f->flags & |
|
(EcsFilterHasPred|EcsFilterMatchDisabled|EcsFilterMatchPrefab))) |
|
{ |
|
ECS_BIT_SET(f->flags, EcsFilterMatchOnlySelf); |
|
|
|
for (i = 0; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
ecs_term_id_t *src = &term->src; |
|
|
|
if (src->flags & EcsUp) { |
|
ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlySelf); |
|
} |
|
|
|
if (!(term->flags & EcsTermIsTrivial)) { |
|
break; |
|
} |
|
|
|
if (!(f->flags & EcsFilterNoData)) { |
|
if (term->inout == EcsInOutNone) { |
|
break; |
|
} |
|
} |
|
} |
|
if (term_count && (i == term_count)) { |
|
ECS_BIT_SET(f->flags, EcsFilterIsTrivial); |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Implementation for iterable mixin */ |
|
static |
|
void flecs_filter_iter_init( |
|
const ecs_world_t *world, |
|
const ecs_poly_t *poly, |
|
ecs_iter_t *iter, |
|
ecs_term_t *filter) |
|
{ |
|
ecs_poly_assert(poly, ecs_filter_t); |
|
|
|
if (filter) { |
|
iter[1] = ecs_filter_iter(world, ECS_CONST_CAST(ecs_filter_t*, poly)); |
|
iter[0] = ecs_term_chain_iter(&iter[1], filter); |
|
} else { |
|
iter[0] = ecs_filter_iter(world, ECS_CONST_CAST(ecs_filter_t*, poly)); |
|
} |
|
} |
|
|
|
/* Implementation for dtor mixin */ |
|
static |
|
void flecs_filter_fini( |
|
ecs_filter_t *filter) |
|
{ |
|
if (filter->terms) { |
|
int i, count = filter->term_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_term_t *term = &filter->terms[i]; |
|
if (term->idr) { |
|
if (!(filter->world->flags & EcsWorldQuit)) { |
|
if (ecs_os_has_threading()) { |
|
ecs_os_adec(&term->idr->keep_alive); |
|
} else { |
|
term->idr->keep_alive --; |
|
} |
|
} |
|
} |
|
ecs_term_fini(&filter->terms[i]); |
|
} |
|
|
|
if (filter->flags & EcsFilterOwnsTermsStorage) { |
|
/* Memory allocated for both terms & sizes */ |
|
ecs_os_free(filter->terms); |
|
} else { |
|
ecs_os_free(filter->sizes); |
|
ecs_os_free(filter->ids); |
|
} |
|
} |
|
|
|
filter->terms = NULL; |
|
|
|
if (filter->flags & EcsFilterOwnsStorage) { |
|
ecs_os_free(filter); |
|
} |
|
} |
|
|
|
void ecs_filter_fini( |
|
ecs_filter_t *filter) |
|
{ |
|
if ((filter->flags & EcsFilterOwnsStorage) && filter->entity) { |
|
/* If filter is associated with entity, use poly dtor path */ |
|
ecs_delete(filter->world, filter->entity); |
|
} else { |
|
flecs_filter_fini(filter); |
|
} |
|
} |
|
|
|
ecs_filter_t* ecs_filter_init( |
|
ecs_world_t *world, |
|
const ecs_filter_desc_t *desc) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
flecs_stage_from_world(&world); |
|
|
|
ecs_filter_t *f = desc->storage; |
|
int32_t i, term_count = desc->terms_buffer_count, storage_count = 0, expr_count = 0; |
|
const ecs_term_t *terms = desc->terms_buffer; |
|
ecs_term_t *storage_terms = NULL, *expr_terms = NULL; |
|
|
|
if (f) { |
|
ecs_check(f->hdr.magic == ecs_filter_t_magic, |
|
ECS_INVALID_PARAMETER, NULL); |
|
storage_count = f->term_count; |
|
storage_terms = f->terms; |
|
ecs_poly_init(f, ecs_filter_t); |
|
} else { |
|
f = ecs_poly_new(ecs_filter_t); |
|
f->flags |= EcsFilterOwnsStorage; |
|
} |
|
if (!storage_terms) { |
|
f->flags |= EcsFilterOwnsTermsStorage; |
|
} |
|
|
|
ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced); |
|
ECS_BIT_SET(f->flags, EcsFilterMatchAnything); |
|
f->flags |= desc->flags; |
|
f->world = world; |
|
|
|
/* If terms_buffer was not set, count number of initialized terms in |
|
* static desc::terms array */ |
|
if (!terms) { |
|
ecs_check(term_count == 0, ECS_INVALID_PARAMETER, NULL); |
|
terms = desc->terms; |
|
for (i = 0; i < FLECS_TERM_DESC_MAX; i ++) { |
|
if (!ecs_term_is_initialized(&terms[i])) { |
|
break; |
|
} |
|
term_count ++; |
|
} |
|
} else { |
|
ecs_check(term_count != 0, ECS_INVALID_PARAMETER, NULL); |
|
} |
|
|
|
/* If expr is set, parse query expression */ |
|
const char *expr = desc->expr; |
|
ecs_entity_t entity = desc->entity; |
|
if (expr) { |
|
#ifdef FLECS_PARSER |
|
const char *name = NULL; |
|
const char *ptr = desc->expr; |
|
ecs_term_t term = {0}; |
|
ecs_term_id_t extra_args[ECS_PARSER_MAX_ARGS]; |
|
ecs_oper_kind_t extra_oper = 0; |
|
int32_t expr_size = 0; |
|
|
|
ecs_os_zeromem(extra_args); |
|
|
|
if (entity) { |
|
name = ecs_get_name(world, entity); |
|
} |
|
|
|
while (ptr[0] && (ptr = ecs_parse_term( |
|
world, name, expr, ptr, &term, &extra_oper, extra_args, true))) |
|
{ |
|
if (!ecs_term_is_initialized(&term)) { |
|
break; |
|
} |
|
|
|
int32_t arg = 0; |
|
|
|
do { |
|
ecs_assert(arg <= ECS_PARSER_MAX_ARGS, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (expr_count == expr_size) { |
|
expr_size = expr_size ? expr_size * 2 : 8; |
|
expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); |
|
} |
|
|
|
ecs_term_t *expr_term = &expr_terms[expr_count ++]; |
|
*expr_term = term; |
|
|
|
if (arg) { |
|
if (extra_oper == EcsAnd) { |
|
expr_term->src = expr_term[-1].second; |
|
expr_term->second = extra_args[arg - 1]; |
|
} else if (extra_oper == EcsOr) { |
|
expr_term->src = expr_term[-1].src; |
|
expr_term->second = extra_args[arg - 1]; |
|
expr_term[-1].oper = EcsOr; |
|
} |
|
if (expr_term->first.name != NULL) { |
|
expr_term->first.name = ecs_os_strdup( |
|
expr_term->first.name); |
|
} |
|
if (expr_term->src.name != NULL) { |
|
expr_term->src.name = ecs_os_strdup( |
|
expr_term->src.name); |
|
} |
|
} |
|
} while (ecs_term_id_is_set(&extra_args[arg ++])); |
|
|
|
if (ptr[0] == '\n') { |
|
break; |
|
} |
|
|
|
ecs_os_zeromem(extra_args); |
|
} |
|
|
|
if (!ptr) { |
|
/* Set terms in filter object to make sur they get cleaned up */ |
|
f->terms = expr_terms; |
|
f->term_count = flecs_ito(int8_t, expr_count); |
|
f->flags |= EcsFilterOwnsTermsStorage; |
|
goto error; |
|
} |
|
#else |
|
(void)expr; |
|
ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); |
|
#endif |
|
} |
|
|
|
/* If storage is provided, make sure it's large enough */ |
|
ecs_check(!storage_terms || storage_count >= (term_count + expr_count), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (term_count || expr_count) { |
|
/* Allocate storage for terms and sizes array */ |
|
if (!storage_terms) { |
|
ecs_assert(f->flags & EcsFilterOwnsTermsStorage, |
|
ECS_INTERNAL_ERROR, NULL); |
|
f->term_count = flecs_ito(int8_t, term_count + expr_count); |
|
ecs_size_t terms_size = ECS_SIZEOF(ecs_term_t) * f->term_count; |
|
ecs_size_t ids_size = ECS_SIZEOF(ecs_id_t) * f->term_count; |
|
ecs_size_t sizes_size = ECS_SIZEOF(int32_t) * f->term_count; |
|
f->terms = ecs_os_calloc(terms_size + sizes_size + ids_size); |
|
f->ids = ECS_OFFSET(f->terms, terms_size); |
|
f->sizes = ECS_OFFSET(f->terms, terms_size + ids_size); |
|
} else { |
|
f->terms = storage_terms; |
|
f->term_count = flecs_ito(int8_t, storage_count); |
|
f->sizes = ecs_os_calloc_n(ecs_size_t, term_count); |
|
f->ids = ecs_os_calloc_n(ecs_id_t, term_count); |
|
} |
|
|
|
/* Copy terms to filter storage */ |
|
for (i = 0; i < term_count; i ++) { |
|
f->terms[i] = ecs_term_copy(&terms[i]); |
|
/* Allow freeing resources from expr parser during finalization */ |
|
f->terms[i].move = true; |
|
} |
|
|
|
/* Move expr terms to filter storage */ |
|
for (i = 0; i < expr_count; i ++) { |
|
f->terms[i + term_count] = ecs_term_move(&expr_terms[i]); |
|
/* Allow freeing resources from expr parser during finalization */ |
|
f->terms[i + term_count].move = true; |
|
} |
|
ecs_os_free(expr_terms); |
|
} |
|
|
|
/* Ensure all fields are consistent and properly filled out */ |
|
if (ecs_filter_finalize(world, f)) { |
|
goto error; |
|
} |
|
|
|
/* Any allocated resources remaining in terms are now owned by filter */ |
|
for (i = 0; i < f->term_count; i ++) { |
|
f->terms[i].move = false; |
|
} |
|
|
|
f->variable_names[0] = NULL; |
|
f->iterable.init = flecs_filter_iter_init; |
|
f->dtor = (ecs_poly_dtor_t)flecs_filter_fini; |
|
f->entity = entity; |
|
|
|
if (entity && (f->flags & EcsFilterOwnsStorage)) { |
|
EcsPoly *poly = ecs_poly_bind(world, entity, ecs_filter_t); |
|
poly->poly = f; |
|
ecs_poly_modified(world, entity, ecs_filter_t); |
|
} |
|
|
|
return f; |
|
error: |
|
ecs_filter_fini(f); |
|
return NULL; |
|
} |
|
|
|
void ecs_filter_copy( |
|
ecs_filter_t *dst, |
|
const ecs_filter_t *src) |
|
{ |
|
if (src == dst) { |
|
return; |
|
} |
|
|
|
if (src) { |
|
*dst = *src; |
|
|
|
int32_t i, term_count = src->term_count; |
|
ecs_size_t terms_size = ECS_SIZEOF(ecs_term_t) * term_count; |
|
ecs_size_t sizes_size = ECS_SIZEOF(int32_t) * term_count; |
|
ecs_size_t ids_size = ECS_SIZEOF(ecs_id_t) * term_count; |
|
dst->terms = ecs_os_malloc(terms_size + sizes_size + ids_size); |
|
dst->ids = ECS_OFFSET(dst->terms, terms_size); |
|
dst->sizes = ECS_OFFSET(dst->terms, terms_size + ids_size); |
|
dst->flags |= EcsFilterOwnsTermsStorage; |
|
ecs_os_memcpy_n(dst->sizes, src->sizes, int32_t, term_count); |
|
ecs_os_memcpy_n(dst->ids, src->ids, int32_t, term_count); |
|
|
|
for (i = 0; i < term_count; i ++) { |
|
dst->terms[i] = ecs_term_copy(&src->terms[i]); |
|
} |
|
} else { |
|
ecs_os_memset_t(dst, 0, ecs_filter_t); |
|
} |
|
} |
|
|
|
void ecs_filter_move( |
|
ecs_filter_t *dst, |
|
ecs_filter_t *src) |
|
{ |
|
if (src == dst) { |
|
return; |
|
} |
|
|
|
if (src) { |
|
*dst = *src; |
|
if (src->flags & EcsFilterOwnsTermsStorage) { |
|
dst->terms = src->terms; |
|
dst->sizes = src->sizes; |
|
dst->ids = src->ids; |
|
dst->flags |= EcsFilterOwnsTermsStorage; |
|
} else { |
|
ecs_filter_copy(dst, src); |
|
} |
|
src->terms = NULL; |
|
src->sizes = NULL; |
|
src->ids = NULL; |
|
src->term_count = 0; |
|
} else { |
|
ecs_os_memset_t(dst, 0, ecs_filter_t); |
|
} |
|
} |
|
|
|
static |
|
void flecs_filter_str_add_id( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
const ecs_term_id_t *id, |
|
bool is_subject, |
|
ecs_flags32_t default_traverse_flags) |
|
{ |
|
bool is_added = false; |
|
if (!is_subject || id->id != EcsThis) { |
|
if (id->flags & EcsIsVariable && !ecs_id_is_wildcard(id->id)) { |
|
ecs_strbuf_appendlit(buf, "$"); |
|
} |
|
if (id->id) { |
|
char *path = ecs_get_fullpath(world, id->id); |
|
ecs_strbuf_appendstr(buf, path); |
|
ecs_os_free(path); |
|
} else if (id->name) { |
|
ecs_strbuf_appendstr(buf, id->name); |
|
} else { |
|
ecs_strbuf_appendlit(buf, "0"); |
|
} |
|
is_added = true; |
|
} |
|
|
|
ecs_flags32_t flags = id->flags; |
|
if (!(flags & EcsTraverseFlags)) { |
|
/* If flags haven't been set yet, initialize with defaults. This can |
|
* happen if an error is thrown while the term is being finalized */ |
|
flags |= default_traverse_flags; |
|
} |
|
|
|
if ((flags & EcsTraverseFlags) != default_traverse_flags) { |
|
if (is_added) { |
|
ecs_strbuf_list_push(buf, ":", "|"); |
|
} else { |
|
ecs_strbuf_list_push(buf, "", "|"); |
|
} |
|
if (id->flags & EcsSelf) { |
|
ecs_strbuf_list_appendstr(buf, "self"); |
|
} |
|
if (id->flags & EcsUp) { |
|
ecs_strbuf_list_appendstr(buf, "up"); |
|
} |
|
if (id->flags & EcsDown) { |
|
ecs_strbuf_list_appendstr(buf, "down"); |
|
} |
|
|
|
if (id->trav && (id->trav != EcsIsA)) { |
|
ecs_strbuf_list_push(buf, "(", ""); |
|
|
|
char *rel_path = ecs_get_fullpath(world, id->trav); |
|
ecs_strbuf_appendstr(buf, rel_path); |
|
ecs_os_free(rel_path); |
|
|
|
ecs_strbuf_list_pop(buf, ")"); |
|
} |
|
|
|
ecs_strbuf_list_pop(buf, ""); |
|
} |
|
} |
|
|
|
static |
|
void flecs_term_str_w_strbuf( |
|
const ecs_world_t *world, |
|
const ecs_term_t *term, |
|
ecs_strbuf_t *buf, |
|
int32_t t) |
|
{ |
|
const ecs_term_id_t *src = &term->src; |
|
const ecs_term_id_t *second = &term->second; |
|
|
|
uint8_t def_src_mask = EcsSelf|EcsUp; |
|
uint8_t def_first_mask = EcsSelf; |
|
uint8_t def_second_mask = EcsSelf; |
|
|
|
bool pred_set = ecs_term_id_is_set(&term->first); |
|
bool subj_set = !ecs_term_match_0(term); |
|
bool obj_set = ecs_term_id_is_set(second); |
|
|
|
if (term->first.id == EcsScopeOpen) { |
|
ecs_strbuf_appendlit(buf, "{"); |
|
return; |
|
} else if (term->first.id == EcsScopeClose) { |
|
ecs_strbuf_appendlit(buf, "}"); |
|
return; |
|
} |
|
|
|
if (((term->first.id == EcsPredEq) || (term->first.id == EcsPredMatch)) && |
|
(term->first.flags & EcsIsEntity)) |
|
{ |
|
ecs_strbuf_appendlit(buf, "$"); |
|
if (term->src.id == EcsThis && term->src.flags & EcsIsVariable) { |
|
ecs_strbuf_appendlit(buf, "this"); |
|
} else if (term->src.flags & EcsIsVariable) { |
|
ecs_strbuf_appendstr(buf, term->src.name); |
|
} else { |
|
/* Shouldn't happen */ |
|
} |
|
|
|
if (term->first.id == EcsPredEq) { |
|
if (term->oper == EcsNot) { |
|
ecs_strbuf_appendlit(buf, " != "); |
|
} else { |
|
ecs_strbuf_appendlit(buf, " == "); |
|
} |
|
} else if (term->first.id == EcsPredMatch) { |
|
ecs_strbuf_appendlit(buf, " ~= "); |
|
} |
|
|
|
if (term->second.flags & EcsIsEntity) { |
|
if (term->second.id != 0) { |
|
ecs_get_path_w_sep_buf( |
|
world, 0, term->second.id, ".", NULL, buf); |
|
} |
|
} else { |
|
if (term->second.flags & EcsIsVariable) { |
|
ecs_strbuf_appendlit(buf, "$"); |
|
if (term->second.name) { |
|
ecs_strbuf_appendstr(buf, term->second.name); |
|
} else if (term->second.id == EcsThis) { |
|
ecs_strbuf_appendlit(buf, "this"); |
|
} |
|
} else if (term->second.flags & EcsIsName) { |
|
ecs_strbuf_appendlit(buf, "\""); |
|
if (term->first.id == EcsPredMatch && term->oper == EcsNot) { |
|
ecs_strbuf_appendlit(buf, "!"); |
|
} |
|
ecs_strbuf_appendstr(buf, term->second.name); |
|
ecs_strbuf_appendlit(buf, "\""); |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
if (!t || !(term[-1].oper == EcsOr)) { |
|
if (term->inout == EcsIn) { |
|
ecs_strbuf_appendlit(buf, "[in] "); |
|
} else if (term->inout == EcsInOut) { |
|
ecs_strbuf_appendlit(buf, "[inout] "); |
|
} else if (term->inout == EcsOut) { |
|
ecs_strbuf_appendlit(buf, "[out] "); |
|
} else if (term->inout == EcsInOutNone && term->oper != EcsNot) { |
|
ecs_strbuf_appendlit(buf, "[none] "); |
|
} |
|
} |
|
|
|
if (term->first.flags & EcsIsEntity && term->first.id != 0) { |
|
if (ecs_has_id(world, term->first.id, EcsDontInherit)) { |
|
def_src_mask = EcsSelf; |
|
} |
|
} |
|
|
|
if (term->oper == EcsNot) { |
|
ecs_strbuf_appendlit(buf, "!"); |
|
} else if (term->oper == EcsOptional) { |
|
ecs_strbuf_appendlit(buf, "?"); |
|
} |
|
|
|
if (!subj_set) { |
|
flecs_filter_str_add_id(world, buf, &term->first, false, |
|
def_first_mask); |
|
if (!obj_set) { |
|
ecs_strbuf_appendlit(buf, "()"); |
|
} else { |
|
ecs_strbuf_appendlit(buf, "(0,"); |
|
flecs_filter_str_add_id(world, buf, &term->second, false, |
|
def_second_mask); |
|
ecs_strbuf_appendlit(buf, ")"); |
|
} |
|
} else if (ecs_term_match_this(term) && |
|
(src->flags & EcsTraverseFlags) == def_src_mask) |
|
{ |
|
if (pred_set) { |
|
if (obj_set) { |
|
ecs_strbuf_appendlit(buf, "("); |
|
} |
|
flecs_filter_str_add_id(world, buf, &term->first, false, def_first_mask); |
|
if (obj_set) { |
|
ecs_strbuf_appendlit(buf, ","); |
|
flecs_filter_str_add_id( |
|
world, buf, &term->second, false, def_second_mask); |
|
ecs_strbuf_appendlit(buf, ")"); |
|
} |
|
} else if (term->id) { |
|
char *str = ecs_id_str(world, term->id); |
|
ecs_strbuf_appendstr(buf, str); |
|
ecs_os_free(str); |
|
} |
|
} else { |
|
if (term->id_flags && !ECS_HAS_ID_FLAG(term->id_flags, PAIR)) { |
|
ecs_strbuf_appendstr(buf, ecs_id_flag_str(term->id_flags)); |
|
ecs_strbuf_appendch(buf, '|'); |
|
} |
|
|
|
flecs_filter_str_add_id(world, buf, &term->first, false, def_first_mask); |
|
ecs_strbuf_appendlit(buf, "("); |
|
if (term->src.flags & EcsIsEntity && term->src.id == term->first.id) { |
|
ecs_strbuf_appendlit(buf, "$"); |
|
} else { |
|
flecs_filter_str_add_id(world, buf, &term->src, true, def_src_mask); |
|
} |
|
if (obj_set) { |
|
ecs_strbuf_appendlit(buf, ","); |
|
flecs_filter_str_add_id(world, buf, &term->second, false, def_second_mask); |
|
} |
|
ecs_strbuf_appendlit(buf, ")"); |
|
} |
|
} |
|
|
|
char* ecs_term_str( |
|
const ecs_world_t *world, |
|
const ecs_term_t *term) |
|
{ |
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
flecs_term_str_w_strbuf(world, term, &buf, 0); |
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
static |
|
char* flecs_filter_str( |
|
const ecs_world_t *world, |
|
const ecs_filter_t *filter, |
|
const ecs_filter_finalize_ctx_t *ctx, |
|
int32_t *term_start_out) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
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]; |
|
|
|
if (term_start_out && ctx) { |
|
if (ctx->term_index == i) { |
|
term_start_out[0] = ecs_strbuf_written(&buf); |
|
if (i) { |
|
term_start_out[0] += 2; /* whitespace + , */ |
|
} |
|
} |
|
} |
|
|
|
flecs_term_str_w_strbuf(world, term, &buf, i); |
|
|
|
if (i != (count - 1)) { |
|
if (term->oper == EcsOr) { |
|
ecs_strbuf_appendlit(&buf, " || "); |
|
} else { |
|
if (term->first.id != EcsScopeOpen) { |
|
if (term[1].first.id != EcsScopeClose) { |
|
ecs_strbuf_appendlit(&buf, ", "); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return ecs_strbuf_get(&buf); |
|
error: |
|
return NULL; |
|
} |
|
|
|
char* ecs_filter_str( |
|
const ecs_world_t *world, |
|
const ecs_filter_t *filter) |
|
{ |
|
return flecs_filter_str(world, filter, NULL, NULL); |
|
} |
|
|
|
int32_t ecs_filter_find_this_var( |
|
const ecs_filter_t *filter) |
|
{ |
|
ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
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; |
|
} |
|
|
|
error: |
|
return -1; |
|
} |
|
|
|
/* Check if the id is a pair that has Any as first or second element. Any |
|
* pairs behave just like Wildcard pairs and reuses the same data structures, |
|
* with as only difference that the number of results returned for an Any pair |
|
* is never more than one. This function is used to tell the difference. */ |
|
static |
|
bool is_any_pair( |
|
ecs_id_t id) |
|
{ |
|
if (!ECS_HAS_ID_FLAG(id, PAIR)) { |
|
return false; |
|
} |
|
|
|
if (ECS_PAIR_FIRST(id) == EcsAny) { |
|
return true; |
|
} |
|
if (ECS_PAIR_SECOND(id) == EcsAny) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_n_term_match_table( |
|
ecs_world_t *world, |
|
const ecs_term_t *term, |
|
const ecs_table_t *table, |
|
ecs_entity_t type_id, |
|
ecs_oper_kind_t oper, |
|
ecs_id_t *id_out, |
|
int32_t *column_out, |
|
ecs_entity_t *subject_out, |
|
int32_t *match_index_out, |
|
bool first, |
|
ecs_flags32_t iter_flags) |
|
{ |
|
(void)column_out; |
|
|
|
const ecs_type_t *type = ecs_get_type(world, type_id); |
|
ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_id_t *ids = type->array; |
|
int32_t i, count = type->count; |
|
ecs_term_t temp = *term; |
|
temp.oper = EcsAnd; |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_id_t id = ids[i]; |
|
if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { |
|
continue; |
|
} |
|
bool result; |
|
if (ECS_HAS_ID_FLAG(id, AND)) { |
|
ecs_oper_kind_t id_oper = EcsAndFrom; |
|
result = flecs_n_term_match_table(world, term, table, |
|
id & ECS_COMPONENT_MASK, id_oper, id_out, column_out, |
|
subject_out, match_index_out, first, iter_flags); |
|
} else { |
|
temp.id = id; |
|
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; |
|
} else |
|
if (result && oper == EcsOrFrom) { |
|
return true; |
|
} |
|
} |
|
|
|
if (oper == EcsAndFrom) { |
|
if (id_out) { |
|
id_out[0] = type_id; |
|
} |
|
return true; |
|
} else |
|
if (oper == EcsOrFrom) { |
|
return false; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
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_index_out, |
|
bool first, |
|
ecs_flags32_t iter_flags) |
|
{ |
|
const ecs_term_id_t *src = &term->src; |
|
ecs_oper_kind_t oper = term->oper; |
|
const ecs_table_t *match_table = table; |
|
ecs_id_t id = term->id; |
|
|
|
ecs_entity_t src_id = src->id; |
|
if (ecs_term_match_0(term)) { |
|
if (id_out) { |
|
id_out[0] = id; /* If no entity is matched, just set id */ |
|
} |
|
return true; |
|
} |
|
|
|
if (oper == EcsAndFrom || oper == EcsOrFrom) { |
|
return flecs_n_term_match_table(world, term, table, term->id, |
|
term->oper, id_out, column_out, subject_out, match_index_out, first, |
|
iter_flags); |
|
} |
|
|
|
/* If source is not This, search in table of source */ |
|
if (!ecs_term_match_this(term)) { |
|
if (iter_flags & EcsIterEntityOptional) { |
|
/* Treat entity terms as optional */ |
|
oper = EcsOptional; |
|
} |
|
|
|
if (ecs_is_alive(world, src_id)) { |
|
match_table = ecs_get_table(world, src_id); |
|
} else { |
|
match_table = NULL; |
|
} |
|
|
|
if (match_table) { |
|
} else if (oper != EcsOptional) { |
|
return false; |
|
} |
|
} else { |
|
/* If filter contains This terms, a table must be provided */ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (!match_table) { |
|
return false; |
|
} |
|
|
|
ecs_entity_t source = 0; |
|
|
|
/* If first = false, we're searching from an offset. This supports returning |
|
* multiple results when using wildcard filters. */ |
|
int32_t column = 0; |
|
if (!first && column_out && column_out[0] != 0) { |
|
column = column_out[0]; |
|
if (column < 0) { |
|
/* In case column is not from This, flip sign */ |
|
column = -column; |
|
} |
|
|
|
/* Remove base 1 offset */ |
|
column --; |
|
} |
|
|
|
/* Find location, source and id of match in table type */ |
|
ecs_table_record_t *tr = 0; |
|
bool is_any = is_any_pair(id); |
|
|
|
ecs_id_record_t *idr = term->idr; |
|
if (world->flags & EcsWorldQuit) { |
|
/* During world cleanup the keep_alive assert for id records is no |
|
* longer enforced */ |
|
idr = NULL; |
|
} |
|
column = flecs_search_relation_w_idr(world, match_table, |
|
column, id, src->trav, src->flags, &source, id_out, &tr, idr); |
|
|
|
if (tr && match_index_out) { |
|
if (!is_any) { |
|
match_index_out[0] = tr->count; |
|
} else { |
|
match_index_out[0] = 1; |
|
} |
|
} |
|
|
|
bool result = column != -1; |
|
|
|
if (oper == EcsNot) { |
|
if (match_index_out) { |
|
match_index_out[0] = 1; |
|
} |
|
result = !result; |
|
} |
|
|
|
if (oper == EcsOptional) { |
|
result = true; |
|
} |
|
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if ((column == -1) && (src->flags & EcsUp) && (table->flags & EcsTableHasTarget)) { |
|
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_t rel = ECS_PAIR_SECOND(table->type.array[table->_->ft_offset]); |
|
if (rel == (uint32_t)src->trav) { |
|
result = true; |
|
} |
|
} |
|
|
|
if (!result) { |
|
if (iter_flags & EcsFilterPopulate) { |
|
column = 0; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
if (!ecs_term_match_this(term)) { |
|
if (!source) { |
|
source = src_id; |
|
} |
|
} |
|
|
|
if (id_out && column < 0) { |
|
id_out[0] = id; |
|
} |
|
|
|
if (column_out) { |
|
if (column >= 0) { |
|
column ++; |
|
if (source != 0) { |
|
column *= -1; |
|
} |
|
column_out[0] = column; |
|
} else { |
|
column_out[0] = 0; |
|
} |
|
} |
|
|
|
if (subject_out) { |
|
subject_out[0] = source; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
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 *sources, |
|
int32_t *match_indices, |
|
int32_t *matches_left, |
|
bool first, |
|
int32_t skip_term, |
|
ecs_flags32_t iter_flags) |
|
{ |
|
ecs_term_t *terms = filter->terms; |
|
int32_t i, count = filter->term_count; |
|
int32_t match_count = 1; |
|
bool result = true; |
|
|
|
if (matches_left) { |
|
match_count = *matches_left; |
|
} |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
ecs_oper_kind_t oper = term->oper; |
|
if (i == skip_term) { |
|
if (oper != EcsAndFrom && oper != EcsOrFrom && oper != EcsNotFrom) { |
|
continue; |
|
} |
|
} |
|
|
|
ecs_term_id_t *src = &term->src; |
|
const ecs_table_t *match_table = table; |
|
int32_t t_i = term->field_index; |
|
|
|
ecs_entity_t src_id = src->id; |
|
if (!src_id) { |
|
if (ids) { |
|
ids[t_i] = term->id; |
|
} |
|
continue; |
|
} |
|
|
|
if (!ecs_term_match_this(term)) { |
|
if (ecs_is_alive(world, src_id)) { |
|
match_table = ecs_get_table(world, src_id); |
|
} else { |
|
match_table = NULL; |
|
} |
|
} else { |
|
if (ECS_BIT_IS_SET(iter_flags, EcsIterIgnoreThis)) { |
|
continue; |
|
} |
|
|
|
/* If filter contains This terms, table must be provided */ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
int32_t match_index = 0; |
|
if (!i || term[-1].oper != EcsOr) { |
|
result = false; |
|
} else { |
|
if (result) { |
|
continue; /* Already found matching OR term */ |
|
} |
|
} |
|
|
|
bool term_result = flecs_term_match_table(world, term, match_table, |
|
ids ? &ids[t_i] : NULL, |
|
columns ? &columns[t_i] : NULL, |
|
sources ? &sources[t_i] : NULL, |
|
&match_index, |
|
first, |
|
iter_flags); |
|
|
|
if (i && term[-1].oper == EcsOr) { |
|
result |= term_result; |
|
} else { |
|
result = term_result; |
|
} |
|
|
|
if (oper != EcsOr && !result) { |
|
return false; |
|
} |
|
|
|
if (first && match_index) { |
|
match_count *= match_index; |
|
} |
|
if (match_indices) { |
|
match_indices[t_i] = match_index; |
|
} |
|
} |
|
|
|
if (matches_left) { |
|
*matches_left = match_count; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
void term_iter_init_no_data( |
|
ecs_term_iter_t *iter) |
|
{ |
|
iter->term = (ecs_term_t){ .field_index = -1 }; |
|
iter->self_index = NULL; |
|
iter->index = 0; |
|
} |
|
|
|
static |
|
void term_iter_init_w_idr( |
|
const ecs_term_t *term, |
|
ecs_term_iter_t *iter, |
|
ecs_id_record_t *idr, |
|
bool empty_tables) |
|
{ |
|
if (idr) { |
|
if (empty_tables) { |
|
flecs_table_cache_all_iter(&idr->cache, &iter->it); |
|
} else { |
|
flecs_table_cache_iter(&idr->cache, &iter->it); |
|
} |
|
} else { |
|
term_iter_init_no_data(iter); |
|
} |
|
|
|
iter->index = 0; |
|
iter->empty_tables = empty_tables; |
|
iter->size = 0; |
|
if (term && term->idr && term->idr->type_info) { |
|
iter->size = term->idr->type_info->size; |
|
} |
|
} |
|
|
|
static |
|
void term_iter_init_wildcard( |
|
const ecs_world_t *world, |
|
ecs_term_iter_t *iter, |
|
bool empty_tables) |
|
{ |
|
iter->term = (ecs_term_t){ .field_index = -1 }; |
|
iter->self_index = flecs_id_record_get(world, EcsAny); |
|
ecs_id_record_t *idr = iter->cur = iter->self_index; |
|
term_iter_init_w_idr(NULL, iter, idr, empty_tables); |
|
} |
|
|
|
static |
|
void term_iter_init( |
|
const ecs_world_t *world, |
|
ecs_term_t *term, |
|
ecs_term_iter_t *iter, |
|
bool empty_tables) |
|
{ |
|
const ecs_term_id_t *src = &term->src; |
|
|
|
iter->term = *term; |
|
|
|
if (src->flags & EcsSelf) { |
|
iter->self_index = term->idr; |
|
if (!iter->self_index) { |
|
iter->self_index = flecs_query_id_record_get(world, term->id); |
|
} |
|
} |
|
|
|
if (src->flags & EcsUp) { |
|
iter->set_index = flecs_id_record_get(world, |
|
ecs_pair(src->trav, EcsWildcard)); |
|
} |
|
|
|
ecs_id_record_t *idr; |
|
if (iter->self_index) { |
|
idr = iter->cur = iter->self_index; |
|
} else { |
|
idr = iter->cur = iter->set_index; |
|
} |
|
|
|
term_iter_init_w_idr(term, iter, idr, empty_tables); |
|
} |
|
|
|
ecs_iter_t ecs_term_iter( |
|
const ecs_world_t *stage, |
|
ecs_term_t *term) |
|
{ |
|
ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const ecs_world_t *world = ecs_get_world(stage); |
|
|
|
flecs_process_pending_tables(world); |
|
|
|
if (ecs_term_finalize(world, term)) { |
|
ecs_throw(ECS_INVALID_PARAMETER, NULL); |
|
} |
|
|
|
ecs_iter_t it = { |
|
.real_world = ECS_CONST_CAST(ecs_world_t*, world), |
|
.world = ECS_CONST_CAST(ecs_world_t*, stage), |
|
.field_count = 1, |
|
.next = ecs_term_next |
|
}; |
|
|
|
/* Term iter populates the iterator with arrays from its own cache, ensure |
|
* they don't get overwritten by flecs_iter_validate. |
|
* |
|
* Note: the reason the term iterator doesn't use the iterator cache itself |
|
* (which could easily accommodate a single term) is that the filter iterator |
|
* is built on top of the term iterator. The private cache of the term |
|
* iterator keeps the filter iterator code simple, as it doesn't need to |
|
* worry about the term iter overwriting the iterator fields. */ |
|
flecs_iter_init(stage, &it, 0); |
|
term_iter_init(world, term, &it.priv.iter.term, false); |
|
ECS_BIT_COND(it.flags, EcsIterNoData, it.priv.iter.term.size == 0); |
|
|
|
return it; |
|
error: |
|
return (ecs_iter_t){ 0 }; |
|
} |
|
|
|
ecs_iter_t ecs_term_chain_iter( |
|
const ecs_iter_t *chain_it, |
|
ecs_term_t *term) |
|
{ |
|
ecs_check(chain_it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_world_t *world = chain_it->real_world; |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (ecs_term_finalize(world, term)) { |
|
ecs_throw(ECS_INVALID_PARAMETER, NULL); |
|
} |
|
|
|
ecs_iter_t it = { |
|
.real_world = world, |
|
.world = chain_it->world, |
|
.terms = term, |
|
.field_count = 1, |
|
.chain_it = ECS_CONST_CAST(ecs_iter_t*, chain_it), |
|
.next = ecs_term_next |
|
}; |
|
|
|
flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); |
|
|
|
term_iter_init(world, term, &it.priv.iter.term, false); |
|
|
|
return it; |
|
error: |
|
return (ecs_iter_t){ 0 }; |
|
} |
|
|
|
ecs_iter_t ecs_children( |
|
const ecs_world_t *world, |
|
ecs_entity_t parent) |
|
{ |
|
return ecs_term_iter(world, &(ecs_term_t){ .id = ecs_childof(parent) }); |
|
} |
|
|
|
bool ecs_children_next( |
|
ecs_iter_t *it) |
|
{ |
|
return ecs_term_next(it); |
|
} |
|
|
|
static |
|
const ecs_table_record_t *flecs_term_iter_next_table( |
|
ecs_term_iter_t *iter) |
|
{ |
|
ecs_id_record_t *idr = iter->cur; |
|
if (!idr) { |
|
return NULL; |
|
} |
|
|
|
return flecs_table_cache_next(&iter->it, ecs_table_record_t); |
|
} |
|
|
|
static |
|
bool flecs_term_iter_find_superset( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_term_t *term, |
|
ecs_entity_t *source, |
|
ecs_id_t *id, |
|
int32_t *column) |
|
{ |
|
ecs_term_id_t *src = &term->src; |
|
|
|
/* Test if following the relationship finds the id */ |
|
ecs_id_record_t *idr = term->idr; |
|
if (world->flags & EcsWorldQuit) { |
|
/* During world cleanup the keep_alive assert for id records is no |
|
* longer enforced */ |
|
idr = NULL; |
|
} |
|
int32_t index = flecs_search_relation_w_idr(world, table, 0, |
|
term->id, src->trav, src->flags, source, id, 0, idr); |
|
|
|
if (index == -1) { |
|
*source = 0; |
|
return false; |
|
} |
|
|
|
ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
*column = (index + 1) * -1; |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_term_iter_next( |
|
ecs_world_t *world, |
|
ecs_term_iter_t *iter, |
|
bool match_prefab, |
|
bool match_disabled) |
|
{ |
|
ecs_table_t *table = iter->table; |
|
ecs_entity_t source = 0; |
|
const ecs_table_record_t *tr; |
|
ecs_term_t *term = &iter->term; |
|
|
|
do { |
|
if (table) { |
|
iter->cur_match ++; |
|
if (iter->cur_match >= iter->match_count) { |
|
table = NULL; |
|
} else { |
|
iter->last_column = ecs_search_offset( |
|
world, table, iter->last_column + 1, term->id, 0); |
|
iter->column = iter->last_column + 1; |
|
if (iter->last_column >= 0) { |
|
iter->id = table->type.array[iter->last_column]; |
|
} |
|
} |
|
} |
|
|
|
if (!table) { |
|
if (!(tr = flecs_term_iter_next_table(iter))) { |
|
if (iter->cur != iter->set_index && iter->set_index != NULL) { |
|
if (iter->observed_table_count != 0) { |
|
iter->cur = iter->set_index; |
|
if (iter->empty_tables) { |
|
flecs_table_cache_all_iter( |
|
&iter->set_index->cache, &iter->it); |
|
} else { |
|
flecs_table_cache_iter( |
|
&iter->set_index->cache, &iter->it); |
|
} |
|
iter->index = 0; |
|
tr = flecs_term_iter_next_table(iter); |
|
} |
|
} |
|
|
|
if (!tr) { |
|
return false; |
|
} |
|
} |
|
|
|
table = tr->hdr.table; |
|
if (table->_->traversable_count) { |
|
iter->observed_table_count ++; |
|
} |
|
|
|
if (!match_prefab && (table->flags & EcsTableIsPrefab)) { |
|
continue; |
|
} |
|
|
|
if (!match_disabled && (table->flags & EcsTableIsDisabled)) { |
|
continue; |
|
} |
|
|
|
iter->table = table; |
|
iter->match_count = tr->count; |
|
if (is_any_pair(term->id)) { |
|
iter->match_count = 1; |
|
} |
|
|
|
iter->cur_match = 0; |
|
iter->last_column = tr->index; |
|
iter->column = tr->index + 1; |
|
iter->id = flecs_to_public_id(table->type.array[tr->index]); |
|
} |
|
|
|
if (iter->cur == iter->set_index) { |
|
if (iter->self_index) { |
|
if (flecs_id_record_get_table(iter->self_index, table) != NULL) { |
|
/* If the table has the id itself and this term matched Self |
|
* we already matched it */ |
|
continue; |
|
} |
|
} |
|
|
|
if (!flecs_term_iter_find_superset( |
|
world, table, term, &source, &iter->id, &iter->column)) |
|
{ |
|
continue; |
|
} |
|
|
|
/* The tr->count field refers to the number of relationship instances, |
|
* not to the number of matches. Superset terms can only yield a |
|
* single match. */ |
|
iter->match_count = 1; |
|
} |
|
|
|
break; |
|
} while (true); |
|
|
|
iter->subject = source; |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_term_iter_set_table( |
|
ecs_world_t *world, |
|
ecs_term_iter_t *iter, |
|
ecs_table_t *table) |
|
{ |
|
const ecs_table_record_t *tr = NULL; |
|
const ecs_id_record_t *idr = iter->self_index; |
|
if (idr) { |
|
tr = ecs_table_cache_get(&idr->cache, table); |
|
if (tr) { |
|
iter->match_count = tr->count; |
|
iter->last_column = tr->index; |
|
iter->column = tr->index + 1; |
|
iter->id = flecs_to_public_id(table->type.array[tr->index]); |
|
} |
|
} |
|
|
|
if (!tr) { |
|
idr = iter->set_index; |
|
if (idr) { |
|
tr = ecs_table_cache_get(&idr->cache, table); |
|
if (!flecs_term_iter_find_superset(world, table, &iter->term, |
|
&iter->subject, &iter->id, &iter->column)) |
|
{ |
|
return false; |
|
} |
|
iter->match_count = 1; |
|
} |
|
} |
|
|
|
if (!tr) { |
|
return false; |
|
} |
|
|
|
/* Populate fields as usual */ |
|
iter->table = table; |
|
iter->cur_match = 0; |
|
|
|
return true; |
|
} |
|
|
|
bool ecs_term_next( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_term_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
flecs_iter_validate(it); |
|
|
|
ecs_term_iter_t *iter = &it->priv.iter.term; |
|
ecs_term_t *term = &iter->term; |
|
ecs_world_t *world = it->real_world; |
|
ecs_table_t *table; |
|
|
|
it->ids = &iter->id; |
|
it->sources = &iter->subject; |
|
it->columns = &iter->column; |
|
it->terms = &iter->term; |
|
it->sizes = &iter->size; |
|
it->ptrs = &iter->ptr; |
|
|
|
ecs_iter_t *chain_it = it->chain_it; |
|
if (chain_it) { |
|
ecs_iter_next_action_t next = chain_it->next; |
|
bool match; |
|
|
|
do { |
|
if (!next(chain_it)) { |
|
goto done; |
|
} |
|
|
|
table = chain_it->table; |
|
match = flecs_term_match_table(world, term, table, |
|
it->ids, it->columns, it->sources, it->match_indices, true, |
|
it->flags); |
|
} while (!match); |
|
goto yield; |
|
|
|
} else { |
|
if (!flecs_term_iter_next(world, iter, |
|
(term->flags & EcsTermMatchPrefab) != 0, |
|
(term->flags & EcsTermMatchDisabled) != 0)) |
|
{ |
|
goto done; |
|
} |
|
|
|
table = iter->table; |
|
|
|
/* Source must either be 0 (EcsThis) or nonzero in case of substitution */ |
|
ecs_assert(iter->subject || iter->cur != iter->set_index, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(iter->table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
yield: |
|
flecs_iter_populate_data(world, it, table, 0, ecs_table_count(table), |
|
it->ptrs); |
|
ECS_BIT_SET(it->flags, EcsIterIsValid); |
|
return true; |
|
done: |
|
ecs_iter_fini(it); |
|
error: |
|
return false; |
|
} |
|
|
|
static |
|
void flecs_init_filter_iter( |
|
ecs_iter_t *it, |
|
const ecs_filter_t *filter) |
|
{ |
|
ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); |
|
it->priv.iter.filter.filter = filter; |
|
it->field_count = filter->field_count; |
|
} |
|
|
|
int32_t ecs_filter_pivot_term( |
|
const ecs_world_t *world, |
|
const ecs_filter_t *filter) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_term_t *terms = filter->terms; |
|
int32_t i, term_count = filter->term_count; |
|
int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; |
|
|
|
for (i = 0; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
ecs_id_t id = term->id; |
|
|
|
if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { |
|
continue; |
|
} |
|
|
|
if (!ecs_term_match_this(term)) { |
|
continue; |
|
} |
|
|
|
ecs_id_record_t *idr = flecs_query_id_record_get(world, id); |
|
if (!idr) { |
|
/* If one of the terms does not match with any data, iterator |
|
* should not return anything */ |
|
return -2; /* -2 indicates filter doesn't match anything */ |
|
} |
|
|
|
int32_t table_count = flecs_table_cache_count(&idr->cache); |
|
if (min_count == -1 || table_count < min_count) { |
|
min_count = table_count; |
|
pivot_term = i; |
|
if ((term->src.flags & EcsTraverseFlags) == EcsSelf) { |
|
self_pivot_term = i; |
|
} |
|
} |
|
} |
|
|
|
if (self_pivot_term != -1) { |
|
pivot_term = self_pivot_term; |
|
} |
|
|
|
return pivot_term; |
|
error: |
|
return -2; |
|
} |
|
|
|
void flecs_filter_apply_iter_flags( |
|
ecs_iter_t *it, |
|
const ecs_filter_t *filter) |
|
{ |
|
ECS_BIT_COND(it->flags, EcsIterIsInstanced, |
|
ECS_BIT_IS_SET(filter->flags, EcsFilterIsInstanced)); |
|
ECS_BIT_COND(it->flags, EcsIterNoData, |
|
ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); |
|
ECS_BIT_COND(it->flags, EcsIterHasCondSet, |
|
ECS_BIT_IS_SET(filter->flags, EcsFilterHasCondSet)); |
|
} |
|
|
|
ecs_iter_t flecs_filter_iter_w_flags( |
|
const ecs_world_t *stage, |
|
const ecs_filter_t *filter, |
|
ecs_flags32_t flags) |
|
{ |
|
ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!(filter->flags & (EcsFilterHasPred|EcsFilterHasScopes)), |
|
ECS_UNSUPPORTED, NULL); |
|
const ecs_world_t *world = ecs_get_world(stage); |
|
|
|
if (!(flags & EcsIterMatchVar)) { |
|
flecs_process_pending_tables(world); |
|
} |
|
|
|
ecs_iter_t it = { |
|
.real_world = ECS_CONST_CAST(ecs_world_t*, world), |
|
.world = ECS_CONST_CAST(ecs_world_t*, stage), |
|
.query = filter, |
|
.terms = filter ? filter->terms : NULL, |
|
.next = ecs_filter_next, |
|
.flags = flags, |
|
.sizes = filter->sizes, |
|
.system = filter->entity |
|
}; |
|
|
|
ecs_filter_iter_t *iter = &it.priv.iter.filter; |
|
iter->pivot_term = -1; |
|
|
|
flecs_init_filter_iter(&it, filter); |
|
flecs_filter_apply_iter_flags(&it, filter); |
|
|
|
/* Find term that represents smallest superset */ |
|
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); |
|
|
|
pivot_term = ecs_filter_pivot_term(world, filter); |
|
iter->kind = EcsIterEvalTables; |
|
iter->pivot_term = pivot_term; |
|
|
|
if (pivot_term == -2) { |
|
/* One or more terms have no matching results */ |
|
term_iter_init_no_data(&iter->term_iter); |
|
} else if (pivot_term == -1) { |
|
/* No terms meet the criteria to be a pivot term, evaluate filter |
|
* against all tables */ |
|
term_iter_init_wildcard(world, &iter->term_iter, |
|
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, |
|
ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); |
|
} |
|
} else { |
|
if (!ECS_BIT_IS_SET(filter->flags, EcsFilterMatchAnything)) { |
|
term_iter_init_no_data(&iter->term_iter); |
|
} else { |
|
iter->kind = EcsIterEvalNone; |
|
} |
|
} |
|
|
|
ECS_BIT_COND(it.flags, EcsIterNoData, |
|
ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); |
|
|
|
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; |
|
|
|
/* Set variable name array */ |
|
it.variable_names = ECS_CONST_CAST(char**, filter->variable_names); |
|
} |
|
|
|
flecs_iter_init(stage, &it, flecs_iter_cache_all); |
|
|
|
return it; |
|
error: |
|
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) |
|
{ |
|
ecs_iter_t it = { |
|
.query = filter, |
|
.terms = filter->terms, |
|
.field_count = filter->field_count, |
|
.world = chain_it->world, |
|
.real_world = chain_it->real_world, |
|
.chain_it = ECS_CONST_CAST(ecs_iter_t*, chain_it), |
|
.next = ecs_filter_next, |
|
.sizes = filter->sizes, |
|
.system = filter->entity |
|
}; |
|
|
|
flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); |
|
ecs_filter_iter_t *iter = &it.priv.iter.filter; |
|
flecs_init_filter_iter(&it, filter); |
|
|
|
iter->kind = EcsIterEvalChain; |
|
|
|
return it; |
|
} |
|
|
|
bool ecs_filter_next( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (flecs_iter_next_row(it)) { |
|
return true; |
|
} |
|
|
|
return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_filter_next_instanced( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->chain_it != it, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_filter_iter_t *iter = &it->priv.iter.filter; |
|
const ecs_filter_t *filter = iter->filter; |
|
ecs_world_t *world = it->real_world; |
|
ecs_table_t *table = NULL; |
|
bool match; |
|
|
|
flecs_iter_validate(it); |
|
|
|
ecs_iter_t *chain_it = it->chain_it; |
|
ecs_iter_kind_t kind = iter->kind; |
|
|
|
if (chain_it) { |
|
ecs_assert(kind == EcsIterEvalChain, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_iter_next_action_t next = chain_it->next; |
|
do { |
|
if (!next(chain_it)) { |
|
goto done; |
|
} |
|
|
|
table = chain_it->table; |
|
match = flecs_filter_match_table(world, filter, table, |
|
it->ids, it->columns, it->sources, it->match_indices, NULL, |
|
true, -1, it->flags); |
|
} while (!match); |
|
|
|
goto yield; |
|
} else if (kind == EcsIterEvalTables || kind == EcsIterEvalCondition) { |
|
ecs_term_iter_t *term_iter = &iter->term_iter; |
|
ecs_term_t *term = &term_iter->term; |
|
int32_t pivot_term = iter->pivot_term; |
|
bool first; |
|
|
|
/* Check if the This variable has been set on the iterator. If set, |
|
* the filter should only be applied to the variable value */ |
|
ecs_var_t *this_var = NULL; |
|
ecs_table_t *this_table = NULL; |
|
if (it->variable_count) { |
|
if (ecs_iter_var_is_constrained(it, 0)) { |
|
this_var = it->variables; |
|
this_table = this_var->range.table; |
|
|
|
/* If variable is constrained, make sure it's a value that's |
|
* pointing to a table, as a filter can't iterate single |
|
* entities (yet) */ |
|
ecs_assert(this_table != NULL, ECS_INVALID_OPERATION, NULL); |
|
|
|
/* Can't set variable for filter that does not iterate tables */ |
|
ecs_assert(kind == EcsIterEvalTables, |
|
ECS_INVALID_OPERATION, NULL); |
|
} |
|
} |
|
|
|
do { |
|
/* If there are no matches left for the previous table, this is the |
|
* first match of the next table. */ |
|
first = iter->matches_left == 0; |
|
|
|
if (first) { |
|
if (kind != EcsIterEvalCondition) { |
|
/* Check if this variable was constrained */ |
|
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 */ |
|
if (it->table == this_table) { |
|
goto done; |
|
} |
|
|
|
/* If table doesn't match term iterator, it doesn't |
|
* match filter. */ |
|
if (!flecs_term_iter_set_table( |
|
world, term_iter, this_table)) |
|
{ |
|
goto done; |
|
} |
|
|
|
it->offset = this_var->range.offset; |
|
it->count = this_var->range.count; |
|
|
|
/* But if it does, forward it to filter matching */ |
|
ecs_assert(term_iter->table == this_table, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* If This variable is not constrained, iterate as usual */ |
|
} else { |
|
it->offset = 0; |
|
it->count = 0; |
|
|
|
/* Find new match, starting with the leading term */ |
|
if (!flecs_term_iter_next(world, term_iter, |
|
ECS_BIT_IS_SET(filter->flags, |
|
EcsFilterMatchPrefab), |
|
ECS_BIT_IS_SET(filter->flags, |
|
EcsFilterMatchDisabled))) |
|
{ |
|
goto done; |
|
} |
|
} |
|
|
|
ecs_assert(term_iter->match_count != 0, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (pivot_term == -1) { |
|
/* Without a pivot term, we're iterating all tables with |
|
* a wildcard, so the match count is meaningless. */ |
|
term_iter->match_count = 1; |
|
} else { |
|
it->match_indices[pivot_term] = term_iter->match_count; |
|
} |
|
|
|
iter->matches_left = term_iter->match_count; |
|
|
|
/* Filter iterator takes control over iterating all the |
|
* permutations that match the wildcard. */ |
|
term_iter->match_count = 1; |
|
|
|
table = term_iter->table; |
|
|
|
if (pivot_term != -1) { |
|
int32_t index = term->field_index; |
|
it->ids[index] = term_iter->id; |
|
it->sources[index] = term_iter->subject; |
|
it->columns[index] = term_iter->column; |
|
} |
|
} else { |
|
/* Progress iterator to next match for table, if any */ |
|
table = it->table; |
|
if (term_iter->index == 0) { |
|
iter->matches_left = 1; |
|
term_iter->index = 1; /* prevents looping again */ |
|
} else { |
|
goto done; |
|
} |
|
} |
|
|
|
/* Match the remainder of the terms */ |
|
match = flecs_filter_match_table(world, filter, table, |
|
it->ids, it->columns, it->sources, |
|
it->match_indices, &iter->matches_left, first, |
|
pivot_term, it->flags); |
|
if (!match) { |
|
it->table = table; |
|
iter->matches_left = 0; |
|
continue; |
|
} |
|
|
|
/* Table got matched, set This variable */ |
|
if (table) { |
|
ecs_assert(it->variable_count == 1, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); |
|
it->variables[0].range.table = table; |
|
} |
|
|
|
ecs_assert(iter->matches_left != 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
/* If this is not the first result for the table, and the table |
|
* is matched more than once, iterate remaining matches */ |
|
if (!first && (iter->matches_left > 0)) { |
|
table = it->table; |
|
|
|
/* Find first term that still has matches left */ |
|
int32_t i, j, count = it->field_count; |
|
for (i = count - 1; i >= 0; i --) { |
|
int32_t mi = -- it->match_indices[i]; |
|
if (mi) { |
|
if (mi < 0) { |
|
continue; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
/* 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) { |
|
/* If this term was matched on a non-This entity, reconvert |
|
* the column back to a positive value */ |
|
column = -column; |
|
} |
|
|
|
it->columns[i] = column + 1; |
|
flecs_term_match_table(world, &filter->terms[i], table, |
|
&it->ids[i], &it->columns[i], &it->sources[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, |
|
&it->ids[j], &it->columns[j], &it->sources[j], |
|
&it->match_indices[j], true, it->flags); |
|
} |
|
} |
|
|
|
match = iter->matches_left != 0; |
|
iter->matches_left --; |
|
|
|
ecs_assert(iter->matches_left >= 0, ECS_INTERNAL_ERROR, NULL); |
|
} while (!match); |
|
|
|
goto yield; |
|
} |
|
|
|
done: |
|
error: |
|
ecs_iter_fini(it); |
|
return false; |
|
|
|
yield: |
|
if (!it->count && table) { |
|
it->count = ecs_table_count(table); |
|
} |
|
flecs_iter_populate_data(world, it, table, it->offset, it->count, it->ptrs); |
|
ECS_BIT_SET(it->flags, EcsIterIsValid); |
|
return true; |
|
} |
|
|
|
/** |
|
* @file iter.c |
|
* @brief Iterator API. |
|
* |
|
* The iterator API contains functions that apply to all iterators, such as |
|
* resource management, or fetching resources for a matched table. The API also |
|
* contains functions for generic iterators, which make it possible to iterate |
|
* an iterator without needing to know what created the iterator. |
|
*/ |
|
|
|
#include <stddef.h> |
|
|
|
/* Utility macros to enforce consistency when initializing iterator fields */ |
|
|
|
/* If term count is smaller than cache size, initialize with inline array, |
|
* otherwise allocate. */ |
|
#define INIT_CACHE(it, stack, fields, f, T, count)\ |
|
if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ |
|
it->f = flecs_stack_calloc_n(stack, T, count);\ |
|
it->priv.cache.used |= flecs_iter_cache_##f;\ |
|
} |
|
|
|
/* If array is allocated, free it when finalizing the iterator */ |
|
#define FINI_CACHE(it, f, T, count)\ |
|
if (it->priv.cache.used & flecs_iter_cache_##f) {\ |
|
flecs_stack_free_n((void*)it->f, T, count);\ |
|
} |
|
|
|
void* flecs_iter_calloc( |
|
ecs_iter_t *it, |
|
ecs_size_t size, |
|
ecs_size_t align) |
|
{ |
|
ecs_world_t *world = it->world; |
|
ecs_stage_t *stage = flecs_stage_from_world((ecs_world_t**)&world); |
|
ecs_stack_t *stack = &stage->allocators.iter_stack; |
|
return flecs_stack_calloc(stack, size, align); |
|
} |
|
|
|
void flecs_iter_free( |
|
void *ptr, |
|
ecs_size_t size) |
|
{ |
|
flecs_stack_free(ptr, size); |
|
} |
|
|
|
void flecs_iter_init( |
|
const ecs_world_t *world, |
|
ecs_iter_t *it, |
|
ecs_flags8_t fields) |
|
{ |
|
ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world( |
|
ECS_CONST_CAST(ecs_world_t**, &world)); |
|
ecs_stack_t *stack = &stage->allocators.iter_stack; |
|
|
|
it->priv.cache.used = 0; |
|
it->priv.cache.allocated = 0; |
|
it->priv.cache.stack_cursor = flecs_stack_get_cursor(stack); |
|
it->priv.entity_iter = flecs_stack_calloc_t( |
|
stack, ecs_entity_filter_iter_t); |
|
|
|
INIT_CACHE(it, stack, fields, ids, ecs_id_t, it->field_count); |
|
INIT_CACHE(it, stack, fields, sources, ecs_entity_t, it->field_count); |
|
INIT_CACHE(it, stack, fields, match_indices, int32_t, it->field_count); |
|
INIT_CACHE(it, stack, fields, columns, int32_t, it->field_count); |
|
INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_count); |
|
INIT_CACHE(it, stack, fields, ptrs, void*, it->field_count); |
|
} |
|
|
|
void flecs_iter_validate( |
|
ecs_iter_t *it) |
|
{ |
|
ECS_BIT_SET(it->flags, EcsIterIsValid); |
|
|
|
/* Make sure multithreaded iterator isn't created for real world */ |
|
ecs_world_t *world = it->real_world; |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(!(world->flags & EcsWorldMultiThreaded) || it->world != it->real_world, |
|
ECS_INVALID_PARAMETER, |
|
"create iterator for stage when world is in multithreaded mode"); |
|
(void)world; |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_iter_fini( |
|
ecs_iter_t *it) |
|
{ |
|
ECS_BIT_CLEAR(it->flags, EcsIterIsValid); |
|
|
|
if (it->fini) { |
|
it->fini(it); |
|
} |
|
|
|
ecs_world_t *world = it->world; |
|
if (!world) { |
|
return; |
|
} |
|
|
|
FINI_CACHE(it, ids, ecs_id_t, it->field_count); |
|
FINI_CACHE(it, sources, ecs_entity_t, it->field_count); |
|
FINI_CACHE(it, match_indices, int32_t, it->field_count); |
|
FINI_CACHE(it, columns, int32_t, it->field_count); |
|
FINI_CACHE(it, variables, ecs_var_t, it->variable_count); |
|
FINI_CACHE(it, ptrs, void*, it->field_count); |
|
flecs_stack_free_t(it->priv.entity_iter, ecs_entity_filter_iter_t); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
flecs_stack_restore_cursor(&stage->allocators.iter_stack, |
|
it->priv.cache.stack_cursor); |
|
} |
|
|
|
static |
|
bool flecs_iter_populate_term_data( |
|
ecs_world_t *world, |
|
ecs_iter_t *it, |
|
int32_t t, |
|
int32_t column, |
|
void **ptr_out) |
|
{ |
|
bool is_shared = false; |
|
ecs_table_t *table; |
|
void *data; |
|
int32_t row, u_index; |
|
|
|
if (!column) { |
|
/* Term has no data. This includes terms that have Not operators. */ |
|
goto no_data; |
|
} |
|
|
|
/* Filter terms may match with data but don't return it */ |
|
if (it->terms[t].inout == EcsInOutNone) { |
|
goto no_data; |
|
} |
|
|
|
ecs_assert(it->sizes != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t size = it->sizes[t]; |
|
if (!size) { |
|
goto no_data; |
|
} |
|
|
|
if (column < 0) { |
|
table = it->table; |
|
is_shared = true; |
|
|
|
/* Data is not from This */ |
|
if (it->references && (!table || !(table->flags & EcsTableHasTarget))) { |
|
/* 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 (ecs_term_match_this(&it->terms[t])) { |
|
|
|
/* 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->id) { |
|
is_shared = false; |
|
} |
|
|
|
return is_shared; |
|
} |
|
|
|
return true; |
|
} else { |
|
ecs_entity_t subj = it->sources[t]; |
|
ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Don't use ecs_get_id directly. Instead, go directly to the |
|
* storage so that we can get both the pointer and size */ |
|
ecs_record_t *r = flecs_entities_get(world, subj); |
|
ecs_assert(r != NULL && r->table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
row = ECS_RECORD_TO_ROW(r->row); |
|
table = r->table; |
|
|
|
ecs_id_t id = it->ids[t]; |
|
ecs_table_record_t *tr; |
|
|
|
if (!(tr = flecs_table_record_get(world, table, id)) || (tr->column == -1)) { |
|
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_vec_t *s = &table->data.columns[column].data; |
|
data = ecs_vec_first(s); |
|
/* Fallthrough to has_data */ |
|
} |
|
} else { |
|
/* Data is from This, use table from iterator */ |
|
table = it->table; |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
row = it->offset; |
|
|
|
int32_t storage_column = ecs_table_type_to_column_index( |
|
table, column - 1); |
|
if (storage_column == -1) { |
|
u_index = flecs_table_column_to_union_index(table, column - 1); |
|
if (u_index != -1) { |
|
goto has_union; |
|
} |
|
goto no_data; |
|
} |
|
|
|
if (!it->count) { |
|
goto no_data; |
|
} |
|
|
|
ecs_vec_t *s = &table->data.columns[storage_column].data; |
|
data = ecs_vec_first(s); |
|
|
|
/* Fallthrough to has_data */ |
|
} |
|
|
|
has_data: |
|
if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); |
|
return is_shared; |
|
|
|
has_union: { |
|
/* Edge case: if column is a switch we should return the vector with case |
|
* identifiers. Will be replaced in the future with pluggable storage */ |
|
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_switch_t *sw = &table->_->sw_columns[u_index]; |
|
data = ecs_vec_first(flecs_switch_values(sw)); |
|
goto has_data; |
|
} |
|
|
|
no_data: |
|
if (ptr_out) ptr_out[0] = NULL; |
|
return false; |
|
} |
|
|
|
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_table_t *prev_table = it->table; |
|
if (prev_table) { |
|
it->frame_offset += ecs_table_count(prev_table); |
|
} |
|
|
|
it->table = table; |
|
it->offset = offset; |
|
it->count = count; |
|
if (table) { |
|
ecs_assert(count != 0 || !ecs_table_count(table) || (it->flags & EcsIterTableOnly), |
|
ECS_INTERNAL_ERROR, NULL); |
|
if (count) { |
|
it->entities = ecs_vec_get_t( |
|
&table->data.entities, ecs_entity_t, offset); |
|
} else { |
|
it->entities = NULL; |
|
} |
|
} |
|
|
|
int t, field_count = it->field_count; |
|
if (ECS_BIT_IS_SET(it->flags, EcsIterNoData)) { |
|
ECS_BIT_CLEAR(it->flags, EcsIterHasShared); |
|
return; |
|
} |
|
|
|
bool has_shared = false; |
|
if (ptrs) { |
|
for (t = 0; t < field_count; t ++) { |
|
int32_t column = it->columns[t]; |
|
has_shared |= flecs_iter_populate_term_data(world, it, t, column, |
|
&ptrs[t]); |
|
} |
|
} |
|
|
|
ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); |
|
} |
|
|
|
bool flecs_iter_next_row( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); |
|
if (!is_instanced) { |
|
int32_t instance_count = it->instance_count; |
|
int32_t count = it->count; |
|
int32_t offset = it->offset; |
|
|
|
if (instance_count > count && offset < (instance_count - 1)) { |
|
ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); |
|
int t, field_count = it->field_count; |
|
|
|
for (t = 0; t < field_count; t ++) { |
|
ecs_entity_t src = it->sources[t]; |
|
if (!src) { |
|
void *ptr = it->ptrs[t]; |
|
if (ptr) { |
|
it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); |
|
} |
|
} |
|
} |
|
|
|
if (it->entities) { |
|
it->entities ++; |
|
} |
|
it->offset ++; |
|
|
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool flecs_iter_next_instanced( |
|
ecs_iter_t *it, |
|
bool result) |
|
{ |
|
it->instance_count = it->count; |
|
bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); |
|
bool has_shared = ECS_BIT_IS_SET(it->flags, EcsIterHasShared); |
|
if (result && !is_instanced && it->count && has_shared) { |
|
it->count = 1; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
/* --- Public API --- */ |
|
|
|
void* ecs_field_w_size( |
|
const ecs_iter_t *it, |
|
size_t size, |
|
int32_t index) |
|
{ |
|
ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_check(!size || ecs_field_size(it, index) == size || |
|
(!ecs_field_size(it, index) && (!it->ptrs[index - 1])), |
|
ECS_INVALID_PARAMETER, NULL); |
|
(void)size; |
|
|
|
return it->ptrs[index - 1]; |
|
error: |
|
return NULL; |
|
} |
|
|
|
bool ecs_field_is_readonly( |
|
const ecs_iter_t *it, |
|
int32_t index) |
|
{ |
|
ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
ecs_term_t *term = &it->terms[index - 1]; |
|
|
|
if (term->inout == EcsIn) { |
|
return true; |
|
} else if (term->inout == EcsInOutDefault) { |
|
if (!ecs_term_match_this(term)) { |
|
return true; |
|
} |
|
|
|
ecs_term_id_t *src = &term->src; |
|
if (!(src->flags & EcsSelf)) { |
|
return true; |
|
} |
|
} |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_field_is_writeonly( |
|
const ecs_iter_t *it, |
|
int32_t index) |
|
{ |
|
ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
ecs_term_t *term = &it->terms[index - 1]; |
|
return term->inout == EcsOut; |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_field_is_set( |
|
const ecs_iter_t *it, |
|
int32_t index) |
|
{ |
|
ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
int32_t column = it->columns[index - 1]; |
|
if (!column) { |
|
return false; |
|
} else if (column < 0) { |
|
if (it->references) { |
|
column = -column - 1; |
|
ecs_ref_t *ref = &it->references[column]; |
|
return ref->entity != 0; |
|
} else { |
|
return true; |
|
} |
|
} |
|
return true; |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_field_is_self( |
|
const ecs_iter_t *it, |
|
int32_t index) |
|
{ |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
return it->sources == NULL || it->sources[index - 1] == 0; |
|
} |
|
|
|
ecs_id_t ecs_field_id( |
|
const ecs_iter_t *it, |
|
int32_t index) |
|
{ |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
return it->ids[index - 1]; |
|
} |
|
|
|
int32_t ecs_field_column_index( |
|
const ecs_iter_t *it, |
|
int32_t index) |
|
{ |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
int32_t result = it->columns[index - 1]; |
|
if (result <= 0) { |
|
return -1; |
|
} else { |
|
return result - 1; |
|
} |
|
} |
|
|
|
ecs_entity_t ecs_field_src( |
|
const ecs_iter_t *it, |
|
int32_t index) |
|
{ |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
if (it->sources) { |
|
return it->sources[index - 1]; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
size_t ecs_field_size( |
|
const ecs_iter_t *it, |
|
int32_t index) |
|
{ |
|
ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); |
|
return (size_t)it->sizes[index - 1]; |
|
} |
|
|
|
char* ecs_iter_str( |
|
const ecs_iter_t *it) |
|
{ |
|
if (!(it->flags & EcsIterIsValid)) { |
|
return NULL; |
|
} |
|
|
|
ecs_world_t *world = it->world; |
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
int i; |
|
|
|
if (it->field_count) { |
|
ecs_strbuf_list_push(&buf, "id: ", ","); |
|
for (i = 0; i < it->field_count; i ++) { |
|
ecs_id_t id = ecs_field_id(it, i + 1); |
|
char *str = ecs_id_str(world, id); |
|
ecs_strbuf_list_appendstr(&buf, str); |
|
ecs_os_free(str); |
|
} |
|
ecs_strbuf_list_pop(&buf, "\n"); |
|
|
|
ecs_strbuf_list_push(&buf, "src: ", ","); |
|
for (i = 0; i < it->field_count; i ++) { |
|
ecs_entity_t subj = ecs_field_src(it, i + 1); |
|
char *str = ecs_get_fullpath(world, subj); |
|
ecs_strbuf_list_appendstr(&buf, str); |
|
ecs_os_free(str); |
|
} |
|
ecs_strbuf_list_pop(&buf, "\n"); |
|
|
|
ecs_strbuf_list_push(&buf, "set: ", ","); |
|
for (i = 0; i < it->field_count; i ++) { |
|
if (ecs_field_is_set(it, i + 1)) { |
|
ecs_strbuf_list_appendlit(&buf, "true"); |
|
} else { |
|
ecs_strbuf_list_appendlit(&buf, "false"); |
|
} |
|
} |
|
ecs_strbuf_list_pop(&buf, "\n"); |
|
} |
|
|
|
if (it->variable_count && it->variable_names) { |
|
int32_t actual_count = 0; |
|
for (i = 0; i < it->variable_count; i ++) { |
|
const char *var_name = it->variable_names[i]; |
|
if (!var_name || var_name[0] == '_' || !strcmp(var_name, "This")) { |
|
/* Skip anonymous variables */ |
|
continue; |
|
} |
|
|
|
ecs_var_t var = it->variables[i]; |
|
if (!var.entity) { |
|
/* Skip table variables */ |
|
continue; |
|
} |
|
|
|
if (!actual_count) { |
|
ecs_strbuf_list_push(&buf, "var: ", ","); |
|
} |
|
|
|
char *str = ecs_get_fullpath(world, var.entity); |
|
ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); |
|
ecs_os_free(str); |
|
|
|
actual_count ++; |
|
} |
|
if (actual_count) { |
|
ecs_strbuf_list_pop(&buf, "\n"); |
|
} |
|
} |
|
|
|
if (it->count) { |
|
ecs_strbuf_appendlit(&buf, "this:\n"); |
|
for (i = 0; i < it->count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
char *str = ecs_get_fullpath(world, e); |
|
ecs_strbuf_appendlit(&buf, " - "); |
|
ecs_strbuf_appendstr(&buf, str); |
|
ecs_strbuf_appendch(&buf, '\n'); |
|
ecs_os_free(str); |
|
} |
|
} |
|
|
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
void ecs_iter_poly( |
|
const ecs_world_t *world, |
|
const ecs_poly_t *poly, |
|
ecs_iter_t *iter_out, |
|
ecs_term_t *filter) |
|
{ |
|
ecs_iterable_t *iterable = ecs_get_iterable(poly); |
|
iterable->init(world, poly, iter_out, filter); |
|
} |
|
|
|
bool ecs_iter_next( |
|
ecs_iter_t *iter) |
|
{ |
|
ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); |
|
return iter->next(iter); |
|
error: |
|
return false; |
|
} |
|
|
|
int32_t ecs_iter_count( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ECS_BIT_SET(it->flags, EcsIterNoData); |
|
ECS_BIT_SET(it->flags, EcsIterIsInstanced); |
|
|
|
int32_t count = 0; |
|
while (ecs_iter_next(it)) { |
|
count += it->count; |
|
} |
|
return count; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_iter_first( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ECS_BIT_SET(it->flags, EcsIterNoData); |
|
ECS_BIT_SET(it->flags, EcsIterIsInstanced); |
|
|
|
ecs_entity_t result = 0; |
|
if (ecs_iter_next(it)) { |
|
result = it->entities[0]; |
|
ecs_iter_fini(it); |
|
} |
|
|
|
return result; |
|
error: |
|
return 0; |
|
} |
|
|
|
bool ecs_iter_is_true( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ECS_BIT_SET(it->flags, EcsIterNoData); |
|
ECS_BIT_SET(it->flags, EcsIterIsInstanced); |
|
|
|
bool result = ecs_iter_next(it); |
|
if (result) { |
|
ecs_iter_fini(it); |
|
} |
|
return result; |
|
error: |
|
return false; |
|
} |
|
|
|
ecs_entity_t ecs_iter_get_var( |
|
ecs_iter_t *it, |
|
int32_t var_id) |
|
{ |
|
ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_var_t *var = &it->variables[var_id]; |
|
ecs_entity_t e = var->entity; |
|
if (!e) { |
|
ecs_table_t *table = var->range.table; |
|
if (!table && !var_id) { |
|
table = it->table; |
|
} |
|
if (table) { |
|
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_vec_get_t(&table->data.entities, ecs_entity_t, |
|
var->range.offset)[0]; |
|
} |
|
} |
|
} else { |
|
ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
return e; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_table_t* ecs_iter_get_var_as_table( |
|
ecs_iter_t *it, |
|
int32_t var_id) |
|
{ |
|
ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_var_t *var = &it->variables[var_id]; |
|
ecs_table_t *table = var->range.table; |
|
if (!table && !var_id) { |
|
table = it->table; |
|
} |
|
|
|
if (!table) { |
|
/* If table is not set, try to get table from entity */ |
|
ecs_entity_t e = var->entity; |
|
if (e) { |
|
ecs_record_t *r = flecs_entities_get(it->real_world, e); |
|
if (r) { |
|
table = r->table; |
|
if (ecs_table_count(table) != 1) { |
|
/* If table contains more than the entity, make sure not to |
|
* return a partial table. */ |
|
return NULL; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (table) { |
|
if (var->range.offset) { |
|
/* Don't return whole table if only partial table is matched */ |
|
return NULL; |
|
} |
|
|
|
if (!var->range.count || ecs_table_count(table) == var->range.count) { |
|
/* Return table if count matches */ |
|
return table; |
|
} |
|
} |
|
|
|
error: |
|
return NULL; |
|
} |
|
|
|
ecs_table_range_t ecs_iter_get_var_as_range( |
|
ecs_iter_t *it, |
|
int32_t var_id) |
|
{ |
|
ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_table_range_t result = { 0 }; |
|
|
|
ecs_var_t *var = &it->variables[var_id]; |
|
ecs_table_t *table = var->range.table; |
|
if (!table && !var_id) { |
|
table = it->table; |
|
} |
|
|
|
if (!table) { |
|
ecs_entity_t e = var->entity; |
|
if (e) { |
|
ecs_record_t *r = flecs_entities_get(it->real_world, e); |
|
if (r) { |
|
result.table = r->table; |
|
result.offset = ECS_RECORD_TO_ROW(r->row); |
|
result.count = 1; |
|
} |
|
} |
|
} else { |
|
result.table = table; |
|
result.offset = var->range.offset; |
|
result.count = var->range.count; |
|
if (!result.count) { |
|
result.count = ecs_table_count(table); |
|
} |
|
} |
|
|
|
return result; |
|
error: |
|
return (ecs_table_range_t){0}; |
|
} |
|
|
|
void ecs_iter_set_var( |
|
ecs_iter_t *it, |
|
int32_t var_id, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id < FLECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); |
|
/* Can't set variable while iterating */ |
|
ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_var_t *var = &it->variables[var_id]; |
|
var->entity = entity; |
|
|
|
ecs_record_t *r = flecs_entities_get(it->real_world, entity); |
|
if (r) { |
|
var->range.table = r->table; |
|
var->range.offset = ECS_RECORD_TO_ROW(r->row); |
|
var->range.count = 1; |
|
} else { |
|
var->range.table = NULL; |
|
var->range.offset = 0; |
|
var->range.count = 0; |
|
} |
|
|
|
it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); |
|
|
|
if (it->set_var) { |
|
it->set_var(it); |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
void ecs_iter_set_var_as_table( |
|
ecs_iter_t *it, |
|
int32_t var_id, |
|
const ecs_table_t *table) |
|
{ |
|
ecs_table_range_t range = { .table = ECS_CONST_CAST(ecs_table_t*, table) }; |
|
ecs_iter_set_var_as_range(it, var_id, &range); |
|
} |
|
|
|
void ecs_iter_set_var_as_range( |
|
ecs_iter_t *it, |
|
int32_t var_id, |
|
const ecs_table_range_t *range) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id < FLECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(range != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(range->table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!range->offset || range->offset < ecs_table_count(range->table), |
|
ECS_INVALID_PARAMETER, NULL); |
|
ecs_check((range->offset + range->count) <= ecs_table_count(range->table), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Can't set variable while iterating */ |
|
ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, NULL); |
|
|
|
ecs_var_t *var = &it->variables[var_id]; |
|
var->range = *range; |
|
|
|
if (range->count == 1) { |
|
ecs_table_t *table = range->table; |
|
var->entity = ecs_vec_get_t( |
|
&table->data.entities, ecs_entity_t, range->offset)[0]; |
|
} else { |
|
var->entity = 0; |
|
} |
|
|
|
it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); |
|
|
|
error: |
|
return; |
|
} |
|
|
|
bool ecs_iter_var_is_constrained( |
|
ecs_iter_t *it, |
|
int32_t var_id) |
|
{ |
|
return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; |
|
} |
|
|
|
static |
|
void ecs_chained_iter_fini( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_iter_fini(it->chain_it); |
|
|
|
it->chain_it = NULL; |
|
} |
|
|
|
ecs_iter_t ecs_page_iter( |
|
const ecs_iter_t *it, |
|
int32_t offset, |
|
int32_t limit) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_iter_t result = *it; |
|
result.priv.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ |
|
|
|
result.priv.iter.page = (ecs_page_iter_t){ |
|
.offset = offset, |
|
.limit = limit, |
|
.remaining = limit |
|
}; |
|
result.next = ecs_page_next; |
|
result.fini = ecs_chained_iter_fini; |
|
result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); |
|
|
|
return result; |
|
error: |
|
return (ecs_iter_t){ 0 }; |
|
} |
|
|
|
static |
|
void flecs_offset_iter( |
|
ecs_iter_t *it, |
|
int32_t offset) |
|
{ |
|
it->entities = &it->entities[offset]; |
|
|
|
int32_t t, field_count = it->field_count; |
|
void **it_ptrs = it->ptrs; |
|
if (it_ptrs) { |
|
for (t = 0; t < field_count; t ++) { |
|
void *ptrs = it_ptrs[t]; |
|
if (!ptrs) { |
|
continue; |
|
} |
|
|
|
if (it->sources[t]) { |
|
continue; |
|
} |
|
|
|
it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); |
|
} |
|
} |
|
} |
|
|
|
static |
|
bool ecs_page_next_instanced( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_iter_t *chain_it = it->chain_it; |
|
bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); |
|
|
|
do { |
|
if (!ecs_iter_next(chain_it)) { |
|
goto depleted; |
|
} |
|
|
|
ecs_page_iter_t *iter = &it->priv.iter.page; |
|
|
|
/* Copy everything up to the private iterator data */ |
|
ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); |
|
|
|
/* Keep instancing setting from original iterator */ |
|
ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); |
|
|
|
if (!chain_it->table) { |
|
goto yield; /* Task query */ |
|
} |
|
|
|
int32_t offset = iter->offset; |
|
int32_t limit = iter->limit; |
|
if (!(offset || limit)) { |
|
if (it->count) { |
|
goto yield; |
|
} else { |
|
goto depleted; |
|
} |
|
} |
|
|
|
int32_t count = it->count; |
|
int32_t remaining = iter->remaining; |
|
|
|
if (offset) { |
|
if (offset > count) { |
|
/* No entities to iterate in current table */ |
|
iter->offset -= count; |
|
it->count = 0; |
|
continue; |
|
} else { |
|
it->offset += offset; |
|
count = it->count -= offset; |
|
iter->offset = 0; |
|
flecs_offset_iter(it, offset); |
|
} |
|
} |
|
|
|
if (remaining) { |
|
if (remaining > count) { |
|
iter->remaining -= count; |
|
} else { |
|
it->count = remaining; |
|
iter->remaining = 0; |
|
} |
|
} else if (limit) { |
|
/* Limit hit: no more entities left to iterate */ |
|
goto done; |
|
} |
|
} while (it->count == 0); |
|
|
|
yield: |
|
if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { |
|
it->offset = 0; |
|
} |
|
|
|
return true; |
|
done: |
|
/* Cleanup iterator resources if it wasn't yet depleted */ |
|
ecs_iter_fini(chain_it); |
|
depleted: |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_page_next( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); |
|
|
|
if (flecs_iter_next_row(it)) { |
|
return true; |
|
} |
|
|
|
return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); |
|
error: |
|
return false; |
|
} |
|
|
|
ecs_iter_t ecs_worker_iter( |
|
const ecs_iter_t *it, |
|
int32_t index, |
|
int32_t count) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(index >= 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_iter_t result = *it; |
|
result.priv.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ |
|
|
|
result.priv.iter.worker = (ecs_worker_iter_t){ |
|
.index = index, |
|
.count = count |
|
}; |
|
result.next = ecs_worker_next; |
|
result.fini = ecs_chained_iter_fini; |
|
result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); |
|
|
|
return result; |
|
error: |
|
return (ecs_iter_t){ 0 }; |
|
} |
|
|
|
static |
|
bool ecs_worker_next_instanced( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); |
|
|
|
ecs_iter_t *chain_it = it->chain_it; |
|
ecs_worker_iter_t *iter = &it->priv.iter.worker; |
|
int32_t res_count = iter->count, res_index = iter->index; |
|
int32_t per_worker, instances_per_worker, first; |
|
|
|
do { |
|
if (!ecs_iter_next(chain_it)) { |
|
return false; |
|
} |
|
|
|
/* Copy everything up to the private iterator data */ |
|
ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); |
|
|
|
/* Keep instancing setting from original iterator */ |
|
ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); |
|
|
|
int32_t count = it->count; |
|
int32_t instance_count = it->instance_count; |
|
per_worker = count / res_count; |
|
instances_per_worker = instance_count / res_count; |
|
first = per_worker * res_index; |
|
count -= per_worker * res_count; |
|
|
|
if (count) { |
|
if (res_index < count) { |
|
per_worker ++; |
|
first += res_index; |
|
} else { |
|
first += count; |
|
} |
|
} |
|
|
|
if (!per_worker && it->table == NULL) { |
|
if (res_index == 0) { |
|
return true; |
|
} else { |
|
// chained iterator was not yet cleaned up |
|
// since it returned true from ecs_iter_next, so clean it up here. |
|
ecs_iter_fini(chain_it); |
|
return false; |
|
} |
|
} |
|
} while (!per_worker); |
|
|
|
it->instance_count = instances_per_worker; |
|
it->frame_offset += first; |
|
|
|
flecs_offset_iter(it, it->offset + first); |
|
it->count = per_worker; |
|
|
|
if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { |
|
it->offset += first; |
|
} else { |
|
it->offset = 0; |
|
} |
|
|
|
return true; |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_worker_next( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); |
|
|
|
if (flecs_iter_next_row(it)) { |
|
return true; |
|
} |
|
|
|
return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); |
|
error: |
|
return false; |
|
} |
|
|
|
/** |
|
* @file misc.c |
|
* @brief Miscellaneous functions. |
|
*/ |
|
|
|
#include <time.h> |
|
#include <ctype.h> |
|
|
|
#ifndef FLECS_NDEBUG |
|
static int64_t flecs_s_min[] = { |
|
[1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; |
|
static int64_t flecs_s_max[] = { |
|
[1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; |
|
static uint64_t flecs_u_max[] = { |
|
[1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; |
|
|
|
uint64_t flecs_ito_( |
|
size_t size, |
|
bool is_signed, |
|
bool lt_zero, |
|
uint64_t u, |
|
const char *err) |
|
{ |
|
union { |
|
uint64_t u; |
|
int64_t s; |
|
} v; |
|
|
|
v.u = u; |
|
|
|
if (is_signed) { |
|
ecs_assert(v.s >= flecs_s_min[size], ECS_INVALID_CONVERSION, err); |
|
ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, err); |
|
} else { |
|
ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); |
|
ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err); |
|
} |
|
|
|
return u; |
|
} |
|
#endif |
|
|
|
int32_t flecs_next_pow_of_2( |
|
int32_t n) |
|
{ |
|
n --; |
|
n |= n >> 1; |
|
n |= n >> 2; |
|
n |= n >> 4; |
|
n |= n >> 8; |
|
n |= n >> 16; |
|
n ++; |
|
|
|
return n; |
|
} |
|
|
|
/** Convert time to double */ |
|
double ecs_time_to_double( |
|
ecs_time_t t) |
|
{ |
|
double result; |
|
result = t.sec; |
|
return result + (double)t.nanosec / (double)1000000000; |
|
} |
|
|
|
ecs_time_t ecs_time_sub( |
|
ecs_time_t t1, |
|
ecs_time_t t2) |
|
{ |
|
ecs_time_t result; |
|
|
|
if (t1.nanosec >= t2.nanosec) { |
|
result.nanosec = t1.nanosec - t2.nanosec; |
|
result.sec = t1.sec - t2.sec; |
|
} else { |
|
result.nanosec = t1.nanosec - t2.nanosec + 1000000000; |
|
result.sec = t1.sec - t2.sec - 1; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
void ecs_sleepf( |
|
double t) |
|
{ |
|
if (t > 0) { |
|
int sec = (int)t; |
|
int nsec = (int)((t - sec) * 1000000000); |
|
ecs_os_sleep(sec, nsec); |
|
} |
|
} |
|
|
|
double ecs_time_measure( |
|
ecs_time_t *start) |
|
{ |
|
ecs_time_t stop, temp; |
|
ecs_os_get_time(&stop); |
|
temp = stop; |
|
stop = ecs_time_sub(stop, *start); |
|
*start = temp; |
|
return ecs_time_to_double(stop); |
|
} |
|
|
|
void* ecs_os_memdup( |
|
const void *src, |
|
ecs_size_t size) |
|
{ |
|
if (!src) { |
|
return NULL; |
|
} |
|
|
|
void *dst = ecs_os_malloc(size); |
|
ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
ecs_os_memcpy(dst, src, size); |
|
return dst; |
|
} |
|
|
|
int flecs_entity_compare( |
|
ecs_entity_t e1, |
|
const void *ptr1, |
|
ecs_entity_t e2, |
|
const void *ptr2) |
|
{ |
|
(void)ptr1; |
|
(void)ptr2; |
|
return (e1 > e2) - (e1 < e2); |
|
} |
|
|
|
uint64_t flecs_string_hash( |
|
const void *ptr) |
|
{ |
|
const ecs_hashed_string_t *str = ptr; |
|
ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); |
|
return str->hash; |
|
} |
|
|
|
char* ecs_vasprintf( |
|
const char *fmt, |
|
va_list args) |
|
{ |
|
ecs_size_t size = 0; |
|
char *result = NULL; |
|
va_list tmpa; |
|
|
|
va_copy(tmpa, args); |
|
|
|
size = vsnprintf(result, 0, fmt, tmpa); |
|
|
|
va_end(tmpa); |
|
|
|
if ((int32_t)size < 0) { |
|
return NULL; |
|
} |
|
|
|
result = (char *) ecs_os_malloc(size + 1); |
|
|
|
if (!result) { |
|
return NULL; |
|
} |
|
|
|
ecs_os_vsprintf(result, fmt, args); |
|
|
|
return result; |
|
} |
|
|
|
char* ecs_asprintf( |
|
const char *fmt, |
|
...) |
|
{ |
|
va_list args; |
|
va_start(args, fmt); |
|
char *result = ecs_vasprintf(fmt, args); |
|
va_end(args); |
|
return result; |
|
} |
|
|
|
char* flecs_to_snake_case(const char *str) { |
|
int32_t upper_count = 0, len = 1; |
|
const char *ptr = str; |
|
char ch, *out, *out_ptr; |
|
|
|
for (ptr = &str[1]; (ch = *ptr); ptr ++) { |
|
if (isupper(ch)) { |
|
upper_count ++; |
|
} |
|
len ++; |
|
} |
|
|
|
out = out_ptr = ecs_os_malloc_n(char, len + upper_count + 1); |
|
for (ptr = str; (ch = *ptr); ptr ++) { |
|
if (isupper(ch)) { |
|
if ((ptr != str) && (out_ptr[-1] != '_')) { |
|
out_ptr[0] = '_'; |
|
out_ptr ++; |
|
} |
|
out_ptr[0] = (char)tolower(ch); |
|
out_ptr ++; |
|
} else { |
|
out_ptr[0] = ch; |
|
out_ptr ++; |
|
} |
|
} |
|
|
|
out_ptr[0] = '\0'; |
|
|
|
return out; |
|
} |
|
|
|
/** |
|
* @file observable.c |
|
* @brief Observable implementation. |
|
* |
|
* The observable implementation contains functions that find the set of |
|
* observers to invoke for an event. The code also contains the implementation |
|
* of a reachable id cache, which is used to speedup event propagation when |
|
* relationships are added/removed to/from entities. |
|
*/ |
|
|
|
|
|
void flecs_observable_init( |
|
ecs_observable_t *observable) |
|
{ |
|
flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t); |
|
observable->on_add.event = EcsOnAdd; |
|
observable->on_remove.event = EcsOnRemove; |
|
observable->on_set.event = EcsOnSet; |
|
observable->un_set.event = EcsUnSet; |
|
} |
|
|
|
void flecs_observable_fini( |
|
ecs_observable_t *observable) |
|
{ |
|
ecs_assert(!ecs_map_is_init(&observable->on_add.event_ids), |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!ecs_map_is_init(&observable->on_remove.event_ids), |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!ecs_map_is_init(&observable->on_set.event_ids), |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!ecs_map_is_init(&observable->un_set.event_ids), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_sparse_t *events = &observable->events; |
|
int32_t i, count = flecs_sparse_count(events); |
|
for (i = 0; i < count; i ++) { |
|
ecs_event_record_t *er = |
|
flecs_sparse_get_dense_t(events, ecs_event_record_t, i); |
|
ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); |
|
(void)er; |
|
|
|
/* All triggers should've unregistered by now */ |
|
ecs_assert(!ecs_map_is_init(&er->event_ids), |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
flecs_sparse_fini(&observable->events); |
|
} |
|
|
|
ecs_event_record_t* flecs_event_record_get( |
|
const ecs_observable_t *o, |
|
ecs_entity_t event) |
|
{ |
|
ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Builtin events*/ |
|
if (event == EcsOnAdd) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_add); |
|
else if (event == EcsOnRemove) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_remove); |
|
else if (event == EcsOnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_set); |
|
else if (event == EcsUnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->un_set); |
|
else if (event == EcsWildcard) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard); |
|
|
|
/* User events */ |
|
return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); |
|
} |
|
|
|
ecs_event_record_t* flecs_event_record_ensure( |
|
ecs_observable_t *o, |
|
ecs_entity_t event) |
|
{ |
|
ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_event_record_t *er = flecs_event_record_get(o, event); |
|
if (er) { |
|
return er; |
|
} |
|
er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event); |
|
er->event = event; |
|
return er; |
|
} |
|
|
|
static |
|
const ecs_event_record_t* flecs_event_record_get_if( |
|
const ecs_observable_t *o, |
|
ecs_entity_t event) |
|
{ |
|
ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
const ecs_event_record_t *er = flecs_event_record_get(o, event); |
|
if (er) { |
|
if (ecs_map_is_init(&er->event_ids)) { |
|
return er; |
|
} |
|
if (er->any) { |
|
return er; |
|
} |
|
if (er->wildcard) { |
|
return er; |
|
} |
|
if (er->wildcard_pair) { |
|
return er; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
ecs_event_id_record_t* flecs_event_id_record_get( |
|
const ecs_event_record_t *er, |
|
ecs_id_t id) |
|
{ |
|
if (!er) { |
|
return NULL; |
|
} |
|
|
|
if (id == EcsAny) return er->any; |
|
else if (id == EcsWildcard) return er->wildcard; |
|
else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair; |
|
else { |
|
if (ecs_map_is_init(&er->event_ids)) { |
|
return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id); |
|
} |
|
return NULL; |
|
} |
|
} |
|
|
|
static |
|
ecs_event_id_record_t* flecs_event_id_record_get_if( |
|
const ecs_event_record_t *er, |
|
ecs_id_t id) |
|
{ |
|
ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); |
|
if (!ider) { |
|
return NULL; |
|
} |
|
|
|
if (ider->observer_count) { |
|
return ider; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
ecs_event_id_record_t* flecs_event_id_record_ensure( |
|
ecs_world_t *world, |
|
ecs_event_record_t *er, |
|
ecs_id_t id) |
|
{ |
|
ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); |
|
if (ider) { |
|
return ider; |
|
} |
|
|
|
ider = ecs_os_calloc_t(ecs_event_id_record_t); |
|
|
|
if (id == EcsAny) { |
|
return er->any = ider; |
|
} else if (id == EcsWildcard) { |
|
return er->wildcard = ider; |
|
} else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { |
|
return er->wildcard_pair = ider; |
|
} |
|
|
|
ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); |
|
ecs_map_insert_ptr(&er->event_ids, id, ider); |
|
return ider; |
|
} |
|
|
|
void flecs_event_id_record_remove( |
|
ecs_event_record_t *er, |
|
ecs_id_t id) |
|
{ |
|
if (id == EcsAny) { |
|
er->any = NULL; |
|
} else if (id == EcsWildcard) { |
|
er->wildcard = NULL; |
|
} else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { |
|
er->wildcard_pair = NULL; |
|
} else { |
|
ecs_map_remove(&er->event_ids, id); |
|
if (!ecs_map_count(&er->event_ids)) { |
|
ecs_map_fini(&er->event_ids); |
|
} |
|
} |
|
} |
|
|
|
static |
|
int32_t flecs_event_observers_get( |
|
const ecs_event_record_t *er, |
|
ecs_id_t id, |
|
ecs_event_id_record_t **iders) |
|
{ |
|
if (!er) { |
|
return 0; |
|
} |
|
|
|
/* Populate array with observer sets matching the id */ |
|
int32_t count = 0; |
|
|
|
if (id != EcsAny) { |
|
iders[0] = flecs_event_id_record_get_if(er, EcsAny); |
|
count += iders[count] != 0; |
|
} |
|
|
|
iders[count] = flecs_event_id_record_get_if(er, id); |
|
count += iders[count] != 0; |
|
|
|
if (id != EcsAny) { |
|
if (ECS_IS_PAIR(id)) { |
|
ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); |
|
ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); |
|
ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); |
|
iders[count] = flecs_event_id_record_get_if(er, id_fwc); |
|
count += iders[count] != 0; |
|
iders[count] = flecs_event_id_record_get_if(er, id_swc); |
|
count += iders[count] != 0; |
|
iders[count] = flecs_event_id_record_get_if(er, id_pwc); |
|
count += iders[count] != 0; |
|
} else { |
|
iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); |
|
count += iders[count] != 0; |
|
} |
|
} |
|
|
|
return count; |
|
} |
|
|
|
bool flecs_observers_exist( |
|
ecs_observable_t *observable, |
|
ecs_id_t id, |
|
ecs_entity_t event) |
|
{ |
|
const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); |
|
if (!er) { |
|
return false; |
|
} |
|
|
|
return flecs_event_id_record_get_if(er, id) != NULL; |
|
} |
|
|
|
static |
|
void flecs_emit_propagate( |
|
ecs_world_t *world, |
|
ecs_iter_t *it, |
|
ecs_id_record_t *idr, |
|
ecs_id_record_t *tgt_idr, |
|
ecs_entity_t trav, |
|
ecs_event_id_record_t **iders, |
|
int32_t ider_count); |
|
|
|
static |
|
void flecs_emit_propagate_id( |
|
ecs_world_t *world, |
|
ecs_iter_t *it, |
|
ecs_id_record_t *idr, |
|
ecs_id_record_t *cur, |
|
ecs_entity_t trav, |
|
ecs_event_id_record_t **iders, |
|
int32_t ider_count) |
|
{ |
|
ecs_table_cache_iter_t idt; |
|
if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { |
|
return; |
|
} |
|
|
|
const ecs_table_record_t *tr; |
|
int32_t event_cur = it->event_cur; |
|
while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
if (!ecs_table_count(table)) { |
|
continue; |
|
} |
|
|
|
bool owned = flecs_id_record_get_table(idr, table) != NULL; |
|
|
|
int32_t e, entity_count = ecs_table_count(table); |
|
it->table = table; |
|
it->other_table = NULL; |
|
it->offset = 0; |
|
it->count = entity_count; |
|
if (entity_count) { |
|
it->entities = ecs_vec_first(&table->data.entities); |
|
} |
|
|
|
/* Treat as new event as this could invoke observers again for |
|
* different tables. */ |
|
it->event_cur = ++ world->event_id; |
|
|
|
int32_t ider_i; |
|
for (ider_i = 0; ider_i < ider_count; ider_i ++) { |
|
ecs_event_id_record_t *ider = iders[ider_i]; |
|
flecs_observers_invoke(world, &ider->up, it, table, trav); |
|
|
|
if (!owned) { |
|
/* Owned takes precedence */ |
|
flecs_observers_invoke(world, &ider->self_up, it, table, trav); |
|
} |
|
} |
|
|
|
if (!table->_->traversable_count) { |
|
continue; |
|
} |
|
|
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities); |
|
for (e = 0; e < entity_count; e ++) { |
|
ecs_record_t *r = flecs_entities_get(world, entities[e]); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_record_t *idr_t = r->idr; |
|
if (idr_t) { |
|
/* Only notify for entities that are used in pairs with |
|
* traversable relationships */ |
|
flecs_emit_propagate(world, it, idr, idr_t, trav, |
|
iders, ider_count); |
|
} |
|
} |
|
} |
|
|
|
it->event_cur = event_cur; |
|
} |
|
|
|
static |
|
void flecs_emit_propagate( |
|
ecs_world_t *world, |
|
ecs_iter_t *it, |
|
ecs_id_record_t *idr, |
|
ecs_id_record_t *tgt_idr, |
|
ecs_entity_t propagate_trav, |
|
ecs_event_id_record_t **iders, |
|
int32_t ider_count) |
|
{ |
|
ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (ecs_should_log_3()) { |
|
char *idstr = ecs_id_str(world, tgt_idr->id); |
|
ecs_dbg_3("propagate events/invalidate cache for %s", idstr); |
|
ecs_os_free(idstr); |
|
} |
|
ecs_log_push_3(); |
|
|
|
/* Propagate to records of traversable relationships */ |
|
ecs_id_record_t *cur = tgt_idr; |
|
while ((cur = cur->trav.next)) { |
|
cur->reachable.generation ++; /* Invalidate cache */ |
|
|
|
/* Get traversed relationship */ |
|
ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); |
|
if (propagate_trav && propagate_trav != trav) { |
|
continue; |
|
} |
|
|
|
flecs_emit_propagate_id( |
|
world, it, idr, cur, trav, iders, ider_count); |
|
} |
|
|
|
ecs_log_pop_3(); |
|
} |
|
|
|
static |
|
void flecs_emit_propagate_invalidate_tables( |
|
ecs_world_t *world, |
|
ecs_id_record_t *tgt_idr) |
|
{ |
|
ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (ecs_should_log_3()) { |
|
char *idstr = ecs_id_str(world, tgt_idr->id); |
|
ecs_dbg_3("invalidate reachable cache for %s", idstr); |
|
ecs_os_free(idstr); |
|
} |
|
|
|
/* Invalidate records of traversable relationships */ |
|
ecs_id_record_t *cur = tgt_idr; |
|
while ((cur = cur->trav.next)) { |
|
ecs_reachable_cache_t *rc = &cur->reachable; |
|
if (rc->current != rc->generation) { |
|
/* Subtree is already marked invalid */ |
|
continue; |
|
} |
|
|
|
rc->generation ++; |
|
|
|
ecs_table_cache_iter_t idt; |
|
if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { |
|
continue; |
|
} |
|
|
|
const ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
if (!table->_->traversable_count) { |
|
continue; |
|
} |
|
|
|
int32_t e, entity_count = ecs_table_count(table); |
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities); |
|
|
|
for (e = 0; e < entity_count; e ++) { |
|
ecs_record_t *r = flecs_entities_get(world, entities[e]); |
|
ecs_id_record_t *idr_t = r->idr; |
|
if (idr_t) { |
|
/* Only notify for entities that are used in pairs with |
|
* traversable relationships */ |
|
flecs_emit_propagate_invalidate_tables(world, idr_t); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void flecs_emit_propagate_invalidate( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t offset, |
|
int32_t count) |
|
{ |
|
ecs_entity_t *entities = ecs_vec_get_t(&table->data.entities, |
|
ecs_entity_t, offset); |
|
int32_t i; |
|
for (i = 0; i < count; i ++) { |
|
ecs_record_t *record = flecs_entities_get(world, entities[i]); |
|
if (!record) { |
|
/* If the event is emitted after a bulk operation, it's possible |
|
* that it hasn't been populated with entities yet. */ |
|
continue; |
|
} |
|
|
|
ecs_id_record_t *idr_t = record->idr; |
|
if (idr_t) { |
|
/* Event is used as target in traversable relationship, propagate */ |
|
flecs_emit_propagate_invalidate_tables(world, idr_t); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_override_copy( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
const ecs_type_info_t *ti, |
|
void *dst, |
|
const void *src, |
|
int32_t offset, |
|
int32_t count) |
|
{ |
|
void *ptr = dst; |
|
ecs_copy_t copy = ti->hooks.copy; |
|
ecs_size_t size = ti->size; |
|
int32_t i; |
|
if (copy) { |
|
for (i = 0; i < count; i ++) { |
|
copy(ptr, src, count, ti); |
|
ptr = ECS_OFFSET(ptr, size); |
|
} |
|
} else { |
|
for (i = 0; i < count; i ++) { |
|
ecs_os_memcpy(ptr, src, size); |
|
ptr = ECS_OFFSET(ptr, size); |
|
} |
|
} |
|
|
|
ecs_iter_action_t on_set = ti->hooks.on_set; |
|
if (on_set) { |
|
ecs_entity_t *entities = ecs_vec_get_t( |
|
&table->data.entities, ecs_entity_t, offset); |
|
flecs_invoke_hook(world, table, count, offset, entities, |
|
dst, ti->component, ti, EcsOnSet, on_set); |
|
} |
|
} |
|
|
|
static |
|
void* flecs_override( |
|
ecs_iter_t *it, |
|
const ecs_type_t *emit_ids, |
|
ecs_id_t id, |
|
ecs_table_t *table, |
|
ecs_id_record_t *idr) |
|
{ |
|
if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) { |
|
return NULL; |
|
} |
|
|
|
int32_t i = 0, count = emit_ids->count; |
|
ecs_id_t *ids = emit_ids->array; |
|
for (i = 0; i < count; i ++) { |
|
if (ids[i] == id) { |
|
/* If an id was both inherited and overridden in the same event |
|
* (like what happens during an auto override), we need to copy the |
|
* value of the inherited component to the new component. |
|
* Also flag to the callee that this component was overridden, so |
|
* that an OnSet event can be emmitted for it. |
|
* Note that this is different from a component that was overridden |
|
* after it was inherited, as this does not change the actual value |
|
* of the component for the entity (it is copied from the existing |
|
* overridden component), and does not require an OnSet event. */ |
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (!tr) { |
|
continue; |
|
} |
|
|
|
int32_t index = tr->column; |
|
ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_column_t *column = &table->data.columns[index]; |
|
ecs_size_t size = column->ti->size; |
|
return ecs_vec_get(&column->data, size, it->offset); |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static |
|
void flecs_emit_forward_up( |
|
ecs_world_t *world, |
|
const ecs_event_record_t *er, |
|
const ecs_event_record_t *er_onset, |
|
const ecs_type_t *emit_ids, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_id_record_t *idr, |
|
ecs_vec_t *stack, |
|
ecs_vec_t *reachable_ids); |
|
|
|
static |
|
void flecs_emit_forward_id( |
|
ecs_world_t *world, |
|
const ecs_event_record_t *er, |
|
const ecs_event_record_t *er_onset, |
|
const ecs_type_t *emit_ids, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_id_record_t *idr, |
|
ecs_entity_t tgt, |
|
ecs_table_t *tgt_table, |
|
int32_t column, |
|
int32_t offset, |
|
ecs_entity_t trav) |
|
{ |
|
ecs_id_t id = idr->id; |
|
ecs_entity_t event = er ? er->event : 0; |
|
bool inherit = trav == EcsIsA; |
|
bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1); |
|
ecs_event_id_record_t *iders[5]; |
|
ecs_event_id_record_t *iders_onset[5]; |
|
|
|
/* Skip id if there are no observers for it */ |
|
int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders); |
|
int32_t ider_onset_i, ider_onset_count = 0; |
|
if (er_onset) { |
|
ider_onset_count = flecs_event_observers_get( |
|
er_onset, id, iders_onset); |
|
} |
|
|
|
if (!may_override && (!ider_count && !ider_onset_count)) { |
|
return; |
|
} |
|
|
|
it->ids[0] = id; |
|
it->sources[0] = tgt; |
|
it->event_id = id; |
|
it->ptrs[0] = NULL; |
|
it->sizes[0] = 0; |
|
|
|
int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); |
|
if (storage_i != -1) { |
|
ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_column_t *c = &tgt_table->data.columns[storage_i]; |
|
it->ptrs[0] = ecs_vec_get(&c->data, c->ti->size, offset); |
|
it->sizes[0] = c->ti->size; |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
bool owned = tr != NULL; |
|
|
|
for (ider_i = 0; ider_i < ider_count; ider_i ++) { |
|
ecs_event_id_record_t *ider = iders[ider_i]; |
|
flecs_observers_invoke(world, &ider->up, it, table, trav); |
|
|
|
/* Owned takes precedence */ |
|
if (!owned) { |
|
flecs_observers_invoke(world, &ider->self_up, it, table, trav); |
|
} |
|
} |
|
|
|
/* Emit OnSet events for newly inherited components */ |
|
if (storage_i != -1) { |
|
bool override = false; |
|
|
|
/* If component was added together with IsA relationship, still emit |
|
* OnSet event, as it's a new value for the entity. */ |
|
void *base_ptr = it->ptrs[0]; |
|
void *ptr = flecs_override(it, emit_ids, id, table, idr); |
|
if (ptr) { |
|
override = true; |
|
it->ptrs[0] = ptr; |
|
} |
|
|
|
if (ider_onset_count) { |
|
it->event = er_onset->event; |
|
|
|
for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) { |
|
ecs_event_id_record_t *ider = iders_onset[ider_onset_i]; |
|
flecs_observers_invoke(world, &ider->up, it, table, trav); |
|
|
|
/* Owned takes precedence */ |
|
if (!owned) { |
|
flecs_observers_invoke( |
|
world, &ider->self_up, it, table, trav); |
|
} else if (override) { |
|
ecs_entity_t src = it->sources[0]; |
|
it->sources[0] = 0; |
|
flecs_observers_invoke(world, &ider->self, it, table, 0); |
|
flecs_observers_invoke(world, &ider->self_up, it, table, 0); |
|
it->sources[0] = src; |
|
} |
|
} |
|
|
|
it->event = event; |
|
it->ptrs[0] = base_ptr; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_emit_forward_and_cache_id( |
|
ecs_world_t *world, |
|
const ecs_event_record_t *er, |
|
const ecs_event_record_t *er_onset, |
|
const ecs_type_t *emit_ids, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_id_record_t *idr, |
|
ecs_entity_t tgt, |
|
ecs_record_t *tgt_record, |
|
ecs_table_t *tgt_table, |
|
const ecs_table_record_t *tgt_tr, |
|
int32_t column, |
|
int32_t offset, |
|
ecs_vec_t *reachable_ids, |
|
ecs_entity_t trav) |
|
{ |
|
/* Cache forwarded id for (rel, tgt) pair */ |
|
ecs_reachable_elem_t *elem = ecs_vec_append_t(&world->allocator, |
|
reachable_ids, ecs_reachable_elem_t); |
|
elem->tr = tgt_tr; |
|
elem->record = tgt_record; |
|
elem->src = tgt; |
|
elem->id = idr->id; |
|
#ifndef NDEBUG |
|
elem->table = tgt_table; |
|
#endif |
|
ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr, |
|
tgt, tgt_table, column, offset, trav); |
|
} |
|
|
|
static |
|
int32_t flecs_emit_stack_at( |
|
ecs_vec_t *stack, |
|
ecs_id_record_t *idr) |
|
{ |
|
int32_t sp = 0, stack_count = ecs_vec_count(stack); |
|
ecs_table_t **stack_elems = ecs_vec_first(stack); |
|
|
|
for (sp = 0; sp < stack_count; sp ++) { |
|
ecs_table_t *elem = stack_elems[sp]; |
|
if (flecs_id_record_get_table(idr, elem)) { |
|
break; |
|
} |
|
} |
|
|
|
return sp; |
|
} |
|
|
|
static |
|
bool flecs_emit_stack_has( |
|
ecs_vec_t *stack, |
|
ecs_id_record_t *idr) |
|
{ |
|
return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack); |
|
} |
|
|
|
static |
|
void flecs_emit_forward_cached_ids( |
|
ecs_world_t *world, |
|
const ecs_event_record_t *er, |
|
const ecs_event_record_t *er_onset, |
|
const ecs_type_t *emit_ids, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_reachable_cache_t *rc, |
|
ecs_vec_t *reachable_ids, |
|
ecs_vec_t *stack, |
|
ecs_entity_t trav) |
|
{ |
|
ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, |
|
ecs_reachable_elem_t); |
|
int32_t i, count = ecs_vec_count(&rc->ids); |
|
for (i = 0; i < count; i ++) { |
|
ecs_reachable_elem_t *rc_elem = &elems[i]; |
|
const ecs_table_record_t *rc_tr = rc_elem->tr; |
|
ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_record_t *rc_idr = (ecs_id_record_t*)rc_tr->hdr.cache; |
|
ecs_record_t *rc_record = rc_elem->record; |
|
|
|
ecs_assert(rc_idr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(flecs_entities_get(world, rc_elem->src) == |
|
rc_record, ECS_INTERNAL_ERROR, NULL); |
|
ecs_dbg_assert(rc_record->table == rc_elem->table, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (flecs_emit_stack_has(stack, rc_idr)) { |
|
continue; |
|
} |
|
|
|
int32_t rc_offset = ECS_RECORD_TO_ROW(rc_record->row); |
|
flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, |
|
it, table, rc_idr, rc_elem->src, |
|
rc_record, rc_record->table, rc_tr, rc_tr->index, |
|
rc_offset, reachable_ids, trav); |
|
} |
|
} |
|
|
|
static |
|
void flecs_emit_dump_cache( |
|
ecs_world_t *world, |
|
const ecs_vec_t *vec) |
|
{ |
|
ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t); |
|
for (int i = 0; i < ecs_vec_count(vec); i ++) { |
|
ecs_reachable_elem_t *elem = &elems[i]; |
|
char *idstr = ecs_id_str(world, elem->id); |
|
char *estr = ecs_id_str(world, elem->src); |
|
ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", |
|
idstr, (uint32_t)elem->id, |
|
estr, (uint32_t)elem->src, |
|
elem->table); |
|
ecs_os_free(idstr); |
|
ecs_os_free(estr); |
|
} |
|
if (!ecs_vec_count(vec)) { |
|
ecs_dbg_3("- no entries"); |
|
} |
|
} |
|
|
|
static |
|
void flecs_emit_forward_table_up( |
|
ecs_world_t *world, |
|
const ecs_event_record_t *er, |
|
const ecs_event_record_t *er_onset, |
|
const ecs_type_t *emit_ids, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_entity_t tgt, |
|
ecs_table_t *tgt_table, |
|
ecs_record_t *tgt_record, |
|
ecs_id_record_t *tgt_idr, |
|
ecs_vec_t *stack, |
|
ecs_vec_t *reachable_ids) |
|
{ |
|
ecs_allocator_t *a = &world->allocator; |
|
int32_t i, id_count = tgt_table->type.count; |
|
ecs_id_t *ids = tgt_table->type.array; |
|
int32_t offset = ECS_RECORD_TO_ROW(tgt_record->row); |
|
int32_t rc_child_offset = ecs_vec_count(reachable_ids); |
|
int32_t stack_count = ecs_vec_count(stack); |
|
|
|
/* If tgt_idr is out of sync but is not the current id record being updated, |
|
* keep track so that we can update two records for the cost of one. */ |
|
ecs_reachable_cache_t *rc = &tgt_idr->reachable; |
|
bool parent_revalidate = (reachable_ids != &rc->ids) && |
|
(rc->current != rc->generation); |
|
if (parent_revalidate) { |
|
ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t); |
|
} |
|
|
|
if (ecs_should_log_3()) { |
|
char *idstr = ecs_id_str(world, tgt_idr->id); |
|
ecs_dbg_3("forward events from %s", idstr); |
|
ecs_os_free(idstr); |
|
} |
|
ecs_log_push_3(); |
|
|
|
/* Function may have to copy values from overridden components if an IsA |
|
* relationship was added together with other components. */ |
|
ecs_entity_t trav = ECS_PAIR_FIRST(tgt_idr->id); |
|
bool inherit = trav == EcsIsA; |
|
|
|
for (i = 0; i < id_count; i ++) { |
|
ecs_id_t id = ids[i]; |
|
ecs_table_record_t *tgt_tr = &tgt_table->_->records[i]; |
|
ecs_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache; |
|
if (inherit && (idr->flags & EcsIdDontInherit)) { |
|
continue; |
|
} |
|
|
|
/* Id has the same relationship, traverse to find ids for forwarding */ |
|
if (ECS_PAIR_FIRST(id) == trav) { |
|
ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, |
|
ecs_table_t*); |
|
t[0] = tgt_table; |
|
|
|
ecs_reachable_cache_t *idr_rc = &idr->reachable; |
|
if (idr_rc->current == idr_rc->generation) { |
|
/* Cache hit, use cached ids to prevent traversing the same |
|
* hierarchy multiple times. This especially speeds up code |
|
* where (deep) hierarchies are created. */ |
|
if (ecs_should_log_3()) { |
|
char *idstr = ecs_id_str(world, id); |
|
ecs_dbg_3("forward cached for %s", idstr); |
|
ecs_os_free(idstr); |
|
} |
|
ecs_log_push_3(); |
|
flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, |
|
table, idr_rc, reachable_ids, stack, trav); |
|
ecs_log_pop_3(); |
|
} else { |
|
/* Cache is dirty, traverse upwards */ |
|
do { |
|
flecs_emit_forward_up(world, er, er_onset, emit_ids, it, |
|
table, idr, stack, reachable_ids); |
|
if (++i >= id_count) { |
|
break; |
|
} |
|
|
|
id = ids[i]; |
|
if (ECS_PAIR_FIRST(id) != trav) { |
|
break; |
|
} |
|
} while (true); |
|
} |
|
|
|
ecs_vec_remove_last(stack); |
|
continue; |
|
} |
|
|
|
int32_t stack_at = flecs_emit_stack_at(stack, idr); |
|
if (parent_revalidate && (stack_at == (stack_count - 1))) { |
|
/* If parent id record needs to be revalidated, add id */ |
|
ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids, |
|
ecs_reachable_elem_t); |
|
elem->tr = tgt_tr; |
|
elem->record = tgt_record; |
|
elem->src = tgt; |
|
elem->id = idr->id; |
|
#ifndef NDEBUG |
|
elem->table = tgt_table; |
|
#endif |
|
} |
|
|
|
/* Skip id if it's masked by a lower table in the tree */ |
|
if (stack_at != stack_count) { |
|
continue; |
|
} |
|
|
|
flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, |
|
table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, |
|
offset, reachable_ids, trav); |
|
} |
|
|
|
if (parent_revalidate) { |
|
/* If this is not the current cache being updated, but it's marked |
|
* as out of date, use intermediate results to populate cache. */ |
|
int32_t rc_parent_offset = ecs_vec_count(&rc->ids); |
|
|
|
/* Only add ids that were added for this table */ |
|
int32_t count = ecs_vec_count(reachable_ids); |
|
count -= rc_child_offset; |
|
|
|
/* Append ids to any ids that already were added /*/ |
|
if (count) { |
|
ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count); |
|
ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, |
|
ecs_reachable_elem_t, rc_parent_offset); |
|
ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids, |
|
ecs_reachable_elem_t, rc_child_offset); |
|
ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); |
|
} |
|
|
|
rc->current = rc->generation; |
|
|
|
if (ecs_should_log_3()) { |
|
char *idstr = ecs_id_str(world, tgt_idr->id); |
|
ecs_dbg_3("cache revalidated for %s:", idstr); |
|
ecs_os_free(idstr); |
|
flecs_emit_dump_cache(world, &rc->ids); |
|
} |
|
} |
|
|
|
ecs_log_pop_3(); |
|
} |
|
|
|
static |
|
void flecs_emit_forward_up( |
|
ecs_world_t *world, |
|
const ecs_event_record_t *er, |
|
const ecs_event_record_t *er_onset, |
|
const ecs_type_t *emit_ids, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_id_record_t *idr, |
|
ecs_vec_t *stack, |
|
ecs_vec_t *reachable_ids) |
|
{ |
|
ecs_id_t id = idr->id; |
|
ecs_entity_t tgt = ECS_PAIR_SECOND(id); |
|
tgt = flecs_entities_get_generation(world, tgt); |
|
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_record_t *tgt_record = flecs_entities_try(world, tgt); |
|
ecs_table_t *tgt_table; |
|
if (!tgt_record || !(tgt_table = tgt_record->table)) { |
|
return; |
|
} |
|
|
|
flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, |
|
tgt, tgt_table, tgt_record, idr, stack, reachable_ids); |
|
} |
|
|
|
static |
|
void flecs_emit_forward( |
|
ecs_world_t *world, |
|
const ecs_event_record_t *er, |
|
const ecs_event_record_t *er_onset, |
|
const ecs_type_t *emit_ids, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_id_record_t *idr) |
|
{ |
|
ecs_reachable_cache_t *rc = &idr->reachable; |
|
|
|
if (rc->current != rc->generation) { |
|
/* Cache miss, iterate the tree to find ids to forward */ |
|
if (ecs_should_log_3()) { |
|
char *idstr = ecs_id_str(world, idr->id); |
|
ecs_dbg_3("reachable cache miss for %s", idstr); |
|
ecs_os_free(idstr); |
|
} |
|
ecs_log_push_3(); |
|
|
|
ecs_vec_t stack; |
|
ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); |
|
ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); |
|
flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, |
|
idr, &stack, &rc->ids); |
|
it->sources[0] = 0; |
|
ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); |
|
|
|
if (it->event == EcsOnAdd || it->event == EcsOnRemove) { |
|
/* Only OnAdd/OnRemove events can validate top-level cache, which |
|
* is for the id for which the event is emitted. |
|
* The reason for this is that we don't want to validate the cache |
|
* while the administration for the mutated entity isn't up to |
|
* date yet. */ |
|
rc->current = rc->generation; |
|
} |
|
|
|
if (ecs_should_log_3()) { |
|
ecs_dbg_3("cache after rebuild:"); |
|
flecs_emit_dump_cache(world, &rc->ids); |
|
} |
|
|
|
ecs_log_pop_3(); |
|
} else { |
|
/* Cache hit, use cached values instead of walking the tree */ |
|
if (ecs_should_log_3()) { |
|
char *idstr = ecs_id_str(world, idr->id); |
|
ecs_dbg_3("reachable cache hit for %s", idstr); |
|
ecs_os_free(idstr); |
|
flecs_emit_dump_cache(world, &rc->ids); |
|
} |
|
|
|
ecs_entity_t trav = ECS_PAIR_FIRST(idr->id); |
|
ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, |
|
ecs_reachable_elem_t); |
|
int32_t i, count = ecs_vec_count(&rc->ids); |
|
for (i = 0; i < count; i ++) { |
|
ecs_reachable_elem_t *elem = &elems[i]; |
|
const ecs_table_record_t *tr = elem->tr; |
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; |
|
ecs_record_t *r = elem->record; |
|
|
|
ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(flecs_entities_get(world, elem->src) == r, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t offset = ECS_RECORD_TO_ROW(r->row); |
|
flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, |
|
rc_idr, elem->src, r->table, tr->index, offset, trav); |
|
} |
|
} |
|
} |
|
|
|
/* The emit function is responsible for finding and invoking the observers |
|
* matching the emitted event. The function is also capable of forwarding events |
|
* for newly reachable ids (after adding a relationship) and propagating events |
|
* downwards. Both capabilities are not just useful in application logic, but |
|
* are also an important building block for keeping query caches in sync. */ |
|
void flecs_emit( |
|
ecs_world_t *world, |
|
ecs_world_t *stage, |
|
ecs_event_desc_t *desc) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_time_t t = {0}; |
|
bool measure_time = world->flags & EcsWorldMeasureSystemTime; |
|
if (measure_time) { |
|
ecs_time_measure(&t); |
|
} |
|
|
|
const ecs_type_t *ids = desc->ids; |
|
ecs_entity_t event = desc->event; |
|
ecs_table_t *table = desc->table, *other_table = desc->other_table; |
|
int32_t offset = desc->offset; |
|
int32_t i, r, count = desc->count; |
|
ecs_flags32_t table_flags = table->flags; |
|
|
|
/* Deferring cannot be suspended for observers */ |
|
int32_t defer = world->stages[0].defer; |
|
if (defer < 0) { |
|
world->stages[0].defer *= -1; |
|
} |
|
|
|
/* Table events are emitted for internal table operations only, and do not |
|
* provide component data and/or entity ids. */ |
|
bool table_event = desc->flags & EcsEventTableOnly; |
|
if (!count && !table_event) { |
|
/* If no count is provided, forward event for all entities in table */ |
|
count = ecs_table_count(table) - offset; |
|
} |
|
|
|
/* When the NoOnSet flag is provided, no OnSet/UnSet events should be |
|
* generated when new components are inherited. */ |
|
bool no_on_set = desc->flags & EcsEventNoOnSet; |
|
|
|
/* The world event id is used to determine if an observer has already been |
|
* triggered for an event. Observers for multiple components are split up |
|
* into multiple observers for a single component, and this counter is used |
|
* to make sure a multi observer only triggers once, even if multiple of its |
|
* single-component observers trigger. */ |
|
int32_t evtx = ++world->event_id; |
|
|
|
ecs_id_t ids_cache = 0; |
|
void *ptrs_cache = NULL; |
|
ecs_size_t sizes_cache = 0; |
|
int32_t columns_cache = 0; |
|
ecs_entity_t sources_cache = 0; |
|
|
|
ecs_iter_t it = { |
|
.world = stage, |
|
.real_world = world, |
|
.event = event, |
|
.event_cur = evtx, |
|
.table = table, |
|
.field_count = 1, |
|
.ids = &ids_cache, |
|
.ptrs = &ptrs_cache, |
|
.sizes = &sizes_cache, |
|
.columns = &columns_cache, |
|
.sources = &sources_cache, |
|
.other_table = other_table, |
|
.offset = offset, |
|
.count = count, |
|
.param = ECS_CONST_CAST(void*, desc->param), |
|
.flags = desc->flags | EcsIterIsValid |
|
}; |
|
|
|
ecs_observable_t *observable = ecs_get_observable(desc->observable); |
|
ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Event records contain all observers for a specific event. In addition to |
|
* the emitted event, also request data for the Wildcard event (for |
|
* observers subscribing to the wildcard event), OnSet and UnSet events. The |
|
* latter to are used for automatically emitting OnSet/UnSet events for |
|
* inherited components, for example when an IsA relationship is added to an |
|
* entity. This doesn't add much overhead, as fetching records is cheap for |
|
* builtin event types. */ |
|
const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); |
|
const ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard); |
|
const ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet); |
|
const ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet); |
|
|
|
ecs_data_t *storage = NULL; |
|
ecs_column_t *columns = NULL; |
|
if (count) { |
|
storage = &table->data; |
|
columns = storage->columns; |
|
it.entities = ecs_vec_get_t(&storage->entities, ecs_entity_t, offset); |
|
} |
|
|
|
int32_t id_count = ids->count; |
|
ecs_id_t *id_array = ids->array; |
|
|
|
/* If a table has IsA relationships, OnAdd/OnRemove events can trigger |
|
* (un)overriding a component. When a component is overridden its value is |
|
* initialized with the value of the overridden component. */ |
|
bool can_override = count && (table_flags & EcsTableHasIsA) && ( |
|
(event == EcsOnAdd) || (event == EcsOnRemove)); |
|
|
|
/* When a new (traversable) relationship is added (emitting an OnAdd/OnRemove |
|
* event) this will cause the components of the target entity to be |
|
* propagated to the source entity. This makes it possible for observers to |
|
* get notified of any new reachable components though the relationship. */ |
|
bool can_forward = event != EcsOnSet; |
|
|
|
/* Does table has observed entities */ |
|
bool has_observed = table_flags & EcsTableHasTraversable; |
|
|
|
/* When a relationship is removed, the events reachable through that |
|
* relationship should emit UnSet events. This is part of the behavior that |
|
* allows observers to be agnostic of whether a component is inherited. */ |
|
bool can_unset = count && (event == EcsOnRemove) && !no_on_set; |
|
|
|
ecs_event_id_record_t *iders[5] = {0}; |
|
int32_t unset_count = 0; |
|
|
|
if (count && can_forward && has_observed) { |
|
flecs_emit_propagate_invalidate(world, table, offset, count); |
|
} |
|
|
|
repeat_event: |
|
/* This is the core event logic, which is executed for each event. By |
|
* default this is just the event kind from the ecs_event_desc_t struct, but |
|
* can also include the Wildcard and UnSet events. The latter is emitted as |
|
* counterpart to OnSet, for any removed ids associated with data. */ |
|
for (i = 0; i < id_count; i ++) { |
|
/* Emit event for each id passed to the function. In most cases this |
|
* will just be one id, like a component that was added, removed or set. |
|
* In some cases events are emitted for multiple ids. |
|
* |
|
* One example is when an id was added with a "With" property, or |
|
* inheriting from a prefab with overrides. In these cases an entity is |
|
* moved directly to the archetype with the additional components. */ |
|
ecs_id_record_t *idr = NULL; |
|
const ecs_type_info_t *ti = NULL; |
|
ecs_id_t id = id_array[i]; |
|
int32_t ider_i, ider_count = 0; |
|
bool is_pair = ECS_IS_PAIR(id); |
|
void *override_ptr = NULL; |
|
ecs_entity_t base = 0; |
|
|
|
/* Check if this id is a pair of an traversable relationship. If so, we |
|
* may have to forward ids from the pair's target. */ |
|
if ((can_forward && is_pair) || can_override) { |
|
idr = flecs_query_id_record_get(world, id); |
|
ecs_flags32_t idr_flags = idr->flags; |
|
|
|
if (is_pair && (idr_flags & EcsIdTraversable)) { |
|
const ecs_event_record_t *er_fwd = NULL; |
|
if (ECS_PAIR_FIRST(id) == EcsIsA) { |
|
if (event == EcsOnAdd) { |
|
if (!world->stages[0].base) { |
|
/* Adding an IsA relationship can trigger prefab |
|
* instantiation, which can instantiate prefab |
|
* hierarchies for the entity to which the |
|
* relationship was added. */ |
|
ecs_entity_t tgt = ECS_PAIR_SECOND(id); |
|
|
|
/* Setting this value prevents flecs_instantiate |
|
* from being called recursively, in case prefab |
|
* children also have IsA relationships. */ |
|
world->stages[0].base = tgt; |
|
flecs_instantiate(world, tgt, table, offset, count); |
|
world->stages[0].base = 0; |
|
} |
|
|
|
/* Adding an IsA relationship will emit OnSet events for |
|
* any new reachable components. */ |
|
er_fwd = er_onset; |
|
} else if (event == EcsOnRemove) { |
|
/* Vice versa for removing an IsA relationship. */ |
|
er_fwd = er_unset; |
|
} |
|
} |
|
|
|
/* Forward events for components from pair target */ |
|
flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr); |
|
ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (can_override && (!(idr_flags & EcsIdDontInherit))) { |
|
/* Initialize overridden components with value from base */ |
|
ti = idr->type_info; |
|
if (ti) { |
|
ecs_table_record_t *base_tr = NULL; |
|
int32_t base_column = ecs_search_relation(world, table, |
|
0, id, EcsIsA, EcsUp, &base, NULL, &base_tr); |
|
if (base_column != -1) { |
|
/* Base found with component */ |
|
ecs_table_t *base_table = base_tr->hdr.table; |
|
base_column = base_tr->column; |
|
ecs_assert(base_column != -1, ECS_INTERNAL_ERROR, NULL); |
|
ecs_record_t *base_r = flecs_entities_get(world, base); |
|
ecs_assert(base_r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t base_row = ECS_RECORD_TO_ROW(base_r->row); |
|
ecs_vec_t *base_v = &base_table->data.columns[base_column].data; |
|
override_ptr = ecs_vec_get(base_v, ti->size, base_row); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (er) { |
|
/* Get observer sets for id. There can be multiple sets of matching |
|
* observers, in case an observer matches for wildcard ids. For |
|
* example, both observers for (ChildOf, p) and (ChildOf, *) would |
|
* match an event for (ChildOf, p). */ |
|
ider_count = flecs_event_observers_get(er, id, iders); |
|
idr = idr ? idr : flecs_query_id_record_get(world, id); |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (can_unset) { |
|
/* Increase UnSet count in case this is a component (has data). This |
|
* will cause the event loop to be ran again as UnSet event. */ |
|
idr = idr ? idr : flecs_query_id_record_get(world, id); |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
unset_count += (idr->type_info != NULL); |
|
} |
|
|
|
if (!ider_count && !override_ptr) { |
|
/* If nothing more to do for this id, early out */ |
|
continue; |
|
} |
|
|
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (tr == NULL) { |
|
/* When a single batch contains multiple add's for an exclusive |
|
* relationship, it's possible that an id was in the added list |
|
* that is no longer available for the entity. */ |
|
continue; |
|
} |
|
|
|
int32_t column = tr->index, storage_i; |
|
it.columns[0] = column + 1; |
|
it.ptrs[0] = NULL; |
|
it.sizes[0] = 0; |
|
it.event_id = id; |
|
it.ids[0] = id; |
|
|
|
if (count) { |
|
storage_i = tr->column; |
|
if (storage_i != -1) { |
|
/* If this is a component, fetch pointer & size */ |
|
ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_column_t *c = &columns[storage_i]; |
|
ecs_size_t size = c->ti->size; |
|
void *ptr = ecs_vec_get(&c->data, size, offset); |
|
it.sizes[0] = size; |
|
|
|
if (override_ptr) { |
|
if (event == EcsOnAdd) { |
|
/* If this is a new override, initialize the component |
|
* with the value of the overridden component. */ |
|
flecs_override_copy( |
|
world, table, ti, ptr, override_ptr, offset, count); |
|
} else if (er_onset) { |
|
/* If an override was removed, this re-exposes the |
|
* overridden component. Because this causes the actual |
|
* (now inherited) value of the component to change, an |
|
* OnSet event must be emitted for the base component.*/ |
|
ecs_assert(event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL); |
|
ecs_event_id_record_t *iders_set[5] = {0}; |
|
int32_t ider_set_i, ider_set_count = |
|
flecs_event_observers_get(er_onset, id, iders_set); |
|
if (ider_set_count) { |
|
/* Set the source temporarily to the base and base |
|
* component pointer. */ |
|
it.sources[0] = base; |
|
it.ptrs[0] = ptr; |
|
for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { |
|
ecs_event_id_record_t *ider = iders_set[ider_set_i]; |
|
flecs_observers_invoke( |
|
world, &ider->self_up, &it, table, EcsIsA); |
|
ecs_assert(it.event_cur == evtx, |
|
ECS_INTERNAL_ERROR, NULL); |
|
flecs_observers_invoke( |
|
world, &ider->up, &it, table, EcsIsA); |
|
ecs_assert(it.event_cur == evtx, |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
it.sources[0] = 0; |
|
} |
|
} |
|
} |
|
|
|
it.ptrs[0] = ptr; |
|
} else { |
|
if (it.event == EcsUnSet) { |
|
/* Only valid for components, not tags */ |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
/* Actually invoke observers for this event/id */ |
|
for (ider_i = 0; ider_i < ider_count; ider_i ++) { |
|
ecs_event_id_record_t *ider = iders[ider_i]; |
|
flecs_observers_invoke(world, &ider->self, &it, table, 0); |
|
ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); |
|
flecs_observers_invoke(world, &ider->self_up, &it, table, 0); |
|
ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (!ider_count || !count || !has_observed) { |
|
continue; |
|
} |
|
|
|
/* The table->traversable_count value indicates if the table contains any |
|
* entities that are used as targets of traversable relationships. If the |
|
* entity/entities for which the event was generated is used as such a |
|
* target, events must be propagated downwards. */ |
|
ecs_entity_t *entities = it.entities; |
|
it.entities = NULL; |
|
|
|
for (r = 0; r < count; r ++) { |
|
ecs_record_t *record = flecs_entities_get(world, entities[r]); |
|
if (!record) { |
|
/* If the event is emitted after a bulk operation, it's possible |
|
* that it hasn't been populated with entities yet. */ |
|
continue; |
|
} |
|
|
|
ecs_id_record_t *idr_t = record->idr; |
|
if (idr_t) { |
|
/* Entity is used as target in traversable pairs, propagate */ |
|
ecs_entity_t e = entities[r]; |
|
it.sources[0] = e; |
|
flecs_emit_propagate( |
|
world, &it, idr, idr_t, 0, iders, ider_count); |
|
} |
|
} |
|
|
|
it.table = table; |
|
it.other_table = other_table; |
|
it.entities = entities; |
|
it.count = count; |
|
it.offset = offset; |
|
it.sources[0] = 0; |
|
} |
|
|
|
can_override = false; /* Don't override twice */ |
|
can_unset = false; /* Don't unset twice */ |
|
can_forward = false; /* Don't forward twice */ |
|
|
|
if (unset_count && er_unset && (er != er_unset)) { |
|
/* Repeat event loop for UnSet event */ |
|
unset_count = 0; |
|
er = er_unset; |
|
it.event = EcsUnSet; |
|
goto repeat_event; |
|
} |
|
|
|
if (wcer && er != wcer) { |
|
/* Repeat event loop for Wildcard event */ |
|
er = wcer; |
|
it.event = event; |
|
goto repeat_event; |
|
} |
|
|
|
error: |
|
world->stages[0].defer = defer; |
|
|
|
if (measure_time) { |
|
world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); |
|
} |
|
return; |
|
} |
|
|
|
void ecs_emit( |
|
ecs_world_t *stage, |
|
ecs_event_desc_t *desc) |
|
{ |
|
ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!(desc->param && desc->const_param), ECS_INVALID_PARAMETER, |
|
"cannot set param and const_param at the same time"); |
|
|
|
if (desc->entity) { |
|
ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_record_t *r = flecs_entities_get(world, desc->entity); |
|
ecs_table_t *table; |
|
if (!r || !(table = r->table)) { |
|
/* Empty entities can't trigger observers */ |
|
return; |
|
} |
|
desc->table = table; |
|
desc->offset = ECS_RECORD_TO_ROW(r->row); |
|
desc->count = 1; |
|
} |
|
|
|
if (!desc->observable) { |
|
desc->observable = world; |
|
} |
|
|
|
ecs_type_t default_ids = (ecs_type_t){ |
|
.count = 1, |
|
.array = (ecs_id_t[]){ EcsAny } |
|
}; |
|
|
|
if (!desc->ids || !desc->ids->count) { |
|
desc->ids = &default_ids; |
|
} |
|
|
|
if (desc->const_param) { |
|
desc->param = ECS_CONST_CAST(void*, desc->const_param); |
|
desc->const_param = NULL; |
|
} |
|
|
|
flecs_emit(world, stage, desc); |
|
|
|
if (desc->ids == &default_ids) { |
|
desc->ids = NULL; |
|
} |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_enqueue( |
|
ecs_world_t *world, |
|
ecs_event_desc_t *desc) |
|
{ |
|
ecs_check(ecs_is_deferred(world), ECS_INVALID_PARAMETER, |
|
"can't enqueue if not in deferred mode"); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
flecs_enqueue(world, stage, desc); |
|
error: |
|
return; |
|
} |
|
|
|
/** |
|
* @file observer.c |
|
* @brief Observer implementation. |
|
* |
|
* The observer implementation contains functions for creating, deleting and |
|
* invoking observers. The code is split up into single-term observers and |
|
* multi-term observers. Multi-term observers are created from multiple single- |
|
* term observers. |
|
*/ |
|
|
|
#include <stddef.h> |
|
|
|
static |
|
ecs_entity_t flecs_get_observer_event( |
|
ecs_term_t *term, |
|
ecs_entity_t event) |
|
{ |
|
/* If operator is Not, reverse the event */ |
|
if (term->oper == EcsNot) { |
|
if (event == EcsOnAdd) { |
|
event = EcsOnRemove; |
|
} else if (event == EcsOnRemove) { |
|
event = EcsOnAdd; |
|
} |
|
} |
|
|
|
return event; |
|
} |
|
|
|
static |
|
ecs_flags32_t flecs_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; |
|
} |
|
if (e == EcsOnTableFill) { |
|
return EcsIdHasOnTableFill; |
|
} |
|
if (e == EcsOnTableEmpty) { |
|
return EcsIdHasOnTableEmpty; |
|
} |
|
if (e == EcsOnTableCreate) { |
|
return EcsIdHasOnTableCreate; |
|
} |
|
if (e == EcsOnTableDelete) { |
|
return EcsIdHasOnTableDelete; |
|
} |
|
return 0; |
|
} |
|
|
|
static |
|
void flecs_inc_observer_count( |
|
ecs_world_t *world, |
|
ecs_entity_t event, |
|
ecs_event_record_t *evt, |
|
ecs_id_t id, |
|
int32_t value) |
|
{ |
|
ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); |
|
ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t result = idt->observer_count += value; |
|
if (result == 1) { |
|
/* Notify framework that there are observers for the event/id. This |
|
* allows parts of the code to skip event evaluation early */ |
|
flecs_notify_tables(world, id, &(ecs_table_event_t){ |
|
.kind = EcsTableTriggersForId, |
|
.event = event |
|
}); |
|
|
|
ecs_flags32_t flags = flecs_id_flag_for_event(event); |
|
if (flags) { |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (idr) { |
|
idr->flags |= flags; |
|
} |
|
} |
|
} else if (result == 0) { |
|
/* Ditto, but the reverse */ |
|
flecs_notify_tables(world, id, &(ecs_table_event_t){ |
|
.kind = EcsTableNoTriggersForId, |
|
.event = event |
|
}); |
|
|
|
ecs_flags32_t flags = flecs_id_flag_for_event(event); |
|
if (flags) { |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (idr) { |
|
idr->flags &= ~flags; |
|
} |
|
} |
|
|
|
flecs_event_id_record_remove(evt, id); |
|
ecs_os_free(idt); |
|
} |
|
} |
|
|
|
static |
|
void flecs_register_observer_for_id( |
|
ecs_world_t *world, |
|
ecs_observable_t *observable, |
|
ecs_observer_t *observer, |
|
size_t offset) |
|
{ |
|
ecs_id_t term_id = observer->register_id; |
|
ecs_term_t *term = &observer->filter.terms[0]; |
|
ecs_entity_t trav = term->src.trav; |
|
|
|
int i; |
|
for (i = 0; i < observer->event_count; i ++) { |
|
ecs_entity_t event = flecs_get_observer_event( |
|
term, observer->events[i]); |
|
|
|
/* Get observers for event */ |
|
ecs_event_record_t *er = flecs_event_record_ensure(observable, event); |
|
ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Get observers for (component) id for event */ |
|
ecs_event_id_record_t *idt = flecs_event_id_record_ensure( |
|
world, er, term_id); |
|
ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_map_t *observers = ECS_OFFSET(idt, offset); |
|
ecs_map_init_w_params_if(observers, &world->allocators.ptr); |
|
ecs_map_insert_ptr(observers, observer->filter.entity, observer); |
|
|
|
flecs_inc_observer_count(world, event, er, term_id, 1); |
|
if (trav) { |
|
flecs_inc_observer_count(world, event, er, |
|
ecs_pair(trav, EcsWildcard), 1); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_uni_observer_register( |
|
ecs_world_t *world, |
|
ecs_observable_t *observable, |
|
ecs_observer_t *observer) |
|
{ |
|
ecs_term_t *term = &observer->filter.terms[0]; |
|
ecs_flags32_t flags = term->src.flags; |
|
|
|
if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { |
|
flecs_register_observer_for_id(world, observable, observer, |
|
offsetof(ecs_event_id_record_t, self_up)); |
|
} else if (flags & EcsSelf) { |
|
flecs_register_observer_for_id(world, observable, observer, |
|
offsetof(ecs_event_id_record_t, self)); |
|
} else if (flags & EcsUp) { |
|
ecs_assert(term->src.trav != 0, ECS_INTERNAL_ERROR, NULL); |
|
flecs_register_observer_for_id(world, observable, observer, |
|
offsetof(ecs_event_id_record_t, up)); |
|
} |
|
} |
|
|
|
static |
|
void flecs_unregister_observer_for_id( |
|
ecs_world_t *world, |
|
ecs_observable_t *observable, |
|
ecs_observer_t *observer, |
|
size_t offset) |
|
{ |
|
ecs_id_t term_id = observer->register_id; |
|
ecs_term_t *term = &observer->filter.terms[0]; |
|
ecs_entity_t trav = term->src.trav; |
|
|
|
int i; |
|
for (i = 0; i < observer->event_count; i ++) { |
|
ecs_entity_t event = flecs_get_observer_event( |
|
term, observer->events[i]); |
|
|
|
/* Get observers for event */ |
|
ecs_event_record_t *er = flecs_event_record_get(observable, event); |
|
ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Get observers for (component) id */ |
|
ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id); |
|
ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_map_t *id_observers = ECS_OFFSET(idt, offset); |
|
ecs_map_remove(id_observers, observer->filter.entity); |
|
if (!ecs_map_count(id_observers)) { |
|
ecs_map_fini(id_observers); |
|
} |
|
|
|
flecs_inc_observer_count(world, event, er, term_id, -1); |
|
if (trav) { |
|
flecs_inc_observer_count(world, event, er, |
|
ecs_pair(trav, EcsWildcard), -1); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_unregister_observer( |
|
ecs_world_t *world, |
|
ecs_observable_t *observable, |
|
ecs_observer_t *observer) |
|
{ |
|
ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!observer->filter.terms) { |
|
ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); |
|
return; |
|
} |
|
|
|
ecs_term_t *term = &observer->filter.terms[0]; |
|
ecs_flags32_t flags = term->src.flags; |
|
|
|
if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { |
|
flecs_unregister_observer_for_id(world, observable, observer, |
|
offsetof(ecs_event_id_record_t, self_up)); |
|
} else if (flags & EcsSelf) { |
|
flecs_unregister_observer_for_id(world, observable, observer, |
|
offsetof(ecs_event_id_record_t, self)); |
|
} else if (flags & EcsUp) { |
|
flecs_unregister_observer_for_id(world, observable, observer, |
|
offsetof(ecs_event_id_record_t, up)); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_ignore_observer( |
|
ecs_observer_t *observer, |
|
ecs_table_t *table, |
|
ecs_iter_t *it) |
|
{ |
|
ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t *last_event_id = observer->last_event_id; |
|
if (last_event_id && last_event_id[0] == it->event_cur) { |
|
return true; |
|
} |
|
|
|
ecs_flags32_t table_flags = table->flags, filter_flags = observer->filter.flags; |
|
|
|
bool result = (table_flags & EcsTableIsPrefab) && |
|
!(filter_flags & EcsFilterMatchPrefab); |
|
result = result || ((table_flags & EcsTableIsDisabled) && |
|
!(filter_flags & EcsFilterMatchDisabled)); |
|
|
|
return result; |
|
} |
|
|
|
static |
|
bool flecs_is_simple_result( |
|
ecs_iter_t *it) |
|
{ |
|
return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0); |
|
} |
|
|
|
static |
|
void flecs_observer_invoke( |
|
ecs_world_t *world, |
|
ecs_iter_t *it, |
|
ecs_observer_t *observer, |
|
ecs_iter_action_t callback, |
|
int32_t term_index, |
|
bool simple_result) |
|
{ |
|
ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (ecs_should_log_3()) { |
|
char *path = ecs_get_fullpath(world, it->system); |
|
ecs_dbg_3("observer: invoke %s", path); |
|
ecs_os_free(path); |
|
} |
|
|
|
ecs_log_push_3(); |
|
|
|
world->info.observers_ran_frame ++; |
|
|
|
ecs_filter_t *filter = &observer->filter; |
|
ecs_assert(term_index < filter->term_count, ECS_INTERNAL_ERROR, NULL); |
|
ecs_term_t *term = &filter->terms[term_index]; |
|
if (term->oper != EcsNot) { |
|
ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
bool instanced = filter->flags & EcsFilterIsInstanced; |
|
bool match_this = filter->flags & EcsFilterMatchThis; |
|
bool table_only = it->flags & EcsIterTableOnly; |
|
if (match_this && (simple_result || instanced || table_only)) { |
|
callback(it); |
|
} else { |
|
ecs_entity_t observer_src = term->src.id; |
|
if (observer_src && !(term->src.flags & EcsIsEntity)) { |
|
observer_src = 0; |
|
} |
|
|
|
ecs_entity_t *entities = it->entities; |
|
int32_t i, count = it->count; |
|
ecs_entity_t src = it->sources[0]; |
|
it->count = 1; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = entities[i]; |
|
it->entities = &e; |
|
if (!observer_src) { |
|
callback(it); |
|
} else if (observer_src == e) { |
|
ecs_entity_t dummy = 0; |
|
it->entities = &dummy; |
|
if (!src) { |
|
it->sources[0] = e; |
|
} |
|
callback(it); |
|
it->sources[0] = src; |
|
break; |
|
} |
|
} |
|
it->entities = entities; |
|
it->count = count; |
|
} |
|
|
|
ecs_log_pop_3(); |
|
} |
|
|
|
static |
|
void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { |
|
ecs_observer_t *o = it->ctx; |
|
it->ctx = o->ctx; |
|
it->callback = o->callback; |
|
|
|
if (ecs_should_log_3()) { |
|
char *path = ecs_get_fullpath(it->world, it->system); |
|
ecs_dbg_3("observer %s", path); |
|
ecs_os_free(path); |
|
} |
|
|
|
ecs_log_push_3(); |
|
flecs_observer_invoke(it->real_world, it, o, o->callback, 0, |
|
flecs_is_simple_result(it)); |
|
ecs_log_pop_3(); |
|
} |
|
|
|
static |
|
void flecs_uni_observer_invoke( |
|
ecs_world_t *world, |
|
ecs_observer_t *observer, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_entity_t trav, |
|
bool simple_result) |
|
{ |
|
ecs_filter_t *filter = &observer->filter; |
|
ecs_term_t *term = &filter->terms[0]; |
|
if (flecs_ignore_observer(observer, table, it)) { |
|
return; |
|
} |
|
|
|
ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); |
|
if (trav && term->src.trav != trav) { |
|
return; |
|
} |
|
|
|
bool is_filter = term->inout == EcsInOutNone; |
|
ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); |
|
it->system = observer->filter.entity; |
|
it->ctx = observer->ctx; |
|
it->binding_ctx = observer->binding_ctx; |
|
it->term_index = observer->term_index; |
|
it->query = &observer->filter; |
|
it->terms = observer->filter.terms; |
|
|
|
ecs_entity_t event = it->event; |
|
int32_t event_cur = it->event_cur; |
|
it->event = flecs_get_observer_event(term, event); |
|
|
|
if (observer->run) { |
|
it->next = flecs_default_observer_next_callback; |
|
it->callback = flecs_default_uni_observer_run_callback; |
|
it->ctx = observer; |
|
observer->run(it); |
|
} else { |
|
ecs_iter_action_t callback = observer->callback; |
|
it->callback = callback; |
|
flecs_observer_invoke(world, it, observer, callback, 0, simple_result); |
|
} |
|
|
|
it->event = event; |
|
it->event_cur = event_cur; |
|
} |
|
|
|
void flecs_observers_invoke( |
|
ecs_world_t *world, |
|
ecs_map_t *observers, |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
ecs_entity_t trav) |
|
{ |
|
if (ecs_map_is_init(observers)) { |
|
ecs_table_lock(it->world, table); |
|
|
|
bool simple_result = flecs_is_simple_result(it); |
|
ecs_map_iter_t oit = ecs_map_iter(observers); |
|
while (ecs_map_next(&oit)) { |
|
ecs_observer_t *o = ecs_map_ptr(&oit); |
|
ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL); |
|
flecs_uni_observer_invoke( |
|
world, o, it, table, trav, simple_result); |
|
} |
|
|
|
ecs_table_unlock(it->world, table); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_multi_observer_invoke( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_observer_t *o = it->ctx; |
|
ecs_world_t *world = it->real_world; |
|
|
|
if (o->last_event_id[0] == it->event_cur) { |
|
/* Already handled this event */ |
|
return false; |
|
} |
|
|
|
o->last_event_id[0] = world->event_id; |
|
|
|
ecs_iter_t user_it = *it; |
|
user_it.field_count = o->filter.field_count; |
|
user_it.query = &o->filter; |
|
user_it.terms = o->filter.terms; |
|
user_it.flags = 0; |
|
ECS_BIT_COND(user_it.flags, EcsIterNoData, |
|
ECS_BIT_IS_SET(o->filter.flags, EcsFilterNoData)); |
|
user_it.ids = NULL; |
|
user_it.columns = NULL; |
|
user_it.sources = NULL; |
|
user_it.sizes = NULL; |
|
user_it.ptrs = NULL; |
|
|
|
flecs_iter_init(it->world, &user_it, flecs_iter_cache_all); |
|
user_it.flags |= (it->flags & EcsIterTableOnly); |
|
|
|
ecs_table_t *table = it->table; |
|
ecs_table_t *prev_table = it->other_table; |
|
int32_t pivot_term = it->term_index; |
|
ecs_term_t *term = &o->filter.terms[pivot_term]; |
|
|
|
int32_t column = it->columns[0]; |
|
if (term->oper == EcsNot) { |
|
table = it->other_table; |
|
prev_table = it->table; |
|
} |
|
|
|
if (!table) { |
|
table = &world->store.root; |
|
} |
|
if (!prev_table) { |
|
prev_table = &world->store.root; |
|
} |
|
|
|
if (column < 0) { |
|
column = -column; |
|
} |
|
|
|
user_it.columns[0] = 0; |
|
user_it.columns[pivot_term] = column; |
|
user_it.sources[pivot_term] = it->sources[0]; |
|
user_it.sizes = o->filter.sizes; |
|
|
|
if (flecs_filter_match_table(world, &o->filter, table, user_it.ids, |
|
user_it.columns, user_it.sources, NULL, NULL, false, pivot_term, |
|
user_it.flags)) |
|
{ |
|
/* Monitor observers only invoke when the filter matches for the first |
|
* time with an entity */ |
|
if (o->is_monitor) { |
|
if (flecs_filter_match_table(world, &o->filter, prev_table, |
|
NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags)) |
|
{ |
|
goto done; |
|
} |
|
} |
|
|
|
/* While filter matching needs to be reversed for a Not term, the |
|
* component data must be fetched from the table we got notified for. |
|
* Repeat the matching process for the non-matching table so we get the |
|
* correct column ids and sources, which we need for populate_data */ |
|
if (term->oper == EcsNot) { |
|
flecs_filter_match_table(world, &o->filter, prev_table, user_it.ids, |
|
user_it.columns, user_it.sources, NULL, NULL, false, -1, |
|
user_it.flags | EcsFilterPopulate); |
|
} |
|
|
|
flecs_iter_populate_data(world, &user_it, it->table, it->offset, |
|
it->count, user_it.ptrs); |
|
|
|
user_it.ptrs[pivot_term] = it->ptrs[0]; |
|
user_it.ids[pivot_term] = it->event_id; |
|
user_it.system = o->filter.entity; |
|
user_it.term_index = pivot_term; |
|
user_it.ctx = o->ctx; |
|
user_it.binding_ctx = o->binding_ctx; |
|
user_it.field_count = o->filter.field_count; |
|
user_it.callback = o->callback; |
|
|
|
flecs_iter_validate(&user_it); |
|
ecs_table_lock(it->world, table); |
|
flecs_observer_invoke(world, &user_it, o, o->callback, |
|
pivot_term, flecs_is_simple_result(&user_it)); |
|
ecs_table_unlock(it->world, table); |
|
ecs_iter_fini(&user_it); |
|
return true; |
|
} |
|
|
|
done: |
|
ecs_iter_fini(&user_it); |
|
return false; |
|
} |
|
|
|
bool ecs_observer_default_run_action(ecs_iter_t *it) { |
|
ecs_observer_t *o = it->ctx; |
|
if (o->is_multi) { |
|
return flecs_multi_observer_invoke(it); |
|
} else { |
|
it->ctx = o->ctx; |
|
ecs_table_lock(it->world, it->table); |
|
flecs_observer_invoke(it->real_world, it, o, o->callback, 0, |
|
flecs_is_simple_result(it)); |
|
ecs_table_unlock(it->world, it->table); |
|
return true; |
|
} |
|
} |
|
|
|
static |
|
void flecs_default_multi_observer_run_callback( |
|
ecs_iter_t *it) |
|
{ |
|
flecs_multi_observer_invoke(it); |
|
} |
|
|
|
/* For convenience, so applications can (in theory) use a single run callback |
|
* that uses ecs_iter_next to iterate results */ |
|
bool flecs_default_observer_next_callback(ecs_iter_t *it) { |
|
if (it->interrupted_by) { |
|
return false; |
|
} else { |
|
/* Use interrupted_by to signal the next iteration must return false */ |
|
it->interrupted_by = it->system; |
|
return true; |
|
} |
|
} |
|
|
|
/* Run action for children of multi observer */ |
|
static |
|
void flecs_multi_observer_builtin_run(ecs_iter_t *it) { |
|
ecs_observer_t *observer = it->ctx; |
|
ecs_run_action_t run = observer->run; |
|
|
|
if (run) { |
|
it->next = flecs_default_observer_next_callback; |
|
it->callback = flecs_default_multi_observer_run_callback; |
|
it->interrupted_by = 0; |
|
run(it); |
|
} else { |
|
flecs_multi_observer_invoke(it); |
|
} |
|
} |
|
|
|
static |
|
void flecs_uni_observer_yield_existing( |
|
ecs_world_t *world, |
|
ecs_observer_t *observer) |
|
{ |
|
ecs_iter_action_t callback = observer->callback; |
|
|
|
ecs_defer_begin(world); |
|
|
|
/* If yield existing is enabled, observer for each thing that matches |
|
* the event, if the event is iterable. */ |
|
int i, count = observer->event_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t evt = observer->events[i]; |
|
const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); |
|
if (!iterable) { |
|
continue; |
|
} |
|
|
|
ecs_iter_t it; |
|
iterable->init(world, world, &it, &observer->filter.terms[0]); |
|
it.system = observer->filter.entity; |
|
it.ctx = observer->ctx; |
|
it.binding_ctx = observer->binding_ctx; |
|
it.event = evt; |
|
|
|
ecs_iter_next_action_t next = it.next; |
|
ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); |
|
while (next(&it)) { |
|
it.event_id = it.ids[0]; |
|
it.event_cur = ++ world->event_id; |
|
callback(&it); |
|
} |
|
} |
|
|
|
ecs_defer_end(world); |
|
} |
|
|
|
static |
|
void flecs_multi_observer_yield_existing( |
|
ecs_world_t *world, |
|
ecs_observer_t *observer) |
|
{ |
|
ecs_run_action_t run = observer->run; |
|
if (!run) { |
|
run = flecs_default_multi_observer_run_callback; |
|
} |
|
|
|
ecs_run_aperiodic(world, EcsAperiodicEmptyTables); |
|
|
|
ecs_defer_begin(world); |
|
|
|
int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); |
|
if (pivot_term < 0) { |
|
return; |
|
} |
|
|
|
/* If yield existing is enabled, invoke for each thing that matches |
|
* the event, if the event is iterable. */ |
|
int i, count = observer->event_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t evt = observer->events[i]; |
|
const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); |
|
if (!iterable) { |
|
continue; |
|
} |
|
|
|
ecs_iter_t it; |
|
iterable->init(world, world, &it, &observer->filter.terms[pivot_term]); |
|
it.query = &observer->filter; |
|
it.terms = observer->filter.terms; |
|
it.field_count = 1; |
|
it.term_index = pivot_term; |
|
it.system = observer->filter.entity; |
|
it.ctx = observer; |
|
it.binding_ctx = observer->binding_ctx; |
|
it.event = evt; |
|
|
|
ecs_iter_next_action_t next = it.next; |
|
ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); |
|
while (next(&it)) { |
|
it.event_id = it.ids[0]; |
|
it.event_cur = ++ world->event_id; |
|
run(&it); |
|
} |
|
} |
|
|
|
ecs_defer_end(world); |
|
} |
|
|
|
static |
|
int flecs_uni_observer_init( |
|
ecs_world_t *world, |
|
ecs_observer_t *observer, |
|
const ecs_observer_desc_t *desc) |
|
{ |
|
ecs_term_t *term = &observer->filter.terms[0]; |
|
observer->last_event_id = desc->last_event_id; |
|
if (!observer->last_event_id) { |
|
observer->last_event_id = &observer->last_event_id_storage; |
|
} |
|
observer->register_id = flecs_from_public_id(world, term->id); |
|
term->field_index = desc->term_index; |
|
|
|
if (ecs_id_is_tag(world, term->id)) { |
|
/* If id is a tag, downgrade OnSet/UnSet to OnAdd/OnRemove. */ |
|
int32_t e, count = observer->event_count; |
|
for (e = 0; e < count; e ++) { |
|
if (observer->events[e] == EcsOnSet) { |
|
observer->events[e] = EcsOnAdd; |
|
} else |
|
if (observer->events[e] == EcsUnSet) { |
|
observer->events[e] = EcsOnRemove; |
|
} |
|
} |
|
} |
|
|
|
flecs_uni_observer_register(world, observer->observable, observer); |
|
|
|
if (desc->yield_existing) { |
|
flecs_uni_observer_yield_existing(world, observer); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_multi_observer_init( |
|
ecs_world_t *world, |
|
ecs_observer_t *observer, |
|
const ecs_observer_desc_t *desc) |
|
{ |
|
/* Create last event id for filtering out the same event that arrives from |
|
* more than one term */ |
|
observer->last_event_id = ecs_os_calloc_t(int32_t); |
|
|
|
/* Mark observer as multi observer */ |
|
observer->is_multi = true; |
|
|
|
/* Create a child observer for each term in the filter */ |
|
ecs_filter_t *filter = &observer->filter; |
|
ecs_observer_desc_t child_desc = *desc; |
|
child_desc.last_event_id = observer->last_event_id; |
|
child_desc.run = NULL; |
|
child_desc.callback = flecs_multi_observer_builtin_run; |
|
child_desc.ctx = observer; |
|
child_desc.ctx_free = NULL; |
|
child_desc.filter.expr = NULL; |
|
child_desc.filter.terms_buffer = NULL; |
|
child_desc.filter.terms_buffer_count = 0; |
|
child_desc.binding_ctx = NULL; |
|
child_desc.binding_ctx_free = NULL; |
|
child_desc.yield_existing = false; |
|
ecs_os_zeromem(&child_desc.entity); |
|
ecs_os_zeromem(&child_desc.filter.terms); |
|
ecs_os_memcpy_n(child_desc.events, observer->events, |
|
ecs_entity_t, observer->event_count); |
|
|
|
int i, term_count = filter->term_count; |
|
bool optional_only = filter->flags & EcsFilterMatchThis; |
|
for (i = 0; i < term_count; i ++) { |
|
if (filter->terms[i].oper != EcsOptional) { |
|
if (ecs_term_match_this(&filter->terms[i])) { |
|
optional_only = false; |
|
} |
|
} |
|
} |
|
|
|
if (filter->flags & EcsFilterMatchPrefab) { |
|
child_desc.filter.flags |= EcsFilterMatchPrefab; |
|
} |
|
if (filter->flags & EcsFilterMatchDisabled) { |
|
child_desc.filter.flags |= EcsFilterMatchDisabled; |
|
} |
|
|
|
/* Create observers as children of observer */ |
|
ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity); |
|
|
|
for (i = 0; i < term_count; i ++) { |
|
if (filter->terms[i].src.flags & EcsFilter) { |
|
continue; |
|
} |
|
|
|
ecs_term_t *term = &child_desc.filter.terms[0]; |
|
child_desc.term_index = filter->terms[i].field_index; |
|
*term = filter->terms[i]; |
|
|
|
ecs_oper_kind_t oper = term->oper; |
|
ecs_id_t id = term->id; |
|
|
|
/* AndFrom & OrFrom terms insert multiple observers */ |
|
if (oper == EcsAndFrom || oper == EcsOrFrom) { |
|
const ecs_type_t *type = ecs_get_type(world, id); |
|
int32_t ti, ti_count = type->count; |
|
ecs_id_t *ti_ids = type->array; |
|
|
|
/* Correct operator will be applied when an event occurs, and |
|
* the observer is evaluated on the observer source */ |
|
term->oper = EcsAnd; |
|
for (ti = 0; ti < ti_count; ti ++) { |
|
ecs_id_t ti_id = ti_ids[ti]; |
|
ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); |
|
if (idr->flags & EcsIdDontInherit) { |
|
continue; |
|
} |
|
|
|
term->first.name = NULL; |
|
term->first.id = ti_ids[ti]; |
|
term->id = ti_ids[ti]; |
|
|
|
if (ecs_observer_init(world, &child_desc) == 0) { |
|
goto error; |
|
} |
|
} |
|
continue; |
|
} |
|
|
|
/* Single component observers never use OR */ |
|
if (oper == EcsOr) { |
|
term->oper = EcsAnd; |
|
} |
|
|
|
/* If observer only contains optional terms, match everything */ |
|
if (optional_only) { |
|
term->id = EcsAny; |
|
term->first.id = EcsAny; |
|
term->src.id = EcsThis; |
|
term->src.flags = EcsIsVariable; |
|
term->second.id = 0; |
|
} else if (term->oper == EcsOptional) { |
|
continue; |
|
} |
|
if (ecs_observer_init(world, &child_desc) == 0) { |
|
goto error; |
|
} |
|
|
|
if (optional_only) { |
|
break; |
|
} |
|
} |
|
|
|
ecs_set_scope(world, old_scope); |
|
|
|
if (desc->yield_existing) { |
|
flecs_multi_observer_yield_existing(world, observer); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
ecs_entity_t ecs_observer_init( |
|
ecs_world_t *world, |
|
const ecs_observer_desc_t *desc) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); |
|
|
|
ecs_entity_t entity = desc->entity; |
|
if (!entity) { |
|
entity = ecs_new(world, 0); |
|
} |
|
|
|
EcsPoly *poly = ecs_poly_bind(world, entity, ecs_observer_t); |
|
if (!poly->poly) { |
|
ecs_check(desc->callback != NULL || desc->run != NULL, |
|
ECS_INVALID_OPERATION, NULL); |
|
|
|
ecs_observer_t *observer = ecs_poly_new(ecs_observer_t); |
|
ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); |
|
observer->dtor = (ecs_poly_dtor_t)flecs_observer_fini; |
|
|
|
/* Make writeable copy of filter desc so that we can set name. This will |
|
* make debugging easier, as any error messages related to creating the |
|
* filter will have the name of the observer. */ |
|
ecs_filter_desc_t filter_desc = desc->filter; |
|
filter_desc.entity = entity; |
|
ecs_filter_t *filter = filter_desc.storage = &observer->filter; |
|
*filter = ECS_FILTER_INIT; |
|
|
|
/* Parse filter */ |
|
if (ecs_filter_init(world, &filter_desc) == NULL) { |
|
flecs_observer_fini(observer); |
|
return 0; |
|
} |
|
|
|
/* Observer must have at least one term */ |
|
ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
poly->poly = observer; |
|
|
|
ecs_observable_t *observable = desc->observable; |
|
if (!observable) { |
|
observable = ecs_get_observable(world); |
|
} |
|
|
|
observer->run = desc->run; |
|
observer->callback = desc->callback; |
|
observer->ctx = desc->ctx; |
|
observer->binding_ctx = desc->binding_ctx; |
|
observer->ctx_free = desc->ctx_free; |
|
observer->binding_ctx_free = desc->binding_ctx_free; |
|
observer->term_index = desc->term_index; |
|
observer->observable = observable; |
|
|
|
/* Check if observer is monitor. Monitors are created as multi observers |
|
* since they require pre/post checking of the filter to test if the |
|
* entity is entering/leaving the monitor. */ |
|
int i; |
|
for (i = 0; i < FLECS_EVENT_DESC_MAX; i ++) { |
|
ecs_entity_t event = desc->events[i]; |
|
if (!event) { |
|
break; |
|
} |
|
|
|
if (event == EcsMonitor) { |
|
/* Monitor event must be first and last event */ |
|
ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
observer->events[0] = EcsOnAdd; |
|
observer->events[1] = EcsOnRemove; |
|
observer->event_count ++; |
|
observer->is_monitor = true; |
|
} else { |
|
observer->events[i] = event; |
|
} |
|
|
|
observer->event_count ++; |
|
} |
|
|
|
/* Observer must have at least one event */ |
|
ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
bool multi = false; |
|
|
|
if (filter->term_count == 1 && !desc->last_event_id) { |
|
ecs_term_t *term = &filter->terms[0]; |
|
/* If the filter has a single term but it is a *From operator, we |
|
* need to create a multi observer */ |
|
multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); |
|
|
|
/* An observer with only optional terms is a special case that is |
|
* only handled by multi observers */ |
|
multi |= term->oper == EcsOptional; |
|
} |
|
|
|
if (filter->term_count == 1 && !observer->is_monitor && !multi) { |
|
if (flecs_uni_observer_init(world, observer, desc)) { |
|
goto error; |
|
} |
|
} else { |
|
if (flecs_multi_observer_init(world, observer, desc)) { |
|
goto error; |
|
} |
|
} |
|
|
|
if (ecs_get_name(world, entity)) { |
|
ecs_trace("#[green]observer#[reset] %s created", |
|
ecs_get_name(world, entity)); |
|
} |
|
} else { |
|
ecs_poly_assert(poly->poly, ecs_observer_t); |
|
ecs_observer_t *observer = (ecs_observer_t*)poly->poly; |
|
|
|
if (desc->run) { |
|
observer->run = desc->run; |
|
} |
|
if (desc->callback) { |
|
observer->callback = desc->callback; |
|
} |
|
|
|
if (observer->ctx_free) { |
|
if (observer->ctx && observer->ctx != desc->ctx) { |
|
observer->ctx_free(observer->ctx); |
|
} |
|
} |
|
if (observer->binding_ctx_free) { |
|
if (observer->binding_ctx && observer->binding_ctx != desc->binding_ctx) { |
|
observer->binding_ctx_free(observer->binding_ctx); |
|
} |
|
} |
|
|
|
if (desc->ctx) { |
|
observer->ctx = desc->ctx; |
|
} |
|
if (desc->binding_ctx) { |
|
observer->binding_ctx = desc->binding_ctx; |
|
} |
|
if (desc->ctx_free) { |
|
observer->ctx_free = desc->ctx_free; |
|
} |
|
if (desc->binding_ctx_free) { |
|
observer->binding_ctx_free = desc->binding_ctx_free; |
|
} |
|
} |
|
|
|
ecs_poly_modified(world, entity, ecs_observer_t); |
|
|
|
return entity; |
|
error: |
|
ecs_delete(world, entity); |
|
return 0; |
|
} |
|
|
|
void* ecs_observer_get_ctx( |
|
const ecs_world_t *world, |
|
ecs_entity_t observer) |
|
{ |
|
const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); |
|
if (o) { |
|
ecs_poly_assert(o->poly, ecs_observer_t); |
|
return ((ecs_observer_t*)o->poly)->ctx; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
void* ecs_observer_get_binding_ctx( |
|
const ecs_world_t *world, |
|
ecs_entity_t observer) |
|
{ |
|
const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); |
|
if (o) { |
|
ecs_poly_assert(o->poly, ecs_observer_t); |
|
return ((ecs_observer_t*)o->poly)->binding_ctx; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
void flecs_observer_fini( |
|
ecs_observer_t *observer) |
|
{ |
|
if (observer->is_multi) { |
|
ecs_os_free(observer->last_event_id); |
|
} else { |
|
if (observer->filter.term_count) { |
|
flecs_unregister_observer( |
|
observer->filter.world, observer->observable, observer); |
|
} else { |
|
/* Observer creation failed while creating filter */ |
|
} |
|
} |
|
|
|
/* Cleanup filters */ |
|
ecs_filter_fini(&observer->filter); |
|
|
|
/* Cleanup context */ |
|
if (observer->ctx_free) { |
|
observer->ctx_free(observer->ctx); |
|
} |
|
|
|
if (observer->binding_ctx_free) { |
|
observer->binding_ctx_free(observer->binding_ctx); |
|
} |
|
|
|
ecs_poly_free(observer, ecs_observer_t); |
|
} |
|
|
|
/** |
|
* @file os_api.c |
|
* @brief Operating system abstraction API. |
|
* |
|
* The OS API implements an overridable interface for implementing functions |
|
* that are operating system specific, in addition to a number of hooks which |
|
* allow for customization by the user, like logging. |
|
*/ |
|
|
|
#include <ctype.h> |
|
#include <time.h> |
|
|
|
void ecs_os_api_impl(ecs_os_api_t *api); |
|
|
|
static bool ecs_os_api_initialized = false; |
|
static bool ecs_os_api_initializing = false; |
|
static int ecs_os_api_init_count = 0; |
|
|
|
ecs_os_api_t ecs_os_api = { |
|
.flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, |
|
.log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ |
|
}; |
|
|
|
int64_t ecs_os_api_malloc_count = 0; |
|
int64_t ecs_os_api_realloc_count = 0; |
|
int64_t ecs_os_api_calloc_count = 0; |
|
int64_t ecs_os_api_free_count = 0; |
|
|
|
void ecs_os_set_api( |
|
ecs_os_api_t *os_api) |
|
{ |
|
if (!ecs_os_api_initialized) { |
|
ecs_os_api = *os_api; |
|
ecs_os_api_initialized = true; |
|
} |
|
} |
|
|
|
ecs_os_api_t ecs_os_get_api(void) { |
|
return ecs_os_api; |
|
} |
|
|
|
void ecs_os_init(void) |
|
{ |
|
if (!ecs_os_api_initialized) { |
|
ecs_os_set_api_defaults(); |
|
} |
|
|
|
if (!(ecs_os_api_init_count ++)) { |
|
if (ecs_os_api.init_) { |
|
ecs_os_api.init_(); |
|
} |
|
} |
|
} |
|
|
|
void ecs_os_fini(void) { |
|
if (!--ecs_os_api_init_count) { |
|
if (ecs_os_api.fini_) { |
|
ecs_os_api.fini_(); |
|
} |
|
} |
|
} |
|
|
|
/* Assume every non-glibc Linux target has no execinfo. |
|
This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */ |
|
#if defined(ECS_TARGET_LINUX) && !defined(__GLIBC__) |
|
#define HAVE_EXECINFO 0 |
|
#elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) |
|
#define HAVE_EXECINFO 1 |
|
#else |
|
#define HAVE_EXECINFO 0 |
|
#endif |
|
|
|
#if HAVE_EXECINFO |
|
#include <execinfo.h> |
|
#define ECS_BT_BUF_SIZE 100 |
|
|
|
void flecs_dump_backtrace( |
|
void *stream) |
|
{ |
|
int nptrs; |
|
void *buffer[ECS_BT_BUF_SIZE]; |
|
char **strings; |
|
|
|
nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); |
|
|
|
strings = backtrace_symbols(buffer, nptrs); |
|
if (strings == NULL) { |
|
return; |
|
} |
|
|
|
for (int j = 1; j < nptrs; j++) { |
|
fprintf(stream, "%s\n", strings[j]); |
|
} |
|
|
|
free(strings); |
|
} |
|
#else |
|
void flecs_dump_backtrace( |
|
void *stream) |
|
{ |
|
(void)stream; |
|
} |
|
#endif |
|
#undef HAVE_EXECINFO_H |
|
|
|
static |
|
void flecs_log_msg( |
|
int32_t level, |
|
const char *file, |
|
int32_t line, |
|
const char *msg) |
|
{ |
|
FILE *stream = ecs_os_api.log_out_; |
|
if (!stream) { |
|
stream = stdout; |
|
} |
|
|
|
bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; |
|
bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; |
|
bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; |
|
|
|
time_t now = 0; |
|
|
|
if (deltatime) { |
|
now = time(NULL); |
|
int64_t delta = 0; |
|
if (ecs_os_api.log_last_timestamp_) { |
|
delta = now - ecs_os_api.log_last_timestamp_; |
|
} |
|
ecs_os_api.log_last_timestamp_ = (int64_t)now; |
|
|
|
if (delta) { |
|
if (delta < 10) { |
|
fputs(" ", stream); |
|
} |
|
if (delta < 100) { |
|
fputs(" ", stream); |
|
} |
|
char time_buf[20]; |
|
ecs_os_sprintf(time_buf, "%u", (uint32_t)delta); |
|
fputs("+", stream); |
|
fputs(time_buf, stream); |
|
fputs(" ", stream); |
|
} else { |
|
fputs(" ", stream); |
|
} |
|
} |
|
|
|
if (timestamp) { |
|
if (!now) { |
|
now = time(NULL); |
|
} |
|
char time_buf[20]; |
|
ecs_os_sprintf(time_buf, "%u", (uint32_t)now); |
|
fputs(time_buf, stream); |
|
fputs(" ", stream); |
|
} |
|
|
|
if (level >= 4) { |
|
if (use_colors) fputs(ECS_NORMAL, stream); |
|
fputs("jrnl", stream); |
|
} else if (level >= 0) { |
|
if (level == 0) { |
|
if (use_colors) fputs(ECS_MAGENTA, stream); |
|
} else { |
|
if (use_colors) fputs(ECS_GREY, stream); |
|
} |
|
fputs("info", stream); |
|
} else if (level == -2) { |
|
if (use_colors) fputs(ECS_YELLOW, stream); |
|
fputs("warning", stream); |
|
} else if (level == -3) { |
|
if (use_colors) fputs(ECS_RED, stream); |
|
fputs("error", stream); |
|
} else if (level == -4) { |
|
if (use_colors) fputs(ECS_RED, stream); |
|
fputs("fatal", stream); |
|
} |
|
|
|
if (use_colors) fputs(ECS_NORMAL, stream); |
|
fputs(": ", stream); |
|
|
|
if (level >= 0) { |
|
if (ecs_os_api.log_indent_) { |
|
char indent[32]; |
|
int i, indent_count = ecs_os_api.log_indent_; |
|
if (indent_count > 15) indent_count = 15; |
|
|
|
for (i = 0; i < indent_count; i ++) { |
|
indent[i * 2] = '|'; |
|
indent[i * 2 + 1] = ' '; |
|
} |
|
|
|
if (ecs_os_api.log_indent_ != indent_count) { |
|
indent[i * 2 - 2] = '+'; |
|
} |
|
|
|
indent[i * 2] = '\0'; |
|
|
|
fputs(indent, stream); |
|
} |
|
} |
|
|
|
if (level < 0) { |
|
if (file) { |
|
const char *file_ptr = strrchr(file, '/'); |
|
if (!file_ptr) { |
|
file_ptr = strrchr(file, '\\'); |
|
} |
|
|
|
if (file_ptr) { |
|
file = file_ptr + 1; |
|
} |
|
|
|
fputs(file, stream); |
|
fputs(": ", stream); |
|
} |
|
|
|
if (line) { |
|
fprintf(stream, "%d: ", line); |
|
} |
|
} |
|
|
|
fputs(msg, stream); |
|
|
|
fputs("\n", stream); |
|
|
|
if (level == -4) { |
|
flecs_dump_backtrace(stream); |
|
} |
|
} |
|
|
|
void ecs_os_dbg( |
|
const char *file, |
|
int32_t line, |
|
const char *msg) |
|
{ |
|
if (ecs_os_api.log_) { |
|
ecs_os_api.log_(1, file, line, msg); |
|
} |
|
} |
|
|
|
void ecs_os_trace( |
|
const char *file, |
|
int32_t line, |
|
const char *msg) |
|
{ |
|
if (ecs_os_api.log_) { |
|
ecs_os_api.log_(0, file, line, msg); |
|
} |
|
} |
|
|
|
void ecs_os_warn( |
|
const char *file, |
|
int32_t line, |
|
const char *msg) |
|
{ |
|
if (ecs_os_api.log_) { |
|
ecs_os_api.log_(-2, file, line, msg); |
|
} |
|
} |
|
|
|
void ecs_os_err( |
|
const char *file, |
|
int32_t line, |
|
const char *msg) |
|
{ |
|
if (ecs_os_api.log_) { |
|
ecs_os_api.log_(-3, file, line, msg); |
|
} |
|
} |
|
|
|
void ecs_os_fatal( |
|
const char *file, |
|
int32_t line, |
|
const char *msg) |
|
{ |
|
if (ecs_os_api.log_) { |
|
ecs_os_api.log_(-4, file, line, msg); |
|
} |
|
} |
|
|
|
static |
|
void ecs_os_gettime(ecs_time_t *time) { |
|
ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); |
|
|
|
uint64_t now = ecs_os_now(); |
|
uint64_t sec = now / 1000000000; |
|
|
|
assert(sec < UINT32_MAX); |
|
assert((now - sec * 1000000000) < UINT32_MAX); |
|
|
|
time->sec = (uint32_t)sec; |
|
time->nanosec = (uint32_t)(now - sec * 1000000000); |
|
} |
|
|
|
static |
|
void* ecs_os_api_malloc(ecs_size_t size) { |
|
ecs_os_linc(&ecs_os_api_malloc_count); |
|
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); |
|
return malloc((size_t)size); |
|
} |
|
|
|
static |
|
void* ecs_os_api_calloc(ecs_size_t size) { |
|
ecs_os_linc(&ecs_os_api_calloc_count); |
|
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); |
|
return calloc(1, (size_t)size); |
|
} |
|
|
|
static |
|
void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { |
|
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (ptr) { |
|
ecs_os_linc(&ecs_os_api_realloc_count); |
|
} else { |
|
/* If not actually reallocing, treat as malloc */ |
|
ecs_os_linc(&ecs_os_api_malloc_count); |
|
} |
|
|
|
return realloc(ptr, (size_t)size); |
|
} |
|
|
|
static |
|
void ecs_os_api_free(void *ptr) { |
|
if (ptr) { |
|
ecs_os_linc(&ecs_os_api_free_count); |
|
} |
|
free(ptr); |
|
} |
|
|
|
static |
|
char* ecs_os_api_strdup(const char *str) { |
|
if (str) { |
|
int len = ecs_os_strlen(str); |
|
char *result = ecs_os_malloc(len + 1); |
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
ecs_os_strcpy(result, str); |
|
return result; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
void ecs_os_strset(char **str, const char *value) { |
|
char *old = str[0]; |
|
str[0] = ecs_os_strdup(value); |
|
ecs_os_free(old); |
|
} |
|
|
|
/* Replace dots with underscores */ |
|
static |
|
char *module_file_base(const char *module, char sep) { |
|
char *base = ecs_os_strdup(module); |
|
ecs_size_t i, len = ecs_os_strlen(base); |
|
for (i = 0; i < len; i ++) { |
|
if (base[i] == '.') { |
|
base[i] = sep; |
|
} |
|
} |
|
|
|
return base; |
|
} |
|
|
|
static |
|
char* ecs_os_api_module_to_dl(const char *module) { |
|
ecs_strbuf_t lib = ECS_STRBUF_INIT; |
|
|
|
/* Best guess, use module name with underscores + OS library extension */ |
|
char *file_base = module_file_base(module, '_'); |
|
|
|
# if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) |
|
ecs_strbuf_appendlit(&lib, "lib"); |
|
ecs_strbuf_appendstr(&lib, file_base); |
|
ecs_strbuf_appendlit(&lib, ".so"); |
|
# elif defined(ECS_TARGET_DARWIN) |
|
ecs_strbuf_appendlit(&lib, "lib"); |
|
ecs_strbuf_appendstr(&lib, file_base); |
|
ecs_strbuf_appendlit(&lib, ".dylib"); |
|
# elif defined(ECS_TARGET_WINDOWS) |
|
ecs_strbuf_appendstr(&lib, file_base); |
|
ecs_strbuf_appendlit(&lib, ".dll"); |
|
# endif |
|
|
|
ecs_os_free(file_base); |
|
|
|
return ecs_strbuf_get(&lib); |
|
} |
|
|
|
static |
|
char* ecs_os_api_module_to_etc(const char *module) { |
|
ecs_strbuf_t lib = ECS_STRBUF_INIT; |
|
|
|
/* Best guess, use module name with dashes + /etc */ |
|
char *file_base = module_file_base(module, '-'); |
|
|
|
ecs_strbuf_appendstr(&lib, file_base); |
|
ecs_strbuf_appendlit(&lib, "/etc"); |
|
|
|
ecs_os_free(file_base); |
|
|
|
return ecs_strbuf_get(&lib); |
|
} |
|
|
|
void ecs_os_set_api_defaults(void) |
|
{ |
|
/* Don't overwrite if already initialized */ |
|
if (ecs_os_api_initialized != 0) { |
|
return; |
|
} |
|
|
|
if (ecs_os_api_initializing != 0) { |
|
return; |
|
} |
|
|
|
ecs_os_api_initializing = true; |
|
|
|
/* Memory management */ |
|
ecs_os_api.malloc_ = ecs_os_api_malloc; |
|
ecs_os_api.free_ = ecs_os_api_free; |
|
ecs_os_api.realloc_ = ecs_os_api_realloc; |
|
ecs_os_api.calloc_ = ecs_os_api_calloc; |
|
|
|
/* Strings */ |
|
ecs_os_api.strdup_ = ecs_os_api_strdup; |
|
|
|
/* Time */ |
|
ecs_os_api.get_time_ = ecs_os_gettime; |
|
|
|
/* Logging */ |
|
ecs_os_api.log_ = flecs_log_msg; |
|
|
|
/* Modules */ |
|
if (!ecs_os_api.module_to_dl_) { |
|
ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; |
|
} |
|
|
|
if (!ecs_os_api.module_to_etc_) { |
|
ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; |
|
} |
|
|
|
ecs_os_api.abort_ = abort; |
|
|
|
# ifdef FLECS_OS_API_IMPL |
|
/* Initialize defaults to OS API IMPL addon, but still allow for overriding |
|
* by the application */ |
|
ecs_set_os_api_impl(); |
|
ecs_os_api_initialized = false; |
|
# endif |
|
|
|
ecs_os_api_initializing = false; |
|
} |
|
|
|
bool ecs_os_has_heap(void) { |
|
return |
|
(ecs_os_api.malloc_ != NULL) && |
|
(ecs_os_api.calloc_ != NULL) && |
|
(ecs_os_api.realloc_ != NULL) && |
|
(ecs_os_api.free_ != NULL); |
|
} |
|
|
|
bool ecs_os_has_threading(void) { |
|
return |
|
(ecs_os_api.mutex_new_ != NULL) && |
|
(ecs_os_api.mutex_free_ != NULL) && |
|
(ecs_os_api.mutex_lock_ != NULL) && |
|
(ecs_os_api.mutex_unlock_ != NULL) && |
|
(ecs_os_api.cond_new_ != NULL) && |
|
(ecs_os_api.cond_free_ != NULL) && |
|
(ecs_os_api.cond_wait_ != NULL) && |
|
(ecs_os_api.cond_signal_ != NULL) && |
|
(ecs_os_api.cond_broadcast_ != NULL) && |
|
(ecs_os_api.thread_new_ != NULL) && |
|
(ecs_os_api.thread_join_ != NULL) && |
|
(ecs_os_api.thread_self_ != NULL); |
|
} |
|
|
|
bool ecs_os_has_task_support(void) { |
|
return |
|
(ecs_os_api.mutex_new_ != NULL) && |
|
(ecs_os_api.mutex_free_ != NULL) && |
|
(ecs_os_api.mutex_lock_ != NULL) && |
|
(ecs_os_api.mutex_unlock_ != NULL) && |
|
(ecs_os_api.cond_new_ != NULL) && |
|
(ecs_os_api.cond_free_ != NULL) && |
|
(ecs_os_api.cond_wait_ != NULL) && |
|
(ecs_os_api.cond_signal_ != NULL) && |
|
(ecs_os_api.cond_broadcast_ != NULL) && |
|
(ecs_os_api.task_new_ != NULL) && |
|
(ecs_os_api.task_join_ != NULL); |
|
} |
|
|
|
bool ecs_os_has_time(void) { |
|
return |
|
(ecs_os_api.get_time_ != NULL) && |
|
(ecs_os_api.sleep_ != NULL) && |
|
(ecs_os_api.now_ != NULL); |
|
} |
|
|
|
bool ecs_os_has_logging(void) { |
|
return (ecs_os_api.log_ != NULL); |
|
} |
|
|
|
bool ecs_os_has_dl(void) { |
|
return |
|
(ecs_os_api.dlopen_ != NULL) && |
|
(ecs_os_api.dlproc_ != NULL) && |
|
(ecs_os_api.dlclose_ != NULL); |
|
} |
|
|
|
bool ecs_os_has_modules(void) { |
|
return |
|
(ecs_os_api.module_to_dl_ != NULL) && |
|
(ecs_os_api.module_to_etc_ != NULL); |
|
} |
|
|
|
#if defined(ECS_TARGET_WINDOWS) |
|
static char error_str[255]; |
|
#endif |
|
|
|
const char* ecs_os_strerror(int err) { |
|
# if defined(ECS_TARGET_WINDOWS) |
|
strerror_s(error_str, 255, err); |
|
return error_str; |
|
# else |
|
return strerror(err); |
|
# endif |
|
} |
|
|
|
/** |
|
* @file poly.c |
|
* @brief Functions for managing poly objects. |
|
* |
|
* The poly framework makes it possible to generalize common functionality for |
|
* different kinds of API objects, as well as improved type safety checks. Poly |
|
* objects have a header that identifiers what kind of object it is. This can |
|
* then be used to discover a set of "mixins" implemented by the type. |
|
* |
|
* Mixins are like a vtable, but for members. Each type populates the table with |
|
* offsets to the members that correspond with the mixin. If an entry in the |
|
* mixin table is not set, the type does not support the mixin. |
|
* |
|
* An example is the Iterable mixin, which makes it possible to create an |
|
* iterator for any poly object (like filters, queries, the world) that |
|
* implements the Iterable mixin. |
|
*/ |
|
|
|
|
|
static const char* mixin_kind_str[] = { |
|
[EcsMixinWorld] = "world", |
|
[EcsMixinEntity] = "entity", |
|
[EcsMixinObservable] = "observable", |
|
[EcsMixinIterable] = "iterable", |
|
[EcsMixinDtor] = "dtor", |
|
[EcsMixinMax] = "max (should never be requested by application)" |
|
}; |
|
|
|
ecs_mixins_t ecs_world_t_mixins = { |
|
.type_name = "ecs_world_t", |
|
.elems = { |
|
[EcsMixinWorld] = offsetof(ecs_world_t, self), |
|
[EcsMixinObservable] = offsetof(ecs_world_t, observable), |
|
[EcsMixinIterable] = offsetof(ecs_world_t, iterable) |
|
} |
|
}; |
|
|
|
ecs_mixins_t ecs_stage_t_mixins = { |
|
.type_name = "ecs_stage_t", |
|
.elems = { |
|
[EcsMixinWorld] = offsetof(ecs_stage_t, world) |
|
} |
|
}; |
|
|
|
ecs_mixins_t ecs_query_t_mixins = { |
|
.type_name = "ecs_query_t", |
|
.elems = { |
|
[EcsMixinWorld] = offsetof(ecs_query_t, filter.world), |
|
[EcsMixinEntity] = offsetof(ecs_query_t, filter.entity), |
|
[EcsMixinIterable] = offsetof(ecs_query_t, iterable), |
|
[EcsMixinDtor] = offsetof(ecs_query_t, dtor) |
|
} |
|
}; |
|
|
|
ecs_mixins_t ecs_observer_t_mixins = { |
|
.type_name = "ecs_observer_t", |
|
.elems = { |
|
[EcsMixinWorld] = offsetof(ecs_observer_t, filter.world), |
|
[EcsMixinEntity] = offsetof(ecs_observer_t, filter.entity), |
|
[EcsMixinDtor] = offsetof(ecs_observer_t, dtor) |
|
} |
|
}; |
|
|
|
ecs_mixins_t ecs_filter_t_mixins = { |
|
.type_name = "ecs_filter_t", |
|
.elems = { |
|
[EcsMixinWorld] = offsetof(ecs_filter_t, world), |
|
[EcsMixinEntity] = offsetof(ecs_filter_t, entity), |
|
[EcsMixinIterable] = offsetof(ecs_filter_t, iterable), |
|
[EcsMixinDtor] = offsetof(ecs_filter_t, dtor) |
|
} |
|
}; |
|
|
|
static |
|
void* assert_mixin( |
|
const ecs_poly_t *poly, |
|
ecs_mixin_kind_t kind) |
|
{ |
|
ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const ecs_header_t *hdr = poly; |
|
ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const ecs_mixins_t *mixins = hdr->mixins; |
|
ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_size_t offset = mixins->elems[kind]; |
|
ecs_assert(offset != 0, ECS_INVALID_PARAMETER, |
|
"mixin %s not available for type %s", |
|
mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); |
|
(void)mixin_kind_str; |
|
|
|
/* Object has mixin, return its address */ |
|
return ECS_OFFSET(hdr, offset); |
|
} |
|
|
|
void* ecs_poly_init_( |
|
ecs_poly_t *poly, |
|
int32_t type, |
|
ecs_size_t size, |
|
ecs_mixins_t *mixins) |
|
{ |
|
ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_header_t *hdr = poly; |
|
ecs_os_memset(poly, 0, size); |
|
|
|
hdr->magic = ECS_OBJECT_MAGIC; |
|
hdr->type = type; |
|
hdr->mixins = mixins; |
|
|
|
return poly; |
|
} |
|
|
|
void ecs_poly_fini_( |
|
ecs_poly_t *poly, |
|
int32_t type) |
|
{ |
|
ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); |
|
(void)type; |
|
|
|
ecs_header_t *hdr = poly; |
|
|
|
/* Don't deinit poly that wasn't initialized */ |
|
ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL); |
|
hdr->magic = 0; |
|
} |
|
|
|
EcsPoly* ecs_poly_bind_( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag) |
|
{ |
|
/* Add tag to the entity for easy querying. This will make it possible to |
|
* query for `Query` instead of `(Poly, Query) */ |
|
if (!ecs_has_id(world, entity, tag)) { |
|
ecs_add_id(world, entity, tag); |
|
} |
|
|
|
/* Never defer creation of a poly object */ |
|
bool deferred = false; |
|
if (ecs_is_deferred(world)) { |
|
deferred = true; |
|
ecs_defer_suspend(world); |
|
} |
|
|
|
/* If this is a new poly, leave the actual creation up to the caller so they |
|
* call tell the difference between a create or an update */ |
|
EcsPoly *result = ecs_get_mut_pair(world, entity, EcsPoly, tag); |
|
|
|
if (deferred) { |
|
ecs_defer_resume(world); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
void ecs_poly_modified_( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag) |
|
{ |
|
ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); |
|
} |
|
|
|
const EcsPoly* ecs_poly_bind_get_( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag) |
|
{ |
|
return ecs_get_pair(world, entity, EcsPoly, tag); |
|
} |
|
|
|
ecs_poly_t* ecs_poly_get_( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t tag) |
|
{ |
|
const EcsPoly *p = ecs_poly_bind_get_(world, entity, tag); |
|
if (p) { |
|
return p->poly; |
|
} |
|
return NULL; |
|
} |
|
|
|
bool ecs_poly_is_( |
|
const ecs_poly_t *poly, |
|
int32_t type) |
|
{ |
|
ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const ecs_header_t *hdr = poly; |
|
ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); |
|
return hdr->type == type; |
|
} |
|
|
|
ecs_iterable_t* ecs_get_iterable( |
|
const ecs_poly_t *poly) |
|
{ |
|
return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); |
|
} |
|
|
|
ecs_observable_t* ecs_get_observable( |
|
const ecs_poly_t *poly) |
|
{ |
|
return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); |
|
} |
|
|
|
const ecs_world_t* ecs_get_world( |
|
const ecs_poly_t *poly) |
|
{ |
|
if (((const ecs_header_t*)poly)->type == ecs_world_t_magic) { |
|
return poly; |
|
} |
|
return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); |
|
} |
|
|
|
ecs_entity_t ecs_get_entity( |
|
const ecs_poly_t *poly) |
|
{ |
|
return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); |
|
} |
|
|
|
ecs_poly_dtor_t* ecs_get_dtor( |
|
const ecs_poly_t *poly) |
|
{ |
|
return (ecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); |
|
} |
|
|
|
/** |
|
* @file query.c |
|
* @brief Cached query implementation. |
|
* |
|
* Cached queries store a list of matched tables. The inputs for a cached query |
|
* are a filter and an observer. The filter is used to initially populate the |
|
* cache, and an observer is used to keep the cacne up to date. |
|
* |
|
* Cached queries additionally support features like sorting and grouping. |
|
* With sorting, an application can iterate over entities that can be sorted by |
|
* a component. Grouping allows an application to group matched tables, which is |
|
* used internally to implement the cascade feature, and can additionally be |
|
* used to implement things like world cells. |
|
*/ |
|
|
|
|
|
static |
|
uint64_t flecs_query_get_group_id( |
|
ecs_query_t *query, |
|
ecs_table_t *table) |
|
{ |
|
if (query->group_by) { |
|
return query->group_by(query->filter.world, table, |
|
query->group_by_id, query->group_by_ctx); |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_compute_group_id( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match) |
|
{ |
|
ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (query->group_by) { |
|
ecs_table_t *table = match->table; |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
match->group_id = flecs_query_get_group_id(query, table); |
|
} else { |
|
match->group_id = 0; |
|
} |
|
} |
|
|
|
static |
|
ecs_query_table_list_t* flecs_query_get_group( |
|
const ecs_query_t *query, |
|
uint64_t group_id) |
|
{ |
|
return ecs_map_get_deref(&query->groups, ecs_query_table_list_t, group_id); |
|
} |
|
|
|
static |
|
ecs_query_table_list_t* flecs_query_ensure_group( |
|
ecs_query_t *query, |
|
uint64_t id) |
|
{ |
|
ecs_query_table_list_t *group = ecs_map_get_deref(&query->groups, |
|
ecs_query_table_list_t, id); |
|
|
|
if (!group) { |
|
group = ecs_map_insert_alloc_t(&query->groups, |
|
ecs_query_table_list_t, id); |
|
ecs_os_zeromem(group); |
|
if (query->on_group_create) { |
|
group->info.ctx = query->on_group_create( |
|
query->filter.world, id, query->group_by_ctx); |
|
} |
|
} |
|
|
|
return group; |
|
} |
|
|
|
static |
|
void flecs_query_remove_group( |
|
ecs_query_t *query, |
|
uint64_t id) |
|
{ |
|
if (query->on_group_delete) { |
|
ecs_query_table_list_t *group = ecs_map_get_deref(&query->groups, |
|
ecs_query_table_list_t, id); |
|
if (group) { |
|
query->on_group_delete(query->filter.world, id, |
|
group->info.ctx, query->group_by_ctx); |
|
} |
|
} |
|
|
|
ecs_map_remove_free(&query->groups, id); |
|
} |
|
|
|
static |
|
uint64_t flecs_query_default_group_by( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t id, |
|
void *ctx) |
|
{ |
|
(void)ctx; |
|
|
|
ecs_id_t match; |
|
if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { |
|
return ecs_pair_second(world, match); |
|
} |
|
return 0; |
|
} |
|
|
|
/* Find the last node of the group after which this group should be inserted */ |
|
static |
|
ecs_query_table_match_t* flecs_query_find_group_insertion_node( |
|
ecs_query_t *query, |
|
uint64_t group_id) |
|
{ |
|
/* Grouping must be enabled */ |
|
ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_map_iter_t it = ecs_map_iter(&query->groups); |
|
ecs_query_table_list_t *list, *closest_list = NULL; |
|
uint64_t id, closest_id = 0; |
|
|
|
bool desc = false; |
|
|
|
if (query->cascade_by) { |
|
desc = (query->filter.terms[ |
|
query->cascade_by - 1].src.flags & EcsDesc) != 0; |
|
} |
|
|
|
/* Find closest smaller group id */ |
|
while (ecs_map_next(&it)) { |
|
id = ecs_map_key(&it); |
|
|
|
if (!desc) { |
|
if (id >= group_id) { |
|
continue; |
|
} |
|
} else { |
|
if (id <= group_id) { |
|
continue; |
|
} |
|
} |
|
|
|
list = ecs_map_ptr(&it); |
|
if (!list->last) { |
|
ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); |
|
continue; |
|
} |
|
|
|
bool comp; |
|
if (!desc) { |
|
comp = ((group_id - id) < (group_id - closest_id)); |
|
} else { |
|
comp = ((group_id - id) > (group_id - closest_id)); |
|
} |
|
|
|
if (!closest_list || comp) { |
|
closest_id = id; |
|
closest_list = list; |
|
} |
|
} |
|
|
|
if (closest_list) { |
|
return closest_list->last; |
|
} else { |
|
return NULL; /* Group should be first in query */ |
|
} |
|
} |
|
|
|
/* Initialize group with first node */ |
|
static |
|
void flecs_query_create_group( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match) |
|
{ |
|
uint64_t group_id = match->group_id; |
|
|
|
/* If query has grouping enabled & this is a new/empty group, find |
|
* the insertion point for the group */ |
|
ecs_query_table_match_t *insert_after = flecs_query_find_group_insertion_node( |
|
query, group_id); |
|
|
|
if (!insert_after) { |
|
/* This group should appear first in the query list */ |
|
ecs_query_table_match_t *query_first = query->list.first; |
|
if (query_first) { |
|
/* If this is not the first match for the query, insert before it */ |
|
match->next = query_first; |
|
query_first->prev = match; |
|
query->list.first = match; |
|
} else { |
|
/* If this is the first match of the query, initialize its list */ |
|
ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL); |
|
query->list.first = match; |
|
query->list.last = match; |
|
} |
|
} else { |
|
ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* This group should appear after another group */ |
|
ecs_query_table_match_t *insert_before = insert_after->next; |
|
match->prev = insert_after; |
|
insert_after->next = match; |
|
match->next = insert_before; |
|
if (insert_before) { |
|
insert_before->prev = match; |
|
} else { |
|
ecs_assert(query->list.last == insert_after, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* This group should appear last in the query list */ |
|
query->list.last = match; |
|
} |
|
} |
|
} |
|
|
|
/* Find the list the node should be part of */ |
|
static |
|
ecs_query_table_list_t* flecs_query_get_node_list( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match) |
|
{ |
|
if (query->group_by) { |
|
return flecs_query_get_group(query, match->group_id); |
|
} else { |
|
return &query->list; |
|
} |
|
} |
|
|
|
/* Find or create the list the node should be part of */ |
|
static |
|
ecs_query_table_list_t* flecs_query_ensure_node_list( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match) |
|
{ |
|
if (query->group_by) { |
|
return flecs_query_ensure_group(query, match->group_id); |
|
} else { |
|
return &query->list; |
|
} |
|
} |
|
|
|
/* Remove node from list */ |
|
static |
|
void flecs_query_remove_table_node( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match) |
|
{ |
|
ecs_query_table_match_t *prev = match->prev; |
|
ecs_query_table_match_t *next = match->next; |
|
|
|
ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_query_table_list_t *list = flecs_query_get_node_list(query, match); |
|
|
|
if (!list || !list->first) { |
|
/* If list contains no matches, the match must be empty */ |
|
ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); |
|
return; |
|
} |
|
|
|
ecs_assert(prev != NULL || query->list.first == match, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(next != NULL || query->list.last == match, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (prev) { |
|
prev->next = next; |
|
} |
|
if (next) { |
|
next->prev = prev; |
|
} |
|
|
|
ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); |
|
list->info.table_count --; |
|
|
|
if (query->group_by) { |
|
uint64_t group_id = match->group_id; |
|
|
|
/* Make sure query.list is updated if this is the first or last group */ |
|
if (query->list.first == match) { |
|
ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); |
|
query->list.first = next; |
|
prev = next; |
|
} |
|
if (query->list.last == match) { |
|
ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); |
|
query->list.last = prev; |
|
next = prev; |
|
} |
|
|
|
ecs_assert(query->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); |
|
query->list.info.table_count --; |
|
list->info.match_count ++; |
|
|
|
/* Make sure group list only contains nodes that belong to the group */ |
|
if (prev && prev->group_id != group_id) { |
|
/* The previous node belonged to another group */ |
|
prev = next; |
|
} |
|
if (next && next->group_id != group_id) { |
|
/* The next node belonged to another group */ |
|
next = prev; |
|
} |
|
|
|
/* Do check again, in case both prev & next belonged to another group */ |
|
if ((!prev && !next) || (prev && prev->group_id != group_id)) { |
|
/* There are no more matches left in this group */ |
|
flecs_query_remove_group(query, group_id); |
|
list = NULL; |
|
} |
|
} |
|
|
|
if (list) { |
|
if (list->first == match) { |
|
list->first = next; |
|
} |
|
if (list->last == match) { |
|
list->last = prev; |
|
} |
|
} |
|
|
|
match->prev = NULL; |
|
match->next = NULL; |
|
|
|
query->match_count ++; |
|
} |
|
|
|
/* Add node to list */ |
|
static |
|
void flecs_query_insert_table_node( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match) |
|
{ |
|
/* Node should not be part of an existing list */ |
|
ecs_assert(match->prev == NULL && match->next == NULL, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* If this is the first match, activate system */ |
|
if (!query->list.first && query->filter.entity) { |
|
ecs_remove_id(query->filter.world, query->filter.entity, EcsEmpty); |
|
} |
|
|
|
flecs_query_compute_group_id(query, match); |
|
|
|
ecs_query_table_list_t *list = flecs_query_ensure_node_list(query, match); |
|
if (list->last) { |
|
ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_query_table_match_t *last = list->last; |
|
ecs_query_table_match_t *last_next = last->next; |
|
|
|
match->prev = last; |
|
match->next = last_next; |
|
last->next = match; |
|
|
|
if (last_next) { |
|
last_next->prev = match; |
|
} |
|
|
|
list->last = match; |
|
|
|
if (query->group_by) { |
|
/* Make sure to update query list if this is the last group */ |
|
if (query->list.last == last) { |
|
query->list.last = match; |
|
} |
|
} |
|
} else { |
|
ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
list->first = match; |
|
list->last = match; |
|
|
|
if (query->group_by) { |
|
/* Initialize group with its first node */ |
|
flecs_query_create_group(query, match); |
|
} |
|
} |
|
|
|
if (query->group_by) { |
|
list->info.table_count ++; |
|
list->info.match_count ++; |
|
} |
|
|
|
query->list.info.table_count ++; |
|
query->match_count ++; |
|
|
|
ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(query->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(query->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
static |
|
ecs_query_table_match_t* flecs_query_cache_add( |
|
ecs_world_t *world, |
|
ecs_query_table_t *elem) |
|
{ |
|
ecs_query_table_match_t *result = |
|
flecs_bcalloc(&world->allocators.query_table_match); |
|
|
|
if (!elem->first) { |
|
elem->first = result; |
|
elem->last = result; |
|
} else { |
|
ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); |
|
elem->last->next_match = result; |
|
elem->last = result; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
typedef struct { |
|
ecs_table_t *table; |
|
int32_t column; |
|
} flecs_table_column_t; |
|
|
|
static |
|
void flecs_query_get_column_for_term( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match, |
|
int32_t t, |
|
flecs_table_column_t *out) |
|
{ |
|
const ecs_filter_t *filter = &query->filter; |
|
ecs_world_t *world = filter->world; |
|
ecs_term_t *term = &filter->terms[t]; |
|
int32_t field = term->field_index; |
|
ecs_entity_t src = match->sources[field]; |
|
ecs_table_t *table = NULL; |
|
int32_t column = -1; |
|
|
|
if (term->oper != EcsNot) { |
|
if (!src) { |
|
if (term->src.flags != EcsIsEntity) { |
|
table = match->table; |
|
column = match->storage_columns[field]; |
|
if (column == -2) { |
|
/* Shared field */ |
|
column = -1; |
|
} |
|
} |
|
} else { |
|
table = ecs_get_table(world, src); |
|
if (ecs_term_match_this(term)) { |
|
int32_t ref_index = -match->columns[field] - 1; |
|
ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); |
|
if (ref->id != 0) { |
|
ecs_ref_update(world, ref); |
|
column = ref->tr->column; |
|
} |
|
} else { |
|
column = -(match->columns[field] + 1); |
|
} |
|
} |
|
} |
|
|
|
out->table = table; |
|
out->column = column; |
|
} |
|
|
|
/* Get match monitor. Monitors are used to keep track of whether components |
|
* matched by the query in a table have changed. */ |
|
static |
|
bool flecs_query_get_match_monitor( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match) |
|
{ |
|
if (match->monitor) { |
|
return false; |
|
} |
|
|
|
int32_t *monitor = flecs_balloc(&query->allocators.monitors); |
|
monitor[0] = 0; |
|
|
|
/* Mark terms that don't need to be monitored. This saves time when reading |
|
* and/or updating the monitor. */ |
|
const ecs_filter_t *f = &query->filter; |
|
int32_t i, field = -1, term_count = f->term_count; |
|
flecs_table_column_t tc; |
|
|
|
for (i = 0; i < term_count; i ++) { |
|
if (field == f->terms[i].field_index) { |
|
if (monitor[field + 1] != -1) { |
|
continue; |
|
} |
|
} |
|
|
|
field = f->terms[i].field_index; |
|
monitor[field + 1] = -1; |
|
|
|
if (f->terms[i].inout != EcsIn && |
|
f->terms[i].inout != EcsInOut && |
|
f->terms[i].inout != EcsInOutDefault) { |
|
continue; /* If term isn't read, don't monitor */ |
|
} |
|
|
|
int32_t column = match->columns[field]; |
|
if (column == 0) { |
|
continue; /* Don't track terms that aren't matched */ |
|
} |
|
|
|
flecs_query_get_column_for_term(query, match, i, &tc); |
|
if (tc.column == -1) { |
|
continue; /* Don't track terms that aren't stored */ |
|
} |
|
|
|
monitor[field + 1] = 0; |
|
} |
|
|
|
/* If matched table needs entity filter, make sure to test fields that could |
|
* be matched by flattened parents. */ |
|
ecs_entity_filter_t *ef = match->entity_filter; |
|
if (ef && ef->flat_tree_column != -1) { |
|
int32_t *fields = ecs_vec_first(&ef->ft_terms); |
|
int32_t field_count = ecs_vec_count(&ef->ft_terms); |
|
for (i = 0; i < field_count; i ++) { |
|
monitor[fields[i] + 1] = 0; |
|
} |
|
} |
|
|
|
match->monitor = monitor; |
|
|
|
query->flags |= EcsQueryHasMonitor; |
|
|
|
return true; |
|
} |
|
|
|
/* Synchronize match monitor with table dirty state */ |
|
static |
|
void flecs_query_sync_match_monitor( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match) |
|
{ |
|
ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!match->monitor) { |
|
if (query->flags & EcsQueryHasMonitor) { |
|
flecs_query_get_match_monitor(query, match); |
|
} else { |
|
return; |
|
} |
|
} |
|
|
|
int32_t *monitor = match->monitor; |
|
ecs_table_t *table = match->table; |
|
if (table) { |
|
int32_t *dirty_state = flecs_table_get_dirty_state( |
|
query->filter.world, table); |
|
ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); |
|
monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ |
|
} |
|
|
|
ecs_filter_t *filter = &query->filter; |
|
{ |
|
flecs_table_column_t tc; |
|
int32_t t, term_count = filter->term_count; |
|
for (t = 0; t < term_count; t ++) { |
|
int32_t field = filter->terms[t].field_index; |
|
if (monitor[field + 1] == -1) { |
|
continue; |
|
} |
|
|
|
flecs_query_get_column_for_term(query, match, t, &tc); |
|
|
|
monitor[field + 1] = flecs_table_get_dirty_state( |
|
filter->world, tc.table)[tc.column + 1]; |
|
} |
|
} |
|
|
|
ecs_entity_filter_t *ef = match->entity_filter; |
|
if (ef && ef->flat_tree_column != -1) { |
|
flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); |
|
int32_t f, field_count = ecs_vec_count(&ef->ft_terms); |
|
for (f = 0; f < field_count; f ++) { |
|
flecs_flat_table_term_t *field = &fields[f]; |
|
flecs_flat_monitor_t *tgt_mon = ecs_vec_first(&field->monitor); |
|
int32_t tgt, tgt_count = ecs_vec_count(&field->monitor); |
|
for (tgt = 0; tgt < tgt_count; tgt ++) { |
|
tgt_mon[tgt].monitor = tgt_mon[tgt].table_state; |
|
} |
|
} |
|
} |
|
|
|
query->prev_match_count = query->match_count; |
|
} |
|
|
|
/* Check if single match term has changed */ |
|
static |
|
bool flecs_query_check_match_monitor_term( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match, |
|
int32_t term) |
|
{ |
|
ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (flecs_query_get_match_monitor(query, match)) { |
|
return true; |
|
} |
|
|
|
int32_t *monitor = match->monitor; |
|
int32_t state = monitor[term]; |
|
if (state == -1) { |
|
return false; |
|
} |
|
|
|
ecs_table_t *table = match->table; |
|
if (table) { |
|
int32_t *dirty_state = flecs_table_get_dirty_state( |
|
query->filter.world, table); |
|
ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!term) { |
|
return monitor[0] != dirty_state[0]; |
|
} |
|
} else if (!term) { |
|
return false; |
|
} |
|
|
|
flecs_table_column_t cur; |
|
flecs_query_get_column_for_term(query, match, term - 1, &cur); |
|
ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return monitor[term] != flecs_table_get_dirty_state( |
|
query->filter.world, cur.table)[cur.column + 1]; |
|
} |
|
|
|
/* Check if any term for match has changed */ |
|
static |
|
bool flecs_query_check_match_monitor( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *match, |
|
const ecs_iter_t *it) |
|
{ |
|
ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (flecs_query_get_match_monitor(query, match)) { |
|
return true; |
|
} |
|
|
|
int32_t *monitor = match->monitor; |
|
ecs_table_t *table = match->table; |
|
int32_t *dirty_state = NULL; |
|
if (table) { |
|
dirty_state = flecs_table_get_dirty_state( |
|
query->filter.world, table); |
|
ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (monitor[0] != dirty_state[0]) { |
|
return true; |
|
} |
|
} |
|
|
|
bool has_flat = false, is_this = false; |
|
const ecs_filter_t *filter = &query->filter; |
|
ecs_world_t *world = filter->world; |
|
int32_t i, j, field_count = filter->field_count; |
|
int32_t *storage_columns = match->storage_columns; |
|
int32_t *columns = it ? it->columns : NULL; |
|
if (!columns) { |
|
columns = match->columns; |
|
} |
|
ecs_vec_t *refs = &match->refs; |
|
for (i = 0; i < field_count; i ++) { |
|
int32_t mon = monitor[i + 1]; |
|
if (mon == -1) { |
|
continue; |
|
} |
|
|
|
int32_t column = storage_columns[i]; |
|
if (columns[i] >= 0) { |
|
/* owned component */ |
|
ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (mon != dirty_state[column + 1]) { |
|
return true; |
|
} |
|
continue; |
|
} else if (column == -1) { |
|
continue; /* owned but not a component */ |
|
} |
|
|
|
column = columns[i]; |
|
if (!column) { |
|
/* Not matched */ |
|
continue; |
|
} |
|
|
|
ecs_assert(column < 0, ECS_INTERNAL_ERROR, NULL); |
|
column = -column; |
|
|
|
/* Find term index from field index, which differ when using || */ |
|
int32_t term_index = i; |
|
if (filter->terms[i].field_index != i) { |
|
for (j = i; j < filter->term_count; j ++) { |
|
if (filter->terms[j].field_index == i) { |
|
term_index = j; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
is_this = ecs_term_match_this(&filter->terms[term_index]); |
|
|
|
/* Flattened fields are encoded by adding field_count to the column |
|
* index of the parent component. */ |
|
if (is_this && it && (column > field_count)) { |
|
has_flat = true; |
|
} else { |
|
if (is_this) { |
|
/* Component reached through traversal from this */ |
|
int32_t ref_index = column - 1; |
|
ecs_ref_t *ref = ecs_vec_get_t(refs, ecs_ref_t, ref_index); |
|
if (ref->id != 0) { |
|
ecs_ref_update(world, ref); |
|
ecs_table_record_t *tr = ref->tr; |
|
ecs_table_t *src_table = tr->hdr.table; |
|
column = tr->index; |
|
column = ecs_table_type_to_column_index(src_table, column); |
|
int32_t *src_dirty_state = flecs_table_get_dirty_state( |
|
world, src_table); |
|
if (mon != src_dirty_state[column + 1]) { |
|
return true; |
|
} |
|
} |
|
} else { |
|
/* Component from static source */ |
|
ecs_entity_t src = match->sources[i]; |
|
ecs_table_t *src_table = ecs_get_table(world, src); |
|
ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
column = ecs_table_type_to_column_index(src_table, column - 1); |
|
int32_t *src_dirty_state = flecs_table_get_dirty_state( |
|
world, src_table); |
|
if (mon != src_dirty_state[column + 1]) { |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (has_flat) { |
|
ecs_entity_filter_t *ef = match->entity_filter; |
|
flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); |
|
ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; |
|
int32_t cur_tgt = ent_it->target_count - 1; |
|
field_count = ecs_vec_count(&ef->ft_terms); |
|
|
|
for (i = 0; i < field_count; i ++) { |
|
flecs_flat_table_term_t *field = &fields[i]; |
|
flecs_flat_monitor_t *fmon = ecs_vec_get_t(&field->monitor, |
|
flecs_flat_monitor_t, cur_tgt); |
|
if (fmon->monitor != fmon->table_state) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/* Check if any term for matched table has changed */ |
|
static |
|
bool flecs_query_check_table_monitor( |
|
ecs_query_t *query, |
|
ecs_query_table_t *table, |
|
int32_t term) |
|
{ |
|
ecs_query_table_match_t *cur, *end = table->last->next; |
|
|
|
for (cur = table->first; cur != end; cur = cur->next) { |
|
ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; |
|
if (term == -1) { |
|
if (flecs_query_check_match_monitor(query, match, NULL)) { |
|
return true; |
|
} |
|
} else { |
|
if (flecs_query_check_match_monitor_term(query, match, term)) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_query_check_query_monitor( |
|
ecs_query_t *query) |
|
{ |
|
ecs_table_cache_iter_t it; |
|
if (flecs_table_cache_iter(&query->cache, &it)) { |
|
ecs_query_table_t *qt; |
|
while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { |
|
if (flecs_query_check_table_monitor(query, qt, -1)) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
void flecs_query_init_query_monitors( |
|
ecs_query_t *query) |
|
{ |
|
ecs_query_table_match_t *cur = query->list.first; |
|
|
|
/* Ensure each match has a monitor */ |
|
for (; cur != NULL; cur = cur->next) { |
|
ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; |
|
flecs_query_get_match_monitor(query, match); |
|
} |
|
} |
|
|
|
/* The group by function for cascade computes the tree depth for the table type. |
|
* This causes tables in the query cache to be ordered by depth, which ensures |
|
* breadth-first iteration order. */ |
|
static |
|
uint64_t flecs_query_group_by_cascade( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t id, |
|
void *ctx) |
|
{ |
|
(void)id; |
|
ecs_term_t *term = ctx; |
|
ecs_entity_t rel = term->src.trav; |
|
int32_t depth = flecs_relation_depth(world, rel, table); |
|
return flecs_ito(uint64_t, depth); |
|
} |
|
|
|
static |
|
void flecs_query_add_ref( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *qm, |
|
ecs_entity_t component, |
|
ecs_entity_t entity, |
|
ecs_size_t size) |
|
{ |
|
ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_ref_t *ref = ecs_vec_append_t(&world->allocator, &qm->refs, ecs_ref_t); |
|
ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (size) { |
|
*ref = ecs_ref_init_id(world, entity, component); |
|
} else { |
|
*ref = (ecs_ref_t){ |
|
.entity = entity, |
|
.id = 0 |
|
}; |
|
} |
|
|
|
query->flags |= EcsQueryHasRefs; |
|
} |
|
|
|
static |
|
ecs_query_table_match_t* flecs_query_add_table_match( |
|
ecs_query_t *query, |
|
ecs_query_table_t *qt, |
|
ecs_table_t *table) |
|
{ |
|
/* Add match for table. One table can have more than one match, if |
|
* the query contains wildcards. */ |
|
ecs_query_table_match_t *qm = flecs_query_cache_add(query->filter.world, qt); |
|
qm->table = table; |
|
|
|
qm->columns = flecs_balloc(&query->allocators.columns); |
|
qm->storage_columns = flecs_balloc(&query->allocators.columns); |
|
qm->ids = flecs_balloc(&query->allocators.ids); |
|
qm->sources = flecs_balloc(&query->allocators.sources); |
|
|
|
/* Insert match to iteration list if table is not empty */ |
|
if (!table || ecs_table_count(table) != 0) { |
|
flecs_query_insert_table_node(query, qm); |
|
} |
|
|
|
return qm; |
|
} |
|
|
|
static |
|
void flecs_query_set_table_match( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *qm, |
|
ecs_table_t *table, |
|
ecs_iter_t *it) |
|
{ |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_filter_t *filter = &query->filter; |
|
int32_t i, term_count = filter->term_count; |
|
int32_t field_count = filter->field_count; |
|
ecs_term_t *terms = filter->terms; |
|
|
|
/* Reset resources in case this is an existing record */ |
|
ecs_vec_reset_t(a, &qm->refs, ecs_ref_t); |
|
ecs_os_memcpy_n(qm->columns, it->columns, int32_t, field_count); |
|
ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); |
|
ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); |
|
|
|
if (table) { |
|
/* Initialize storage columns for faster access to component storage */ |
|
for (i = 0; i < field_count; i ++) { |
|
if (terms[i].inout == EcsInOutNone) { |
|
qm->storage_columns[i] = -1; |
|
continue; |
|
} |
|
|
|
int32_t column = qm->columns[i]; |
|
if (column > 0) { |
|
qm->storage_columns[i] = ecs_table_type_to_column_index(table, |
|
qm->columns[i] - 1); |
|
} else { |
|
/* Shared field (not from table) */ |
|
qm->storage_columns[i] = -2; |
|
} |
|
} |
|
|
|
flecs_entity_filter_init(world, &qm->entity_filter, filter, |
|
table, qm->ids, qm->columns); |
|
|
|
if (qm->entity_filter) { |
|
query->flags &= ~EcsQueryTrivialIter; |
|
} |
|
if (table->flags & EcsTableHasUnion) { |
|
query->flags &= ~EcsQueryTrivialIter; |
|
} |
|
} |
|
|
|
/* Add references for substituted terms */ |
|
for (i = 0; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
if (!ecs_term_match_this(term)) { |
|
/* non-This terms are set during iteration */ |
|
continue; |
|
} |
|
|
|
int32_t field = terms[i].field_index; |
|
ecs_entity_t src = it->sources[field]; |
|
ecs_size_t size = 0; |
|
if (it->sizes) { |
|
size = it->sizes[field]; |
|
} |
|
if (src) { |
|
ecs_id_t id = it->ids[field]; |
|
ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (id) { |
|
flecs_query_add_ref(world, query, qm, id, src, size); |
|
|
|
/* Use column index to bind term and ref */ |
|
if (qm->columns[field] != 0) { |
|
qm->columns[field] = -ecs_vec_count(&qm->refs); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
ecs_query_table_t* flecs_query_table_insert( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
ecs_table_t *table) |
|
{ |
|
ecs_query_table_t *qt = flecs_bcalloc(&world->allocators.query_table); |
|
if (table) { |
|
qt->table_id = table->id; |
|
} else { |
|
qt->table_id = 0; |
|
} |
|
ecs_table_cache_insert(&query->cache, table, &qt->hdr); |
|
return qt; |
|
} |
|
|
|
/** Populate query cache with tables */ |
|
static |
|
void flecs_query_match_tables( |
|
ecs_world_t *world, |
|
ecs_query_t *query) |
|
{ |
|
ecs_table_t *table = NULL; |
|
ecs_query_table_t *qt = NULL; |
|
|
|
ecs_iter_t it = ecs_filter_iter(world, &query->filter); |
|
ECS_BIT_SET(it.flags, EcsIterIsInstanced); |
|
ECS_BIT_SET(it.flags, EcsIterNoData); |
|
ECS_BIT_SET(it.flags, EcsIterTableOnly); |
|
ECS_BIT_SET(it.flags, EcsIterEntityOptional); |
|
|
|
while (ecs_filter_next(&it)) { |
|
if ((table != it.table) || (!it.table && !qt)) { |
|
/* New table matched, add record to cache */ |
|
table = it.table; |
|
qt = flecs_query_table_insert(world, query, table); |
|
} |
|
|
|
ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); |
|
flecs_query_set_table_match(world, query, qm, table, &it); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_query_match_table( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
ecs_table_t *table) |
|
{ |
|
if (!ecs_map_is_init(&query->cache.index)) { |
|
return false; |
|
} |
|
|
|
ecs_query_table_t *qt = NULL; |
|
ecs_filter_t *filter = &query->filter; |
|
int var_id = ecs_filter_find_this_var(filter); |
|
if (var_id == -1) { |
|
/* If query doesn't match with This term, it can't match with tables */ |
|
return false; |
|
} |
|
|
|
ecs_iter_t it = flecs_filter_iter_w_flags(world, filter, EcsIterMatchVar| |
|
EcsIterIsInstanced|EcsIterNoData|EcsIterEntityOptional); |
|
ecs_iter_set_var_as_table(&it, var_id, table); |
|
|
|
while (ecs_filter_next(&it)) { |
|
ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); |
|
if (qt == NULL) { |
|
table = it.table; |
|
qt = flecs_query_table_insert(world, query, table); |
|
} |
|
|
|
ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); |
|
flecs_query_set_table_match(world, query, qm, table, &it); |
|
} |
|
|
|
return qt != NULL; |
|
} |
|
|
|
ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_sort_table_generic, order_by, static) |
|
|
|
static |
|
void flecs_query_sort_table( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t column_index, |
|
ecs_order_by_action_t compare, |
|
ecs_sort_table_action_t sort) |
|
{ |
|
ecs_data_t *data = &table->data; |
|
if (!ecs_vec_count(&data->entities)) { |
|
/* Nothing to sort */ |
|
return; |
|
} |
|
|
|
int32_t count = flecs_table_data_count(data); |
|
if (count < 2) { |
|
return; |
|
} |
|
|
|
ecs_entity_t *entities = ecs_vec_first(&data->entities); |
|
|
|
void *ptr = NULL; |
|
int32_t size = 0; |
|
if (column_index != -1) { |
|
ecs_column_t *column = &data->columns[column_index]; |
|
ecs_type_info_t *ti = column->ti; |
|
size = ti->size; |
|
ptr = ecs_vec_first(&column->data); |
|
} |
|
|
|
if (sort) { |
|
sort(world, table, entities, ptr, size, 0, count - 1, compare); |
|
} else { |
|
flecs_query_sort_table_generic(world, table, entities, ptr, size, 0, count - 1, compare); |
|
} |
|
} |
|
|
|
/* Helper struct for building sorted table ranges */ |
|
typedef struct sort_helper_t { |
|
ecs_query_table_match_t *match; |
|
ecs_entity_t *entities; |
|
const void *ptr; |
|
int32_t row; |
|
int32_t elem_size; |
|
int32_t count; |
|
bool shared; |
|
} sort_helper_t; |
|
|
|
static |
|
const void* ptr_from_helper( |
|
sort_helper_t *helper) |
|
{ |
|
ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); |
|
if (helper->shared) { |
|
return helper->ptr; |
|
} else { |
|
return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); |
|
} |
|
} |
|
|
|
static |
|
ecs_entity_t e_from_helper( |
|
sort_helper_t *helper) |
|
{ |
|
if (helper->row < helper->count) { |
|
return helper->entities[helper->row]; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_build_sorted_table_range( |
|
ecs_query_t *query, |
|
ecs_query_table_list_t *list) |
|
{ |
|
ecs_world_t *world = query->filter.world; |
|
ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, |
|
"cannot sort query in multithreaded mode"); |
|
|
|
ecs_entity_t id = query->order_by_component; |
|
ecs_order_by_action_t compare = query->order_by; |
|
int32_t table_count = list->info.table_count; |
|
if (!table_count) { |
|
return; |
|
} |
|
|
|
ecs_vec_init_if_t(&query->table_slices, ecs_query_table_match_t); |
|
int32_t to_sort = 0; |
|
int32_t order_by_term = query->order_by_term; |
|
|
|
sort_helper_t *helper = flecs_alloc_n( |
|
&world->allocator, sort_helper_t, table_count); |
|
ecs_query_table_match_t *cur, *end = list->last->next; |
|
for (cur = list->first; cur != end; cur = cur->next) { |
|
ecs_table_t *table = cur->table; |
|
ecs_data_t *data = &table->data; |
|
|
|
ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (id) { |
|
const ecs_term_t *term = &query->filter.terms[order_by_term]; |
|
int32_t field = term->field_index; |
|
int32_t column = cur->columns[field]; |
|
ecs_size_t size = query->filter.sizes[field]; |
|
ecs_assert(column != 0, ECS_INTERNAL_ERROR, NULL); |
|
if (column >= 0) { |
|
column = table->column_map[column - 1]; |
|
ecs_vec_t *vec = &data->columns[column].data; |
|
helper[to_sort].ptr = ecs_vec_first(vec); |
|
helper[to_sort].elem_size = size; |
|
helper[to_sort].shared = false; |
|
} else { |
|
ecs_entity_t src = cur->sources[field]; |
|
ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_record_t *r = flecs_entities_get(world, src); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (term->src.flags & EcsUp) { |
|
ecs_entity_t base = 0; |
|
ecs_search_relation(world, r->table, 0, id, |
|
EcsIsA, term->src.flags & EcsTraverseFlags, &base, 0, 0); |
|
if (base && base != src) { /* Component could be inherited */ |
|
r = flecs_entities_get(world, base); |
|
} |
|
} |
|
|
|
helper[to_sort].ptr = ecs_table_get_id( |
|
world, r->table, id, ECS_RECORD_TO_ROW(r->row)); |
|
helper[to_sort].elem_size = size; |
|
helper[to_sort].shared = true; |
|
} |
|
ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); |
|
} else { |
|
helper[to_sort].ptr = NULL; |
|
helper[to_sort].elem_size = 0; |
|
helper[to_sort].shared = false; |
|
} |
|
|
|
helper[to_sort].match = cur; |
|
helper[to_sort].entities = ecs_vec_first(&data->entities); |
|
helper[to_sort].row = 0; |
|
helper[to_sort].count = ecs_table_count(table); |
|
to_sort ++; |
|
} |
|
|
|
ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
bool proceed; |
|
do { |
|
int32_t j, min = 0; |
|
proceed = true; |
|
|
|
ecs_entity_t e1; |
|
while (!(e1 = e_from_helper(&helper[min]))) { |
|
min ++; |
|
if (min == to_sort) { |
|
proceed = false; |
|
break; |
|
} |
|
} |
|
|
|
if (!proceed) { |
|
break; |
|
} |
|
|
|
for (j = min + 1; j < to_sort; j++) { |
|
ecs_entity_t e2 = e_from_helper(&helper[j]); |
|
if (!e2) { |
|
continue; |
|
} |
|
|
|
const void *ptr1 = ptr_from_helper(&helper[min]); |
|
const void *ptr2 = ptr_from_helper(&helper[j]); |
|
|
|
if (compare(e1, ptr1, e2, ptr2) > 0) { |
|
min = j; |
|
e1 = e_from_helper(&helper[min]); |
|
} |
|
} |
|
|
|
sort_helper_t *cur_helper = &helper[min]; |
|
if (!cur || cur->columns != cur_helper->match->columns) { |
|
cur = ecs_vec_append_t(NULL, &query->table_slices, |
|
ecs_query_table_match_t); |
|
*cur = *(cur_helper->match); |
|
cur->offset = cur_helper->row; |
|
cur->count = 1; |
|
} else { |
|
cur->count ++; |
|
} |
|
|
|
cur_helper->row ++; |
|
} while (proceed); |
|
|
|
/* Iterate through the vector of slices to set the prev/next ptrs. This |
|
* can't be done while building the vector, as reallocs may occur */ |
|
int32_t i, count = ecs_vec_count(&query->table_slices); |
|
ecs_query_table_match_t *nodes = ecs_vec_first(&query->table_slices); |
|
for (i = 0; i < count; i ++) { |
|
nodes[i].prev = &nodes[i - 1]; |
|
nodes[i].next = &nodes[i + 1]; |
|
} |
|
|
|
nodes[0].prev = NULL; |
|
nodes[i - 1].next = NULL; |
|
|
|
flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); |
|
} |
|
|
|
static |
|
void flecs_query_build_sorted_tables( |
|
ecs_query_t *query) |
|
{ |
|
ecs_vec_clear(&query->table_slices); |
|
|
|
if (query->group_by) { |
|
/* Populate sorted node list in grouping order */ |
|
ecs_query_table_match_t *cur = query->list.first; |
|
if (cur) { |
|
do { |
|
/* Find list for current group */ |
|
uint64_t group_id = cur->group_id; |
|
ecs_query_table_list_t *list = ecs_map_get_deref( |
|
&query->groups, ecs_query_table_list_t, group_id); |
|
ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Sort tables in current group */ |
|
flecs_query_build_sorted_table_range(query, list); |
|
|
|
/* Find next group to sort */ |
|
cur = list->last->next; |
|
} while (cur); |
|
} |
|
} else { |
|
flecs_query_build_sorted_table_range(query, &query->list); |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_sort_tables( |
|
ecs_world_t *world, |
|
ecs_query_t *query) |
|
{ |
|
ecs_order_by_action_t compare = query->order_by; |
|
if (!compare) { |
|
return; |
|
} |
|
|
|
ecs_sort_table_action_t sort = query->sort_table; |
|
|
|
ecs_entity_t order_by_component = query->order_by_component; |
|
int32_t order_by_term = query->order_by_term; |
|
|
|
/* Iterate over non-empty tables. Don't bother with empty tables as they |
|
* have nothing to sort */ |
|
|
|
bool tables_sorted = false; |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, order_by_component); |
|
ecs_table_cache_iter_t it; |
|
ecs_query_table_t *qt; |
|
flecs_table_cache_iter(&query->cache, &it); |
|
|
|
while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { |
|
ecs_table_t *table = qt->hdr.table; |
|
bool dirty = false; |
|
|
|
if (flecs_query_check_table_monitor(query, qt, 0)) { |
|
dirty = true; |
|
} |
|
|
|
int32_t column = -1; |
|
if (order_by_component) { |
|
if (flecs_query_check_table_monitor(query, qt, order_by_term + 1)) { |
|
dirty = true; |
|
} |
|
|
|
if (dirty) { |
|
column = -1; |
|
|
|
const ecs_table_record_t *tr = flecs_id_record_get_table( |
|
idr, table); |
|
if (tr) { |
|
column = tr->column; |
|
} |
|
|
|
if (column == -1) { |
|
/* Component is shared, no sorting is needed */ |
|
dirty = false; |
|
} |
|
} |
|
} |
|
|
|
if (!dirty) { |
|
continue; |
|
} |
|
|
|
/* Something has changed, sort the table. Prefers using |
|
* flecs_query_sort_table when available */ |
|
flecs_query_sort_table(world, table, column, compare, sort); |
|
tables_sorted = true; |
|
} |
|
|
|
if (tables_sorted || query->match_count != query->prev_match_count) { |
|
flecs_query_build_sorted_tables(query); |
|
query->match_count ++; /* Increase version if tables changed */ |
|
} |
|
} |
|
|
|
static |
|
bool flecs_query_has_refs( |
|
ecs_query_t *query) |
|
{ |
|
ecs_term_t *terms = query->filter.terms; |
|
int32_t i, count = query->filter.term_count; |
|
for (i = 0; i < count; i ++) { |
|
if (terms[i].src.flags & (EcsUp | EcsIsEntity)) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
void flecs_query_for_each_component_monitor( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
void(*callback)( |
|
ecs_world_t* world, |
|
ecs_id_t id, |
|
ecs_query_t *query)) |
|
{ |
|
ecs_term_t *terms = query->filter.terms; |
|
int32_t i, count = query->filter.term_count; |
|
|
|
for (i = 0; i < count; i++) { |
|
ecs_term_t *term = &terms[i]; |
|
ecs_term_id_t *src = &term->src; |
|
|
|
if (src->flags & EcsUp) { |
|
callback(world, ecs_pair(src->trav, EcsWildcard), query); |
|
if (src->trav != EcsIsA) { |
|
callback(world, ecs_pair(EcsIsA, EcsWildcard), query); |
|
} |
|
callback(world, term->id, query); |
|
|
|
} else if (src->flags & EcsSelf && !ecs_term_match_this(term)) { |
|
callback(world, term->id, query); |
|
} |
|
} |
|
} |
|
|
|
static |
|
bool flecs_query_is_term_id_supported( |
|
ecs_term_id_t *term_id) |
|
{ |
|
if (!(term_id->flags & EcsIsVariable)) { |
|
return true; |
|
} |
|
if (ecs_id_is_wildcard(term_id->id)) { |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
static |
|
int flecs_query_process_signature( |
|
ecs_world_t *world, |
|
ecs_query_t *query) |
|
{ |
|
ecs_term_t *terms = query->filter.terms; |
|
int32_t i, count = query->filter.term_count; |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
ecs_term_id_t *first = &term->first; |
|
ecs_term_id_t *src = &term->src; |
|
ecs_term_id_t *second = &term->second; |
|
ecs_inout_kind_t inout = term->inout; |
|
|
|
bool is_src_ok = flecs_query_is_term_id_supported(src); |
|
bool is_first_ok = flecs_query_is_term_id_supported(first); |
|
bool is_second_ok = flecs_query_is_term_id_supported(second); |
|
|
|
(void)first; |
|
(void)second; |
|
(void)is_src_ok; |
|
(void)is_first_ok; |
|
(void)is_second_ok; |
|
|
|
/* Queries do not support named variables */ |
|
ecs_check(is_src_ok || ecs_term_match_this(term), |
|
ECS_UNSUPPORTED, NULL); |
|
ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); |
|
ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); |
|
ecs_check(!(src->flags & EcsFilter), ECS_INVALID_PARAMETER, |
|
"invalid usage of Filter for query"); |
|
|
|
if (inout != EcsIn && inout != EcsInOutNone) { |
|
/* Non-this terms default to EcsIn */ |
|
if (ecs_term_match_this(term) || inout != EcsInOutDefault) { |
|
query->flags |= EcsQueryHasOutTerms; |
|
} |
|
|
|
bool match_non_this = !ecs_term_match_this(term) || |
|
(term->src.flags & EcsUp); |
|
if (match_non_this && inout != EcsInOutDefault) { |
|
query->flags |= EcsQueryHasNonThisOutTerms; |
|
} |
|
} |
|
|
|
if (src->flags & EcsCascade) { |
|
/* Query can only have one cascade column */ |
|
ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); |
|
query->cascade_by = i + 1; |
|
} |
|
} |
|
|
|
query->flags |= (ecs_flags32_t)(flecs_query_has_refs(query) * EcsQueryHasRefs); |
|
|
|
if (!(query->flags & EcsQueryIsSubquery)) { |
|
flecs_query_for_each_component_monitor(world, query, flecs_monitor_register); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/** When a table becomes empty remove it from the query list, or vice versa. */ |
|
static |
|
void flecs_query_update_table( |
|
ecs_query_t *query, |
|
ecs_table_t *table, |
|
bool empty) |
|
{ |
|
int32_t prev_count = ecs_query_table_count(query); |
|
ecs_table_cache_set_empty(&query->cache, table, empty); |
|
int32_t cur_count = ecs_query_table_count(query); |
|
|
|
if (prev_count != cur_count) { |
|
ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); |
|
ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_query_table_match_t *cur, *next; |
|
|
|
for (cur = qt->first; cur != NULL; cur = next) { |
|
next = cur->next_match; |
|
|
|
if (empty) { |
|
ecs_assert(ecs_table_count(table) == 0, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_query_remove_table_node(query, cur); |
|
} else { |
|
ecs_assert(ecs_table_count(table) != 0, |
|
ECS_INTERNAL_ERROR, NULL); |
|
flecs_query_insert_table_node(query, cur); |
|
} |
|
} |
|
} |
|
|
|
ecs_assert(cur_count || query->list.first == NULL, |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
static |
|
void flecs_query_add_subquery( |
|
ecs_world_t *world, |
|
ecs_query_t *parent, |
|
ecs_query_t *subquery) |
|
{ |
|
ecs_vec_init_if_t(&parent->subqueries, ecs_query_t*); |
|
ecs_query_t **elem = ecs_vec_append_t( |
|
NULL, &parent->subqueries, ecs_query_t*); |
|
*elem = subquery; |
|
|
|
ecs_table_cache_t *cache = &parent->cache; |
|
ecs_table_cache_iter_t it; |
|
ecs_query_table_t *qt; |
|
flecs_table_cache_all_iter(cache, &it); |
|
while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { |
|
flecs_query_match_table(world, subquery, qt->hdr.table); |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_notify_subqueries( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
ecs_query_event_t *event) |
|
{ |
|
if (query->subqueries.array) { |
|
ecs_query_t **queries = ecs_vec_first(&query->subqueries); |
|
int32_t i, count = ecs_vec_count(&query->subqueries); |
|
|
|
ecs_query_event_t sub_event = *event; |
|
sub_event.parent_query = query; |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_query_t *sub = queries[i]; |
|
flecs_query_notify(world, sub, &sub_event); |
|
} |
|
} |
|
} |
|
|
|
/* Remove table */ |
|
static |
|
void flecs_query_table_match_free( |
|
ecs_query_t *query, |
|
ecs_query_table_t *elem, |
|
ecs_query_table_match_t *first) |
|
{ |
|
ecs_query_table_match_t *cur, *next; |
|
ecs_world_t *world = query->filter.world; |
|
|
|
for (cur = first; cur != NULL; cur = next) { |
|
flecs_bfree(&query->allocators.columns, cur->columns); |
|
flecs_bfree(&query->allocators.columns, cur->storage_columns); |
|
flecs_bfree(&query->allocators.ids, cur->ids); |
|
flecs_bfree(&query->allocators.sources, cur->sources); |
|
|
|
if (cur->monitor) { |
|
flecs_bfree(&query->allocators.monitors, cur->monitor); |
|
} |
|
if (!elem->hdr.empty) { |
|
flecs_query_remove_table_node(query, cur); |
|
} |
|
|
|
ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t); |
|
flecs_entity_filter_fini(world, cur->entity_filter); |
|
|
|
next = cur->next_match; |
|
|
|
flecs_bfree(&world->allocators.query_table_match, cur); |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_table_free( |
|
ecs_query_t *query, |
|
ecs_query_table_t *elem) |
|
{ |
|
flecs_query_table_match_free(query, elem, elem->first); |
|
flecs_bfree(&query->filter.world->allocators.query_table, elem); |
|
} |
|
|
|
static |
|
void flecs_query_unmatch_table( |
|
ecs_query_t *query, |
|
ecs_table_t *table, |
|
ecs_query_table_t *elem) |
|
{ |
|
if (!elem) { |
|
elem = ecs_table_cache_get(&query->cache, table); |
|
} |
|
if (elem) { |
|
ecs_table_cache_remove(&query->cache, elem->table_id, &elem->hdr); |
|
flecs_query_table_free(query, elem); |
|
} |
|
} |
|
|
|
/* Rematch system with tables after a change happened to a watched entity */ |
|
static |
|
void flecs_query_rematch_tables( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
ecs_query_t *parent_query) |
|
{ |
|
ecs_iter_t it, parent_it; |
|
ecs_table_t *table = NULL; |
|
ecs_query_table_t *qt = NULL; |
|
ecs_query_table_match_t *qm = NULL; |
|
|
|
if (query->monitor_generation == world->monitor_generation) { |
|
return; |
|
} |
|
|
|
query->monitor_generation = world->monitor_generation; |
|
|
|
if (parent_query) { |
|
parent_it = ecs_query_iter(world, parent_query); |
|
it = ecs_filter_chain_iter(&parent_it, &query->filter); |
|
} else { |
|
it = ecs_filter_iter(world, &query->filter); |
|
} |
|
|
|
ECS_BIT_SET(it.flags, EcsIterIsInstanced); |
|
ECS_BIT_SET(it.flags, EcsIterNoData); |
|
ECS_BIT_SET(it.flags, EcsIterEntityOptional); |
|
|
|
world->info.rematch_count_total ++; |
|
int32_t rematch_count = ++ query->rematch_count; |
|
|
|
ecs_time_t t = {0}; |
|
if (world->flags & EcsWorldMeasureFrameTime) { |
|
ecs_time_measure(&t); |
|
} |
|
|
|
while (ecs_filter_next(&it)) { |
|
if ((table != it.table) || (!it.table && !qt)) { |
|
if (qm && qm->next_match) { |
|
flecs_query_table_match_free(query, qt, qm->next_match); |
|
qm->next_match = NULL; |
|
} |
|
|
|
table = it.table; |
|
|
|
qt = ecs_table_cache_get(&query->cache, table); |
|
if (!qt) { |
|
qt = flecs_query_table_insert(world, query, table); |
|
} |
|
|
|
ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); |
|
qt->rematch_count = rematch_count; |
|
qm = NULL; |
|
} |
|
if (!qm) { |
|
qm = qt->first; |
|
} else { |
|
qm = qm->next_match; |
|
} |
|
if (!qm) { |
|
qm = flecs_query_add_table_match(query, qt, table); |
|
} |
|
|
|
flecs_query_set_table_match(world, query, qm, table, &it); |
|
|
|
if (table && ecs_table_count(table) && query->group_by) { |
|
if (flecs_query_get_group_id(query, table) != qm->group_id) { |
|
/* Update table group */ |
|
flecs_query_remove_table_node(query, qm); |
|
flecs_query_insert_table_node(query, qm); |
|
} |
|
} |
|
} |
|
|
|
if (qm && qm->next_match) { |
|
flecs_query_table_match_free(query, qt, qm->next_match); |
|
qm->next_match = NULL; |
|
} |
|
|
|
/* Iterate all tables in cache, remove ones that weren't just matched */ |
|
ecs_table_cache_iter_t cache_it; |
|
if (flecs_table_cache_all_iter(&query->cache, &cache_it)) { |
|
while ((qt = flecs_table_cache_next(&cache_it, ecs_query_table_t))) { |
|
if (qt->rematch_count != rematch_count) { |
|
flecs_query_unmatch_table(query, qt->hdr.table, qt); |
|
} |
|
} |
|
} |
|
|
|
if (world->flags & EcsWorldMeasureFrameTime) { |
|
world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_remove_subquery( |
|
ecs_query_t *parent, |
|
ecs_query_t *sub) |
|
{ |
|
ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(parent->subqueries.array, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t i, count = ecs_vec_count(&parent->subqueries); |
|
ecs_query_t **sq = ecs_vec_first(&parent->subqueries); |
|
|
|
for (i = 0; i < count; i ++) { |
|
if (sq[i] == sub) { |
|
break; |
|
} |
|
} |
|
|
|
ecs_vec_remove_t(&parent->subqueries, ecs_query_t*, i); |
|
} |
|
|
|
/* -- Private API -- */ |
|
|
|
void flecs_query_notify( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
ecs_query_event_t *event) |
|
{ |
|
bool notify = true; |
|
|
|
switch(event->kind) { |
|
case EcsQueryTableMatch: |
|
/* Creation of new table */ |
|
if (flecs_query_match_table(world, query, event->table)) { |
|
if (query->subqueries.array) { |
|
flecs_query_notify_subqueries(world, query, event); |
|
} |
|
} |
|
notify = false; |
|
break; |
|
case EcsQueryTableUnmatch: |
|
/* Deletion of table */ |
|
flecs_query_unmatch_table(query, event->table, NULL); |
|
break; |
|
case EcsQueryTableRematch: |
|
/* Rematch tables of query */ |
|
flecs_query_rematch_tables(world, query, event->parent_query); |
|
break; |
|
case EcsQueryOrphan: |
|
ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); |
|
query->flags |= EcsQueryIsOrphaned; |
|
query->parent = NULL; |
|
break; |
|
} |
|
|
|
if (notify) { |
|
flecs_query_notify_subqueries(world, query, event); |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_order_by( |
|
ecs_world_t *world, |
|
ecs_query_t *query, |
|
ecs_entity_t order_by_component, |
|
ecs_order_by_action_t order_by, |
|
ecs_sort_table_action_t action) |
|
{ |
|
ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!ecs_id_is_wildcard(order_by_component), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Find order_by_component term & make sure it is queried for */ |
|
const ecs_filter_t *filter = &query->filter; |
|
int32_t i, count = filter->term_count; |
|
int32_t order_by_term = -1; |
|
|
|
if (order_by_component) { |
|
for (i = 0; i < count; i ++) { |
|
ecs_term_t *term = &filter->terms[i]; |
|
|
|
/* Only And terms are supported */ |
|
if (term->id == order_by_component && term->oper == EcsAnd) { |
|
order_by_term = i; |
|
break; |
|
} |
|
} |
|
|
|
ecs_check(order_by_term != -1, ECS_INVALID_PARAMETER, |
|
"sorted component not is queried for"); |
|
} |
|
|
|
query->order_by_component = order_by_component; |
|
query->order_by = order_by; |
|
query->order_by_term = order_by_term; |
|
query->sort_table = action; |
|
|
|
ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); |
|
flecs_query_sort_tables(world, query); |
|
|
|
if (!query->table_slices.array) { |
|
flecs_query_build_sorted_tables(query); |
|
} |
|
|
|
query->flags &= ~EcsQueryTrivialIter; |
|
error: |
|
return; |
|
} |
|
|
|
static |
|
void flecs_query_group_by( |
|
ecs_query_t *query, |
|
ecs_entity_t sort_component, |
|
ecs_group_by_action_t group_by) |
|
{ |
|
/* Cannot change grouping once a query has been created */ |
|
ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL); |
|
ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL); |
|
|
|
if (!group_by) { |
|
/* Builtin function that groups by relationship */ |
|
group_by = flecs_query_default_group_by; |
|
} |
|
|
|
query->group_by_id = sort_component; |
|
query->group_by = group_by; |
|
|
|
ecs_map_init_w_params(&query->groups, |
|
&query->filter.world->allocators.query_table_list); |
|
error: |
|
return; |
|
} |
|
|
|
/* Implementation for iterable mixin */ |
|
static |
|
void flecs_query_iter_init( |
|
const ecs_world_t *world, |
|
const ecs_poly_t *poly, |
|
ecs_iter_t *iter, |
|
ecs_term_t *filter) |
|
{ |
|
ecs_poly_assert(poly, ecs_query_t); |
|
|
|
if (filter) { |
|
iter[1] = ecs_query_iter(world, ECS_CONST_CAST(ecs_query_t*, poly)); |
|
iter[0] = ecs_term_chain_iter(&iter[1], filter); |
|
} else { |
|
iter[0] = ecs_query_iter(world, ECS_CONST_CAST(ecs_query_t*, poly)); |
|
} |
|
} |
|
|
|
static |
|
void flecs_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; |
|
ecs_observer_t *o = it->ctx; |
|
if (o->last_event_id) { |
|
if (o->last_event_id[0] == world->event_id) { |
|
return; |
|
} |
|
o->last_event_id[0] = world->event_id; |
|
} |
|
|
|
ecs_query_t *query = o->ctx; |
|
ecs_table_t *table = it->table; |
|
ecs_entity_t event = it->event; |
|
|
|
if (event == EcsOnTableCreate) { |
|
/* Creation of new table */ |
|
if (flecs_query_match_table(world, query, table)) { |
|
if (query->subqueries.array) { |
|
ecs_query_event_t evt = { |
|
.kind = EcsQueryTableMatch, |
|
.table = table, |
|
.parent_query = query |
|
}; |
|
flecs_query_notify_subqueries(world, query, &evt); |
|
} |
|
} |
|
return; |
|
} |
|
|
|
ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* The observer isn't doing the matching because the query can do it more |
|
* efficiently by checking the table with the query cache. */ |
|
if (ecs_table_cache_get(&query->cache, table) == NULL) { |
|
return; |
|
} |
|
|
|
if (event == EcsOnTableEmpty) { |
|
flecs_query_update_table(query, table, true); |
|
} else |
|
if (event == EcsOnTableFill) { |
|
flecs_query_update_table(query, table, false); |
|
} else if (event == EcsOnTableDelete) { |
|
/* Deletion of table */ |
|
flecs_query_unmatch_table(query, table, NULL); |
|
if (query->subqueries.array) { |
|
ecs_query_event_t evt = { |
|
.kind = EcsQueryTableUnmatch, |
|
.table = table, |
|
.parent_query = query |
|
}; |
|
flecs_query_notify_subqueries(world, query, &evt); |
|
} |
|
return; |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_table_cache_free( |
|
ecs_query_t *query) |
|
{ |
|
ecs_table_cache_iter_t it; |
|
ecs_query_table_t *qt; |
|
|
|
if (flecs_table_cache_all_iter(&query->cache, &it)) { |
|
while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { |
|
flecs_query_table_free(query, qt); |
|
} |
|
} |
|
|
|
ecs_table_cache_fini(&query->cache); |
|
} |
|
|
|
static |
|
void flecs_query_allocators_init( |
|
ecs_query_t *query) |
|
{ |
|
int32_t field_count = query->filter.field_count; |
|
if (field_count) { |
|
flecs_ballocator_init(&query->allocators.columns, |
|
field_count * ECS_SIZEOF(int32_t)); |
|
flecs_ballocator_init(&query->allocators.ids, |
|
field_count * ECS_SIZEOF(ecs_id_t)); |
|
flecs_ballocator_init(&query->allocators.sources, |
|
field_count * ECS_SIZEOF(ecs_entity_t)); |
|
flecs_ballocator_init(&query->allocators.monitors, |
|
(1 + field_count) * ECS_SIZEOF(int32_t)); |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_allocators_fini( |
|
ecs_query_t *query) |
|
{ |
|
int32_t field_count = query->filter.field_count; |
|
if (field_count) { |
|
flecs_ballocator_fini(&query->allocators.columns); |
|
flecs_ballocator_fini(&query->allocators.ids); |
|
flecs_ballocator_fini(&query->allocators.sources); |
|
flecs_ballocator_fini(&query->allocators.monitors); |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_fini( |
|
ecs_query_t *query) |
|
{ |
|
ecs_world_t *world = query->filter.world; |
|
|
|
ecs_group_delete_action_t on_delete = query->on_group_delete; |
|
if (on_delete) { |
|
ecs_map_iter_t it = ecs_map_iter(&query->groups); |
|
while (ecs_map_next(&it)) { |
|
ecs_query_table_list_t *group = ecs_map_ptr(&it); |
|
uint64_t group_id = ecs_map_key(&it); |
|
on_delete(world, group_id, group->info.ctx, query->group_by_ctx); |
|
} |
|
query->on_group_delete = NULL; |
|
} |
|
|
|
if (query->group_by_ctx_free) { |
|
if (query->group_by_ctx) { |
|
query->group_by_ctx_free(query->group_by_ctx); |
|
} |
|
} |
|
|
|
if ((query->flags & EcsQueryIsSubquery) && |
|
!(query->flags & EcsQueryIsOrphaned)) |
|
{ |
|
flecs_query_remove_subquery(query->parent, query); |
|
} |
|
|
|
flecs_query_notify_subqueries(world, query, &(ecs_query_event_t){ |
|
.kind = EcsQueryOrphan |
|
}); |
|
|
|
flecs_query_for_each_component_monitor(world, query, |
|
flecs_monitor_unregister); |
|
flecs_query_table_cache_free(query); |
|
|
|
ecs_map_fini(&query->groups); |
|
|
|
ecs_vec_fini_t(NULL, &query->subqueries, ecs_query_t*); |
|
ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); |
|
ecs_filter_fini(&query->filter); |
|
|
|
flecs_query_allocators_fini(query); |
|
|
|
if (query->ctx_free) { |
|
query->ctx_free(query->ctx); |
|
} |
|
if (query->binding_ctx_free) { |
|
query->binding_ctx_free(query->binding_ctx); |
|
} |
|
|
|
ecs_poly_free(query, ecs_query_t); |
|
} |
|
|
|
/* -- Public API -- */ |
|
|
|
ecs_query_t* ecs_query_init( |
|
ecs_world_t *world, |
|
const ecs_query_desc_t *desc) |
|
{ |
|
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); |
|
|
|
ecs_query_t *result = ecs_poly_new(ecs_query_t); |
|
ecs_observer_desc_t observer_desc = { .filter = desc->filter }; |
|
ecs_entity_t entity = desc->filter.entity; |
|
|
|
observer_desc.filter.flags = EcsFilterMatchEmptyTables; |
|
observer_desc.filter.storage = &result->filter; |
|
result->filter = ECS_FILTER_INIT; |
|
|
|
if (ecs_filter_init(world, &observer_desc.filter) == NULL) { |
|
goto error; |
|
} |
|
|
|
ECS_BIT_COND(result->flags, EcsQueryTrivialIter, |
|
!!(result->filter.flags & EcsFilterMatchOnlyThis)); |
|
|
|
flecs_query_allocators_init(result); |
|
|
|
if (result->filter.term_count) { |
|
observer_desc.entity = entity; |
|
observer_desc.run = flecs_query_on_event; |
|
observer_desc.ctx = result; |
|
observer_desc.events[0] = EcsOnTableEmpty; |
|
observer_desc.events[1] = EcsOnTableFill; |
|
if (!desc->parent) { |
|
observer_desc.events[2] = EcsOnTableCreate; |
|
observer_desc.events[3] = EcsOnTableDelete; |
|
} |
|
observer_desc.filter.flags |= EcsFilterNoData; |
|
observer_desc.filter.instanced = true; |
|
|
|
/* ecs_filter_init could have moved away resources from the terms array |
|
* in the descriptor, so use the terms array from the filter. */ |
|
observer_desc.filter.terms_buffer = result->filter.terms; |
|
observer_desc.filter.terms_buffer_count = result->filter.term_count; |
|
observer_desc.filter.expr = NULL; /* Already parsed */ |
|
|
|
entity = ecs_observer_init(world, &observer_desc); |
|
if (!entity) { |
|
goto error; |
|
} |
|
} |
|
|
|
result->iterable.init = flecs_query_iter_init; |
|
result->dtor = (ecs_poly_dtor_t)flecs_query_fini; |
|
result->prev_match_count = -1; |
|
|
|
result->ctx = desc->ctx; |
|
result->binding_ctx = desc->binding_ctx; |
|
result->ctx_free = desc->ctx_free; |
|
result->binding_ctx_free = desc->binding_ctx_free; |
|
|
|
if (ecs_should_log_1()) { |
|
char *filter_expr = ecs_filter_str(world, &result->filter); |
|
ecs_dbg_1("#[green]query#[normal] [%s] created", |
|
filter_expr ? filter_expr : ""); |
|
ecs_os_free(filter_expr); |
|
} |
|
|
|
ecs_log_push_1(); |
|
|
|
if (flecs_query_process_signature(world, result)) { |
|
goto error; |
|
} |
|
|
|
/* Group before matching so we won't have to move tables around later */ |
|
int32_t cascade_by = result->cascade_by; |
|
if (cascade_by) { |
|
flecs_query_group_by(result, result->filter.terms[cascade_by - 1].id, |
|
flecs_query_group_by_cascade); |
|
result->group_by_ctx = &result->filter.terms[cascade_by - 1]; |
|
} |
|
|
|
if (desc->group_by || desc->group_by_id) { |
|
/* Can't have a cascade term and group by at the same time, as cascade |
|
* uses the group_by mechanism */ |
|
ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, NULL); |
|
flecs_query_group_by(result, desc->group_by_id, desc->group_by); |
|
result->group_by_ctx = desc->group_by_ctx; |
|
result->on_group_create = desc->on_group_create; |
|
result->on_group_delete = desc->on_group_delete; |
|
result->group_by_ctx_free = desc->group_by_ctx_free; |
|
} |
|
|
|
if (desc->parent != NULL) { |
|
result->flags |= EcsQueryIsSubquery; |
|
} |
|
|
|
/* If the query refers to itself, add the components that were queried for |
|
* to the query itself. */ |
|
if (entity) { |
|
int32_t t, term_count = result->filter.term_count; |
|
ecs_term_t *terms = result->filter.terms; |
|
|
|
for (t = 0; t < term_count; t ++) { |
|
ecs_term_t *term = &terms[t]; |
|
if (term->src.id == entity) { |
|
ecs_add_id(world, entity, term->id); |
|
} |
|
} |
|
} |
|
|
|
if (!entity) { |
|
entity = ecs_new_id(world); |
|
} |
|
|
|
EcsPoly *poly = ecs_poly_bind(world, entity, ecs_query_t); |
|
if (poly->poly) { |
|
/* If entity already had poly query, delete previous */ |
|
flecs_query_fini(poly->poly); |
|
} |
|
poly->poly = result; |
|
result->filter.entity = entity; |
|
|
|
/* Ensure that while initially populating the query with tables, they are |
|
* 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_run_aperiodic(world, EcsAperiodicEmptyTables); |
|
|
|
ecs_table_cache_init(world, &result->cache); |
|
|
|
if (!desc->parent) { |
|
flecs_query_match_tables(world, result); |
|
} else { |
|
flecs_query_add_subquery(world, desc->parent, result); |
|
result->parent = desc->parent; |
|
} |
|
|
|
if (desc->order_by) { |
|
flecs_query_order_by( |
|
world, result, desc->order_by_component, desc->order_by, |
|
desc->sort_table); |
|
} |
|
|
|
if (!ecs_query_table_count(result) && result->filter.term_count) { |
|
ecs_add_id(world, entity, EcsEmpty); |
|
} |
|
|
|
ecs_poly_modified(world, entity, ecs_query_t); |
|
|
|
ecs_log_pop_1(); |
|
|
|
return result; |
|
error: |
|
if (result) { |
|
ecs_filter_fini(&result->filter); |
|
ecs_os_free(result); |
|
} |
|
return NULL; |
|
} |
|
|
|
void ecs_query_fini( |
|
ecs_query_t *query) |
|
{ |
|
ecs_poly_assert(query, ecs_query_t); |
|
ecs_delete(query->filter.world, query->filter.entity); |
|
} |
|
|
|
const ecs_filter_t* ecs_query_get_filter( |
|
const ecs_query_t *query) |
|
{ |
|
ecs_poly_assert(query, ecs_query_t); |
|
return &query->filter; |
|
} |
|
|
|
static |
|
void flecs_query_set_var( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it->constrained_vars == 1, ECS_INVALID_OPERATION, |
|
"can only set $this variable for queries"); |
|
|
|
ecs_var_t *var = &it->variables[0]; |
|
ecs_table_t *table = var->range.table; |
|
if (!table) { |
|
goto nodata; |
|
} |
|
|
|
ecs_query_iter_t *qit = &it->priv.iter.query; |
|
ecs_query_t *query = qit->query; |
|
ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); |
|
if (!qt) { |
|
goto nodata; |
|
} |
|
|
|
qit->node = qt->first; |
|
qit->last = qt->last->next_match; |
|
it->offset = var->range.offset; |
|
it->count = var->range.count; |
|
return; |
|
error: |
|
nodata: |
|
it->priv.iter.query.node = NULL; |
|
it->priv.iter.query.last = NULL; |
|
return; |
|
} |
|
|
|
ecs_iter_t ecs_query_iter( |
|
const ecs_world_t *stage, |
|
ecs_query_t *query) |
|
{ |
|
ecs_poly_assert(query, ecs_query_t); |
|
ecs_check(!(query->flags & EcsQueryIsOrphaned), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_world_t *world = query->filter.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 */ |
|
flecs_query_sort_tables(world, query); |
|
|
|
/* If monitors changed, do query rematching */ |
|
if (!(world->flags & EcsWorldReadonly) && query->flags & EcsQueryHasRefs) { |
|
flecs_eval_component_monitors(world); |
|
} |
|
|
|
/* Prepare iterator */ |
|
|
|
int32_t table_count; |
|
if (ecs_vec_count(&query->table_slices)) { |
|
table_count = ecs_vec_count(&query->table_slices); |
|
} else { |
|
table_count = ecs_query_table_count(query); |
|
} |
|
|
|
ecs_query_iter_t it = { |
|
.query = query, |
|
.node = query->list.first, |
|
.last = NULL |
|
}; |
|
|
|
if (query->order_by && query->list.info.table_count) { |
|
it.node = ecs_vec_first(&query->table_slices); |
|
} |
|
|
|
ecs_iter_t result = { |
|
.real_world = world, |
|
.world = ECS_CONST_CAST(ecs_world_t*, stage), |
|
.query = &query->filter, |
|
.terms = query->filter.terms, |
|
.field_count = query->filter.field_count, |
|
.table_count = table_count, |
|
.variable_count = 1, |
|
.priv.iter.query = it, |
|
.next = ecs_query_next, |
|
.set_var = flecs_query_set_var, |
|
.system = query->filter.entity |
|
}; |
|
|
|
flecs_filter_apply_iter_flags(&result, &query->filter); |
|
|
|
ecs_filter_t *filter = &query->filter; |
|
ecs_iter_t fit; |
|
if (!(query->flags & EcsQueryTrivialIter)) { |
|
/* Check if non-This terms (like singleton terms) still match */ |
|
if (!(filter->flags & EcsFilterMatchOnlyThis)) { |
|
fit = flecs_filter_iter_w_flags(ECS_CONST_CAST(ecs_world_t*, stage), |
|
&query->filter, EcsIterIgnoreThis); |
|
if (!ecs_filter_next(&fit)) { |
|
/* No match, so return nothing */ |
|
goto noresults; |
|
} |
|
} |
|
|
|
flecs_iter_init(stage, &result, flecs_iter_cache_all); |
|
|
|
/* Copy the data */ |
|
if (!(filter->flags & EcsFilterMatchOnlyThis)) { |
|
int32_t field_count = filter->field_count; |
|
if (field_count) { |
|
if (result.ptrs) { |
|
ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, field_count); |
|
} |
|
ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, field_count); |
|
ecs_os_memcpy_n(result.columns, fit.columns, int32_t, field_count); |
|
ecs_os_memcpy_n(result.sources, fit.sources, int32_t, field_count); |
|
} |
|
ecs_iter_fini(&fit); |
|
} |
|
} else { |
|
/* Trivial iteration, use arrays from query cache */ |
|
flecs_iter_init(stage, &result, |
|
flecs_iter_cache_ptrs|flecs_iter_cache_variables); |
|
} |
|
|
|
result.sizes = query->filter.sizes; |
|
|
|
return result; |
|
error: |
|
noresults: |
|
result.priv.iter.query.node = NULL; |
|
return result; |
|
} |
|
|
|
void ecs_query_set_group( |
|
ecs_iter_t *it, |
|
uint64_t group_id) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_query_iter_t *qit = &it->priv.iter.query; |
|
ecs_query_t *q = qit->query; |
|
ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_query_table_list_t *node = flecs_query_get_group(q, group_id); |
|
if (!node) { |
|
qit->node = NULL; |
|
return; |
|
} |
|
|
|
ecs_query_table_match_t *first = node->first; |
|
if (first) { |
|
qit->node = node->first; |
|
qit->last = node->last->next; |
|
} else { |
|
qit->node = NULL; |
|
qit->last = NULL; |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
const ecs_query_group_info_t* ecs_query_get_group_info( |
|
const ecs_query_t *query, |
|
uint64_t group_id) |
|
{ |
|
ecs_query_table_list_t *node = flecs_query_get_group(query, group_id); |
|
if (!node) { |
|
return NULL; |
|
} |
|
|
|
return &node->info; |
|
} |
|
|
|
void* ecs_query_get_group_ctx( |
|
const ecs_query_t *query, |
|
uint64_t group_id) |
|
{ |
|
const ecs_query_group_info_t *info = |
|
ecs_query_get_group_info(query, group_id); |
|
if (!info) { |
|
return NULL; |
|
} else { |
|
return info->ctx; |
|
} |
|
} |
|
|
|
static |
|
void flecs_query_mark_columns_dirty( |
|
ecs_query_t *query, |
|
ecs_query_table_match_t *qm) |
|
{ |
|
ecs_table_t *table = qm->table; |
|
ecs_filter_t *filter = &query->filter; |
|
if ((table && table->dirty_state) || (query->flags & EcsQueryHasNonThisOutTerms)) { |
|
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_inout_kind_t inout = term->inout; |
|
if (inout == EcsIn || inout == EcsInOutNone) { |
|
/* Don't mark readonly terms dirty */ |
|
continue; |
|
} |
|
|
|
flecs_table_column_t tc; |
|
flecs_query_get_column_for_term(query, qm, i, &tc); |
|
|
|
if (tc.column == -1) { |
|
continue; |
|
} |
|
|
|
ecs_assert(tc.table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t *dirty_state = tc.table->dirty_state; |
|
if (!dirty_state) { |
|
continue; |
|
} |
|
|
|
if (table != tc.table) { |
|
if (inout == EcsInOutDefault) { |
|
continue; |
|
} |
|
} |
|
|
|
ecs_assert(tc.column >= 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
dirty_state[tc.column + 1] ++; |
|
} |
|
} |
|
} |
|
|
|
bool ecs_query_next_table( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
flecs_iter_validate(it); |
|
|
|
ecs_query_iter_t *iter = &it->priv.iter.query; |
|
ecs_query_table_match_t *node = iter->node; |
|
ecs_query_t *query = iter->query; |
|
|
|
ecs_query_table_match_t *prev = iter->prev; |
|
if (prev) { |
|
if (query->flags & EcsQueryHasMonitor) { |
|
flecs_query_sync_match_monitor(query, prev); |
|
} |
|
if (query->flags & EcsQueryHasOutTerms) { |
|
if (it->count) { |
|
flecs_query_mark_columns_dirty(query, prev); |
|
} |
|
} |
|
} |
|
|
|
if (node != iter->last) { |
|
it->table = node->table; |
|
it->group_id = node->group_id; |
|
it->count = 0; |
|
iter->node = node->next; |
|
iter->prev = node; |
|
return true; |
|
} |
|
|
|
error: |
|
query->match_count = query->prev_match_count; |
|
ecs_iter_fini(it); |
|
return false; |
|
} |
|
|
|
static |
|
void flecs_query_populate_trivial( |
|
ecs_iter_t *it, |
|
ecs_query_table_match_t *match) |
|
{; |
|
ecs_table_t *table = match->table; |
|
int32_t offset, count; |
|
if (!it->constrained_vars) { |
|
it->offset = offset = 0; |
|
it->count = count = ecs_table_count(table); |
|
} else { |
|
offset = it->offset; |
|
count = it->count; |
|
} |
|
|
|
it->ids = match->ids; |
|
it->sources = match->sources; |
|
it->columns = match->columns; |
|
it->group_id = match->group_id; |
|
it->instance_count = 0; |
|
it->references = ecs_vec_first(&match->refs); |
|
|
|
if (!it->references) { |
|
ecs_data_t *data = &table->data; |
|
if (!(it->flags & EcsIterNoData)) { |
|
int32_t i; |
|
for (i = 0; i < it->field_count; i ++) { |
|
int32_t column = match->storage_columns[i]; |
|
if (column < 0) { |
|
it->ptrs[i] = NULL; |
|
continue; |
|
} |
|
|
|
ecs_size_t size = it->sizes[i]; |
|
if (!size) { |
|
it->ptrs[i] = NULL; |
|
continue; |
|
} |
|
|
|
it->ptrs[i] = ecs_vec_get(&data->columns[column].data, |
|
it->sizes[i], offset); |
|
} |
|
} |
|
|
|
it->frame_offset += it->table ? ecs_table_count(it->table) : 0; |
|
it->table = table; |
|
it->entities = ecs_vec_get_t(&data->entities, ecs_entity_t, offset); |
|
} else { |
|
flecs_iter_populate_data( |
|
it->real_world, it, table, offset, count, it->ptrs); |
|
} |
|
} |
|
|
|
int ecs_query_populate( |
|
ecs_iter_t *it, |
|
bool when_changed) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_query_iter_t *iter = &it->priv.iter.query; |
|
ecs_query_t *query = iter->query; |
|
ecs_query_table_match_t *match = iter->prev; |
|
ecs_assert(match != NULL, ECS_INVALID_OPERATION, NULL); |
|
if (query->flags & EcsQueryTrivialIter) { |
|
flecs_query_populate_trivial(it, match); |
|
return EcsIterNextYield; |
|
} |
|
|
|
ecs_table_t *table = match->table; |
|
ecs_world_t *world = query->filter.world; |
|
const ecs_filter_t *filter = &query->filter; |
|
ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; |
|
ecs_assert(ent_it != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_range_t *range = &ent_it->range; |
|
int32_t t, term_count = filter->term_count; |
|
int result; |
|
|
|
repeat: |
|
result = EcsIterNextYield; |
|
|
|
ecs_os_memcpy_n(it->sources, match->sources, ecs_entity_t, |
|
filter->field_count); |
|
|
|
for (t = 0; t < term_count; t ++) { |
|
ecs_term_t *term = &filter->terms[t]; |
|
int32_t field = term->field_index; |
|
if (!ecs_term_match_this(term)) { |
|
continue; |
|
} |
|
|
|
it->ids[field] = match->ids[field]; |
|
it->columns[field] = match->columns[field]; |
|
} |
|
|
|
if (table) { |
|
range->offset = match->offset; |
|
range->count = match->count; |
|
if (!range->count) { |
|
range->count = ecs_table_count(table); |
|
ecs_assert(range->count != 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (match->entity_filter) { |
|
ent_it->entity_filter = match->entity_filter; |
|
ent_it->columns = match->columns; |
|
ent_it->range.table = table; |
|
ent_it->it = it; |
|
result = flecs_entity_filter_next(ent_it); |
|
if (result == EcsIterNext) { |
|
goto done; |
|
} |
|
} |
|
|
|
it->group_id = match->group_id; |
|
} else { |
|
range->offset = 0; |
|
range->count = 0; |
|
} |
|
|
|
if (when_changed) { |
|
if (!ecs_query_changed(NULL, it)) { |
|
if (result == EcsIterYield) { |
|
goto repeat; |
|
} else { |
|
result = EcsIterNext; |
|
goto done; |
|
} |
|
} |
|
} |
|
|
|
it->references = ecs_vec_first(&match->refs); |
|
it->instance_count = 0; |
|
|
|
flecs_iter_populate_data(world, it, table, range->offset, range->count, |
|
it->ptrs); |
|
|
|
error: |
|
done: |
|
return result; |
|
} |
|
|
|
bool ecs_query_next( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (flecs_iter_next_row(it)) { |
|
return true; |
|
} |
|
|
|
return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); |
|
error: |
|
return false; |
|
} |
|
|
|
bool ecs_query_next_instanced( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_query_iter_t *iter = &it->priv.iter.query; |
|
ecs_query_t *query = iter->query; |
|
ecs_flags32_t flags = query->flags; |
|
|
|
ecs_query_table_match_t *prev, *next, *cur = iter->node, *last = iter->last; |
|
if ((prev = iter->prev)) { |
|
/* Match has been iterated, update monitor for change tracking */ |
|
if (flags & EcsQueryHasMonitor) { |
|
flecs_query_sync_match_monitor(query, prev); |
|
} |
|
if (flags & EcsQueryHasOutTerms) { |
|
flecs_query_mark_columns_dirty(query, prev); |
|
} |
|
} |
|
|
|
flecs_iter_validate(it); |
|
iter->skip_count = 0; |
|
|
|
/* Trivial iteration: each entry in the cache is a full match and ids are |
|
* only matched on $this or through traversal starting from $this. */ |
|
if (flags & EcsQueryTrivialIter) { |
|
if (cur == last) { |
|
goto done; |
|
} |
|
iter->node = cur->next; |
|
iter->prev = cur; |
|
flecs_query_populate_trivial(it, cur); |
|
return true; |
|
} |
|
|
|
/* Non-trivial iteration: query matches with static sources, or matches with |
|
* tables that require per-entity filtering. */ |
|
for (; cur != last; cur = next) { |
|
next = cur->next; |
|
iter->prev = cur; |
|
switch(ecs_query_populate(it, false)) { |
|
case EcsIterNext: iter->node = next; continue; |
|
case EcsIterYield: next = cur; /* fall through */ |
|
case EcsIterNextYield: goto yield; |
|
default: ecs_abort(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
done: error: |
|
query->match_count = query->prev_match_count; |
|
ecs_iter_fini(it); |
|
return false; |
|
|
|
yield: |
|
iter->node = next; |
|
iter->prev = cur; |
|
return true; |
|
} |
|
|
|
bool ecs_query_changed( |
|
ecs_query_t *query, |
|
const ecs_iter_t *it) |
|
{ |
|
if (it) { |
|
ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_query_table_match_t *qm = |
|
(ecs_query_table_match_t*)it->priv.iter.query.prev; |
|
ecs_assert(qm != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!query) { |
|
query = it->priv.iter.query.query; |
|
} else { |
|
ecs_check(query == it->priv.iter.query.query, |
|
ECS_INVALID_PARAMETER, NULL); |
|
} |
|
|
|
ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_poly_assert(query, ecs_query_t); |
|
|
|
return flecs_query_check_match_monitor(query, qm, it); |
|
} |
|
|
|
ecs_poly_assert(query, ecs_query_t); |
|
ecs_check(!(query->flags & EcsQueryIsOrphaned), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
flecs_process_pending_tables(query->filter.world); |
|
|
|
if (!(query->flags & EcsQueryHasMonitor)) { |
|
query->flags |= EcsQueryHasMonitor; |
|
flecs_query_init_query_monitors(query); |
|
return true; /* Monitors didn't exist yet */ |
|
} |
|
|
|
if (query->match_count != query->prev_match_count) { |
|
return true; |
|
} |
|
|
|
return flecs_query_check_query_monitor(query); |
|
error: |
|
return false; |
|
} |
|
|
|
void ecs_query_skip( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (it->instance_count > it->count) { |
|
it->priv.iter.query.skip_count ++; |
|
if (it->priv.iter.query.skip_count == it->instance_count) { |
|
/* For non-instanced queries, make sure all entities are skipped */ |
|
it->priv.iter.query.prev = NULL; |
|
} |
|
} else { |
|
it->priv.iter.query.prev = NULL; |
|
} |
|
} |
|
|
|
bool ecs_query_orphaned( |
|
const ecs_query_t *query) |
|
{ |
|
ecs_poly_assert(query, ecs_query_t); |
|
return query->flags & EcsQueryIsOrphaned; |
|
} |
|
|
|
char* ecs_query_str( |
|
const ecs_query_t *query) |
|
{ |
|
return ecs_filter_str(query->filter.world, &query->filter); |
|
} |
|
|
|
int32_t ecs_query_table_count( |
|
const ecs_query_t *query) |
|
{ |
|
ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); |
|
return query->cache.tables.count; |
|
} |
|
|
|
int32_t ecs_query_empty_table_count( |
|
const ecs_query_t *query) |
|
{ |
|
ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); |
|
return query->cache.empty_tables.count; |
|
} |
|
|
|
int32_t ecs_query_entity_count( |
|
const ecs_query_t *query) |
|
{ |
|
ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); |
|
|
|
int32_t result = 0; |
|
ecs_table_cache_hdr_t *cur, *last = query->cache.tables.last; |
|
if (!last) { |
|
return 0; |
|
} |
|
|
|
for (cur = query->cache.tables.first; cur != NULL; cur = cur->next) { |
|
result += ecs_table_count(cur->table); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
void* ecs_query_get_ctx( |
|
const ecs_query_t *query) |
|
{ |
|
return query->ctx; |
|
} |
|
|
|
void* ecs_query_get_binding_ctx( |
|
const ecs_query_t *query) |
|
{ |
|
return query->binding_ctx; |
|
} |
|
|
|
/** |
|
* @file search.c |
|
* @brief Search functions to find (component) ids in table types. |
|
* |
|
* Search functions are used to find the column index of a (component) id in a |
|
* table. Additionally, search functions implement the logic for finding a |
|
* component id by following a relationship upwards. |
|
*/ |
|
|
|
|
|
static |
|
int32_t flecs_type_search( |
|
const ecs_table_t *table, |
|
ecs_id_t search_id, |
|
ecs_id_record_t *idr, |
|
ecs_id_t *ids, |
|
ecs_id_t *id_out, |
|
ecs_table_record_t **tr_out) |
|
{ |
|
ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); |
|
if (tr) { |
|
int32_t r = tr->index; |
|
if (tr_out) tr_out[0] = tr; |
|
if (id_out) { |
|
if (ECS_PAIR_FIRST(search_id) == EcsUnion) { |
|
id_out[0] = ids[r]; |
|
} else { |
|
id_out[0] = flecs_to_public_id(ids[r]); |
|
} |
|
} |
|
return r; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
static |
|
int32_t flecs_type_offset_search( |
|
int32_t offset, |
|
ecs_id_t id, |
|
ecs_id_t *ids, |
|
int32_t count, |
|
ecs_id_t *id_out) |
|
{ |
|
ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(id != 0, 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] = flecs_to_public_id(type_id); |
|
} |
|
return offset - 1; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
bool flecs_type_can_inherit_id( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
const ecs_id_record_t *idr, |
|
ecs_id_t id) |
|
{ |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (idr->flags & EcsIdDontInherit) { |
|
return false; |
|
} |
|
if (idr->flags & EcsIdExclusive) { |
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
ecs_entity_t er = ECS_PAIR_FIRST(id); |
|
if (flecs_table_record_get( |
|
world, table, ecs_pair(er, EcsWildcard))) |
|
{ |
|
return false; |
|
} |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
static |
|
int32_t flecs_type_search_relation( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
int32_t offset, |
|
ecs_id_t id, |
|
ecs_id_record_t *idr, |
|
ecs_id_t rel, |
|
ecs_id_record_t *idr_r, |
|
bool self, |
|
ecs_entity_t *subject_out, |
|
ecs_id_t *id_out, |
|
ecs_table_record_t **tr_out) |
|
{ |
|
ecs_type_t type = table->type; |
|
ecs_id_t *ids = type.array; |
|
int32_t count = type.count; |
|
|
|
if (self) { |
|
if (offset) { |
|
int32_t r = flecs_type_offset_search(offset, id, ids, count, id_out); |
|
if (r != -1) { |
|
return r; |
|
} |
|
} else { |
|
int32_t r = flecs_type_search(table, id, idr, ids, id_out, tr_out); |
|
if (r != -1) { |
|
return r; |
|
} |
|
} |
|
} |
|
|
|
ecs_flags32_t flags = table->flags; |
|
if ((flags & EcsTableHasPairs) && rel) { |
|
bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard); |
|
if (is_a) { |
|
if (!(flags & EcsTableHasIsA)) { |
|
return -1; |
|
} |
|
idr_r = world->idr_isa_wildcard; |
|
|
|
if (!flecs_type_can_inherit_id(world, table, idr, id)) { |
|
return -1; |
|
} |
|
} |
|
|
|
if (!idr_r) { |
|
idr_r = flecs_id_record_get(world, rel); |
|
if (!idr_r) { |
|
return -1; |
|
} |
|
} |
|
|
|
ecs_id_t id_r; |
|
int32_t r, r_column; |
|
if (offset) { |
|
r_column = flecs_type_offset_search(offset, rel, ids, count, &id_r); |
|
} else { |
|
r_column = flecs_type_search(table, id, idr_r, ids, &id_r, 0); |
|
} |
|
while (r_column != -1) { |
|
ecs_entity_t obj = ECS_PAIR_SECOND(id_r); |
|
ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_record_t *rec = flecs_entities_get_any(world, obj); |
|
ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_t *obj_table = rec->table; |
|
if (obj_table) { |
|
ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); |
|
|
|
r = flecs_type_search_relation(world, obj_table, 0, id, idr, |
|
rel, idr_r, true, subject_out, id_out, tr_out); |
|
if (r != -1) { |
|
if (subject_out && !subject_out[0]) { |
|
subject_out[0] = ecs_get_alive(world, obj); |
|
} |
|
return r_column; |
|
} |
|
|
|
if (!is_a) { |
|
r = flecs_type_search_relation(world, obj_table, 0, id, idr, |
|
ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, |
|
true, subject_out, id_out, tr_out); |
|
if (r != -1) { |
|
if (subject_out && !subject_out[0]) { |
|
subject_out[0] = ecs_get_alive(world, obj); |
|
} |
|
return r_column; |
|
} |
|
} |
|
} |
|
|
|
r_column = flecs_type_offset_search( |
|
r_column + 1, rel, ids, count, &id_r); |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
int32_t flecs_search_relation_w_idr( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
int32_t offset, |
|
ecs_id_t id, |
|
ecs_entity_t rel, |
|
ecs_flags32_t flags, |
|
ecs_entity_t *subject_out, |
|
ecs_id_t *id_out, |
|
struct ecs_table_record_t **tr_out, |
|
ecs_id_record_t *idr) |
|
{ |
|
if (!table) return -1; |
|
|
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
flags = flags ? flags : (EcsSelf|EcsUp); |
|
|
|
if (!idr) { |
|
idr = flecs_query_id_record_get(world, id); |
|
if (!idr) { |
|
return -1; |
|
} |
|
} |
|
|
|
if (subject_out) subject_out[0] = 0; |
|
if (!(flags & EcsUp)) { |
|
if (offset) { |
|
return ecs_search_offset(world, table, offset, id, id_out); |
|
} else { |
|
return flecs_type_search( |
|
table, id, idr, table->type.array, id_out, tr_out); |
|
} |
|
} |
|
|
|
int32_t result = flecs_type_search_relation(world, table, offset, id, idr, |
|
ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, |
|
id_out, tr_out); |
|
|
|
return result; |
|
} |
|
|
|
int32_t ecs_search_relation( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
int32_t offset, |
|
ecs_id_t id, |
|
ecs_entity_t rel, |
|
ecs_flags32_t flags, |
|
ecs_entity_t *subject_out, |
|
ecs_id_t *id_out, |
|
struct ecs_table_record_t **tr_out) |
|
{ |
|
if (!table) return -1; |
|
|
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
flags = flags ? flags : (EcsSelf|EcsUp); |
|
|
|
if (subject_out) subject_out[0] = 0; |
|
if (!(flags & EcsUp)) { |
|
return ecs_search_offset(world, table, offset, id, id_out); |
|
} |
|
|
|
ecs_id_record_t *idr = flecs_query_id_record_get(world, id); |
|
if (!idr) { |
|
return -1; |
|
} |
|
|
|
int32_t result = flecs_type_search_relation(world, table, offset, id, idr, |
|
ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, |
|
id_out, tr_out); |
|
|
|
return result; |
|
} |
|
|
|
int32_t flecs_search_w_idr( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_id_t id, |
|
ecs_id_t *id_out, |
|
ecs_id_record_t *idr) |
|
{ |
|
if (!table) return -1; |
|
|
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); |
|
(void)world; |
|
|
|
ecs_type_t type = table->type; |
|
ecs_id_t *ids = type.array; |
|
return flecs_type_search(table, id, idr, ids, id_out, 0); |
|
} |
|
|
|
int32_t ecs_search( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_id_t id, |
|
ecs_id_t *id_out) |
|
{ |
|
if (!table) return -1; |
|
|
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_id_record_t *idr = flecs_query_id_record_get(world, id); |
|
if (!idr) { |
|
return -1; |
|
} |
|
|
|
ecs_type_t type = table->type; |
|
ecs_id_t *ids = type.array; |
|
return flecs_type_search(table, id, idr, ids, id_out, 0); |
|
} |
|
|
|
int32_t ecs_search_offset( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
int32_t offset, |
|
ecs_id_t id, |
|
ecs_id_t *id_out) |
|
{ |
|
if (!offset) { |
|
ecs_poly_assert(world, ecs_world_t); |
|
return ecs_search(world, table, id, id_out); |
|
} |
|
|
|
if (!table) return -1; |
|
|
|
ecs_type_t type = table->type; |
|
ecs_id_t *ids = type.array; |
|
int32_t count = type.count; |
|
return flecs_type_offset_search(offset, id, ids, count, id_out); |
|
} |
|
|
|
static |
|
int32_t flecs_relation_depth_walk( |
|
const ecs_world_t *world, |
|
const ecs_id_record_t *idr, |
|
const ecs_table_t *first, |
|
const ecs_table_t *table) |
|
{ |
|
int32_t result = 0; |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (!tr) { |
|
return 0; |
|
} |
|
|
|
int32_t i = tr->index, end = i + tr->count; |
|
for (; i != end; i ++) { |
|
ecs_entity_t o = ecs_pair_second(world, table->type.array[i]); |
|
ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_t *ot = ecs_get_table(world, o); |
|
if (!ot) { |
|
continue; |
|
} |
|
|
|
ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL); |
|
int32_t cur = flecs_relation_depth_walk(world, idr, first, ot); |
|
if (cur > result) { |
|
result = cur; |
|
} |
|
} |
|
|
|
return result + 1; |
|
} |
|
|
|
int32_t flecs_relation_depth( |
|
const ecs_world_t *world, |
|
ecs_entity_t r, |
|
const ecs_table_t *table) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); |
|
if (!idr) { |
|
return 0; |
|
} |
|
|
|
int32_t depth_offset = 0; |
|
if (table->flags & EcsTableHasTarget) { |
|
if (ecs_table_get_type_index(world, table, |
|
ecs_pair_t(EcsTarget, r)) != -1) |
|
{ |
|
ecs_id_t id; |
|
int32_t col = ecs_search(world, table, |
|
ecs_pair(EcsFlatten, EcsWildcard), &id); |
|
if (col == -1) { |
|
return 0; |
|
} |
|
|
|
ecs_entity_t did = ecs_pair_second(world, id); |
|
ecs_assert(did != 0, ECS_INTERNAL_ERROR, NULL); |
|
uint64_t *val = ecs_map_get(&world->store.entity_to_depth, did); |
|
ecs_assert(val != NULL, ECS_INTERNAL_ERROR, NULL); |
|
depth_offset = flecs_uto(int32_t, val[0]); |
|
} |
|
} |
|
|
|
return flecs_relation_depth_walk(world, idr, table, table) + depth_offset; |
|
} |
|
|
|
/** |
|
* @file stage.c |
|
* @brief Staging implementation. |
|
* |
|
* A stage is an object that can be used to temporarily store mutations to a |
|
* world while a world is in readonly mode. ECS operations that are invoked on |
|
* a stage are stored in a command buffer, which is flushed during sync points, |
|
* or manually by the user. |
|
* |
|
* Stages contain additional state to enable other API functionality without |
|
* having to mutate the world, such as setting the current scope, and allocators |
|
* that are local to a stage. |
|
* |
|
* In a multi threaded application, each thread has its own stage which allows |
|
* threads to insert mutations without having to lock administration. |
|
*/ |
|
|
|
|
|
static |
|
ecs_cmd_t* flecs_cmd_new( |
|
ecs_stage_t *stage) |
|
{ |
|
ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, |
|
ecs_cmd_t); |
|
cmd->is._1.value = NULL; |
|
cmd->next_for_entity = 0; |
|
cmd->entry = NULL; |
|
return cmd; |
|
} |
|
|
|
static |
|
ecs_cmd_t* flecs_cmd_new_batched( |
|
ecs_stage_t *stage, |
|
ecs_entity_t e) |
|
{ |
|
ecs_vec_t *cmds = &stage->cmd->queue; |
|
ecs_cmd_entry_t *entry = flecs_sparse_get_any_t( |
|
&stage->cmd->entries, ecs_cmd_entry_t, e); |
|
|
|
int32_t cur = ecs_vec_count(cmds); |
|
ecs_cmd_t *cmd = flecs_cmd_new(stage); |
|
if (entry) { |
|
if (entry->first == -1) { |
|
/* Existing but invalidated entry */ |
|
entry->first = cur; |
|
cmd->entry = entry; |
|
} else { |
|
int32_t last = entry->last; |
|
ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); |
|
ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); |
|
ecs_cmd_t *last_op = &arr[last]; |
|
last_op->next_for_entity = cur; |
|
if (last == entry->first) { |
|
/* Flip sign bit so flush logic can tell which command |
|
* is the first for an entity */ |
|
last_op->next_for_entity *= -1; |
|
} |
|
} |
|
} else { |
|
cmd->entry = entry = flecs_sparse_ensure_fast_t( |
|
&stage->cmd->entries, ecs_cmd_entry_t, e); |
|
entry->first = cur; |
|
} |
|
|
|
entry->last = cur; |
|
|
|
return cmd; |
|
} |
|
|
|
static |
|
void flecs_stages_merge( |
|
ecs_world_t *world, |
|
bool force_merge) |
|
{ |
|
bool is_stage = ecs_poly_is(world, ecs_stage_t); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
bool measure_frame_time = ECS_BIT_IS_SET(world->flags, |
|
EcsWorldMeasureFrameTime); |
|
|
|
ecs_time_t t_start = {0}; |
|
if (measure_frame_time) { |
|
ecs_os_get_time(&t_start); |
|
} |
|
|
|
ecs_dbg_3("#[magenta]merge"); |
|
ecs_log_push_3(); |
|
|
|
if (is_stage) { |
|
/* Check for consistency if force_merge is enabled. In practice this |
|
* function will never get called with force_merge disabled for just |
|
* a single stage. */ |
|
if (force_merge || stage->auto_merge) { |
|
ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, |
|
"mismatching defer_begin/defer_end detected"); |
|
flecs_defer_end(world, stage); |
|
} |
|
} else { |
|
/* Merge stages. Only merge if the stage has auto_merging turned on, or |
|
* if this is a forced merge (like when ecs_merge is called) */ |
|
int32_t i, count = ecs_get_stage_count(world); |
|
for (i = 0; i < count; i ++) { |
|
ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); |
|
ecs_poly_assert(s, ecs_stage_t); |
|
if (force_merge || s->auto_merge) { |
|
flecs_defer_end(world, s); |
|
} |
|
} |
|
} |
|
|
|
flecs_eval_component_monitors(world); |
|
|
|
if (measure_frame_time) { |
|
world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); |
|
} |
|
|
|
world->info.merge_count_total ++; |
|
|
|
/* If stage is asynchronous, deferring is always enabled */ |
|
if (stage->async) { |
|
flecs_defer_begin(world, stage); |
|
} |
|
|
|
ecs_log_pop_3(); |
|
} |
|
|
|
static |
|
void flecs_stage_auto_merge( |
|
ecs_world_t *world) |
|
{ |
|
flecs_stages_merge(world, false); |
|
} |
|
|
|
static |
|
void flecs_stage_manual_merge( |
|
ecs_world_t *world) |
|
{ |
|
flecs_stages_merge(world, true); |
|
} |
|
|
|
bool flecs_defer_begin( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_poly_assert(stage, ecs_stage_t); |
|
(void)world; |
|
if (stage->defer < 0) return false; |
|
return (++ stage->defer) == 1; |
|
} |
|
|
|
bool flecs_defer_cmd( |
|
ecs_stage_t *stage) |
|
{ |
|
if (stage->defer) { |
|
return (stage->defer > 0); |
|
} |
|
|
|
stage->defer ++; |
|
return false; |
|
} |
|
|
|
bool flecs_defer_modified( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); |
|
if (cmd) { |
|
cmd->kind = EcsCmdModified; |
|
cmd->id = id; |
|
cmd->entity = entity; |
|
} |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_defer_clone( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_entity_t src, |
|
bool clone_value) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_cmd_t *cmd = flecs_cmd_new(stage); |
|
cmd->kind = EcsCmdClone; |
|
cmd->id = src; |
|
cmd->entity = entity; |
|
cmd->is._1.clone_value = clone_value; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_defer_path( |
|
ecs_stage_t *stage, |
|
ecs_entity_t parent, |
|
ecs_entity_t entity, |
|
const char *name) |
|
{ |
|
if (stage->defer > 0) { |
|
ecs_cmd_t *cmd = flecs_cmd_new(stage); |
|
cmd->kind = EcsCmdPath; |
|
cmd->entity = entity; |
|
cmd->id = parent; |
|
cmd->is._1.value = ecs_os_strdup(name); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_defer_delete( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_cmd_t *cmd = flecs_cmd_new(stage); |
|
cmd->kind = EcsCmdDelete; |
|
cmd->entity = entity; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_defer_clear( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); |
|
cmd->kind = EcsCmdClear; |
|
cmd->entity = entity; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_defer_on_delete_action( |
|
ecs_stage_t *stage, |
|
ecs_id_t id, |
|
ecs_entity_t action) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_cmd_t *cmd = flecs_cmd_new(stage); |
|
cmd->kind = EcsCmdOnDeleteAction; |
|
cmd->id = id; |
|
cmd->entity = action; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_defer_enable( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_id_t id, |
|
bool enable) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_cmd_t *cmd = flecs_cmd_new(stage); |
|
cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; |
|
cmd->entity = entity; |
|
cmd->id = id; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
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) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); |
|
|
|
/* Use ecs_new_id as this is thread safe */ |
|
int i; |
|
for (i = 0; i < count; i ++) { |
|
ids[i] = ecs_new_id(world); |
|
} |
|
|
|
*ids_out = ids; |
|
|
|
/* Store data in op */ |
|
ecs_cmd_t *cmd = flecs_cmd_new(stage); |
|
cmd->kind = EcsCmdBulkNew; |
|
cmd->id = id; |
|
cmd->is._n.entities = ids; |
|
cmd->is._n.count = count; |
|
cmd->entity = 0; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_defer_add( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); |
|
cmd->kind = EcsCmdAdd; |
|
cmd->id = id; |
|
cmd->entity = entity; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_defer_remove( |
|
ecs_stage_t *stage, |
|
ecs_entity_t entity, |
|
ecs_id_t id) |
|
{ |
|
if (flecs_defer_cmd(stage)) { |
|
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); |
|
cmd->kind = EcsCmdRemove; |
|
cmd->id = id; |
|
cmd->entity = entity; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
void* flecs_defer_set( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_cmd_kind_t cmd_kind, |
|
ecs_entity_t entity, |
|
ecs_id_t id, |
|
ecs_size_t size, |
|
void *value) |
|
{ |
|
ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); |
|
|
|
/* Find type info for id */ |
|
const ecs_type_info_t *ti = NULL; |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
/* If idr doesn't exist yet, create it but only if the |
|
* application is not multithreaded. */ |
|
if (stage->async || (world->flags & EcsWorldMultiThreaded)) { |
|
ti = ecs_get_type_info(world, id); |
|
ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); |
|
} else { |
|
/* When not in multi threaded mode, it's safe to find or |
|
* create the id record. */ |
|
idr = flecs_id_record_ensure(world, id); |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Get type_info from id record. We could have called |
|
* ecs_get_type_info directly, but since this function can be |
|
* expensive for pairs, creating the id record ensures we can |
|
* find the type_info quickly for subsequent operations. */ |
|
ti = idr->type_info; |
|
} |
|
} else { |
|
ti = idr->type_info; |
|
} |
|
|
|
/* If the id isn't associated with a type, we can't set anything */ |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Make sure the size of the value equals the type size */ |
|
ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); |
|
size = ti->size; |
|
|
|
/* Find existing component. Make sure it's owned, so that we won't use the |
|
* component of a prefab. */ |
|
void *existing = NULL; |
|
ecs_table_t *table = NULL; |
|
if (idr) { |
|
/* Entity can only have existing component if id record exists */ |
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
table = r->table; |
|
if (r && table) { |
|
const ecs_table_record_t *tr = flecs_id_record_get_table( |
|
idr, table); |
|
if (tr) { |
|
ecs_assert(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); |
|
/* Entity has the component */ |
|
ecs_vec_t *column = &table->data.columns[tr->column].data; |
|
existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row)); |
|
} |
|
} |
|
} |
|
|
|
/* Get existing value from storage */ |
|
void *cmd_value = existing; |
|
bool emplace = cmd_kind == EcsCmdEmplace; |
|
|
|
/* If the component does not yet exist, create a temporary value. This is |
|
* necessary so we can store a component value in the deferred command, |
|
* without adding the component to the entity which is not allowed in |
|
* deferred mode. */ |
|
if (!existing) { |
|
ecs_stack_t *stack = &stage->cmd->stack; |
|
cmd_value = flecs_stack_alloc(stack, size, ti->alignment); |
|
|
|
/* If the component doesn't yet exist, construct it and move the |
|
* provided value into the component, if provided. Don't construct if |
|
* this is an emplace operation, in which case the application is |
|
* responsible for constructing. */ |
|
if (value) { |
|
if (emplace) { |
|
ecs_move_t move = ti->hooks.move_ctor; |
|
if (move) { |
|
move(cmd_value, value, 1, ti); |
|
} else { |
|
ecs_os_memcpy(cmd_value, value, size); |
|
} |
|
} else { |
|
ecs_copy_t copy = ti->hooks.copy_ctor; |
|
if (copy) { |
|
copy(cmd_value, value, 1, ti); |
|
} else { |
|
ecs_os_memcpy(cmd_value, value, size); |
|
} |
|
} |
|
} else if (!emplace) { |
|
/* If the command is not an emplace, construct the temp storage */ |
|
|
|
/* Check if entity inherits component */ |
|
void *base = NULL; |
|
if (table && (table->flags & EcsTableHasIsA)) { |
|
base = flecs_get_base_component(world, table, id, idr, 0); |
|
} |
|
|
|
if (!base) { |
|
/* Normal ctor */ |
|
ecs_xtor_t ctor = ti->hooks.ctor; |
|
if (ctor) { |
|
ctor(cmd_value, 1, ti); |
|
} |
|
} else { |
|
/* Override */ |
|
ecs_copy_t copy = ti->hooks.copy_ctor; |
|
if (copy) { |
|
copy(cmd_value, base, 1, ti); |
|
} else { |
|
ecs_os_memcpy(cmd_value, base, size); |
|
} |
|
} |
|
} |
|
} else if (value) { |
|
/* If component exists and value is provided, copy */ |
|
ecs_copy_t copy = ti->hooks.copy; |
|
if (copy) { |
|
copy(existing, value, 1, ti); |
|
} else { |
|
ecs_os_memcpy(existing, value, size); |
|
} |
|
} |
|
|
|
if (!cmd) { |
|
/* If cmd is NULL, entity was already deleted. Check if we need to |
|
* insert a command into the queue. */ |
|
if (!ti->hooks.dtor) { |
|
/* If temporary memory does not need to be destructed, it'll get |
|
* freed when the stack allocator is reset. This prevents us |
|
* from having to insert a command when the entity was |
|
* already deleted. */ |
|
return cmd_value; |
|
} |
|
cmd = flecs_cmd_new(stage); |
|
} |
|
|
|
if (!existing) { |
|
/* If component didn't exist yet, insert command that will create it */ |
|
cmd->kind = cmd_kind; |
|
cmd->id = id; |
|
cmd->idr = idr; |
|
cmd->entity = entity; |
|
cmd->is._1.size = size; |
|
cmd->is._1.value = cmd_value; |
|
} else { |
|
/* If component already exists, still insert an Add command to ensure |
|
* that any preceding remove commands won't remove the component. If the |
|
* operation is a set, also insert a Modified command. */ |
|
if (cmd_kind == EcsCmdSet) { |
|
cmd->kind = EcsCmdAddModified; |
|
} else { |
|
cmd->kind = EcsCmdAdd; |
|
} |
|
cmd->id = id; |
|
cmd->entity = entity; |
|
} |
|
|
|
return cmd_value; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void flecs_enqueue( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_event_desc_t *desc) |
|
{ |
|
ecs_cmd_t *cmd = flecs_cmd_new(stage); |
|
cmd->kind = EcsCmdEvent; |
|
cmd->entity = desc->entity; |
|
|
|
ecs_stack_t *stack = &stage->cmd->stack; |
|
ecs_event_desc_t *desc_cmd = flecs_stack_alloc_t(stack, ecs_event_desc_t); |
|
ecs_os_memcpy_t(desc_cmd, desc, ecs_event_desc_t); |
|
|
|
if (desc->ids && desc->ids->count != 0) { |
|
ecs_type_t *type_cmd = flecs_stack_alloc_t(stack, ecs_type_t); |
|
int32_t id_count = desc->ids->count; |
|
type_cmd->count = id_count; |
|
type_cmd->array = flecs_stack_alloc_n(stack, ecs_id_t, id_count); |
|
ecs_os_memcpy_n(type_cmd->array, desc->ids->array, ecs_id_t, id_count); |
|
desc_cmd->ids = type_cmd; |
|
} else { |
|
desc_cmd->ids = NULL; |
|
} |
|
|
|
cmd->is._1.value = desc_cmd; |
|
cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); |
|
|
|
if (desc->param || desc->const_param) { |
|
ecs_assert(!(desc->const_param && desc->param), ECS_INVALID_PARAMETER, |
|
"cannot set param and const_param at the same time"); |
|
|
|
const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); |
|
ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, |
|
"can only enqueue events with data for events that are components"); |
|
|
|
void *param_cmd = flecs_stack_alloc(stack, ti->size, ti->alignment); |
|
ecs_assert(param_cmd != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (desc->param) { |
|
if (ti->hooks.move_ctor) { |
|
ti->hooks.move_ctor(param_cmd, desc->param, 1, ti); |
|
} else { |
|
ecs_os_memcpy(param_cmd, desc->param, ti->size); |
|
} |
|
} else { |
|
if (ti->hooks.copy_ctor) { |
|
ti->hooks.copy_ctor(param_cmd, desc->const_param, 1, ti); |
|
} else { |
|
ecs_os_memcpy(param_cmd, desc->const_param, ti->size); |
|
} |
|
} |
|
|
|
desc_cmd->param = param_cmd; |
|
desc_cmd->const_param = NULL; |
|
} |
|
} |
|
|
|
void flecs_stage_merge_post_frame( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage) |
|
{ |
|
/* Execute post frame actions */ |
|
int32_t i, count = ecs_vec_count(&stage->post_frame_actions); |
|
ecs_action_elem_t *elems = ecs_vec_first(&stage->post_frame_actions); |
|
for (i = 0; i < count; i ++) { |
|
elems[i].action(world, elems[i].ctx); |
|
} |
|
|
|
ecs_vec_clear(&stage->post_frame_actions); |
|
} |
|
|
|
static |
|
void flecs_commands_init( |
|
ecs_stage_t *stage, |
|
ecs_commands_t *cmd) |
|
{ |
|
flecs_stack_init(&cmd->stack); |
|
ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0); |
|
flecs_sparse_init_t(&cmd->entries, &stage->allocator, |
|
&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); |
|
} |
|
|
|
static |
|
void flecs_commands_fini( |
|
ecs_stage_t *stage, |
|
ecs_commands_t *cmd) |
|
{ |
|
/* Make sure stage has no unmerged data */ |
|
ecs_assert(ecs_vec_count(&stage->cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_stack_fini(&cmd->stack); |
|
ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); |
|
flecs_sparse_fini(&cmd->entries); |
|
} |
|
|
|
void flecs_commands_push( |
|
ecs_stage_t *stage) |
|
{ |
|
int32_t sp = ++ stage->cmd_sp; |
|
ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL); |
|
stage->cmd = &stage->cmd_stack[sp]; |
|
} |
|
|
|
void flecs_commands_pop( |
|
ecs_stage_t *stage) |
|
{ |
|
int32_t sp = -- stage->cmd_sp; |
|
ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL); |
|
stage->cmd = &stage->cmd_stack[sp]; |
|
} |
|
|
|
void flecs_stage_init( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_poly_init(stage, ecs_stage_t); |
|
|
|
stage->world = world; |
|
stage->thread_ctx = world; |
|
stage->auto_merge = true; |
|
stage->async = false; |
|
|
|
flecs_stack_init(&stage->allocators.iter_stack); |
|
flecs_stack_init(&stage->allocators.deser_stack); |
|
flecs_allocator_init(&stage->allocator); |
|
flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t, |
|
FLECS_SPARSE_PAGE_SIZE); |
|
|
|
ecs_allocator_t *a = &stage->allocator; |
|
ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); |
|
|
|
int32_t i; |
|
for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { |
|
flecs_commands_init(stage, &stage->cmd_stack[i]); |
|
} |
|
|
|
stage->cmd = &stage->cmd_stack[0]; |
|
} |
|
|
|
void flecs_stage_fini( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage) |
|
{ |
|
(void)world; |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_poly_assert(stage, ecs_stage_t); |
|
|
|
ecs_poly_fini(stage, ecs_stage_t); |
|
|
|
ecs_allocator_t *a = &stage->allocator; |
|
|
|
ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); |
|
ecs_vec_fini(NULL, &stage->variables, 0); |
|
ecs_vec_fini(NULL, &stage->operations, 0); |
|
|
|
int32_t i; |
|
for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { |
|
flecs_commands_fini(stage, &stage->cmd_stack[i]); |
|
} |
|
|
|
flecs_stack_fini(&stage->allocators.iter_stack); |
|
flecs_stack_fini(&stage->allocators.deser_stack); |
|
flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); |
|
flecs_allocator_fini(&stage->allocator); |
|
} |
|
|
|
void ecs_set_stage_count( |
|
ecs_world_t *world, |
|
int32_t stage_count) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
/* World must have at least one default stage */ |
|
ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
bool auto_merge = true; |
|
const ecs_entity_t *lookup_path = NULL; |
|
ecs_entity_t scope = 0; |
|
ecs_entity_t with = 0; |
|
if (world->stage_count >= 1) { |
|
auto_merge = world->stages[0].auto_merge; |
|
lookup_path = world->stages[0].lookup_path; |
|
scope = world->stages[0].scope; |
|
with = world->stages[0].with; |
|
} |
|
|
|
int32_t i, count = world->stage_count; |
|
if (count && count != stage_count) { |
|
ecs_stage_t *stages = world->stages; |
|
|
|
for (i = 0; i < count; i ++) { |
|
/* If stage contains a thread handle, ecs_set_threads was used to |
|
* create the stages. ecs_set_threads and ecs_set_stage_count should not |
|
* be mixed. */ |
|
ecs_poly_assert(&stages[i], ecs_stage_t); |
|
ecs_check(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); |
|
flecs_stage_fini(world, &stages[i]); |
|
} |
|
|
|
ecs_os_free(world->stages); |
|
} |
|
|
|
if (stage_count) { |
|
world->stages = ecs_os_malloc_n(ecs_stage_t, stage_count); |
|
|
|
for (i = 0; i < stage_count; i ++) { |
|
ecs_stage_t *stage = &world->stages[i]; |
|
flecs_stage_init(world, stage); |
|
stage->id = i; |
|
|
|
/* Set thread_ctx to stage, as this stage might be used in a |
|
* multithreaded context */ |
|
stage->thread_ctx = (ecs_world_t*)stage; |
|
stage->thread = 0; |
|
} |
|
} else { |
|
/* Set to NULL to prevent double frees */ |
|
world->stages = NULL; |
|
} |
|
|
|
/* Regardless of whether the stage was just initialized or not, when the |
|
* ecs_set_stage_count function is called, all stages inherit the auto_merge |
|
* property from the world */ |
|
for (i = 0; i < stage_count; i ++) { |
|
world->stages[i].auto_merge = auto_merge; |
|
world->stages[i].lookup_path = lookup_path; |
|
world->stages[0].scope = scope; |
|
world->stages[0].with = with; |
|
} |
|
|
|
world->stage_count = stage_count; |
|
error: |
|
return; |
|
} |
|
|
|
int32_t ecs_get_stage_count( |
|
const ecs_world_t *world) |
|
{ |
|
world = ecs_get_world(world); |
|
return world->stage_count; |
|
} |
|
|
|
int32_t ecs_get_stage_id( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (ecs_poly_is(world, ecs_stage_t)) { |
|
ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); |
|
|
|
/* Index 0 is reserved for main stage */ |
|
return stage->id; |
|
} else if (ecs_poly_is(world, ecs_world_t)) { |
|
return 0; |
|
} else { |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_world_t* ecs_get_stage( |
|
const ecs_world_t *world, |
|
int32_t stage_id) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); |
|
return (ecs_world_t*)&world->stages[stage_id]; |
|
error: |
|
return NULL; |
|
} |
|
|
|
bool ecs_readonly_begin( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
flecs_process_pending_tables(world); |
|
|
|
ecs_dbg_3("#[bold]readonly"); |
|
ecs_log_push_3(); |
|
|
|
int32_t i, count = ecs_get_stage_count(world); |
|
for (i = 0; i < count; i ++) { |
|
ecs_stage_t *stage = &world->stages[i]; |
|
stage->lookup_path = world->stages[0].lookup_path; |
|
ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, |
|
"deferred mode cannot be enabled when entering readonly mode"); |
|
flecs_defer_begin(world, stage); |
|
} |
|
|
|
bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); |
|
|
|
/* From this point on, the world is "locked" for mutations, and it is only |
|
* allowed to enqueue commands from stages */ |
|
ECS_BIT_SET(world->flags, EcsWorldReadonly); |
|
|
|
/* If world has more than one stage, signal we might be running on multiple |
|
* threads. This is a stricter version of readonly mode: while some |
|
* mutations like implicit component registration are still allowed in plain |
|
* readonly mode, no mutations are allowed when multithreaded. */ |
|
if (world->worker_cond) { |
|
ECS_BIT_SET(world->flags, EcsWorldMultiThreaded); |
|
} |
|
|
|
return is_readonly; |
|
} |
|
|
|
void ecs_readonly_end( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); |
|
|
|
/* After this it is safe again to mutate the world directly */ |
|
ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); |
|
ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); |
|
|
|
ecs_log_pop_3(); |
|
|
|
flecs_stage_auto_merge(world); |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_merge( |
|
ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_poly_is(world, ecs_world_t) || |
|
ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); |
|
flecs_stage_manual_merge(world); |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_set_automerge( |
|
ecs_world_t *world, |
|
bool auto_merge) |
|
{ |
|
/* If a world is provided, set auto_merge globally for the world. This |
|
* doesn't actually do anything (the main stage never merges) but it serves |
|
* as the default for when stages are created. */ |
|
if (ecs_poly_is(world, ecs_world_t)) { |
|
world->stages[0].auto_merge = auto_merge; |
|
|
|
/* Propagate change to all stages */ |
|
int i, stage_count = ecs_get_stage_count(world); |
|
for (i = 0; i < stage_count; i ++) { |
|
ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); |
|
stage->auto_merge = auto_merge; |
|
} |
|
|
|
/* If a stage is provided, override the auto_merge value for the individual |
|
* stage. This allows an application to control per-stage which stage should |
|
* be automatically merged and which one shouldn't */ |
|
} else { |
|
ecs_poly_assert(world, ecs_stage_t); |
|
ecs_stage_t *stage = (ecs_stage_t*)world; |
|
stage->auto_merge = auto_merge; |
|
} |
|
} |
|
|
|
bool ecs_stage_is_readonly( |
|
const ecs_world_t *stage) |
|
{ |
|
const ecs_world_t *world = ecs_get_world(stage); |
|
|
|
if (ecs_poly_is(stage, ecs_stage_t)) { |
|
if (((const ecs_stage_t*)stage)->async) { |
|
return false; |
|
} |
|
} |
|
|
|
if (world->flags & EcsWorldReadonly) { |
|
if (ecs_poly_is(stage, ecs_world_t)) { |
|
return true; |
|
} |
|
} else { |
|
if (ecs_poly_is(stage, ecs_stage_t)) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
ecs_world_t* ecs_async_stage_new( |
|
ecs_world_t *world) |
|
{ |
|
ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); |
|
flecs_stage_init(world, stage); |
|
|
|
stage->id = -1; |
|
stage->auto_merge = false; |
|
stage->async = true; |
|
|
|
flecs_defer_begin(world, stage); |
|
|
|
return (ecs_world_t*)stage; |
|
} |
|
|
|
void ecs_async_stage_free( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_stage_t); |
|
ecs_stage_t *stage = (ecs_stage_t*)world; |
|
ecs_check(stage->async == true, ECS_INVALID_PARAMETER, NULL); |
|
flecs_stage_fini(stage->world, stage); |
|
ecs_os_free(stage); |
|
error: |
|
return; |
|
} |
|
|
|
bool ecs_stage_is_async( |
|
ecs_world_t *stage) |
|
{ |
|
if (!stage) { |
|
return false; |
|
} |
|
|
|
if (!ecs_poly_is(stage, ecs_stage_t)) { |
|
return false; |
|
} |
|
|
|
return ((ecs_stage_t*)stage)->async; |
|
} |
|
|
|
bool ecs_is_deferred( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); |
|
return stage->defer > 0; |
|
error: |
|
return false; |
|
} |
|
|
|
/** |
|
* @file value.c |
|
* @brief Utility functions to work with non-trivial pointers of user types. |
|
*/ |
|
|
|
|
|
int ecs_value_init_w_type_info( |
|
const ecs_world_t *world, |
|
const ecs_type_info_t *ti, |
|
void *ptr) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); |
|
(void)world; |
|
|
|
ecs_xtor_t ctor; |
|
if ((ctor = ti->hooks.ctor)) { |
|
ctor(ptr, 1, ti); |
|
} else { |
|
ecs_os_memset(ptr, 0, ti->size); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_init( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
void *ptr) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, type); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); |
|
return ecs_value_init_w_type_info(world, ti, ptr); |
|
error: |
|
return -1; |
|
} |
|
|
|
void* ecs_value_new_w_type_info( |
|
ecs_world_t *world, |
|
const ecs_type_info_t *ti) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); |
|
(void)world; |
|
|
|
void *result = flecs_alloc(&world->allocator, ti->size); |
|
if (ecs_value_init_w_type_info(world, ti, result) != 0) { |
|
flecs_free(&world->allocator, ti->size, result); |
|
goto error; |
|
} |
|
|
|
return result; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void* ecs_value_new( |
|
ecs_world_t *world, |
|
ecs_entity_t type) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, type); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); |
|
|
|
return ecs_value_new_w_type_info(world, ti); |
|
error: |
|
return NULL; |
|
} |
|
|
|
int ecs_value_fini_w_type_info( |
|
const ecs_world_t *world, |
|
const ecs_type_info_t *ti, |
|
void *ptr) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); |
|
(void)world; |
|
|
|
ecs_xtor_t dtor; |
|
if ((dtor = ti->hooks.dtor)) { |
|
dtor(ptr, 1, ti); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_fini( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
void* ptr) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
(void)world; |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, type); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); |
|
return ecs_value_fini_w_type_info(world, ti, ptr); |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_free( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
void* ptr) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, type); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); |
|
if (ecs_value_fini_w_type_info(world, ti, ptr) != 0) { |
|
goto error; |
|
} |
|
|
|
flecs_free(&world->allocator, ti->size, ptr); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_copy_w_type_info( |
|
const ecs_world_t *world, |
|
const ecs_type_info_t *ti, |
|
void* dst, |
|
const void *src) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); |
|
(void)world; |
|
|
|
ecs_copy_t copy; |
|
if ((copy = ti->hooks.copy)) { |
|
copy(dst, src, 1, ti); |
|
} else { |
|
ecs_os_memcpy(dst, src, ti->size); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_copy( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
void* dst, |
|
const void *src) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, type); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); |
|
return ecs_value_copy_w_type_info(world, ti, dst, src); |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_move_w_type_info( |
|
const ecs_world_t *world, |
|
const ecs_type_info_t *ti, |
|
void* dst, |
|
void *src) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); |
|
(void)world; |
|
|
|
ecs_move_t move; |
|
if ((move = ti->hooks.move)) { |
|
move(dst, src, 1, ti); |
|
} else { |
|
ecs_os_memcpy(dst, src, ti->size); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_move( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
void* dst, |
|
void *src) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, type); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); |
|
return ecs_value_move_w_type_info(world, ti, dst, src); |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_move_ctor_w_type_info( |
|
const ecs_world_t *world, |
|
const ecs_type_info_t *ti, |
|
void* dst, |
|
void *src) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); |
|
(void)world; |
|
|
|
ecs_move_t move; |
|
if ((move = ti->hooks.move_ctor)) { |
|
move(dst, src, 1, ti); |
|
} else { |
|
ecs_os_memcpy(dst, src, ti->size); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_value_move_ctor( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
void* dst, |
|
void *src) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, type); |
|
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); |
|
return ecs_value_move_w_type_info(world, ti, dst, src); |
|
error: |
|
return -1; |
|
} |
|
|
|
/** |
|
* @file world.c |
|
* @brief World-level API. |
|
*/ |
|
|
|
|
|
/* Id flags */ |
|
const ecs_id_t ECS_PAIR = (1ull << 63); |
|
const ecs_id_t ECS_OVERRIDE = (1ull << 62); |
|
const ecs_id_t ECS_TOGGLE = (1ull << 61); |
|
const ecs_id_t ECS_AND = (1ull << 60); |
|
|
|
/** Builtin component ids */ |
|
const ecs_entity_t ecs_id(EcsComponent) = 1; |
|
const ecs_entity_t ecs_id(EcsIdentifier) = 2; |
|
const ecs_entity_t ecs_id(EcsIterable) = 3; |
|
const ecs_entity_t ecs_id(EcsPoly) = 4; |
|
|
|
/* Poly target components */ |
|
const ecs_entity_t EcsQuery = 5; |
|
const ecs_entity_t EcsObserver = 6; |
|
const ecs_entity_t EcsSystem = 7; |
|
|
|
/* Core scopes & entities */ |
|
const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 0; |
|
const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 1; |
|
const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 2; |
|
const ecs_entity_t EcsFlecsInternals = FLECS_HI_COMPONENT_ID + 3; |
|
const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 4; |
|
const ecs_entity_t EcsPrivate = FLECS_HI_COMPONENT_ID + 5; |
|
const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 6; |
|
const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 7; |
|
|
|
const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 8; |
|
const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 9; |
|
|
|
/* Relationship properties */ |
|
const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 10; |
|
const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 11; |
|
const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 12; |
|
const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 13; |
|
const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 14; |
|
const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 15; |
|
const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 16; |
|
const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 17; |
|
const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 18; |
|
const ecs_entity_t EcsAlwaysOverride = FLECS_HI_COMPONENT_ID + 19; |
|
const ecs_entity_t EcsTag = FLECS_HI_COMPONENT_ID + 20; |
|
const ecs_entity_t EcsUnion = FLECS_HI_COMPONENT_ID + 21; |
|
const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 22; |
|
const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 23; |
|
const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 24; |
|
const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 25; |
|
const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 26; |
|
|
|
/* Builtin relationships */ |
|
const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 27; |
|
const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 28; |
|
const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 29; |
|
|
|
/* Identifier tags */ |
|
const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 30; |
|
const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 31; |
|
const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 32; |
|
|
|
/* Events */ |
|
const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 33; |
|
const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 34; |
|
const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 35; |
|
const ecs_entity_t EcsUnSet = FLECS_HI_COMPONENT_ID + 36; |
|
const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 37; |
|
const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 38; |
|
const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 39; |
|
const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 40; |
|
const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 41; |
|
const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 46; |
|
|
|
/* Timers */ |
|
const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 47; |
|
const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 48; |
|
const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 49; |
|
|
|
/* Actions */ |
|
const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 50; |
|
const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 51; |
|
const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 52; |
|
|
|
/* Misc */ |
|
const ecs_entity_t ecs_id(EcsTarget) = FLECS_HI_COMPONENT_ID + 53; |
|
const ecs_entity_t EcsFlatten = FLECS_HI_COMPONENT_ID + 54; |
|
const ecs_entity_t EcsDefaultChildComponent = FLECS_HI_COMPONENT_ID + 55; |
|
|
|
/* Builtin predicate ids (used by rule engine) */ |
|
const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 56; |
|
const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 57; |
|
const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 58; |
|
const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 59; |
|
const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 60; |
|
|
|
/* Systems */ |
|
const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 61; |
|
const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 62; |
|
const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 63; |
|
const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 64; |
|
const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 65; |
|
const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 66; |
|
const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 67; |
|
const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 68; |
|
const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 69; |
|
const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 70; |
|
const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 71; |
|
const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 72; |
|
const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 73; |
|
const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 74; |
|
const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 75; |
|
|
|
/* Meta primitive components (don't use low ids to save id space) */ |
|
const ecs_entity_t ecs_id(ecs_bool_t) = FLECS_HI_COMPONENT_ID + 80; |
|
const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 81; |
|
const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 82; |
|
const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 83; |
|
const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 84; |
|
const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 85; |
|
const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 86; |
|
const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 87; |
|
const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 88; |
|
const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 89; |
|
const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 90; |
|
const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 91; |
|
const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 92; |
|
const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 93; |
|
const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 94; |
|
const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 95; |
|
const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 96; |
|
const ecs_entity_t ecs_id(ecs_id_t) = FLECS_HI_COMPONENT_ID + 97; |
|
|
|
/** Meta module component ids */ |
|
const ecs_entity_t ecs_id(EcsMetaType) = FLECS_HI_COMPONENT_ID + 98; |
|
const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = FLECS_HI_COMPONENT_ID + 99; |
|
const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 100; |
|
const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 101; |
|
const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 102; |
|
const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 103; |
|
const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 104; |
|
const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 105; |
|
const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 106; |
|
const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 107; |
|
const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 108; |
|
const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 109; |
|
const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 110; |
|
const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 111; |
|
const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 112; |
|
|
|
/* Doc module components */ |
|
const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 113; |
|
const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 114; |
|
const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 115; |
|
const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 116; |
|
const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 117; |
|
|
|
/* REST module components */ |
|
const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 118; |
|
|
|
/* Default lookup path */ |
|
static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; |
|
|
|
/* Declarations for addons. Located in world.c to avoid issues during linking of |
|
* static library */ |
|
#ifdef FLECS_ALERTS |
|
ECS_COMPONENT_DECLARE(EcsAlert); |
|
ECS_COMPONENT_DECLARE(EcsAlertInstance); |
|
ECS_COMPONENT_DECLARE(EcsAlertsActive); |
|
ECS_TAG_DECLARE(EcsAlertInfo); |
|
ECS_TAG_DECLARE(EcsAlertWarning); |
|
ECS_TAG_DECLARE(EcsAlertError); |
|
ECS_TAG_DECLARE(EcsAlertCritical); |
|
#endif |
|
#ifdef FLECS_UNITS |
|
ECS_DECLARE(EcsUnitPrefixes); |
|
|
|
ECS_DECLARE(EcsYocto); |
|
ECS_DECLARE(EcsZepto); |
|
ECS_DECLARE(EcsAtto); |
|
ECS_DECLARE(EcsFemto); |
|
ECS_DECLARE(EcsPico); |
|
ECS_DECLARE(EcsNano); |
|
ECS_DECLARE(EcsMicro); |
|
ECS_DECLARE(EcsMilli); |
|
ECS_DECLARE(EcsCenti); |
|
ECS_DECLARE(EcsDeci); |
|
ECS_DECLARE(EcsDeca); |
|
ECS_DECLARE(EcsHecto); |
|
ECS_DECLARE(EcsKilo); |
|
ECS_DECLARE(EcsMega); |
|
ECS_DECLARE(EcsGiga); |
|
ECS_DECLARE(EcsTera); |
|
ECS_DECLARE(EcsPeta); |
|
ECS_DECLARE(EcsExa); |
|
ECS_DECLARE(EcsZetta); |
|
ECS_DECLARE(EcsYotta); |
|
|
|
ECS_DECLARE(EcsKibi); |
|
ECS_DECLARE(EcsMebi); |
|
ECS_DECLARE(EcsGibi); |
|
ECS_DECLARE(EcsTebi); |
|
ECS_DECLARE(EcsPebi); |
|
ECS_DECLARE(EcsExbi); |
|
ECS_DECLARE(EcsZebi); |
|
ECS_DECLARE(EcsYobi); |
|
|
|
ECS_DECLARE(EcsDuration); |
|
ECS_DECLARE(EcsPicoSeconds); |
|
ECS_DECLARE(EcsNanoSeconds); |
|
ECS_DECLARE(EcsMicroSeconds); |
|
ECS_DECLARE(EcsMilliSeconds); |
|
ECS_DECLARE(EcsSeconds); |
|
ECS_DECLARE(EcsMinutes); |
|
ECS_DECLARE(EcsHours); |
|
ECS_DECLARE(EcsDays); |
|
|
|
ECS_DECLARE(EcsTime); |
|
ECS_DECLARE(EcsDate); |
|
|
|
ECS_DECLARE(EcsMass); |
|
ECS_DECLARE(EcsGrams); |
|
ECS_DECLARE(EcsKiloGrams); |
|
|
|
ECS_DECLARE(EcsElectricCurrent); |
|
ECS_DECLARE(EcsAmpere); |
|
|
|
ECS_DECLARE(EcsAmount); |
|
ECS_DECLARE(EcsMole); |
|
|
|
ECS_DECLARE(EcsLuminousIntensity); |
|
ECS_DECLARE(EcsCandela); |
|
|
|
ECS_DECLARE(EcsForce); |
|
ECS_DECLARE(EcsNewton); |
|
|
|
ECS_DECLARE(EcsLength); |
|
ECS_DECLARE(EcsMeters); |
|
ECS_DECLARE(EcsPicoMeters); |
|
ECS_DECLARE(EcsNanoMeters); |
|
ECS_DECLARE(EcsMicroMeters); |
|
ECS_DECLARE(EcsMilliMeters); |
|
ECS_DECLARE(EcsCentiMeters); |
|
ECS_DECLARE(EcsKiloMeters); |
|
ECS_DECLARE(EcsMiles); |
|
ECS_DECLARE(EcsPixels); |
|
|
|
ECS_DECLARE(EcsPressure); |
|
ECS_DECLARE(EcsPascal); |
|
ECS_DECLARE(EcsBar); |
|
|
|
ECS_DECLARE(EcsSpeed); |
|
ECS_DECLARE(EcsMetersPerSecond); |
|
ECS_DECLARE(EcsKiloMetersPerSecond); |
|
ECS_DECLARE(EcsKiloMetersPerHour); |
|
ECS_DECLARE(EcsMilesPerHour); |
|
|
|
ECS_DECLARE(EcsAcceleration); |
|
|
|
ECS_DECLARE(EcsTemperature); |
|
ECS_DECLARE(EcsKelvin); |
|
ECS_DECLARE(EcsCelsius); |
|
ECS_DECLARE(EcsFahrenheit); |
|
|
|
ECS_DECLARE(EcsData); |
|
ECS_DECLARE(EcsBits); |
|
ECS_DECLARE(EcsKiloBits); |
|
ECS_DECLARE(EcsMegaBits); |
|
ECS_DECLARE(EcsGigaBits); |
|
ECS_DECLARE(EcsBytes); |
|
ECS_DECLARE(EcsKiloBytes); |
|
ECS_DECLARE(EcsMegaBytes); |
|
ECS_DECLARE(EcsGigaBytes); |
|
ECS_DECLARE(EcsKibiBytes); |
|
ECS_DECLARE(EcsGibiBytes); |
|
ECS_DECLARE(EcsMebiBytes); |
|
|
|
ECS_DECLARE(EcsDataRate); |
|
ECS_DECLARE(EcsBitsPerSecond); |
|
ECS_DECLARE(EcsKiloBitsPerSecond); |
|
ECS_DECLARE(EcsMegaBitsPerSecond); |
|
ECS_DECLARE(EcsGigaBitsPerSecond); |
|
ECS_DECLARE(EcsBytesPerSecond); |
|
ECS_DECLARE(EcsKiloBytesPerSecond); |
|
ECS_DECLARE(EcsMegaBytesPerSecond); |
|
ECS_DECLARE(EcsGigaBytesPerSecond); |
|
|
|
ECS_DECLARE(EcsPercentage); |
|
|
|
ECS_DECLARE(EcsAngle); |
|
ECS_DECLARE(EcsRadians); |
|
ECS_DECLARE(EcsDegrees); |
|
|
|
ECS_DECLARE(EcsBel); |
|
ECS_DECLARE(EcsDeciBel); |
|
|
|
ECS_DECLARE(EcsFrequency); |
|
ECS_DECLARE(EcsHertz); |
|
ECS_DECLARE(EcsKiloHertz); |
|
ECS_DECLARE(EcsMegaHertz); |
|
ECS_DECLARE(EcsGigaHertz); |
|
|
|
ECS_DECLARE(EcsUri); |
|
ECS_DECLARE(EcsUriHyperlink); |
|
ECS_DECLARE(EcsUriImage); |
|
ECS_DECLARE(EcsUriFile); |
|
#endif |
|
|
|
/* -- Private functions -- */ |
|
|
|
const ecs_stage_t* flecs_stage_from_readonly_world( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_assert(ecs_poly_is(world, ecs_world_t) || |
|
ecs_poly_is(world, ecs_stage_t), |
|
ECS_INTERNAL_ERROR, |
|
NULL); |
|
|
|
if (ecs_poly_is(world, ecs_world_t)) { |
|
return &world->stages[0]; |
|
|
|
} else if (ecs_poly_is(world, ecs_stage_t)) { |
|
return ECS_CONST_CAST(ecs_stage_t*, world); |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
ecs_stage_t* flecs_stage_from_world( |
|
ecs_world_t **world_ptr) |
|
{ |
|
ecs_world_t *world = *world_ptr; |
|
|
|
ecs_assert(ecs_poly_is(world, ecs_world_t) || |
|
ecs_poly_is(world, ecs_stage_t), |
|
ECS_INTERNAL_ERROR, |
|
NULL); |
|
|
|
if (ecs_poly_is(world, ecs_world_t)) { |
|
return &world->stages[0]; |
|
} |
|
|
|
*world_ptr = ((ecs_stage_t*)world)->world; |
|
return ECS_CONST_CAST(ecs_stage_t*, world); |
|
} |
|
|
|
ecs_world_t* flecs_suspend_readonly( |
|
const ecs_world_t *stage_world, |
|
ecs_suspend_readonly_state_t *state) |
|
{ |
|
ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_world_t *world = |
|
ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); |
|
ecs_world_t *temp_world = world; |
|
ecs_stage_t *stage = flecs_stage_from_world(&temp_world); |
|
|
|
if (!is_readonly && !stage->defer) { |
|
state->is_readonly = false; |
|
state->is_deferred = false; |
|
return world; |
|
} |
|
|
|
ecs_dbg_3("suspending readonly mode"); |
|
|
|
/* Cannot suspend when running with multiple threads */ |
|
ecs_assert(!(world->flags & EcsWorldReadonly) || |
|
(ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); |
|
|
|
state->is_readonly = is_readonly; |
|
state->is_deferred = stage->defer != 0; |
|
|
|
/* Silence readonly checks */ |
|
world->flags &= ~EcsWorldReadonly; |
|
|
|
/* Hack around safety checks (this ought to look ugly) */ |
|
state->defer_count = stage->defer; |
|
state->commands = stage->cmd->queue; |
|
state->defer_stack = stage->cmd->stack; |
|
flecs_stack_init(&stage->cmd->stack); |
|
state->scope = stage->scope; |
|
state->with = stage->with; |
|
stage->defer = 0; |
|
ecs_vec_init_t(NULL, &stage->cmd->queue, ecs_cmd_t, 0); |
|
|
|
return world; |
|
} |
|
|
|
void flecs_resume_readonly( |
|
ecs_world_t *world, |
|
ecs_suspend_readonly_state_t *state) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_world_t *temp_world = world; |
|
ecs_stage_t *stage = flecs_stage_from_world(&temp_world); |
|
|
|
if (state->is_readonly || state->is_deferred) { |
|
ecs_dbg_3("resuming readonly mode"); |
|
|
|
ecs_run_aperiodic(world, 0); |
|
|
|
/* Restore readonly state / defer count */ |
|
ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); |
|
stage->defer = state->defer_count; |
|
ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); |
|
stage->cmd->queue = state->commands; |
|
flecs_stack_fini(&stage->cmd->stack); |
|
stage->cmd->stack = state->defer_stack; |
|
stage->scope = state->scope; |
|
stage->with = state->with; |
|
} |
|
} |
|
|
|
/* Evaluate component monitor. If a monitored entity changed it will have set a |
|
* flag in one of the world's component monitors. Queries can register |
|
* themselves with component monitors to determine whether they need to rematch |
|
* with tables. */ |
|
static |
|
void flecs_eval_component_monitor( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
if (!world->monitors.is_dirty) { |
|
return; |
|
} |
|
|
|
world->monitors.is_dirty = false; |
|
|
|
ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); |
|
while (ecs_map_next(&it)) { |
|
ecs_monitor_t *m = ecs_map_ptr(&it); |
|
if (!m->is_dirty) { |
|
continue; |
|
} |
|
|
|
m->is_dirty = false; |
|
|
|
int32_t i, count = ecs_vec_count(&m->queries); |
|
ecs_query_t **elems = ecs_vec_first(&m->queries); |
|
for (i = 0; i < count; i ++) { |
|
ecs_query_t *q = elems[i]; |
|
flecs_query_notify(world, q, &(ecs_query_event_t) { |
|
.kind = EcsQueryTableRematch |
|
}); |
|
} |
|
} |
|
} |
|
|
|
void flecs_monitor_mark_dirty( |
|
ecs_world_t *world, |
|
ecs_entity_t id) |
|
{ |
|
ecs_map_t *monitors = &world->monitors.monitors; |
|
|
|
/* Only flag if there are actually monitors registered, so that we |
|
* don't waste cycles evaluating monitors if there's no interest */ |
|
if (ecs_map_is_init(monitors)) { |
|
ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); |
|
if (m) { |
|
if (!world->monitors.is_dirty) { |
|
world->monitor_generation ++; |
|
} |
|
m->is_dirty = true; |
|
world->monitors.is_dirty = true; |
|
} |
|
} |
|
} |
|
|
|
void flecs_monitor_register( |
|
ecs_world_t *world, |
|
ecs_entity_t id, |
|
ecs_query_t *query) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_map_t *monitors = &world->monitors.monitors; |
|
ecs_map_init_if(monitors, &world->allocator); |
|
ecs_monitor_t *m = ecs_map_ensure_alloc_t(monitors, ecs_monitor_t, id); |
|
ecs_vec_init_if_t(&m->queries, ecs_query_t*); |
|
ecs_query_t **q = ecs_vec_append_t( |
|
&world->allocator, &m->queries, ecs_query_t*); |
|
*q = query; |
|
} |
|
|
|
void flecs_monitor_unregister( |
|
ecs_world_t *world, |
|
ecs_entity_t id, |
|
ecs_query_t *query) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_map_t *monitors = &world->monitors.monitors; |
|
if (!ecs_map_is_init(monitors)) { |
|
return; |
|
} |
|
|
|
ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); |
|
if (!m) { |
|
return; |
|
} |
|
|
|
int32_t i, count = ecs_vec_count(&m->queries); |
|
ecs_query_t **queries = ecs_vec_first(&m->queries); |
|
for (i = 0; i < count; i ++) { |
|
if (queries[i] == query) { |
|
ecs_vec_remove_t(&m->queries, ecs_query_t*, i); |
|
count --; |
|
break; |
|
} |
|
} |
|
|
|
if (!count) { |
|
ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); |
|
ecs_map_remove_free(monitors, id); |
|
} |
|
|
|
if (!ecs_map_count(monitors)) { |
|
ecs_map_fini(monitors); |
|
} |
|
} |
|
|
|
static |
|
void flecs_init_store( |
|
ecs_world_t *world) |
|
{ |
|
ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); |
|
|
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_vec_init_t(a, &world->store.records, ecs_table_record_t, 0); |
|
ecs_vec_init_t(a, &world->store.marked_ids, ecs_marked_id_t, 0); |
|
ecs_vec_init_t(a, &world->store.depth_ids, ecs_entity_t, 0); |
|
ecs_map_init(&world->store.entity_to_depth, &world->allocator); |
|
|
|
/* Initialize entity index */ |
|
flecs_entities_init(world); |
|
|
|
/* Initialize root table */ |
|
flecs_sparse_init_t(&world->store.tables, |
|
a, &world->allocators.sparse_chunk, ecs_table_t); |
|
|
|
/* Initialize table map */ |
|
flecs_table_hashmap_init(world, &world->store.table_map); |
|
|
|
/* Initialize one root table per stage */ |
|
flecs_init_root_table(world); |
|
} |
|
|
|
static |
|
void flecs_clean_tables( |
|
ecs_world_t *world) |
|
{ |
|
int32_t i, count = flecs_sparse_count(&world->store.tables); |
|
|
|
/* Ensure that first table in sparse set has id 0. This is a dummy table |
|
* that only exists so that there is no table with id 0 */ |
|
ecs_table_t *first = flecs_sparse_get_dense_t(&world->store.tables, |
|
ecs_table_t, 0); |
|
(void)first; |
|
|
|
for (i = 1; i < count; i ++) { |
|
ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, |
|
ecs_table_t, i); |
|
flecs_table_free(world, t); |
|
} |
|
|
|
/* Free table types separately so that if application destructors rely on |
|
* a type it's still valid. */ |
|
for (i = 1; i < count; i ++) { |
|
ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, |
|
ecs_table_t, i); |
|
flecs_table_free_type(world, t); |
|
} |
|
|
|
/* Clear the root table */ |
|
if (count) { |
|
flecs_table_reset(world, &world->store.root); |
|
} |
|
} |
|
|
|
static |
|
void flecs_fini_root_tables( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr, |
|
bool fini_targets) |
|
{ |
|
ecs_table_cache_iter_t it; |
|
|
|
bool has_roots = flecs_table_cache_iter(&idr->cache, &it); |
|
ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); |
|
(void)has_roots; |
|
|
|
const ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
if (table->flags & EcsTableHasBuiltins) { |
|
continue; /* Filter out modules */ |
|
} |
|
|
|
int32_t i, count = table->data.entities.count; |
|
ecs_entity_t *entities = table->data.entities.array; |
|
|
|
if (fini_targets) { |
|
/* Only delete entities that are used as pair target. Iterate |
|
* backwards to minimize moving entities around in table. */ |
|
for (i = count - 1; i >= 0; i --) { |
|
ecs_record_t *r = flecs_entities_get(world, entities[i]); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { |
|
ecs_delete(world, entities[i]); |
|
} |
|
} |
|
} else { |
|
/* Delete remaining entities that are not in use (added to another |
|
* entity). This limits table moves during cleanup and delays |
|
* cleanup of tags. */ |
|
for (i = count - 1; i >= 0; i --) { |
|
ecs_record_t *r = flecs_entities_get(world, entities[i]); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { |
|
ecs_delete(world, entities[i]); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_fini_roots( |
|
ecs_world_t *world) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); |
|
|
|
ecs_run_aperiodic(world, EcsAperiodicEmptyTables); |
|
|
|
/* Delete root entities that are not modules. This prioritizes deleting |
|
* regular entities first, which reduces the chance of components getting |
|
* destructed in random order because it got deleted before entities, |
|
* thereby bypassing the OnDeleteTarget policy. */ |
|
flecs_defer_begin(world, &world->stages[0]); |
|
flecs_fini_root_tables(world, idr, true); |
|
flecs_defer_end(world, &world->stages[0]); |
|
|
|
flecs_defer_begin(world, &world->stages[0]); |
|
flecs_fini_root_tables(world, idr, false); |
|
flecs_defer_end(world, &world->stages[0]); |
|
} |
|
|
|
static |
|
void flecs_fini_store(ecs_world_t *world) { |
|
flecs_clean_tables(world); |
|
flecs_sparse_fini(&world->store.tables); |
|
flecs_table_free(world, &world->store.root); |
|
flecs_entities_clear(world); |
|
flecs_hashmap_fini(&world->store.table_map); |
|
|
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); |
|
ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); |
|
ecs_vec_fini_t(a, &world->store.depth_ids, ecs_entity_t); |
|
ecs_map_fini(&world->store.entity_to_depth); |
|
} |
|
|
|
/* Implementation for iterable mixin */ |
|
static |
|
bool flecs_world_iter_next( |
|
ecs_iter_t *it) |
|
{ |
|
if (ECS_BIT_IS_SET(it->flags, EcsIterIsValid)) { |
|
ECS_BIT_CLEAR(it->flags, EcsIterIsValid); |
|
ecs_iter_fini(it); |
|
return false; |
|
} |
|
|
|
ecs_world_t *world = it->real_world; |
|
it->entities = ECS_CONST_CAST(ecs_entity_t*, flecs_entities_ids(world)); |
|
it->count = flecs_entities_count(world); |
|
flecs_iter_validate(it); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
void flecs_world_iter_init( |
|
const ecs_world_t *world, |
|
const ecs_poly_t *poly, |
|
ecs_iter_t *iter, |
|
ecs_term_t *filter) |
|
{ |
|
ecs_poly_assert(poly, ecs_world_t); |
|
(void)poly; |
|
|
|
if (filter) { |
|
iter[0] = ecs_term_iter(world, filter); |
|
} else { |
|
iter[0] = (ecs_iter_t){ |
|
.world = ECS_CONST_CAST(ecs_world_t*, world), |
|
.real_world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)), |
|
.next = flecs_world_iter_next |
|
}; |
|
} |
|
} |
|
|
|
static |
|
void flecs_world_allocators_init( |
|
ecs_world_t *world) |
|
{ |
|
ecs_world_allocators_t *a = &world->allocators; |
|
|
|
flecs_allocator_init(&world->allocator); |
|
|
|
ecs_map_params_init(&a->ptr, &world->allocator); |
|
ecs_map_params_init(&a->query_table_list, &world->allocator); |
|
|
|
flecs_ballocator_init_t(&a->query_table, ecs_query_table_t); |
|
flecs_ballocator_init_t(&a->query_table_match, ecs_query_table_match_t); |
|
flecs_ballocator_init_n(&a->graph_edge_lo, ecs_graph_edge_t, FLECS_HI_COMPONENT_ID); |
|
flecs_ballocator_init_t(&a->graph_edge, ecs_graph_edge_t); |
|
flecs_ballocator_init_t(&a->id_record, ecs_id_record_t); |
|
flecs_ballocator_init_n(&a->id_record_chunk, ecs_id_record_t, FLECS_SPARSE_PAGE_SIZE); |
|
flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t); |
|
flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_PAGE_SIZE); |
|
flecs_ballocator_init_t(&a->hashmap, ecs_hashmap_t); |
|
flecs_table_diff_builder_init(world, &world->allocators.diff_builder); |
|
} |
|
|
|
static |
|
void flecs_world_allocators_fini( |
|
ecs_world_t *world) |
|
{ |
|
ecs_world_allocators_t *a = &world->allocators; |
|
|
|
ecs_map_params_fini(&a->ptr); |
|
ecs_map_params_fini(&a->query_table_list); |
|
|
|
flecs_ballocator_fini(&a->query_table); |
|
flecs_ballocator_fini(&a->query_table_match); |
|
flecs_ballocator_fini(&a->graph_edge_lo); |
|
flecs_ballocator_fini(&a->graph_edge); |
|
flecs_ballocator_fini(&a->id_record); |
|
flecs_ballocator_fini(&a->id_record_chunk); |
|
flecs_ballocator_fini(&a->table_diff); |
|
flecs_ballocator_fini(&a->sparse_chunk); |
|
flecs_ballocator_fini(&a->hashmap); |
|
flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); |
|
|
|
flecs_allocator_fini(&world->allocator); |
|
} |
|
|
|
static |
|
void flecs_log_addons(void) { |
|
ecs_trace("addons included in build:"); |
|
ecs_log_push(); |
|
#ifdef FLECS_CPP |
|
ecs_trace("FLECS_CPP"); |
|
#endif |
|
#ifdef FLECS_MODULE |
|
ecs_trace("FLECS_MODULE"); |
|
#endif |
|
#ifdef FLECS_PARSER |
|
ecs_trace("FLECS_PARSER"); |
|
#endif |
|
#ifdef FLECS_PLECS |
|
ecs_trace("FLECS_PLECS"); |
|
#endif |
|
#ifdef FLECS_RULES |
|
ecs_trace("FLECS_RULES"); |
|
#endif |
|
#ifdef FLECS_SNAPSHOT |
|
ecs_trace("FLECS_SNAPSHOT"); |
|
#endif |
|
#ifdef FLECS_STATS |
|
ecs_trace("FLECS_STATS"); |
|
#endif |
|
#ifdef FLECS_MONITOR |
|
ecs_trace("FLECS_MONITOR"); |
|
#endif |
|
#ifdef FLECS_METRICS |
|
ecs_trace("FLECS_METRICS"); |
|
#endif |
|
#ifdef FLECS_SYSTEM |
|
ecs_trace("FLECS_SYSTEM"); |
|
#endif |
|
#ifdef FLECS_PIPELINE |
|
ecs_trace("FLECS_PIPELINE"); |
|
#endif |
|
#ifdef FLECS_TIMER |
|
ecs_trace("FLECS_TIMER"); |
|
#endif |
|
#ifdef FLECS_META |
|
ecs_trace("FLECS_META"); |
|
#endif |
|
#ifdef FLECS_META_C |
|
ecs_trace("FLECS_META_C"); |
|
#endif |
|
#ifdef FLECS_UNITS |
|
ecs_trace("FLECS_UNITS"); |
|
#endif |
|
#ifdef FLECS_EXPR |
|
ecs_trace("FLECS_EXPR"); |
|
#endif |
|
#ifdef FLECS_JSON |
|
ecs_trace("FLECS_JSON"); |
|
#endif |
|
#ifdef FLECS_DOC |
|
ecs_trace("FLECS_DOC"); |
|
#endif |
|
#ifdef FLECS_COREDOC |
|
ecs_trace("FLECS_COREDOC"); |
|
#endif |
|
#ifdef FLECS_LOG |
|
ecs_trace("FLECS_LOG"); |
|
#endif |
|
#ifdef FLECS_JOURNAL |
|
ecs_trace("FLECS_JOURNAL"); |
|
#endif |
|
#ifdef FLECS_APP |
|
ecs_trace("FLECS_APP"); |
|
#endif |
|
#ifdef FLECS_OS_API_IMPL |
|
ecs_trace("FLECS_OS_API_IMPL"); |
|
#endif |
|
#ifdef FLECS_SCRIPT |
|
ecs_trace("FLECS_SCRIPT"); |
|
#endif |
|
#ifdef FLECS_HTTP |
|
ecs_trace("FLECS_HTTP"); |
|
#endif |
|
#ifdef FLECS_REST |
|
ecs_trace("FLECS_REST"); |
|
#endif |
|
ecs_log_pop(); |
|
} |
|
|
|
/* -- Public functions -- */ |
|
|
|
ecs_world_t *ecs_mini(void) { |
|
#ifdef FLECS_OS_API_IMPL |
|
ecs_set_os_api_impl(); |
|
#endif |
|
ecs_os_init(); |
|
|
|
ecs_trace("#[bold]bootstrapping world"); |
|
ecs_log_push(); |
|
|
|
ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); |
|
|
|
if (!ecs_os_has_heap()) { |
|
ecs_abort(ECS_MISSING_OS_API, NULL); |
|
} |
|
|
|
if (!ecs_os_has_threading()) { |
|
ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); |
|
} |
|
|
|
if (!ecs_os_has_time()) { |
|
ecs_trace("time management not available"); |
|
} |
|
|
|
flecs_log_addons(); |
|
|
|
#ifdef FLECS_SANITIZE |
|
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 " |
|
"performance"); |
|
#else |
|
ecs_trace("#[green]release#[reset] build"); |
|
#endif |
|
|
|
#ifdef __clang__ |
|
ecs_trace("compiled with clang %s", __clang_version__); |
|
#elif defined(__GNUC__) |
|
ecs_trace("compiled with gcc %d.%d", __GNUC__, __GNUC_MINOR__); |
|
#elif defined (_MSC_VER) |
|
ecs_trace("compiled with msvc %d", _MSC_VER); |
|
#elif defined (__TINYC__) |
|
ecs_trace("compiled with tcc %d", __TINYC__); |
|
#endif |
|
|
|
ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); |
|
ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
ecs_poly_init(world, ecs_world_t); |
|
|
|
world->flags |= EcsWorldInit; |
|
|
|
flecs_world_allocators_init(world); |
|
ecs_allocator_t *a = &world->allocator; |
|
|
|
world->self = world; |
|
flecs_sparse_init_t(&world->type_info, a, |
|
&world->allocators.sparse_chunk, ecs_type_info_t); |
|
ecs_map_init_w_params(&world->id_index_hi, &world->allocators.ptr); |
|
world->id_index_lo = ecs_os_calloc_n(ecs_id_record_t, FLECS_HI_ID_RECORD_ID); |
|
flecs_observable_init(&world->observable); |
|
world->iterable.init = flecs_world_iter_init; |
|
|
|
world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); |
|
flecs_sparse_init_t(world->pending_tables, a, |
|
&world->allocators.sparse_chunk, ecs_table_t*); |
|
world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); |
|
flecs_sparse_init_t(world->pending_buffer, a, |
|
&world->allocators.sparse_chunk, ecs_table_t*); |
|
|
|
flecs_name_index_init(&world->aliases, a); |
|
flecs_name_index_init(&world->symbols, a); |
|
ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); |
|
|
|
world->info.time_scale = 1.0; |
|
if (ecs_os_has_time()) { |
|
ecs_os_get_time(&world->world_start_time); |
|
} |
|
|
|
ecs_set_stage_count(world, 1); |
|
ecs_default_lookup_path[0] = EcsFlecsCore; |
|
ecs_set_lookup_path(world, ecs_default_lookup_path); |
|
flecs_init_store(world); |
|
|
|
flecs_bootstrap(world); |
|
|
|
world->flags &= ~EcsWorldInit; |
|
|
|
ecs_trace("world ready!"); |
|
ecs_log_pop(); |
|
|
|
return world; |
|
} |
|
|
|
ecs_world_t *ecs_init(void) { |
|
ecs_world_t *world = ecs_mini(); |
|
|
|
#ifdef FLECS_MODULE_H |
|
ecs_trace("#[bold]import addons"); |
|
ecs_log_push(); |
|
ecs_trace("use ecs_mini to create world without importing addons"); |
|
#ifdef FLECS_SYSTEM |
|
ECS_IMPORT(world, FlecsSystem); |
|
#endif |
|
#ifdef FLECS_PIPELINE |
|
ECS_IMPORT(world, FlecsPipeline); |
|
#endif |
|
#ifdef FLECS_TIMER |
|
ECS_IMPORT(world, FlecsTimer); |
|
#endif |
|
#ifdef FLECS_META |
|
ECS_IMPORT(world, FlecsMeta); |
|
#endif |
|
#ifdef FLECS_DOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
#endif |
|
#ifdef FLECS_COREDOC |
|
ECS_IMPORT(world, FlecsCoreDoc); |
|
#endif |
|
#ifdef FLECS_SCRIPT |
|
ECS_IMPORT(world, FlecsScript); |
|
#endif |
|
#ifdef FLECS_REST |
|
ECS_IMPORT(world, FlecsRest); |
|
#endif |
|
#ifdef FLECS_UNITS |
|
ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); |
|
#endif |
|
ecs_trace("addons imported!"); |
|
ecs_log_pop(); |
|
#endif |
|
return world; |
|
} |
|
|
|
ecs_world_t* ecs_init_w_args( |
|
int argc, |
|
char *argv[]) |
|
{ |
|
ecs_world_t *world = ecs_init(); |
|
|
|
(void)argc; |
|
(void)argv; |
|
|
|
#ifdef FLECS_DOC |
|
if (argc) { |
|
char *app = argv[0]; |
|
char *last_elem = strrchr(app, '/'); |
|
if (!last_elem) { |
|
last_elem = strrchr(app, '\\'); |
|
} |
|
if (last_elem) { |
|
app = last_elem + 1; |
|
} |
|
ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); |
|
} |
|
#endif |
|
|
|
return world; |
|
} |
|
|
|
void ecs_quit( |
|
ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_stage_from_world(&world); |
|
world->flags |= EcsWorldQuit; |
|
error: |
|
return; |
|
} |
|
|
|
bool ecs_should_quit( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
world = ecs_get_world(world); |
|
return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); |
|
error: |
|
return true; |
|
} |
|
|
|
void flecs_notify_tables( |
|
ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_table_event_t *event) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
/* If no id is specified, broadcast to all tables */ |
|
if (!id) { |
|
ecs_sparse_t *tables = &world->store.tables; |
|
int32_t i, count = flecs_sparse_count(tables); |
|
for (i = 0; i < count; i ++) { |
|
ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); |
|
flecs_table_notify(world, table, event); |
|
} |
|
|
|
/* If id is specified, only broadcast to tables with id */ |
|
} else { |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
return; |
|
} |
|
|
|
ecs_table_cache_iter_t it; |
|
const ecs_table_record_t *tr; |
|
|
|
flecs_table_cache_all_iter(&idr->cache, &it); |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
flecs_table_notify(world, tr->hdr.table, event); |
|
} |
|
} |
|
} |
|
|
|
void ecs_default_ctor( |
|
void *ptr, |
|
int32_t count, |
|
const ecs_type_info_t *ti) |
|
{ |
|
ecs_os_memset(ptr, 0, ti->size * count); |
|
} |
|
|
|
static |
|
void flecs_default_copy_ctor(void *dst_ptr, const void *src_ptr, |
|
int32_t count, const ecs_type_info_t *ti) |
|
{ |
|
const ecs_type_hooks_t *cl = &ti->hooks; |
|
cl->ctor(dst_ptr, count, ti); |
|
cl->copy(dst_ptr, src_ptr, count, ti); |
|
} |
|
|
|
static |
|
void flecs_default_move_ctor(void *dst_ptr, void *src_ptr, |
|
int32_t count, const ecs_type_info_t *ti) |
|
{ |
|
const ecs_type_hooks_t *cl = &ti->hooks; |
|
cl->ctor(dst_ptr, count, ti); |
|
cl->move(dst_ptr, src_ptr, count, ti); |
|
} |
|
|
|
static |
|
void flecs_default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, |
|
int32_t count, const ecs_type_info_t *ti) |
|
{ |
|
const ecs_type_hooks_t *cl = &ti->hooks; |
|
cl->ctor(dst_ptr, count, ti); |
|
cl->move(dst_ptr, src_ptr, count, ti); |
|
cl->dtor(src_ptr, count, ti); |
|
} |
|
|
|
static |
|
void flecs_default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, |
|
int32_t count, const ecs_type_info_t *ti) |
|
{ |
|
const ecs_type_hooks_t *cl = &ti->hooks; |
|
cl->move_ctor(dst_ptr, src_ptr, count, ti); |
|
cl->dtor(src_ptr, count, ti); |
|
} |
|
|
|
static |
|
void flecs_default_move(void *dst_ptr, void *src_ptr, |
|
int32_t count, const ecs_type_info_t *ti) |
|
{ |
|
const ecs_type_hooks_t *cl = &ti->hooks; |
|
cl->move(dst_ptr, src_ptr, count, ti); |
|
} |
|
|
|
static |
|
void flecs_default_dtor(void *dst_ptr, void *src_ptr, |
|
int32_t count, const ecs_type_info_t *ti) |
|
{ |
|
/* When there is no move, destruct the destination component & memcpy the |
|
* component to dst. The src component does not have to be destructed when |
|
* a component has a trivial move. */ |
|
const ecs_type_hooks_t *cl = &ti->hooks; |
|
cl->dtor(dst_ptr, count, ti); |
|
ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); |
|
} |
|
|
|
static |
|
void flecs_default_move_w_dtor(void *dst_ptr, void *src_ptr, |
|
int32_t count, const ecs_type_info_t *ti) |
|
{ |
|
/* If a component has a move, the move will take care of memcpying the data |
|
* and destroying any data in dst. Because this is not a trivial move, the |
|
* src component must also be destructed. */ |
|
const ecs_type_hooks_t *cl = &ti->hooks; |
|
cl->move(dst_ptr, src_ptr, count, ti); |
|
cl->dtor(src_ptr, count, ti); |
|
} |
|
|
|
void ecs_set_hooks_id( |
|
ecs_world_t *world, |
|
ecs_entity_t component, |
|
const ecs_type_hooks_t *h) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
flecs_stage_from_world(&world); |
|
|
|
/* Ensure that no tables have yet been created for the component */ |
|
ecs_assert( ecs_id_in_use(world, component) == false, |
|
ECS_ALREADY_IN_USE, ecs_get_name(world, component)); |
|
ecs_assert( ecs_id_in_use(world, ecs_pair(component, EcsWildcard)) == false, |
|
ECS_ALREADY_IN_USE, ecs_get_name(world, component)); |
|
|
|
ecs_type_info_t *ti = flecs_type_info_ensure(world, component); |
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_check(!ti->component || ti->component == component, |
|
ECS_INCONSISTENT_COMPONENT_ACTION, NULL); |
|
|
|
if (!ti->size) { |
|
const EcsComponent *component_ptr = ecs_get( |
|
world, component, EcsComponent); |
|
|
|
/* Cannot register lifecycle actions for things that aren't a component */ |
|
ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
/* Cannot register lifecycle actions for components with size 0 */ |
|
ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ti->size = component_ptr->size; |
|
ti->alignment = component_ptr->alignment; |
|
} |
|
|
|
if (h->ctor) ti->hooks.ctor = h->ctor; |
|
if (h->dtor) ti->hooks.dtor = h->dtor; |
|
if (h->copy) ti->hooks.copy = h->copy; |
|
if (h->move) ti->hooks.move = h->move; |
|
if (h->copy_ctor) ti->hooks.copy_ctor = h->copy_ctor; |
|
if (h->move_ctor) ti->hooks.move_ctor = h->move_ctor; |
|
if (h->ctor_move_dtor) ti->hooks.ctor_move_dtor = h->ctor_move_dtor; |
|
if (h->move_dtor) ti->hooks.move_dtor = h->move_dtor; |
|
|
|
if (h->on_add) ti->hooks.on_add = h->on_add; |
|
if (h->on_remove) ti->hooks.on_remove = h->on_remove; |
|
if (h->on_set) ti->hooks.on_set = h->on_set; |
|
|
|
if (h->ctx) ti->hooks.ctx = h->ctx; |
|
if (h->binding_ctx) ti->hooks.binding_ctx = h->binding_ctx; |
|
if (h->ctx_free) ti->hooks.ctx_free = h->ctx_free; |
|
if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_ctx_free; |
|
|
|
/* If no constructor is set, invoking any of the other lifecycle actions |
|
* is not safe as they will potentially access uninitialized memory. For |
|
* ease of use, if no constructor is specified, set a default one that |
|
* initializes the component to 0. */ |
|
if (!h->ctor && (h->dtor || h->copy || h->move)) { |
|
ti->hooks.ctor = ecs_default_ctor; |
|
} |
|
|
|
/* Set default copy ctor, move ctor and merge */ |
|
if (h->copy && !h->copy_ctor) { |
|
ti->hooks.copy_ctor = flecs_default_copy_ctor; |
|
} |
|
|
|
if (h->move && !h->move_ctor) { |
|
ti->hooks.move_ctor = flecs_default_move_ctor; |
|
} |
|
|
|
if (!h->ctor_move_dtor) { |
|
if (h->move) { |
|
if (h->dtor) { |
|
if (h->move_ctor) { |
|
/* If an explicit move ctor has been set, use callback |
|
* that uses the move ctor vs. using a ctor+move */ |
|
ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; |
|
} else { |
|
/* If no explicit move_ctor has been set, use |
|
* combination of ctor + move + dtor */ |
|
ti->hooks.ctor_move_dtor = flecs_default_ctor_w_move_w_dtor; |
|
} |
|
} else { |
|
/* If no dtor has been set, this is just a move ctor */ |
|
ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; |
|
} |
|
} else { |
|
/* If move is not set but move_ctor and dtor is, we can still set |
|
* ctor_move_dtor. */ |
|
if (h->move_ctor) { |
|
if (h->dtor) { |
|
ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; |
|
} else { |
|
ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (!h->move_dtor) { |
|
if (h->move) { |
|
if (h->dtor) { |
|
ti->hooks.move_dtor = flecs_default_move_w_dtor; |
|
} else { |
|
ti->hooks.move_dtor = flecs_default_move; |
|
} |
|
} else { |
|
if (h->dtor) { |
|
ti->hooks.move_dtor = flecs_default_dtor; |
|
} |
|
} |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
const ecs_type_hooks_t* ecs_get_hooks_id( |
|
ecs_world_t *world, |
|
ecs_entity_t id) |
|
{ |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, id); |
|
if (ti) { |
|
return &ti->hooks; |
|
} |
|
return NULL; |
|
} |
|
|
|
void ecs_atfini( |
|
ecs_world_t *world, |
|
ecs_fini_action_t action, |
|
void *ctx) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_action_elem_t *elem = ecs_vec_append_t(NULL, &world->fini_actions, |
|
ecs_action_elem_t); |
|
ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
elem->action = action; |
|
elem->ctx = ctx; |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_run_post_frame( |
|
ecs_world_t *world, |
|
ecs_fini_action_t action, |
|
void *ctx) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_action_elem_t *elem = ecs_vec_append_t(&stage->allocator, |
|
&stage->post_frame_actions, ecs_action_elem_t); |
|
ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
elem->action = action; |
|
elem->ctx = ctx; |
|
error: |
|
return; |
|
} |
|
|
|
/* Unset data in tables */ |
|
static |
|
void flecs_fini_unset_tables( |
|
ecs_world_t *world) |
|
{ |
|
ecs_sparse_t *tables = &world->store.tables; |
|
int32_t i, count = flecs_sparse_count(tables); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); |
|
flecs_table_remove_actions(world, table); |
|
} |
|
} |
|
|
|
/* Invoke fini actions */ |
|
static |
|
void flecs_fini_actions( |
|
ecs_world_t *world) |
|
{ |
|
int32_t i, count = ecs_vec_count(&world->fini_actions); |
|
ecs_action_elem_t *elems = ecs_vec_first(&world->fini_actions); |
|
for (i = 0; i < count; i ++) { |
|
elems[i].action(world, elems[i].ctx); |
|
} |
|
|
|
ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); |
|
} |
|
|
|
/* Cleanup remaining type info elements */ |
|
static |
|
void flecs_fini_type_info( |
|
ecs_world_t *world) |
|
{ |
|
int32_t i, count = flecs_sparse_count(&world->type_info); |
|
ecs_sparse_t *type_info = &world->type_info; |
|
for (i = 0; i < count; i ++) { |
|
ecs_type_info_t *ti = flecs_sparse_get_dense_t(type_info, |
|
ecs_type_info_t, i); |
|
flecs_type_info_fini(ti); |
|
} |
|
flecs_sparse_fini(&world->type_info); |
|
} |
|
|
|
ecs_entity_t flecs_get_oneof( |
|
const ecs_world_t *world, |
|
ecs_entity_t e) |
|
{ |
|
if (ecs_is_alive(world, e)) { |
|
if (ecs_has_id(world, e, EcsOneOf)) { |
|
return e; |
|
} else { |
|
return ecs_get_target(world, e, EcsOneOf, 0); |
|
} |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
/* The destroyer of worlds */ |
|
int ecs_fini( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); |
|
ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); |
|
ecs_assert(world->stages[0].defer == 0, ECS_INVALID_OPERATION, |
|
"call defer_end before destroying world"); |
|
|
|
ecs_trace("#[bold]shutting down world"); |
|
ecs_log_push(); |
|
|
|
world->flags |= EcsWorldQuit; |
|
|
|
/* Delete root entities first using regular APIs. This ensures that cleanup |
|
* policies get a chance to execute. */ |
|
ecs_dbg_1("#[bold]cleanup root entities"); |
|
ecs_log_push_1(); |
|
flecs_fini_roots(world); |
|
ecs_log_pop_1(); |
|
|
|
world->flags |= EcsWorldFini; |
|
|
|
/* Run fini actions (simple callbacks ran when world is deleted) before |
|
* destroying the storage */ |
|
ecs_dbg_1("#[bold]run fini actions"); |
|
ecs_log_push_1(); |
|
flecs_fini_actions(world); |
|
ecs_log_pop_1(); |
|
|
|
ecs_dbg_1("#[bold]cleanup remaining entities"); |
|
ecs_log_push_1(); |
|
|
|
/* Operations invoked during UnSet/OnRemove/destructors are deferred and |
|
* will be discarded after world cleanup */ |
|
flecs_defer_begin(world, &world->stages[0]); |
|
|
|
/* Run UnSet/OnRemove actions for components while the store is still |
|
* unmodified by cleanup. */ |
|
flecs_fini_unset_tables(world); |
|
|
|
/* This will destroy all entities and components. */ |
|
flecs_fini_store(world); |
|
|
|
/* Purge deferred operations from the queue. This discards operations but |
|
* makes sure that any resources in the queue are freed */ |
|
flecs_defer_purge(world, &world->stages[0]); |
|
ecs_log_pop_1(); |
|
|
|
/* All queries are cleaned up, so monitors should've been cleaned up too */ |
|
ecs_assert(!ecs_map_is_init(&world->monitors.monitors), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Cleanup world ctx and binding_ctx */ |
|
if (world->ctx_free) { |
|
world->ctx_free(world->ctx); |
|
} |
|
if (world->binding_ctx_free) { |
|
world->binding_ctx_free(world->binding_ctx); |
|
} |
|
|
|
/* After this point no more user code is invoked */ |
|
|
|
ecs_dbg_1("#[bold]cleanup world data structures"); |
|
ecs_log_push_1(); |
|
flecs_entities_fini(world); |
|
flecs_sparse_fini(world->pending_tables); |
|
flecs_sparse_fini(world->pending_buffer); |
|
ecs_os_free(world->pending_tables); |
|
ecs_os_free(world->pending_buffer); |
|
flecs_fini_id_records(world); |
|
flecs_fini_type_info(world); |
|
flecs_observable_fini(&world->observable); |
|
flecs_name_index_fini(&world->aliases); |
|
flecs_name_index_fini(&world->symbols); |
|
ecs_set_stage_count(world, 0); |
|
ecs_log_pop_1(); |
|
|
|
flecs_world_allocators_fini(world); |
|
|
|
/* End of the world */ |
|
ecs_poly_free(world, ecs_world_t); |
|
ecs_os_fini(); |
|
|
|
ecs_trace("world destroyed, bye!"); |
|
ecs_log_pop(); |
|
|
|
return 0; |
|
} |
|
|
|
bool ecs_is_fini( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
world = ecs_get_world(world); |
|
return ECS_BIT_IS_SET(world->flags, EcsWorldFini); |
|
} |
|
|
|
void ecs_dim( |
|
ecs_world_t *world, |
|
int32_t entity_count) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); |
|
} |
|
|
|
void flecs_eval_component_monitors( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
flecs_process_pending_tables(world); |
|
flecs_eval_component_monitor(world); |
|
} |
|
|
|
void ecs_measure_frame_time( |
|
ecs_world_t *world, |
|
bool enable) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); |
|
|
|
if (ECS_EQZERO(world->info.target_fps) || enable) { |
|
ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); |
|
} |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_measure_system_time( |
|
ecs_world_t *world, |
|
bool enable) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); |
|
ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_set_target_fps( |
|
ecs_world_t *world, |
|
ecs_ftime_t fps) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); |
|
|
|
ecs_measure_frame_time(world, true); |
|
world->info.target_fps = fps; |
|
error: |
|
return; |
|
} |
|
|
|
void* ecs_get_ctx( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
world = ecs_get_world(world); |
|
return world->ctx; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void* ecs_get_binding_ctx( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
world = ecs_get_world(world); |
|
return world->binding_ctx; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void ecs_set_ctx( |
|
ecs_world_t *world, |
|
void *ctx, |
|
ecs_ctx_free_t ctx_free) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
world->ctx = ctx; |
|
world->ctx_free = ctx_free; |
|
} |
|
|
|
void ecs_set_binding_ctx( |
|
ecs_world_t *world, |
|
void *ctx, |
|
ecs_ctx_free_t ctx_free) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
world->binding_ctx = ctx; |
|
world->binding_ctx_free = ctx_free; |
|
} |
|
|
|
void ecs_set_entity_range( |
|
ecs_world_t *world, |
|
ecs_entity_t id_start, |
|
ecs_entity_t id_end) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!id_end || id_end > flecs_entities_max_id(world), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
uint32_t start = (uint32_t)id_start; |
|
uint32_t end = (uint32_t)id_end; |
|
|
|
if (flecs_entities_max_id(world) < start) { |
|
flecs_entities_max_id(world) = start - 1; |
|
} |
|
|
|
world->info.min_id = start; |
|
world->info.max_id = end; |
|
error: |
|
return; |
|
} |
|
|
|
bool ecs_enable_range_check( |
|
ecs_world_t *world, |
|
bool enable) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
bool old_value = world->range_check_enabled; |
|
world->range_check_enabled = enable; |
|
return old_value; |
|
} |
|
|
|
ecs_entity_t ecs_get_max_id( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
world = ecs_get_world(world); |
|
return flecs_entities_max_id(world); |
|
error: |
|
return 0; |
|
} |
|
|
|
void ecs_set_entity_generation( |
|
ecs_world_t *world, |
|
ecs_entity_t entity_with_generation) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); |
|
ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, NULL); |
|
|
|
flecs_entities_set_generation(world, entity_with_generation); |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity_with_generation); |
|
if (r && r->table) { |
|
int32_t row = ECS_RECORD_TO_ROW(r->row); |
|
ecs_entity_t *entities = r->table->data.entities.array; |
|
entities[row] = entity_with_generation; |
|
} |
|
} |
|
|
|
const ecs_type_info_t* flecs_type_info_get( |
|
const ecs_world_t *world, |
|
ecs_entity_t component) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); |
|
|
|
return flecs_sparse_try_t(&world->type_info, ecs_type_info_t, component); |
|
} |
|
|
|
ecs_type_info_t* flecs_type_info_ensure( |
|
ecs_world_t *world, |
|
ecs_entity_t component) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
const ecs_type_info_t *ti = flecs_type_info_get(world, component); |
|
ecs_type_info_t *ti_mut = NULL; |
|
if (!ti) { |
|
ti_mut = flecs_sparse_ensure_t( |
|
&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_CONST_CAST(ecs_type_info_t*, ti); |
|
} |
|
|
|
if (!ti_mut->name) { |
|
const char *sym = ecs_get_symbol(world, component); |
|
if (sym) { |
|
ti_mut->name = ecs_os_strdup(sym); |
|
} else { |
|
const char *name = ecs_get_name(world, component); |
|
if (name) { |
|
ti_mut->name = ecs_os_strdup(name); |
|
} |
|
} |
|
} |
|
|
|
return ti_mut; |
|
} |
|
|
|
bool flecs_type_info_init_id( |
|
ecs_world_t *world, |
|
ecs_entity_t component, |
|
ecs_size_t size, |
|
ecs_size_t alignment, |
|
const ecs_type_hooks_t *li) |
|
{ |
|
bool changed = false; |
|
|
|
flecs_entities_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_sparse_remove_t(&world->type_info, ecs_type_info_t, component); |
|
} else { |
|
ti = flecs_type_info_ensure(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_hooks_id(world, component, li); |
|
} |
|
} |
|
|
|
/* Set type info for id record of component */ |
|
ecs_id_record_t *idr = flecs_id_record_ensure(world, component); |
|
changed |= flecs_id_record_set_type_info(world, idr, ti); |
|
bool is_tag = idr->flags & EcsIdTag; |
|
|
|
/* All id records with component as relationship inherit type info */ |
|
idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); |
|
do { |
|
if (is_tag) { |
|
changed |= flecs_id_record_set_type_info(world, idr, NULL); |
|
} else if (ti) { |
|
changed |= flecs_id_record_set_type_info(world, idr, ti); |
|
} else if ((idr->type_info != NULL) && |
|
(idr->type_info->component == component)) |
|
{ |
|
changed |= flecs_id_record_set_type_info(world, idr, NULL); |
|
} |
|
} while ((idr = idr->first.next)); |
|
|
|
/* All non-tag id records with component as object inherit type info, |
|
* if relationship doesn't have type info */ |
|
idr = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, component)); |
|
do { |
|
if (!(idr->flags & EcsIdTag) && !idr->type_info) { |
|
changed |= flecs_id_record_set_type_info(world, idr, ti); |
|
} |
|
} while ((idr = idr->first.next)); |
|
|
|
/* Type info of (*, component) should always point to component */ |
|
ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> |
|
type_info == ti, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return changed; |
|
} |
|
|
|
void flecs_type_info_fini( |
|
ecs_type_info_t *ti) |
|
{ |
|
if (ti->hooks.ctx_free) { |
|
ti->hooks.ctx_free(ti->hooks.ctx); |
|
} |
|
if (ti->hooks.binding_ctx_free) { |
|
ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); |
|
} |
|
if (ti->name) { |
|
/* Safe to cast away const, world has ownership over string */ |
|
ecs_os_free(ECS_CONST_CAST(char*, ti->name)); |
|
ti->name = NULL; |
|
} |
|
} |
|
|
|
void flecs_type_info_free( |
|
ecs_world_t *world, |
|
ecs_entity_t component) |
|
{ |
|
if (world->flags & EcsWorldQuit) { |
|
/* If world is in the final teardown stages, cleanup policies are no |
|
* longer applied and it can't be guaranteed that a component is not |
|
* deleted before entities that use it. The remaining type info elements |
|
* will be deleted after the store is finalized. */ |
|
return; |
|
} |
|
|
|
ecs_type_info_t *ti = flecs_sparse_try_t(&world->type_info, |
|
ecs_type_info_t, component); |
|
if (ti) { |
|
flecs_type_info_fini(ti); |
|
flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); |
|
} |
|
} |
|
|
|
static |
|
ecs_ftime_t flecs_insert_sleep( |
|
ecs_world_t *world, |
|
ecs_time_t *stop) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_time_t start = *stop, now = start; |
|
ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); |
|
|
|
if (ECS_EQZERO(world->info.target_fps)) { |
|
return delta_time; |
|
} |
|
|
|
ecs_ftime_t target_delta_time = |
|
((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); |
|
|
|
/* Calculate the time we need to sleep by taking the measured delta from the |
|
* previous frame, and subtracting it from target_delta_time. */ |
|
ecs_ftime_t sleep = target_delta_time - delta_time; |
|
|
|
/* Pick a sleep interval that is 4 times smaller than the time one frame |
|
* should take. */ |
|
ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)4.0; |
|
|
|
do { |
|
/* Only call sleep when sleep_time is not 0. On some platforms, even |
|
* a sleep with a timeout of 0 can cause stutter. */ |
|
if (ECS_NEQZERO(sleep_time)) { |
|
ecs_sleepf((double)sleep_time); |
|
} |
|
|
|
now = start; |
|
delta_time = (ecs_ftime_t)ecs_time_measure(&now); |
|
} while ((target_delta_time - delta_time) > |
|
(sleep_time / (ecs_ftime_t)2.0)); |
|
|
|
*stop = now; |
|
return delta_time; |
|
} |
|
|
|
static |
|
ecs_ftime_t flecs_start_measure_frame( |
|
ecs_world_t *world, |
|
ecs_ftime_t user_delta_time) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_ftime_t delta_time = 0; |
|
|
|
if ((world->flags & EcsWorldMeasureFrameTime) || |
|
(ECS_EQZERO(user_delta_time))) |
|
{ |
|
ecs_time_t t = world->frame_start_time; |
|
do { |
|
if (world->frame_start_time.nanosec || world->frame_start_time.sec){ |
|
delta_time = flecs_insert_sleep(world, &t); |
|
} else { |
|
ecs_time_measure(&t); |
|
if (ECS_NEQZERO(world->info.target_fps)) { |
|
delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; |
|
} else { |
|
/* Best guess */ |
|
delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; |
|
} |
|
} |
|
|
|
/* Keep trying while delta_time is zero */ |
|
} while (ECS_EQZERO(delta_time)); |
|
|
|
world->frame_start_time = t; |
|
|
|
/* Keep track of total time passed in world */ |
|
world->info.world_time_total_raw += (ecs_ftime_t)delta_time; |
|
} |
|
|
|
return (ecs_ftime_t)delta_time; |
|
} |
|
|
|
static |
|
void flecs_stop_measure_frame( |
|
ecs_world_t* world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
if (world->flags & EcsWorldMeasureFrameTime) { |
|
ecs_time_t t = world->frame_start_time; |
|
world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); |
|
} |
|
} |
|
|
|
ecs_ftime_t ecs_frame_begin( |
|
ecs_world_t *world, |
|
ecs_ftime_t user_delta_time) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); |
|
ecs_check(ECS_NEQZERO(user_delta_time) || ecs_os_has_time(), |
|
ECS_MISSING_OS_API, "get_time"); |
|
|
|
/* Start measuring total frame time */ |
|
ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); |
|
if (ECS_EQZERO(user_delta_time)) { |
|
user_delta_time = delta_time; |
|
} |
|
|
|
world->info.delta_time_raw = user_delta_time; |
|
world->info.delta_time = user_delta_time * world->info.time_scale; |
|
|
|
/* Keep track of total scaled time passed in world */ |
|
world->info.world_time_total += world->info.delta_time; |
|
|
|
ecs_run_aperiodic(world, 0); |
|
|
|
return world->info.delta_time; |
|
error: |
|
return (ecs_ftime_t)0; |
|
} |
|
|
|
void ecs_frame_end( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); |
|
|
|
world->info.frame_count_total ++; |
|
|
|
ecs_stage_t *stages = world->stages; |
|
int32_t i, count = world->stage_count; |
|
for (i = 0; i < count; i ++) { |
|
flecs_stage_merge_post_frame(world, &stages[i]); |
|
} |
|
|
|
flecs_stop_measure_frame(world); |
|
error: |
|
return; |
|
} |
|
|
|
const ecs_world_info_t* ecs_get_world_info( |
|
const ecs_world_t *world) |
|
{ |
|
world = ecs_get_world(world); |
|
return &world->info; |
|
} |
|
|
|
void flecs_delete_table( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
flecs_table_free(world, table); |
|
} |
|
|
|
static |
|
void flecs_process_empty_queries( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, |
|
ecs_pair(ecs_id(EcsPoly), EcsQuery)); |
|
if (!idr) { |
|
return; |
|
} |
|
|
|
ecs_run_aperiodic(world, EcsAperiodicEmptyTables); |
|
|
|
/* Make sure that we defer adding the inactive tags until after iterating |
|
* the query */ |
|
flecs_defer_begin(world, &world->stages[0]); |
|
|
|
ecs_table_cache_iter_t it; |
|
const ecs_table_record_t *tr; |
|
if (flecs_table_cache_iter(&idr->cache, &it)) { |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); |
|
int32_t i, count = ecs_table_count(table); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_query_t *query = queries[i].poly; |
|
ecs_entity_t *entities = table->data.entities.array; |
|
if (!ecs_query_table_count(query)) { |
|
ecs_add_id(world, entities[i], EcsEmpty); |
|
} |
|
} |
|
} |
|
} |
|
|
|
flecs_defer_end(world, &world->stages[0]); |
|
} |
|
|
|
/** Walk over tables that had a state change which requires bookkeeping */ |
|
void flecs_process_pending_tables( |
|
const ecs_world_t *world_r) |
|
{ |
|
ecs_poly_assert(world_r, ecs_world_t); |
|
|
|
/* We can't update the administration while in readonly mode, but we can |
|
* ensure that when this function is called there are no pending events. */ |
|
if (world_r->flags & EcsWorldReadonly) { |
|
ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, |
|
ECS_INTERNAL_ERROR, NULL); |
|
return; |
|
} |
|
|
|
/* Safe to cast, world is not readonly */ |
|
ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); |
|
|
|
/* If pending buffer is NULL there already is a stackframe that's iterating |
|
* the table list. This can happen when an observer for a table event results |
|
* in a mutation that causes another table to change state. A typical |
|
* example of this is a system that becomes active/inactive as the result of |
|
* a query (and as a result, its matched tables) becoming empty/non empty */ |
|
if (!world->pending_buffer) { |
|
return; |
|
} |
|
|
|
/* Swap buffer. The logic could in theory have been implemented with a |
|
* single sparse set, but that would've complicated (and slowed down) the |
|
* iteration. Additionally, by using a double buffer approach we can still |
|
* keep most of the original ordering of events intact, which is desirable |
|
* as it means that the ordering of tables in the internal data structures is |
|
* more predictable. */ |
|
int32_t i, count = flecs_sparse_count(world->pending_tables); |
|
if (!count) { |
|
return; |
|
} |
|
|
|
flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); |
|
|
|
do { |
|
ecs_sparse_t *pending_tables = world->pending_tables; |
|
world->pending_tables = world->pending_buffer; |
|
world->pending_buffer = NULL; |
|
|
|
/* Make sure that any ECS operations that occur while delivering the |
|
* events does not cause inconsistencies, like sending an Empty |
|
* notification for a table that just became non-empty. */ |
|
flecs_defer_begin(world, &world->stages[0]); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_table_t *table = flecs_sparse_get_dense_t( |
|
pending_tables, ecs_table_t*, i)[0]; |
|
if (!table->id) { |
|
/* Table is being deleted, ignore empty events */ |
|
continue; |
|
} |
|
|
|
/* For each id in the table, add it to the empty/non empty list |
|
* based on its current state */ |
|
if (flecs_table_records_update_empty(table)) { |
|
int32_t table_count = ecs_table_count(table); |
|
if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { |
|
/* Only emit an event when there was a change in the |
|
* administration. It is possible that a table ended up in the |
|
* pending_tables list by going from empty->non-empty, but then |
|
* became empty again. By the time we run this code, no changes |
|
* in the administration would actually be made. */ |
|
ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; |
|
if (ecs_should_log_3()) { |
|
ecs_dbg_3("table %u state change (%s)", |
|
(uint32_t)table->id, |
|
table_count ? "non-empty" : "empty"); |
|
} |
|
|
|
ecs_log_push_3(); |
|
|
|
flecs_emit(world, world, &(ecs_event_desc_t){ |
|
.event = evt, |
|
.table = table, |
|
.ids = &table->type, |
|
.observable = world, |
|
.flags = EcsEventTableOnly |
|
}); |
|
|
|
ecs_log_pop_3(); |
|
} |
|
world->info.empty_table_count += (table_count == 0) * 2 - 1; |
|
} |
|
} |
|
|
|
flecs_sparse_clear(pending_tables); |
|
ecs_defer_end(world); |
|
|
|
world->pending_buffer = pending_tables; |
|
} while ((count = flecs_sparse_count(world->pending_tables))); |
|
|
|
flecs_journal_end(); |
|
} |
|
|
|
void flecs_table_set_empty( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (ecs_table_count(table)) { |
|
table->_->generation = 0; |
|
} |
|
|
|
flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, |
|
(uint32_t)table->id)[0] = table; |
|
} |
|
|
|
bool ecs_id_in_use( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
return false; |
|
} |
|
return (flecs_table_cache_count(&idr->cache) != 0) || |
|
(flecs_table_cache_empty_count(&idr->cache) != 0); |
|
} |
|
|
|
void ecs_run_aperiodic( |
|
ecs_world_t *world, |
|
ecs_flags32_t flags) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
if (!flags || (flags & EcsAperiodicEmptyTables)) { |
|
flecs_process_pending_tables(world); |
|
} |
|
if ((flags & EcsAperiodicEmptyQueries)) { |
|
flecs_process_empty_queries(world); |
|
} |
|
if (!flags || (flags & EcsAperiodicComponentMonitors)) { |
|
flecs_eval_component_monitors(world); |
|
} |
|
} |
|
|
|
int32_t ecs_delete_empty_tables( |
|
ecs_world_t *world, |
|
ecs_id_t id, |
|
uint16_t clear_generation, |
|
uint16_t delete_generation, |
|
int32_t min_id_count, |
|
double time_budget_seconds) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
/* Make sure empty tables are in the empty table lists */ |
|
ecs_run_aperiodic(world, EcsAperiodicEmptyTables); |
|
|
|
ecs_time_t start = {0}, cur = {0}; |
|
int32_t delete_count = 0, clear_count = 0; |
|
bool time_budget = false; |
|
|
|
if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { |
|
ecs_time_measure(&start); |
|
} |
|
|
|
if (ECS_NEQZERO(time_budget_seconds)) { |
|
time_budget = true; |
|
} |
|
|
|
if (!id) { |
|
id = EcsAny; /* Iterate all empty tables */ |
|
} |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
ecs_table_cache_iter_t it; |
|
if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { |
|
ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
if (time_budget) { |
|
cur = start; |
|
if (ecs_time_measure(&cur) > time_budget_seconds) { |
|
goto done; |
|
} |
|
} |
|
|
|
ecs_table_t *table = tr->hdr.table; |
|
ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (table->type.count < min_id_count) { |
|
continue; |
|
} |
|
|
|
uint16_t gen = ++ table->_->generation; |
|
if (delete_generation && (gen > delete_generation)) { |
|
flecs_table_free(world, table); |
|
delete_count ++; |
|
} else if (clear_generation && (gen > clear_generation)) { |
|
if (flecs_table_shrink(world, table)) { |
|
clear_count ++; |
|
} |
|
} |
|
} |
|
} |
|
|
|
done: |
|
if (ecs_should_log_1() && ecs_os_has_time()) { |
|
if (delete_count) { |
|
ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", |
|
delete_count, ecs_time_measure(&start)); |
|
} |
|
if (clear_count) { |
|
ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", |
|
clear_count, ecs_time_measure(&start)); |
|
} |
|
} |
|
|
|
return delete_count; |
|
} |
|
|
|
/** |
|
* @file addons/alerts.c |
|
* @brief Alerts addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_ALERTS |
|
|
|
ECS_COMPONENT_DECLARE(FlecsAlerts); |
|
|
|
typedef struct EcsAlert { |
|
char *message; |
|
ecs_map_t instances; /* Active instances for metric */ |
|
ecs_ftime_t retain_period; /* How long to retain the alert */ |
|
ecs_vec_t severity_filters; /* Severity filters */ |
|
|
|
/* Member range monitoring */ |
|
ecs_id_t id; /* (Component) id that contains to monitor member */ |
|
ecs_entity_t member; /* Member to monitor */ |
|
int32_t offset; /* Offset of member in component */ |
|
int32_t size; /* Size of component */ |
|
ecs_primitive_kind_t kind; /* Primitive type kind */ |
|
ecs_ref_t ranges; /* Reference to ranges component */ |
|
int32_t var_id; /* Variable from which to obtain data (0 = $this) */ |
|
} EcsAlert; |
|
|
|
typedef struct EcsAlertTimeout { |
|
ecs_ftime_t inactive_time; /* Time the alert has been inactive */ |
|
ecs_ftime_t expire_time; /* Expiration duration */ |
|
} EcsAlertTimeout; |
|
|
|
ECS_COMPONENT_DECLARE(EcsAlertTimeout); |
|
|
|
static |
|
ECS_CTOR(EcsAlert, ptr, { |
|
ecs_os_zeromem(ptr); |
|
ecs_map_init(&ptr->instances, NULL); |
|
ecs_vec_init_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t, 0); |
|
}) |
|
|
|
static |
|
ECS_DTOR(EcsAlert, ptr, { |
|
ecs_os_free(ptr->message); |
|
ecs_map_fini(&ptr->instances); |
|
ecs_vec_fini_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t); |
|
}) |
|
|
|
static |
|
ECS_MOVE(EcsAlert, dst, src, { |
|
ecs_os_free(dst->message); |
|
dst->message = src->message; |
|
src->message = NULL; |
|
|
|
ecs_map_fini(&dst->instances); |
|
dst->instances = src->instances; |
|
src->instances = (ecs_map_t){0}; |
|
|
|
ecs_vec_fini_t(NULL, &dst->severity_filters, ecs_alert_severity_filter_t); |
|
dst->severity_filters = src->severity_filters; |
|
src->severity_filters = (ecs_vec_t){0}; |
|
|
|
dst->retain_period = src->retain_period; |
|
dst->id = src->id; |
|
dst->member = src->member; |
|
dst->offset = src->offset; |
|
dst->size = src->size; |
|
dst->kind = src->kind; |
|
dst->ranges = src->ranges; |
|
dst->var_id = src->var_id; |
|
}) |
|
|
|
static |
|
ECS_CTOR(EcsAlertsActive, ptr, { |
|
ecs_map_init(&ptr->alerts, NULL); |
|
ptr->info_count = 0; |
|
ptr->warning_count = 0; |
|
ptr->error_count = 0; |
|
}) |
|
|
|
static |
|
ECS_DTOR(EcsAlertsActive, ptr, { |
|
ecs_map_fini(&ptr->alerts); |
|
}) |
|
|
|
static |
|
ECS_MOVE(EcsAlertsActive, dst, src, { |
|
ecs_map_fini(&dst->alerts); |
|
dst->alerts = src->alerts; |
|
dst->info_count = src->info_count; |
|
dst->warning_count = src->warning_count; |
|
dst->error_count = src->error_count; |
|
src->alerts = (ecs_map_t){0}; |
|
}) |
|
|
|
static |
|
ECS_DTOR(EcsAlertInstance, ptr, { |
|
ecs_os_free(ptr->message); |
|
}) |
|
|
|
static |
|
ECS_MOVE(EcsAlertInstance, dst, src, { |
|
ecs_os_free(dst->message); |
|
dst->message = src->message; |
|
src->message = NULL; |
|
}) |
|
|
|
static |
|
ECS_COPY(EcsAlertInstance, dst, src, { |
|
ecs_os_free(dst->message); |
|
dst->message = ecs_os_strdup(src->message); |
|
}) |
|
|
|
static |
|
void flecs_alerts_add_alert_to_src( |
|
ecs_world_t *world, |
|
ecs_entity_t source, |
|
ecs_entity_t alert, |
|
ecs_entity_t alert_instance) |
|
{ |
|
EcsAlertsActive *active = ecs_get_mut( |
|
world, source, EcsAlertsActive); |
|
ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); |
|
if (severity == EcsAlertInfo) { |
|
active->info_count ++; |
|
} else if (severity == EcsAlertWarning) { |
|
active->warning_count ++; |
|
} else if (severity == EcsAlertError) { |
|
active->error_count ++; |
|
} |
|
|
|
ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert); |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ptr[0] = alert_instance; |
|
ecs_modified(world, source, EcsAlertsActive); |
|
} |
|
|
|
static |
|
void flecs_alerts_remove_alert_from_src( |
|
ecs_world_t *world, |
|
ecs_entity_t source, |
|
ecs_entity_t alert) |
|
{ |
|
EcsAlertsActive *active = ecs_get_mut( |
|
world, source, EcsAlertsActive); |
|
ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_map_remove(&active->alerts, alert); |
|
|
|
ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); |
|
if (severity == EcsAlertInfo) { |
|
active->info_count --; |
|
} else if (severity == EcsAlertWarning) { |
|
active->warning_count --; |
|
} else if (severity == EcsAlertError) { |
|
active->error_count --; |
|
} |
|
|
|
if (!ecs_map_count(&active->alerts)) { |
|
ecs_remove(world, source, EcsAlertsActive); |
|
} else { |
|
ecs_modified(world, source, EcsAlertsActive); |
|
} |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_alert_get_severity( |
|
ecs_world_t *world, |
|
ecs_iter_t *it, |
|
EcsAlert *alert) |
|
{ |
|
int32_t i, filter_count = ecs_vec_count(&alert->severity_filters); |
|
ecs_alert_severity_filter_t *filters = |
|
ecs_vec_first(&alert->severity_filters); |
|
for (i = 0; i < filter_count; i ++) { |
|
ecs_alert_severity_filter_t *filter = &filters[i]; |
|
if (!filter->var) { |
|
if (ecs_table_has_id(world, it->table, filters[i].with)) { |
|
return filters[i].severity; |
|
} |
|
} else { |
|
ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index); |
|
if (src && src != EcsWildcard) { |
|
if (ecs_has_id(world, src, filters[i].with)) { |
|
return filters[i].severity; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_alert_out_of_range_kind( |
|
EcsAlert *alert, |
|
const EcsMemberRanges *ranges, |
|
const void *value_ptr) |
|
{ |
|
double value = 0; |
|
|
|
switch(alert->kind) { |
|
case EcsU8: value = *(const uint8_t*)value_ptr; break; |
|
case EcsU16: value = *(const uint16_t*)value_ptr; break; |
|
case EcsU32: value = *(const uint32_t*)value_ptr; break; |
|
case EcsU64: value = (double)*(const uint64_t*)value_ptr; break; |
|
case EcsI8: value = *(const int8_t*)value_ptr; break; |
|
case EcsI16: value = *(const int16_t*)value_ptr; break; |
|
case EcsI32: value = *(const int32_t*)value_ptr; break; |
|
case EcsI64: value = (double)*(const int64_t*)value_ptr; break; |
|
case EcsF32: value = (double)*(const float*)value_ptr; break; |
|
case EcsF64: value = *(const double*)value_ptr; break; |
|
case EcsBool: |
|
case EcsChar: |
|
case EcsByte: |
|
case EcsUPtr: |
|
case EcsIPtr: |
|
case EcsString: |
|
case EcsEntity: |
|
case EcsId: |
|
return 0; |
|
} |
|
|
|
bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); |
|
bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); |
|
|
|
if (has_error && (value < ranges->error.min || value > ranges->error.max)) { |
|
return EcsAlertError; |
|
} else if (has_warning && |
|
(value < ranges->warning.min || value > ranges->warning.max)) |
|
{ |
|
return EcsAlertWarning; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
static |
|
void MonitorAlerts(ecs_iter_t *it) { |
|
ecs_world_t *world = it->real_world; |
|
EcsAlert *alert = ecs_field(it, EcsAlert, 1); |
|
EcsPoly *poly = ecs_field(it, EcsPoly, 2); |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t a = it->entities[i]; /* Alert entity */ |
|
ecs_entity_t default_severity = ecs_get_target( |
|
world, a, ecs_id(EcsAlert), 0); |
|
ecs_rule_t *rule = poly[i].poly; |
|
ecs_poly_assert(rule, ecs_rule_t); |
|
|
|
ecs_id_t member_id = alert[i].id; |
|
const EcsMemberRanges *ranges = NULL; |
|
if (member_id) { |
|
ranges = ecs_ref_get(world, &alert[i].ranges, EcsMemberRanges); |
|
} |
|
|
|
ecs_iter_t rit = ecs_rule_iter(world, rule); |
|
rit.flags |= EcsIterNoData; |
|
rit.flags |= EcsIterIsInstanced; |
|
|
|
while (ecs_rule_next(&rit)) { |
|
ecs_entity_t severity = flecs_alert_get_severity( |
|
world, &rit, &alert[i]); |
|
if (!severity) { |
|
severity = default_severity; |
|
} |
|
|
|
const void *member_data = NULL; |
|
ecs_entity_t member_src = 0; |
|
if (ranges) { |
|
if (alert[i].var_id) { |
|
member_src = ecs_iter_get_var(&rit, alert[i].var_id); |
|
if (!member_src || member_src == EcsWildcard) { |
|
continue; |
|
} |
|
} |
|
if (!member_src) { |
|
member_data = ecs_table_get_id( |
|
world, rit.table, member_id, rit.offset); |
|
} else { |
|
member_data = ecs_get_id(world, member_src, member_id); |
|
} |
|
if (!member_data) { |
|
continue; |
|
} |
|
member_data = ECS_OFFSET(member_data, alert[i].offset); |
|
} |
|
|
|
int32_t j, alert_src_count = rit.count; |
|
for (j = 0; j < alert_src_count; j ++) { |
|
ecs_entity_t src_severity = severity; |
|
ecs_entity_t e = rit.entities[j]; |
|
if (member_data) { |
|
ecs_entity_t range_severity = flecs_alert_out_of_range_kind( |
|
&alert[i], ranges, member_data); |
|
if (!member_src) { |
|
member_data = ECS_OFFSET(member_data, alert[i].size); |
|
} |
|
if (!range_severity) { |
|
continue; |
|
} |
|
if (range_severity < src_severity) { |
|
/* Range severity should not exceed alert severity */ |
|
src_severity = range_severity; |
|
} |
|
} |
|
|
|
ecs_entity_t *aptr = ecs_map_ensure(&alert[i].instances, e); |
|
ecs_assert(aptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!aptr[0]) { |
|
/* Alert does not yet exist for entity */ |
|
ecs_entity_t ai = ecs_new_w_pair(world, EcsChildOf, a); |
|
ecs_set(world, ai, EcsAlertInstance, { .message = NULL }); |
|
ecs_set(world, ai, EcsMetricSource, { .entity = e }); |
|
ecs_set(world, ai, EcsMetricValue, { .value = 0 }); |
|
ecs_add_pair(world, ai, ecs_id(EcsAlert), src_severity); |
|
if (ECS_NEQZERO(alert[i].retain_period)) { |
|
ecs_set(world, ai, EcsAlertTimeout, { |
|
.inactive_time = 0, |
|
.expire_time = alert[i].retain_period |
|
}); |
|
} |
|
|
|
ecs_defer_suspend(it->world); |
|
flecs_alerts_add_alert_to_src(world, e, a, ai); |
|
ecs_defer_resume(it->world); |
|
aptr[0] = ai; |
|
} else { |
|
/* Make sure alert severity is up to date */ |
|
if (ecs_vec_count(&alert[i].severity_filters) || member_data) { |
|
ecs_entity_t cur_severity = ecs_get_target( |
|
world, aptr[0], ecs_id(EcsAlert), 0); |
|
if (cur_severity != src_severity) { |
|
ecs_add_pair(world, aptr[0], ecs_id(EcsAlert), |
|
src_severity); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
void MonitorAlertInstances(ecs_iter_t *it) { |
|
ecs_world_t *world = it->real_world; |
|
EcsAlertInstance *alert_instance = ecs_field(it, EcsAlertInstance, 1); |
|
EcsMetricSource *source = ecs_field(it, EcsMetricSource, 2); |
|
EcsMetricValue *value = ecs_field(it, EcsMetricValue, 3); |
|
EcsAlertTimeout *timeout = ecs_field(it, EcsAlertTimeout, 4); |
|
|
|
/* Get alert component from alert instance parent (the alert) */ |
|
ecs_id_t childof_pair; |
|
if (ecs_search(world, it->table, ecs_childof(EcsWildcard), &childof_pair) == -1) { |
|
ecs_err("alert instances must be a child of an alert"); |
|
return; |
|
} |
|
ecs_entity_t parent = ecs_pair_second(world, childof_pair); |
|
ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ecs_has(world, parent, EcsAlert), ECS_INVALID_OPERATION, |
|
"alert entity does not have Alert component"); |
|
EcsAlert *alert = ecs_get_mut(world, parent, EcsAlert); |
|
const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery); |
|
ecs_assert(poly != NULL, ECS_INVALID_OPERATION, |
|
"alert entity does not have (Poly, Query) component"); |
|
ecs_rule_t *rule = poly->poly; |
|
ecs_poly_assert(rule, ecs_rule_t); |
|
|
|
ecs_id_t member_id = alert->id; |
|
const EcsMemberRanges *ranges = NULL; |
|
if (member_id) { |
|
ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges); |
|
} |
|
|
|
ecs_vars_t vars = {0}; |
|
ecs_vars_init(world, &vars); |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t ai = it->entities[i]; |
|
ecs_entity_t e = source[i].entity; |
|
|
|
/* If source of alert is no longer alive, delete alert instance even if |
|
* the alert has a retain period. */ |
|
if (!ecs_is_alive(world, e)) { |
|
ecs_delete(world, ai); |
|
continue; |
|
} |
|
|
|
/* Check if alert instance still matches rule */ |
|
ecs_iter_t rit = ecs_rule_iter(world, rule); |
|
rit.flags |= EcsIterNoData; |
|
rit.flags |= EcsIterIsInstanced; |
|
ecs_iter_set_var(&rit, 0, e); |
|
|
|
if (ecs_rule_next(&rit)) { |
|
bool match = true; |
|
|
|
/* If alert is monitoring member range, test value against range */ |
|
if (ranges) { |
|
ecs_entity_t member_src = e; |
|
if (alert->var_id) { |
|
member_src = ecs_iter_get_var(&rit, alert->var_id); |
|
} |
|
|
|
const void *member_data = ecs_get_id( |
|
world, member_src, member_id); |
|
if (!member_data) { |
|
match = false; |
|
} else { |
|
member_data = ECS_OFFSET(member_data, alert->offset); |
|
if (flecs_alert_out_of_range_kind( |
|
alert, ranges, member_data) == 0) |
|
{ |
|
match = false; |
|
} |
|
} |
|
} |
|
|
|
if (match) { |
|
/* Only increase alert duration if the alert was active */ |
|
value[i].value += (double)it->delta_system_time; |
|
|
|
bool generate_message = alert->message; |
|
if (generate_message) { |
|
if (alert_instance[i].message) { |
|
/* If a message was already generated, only regenerate if |
|
* rule has multiple variables. Variable values could have |
|
* changed, this ensures the message remains up to date. */ |
|
generate_message = rit.variable_count > 1; |
|
} |
|
} |
|
|
|
if (generate_message) { |
|
if (alert_instance[i].message) { |
|
ecs_os_free(alert_instance[i].message); |
|
} |
|
|
|
ecs_iter_to_vars(&rit, &vars, 0); |
|
alert_instance[i].message = ecs_interpolate_string( |
|
world, alert->message, &vars); |
|
} |
|
|
|
if (timeout) { |
|
if (ECS_NEQZERO(timeout[i].inactive_time)) { |
|
/* The alert just became active. Remove Disabled tag */ |
|
flecs_alerts_add_alert_to_src(world, e, parent, ai); |
|
ecs_remove_id(world, ai, EcsDisabled); |
|
} |
|
timeout[i].inactive_time = 0; |
|
} |
|
|
|
/* Alert instance still matches rule, keep it alive */ |
|
ecs_iter_fini(&rit); |
|
continue; |
|
} |
|
|
|
ecs_iter_fini(&rit); |
|
} |
|
|
|
/* Alert instance is no longer active */ |
|
|
|
if (timeout) { |
|
if (ECS_EQZERO(timeout[i].inactive_time)) { |
|
/* The alert just became inactive. Add Disabled tag */ |
|
flecs_alerts_remove_alert_from_src(world, e, parent); |
|
ecs_add_id(world, ai, EcsDisabled); |
|
} |
|
ecs_ftime_t t = timeout[i].inactive_time; |
|
timeout[i].inactive_time += it->delta_system_time; |
|
if (t < timeout[i].expire_time) { |
|
/* Alert instance no longer matches rule, but is still |
|
* within the timeout period. Keep it alive. */ |
|
continue; |
|
} |
|
} |
|
|
|
/* Alert instance no longer matches rule, remove it */ |
|
flecs_alerts_remove_alert_from_src(world, e, parent); |
|
ecs_map_remove(&alert->instances, e); |
|
ecs_delete(world, ai); |
|
} |
|
|
|
ecs_vars_fini(&vars); |
|
} |
|
|
|
ecs_entity_t ecs_alert_init( |
|
ecs_world_t *world, |
|
const ecs_alert_desc_t *desc) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!desc->filter.entity || desc->entity == desc->filter.entity, |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_entity_t result = desc->entity; |
|
if (!result) { |
|
result = ecs_new(world, 0); |
|
} |
|
|
|
ecs_filter_desc_t private_desc = desc->filter; |
|
private_desc.entity = result; |
|
|
|
ecs_rule_t *rule = ecs_rule_init(world, &private_desc); |
|
if (!rule) { |
|
ecs_err("failed to create alert filter"); |
|
return 0; |
|
} |
|
|
|
const ecs_filter_t *filter = ecs_rule_get_filter(rule); |
|
if (!(filter->flags & EcsFilterMatchThis)) { |
|
ecs_err("alert filter must have at least one '$this' term"); |
|
ecs_rule_fini(rule); |
|
return 0; |
|
} |
|
|
|
/* Initialize Alert component which identifiers entity as alert */ |
|
EcsAlert *alert = ecs_get_mut(world, result, EcsAlert); |
|
ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL); |
|
alert->message = ecs_os_strdup(desc->message); |
|
alert->retain_period = desc->retain_period; |
|
|
|
/* Initialize severity filters */ |
|
int32_t i; |
|
for (i = 0; i < 4; i ++) { |
|
if (desc->severity_filters[i].with) { |
|
if (!desc->severity_filters[i].severity) { |
|
ecs_err("severity filter must have severity"); |
|
goto error; |
|
} |
|
ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL, |
|
&alert->severity_filters, ecs_alert_severity_filter_t); |
|
*sf = desc->severity_filters[i]; |
|
if (sf->var) { |
|
sf->_var_index = ecs_rule_find_var(rule, sf->var); |
|
if (sf->_var_index == -1) { |
|
ecs_err("unresolved variable '%s' in alert severity filter", |
|
sf->var); |
|
goto error; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Fetch data for member monitoring */ |
|
if (desc->member) { |
|
alert->member = desc->member; |
|
if (!desc->id) { |
|
alert->id = ecs_get_parent(world, desc->member); |
|
if (!alert->id) { |
|
ecs_err("ecs_alert_desc_t::member is not a member"); |
|
goto error; |
|
} |
|
ecs_check(alert->id != 0, ECS_INVALID_PARAMETER, NULL); |
|
} else { |
|
alert->id = desc->id; |
|
} |
|
|
|
ecs_id_record_t *idr = flecs_id_record_ensure(world, alert->id); |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!idr->type_info) { |
|
ecs_err("ecs_alert_desc_t::id must be a component"); |
|
goto error; |
|
} |
|
|
|
ecs_entity_t type = idr->type_info->component; |
|
if (type != ecs_get_parent(world, desc->member)) { |
|
char *type_name = ecs_get_fullpath(world, type); |
|
ecs_err("member '%s' is not a member of '%s'", |
|
ecs_get_name(world, desc->member), type_name); |
|
ecs_os_free(type_name); |
|
goto error; |
|
} |
|
|
|
const EcsMember *member = ecs_get(world, alert->member, EcsMember); |
|
if (!member) { |
|
ecs_err("ecs_alert_desc_t::member is not a member"); |
|
goto error; |
|
} |
|
if (!member->type) { |
|
ecs_err("ecs_alert_desc_t::member must have a type"); |
|
goto error; |
|
} |
|
|
|
const EcsPrimitive *pr = ecs_get(world, member->type, EcsPrimitive); |
|
if (!pr) { |
|
ecs_err("ecs_alert_desc_t::member must be of a primitive type"); |
|
goto error; |
|
} |
|
|
|
if (!ecs_has(world, desc->member, EcsMemberRanges)) { |
|
ecs_err("ecs_alert_desc_t::member must have warning/error ranges"); |
|
goto error; |
|
} |
|
|
|
int32_t var_id = 0; |
|
if (desc->var) { |
|
var_id = ecs_rule_find_var(rule, desc->var); |
|
if (var_id == -1) { |
|
ecs_err("unresolved variable '%s' in alert member", desc->var); |
|
goto error; |
|
} |
|
} |
|
|
|
alert->offset = member->offset; |
|
alert->size = idr->type_info->size; |
|
alert->kind = pr->kind; |
|
alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); |
|
alert->var_id = var_id; |
|
} |
|
|
|
ecs_modified(world, result, EcsAlert); |
|
|
|
/* Register alert as metric */ |
|
ecs_add(world, result, EcsMetric); |
|
ecs_add_pair(world, result, EcsMetric, EcsCounter); |
|
|
|
/* Add severity to alert */ |
|
ecs_entity_t severity = desc->severity; |
|
if (!severity) { |
|
severity = EcsAlertError; |
|
} |
|
|
|
ecs_add_pair(world, result, ecs_id(EcsAlert), severity); |
|
|
|
if (desc->doc_name) { |
|
#ifdef FLECS_DOC |
|
ecs_doc_set_name(world, result, desc->doc_name); |
|
#else |
|
ecs_err("cannot set doc_name for alert, requires FLECS_DOC addon"); |
|
goto error; |
|
#endif |
|
} |
|
|
|
if (desc->brief) { |
|
#ifdef FLECS_DOC |
|
ecs_doc_set_brief(world, result, desc->brief); |
|
#else |
|
ecs_err("cannot set brief for alert, requires FLECS_DOC addon"); |
|
goto error; |
|
#endif |
|
} |
|
|
|
return result; |
|
error: |
|
if (result) { |
|
ecs_delete(world, result); |
|
} |
|
return 0; |
|
} |
|
|
|
int32_t ecs_get_alert_count( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t alert) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!alert || ecs_has(world, alert, EcsAlert), |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); |
|
if (!active) { |
|
return 0; |
|
} |
|
|
|
if (alert) { |
|
return ecs_map_get(&active->alerts, alert) != NULL; |
|
} |
|
|
|
return ecs_map_count(&active->alerts); |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_get_alert( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t alert) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(alert != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); |
|
if (!active) { |
|
return 0; |
|
} |
|
|
|
ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert); |
|
if (ptr) { |
|
return ptr[0]; |
|
} |
|
|
|
error: |
|
return 0; |
|
} |
|
|
|
void FlecsAlertsImport(ecs_world_t *world) { |
|
ECS_MODULE_DEFINE(world, FlecsAlerts); |
|
|
|
ECS_IMPORT(world, FlecsPipeline); |
|
ECS_IMPORT(world, FlecsTimer); |
|
ECS_IMPORT(world, FlecsMetrics); |
|
#ifdef FLECS_DOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
#endif |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
ECS_COMPONENT_DEFINE(world, EcsAlert); |
|
ecs_remove_pair(world, ecs_id(EcsAlert), ecs_id(EcsIdentifier), EcsSymbol); |
|
ECS_COMPONENT_DEFINE(world, EcsAlertsActive); |
|
|
|
ecs_set_name_prefix(world, "EcsAlert"); |
|
ECS_COMPONENT_DEFINE(world, EcsAlertInstance); |
|
ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); |
|
|
|
ECS_TAG_DEFINE(world, EcsAlertInfo); |
|
ECS_TAG_DEFINE(world, EcsAlertWarning); |
|
ECS_TAG_DEFINE(world, EcsAlertError); |
|
ECS_TAG_DEFINE(world, EcsAlertCritical); |
|
|
|
ecs_add_id(world, ecs_id(EcsAlert), EcsTag); |
|
ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive); |
|
ecs_add_id(world, ecs_id(EcsAlertsActive), EcsPrivate); |
|
|
|
ecs_struct(world, { |
|
.entity = ecs_id(EcsAlertInstance), |
|
.members = { |
|
{ .name = "message", .type = ecs_id(ecs_string_t) } |
|
} |
|
}); |
|
|
|
ecs_set_hooks(world, EcsAlert, { |
|
.ctor = ecs_ctor(EcsAlert), |
|
.dtor = ecs_dtor(EcsAlert), |
|
.move = ecs_move(EcsAlert) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsAlertsActive, { |
|
.ctor = ecs_ctor(EcsAlertsActive), |
|
.dtor = ecs_dtor(EcsAlertsActive), |
|
.move = ecs_move(EcsAlertsActive) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsAlertInstance, { |
|
.ctor = ecs_default_ctor, |
|
.dtor = ecs_dtor(EcsAlertInstance), |
|
.move = ecs_move(EcsAlertInstance), |
|
.copy = ecs_copy(EcsAlertInstance) |
|
}); |
|
|
|
ecs_struct(world, { |
|
.entity = ecs_id(EcsAlertsActive), |
|
.members = { |
|
{ .name = "info_count", .type = ecs_id(ecs_i32_t) }, |
|
{ .name = "warning_count", .type = ecs_id(ecs_i32_t) }, |
|
{ .name = "error_count", .type = ecs_id(ecs_i32_t) } |
|
} |
|
}); |
|
|
|
ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query)); |
|
ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, |
|
flecs.metrics.Source, flecs.metrics.Value, ?EcsAlertTimeout, ?Disabled); |
|
|
|
ecs_system(world, { |
|
.entity = ecs_id(MonitorAlerts), |
|
.no_readonly = true, |
|
.interval = 0.5 |
|
}); |
|
|
|
ecs_system(world, { |
|
.entity = ecs_id(MonitorAlertInstances), |
|
.interval = 0.5 |
|
}); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/app.c |
|
* @brief App addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_APP |
|
|
|
static |
|
int flecs_default_run_action( |
|
ecs_world_t *world, |
|
ecs_app_desc_t *desc) |
|
{ |
|
if (desc->init) { |
|
desc->init(world); |
|
} |
|
|
|
int result = 0; |
|
if (desc->frames) { |
|
int32_t i; |
|
for (i = 0; i < desc->frames; i ++) { |
|
if ((result = ecs_app_run_frame(world, desc)) != 0) { |
|
break; |
|
} |
|
} |
|
} else { |
|
while ((result = ecs_app_run_frame(world, desc)) == 0) { } |
|
} |
|
|
|
/* Ensure quit flag is set on world, which can be used to determine if |
|
* world needs to be cleaned up. */ |
|
#ifndef __EMSCRIPTEN__ |
|
ecs_quit(world); |
|
#endif |
|
|
|
if (result == 1) { |
|
return 0; /* Normal exit */ |
|
} else { |
|
return result; /* Error code */ |
|
} |
|
} |
|
|
|
static |
|
int flecs_default_frame_action( |
|
ecs_world_t *world, |
|
const ecs_app_desc_t *desc) |
|
{ |
|
return !ecs_progress(world, desc->delta_time); |
|
} |
|
|
|
static ecs_app_run_action_t run_action = flecs_default_run_action; |
|
static ecs_app_frame_action_t frame_action = flecs_default_frame_action; |
|
static ecs_app_desc_t ecs_app_desc; |
|
|
|
/* Serve REST API from wasm image when running in emscripten */ |
|
#ifdef ECS_TARGET_EM |
|
#include <emscripten.h> |
|
|
|
ecs_http_server_t *flecs_wasm_rest_server; |
|
|
|
EMSCRIPTEN_KEEPALIVE |
|
char* flecs_explorer_request(const char *method, char *request) { |
|
ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; |
|
ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); |
|
if (reply.code == 200) { |
|
return ecs_strbuf_get(&reply.body); |
|
} else { |
|
char *body = ecs_strbuf_get(&reply.body); |
|
if (body) { |
|
return body; |
|
} else { |
|
return ecs_asprintf( |
|
"{\"error\": \"bad request (code %d)\"}", reply.code); |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
int ecs_app_run( |
|
ecs_world_t *world, |
|
ecs_app_desc_t *desc) |
|
{ |
|
ecs_app_desc = *desc; |
|
|
|
/* Don't set FPS & threads if custom run action is set, as the platform on |
|
* which the app is running may not support it. */ |
|
if (run_action == flecs_default_run_action) { |
|
if (ECS_NEQZERO(ecs_app_desc.target_fps)) { |
|
ecs_set_target_fps(world, ecs_app_desc.target_fps); |
|
} |
|
if (ecs_app_desc.threads) { |
|
ecs_set_threads(world, ecs_app_desc.threads); |
|
} |
|
} |
|
|
|
/* REST server enables connecting to app with explorer */ |
|
if (desc->enable_rest) { |
|
#ifdef FLECS_REST |
|
#ifdef ECS_TARGET_EM |
|
flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); |
|
#else |
|
ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); |
|
#endif |
|
#else |
|
ecs_warn("cannot enable remote API, REST addon not available"); |
|
#endif |
|
} |
|
|
|
/* Monitoring periodically collects statistics */ |
|
if (desc->enable_monitor) { |
|
#ifdef FLECS_MONITOR |
|
ECS_IMPORT(world, FlecsMonitor); |
|
#else |
|
ecs_warn("cannot enable monitoring, MONITOR addon not available"); |
|
#endif |
|
} |
|
|
|
return run_action(world, &ecs_app_desc); |
|
} |
|
|
|
int ecs_app_run_frame( |
|
ecs_world_t *world, |
|
const ecs_app_desc_t *desc) |
|
{ |
|
return frame_action(world, desc); |
|
} |
|
|
|
int ecs_app_set_run_action( |
|
ecs_app_run_action_t callback) |
|
{ |
|
if (run_action != flecs_default_run_action && run_action != callback) { |
|
ecs_err("run action already set"); |
|
return -1; |
|
} |
|
|
|
run_action = callback; |
|
|
|
return 0; |
|
} |
|
|
|
int ecs_app_set_frame_action( |
|
ecs_app_frame_action_t callback) |
|
{ |
|
if (frame_action != flecs_default_frame_action && frame_action != callback) { |
|
ecs_err("frame action already set"); |
|
return -1; |
|
} |
|
|
|
frame_action = callback; |
|
|
|
return 0; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/coredoc.c |
|
* @brief Core doc addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_COREDOC |
|
|
|
#define URL_ROOT "https://www.flecs.dev/flecs/md_docs_2Relationships.html" |
|
|
|
void FlecsCoreDocImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsCoreDoc); |
|
|
|
ECS_IMPORT(world, FlecsMeta); |
|
ECS_IMPORT(world, FlecsDoc); |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
/* Initialize reflection data for core components */ |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsComponent), |
|
.members = { |
|
{.name = "size", .type = ecs_id(ecs_i32_t)}, |
|
{.name = "alignment", .type = ecs_id(ecs_i32_t)} |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsDocDescription), |
|
.members = { |
|
{.name = "value", .type = ecs_id(ecs_string_t)} |
|
} |
|
}); |
|
|
|
/* Initialize documentation data for core components */ |
|
ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); |
|
ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); |
|
ecs_doc_set_brief(world, EcsFlecsCore, "Module with builtin components"); |
|
ecs_doc_set_brief(world, EcsFlecsInternals, "Module with internal entities"); |
|
|
|
ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); |
|
|
|
ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to components"); |
|
ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); |
|
ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); |
|
ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); |
|
ecs_doc_set_brief(world, EcsPrivate, "Tag that is added to private components"); |
|
ecs_doc_set_brief(world, EcsFlag, "Internal tag for tracking ids with special id flags"); |
|
ecs_doc_set_brief(world, ecs_id(EcsIterable), "Internal component to make (query) entities iterable"); |
|
ecs_doc_set_brief(world, ecs_id(EcsPoly), "Internal component that stores pointer to poly objects"); |
|
|
|
ecs_doc_set_brief(world, ecs_id(EcsTarget), "Internal component that stores information for flattened trees"); |
|
ecs_doc_set_brief(world, EcsFlatten, "Tag that when added to assembly automatically flattens tree"); |
|
|
|
ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); |
|
ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to store entity name"); |
|
ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to store entity symbol"); |
|
ecs_doc_set_brief(world, EcsAlias, "Tag used with EcsIdentifier to store entity alias"); |
|
|
|
ecs_doc_set_brief(world, EcsQuery, "Tag added to query entities"); |
|
ecs_doc_set_brief(world, EcsObserver, "Tag added to observer entities"); |
|
|
|
ecs_doc_set_brief(world, EcsTransitive, "Trait that enables transitive evaluation of relationships"); |
|
ecs_doc_set_brief(world, EcsReflexive, "Trait that enables reflexive evaluation of relationships"); |
|
ecs_doc_set_brief(world, EcsFinal, "Trait that indicates an entity cannot be inherited from"); |
|
ecs_doc_set_brief(world, EcsDontInherit, "Trait that indicates it should not be inherited"); |
|
ecs_doc_set_brief(world, EcsTag, "Trait that ensures a pair cannot contain a value"); |
|
ecs_doc_set_brief(world, EcsAcyclic, "Trait that indicates a relationship is acyclic"); |
|
ecs_doc_set_brief(world, EcsTraversable, "Trait that indicates a relationship is traversable"); |
|
ecs_doc_set_brief(world, EcsExclusive, "Trait that ensures a relationship can only have one target"); |
|
ecs_doc_set_brief(world, EcsSymmetric, "Trait that causes a relationship to be two-way"); |
|
ecs_doc_set_brief(world, EcsWith, "Trait for adding additional components when a component is added"); |
|
ecs_doc_set_brief(world, EcsAlwaysOverride, "Trait that indicates a component should always be overridden"); |
|
ecs_doc_set_brief(world, EcsUnion, "Trait for creating a non-fragmenting relationship"); |
|
ecs_doc_set_brief(world, EcsOneOf, "Trait that enforces target of relationship is a child of <specified>"); |
|
ecs_doc_set_brief(world, EcsOnDelete, "Cleanup trait for specifying what happens when component is deleted"); |
|
ecs_doc_set_brief(world, EcsOnDeleteTarget, "Cleanup trait for specifying what happens when pair target is deleted"); |
|
ecs_doc_set_brief(world, EcsRemove, "Cleanup action used with OnDelete/OnDeleteTarget"); |
|
ecs_doc_set_brief(world, EcsDelete, "Cleanup action used with OnDelete/OnDeleteTarget"); |
|
ecs_doc_set_brief(world, EcsPanic, "Cleanup action used with OnDelete/OnDeleteTarget"); |
|
ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); |
|
ecs_doc_set_brief(world, EcsIsA, "Relationship used for expressing inheritance"); |
|
ecs_doc_set_brief(world, EcsChildOf, "Relationship used for expressing hierarchies"); |
|
ecs_doc_set_brief(world, EcsDependsOn, "Relationship used for expressing dependencies"); |
|
ecs_doc_set_brief(world, EcsSlotOf, "Relationship used for expressing prefab slots"); |
|
ecs_doc_set_brief(world, EcsOnAdd, "Event emitted when component is added"); |
|
ecs_doc_set_brief(world, EcsOnRemove, "Event emitted when component is removed"); |
|
ecs_doc_set_brief(world, EcsOnSet, "Event emitted when component is set"); |
|
ecs_doc_set_brief(world, EcsUnSet, "Event emitted when component is unset"); |
|
ecs_doc_set_brief(world, EcsMonitor, "Marker used to create monitor observers"); |
|
ecs_doc_set_brief(world, EcsOnTableFill, "Event emitted when table becomes non-empty"); |
|
ecs_doc_set_brief(world, EcsOnTableEmpty, "Event emitted when table becomes empty"); |
|
ecs_doc_set_brief(world, EcsOnTableCreate, "Event emitted when table is created"); |
|
ecs_doc_set_brief(world, EcsOnTableDelete, "Event emitted when table is deleted"); |
|
|
|
ecs_doc_set_brief(world, EcsThis, "Query marker to express $this variable"); |
|
ecs_doc_set_brief(world, EcsWildcard, "Query marker to express match all wildcard"); |
|
ecs_doc_set_brief(world, EcsAny, "Query marker to express match at least one wildcard"); |
|
|
|
ecs_doc_set_brief(world, EcsPredEq, "Query marker to express == operator"); |
|
ecs_doc_set_brief(world, EcsPredMatch, "Query marker to express ~= operator"); |
|
ecs_doc_set_brief(world, EcsPredLookup, "Query marker to express by-name lookup"); |
|
ecs_doc_set_brief(world, EcsScopeOpen, "Query marker to express scope open"); |
|
ecs_doc_set_brief(world, EcsScopeClose, "Query marker to express scope close"); |
|
ecs_doc_set_brief(world, EcsEmpty, "Tag used to indicate a query has no results"); |
|
|
|
/* Initialize documentation for meta components */ |
|
ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta"); |
|
ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); |
|
|
|
ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types"); |
|
ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format"); |
|
ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); |
|
ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); |
|
ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); |
|
ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); |
|
ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); |
|
ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); |
|
ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); |
|
|
|
ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); |
|
ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); |
|
|
|
/* Initialize documentation for doc components */ |
|
ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc"); |
|
ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); |
|
|
|
ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); |
|
ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); |
|
ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); |
|
ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/doc.c |
|
* @brief Doc addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_DOC |
|
|
|
static ECS_COPY(EcsDocDescription, dst, src, { |
|
ecs_os_strset((char**)&dst->value, src->value); |
|
|
|
}) |
|
|
|
static ECS_MOVE(EcsDocDescription, dst, src, { |
|
ecs_os_free((char*)dst->value); |
|
dst->value = src->value; |
|
src->value = NULL; |
|
}) |
|
|
|
static ECS_DTOR(EcsDocDescription, ptr, { |
|
ecs_os_free((char*)ptr->value); |
|
}) |
|
|
|
static |
|
void flecs_doc_set( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_entity_t kind, |
|
const char *value) |
|
{ |
|
if (value) { |
|
ecs_set_pair(world, entity, EcsDocDescription, kind, { |
|
/* Safe, value gets copied by copy hook */ |
|
.value = ECS_CONST_CAST(char*, value) |
|
}); |
|
} else { |
|
ecs_remove_pair(world, entity, ecs_id(EcsDocDescription), kind); |
|
} |
|
} |
|
|
|
void ecs_doc_set_name( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *name) |
|
{ |
|
flecs_doc_set(world, entity, EcsName, name); |
|
} |
|
|
|
void ecs_doc_set_brief( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *brief) |
|
{ |
|
flecs_doc_set(world, entity, EcsDocBrief, brief); |
|
} |
|
|
|
void ecs_doc_set_detail( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *detail) |
|
{ |
|
flecs_doc_set(world, entity, EcsDocDetail, detail); |
|
} |
|
|
|
void ecs_doc_set_link( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *link) |
|
{ |
|
flecs_doc_set(world, entity, EcsDocLink, link); |
|
} |
|
|
|
void ecs_doc_set_color( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const char *color) |
|
{ |
|
flecs_doc_set(world, entity, EcsDocColor, color); |
|
} |
|
|
|
const char* ecs_doc_get_name( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
const EcsDocDescription *ptr = ecs_get_pair( |
|
world, entity, EcsDocDescription, EcsName); |
|
if (ptr) { |
|
return ptr->value; |
|
} else { |
|
return ecs_get_name(world, entity); |
|
} |
|
} |
|
|
|
const char* ecs_doc_get_brief( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
const EcsDocDescription *ptr = ecs_get_pair( |
|
world, entity, EcsDocDescription, EcsDocBrief); |
|
if (ptr) { |
|
return ptr->value; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
const char* ecs_doc_get_detail( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
const EcsDocDescription *ptr = ecs_get_pair( |
|
world, entity, EcsDocDescription, EcsDocDetail); |
|
if (ptr) { |
|
return ptr->value; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
const char* ecs_doc_get_link( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
const EcsDocDescription *ptr = ecs_get_pair( |
|
world, entity, EcsDocDescription, EcsDocLink); |
|
if (ptr) { |
|
return ptr->value; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
const char* ecs_doc_get_color( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
const EcsDocDescription *ptr = ecs_get_pair( |
|
world, entity, EcsDocDescription, EcsDocColor); |
|
if (ptr) { |
|
return ptr->value; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
void FlecsDocImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsDoc); |
|
|
|
ecs_set_name_prefix(world, "EcsDoc"); |
|
|
|
flecs_bootstrap_component(world, EcsDocDescription); |
|
flecs_bootstrap_tag(world, EcsDocBrief); |
|
flecs_bootstrap_tag(world, EcsDocDetail); |
|
flecs_bootstrap_tag(world, EcsDocLink); |
|
flecs_bootstrap_tag(world, EcsDocColor); |
|
|
|
ecs_set_hooks(world, EcsDocDescription, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsDocDescription), |
|
.copy = ecs_copy(EcsDocDescription), |
|
.dtor = ecs_dtor(EcsDocDescription) |
|
}); |
|
|
|
ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); |
|
ecs_add_id(world, ecs_id(EcsDocDescription), EcsPrivate); |
|
|
|
ecs_doc_set_brief(world, EcsDocBrief, "Brief description"); |
|
ecs_doc_set_brief(world, EcsDocDetail, "Detailed description"); |
|
ecs_doc_set_brief(world, EcsDocLink, "Link to additional documentation"); |
|
ecs_doc_set_brief(world, EcsDocColor, "Color hint for entity"); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/flecs_cpp.c |
|
* @brief Utilities for C++ addon. |
|
*/ |
|
|
|
#include <ctype.h> |
|
|
|
/* Utilities for C++ API */ |
|
|
|
#ifdef FLECS_CPP |
|
|
|
/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to |
|
* a uniform identifier */ |
|
|
|
#define ECS_CONST_PREFIX "const " |
|
#define ECS_STRUCT_PREFIX "struct " |
|
#define ECS_CLASS_PREFIX "class " |
|
#define ECS_ENUM_PREFIX "enum " |
|
|
|
#define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) |
|
#define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) |
|
#define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) |
|
#define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) |
|
|
|
static |
|
ecs_size_t ecs_cpp_strip_prefix( |
|
char *typeName, |
|
ecs_size_t len, |
|
const char *prefix, |
|
ecs_size_t prefix_len) |
|
{ |
|
if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { |
|
ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); |
|
typeName[len - prefix_len] = '\0'; |
|
len -= prefix_len; |
|
} |
|
return len; |
|
} |
|
|
|
static |
|
void ecs_cpp_trim_type_name( |
|
char *typeName) |
|
{ |
|
ecs_size_t len = ecs_os_strlen(typeName); |
|
|
|
len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); |
|
len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); |
|
len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); |
|
len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); |
|
|
|
while (typeName[len - 1] == ' ' || |
|
typeName[len - 1] == '&' || |
|
typeName[len - 1] == '*') |
|
{ |
|
len --; |
|
typeName[len] = '\0'; |
|
} |
|
|
|
/* Remove const at end of string */ |
|
if (len > ECS_CONST_LEN) { |
|
if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { |
|
typeName[len - ECS_CONST_LEN] = '\0'; |
|
} |
|
len -= ECS_CONST_LEN; |
|
} |
|
|
|
/* Check if there are any remaining "struct " strings, which can happen |
|
* if this is a template type on msvc. */ |
|
if (len > ECS_STRUCT_LEN) { |
|
char *ptr = typeName; |
|
while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { |
|
/* Make sure we're not matched with part of a longer identifier |
|
* that contains 'struct' */ |
|
if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { |
|
ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, |
|
ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); |
|
len -= ECS_STRUCT_LEN; |
|
} |
|
} |
|
} |
|
} |
|
|
|
char* ecs_cpp_get_type_name( |
|
char *type_name, |
|
const char *func_name, |
|
size_t len, |
|
size_t front_len) |
|
{ |
|
memcpy(type_name, func_name + front_len, len); |
|
type_name[len] = '\0'; |
|
ecs_cpp_trim_type_name(type_name); |
|
return type_name; |
|
} |
|
|
|
char* ecs_cpp_get_symbol_name( |
|
char *symbol_name, |
|
const char *type_name, |
|
size_t len) |
|
{ |
|
// Symbol is same as name, but with '::' replaced with '.' |
|
ecs_os_strcpy(symbol_name, type_name); |
|
|
|
char *ptr; |
|
size_t i; |
|
for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { |
|
if (*ptr == ':') { |
|
symbol_name[i] = '.'; |
|
ptr ++; |
|
} else { |
|
symbol_name[i] = *ptr; |
|
} |
|
} |
|
|
|
symbol_name[i] = '\0'; |
|
|
|
return symbol_name; |
|
} |
|
|
|
static |
|
const char* flecs_cpp_func_rchr( |
|
const char *func_name, |
|
ecs_size_t func_name_len, |
|
ecs_size_t func_back_len, |
|
char ch) |
|
{ |
|
const char *r = strrchr(func_name, ch); |
|
if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) { |
|
return NULL; |
|
} |
|
return r; |
|
} |
|
|
|
static |
|
const char* flecs_cpp_func_max( |
|
const char *a, |
|
const char *b) |
|
{ |
|
if (a > b) return a; |
|
return b; |
|
} |
|
|
|
char* ecs_cpp_get_constant_name( |
|
char *constant_name, |
|
const char *func_name, |
|
size_t func_name_len, |
|
size_t func_back_len) |
|
{ |
|
ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); |
|
ecs_size_t fb_len = flecs_uto(ecs_size_t, func_back_len); |
|
const char *start = flecs_cpp_func_rchr(func_name, f_len, fb_len, ' '); |
|
start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( |
|
func_name, f_len, fb_len, ')')); |
|
start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( |
|
func_name, f_len, fb_len, ':')); |
|
start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( |
|
func_name, f_len, fb_len, ',')); |
|
ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); |
|
start ++; |
|
|
|
ecs_size_t len = flecs_uto(ecs_size_t, |
|
(f_len - (start - func_name) - fb_len)); |
|
ecs_os_memcpy_n(constant_name, start, char, len); |
|
constant_name[len] = '\0'; |
|
return constant_name; |
|
} |
|
|
|
// Names returned from the name_helper class do not start with :: |
|
// but are relative to the root. If the namespace of the type |
|
// overlaps with the namespace of the current module, strip it from |
|
// the implicit identifier. |
|
// This allows for registration of component types that are not in the |
|
// module namespace to still be registered under the module scope. |
|
const char* ecs_cpp_trim_module( |
|
ecs_world_t *world, |
|
const char *type_name) |
|
{ |
|
ecs_entity_t scope = ecs_get_scope(world); |
|
if (!scope) { |
|
return type_name; |
|
} |
|
|
|
char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); |
|
if (path) { |
|
ecs_size_t len = ecs_os_strlen(path); |
|
if (!ecs_os_strncmp(path, type_name, len)) { |
|
// Type is a child of current parent, trim name of parent |
|
type_name += len; |
|
ecs_assert(type_name[0], ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(type_name[0] == ':', ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(type_name[1] == ':', ECS_INVALID_PARAMETER, NULL); |
|
type_name += 2; |
|
} else { |
|
// Type is not a child of current parent, trim entire path |
|
char *ptr = strrchr(type_name, ':'); |
|
if (ptr) { |
|
type_name = ptr + 1; |
|
} |
|
|
|
} |
|
} |
|
ecs_os_free(path); |
|
|
|
return type_name; |
|
} |
|
|
|
// Validate registered component |
|
void ecs_cpp_component_validate( |
|
ecs_world_t *world, |
|
ecs_entity_t id, |
|
const char *name, |
|
const char *symbol, |
|
size_t size, |
|
size_t alignment, |
|
bool implicit_name) |
|
{ |
|
/* If entity has a name check if it matches */ |
|
if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { |
|
if (!implicit_name && id >= EcsFirstUserComponentId) { |
|
#ifndef FLECS_NDEBUG |
|
char *path = ecs_get_path_w_sep( |
|
world, 0, id, "::", NULL); |
|
if (ecs_os_strcmp(path, name)) { |
|
ecs_abort(ECS_INCONSISTENT_NAME, |
|
"component '%s' already registered with name '%s'", |
|
name, path); |
|
} |
|
ecs_os_free(path); |
|
#endif |
|
} |
|
|
|
if (symbol) { |
|
const char *existing_symbol = ecs_get_symbol(world, id); |
|
if (existing_symbol) { |
|
if (ecs_os_strcmp(symbol, existing_symbol)) { |
|
ecs_abort(ECS_INCONSISTENT_NAME, |
|
"component '%s' with symbol '%s' already registered with symbol '%s'", |
|
name, symbol, existing_symbol); |
|
} |
|
} |
|
} |
|
} else { |
|
/* Ensure that the entity id valid */ |
|
if (!ecs_is_alive(world, id)) { |
|
ecs_ensure(world, id); |
|
} |
|
|
|
/* Register name with entity, so that when the entity is created the |
|
* correct id will be resolved from the name. Only do this when the |
|
* entity is empty. */ |
|
ecs_add_path_w_sep(world, id, 0, name, "::", "::"); |
|
} |
|
|
|
/* If a component was already registered with this id but with a |
|
* different size, the ecs_component_init function will fail. */ |
|
|
|
/* We need to explicitly call ecs_component_init here again. Even though |
|
* the component was already registered, it may have been registered |
|
* with a different world. This ensures that the component is registered |
|
* with the same id for the current world. |
|
* If the component was registered already, nothing will change. */ |
|
ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ |
|
.entity = id, |
|
.type.size = flecs_uto(int32_t, size), |
|
.type.alignment = flecs_uto(int32_t, alignment) |
|
}); |
|
(void)ent; |
|
ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
ecs_entity_t ecs_cpp_component_register( |
|
ecs_world_t *world, |
|
ecs_entity_t id, |
|
const char *name, |
|
const char *symbol, |
|
ecs_size_t size, |
|
ecs_size_t alignment, |
|
bool implicit_name, |
|
bool *existing_out) |
|
{ |
|
(void)size; |
|
(void)alignment; |
|
|
|
/* If the component is not yet registered, ensure no other component |
|
* or entity has been registered with this name. Ensure component is |
|
* looked up from root. */ |
|
bool existing = false; |
|
ecs_entity_t prev_scope = ecs_set_scope(world, 0); |
|
ecs_entity_t ent; |
|
if (id) { |
|
ent = id; |
|
} else { |
|
ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); |
|
existing = ent != 0 && ecs_has(world, ent, EcsComponent); |
|
} |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* If entity exists, compare symbol name to ensure that the component |
|
* we are trying to register under this name is the same */ |
|
if (ent) { |
|
const EcsComponent *component = ecs_get(world, ent, EcsComponent); |
|
if (component != NULL) { |
|
const char *sym = ecs_get_symbol(world, ent); |
|
if (sym && ecs_os_strcmp(sym, symbol)) { |
|
/* Application is trying to register a type with an entity that |
|
* was already associated with another type. In most cases this |
|
* is an error, with the exception of a scenario where the |
|
* application is wrapping a C type with a C++ type. |
|
* |
|
* In this case the C++ type typically inherits from the C type, |
|
* and adds convenience methods to the derived class without |
|
* changing anything that would change the size or layout. |
|
* |
|
* To meet this condition, the new type must have the same size |
|
* and alignment as the existing type, and the name of the type |
|
* type must be equal to the registered name (not symbol). |
|
* |
|
* The latter ensures that it was the intent of the application |
|
* to alias the type, vs. accidentally registering an unrelated |
|
* type with the same size/alignment. */ |
|
char *type_path = ecs_get_fullpath(world, ent); |
|
if (ecs_os_strcmp(type_path, symbol) || |
|
component->size != size || |
|
component->alignment != alignment) |
|
{ |
|
ecs_err( |
|
"component with name '%s' is already registered for"\ |
|
" type '%s' (trying to register for type '%s')", |
|
name, sym, symbol); |
|
ecs_abort(ECS_NAME_IN_USE, NULL); |
|
} |
|
ecs_os_free(type_path); |
|
} else if (!sym) { |
|
ecs_set_symbol(world, ent, symbol); |
|
} |
|
} |
|
|
|
/* If no entity is found, lookup symbol to check if the component was |
|
* registered under a different name. */ |
|
} else if (!implicit_name) { |
|
ent = ecs_lookup_symbol(world, symbol, false, false); |
|
ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); |
|
} |
|
|
|
if (existing_out) { |
|
*existing_out = existing; |
|
} |
|
|
|
return ent; |
|
} |
|
|
|
ecs_entity_t ecs_cpp_component_register_explicit( |
|
ecs_world_t *world, |
|
ecs_entity_t s_id, |
|
ecs_entity_t id, |
|
const char *name, |
|
const char *type_name, |
|
const char *symbol, |
|
size_t size, |
|
size_t alignment, |
|
bool is_component, |
|
bool *existing_out) |
|
{ |
|
char *existing_name = NULL; |
|
if (existing_out) *existing_out = false; |
|
|
|
// 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 first check if a type with the provided |
|
// symbol was already registered. |
|
id = ecs_lookup_symbol(world, symbol, false, false); |
|
if (id) { |
|
existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); |
|
name = existing_name; |
|
if (existing_out) *existing_out = true; |
|
} 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 |
|
// the name from the type. |
|
if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { |
|
name = ecs_cpp_trim_module(world, type_name); |
|
} |
|
} |
|
|
|
ecs_entity_t entity; |
|
if (is_component || size != 0) { |
|
entity = ecs_entity(world, { |
|
.id = s_id, |
|
.name = name, |
|
.sep = "::", |
|
.root_sep = "::", |
|
.symbol = symbol, |
|
.use_low_id = true |
|
}); |
|
ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); |
|
|
|
entity = ecs_component_init(world, &(ecs_component_desc_t){ |
|
.entity = entity, |
|
.type.size = flecs_uto(int32_t, size), |
|
.type.alignment = flecs_uto(int32_t, alignment) |
|
}); |
|
ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); |
|
} else { |
|
entity = ecs_entity(world, { |
|
.id = s_id, |
|
.name = name, |
|
.sep = "::", |
|
.root_sep = "::", |
|
.symbol = symbol, |
|
.use_low_id = true |
|
}); |
|
} |
|
|
|
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; |
|
} |
|
|
|
void ecs_cpp_enum_init( |
|
ecs_world_t *world, |
|
ecs_entity_t id) |
|
{ |
|
(void)world; |
|
(void)id; |
|
#ifdef FLECS_META |
|
ecs_suspend_readonly_state_t readonly_state; |
|
world = flecs_suspend_readonly(world, &readonly_state); |
|
ecs_set(world, id, EcsEnum, {0}); |
|
flecs_resume_readonly(world, &readonly_state); |
|
#endif |
|
} |
|
|
|
ecs_entity_t ecs_cpp_enum_constant_register( |
|
ecs_world_t *world, |
|
ecs_entity_t parent, |
|
ecs_entity_t id, |
|
const char *name, |
|
int value) |
|
{ |
|
ecs_suspend_readonly_state_t readonly_state; |
|
world = flecs_suspend_readonly(world, &readonly_state); |
|
|
|
const char *parent_name = ecs_get_name(world, parent); |
|
ecs_size_t parent_name_len = ecs_os_strlen(parent_name); |
|
if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { |
|
name += parent_name_len; |
|
if (name[0] == '_') { |
|
name ++; |
|
} |
|
} |
|
|
|
ecs_entity_t prev = ecs_set_scope(world, parent); |
|
id = ecs_entity(world, { |
|
.id = id, |
|
.name = name |
|
}); |
|
ecs_assert(id != 0, ECS_INVALID_OPERATION, name); |
|
ecs_set_scope(world, prev); |
|
|
|
#ifdef FLECS_DEBUG |
|
const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); |
|
ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); |
|
ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, |
|
"enum component must have 32bit size"); |
|
#endif |
|
|
|
#ifdef FLECS_META |
|
ecs_set_id(world, id, ecs_pair(EcsConstant, ecs_id(ecs_i32_t)), |
|
sizeof(ecs_i32_t), &value); |
|
#endif |
|
|
|
flecs_resume_readonly(world, &readonly_state); |
|
|
|
ecs_trace("#[green]constant#[reset] %s.%s created with value %d", |
|
ecs_get_name(world, parent), name, value); |
|
|
|
return id; |
|
} |
|
|
|
static int32_t flecs_reset_count = 0; |
|
|
|
int32_t ecs_cpp_reset_count_get(void) { |
|
return flecs_reset_count; |
|
} |
|
|
|
int32_t ecs_cpp_reset_count_inc(void) { |
|
return ++flecs_reset_count; |
|
} |
|
|
|
#ifdef FLECS_META |
|
const ecs_member_t* ecs_cpp_last_member( |
|
const ecs_world_t *world, |
|
ecs_entity_t type) |
|
{ |
|
const EcsStruct *st = ecs_get(world, type, EcsStruct); |
|
if (!st) { |
|
char *type_str = ecs_get_fullpath(world, type); |
|
ecs_err("entity '%s' is not a struct", type_str); |
|
ecs_os_free(type_str); |
|
return 0; |
|
} |
|
|
|
ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t, |
|
ecs_vec_count(&st->members) - 1); |
|
ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return m; |
|
} |
|
#endif |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/http.c |
|
* @brief HTTP addon. |
|
* |
|
* This is a heavily modified version of the EmbeddableWebServer (see copyright |
|
* below). This version has been stripped from everything not strictly necessary |
|
* for receiving/replying to simple HTTP requests, and has been modified to use |
|
* the Flecs OS API. |
|
* |
|
* EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and |
|
* CONTRIBUTORS (see below) - All rights reserved. |
|
* |
|
* CONTRIBUTORS: |
|
* Martin Pulec - bug fixes, warning fixes, IPv6 support |
|
* Daniel Barry - bug fix (ifa_addr != NULL) |
|
* |
|
* Released under the BSD 2-clause license: |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions are met: |
|
* 1. Redistributions of source code must retain the above copyright notice, |
|
* this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright notice, |
|
* this list of conditions and the following disclaimer in the documentation |
|
* and/or other materials provided with the distribution. THIS SOFTWARE IS |
|
* PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS |
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN |
|
* NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_HTTP |
|
|
|
#ifdef ECS_TARGET_MSVC |
|
#pragma comment(lib, "Ws2_32.lib") |
|
#endif |
|
|
|
#if defined(ECS_TARGET_WINDOWS) |
|
#ifndef WIN32_LEAN_AND_MEAN |
|
#define WIN32_LEAN_AND_MEAN |
|
#endif |
|
#include <winsock2.h> |
|
#include <ws2tcpip.h> |
|
#include <windows.h> |
|
typedef SOCKET ecs_http_socket_t; |
|
#else |
|
#include <unistd.h> |
|
#include <arpa/inet.h> |
|
#include <sys/socket.h> |
|
#include <sys/time.h> |
|
#include <netdb.h> |
|
#include <strings.h> |
|
#include <signal.h> |
|
#include <fcntl.h> |
|
#ifdef __FreeBSD__ |
|
#include <netinet/in.h> |
|
#endif |
|
typedef int ecs_http_socket_t; |
|
|
|
#if !defined(MSG_NOSIGNAL) |
|
#define MSG_NOSIGNAL (0) |
|
#endif |
|
|
|
#endif |
|
|
|
/* Max length of request method */ |
|
#define ECS_HTTP_METHOD_LEN_MAX (8) |
|
|
|
/* Timeout (s) before connection purge */ |
|
#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) |
|
|
|
/* Number of dequeues before purging */ |
|
#define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) |
|
|
|
/* Number of retries receiving request */ |
|
#define ECS_HTTP_REQUEST_RECV_RETRY (10) |
|
|
|
/* Minimum interval between dequeueing requests (ms) */ |
|
#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) |
|
|
|
/* Minimum interval between printing statistics (ms) */ |
|
#define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) |
|
|
|
/* Receive buffer size */ |
|
#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) |
|
|
|
/* Max length of request (path + query + headers + body) */ |
|
#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) |
|
|
|
/* Total number of outstanding send requests */ |
|
#define ECS_HTTP_SEND_QUEUE_MAX (256) |
|
|
|
/* Global statistics */ |
|
int64_t ecs_http_request_received_count = 0; |
|
int64_t ecs_http_request_invalid_count = 0; |
|
int64_t ecs_http_request_handled_ok_count = 0; |
|
int64_t ecs_http_request_handled_error_count = 0; |
|
int64_t ecs_http_request_not_handled_count = 0; |
|
int64_t ecs_http_request_preflight_count = 0; |
|
int64_t ecs_http_send_ok_count = 0; |
|
int64_t ecs_http_send_error_count = 0; |
|
int64_t ecs_http_busy_count = 0; |
|
|
|
/* Send request queue */ |
|
typedef struct ecs_http_send_request_t { |
|
ecs_http_socket_t sock; |
|
char *headers; |
|
int32_t header_length; |
|
char *content; |
|
int32_t content_length; |
|
} ecs_http_send_request_t; |
|
|
|
typedef struct ecs_http_send_queue_t { |
|
ecs_http_send_request_t requests[ECS_HTTP_SEND_QUEUE_MAX]; |
|
int32_t head; |
|
int32_t tail; |
|
ecs_os_thread_t thread; |
|
int32_t wait_ms; |
|
} ecs_http_send_queue_t; |
|
|
|
typedef struct ecs_http_request_key_t { |
|
const char *array; |
|
ecs_size_t count; |
|
} ecs_http_request_key_t; |
|
|
|
typedef struct ecs_http_request_entry_t { |
|
char *content; |
|
int32_t content_length; |
|
int code; |
|
ecs_ftime_t time; |
|
} ecs_http_request_entry_t; |
|
|
|
/* HTTP server struct */ |
|
struct ecs_http_server_t { |
|
bool should_run; |
|
bool running; |
|
|
|
ecs_http_socket_t sock; |
|
ecs_os_mutex_t lock; |
|
ecs_os_thread_t thread; |
|
|
|
ecs_http_reply_action_t callback; |
|
void *ctx; |
|
|
|
ecs_ftime_t cache_timeout; |
|
ecs_ftime_t cache_purge_timeout; |
|
|
|
ecs_sparse_t connections; /* sparse<http_connection_t> */ |
|
ecs_sparse_t requests; /* sparse<http_request_t> */ |
|
|
|
bool initialized; |
|
|
|
uint16_t port; |
|
const char *ipaddr; |
|
|
|
double dequeue_timeout; /* used to not lock request queue too often */ |
|
double stats_timeout; /* used for periodic reporting of statistics */ |
|
|
|
double request_time; /* time spent on requests in last stats interval */ |
|
double request_time_total; /* total time spent on requests */ |
|
int32_t requests_processed; /* requests processed in last stats interval */ |
|
int32_t requests_processed_total; /* total requests processed */ |
|
int32_t dequeue_count; /* number of dequeues in last stats interval */ |
|
ecs_http_send_queue_t send_queue; |
|
|
|
ecs_hashmap_t request_cache; |
|
}; |
|
|
|
/** Fragment state, used by HTTP request parser */ |
|
typedef enum { |
|
HttpFragStateBegin, |
|
HttpFragStateMethod, |
|
HttpFragStatePath, |
|
HttpFragStateVersion, |
|
HttpFragStateHeaderStart, |
|
HttpFragStateHeaderName, |
|
HttpFragStateHeaderValueStart, |
|
HttpFragStateHeaderValue, |
|
HttpFragStateCR, |
|
HttpFragStateCRLF, |
|
HttpFragStateCRLFCR, |
|
HttpFragStateBody, |
|
HttpFragStateDone |
|
} HttpFragState; |
|
|
|
/** A fragment is a partially received HTTP request */ |
|
typedef struct { |
|
HttpFragState state; |
|
ecs_strbuf_t buf; |
|
ecs_http_method_t method; |
|
int32_t body_offset; |
|
int32_t query_offset; |
|
int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; |
|
int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; |
|
int32_t header_count; |
|
int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; |
|
int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; |
|
int32_t param_count; |
|
int32_t content_length; |
|
char *header_buf_ptr; |
|
char header_buf[32]; |
|
bool parse_content_length; |
|
bool invalid; |
|
} ecs_http_fragment_t; |
|
|
|
/** Extend public connection type with fragment data */ |
|
typedef struct { |
|
ecs_http_connection_t pub; |
|
ecs_http_socket_t sock; |
|
|
|
/* Connection is purged after both timeout expires and connection has |
|
* exceeded retry count. This ensures that a connection does not immediately |
|
* timeout when a frame takes longer than usual */ |
|
double dequeue_timeout; |
|
int32_t dequeue_retries; |
|
} ecs_http_connection_impl_t; |
|
|
|
typedef struct { |
|
ecs_http_request_t pub; |
|
uint64_t conn_id; /* for sanity check */ |
|
char *res; |
|
int32_t req_len; |
|
} ecs_http_request_impl_t; |
|
|
|
static |
|
ecs_size_t http_send( |
|
ecs_http_socket_t sock, |
|
const void *buf, |
|
ecs_size_t size, |
|
int flags) |
|
{ |
|
ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); |
|
#ifdef ECS_TARGET_POSIX |
|
ssize_t send_bytes = send(sock, buf, flecs_itosize(size), |
|
flags | MSG_NOSIGNAL); |
|
return flecs_itoi32(send_bytes); |
|
#else |
|
int send_bytes = send(sock, buf, size, flags); |
|
return flecs_itoi32(send_bytes); |
|
#endif |
|
} |
|
|
|
static |
|
ecs_size_t http_recv( |
|
ecs_http_socket_t sock, |
|
void *buf, |
|
ecs_size_t size, |
|
int flags) |
|
{ |
|
ecs_size_t ret; |
|
#ifdef ECS_TARGET_POSIX |
|
ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); |
|
ret = flecs_itoi32(recv_bytes); |
|
#else |
|
int recv_bytes = recv(sock, buf, size, flags); |
|
ret = flecs_itoi32(recv_bytes); |
|
#endif |
|
if (ret == -1) { |
|
ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); |
|
} else if (ret == 0) { |
|
ecs_dbg("recv: received 0 bytes (sock = %d)", sock); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static |
|
void http_sock_set_timeout( |
|
ecs_http_socket_t sock, |
|
int32_t timeout_ms) |
|
{ |
|
int r; |
|
#ifdef ECS_TARGET_POSIX |
|
struct timeval tv; |
|
tv.tv_sec = timeout_ms * 1000; |
|
tv.tv_usec = 0; |
|
r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); |
|
#else |
|
DWORD t = (DWORD)timeout_ms; |
|
r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&t, sizeof t); |
|
#endif |
|
if (r) { |
|
ecs_warn("http: failed to set socket timeout: %s", |
|
ecs_os_strerror(errno)); |
|
} |
|
} |
|
|
|
static |
|
void http_sock_keep_alive( |
|
ecs_http_socket_t sock) |
|
{ |
|
int v = 1; |
|
if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&v, sizeof v)) { |
|
ecs_warn("http: failed to set socket KEEPALIVE: %s", |
|
ecs_os_strerror(errno)); |
|
} |
|
} |
|
|
|
static |
|
void http_sock_nonblock(ecs_http_socket_t sock, bool enable) { |
|
(void)sock; |
|
(void)enable; |
|
#ifdef ECS_TARGET_POSIX |
|
int flags; |
|
flags = fcntl(sock,F_GETFL,0); |
|
if (flags == -1) { |
|
ecs_warn("http: failed to set socket NONBLOCK: %s", |
|
ecs_os_strerror(errno)); |
|
return; |
|
} |
|
if (enable) { |
|
flags = fcntl(sock, F_SETFL, flags | O_NONBLOCK); |
|
} else { |
|
flags = fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); |
|
} |
|
if (flags == -1) { |
|
ecs_warn("http: failed to set socket NONBLOCK: %s", |
|
ecs_os_strerror(errno)); |
|
return; |
|
} |
|
#endif |
|
} |
|
|
|
static |
|
int http_getnameinfo( |
|
const struct sockaddr* addr, |
|
ecs_size_t addr_len, |
|
char *host, |
|
ecs_size_t host_len, |
|
char *port, |
|
ecs_size_t port_len, |
|
int flags) |
|
{ |
|
ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); |
|
#if defined(ECS_TARGET_WINDOWS) |
|
return getnameinfo(addr, addr_len, host, |
|
flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), |
|
flags); |
|
#else |
|
return getnameinfo(addr, flecs_ito(uint32_t, addr_len), host, |
|
flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), |
|
flags); |
|
#endif |
|
} |
|
|
|
static |
|
int http_bind( |
|
ecs_http_socket_t sock, |
|
const struct sockaddr* addr, |
|
ecs_size_t addr_len) |
|
{ |
|
ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); |
|
#if defined(ECS_TARGET_WINDOWS) |
|
return bind(sock, addr, addr_len); |
|
#else |
|
return bind(sock, addr, flecs_ito(uint32_t, addr_len)); |
|
#endif |
|
} |
|
|
|
static |
|
bool http_socket_is_valid( |
|
ecs_http_socket_t sock) |
|
{ |
|
#if defined(ECS_TARGET_WINDOWS) |
|
return sock != INVALID_SOCKET; |
|
#else |
|
return sock >= 0; |
|
#endif |
|
} |
|
|
|
#if defined(ECS_TARGET_WINDOWS) |
|
#define HTTP_SOCKET_INVALID INVALID_SOCKET |
|
#else |
|
#define HTTP_SOCKET_INVALID (-1) |
|
#endif |
|
|
|
static |
|
void http_close( |
|
ecs_http_socket_t *sock) |
|
{ |
|
ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
#if defined(ECS_TARGET_WINDOWS) |
|
closesocket(*sock); |
|
#else |
|
ecs_dbg_2("http: closing socket %u", *sock); |
|
shutdown(*sock, SHUT_RDWR); |
|
close(*sock); |
|
#endif |
|
*sock = HTTP_SOCKET_INVALID; |
|
} |
|
|
|
static |
|
ecs_http_socket_t http_accept( |
|
ecs_http_socket_t sock, |
|
struct sockaddr* addr, |
|
ecs_size_t *addr_len) |
|
{ |
|
socklen_t len = (socklen_t)addr_len[0]; |
|
ecs_http_socket_t result = accept(sock, addr, &len); |
|
addr_len[0] = (ecs_size_t)len; |
|
return result; |
|
} |
|
|
|
static |
|
void http_reply_fini(ecs_http_reply_t* reply) { |
|
ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_os_free(reply->body.content); |
|
} |
|
|
|
static |
|
void http_request_fini(ecs_http_request_impl_t *req) { |
|
ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); |
|
ecs_os_free(req->res); |
|
flecs_sparse_remove_t(&req->pub.conn->server->requests, |
|
ecs_http_request_impl_t, req->pub.id); |
|
} |
|
|
|
static |
|
void http_connection_free(ecs_http_connection_impl_t *conn) { |
|
ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); |
|
uint64_t conn_id = conn->pub.id; |
|
|
|
if (http_socket_is_valid(conn->sock)) { |
|
http_close(&conn->sock); |
|
} |
|
|
|
flecs_sparse_remove_t(&conn->pub.server->connections, |
|
ecs_http_connection_impl_t, conn_id); |
|
} |
|
|
|
// https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int |
|
static |
|
char http_hex_2_int(char a, char b){ |
|
a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); |
|
b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); |
|
return (char)((a << 4) + b); |
|
} |
|
|
|
static |
|
void http_decode_url_str( |
|
char *str) |
|
{ |
|
char ch, *ptr, *dst = str; |
|
for (ptr = str; (ch = *ptr); ptr++) { |
|
if (ch == '%') { |
|
dst[0] = http_hex_2_int(ptr[1], ptr[2]); |
|
dst ++; |
|
ptr += 2; |
|
} else { |
|
dst[0] = ptr[0]; |
|
dst ++; |
|
} |
|
} |
|
dst[0] = '\0'; |
|
} |
|
|
|
static |
|
void http_parse_method( |
|
ecs_http_fragment_t *frag) |
|
{ |
|
char *method = ecs_strbuf_get_small(&frag->buf); |
|
if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; |
|
else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; |
|
else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; |
|
else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; |
|
else if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; |
|
else { |
|
frag->method = EcsHttpMethodUnsupported; |
|
frag->invalid = true; |
|
} |
|
ecs_strbuf_reset(&frag->buf); |
|
} |
|
|
|
static |
|
bool http_header_writable( |
|
ecs_http_fragment_t *frag) |
|
{ |
|
return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; |
|
} |
|
|
|
static |
|
void http_header_buf_reset( |
|
ecs_http_fragment_t *frag) |
|
{ |
|
frag->header_buf[0] = '\0'; |
|
frag->header_buf_ptr = frag->header_buf; |
|
} |
|
|
|
static |
|
void http_header_buf_append( |
|
ecs_http_fragment_t *frag, |
|
char ch) |
|
{ |
|
if ((frag->header_buf_ptr - frag->header_buf) < |
|
ECS_SIZEOF(frag->header_buf)) |
|
{ |
|
frag->header_buf_ptr[0] = ch; |
|
frag->header_buf_ptr ++; |
|
} else { |
|
frag->header_buf_ptr[0] = '\0'; |
|
} |
|
} |
|
|
|
static |
|
uint64_t http_request_key_hash(const void *ptr) { |
|
const ecs_http_request_key_t *key = ptr; |
|
const char *array = key->array; |
|
int32_t count = key->count; |
|
return flecs_hash(array, count * ECS_SIZEOF(char)); |
|
} |
|
|
|
static |
|
int http_request_key_compare(const void *ptr_1, const void *ptr_2) { |
|
const ecs_http_request_key_t *type_1 = ptr_1; |
|
const ecs_http_request_key_t *type_2 = ptr_2; |
|
|
|
int32_t count_1 = type_1->count; |
|
int32_t count_2 = type_2->count; |
|
|
|
if (count_1 != count_2) { |
|
return (count_1 > count_2) - (count_1 < count_2); |
|
} |
|
|
|
return ecs_os_memcmp(type_1->array, type_2->array, count_1); |
|
} |
|
|
|
static |
|
ecs_http_request_entry_t* http_find_request_entry( |
|
ecs_http_server_t *srv, |
|
const char *array, |
|
int32_t count) |
|
{ |
|
ecs_http_request_key_t key; |
|
key.array = array; |
|
key.count = count; |
|
|
|
ecs_time_t t = {0, 0}; |
|
ecs_http_request_entry_t *entry = flecs_hashmap_get( |
|
&srv->request_cache, &key, ecs_http_request_entry_t); |
|
|
|
if (entry) { |
|
ecs_ftime_t tf = (ecs_ftime_t)ecs_time_measure(&t); |
|
if ((tf - entry->time) < srv->cache_timeout) { |
|
return entry; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
static |
|
void http_insert_request_entry( |
|
ecs_http_server_t *srv, |
|
ecs_http_request_impl_t *req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
int32_t content_length = ecs_strbuf_written(&reply->body); |
|
if (!content_length) { |
|
return; |
|
} |
|
|
|
ecs_http_request_key_t key; |
|
key.array = req->res; |
|
key.count = req->req_len; |
|
ecs_http_request_entry_t *entry = flecs_hashmap_get( |
|
&srv->request_cache, &key, ecs_http_request_entry_t); |
|
if (!entry) { |
|
flecs_hashmap_result_t elem = flecs_hashmap_ensure( |
|
&srv->request_cache, &key, ecs_http_request_entry_t); |
|
ecs_http_request_key_t *elem_key = elem.key; |
|
elem_key->array = ecs_os_memdup_n(key.array, char, key.count); |
|
entry = elem.value; |
|
} else { |
|
ecs_os_free(entry->content); |
|
} |
|
|
|
ecs_time_t t = {0, 0}; |
|
entry->time = (ecs_ftime_t)ecs_time_measure(&t); |
|
entry->content_length = ecs_strbuf_written(&reply->body); |
|
entry->content = ecs_strbuf_get(&reply->body); |
|
entry->code = reply->code; |
|
ecs_strbuf_appendstrn(&reply->body, |
|
entry->content, entry->content_length); |
|
} |
|
|
|
static |
|
char* http_decode_request( |
|
ecs_http_request_impl_t *req, |
|
ecs_http_fragment_t *frag) |
|
{ |
|
ecs_os_zeromem(req); |
|
|
|
ecs_size_t req_len = frag->buf.length; |
|
char *res = ecs_strbuf_get(&frag->buf); |
|
if (!res) { |
|
return NULL; |
|
} |
|
|
|
req->pub.method = frag->method; |
|
req->pub.path = res + 1; |
|
http_decode_url_str(req->pub.path); |
|
|
|
if (frag->body_offset) { |
|
req->pub.body = &res[frag->body_offset]; |
|
} |
|
int32_t i, count = frag->header_count; |
|
for (i = 0; i < count; i ++) { |
|
req->pub.headers[i].key = &res[frag->header_offsets[i]]; |
|
req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; |
|
} |
|
count = frag->param_count; |
|
for (i = 0; i < count; i ++) { |
|
req->pub.params[i].key = &res[frag->param_offsets[i]]; |
|
req->pub.params[i].value = &res[frag->param_value_offsets[i]]; |
|
/* Safe, member is only const so that end-user can't change it */ |
|
http_decode_url_str(ECS_CONST_CAST(char*, req->pub.params[i].value)); |
|
} |
|
|
|
req->pub.header_count = frag->header_count; |
|
req->pub.param_count = frag->param_count; |
|
req->res = res; |
|
req->req_len = frag->header_offsets[0]; |
|
if (!req->req_len) { |
|
req->req_len = req_len; |
|
} |
|
|
|
return res; |
|
} |
|
|
|
static |
|
ecs_http_request_entry_t* http_enqueue_request( |
|
ecs_http_connection_impl_t *conn, |
|
uint64_t conn_id, |
|
ecs_http_fragment_t *frag) |
|
{ |
|
ecs_http_server_t *srv = conn->pub.server; |
|
|
|
ecs_os_mutex_lock(srv->lock); |
|
bool is_alive = conn->pub.id == conn_id; |
|
|
|
if (!is_alive || frag->invalid) { |
|
/* Don't enqueue invalid requests or requests for purged connections */ |
|
ecs_strbuf_reset(&frag->buf); |
|
} else { |
|
ecs_http_request_impl_t req; |
|
char *res = http_decode_request(&req, frag); |
|
if (res) { |
|
req.pub.conn = (ecs_http_connection_t*)conn; |
|
|
|
/* Check cache for GET requests */ |
|
if (frag->method == EcsHttpGet) { |
|
ecs_http_request_entry_t *entry = |
|
http_find_request_entry(srv, res, frag->header_offsets[0]); |
|
if (entry) { |
|
/* If an entry is found, don't enqueue a request. Instead |
|
* return the cached response immediately. */ |
|
ecs_os_free(res); |
|
return entry; |
|
} |
|
} |
|
|
|
ecs_http_request_impl_t *req_ptr = flecs_sparse_add_t( |
|
&srv->requests, ecs_http_request_impl_t); |
|
*req_ptr = req; |
|
req_ptr->pub.id = flecs_sparse_last_id(&srv->requests); |
|
req_ptr->conn_id = conn->pub.id; |
|
ecs_os_linc(&ecs_http_request_received_count); |
|
} |
|
} |
|
|
|
ecs_os_mutex_unlock(srv->lock); |
|
return NULL; |
|
} |
|
|
|
static |
|
bool http_parse_request( |
|
ecs_http_fragment_t *frag, |
|
const char* req_frag, |
|
ecs_size_t req_frag_len) |
|
{ |
|
int32_t i; |
|
for (i = 0; i < req_frag_len; i++) { |
|
char c = req_frag[i]; |
|
switch (frag->state) { |
|
case HttpFragStateBegin: |
|
ecs_os_memset_t(frag, 0, ecs_http_fragment_t); |
|
frag->state = HttpFragStateMethod; |
|
frag->header_buf_ptr = frag->header_buf; |
|
|
|
/* fall through */ |
|
case HttpFragStateMethod: |
|
if (c == ' ') { |
|
http_parse_method(frag); |
|
ecs_strbuf_reset(&frag->buf); |
|
frag->state = HttpFragStatePath; |
|
frag->buf.content = NULL; |
|
} else { |
|
ecs_strbuf_appendch(&frag->buf, c); |
|
} |
|
break; |
|
case HttpFragStatePath: |
|
if (c == ' ') { |
|
frag->state = HttpFragStateVersion; |
|
ecs_strbuf_appendch(&frag->buf, '\0'); |
|
} else { |
|
if (c == '?' || c == '=' || c == '&') { |
|
ecs_strbuf_appendch(&frag->buf, '\0'); |
|
int32_t offset = ecs_strbuf_written(&frag->buf); |
|
if (c == '?' || c == '&') { |
|
frag->param_offsets[frag->param_count] = offset; |
|
} else { |
|
frag->param_value_offsets[frag->param_count] = offset; |
|
frag->param_count ++; |
|
} |
|
} else { |
|
ecs_strbuf_appendch(&frag->buf, c); |
|
} |
|
} |
|
break; |
|
case HttpFragStateVersion: |
|
if (c == '\r') { |
|
frag->state = HttpFragStateCR; |
|
} /* version is not stored */ |
|
break; |
|
case HttpFragStateHeaderStart: |
|
if (http_header_writable(frag)) { |
|
frag->header_offsets[frag->header_count] = |
|
ecs_strbuf_written(&frag->buf); |
|
} |
|
http_header_buf_reset(frag); |
|
frag->state = HttpFragStateHeaderName; |
|
|
|
/* fall through */ |
|
case HttpFragStateHeaderName: |
|
if (c == ':') { |
|
frag->state = HttpFragStateHeaderValueStart; |
|
http_header_buf_append(frag, '\0'); |
|
frag->parse_content_length = !ecs_os_strcmp( |
|
frag->header_buf, "Content-Length"); |
|
|
|
if (http_header_writable(frag)) { |
|
ecs_strbuf_appendch(&frag->buf, '\0'); |
|
frag->header_value_offsets[frag->header_count] = |
|
ecs_strbuf_written(&frag->buf); |
|
} |
|
} else if (c == '\r') { |
|
frag->state = HttpFragStateCR; |
|
} else { |
|
http_header_buf_append(frag, c); |
|
if (http_header_writable(frag)) { |
|
ecs_strbuf_appendch(&frag->buf, c); |
|
} |
|
} |
|
break; |
|
case HttpFragStateHeaderValueStart: |
|
http_header_buf_reset(frag); |
|
frag->state = HttpFragStateHeaderValue; |
|
if (c == ' ') { /* skip first space */ |
|
break; |
|
} |
|
|
|
/* fall through */ |
|
case HttpFragStateHeaderValue: |
|
if (c == '\r') { |
|
if (frag->parse_content_length) { |
|
http_header_buf_append(frag, '\0'); |
|
int32_t len = atoi(frag->header_buf); |
|
if (len < 0) { |
|
frag->invalid = true; |
|
} else { |
|
frag->content_length = len; |
|
} |
|
frag->parse_content_length = false; |
|
} |
|
if (http_header_writable(frag)) { |
|
int32_t cur = ecs_strbuf_written(&frag->buf); |
|
if (frag->header_offsets[frag->header_count] < cur && |
|
frag->header_value_offsets[frag->header_count] < cur) |
|
{ |
|
ecs_strbuf_appendch(&frag->buf, '\0'); |
|
frag->header_count ++; |
|
} |
|
} |
|
frag->state = HttpFragStateCR; |
|
} else { |
|
if (frag->parse_content_length) { |
|
http_header_buf_append(frag, c); |
|
} |
|
if (http_header_writable(frag)) { |
|
ecs_strbuf_appendch(&frag->buf, c); |
|
} |
|
} |
|
break; |
|
case HttpFragStateCR: |
|
if (c == '\n') { |
|
frag->state = HttpFragStateCRLF; |
|
} else { |
|
frag->state = HttpFragStateHeaderStart; |
|
} |
|
break; |
|
case HttpFragStateCRLF: |
|
if (c == '\r') { |
|
frag->state = HttpFragStateCRLFCR; |
|
} else { |
|
frag->state = HttpFragStateHeaderStart; |
|
i--; |
|
} |
|
break; |
|
case HttpFragStateCRLFCR: |
|
if (c == '\n') { |
|
if (frag->content_length != 0) { |
|
frag->body_offset = ecs_strbuf_written(&frag->buf); |
|
frag->state = HttpFragStateBody; |
|
} else { |
|
frag->state = HttpFragStateDone; |
|
} |
|
} else { |
|
frag->state = HttpFragStateHeaderStart; |
|
} |
|
break; |
|
case HttpFragStateBody: { |
|
ecs_strbuf_appendch(&frag->buf, c); |
|
if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == |
|
frag->content_length) |
|
{ |
|
frag->state = HttpFragStateDone; |
|
} |
|
} |
|
break; |
|
case HttpFragStateDone: |
|
break; |
|
} |
|
} |
|
|
|
if (frag->state == HttpFragStateDone) { |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
ecs_http_send_request_t* http_send_queue_post( |
|
ecs_http_server_t *srv) |
|
{ |
|
/* This function should only be called while the server is locked. Before |
|
* the lock is released, the returned element should be populated. */ |
|
ecs_http_send_queue_t *sq = &srv->send_queue; |
|
int32_t next = (sq->head + 1) % ECS_HTTP_SEND_QUEUE_MAX; |
|
if (next == sq->tail) { |
|
return NULL; |
|
} |
|
|
|
/* Don't enqueue new requests if server is shutting down */ |
|
if (!srv->should_run) { |
|
return NULL; |
|
} |
|
|
|
/* Return element at end of the queue */ |
|
ecs_http_send_request_t *result = &sq->requests[sq->head]; |
|
sq->head = next; |
|
return result; |
|
} |
|
|
|
static |
|
ecs_http_send_request_t* http_send_queue_get( |
|
ecs_http_server_t *srv) |
|
{ |
|
ecs_os_mutex_lock(srv->lock); |
|
ecs_http_send_queue_t *sq = &srv->send_queue; |
|
if (sq->tail == sq->head) { |
|
return NULL; |
|
} |
|
|
|
int32_t next = (sq->tail + 1) % ECS_HTTP_SEND_QUEUE_MAX; |
|
ecs_http_send_request_t *result = &sq->requests[sq->tail]; |
|
sq->tail = next; |
|
return result; |
|
} |
|
|
|
static |
|
void* http_server_send_queue(void* arg) { |
|
ecs_http_server_t *srv = arg; |
|
int32_t wait_ms = srv->send_queue.wait_ms; |
|
|
|
/* Run for as long as the server is running or there are messages. When the |
|
* server is stopping, no new messages will be enqueued */ |
|
while (srv->should_run || (srv->send_queue.head != srv->send_queue.tail)) { |
|
ecs_http_send_request_t* r = http_send_queue_get(srv); |
|
if (!r) { |
|
ecs_os_mutex_unlock(srv->lock); |
|
/* If the queue is empty, wait so we don't run too fast */ |
|
if (srv->should_run) { |
|
ecs_os_sleep(0, wait_ms * 1000 * 1000); |
|
} |
|
} else { |
|
ecs_http_socket_t sock = r->sock; |
|
char *headers = r->headers; |
|
int32_t headers_length = r->header_length; |
|
char *content = r->content; |
|
int32_t content_length = r->content_length; |
|
ecs_os_mutex_unlock(srv->lock); |
|
|
|
if (http_socket_is_valid(sock)) { |
|
bool error = false; |
|
|
|
http_sock_nonblock(sock, false); |
|
|
|
/* Write headers */ |
|
ecs_size_t written = http_send(sock, headers, headers_length, 0); |
|
if (written != headers_length) { |
|
ecs_err("http: failed to write HTTP response headers: %s", |
|
ecs_os_strerror(errno)); |
|
ecs_os_linc(&ecs_http_send_error_count); |
|
error = true; |
|
} else if (content_length >= 0) { |
|
/* Write content */ |
|
written = http_send(sock, content, content_length, 0); |
|
if (written != content_length) { |
|
ecs_err("http: failed to write HTTP response body: %s", |
|
ecs_os_strerror(errno)); |
|
ecs_os_linc(&ecs_http_send_error_count); |
|
error = true; |
|
} |
|
} |
|
if (!error) { |
|
ecs_os_linc(&ecs_http_send_ok_count); |
|
} |
|
|
|
http_close(&sock); |
|
} else { |
|
ecs_err("http: invalid socket\n"); |
|
} |
|
|
|
ecs_os_free(content); |
|
ecs_os_free(headers); |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
static |
|
void http_append_send_headers( |
|
ecs_strbuf_t *hdrs, |
|
int code, |
|
const char* status, |
|
const char* content_type, |
|
ecs_strbuf_t *extra_headers, |
|
ecs_size_t content_len, |
|
bool preflight) |
|
{ |
|
ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); |
|
ecs_strbuf_appendint(hdrs, code); |
|
ecs_strbuf_appendch(hdrs, ' '); |
|
ecs_strbuf_appendstr(hdrs, status); |
|
ecs_strbuf_appendlit(hdrs, "\r\n"); |
|
|
|
if (content_type) { |
|
ecs_strbuf_appendlit(hdrs, "Content-Type: "); |
|
ecs_strbuf_appendstr(hdrs, content_type); |
|
ecs_strbuf_appendlit(hdrs, "\r\n"); |
|
} |
|
|
|
if (content_len >= 0) { |
|
ecs_strbuf_appendlit(hdrs, "Content-Length: "); |
|
ecs_strbuf_append(hdrs, "%d", content_len); |
|
ecs_strbuf_appendlit(hdrs, "\r\n"); |
|
} |
|
|
|
ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); |
|
if (preflight) { |
|
ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); |
|
ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, OPTIONS\r\n"); |
|
ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); |
|
} |
|
|
|
ecs_strbuf_mergebuff(hdrs, extra_headers); |
|
|
|
ecs_strbuf_appendlit(hdrs, "\r\n"); |
|
} |
|
|
|
static |
|
void http_send_reply( |
|
ecs_http_connection_impl_t* conn, |
|
ecs_http_reply_t* reply, |
|
bool preflight) |
|
{ |
|
ecs_strbuf_t hdrs = ECS_STRBUF_INIT; |
|
int32_t content_length = reply->body.length; |
|
char *content = ecs_strbuf_get(&reply->body); |
|
|
|
/* Use asynchronous send queue for outgoing data so send operations won't |
|
* hold up main thread */ |
|
ecs_http_send_request_t *req = NULL; |
|
|
|
if (!preflight) { |
|
req = http_send_queue_post(conn->pub.server); |
|
if (!req) { |
|
reply->code = 503; /* queue full, server is busy */ |
|
ecs_os_linc(&ecs_http_busy_count); |
|
} |
|
} |
|
|
|
http_append_send_headers(&hdrs, reply->code, reply->status, |
|
reply->content_type, &reply->headers, content_length, preflight); |
|
ecs_size_t headers_length = ecs_strbuf_written(&hdrs); |
|
char *headers = ecs_strbuf_get(&hdrs); |
|
|
|
if (!req) { |
|
ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); |
|
if (written != headers_length) { |
|
ecs_err("http: failed to send reply to '%s:%s': %s", |
|
conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); |
|
ecs_os_linc(&ecs_http_send_error_count); |
|
} |
|
ecs_os_free(content); |
|
ecs_os_free(headers); |
|
http_close(&conn->sock); |
|
return; |
|
} |
|
|
|
/* Second, enqueue send request for response body */ |
|
req->sock = conn->sock; |
|
req->headers = headers; |
|
req->header_length = headers_length; |
|
req->content = content; |
|
req->content_length = content_length; |
|
|
|
/* Take ownership of values */ |
|
reply->body.content = NULL; |
|
conn->sock = HTTP_SOCKET_INVALID; |
|
} |
|
|
|
static |
|
void http_recv_connection( |
|
ecs_http_server_t *srv, |
|
ecs_http_connection_impl_t *conn, |
|
uint64_t conn_id, |
|
ecs_http_socket_t sock) |
|
{ |
|
ecs_size_t bytes_read; |
|
char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; |
|
ecs_http_fragment_t frag = {0}; |
|
int32_t retries = 0; |
|
|
|
do { |
|
if ((bytes_read = http_recv( |
|
sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) |
|
{ |
|
bool is_alive = conn->pub.id == conn_id; |
|
if (!is_alive) { |
|
/* Connection has been purged by main thread */ |
|
goto done; |
|
} |
|
|
|
if (http_parse_request(&frag, recv_buf, bytes_read)) { |
|
if (frag.method == EcsHttpOptions) { |
|
ecs_http_reply_t reply; |
|
reply.body = ECS_STRBUF_INIT; |
|
reply.code = 200; |
|
reply.content_type = NULL; |
|
reply.headers = ECS_STRBUF_INIT; |
|
reply.status = "OK"; |
|
http_send_reply(conn, &reply, true); |
|
ecs_os_linc(&ecs_http_request_preflight_count); |
|
} else { |
|
ecs_http_request_entry_t *entry = |
|
http_enqueue_request(conn, conn_id, &frag); |
|
if (entry) { |
|
ecs_http_reply_t reply; |
|
reply.body = ECS_STRBUF_INIT; |
|
reply.code = entry->code; |
|
reply.content_type = "application/json"; |
|
reply.headers = ECS_STRBUF_INIT; |
|
reply.status = "OK"; |
|
ecs_strbuf_appendstrn(&reply.body, |
|
entry->content, entry->content_length); |
|
http_send_reply(conn, &reply, false); |
|
http_connection_free(conn); |
|
|
|
/* Lock was transferred from enqueue_request */ |
|
ecs_os_mutex_unlock(srv->lock); |
|
} |
|
} |
|
} else { |
|
ecs_os_linc(&ecs_http_request_invalid_count); |
|
} |
|
} |
|
|
|
ecs_os_sleep(0, 10 * 1000 * 1000); |
|
} while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); |
|
|
|
if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { |
|
http_close(&sock); |
|
} |
|
|
|
done: |
|
ecs_strbuf_reset(&frag.buf); |
|
} |
|
|
|
typedef struct { |
|
ecs_http_connection_impl_t *conn; |
|
uint64_t id; |
|
} http_conn_res_t; |
|
|
|
static |
|
http_conn_res_t http_init_connection( |
|
ecs_http_server_t *srv, |
|
ecs_http_socket_t sock_conn, |
|
struct sockaddr_storage *remote_addr, |
|
ecs_size_t remote_addr_len) |
|
{ |
|
http_sock_set_timeout(sock_conn, 100); |
|
http_sock_keep_alive(sock_conn); |
|
http_sock_nonblock(sock_conn, true); |
|
|
|
/* Create new connection */ |
|
ecs_os_mutex_lock(srv->lock); |
|
ecs_http_connection_impl_t *conn = flecs_sparse_add_t( |
|
&srv->connections, ecs_http_connection_impl_t); |
|
uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); |
|
conn->pub.server = srv; |
|
conn->sock = sock_conn; |
|
ecs_os_mutex_unlock(srv->lock); |
|
|
|
char *remote_host = conn->pub.host; |
|
char *remote_port = conn->pub.port; |
|
|
|
/* Fetch name & port info */ |
|
if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, |
|
remote_host, ECS_SIZEOF(conn->pub.host), |
|
remote_port, ECS_SIZEOF(conn->pub.port), |
|
NI_NUMERICHOST | NI_NUMERICSERV)) |
|
{ |
|
ecs_os_strcpy(remote_host, "unknown"); |
|
ecs_os_strcpy(remote_port, "unknown"); |
|
} |
|
|
|
ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", |
|
remote_host, remote_port, sock_conn); |
|
|
|
return (http_conn_res_t){ .conn = conn, .id = conn_id }; |
|
} |
|
|
|
static |
|
void http_accept_connections( |
|
ecs_http_server_t* srv, |
|
const struct sockaddr* addr, |
|
ecs_size_t addr_len) |
|
{ |
|
#ifdef ECS_TARGET_WINDOWS |
|
/* If on Windows, test if winsock needs to be initialized */ |
|
SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
|
if (INVALID_SOCKET == testsocket && WSANOTINITIALISED == WSAGetLastError()){ |
|
WSADATA data = { 0 }; |
|
int result = WSAStartup(MAKEWORD(2, 2), &data); |
|
if (result) { |
|
ecs_warn("http: WSAStartup failed with GetLastError = %d\n", |
|
GetLastError()); |
|
return; |
|
} |
|
} else { |
|
http_close(&testsocket); |
|
} |
|
#endif |
|
|
|
/* Resolve name + port (used for logging) */ |
|
char addr_host[256]; |
|
char addr_port[20]; |
|
|
|
ecs_http_socket_t sock = HTTP_SOCKET_INVALID; |
|
ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (http_getnameinfo( |
|
addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, |
|
ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) |
|
{ |
|
ecs_os_strcpy(addr_host, "unknown"); |
|
ecs_os_strcpy(addr_port, "unknown"); |
|
} |
|
|
|
ecs_os_mutex_lock(srv->lock); |
|
if (srv->should_run) { |
|
ecs_dbg_2("http: initializing connection socket"); |
|
|
|
sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); |
|
if (!http_socket_is_valid(sock)) { |
|
ecs_err("http: unable to create new connection socket: %s", |
|
ecs_os_strerror(errno)); |
|
ecs_os_mutex_unlock(srv->lock); |
|
goto done; |
|
} |
|
|
|
int reuse = 1, result; |
|
result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, |
|
(char*)&reuse, ECS_SIZEOF(reuse)); |
|
if (result) { |
|
ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); |
|
} |
|
|
|
if (addr->sa_family == AF_INET6) { |
|
int ipv6only = 0; |
|
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, |
|
(char*)&ipv6only, ECS_SIZEOF(ipv6only))) |
|
{ |
|
ecs_warn("http: failed to setsockopt: %s", |
|
ecs_os_strerror(errno)); |
|
} |
|
} |
|
|
|
result = http_bind(sock, addr, addr_len); |
|
if (result) { |
|
ecs_err("http: failed to bind to '%s:%s': %s", |
|
addr_host, addr_port, ecs_os_strerror(errno)); |
|
ecs_os_mutex_unlock(srv->lock); |
|
goto done; |
|
} |
|
|
|
http_sock_set_timeout(sock, 1000); |
|
|
|
srv->sock = sock; |
|
|
|
result = listen(srv->sock, SOMAXCONN); |
|
if (result) { |
|
ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", |
|
SOMAXCONN, ecs_os_strerror(errno)); |
|
} |
|
|
|
ecs_trace("http: listening for incoming connections on '%s:%s'", |
|
addr_host, addr_port); |
|
} else { |
|
ecs_dbg_2("http: server shut down while initializing"); |
|
} |
|
ecs_os_mutex_unlock(srv->lock); |
|
|
|
struct sockaddr_storage remote_addr; |
|
ecs_size_t remote_addr_len = 0; |
|
|
|
while (srv->should_run) { |
|
remote_addr_len = ECS_SIZEOF(remote_addr); |
|
ecs_http_socket_t sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, |
|
&remote_addr_len); |
|
|
|
if (!http_socket_is_valid(sock_conn)) { |
|
if (srv->should_run) { |
|
ecs_dbg("http: connection attempt failed: %s", |
|
ecs_os_strerror(errno)); |
|
} |
|
continue; |
|
} |
|
|
|
http_conn_res_t conn = http_init_connection(srv, sock_conn, &remote_addr, remote_addr_len); |
|
http_recv_connection(srv, conn.conn, conn.id, sock_conn); |
|
} |
|
|
|
done: |
|
ecs_os_mutex_lock(srv->lock); |
|
if (http_socket_is_valid(sock) && errno != EBADF) { |
|
http_close(&sock); |
|
srv->sock = sock; |
|
} |
|
ecs_os_mutex_unlock(srv->lock); |
|
|
|
ecs_trace("http: no longer accepting connections on '%s:%s'", |
|
addr_host, addr_port); |
|
} |
|
|
|
static |
|
void* http_server_thread(void* arg) { |
|
ecs_http_server_t *srv = arg; |
|
struct sockaddr_in addr; |
|
ecs_os_zeromem(&addr); |
|
addr.sin_family = AF_INET; |
|
addr.sin_port = htons(srv->port); |
|
|
|
if (!srv->ipaddr) { |
|
addr.sin_addr.s_addr = htonl(INADDR_ANY); |
|
} else { |
|
inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); |
|
} |
|
|
|
http_accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); |
|
return NULL; |
|
} |
|
|
|
static |
|
void http_do_request( |
|
ecs_http_server_t *srv, |
|
ecs_http_reply_t *reply, |
|
const ecs_http_request_impl_t *req) |
|
{ |
|
ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(srv->callback != NULL, ECS_INVALID_OPERATION, |
|
"missing request handler for server"); |
|
|
|
if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, |
|
srv->ctx) == false) |
|
{ |
|
reply->code = 404; |
|
reply->status = "Resource not found"; |
|
ecs_os_linc(&ecs_http_request_not_handled_count); |
|
} else { |
|
if (reply->code >= 400) { |
|
ecs_os_linc(&ecs_http_request_handled_error_count); |
|
} else { |
|
ecs_os_linc(&ecs_http_request_handled_ok_count); |
|
} |
|
} |
|
error: |
|
return; |
|
} |
|
|
|
static |
|
void http_handle_request( |
|
ecs_http_server_t *srv, |
|
ecs_http_request_impl_t *req) |
|
{ |
|
ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; |
|
ecs_http_connection_impl_t *conn = |
|
(ecs_http_connection_impl_t*)req->pub.conn; |
|
|
|
if (req->pub.method != EcsHttpOptions) { |
|
if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { |
|
reply.code = 404; |
|
reply.status = "Resource not found"; |
|
ecs_os_linc(&ecs_http_request_not_handled_count); |
|
} else { |
|
if (reply.code >= 400) { |
|
ecs_os_linc(&ecs_http_request_handled_error_count); |
|
} else { |
|
ecs_os_linc(&ecs_http_request_handled_ok_count); |
|
} |
|
} |
|
|
|
if (req->pub.method == EcsHttpGet) { |
|
http_insert_request_entry(srv, req, &reply); |
|
} |
|
|
|
http_send_reply(conn, &reply, false); |
|
ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); |
|
} else { |
|
/* Already taken care of */ |
|
} |
|
|
|
http_reply_fini(&reply); |
|
http_request_fini(req); |
|
http_connection_free(conn); |
|
} |
|
|
|
static |
|
void http_purge_request_cache( |
|
ecs_http_server_t *srv, |
|
bool fini) |
|
{ |
|
ecs_time_t t = {0, 0}; |
|
ecs_ftime_t time = (ecs_ftime_t)ecs_time_measure(&t); |
|
ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); |
|
while (ecs_map_next(&it)) { |
|
ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); |
|
int32_t i, count = ecs_vec_count(&bucket->values); |
|
ecs_http_request_key_t *keys = ecs_vec_first(&bucket->keys); |
|
ecs_http_request_entry_t *entries = ecs_vec_first(&bucket->values); |
|
for (i = count - 1; i >= 0; i --) { |
|
ecs_http_request_entry_t *entry = &entries[i]; |
|
if (fini || ((time - entry->time) > srv->cache_purge_timeout)) { |
|
ecs_http_request_key_t *key = &keys[i]; |
|
/* Safe, code owns the value */ |
|
ecs_os_free(ECS_CONST_CAST(char*, key->array)); |
|
ecs_os_free(entry->content); |
|
flecs_hm_bucket_remove(&srv->request_cache, bucket, |
|
ecs_map_key(&it), i); |
|
} |
|
} |
|
} |
|
|
|
if (fini) { |
|
flecs_hashmap_fini(&srv->request_cache); |
|
} |
|
} |
|
|
|
static |
|
int32_t http_dequeue_requests( |
|
ecs_http_server_t *srv, |
|
double delta_time) |
|
{ |
|
ecs_os_mutex_lock(srv->lock); |
|
|
|
int32_t i, request_count = flecs_sparse_count(&srv->requests); |
|
for (i = request_count - 1; i >= 1; i --) { |
|
ecs_http_request_impl_t *req = flecs_sparse_get_dense_t( |
|
&srv->requests, ecs_http_request_impl_t, i); |
|
http_handle_request(srv, req); |
|
} |
|
|
|
int32_t connections_count = flecs_sparse_count(&srv->connections); |
|
for (i = connections_count - 1; i >= 1; i --) { |
|
ecs_http_connection_impl_t *conn = flecs_sparse_get_dense_t( |
|
&srv->connections, ecs_http_connection_impl_t, i); |
|
|
|
conn->dequeue_timeout += delta_time; |
|
conn->dequeue_retries ++; |
|
|
|
if ((conn->dequeue_timeout > |
|
(double)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && |
|
(conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) |
|
{ |
|
ecs_dbg("http: purging connection '%s:%s' (sock = %d)", |
|
conn->pub.host, conn->pub.port, conn->sock); |
|
http_connection_free(conn); |
|
} |
|
} |
|
|
|
http_purge_request_cache(srv, false); |
|
ecs_os_mutex_unlock(srv->lock); |
|
|
|
return request_count - 1; |
|
} |
|
|
|
const char* ecs_http_get_header( |
|
const ecs_http_request_t* req, |
|
const char* name) |
|
{ |
|
for (ecs_size_t i = 0; i < req->header_count; i++) { |
|
if (!ecs_os_strcmp(req->headers[i].key, name)) { |
|
return req->headers[i].value; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
const char* ecs_http_get_param( |
|
const ecs_http_request_t* req, |
|
const char* name) |
|
{ |
|
for (ecs_size_t i = 0; i < req->param_count; i++) { |
|
if (!ecs_os_strcmp(req->params[i].key, name)) { |
|
return req->params[i].value; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
ecs_http_server_t* ecs_http_server_init( |
|
const ecs_http_server_desc_t *desc) |
|
{ |
|
ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, |
|
"missing OS API implementation"); |
|
|
|
ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); |
|
srv->lock = ecs_os_mutex_new(); |
|
srv->sock = HTTP_SOCKET_INVALID; |
|
|
|
srv->should_run = false; |
|
srv->initialized = true; |
|
|
|
srv->cache_timeout = desc->cache_timeout; |
|
srv->cache_purge_timeout = desc->cache_purge_timeout; |
|
|
|
if (!ECS_EQZERO(srv->cache_timeout) && |
|
ECS_EQZERO(srv->cache_purge_timeout)) |
|
{ |
|
srv->cache_purge_timeout = srv->cache_timeout * 10; |
|
} |
|
|
|
srv->callback = desc->callback; |
|
srv->ctx = desc->ctx; |
|
srv->port = desc->port; |
|
srv->ipaddr = desc->ipaddr; |
|
srv->send_queue.wait_ms = desc->send_queue_wait_ms; |
|
if (!srv->send_queue.wait_ms) { |
|
srv->send_queue.wait_ms = 1; |
|
} |
|
|
|
flecs_sparse_init_t(&srv->connections, NULL, NULL, ecs_http_connection_impl_t); |
|
flecs_sparse_init_t(&srv->requests, NULL, NULL, ecs_http_request_impl_t); |
|
|
|
/* Start at id 1 */ |
|
flecs_sparse_new_id(&srv->connections); |
|
flecs_sparse_new_id(&srv->requests); |
|
|
|
/* Initialize request cache */ |
|
flecs_hashmap_init(&srv->request_cache, |
|
ecs_http_request_key_t, ecs_http_request_entry_t, |
|
http_request_key_hash, http_request_key_compare, NULL); |
|
|
|
#ifndef ECS_TARGET_WINDOWS |
|
/* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client |
|
* but te client already disconnected. */ |
|
signal(SIGPIPE, SIG_IGN); |
|
#endif |
|
|
|
return srv; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void ecs_http_server_fini( |
|
ecs_http_server_t* srv) |
|
{ |
|
if (srv->should_run) { |
|
ecs_http_server_stop(srv); |
|
} |
|
ecs_os_mutex_free(srv->lock); |
|
http_purge_request_cache(srv, true); |
|
flecs_sparse_fini(&srv->requests); |
|
flecs_sparse_fini(&srv->connections); |
|
ecs_os_free(srv); |
|
} |
|
|
|
int ecs_http_server_start( |
|
ecs_http_server_t *srv) |
|
{ |
|
ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); |
|
|
|
srv->should_run = true; |
|
|
|
ecs_dbg("http: starting server thread"); |
|
|
|
srv->thread = ecs_os_thread_new(http_server_thread, srv); |
|
if (!srv->thread) { |
|
goto error; |
|
} |
|
|
|
srv->send_queue.thread = ecs_os_thread_new(http_server_send_queue, srv); |
|
if (!srv->send_queue.thread) { |
|
goto error; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
void ecs_http_server_stop( |
|
ecs_http_server_t* srv) |
|
{ |
|
ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(srv->initialized, ECS_INVALID_OPERATION, NULL); |
|
ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Stop server thread */ |
|
ecs_dbg("http: shutting down server thread"); |
|
|
|
ecs_os_mutex_lock(srv->lock); |
|
srv->should_run = false; |
|
if (http_socket_is_valid(srv->sock)) { |
|
http_close(&srv->sock); |
|
} |
|
ecs_os_mutex_unlock(srv->lock); |
|
|
|
ecs_os_thread_join(srv->thread); |
|
ecs_os_thread_join(srv->send_queue.thread); |
|
ecs_trace("http: server threads shut down"); |
|
|
|
/* Cleanup all outstanding requests */ |
|
int i, count = flecs_sparse_count(&srv->requests); |
|
for (i = count - 1; i >= 1; i --) { |
|
http_request_fini(flecs_sparse_get_dense_t( |
|
&srv->requests, ecs_http_request_impl_t, i)); |
|
} |
|
|
|
/* Close all connections */ |
|
count = flecs_sparse_count(&srv->connections); |
|
for (i = count - 1; i >= 1; i --) { |
|
http_connection_free(flecs_sparse_get_dense_t( |
|
&srv->connections, ecs_http_connection_impl_t, i)); |
|
} |
|
|
|
ecs_assert(flecs_sparse_count(&srv->connections) == 1, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(flecs_sparse_count(&srv->requests) == 1, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
srv->thread = 0; |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_http_server_dequeue( |
|
ecs_http_server_t* srv, |
|
ecs_ftime_t delta_time) |
|
{ |
|
ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); |
|
|
|
srv->dequeue_timeout += (double)delta_time; |
|
srv->stats_timeout += (double)delta_time; |
|
|
|
if ((1000 * srv->dequeue_timeout) > (double)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { |
|
srv->dequeue_timeout = 0; |
|
|
|
ecs_time_t t = {0}; |
|
ecs_time_measure(&t); |
|
int32_t request_count = http_dequeue_requests(srv, srv->dequeue_timeout); |
|
srv->requests_processed += request_count; |
|
srv->requests_processed_total += request_count; |
|
double time_spent = ecs_time_measure(&t); |
|
srv->request_time += time_spent; |
|
srv->request_time_total += time_spent; |
|
srv->dequeue_count ++; |
|
} |
|
|
|
if ((1000 * srv->stats_timeout) > (double)ECS_HTTP_MIN_STATS_INTERVAL) { |
|
srv->stats_timeout = 0; |
|
ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", |
|
srv->requests_processed, srv->request_time, |
|
(srv->request_time / (double)srv->dequeue_count)); |
|
srv->requests_processed = 0; |
|
srv->request_time = 0; |
|
srv->dequeue_count = 0; |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
int ecs_http_server_http_request( |
|
ecs_http_server_t* srv, |
|
const char *req, |
|
ecs_size_t len, |
|
ecs_http_reply_t *reply_out) |
|
{ |
|
if (!len) { |
|
len = ecs_os_strlen(req); |
|
} |
|
|
|
ecs_http_fragment_t frag = {0}; |
|
if (!http_parse_request(&frag, req, len)) { |
|
ecs_strbuf_reset(&frag.buf); |
|
reply_out->code = 400; |
|
return -1; |
|
} |
|
|
|
ecs_http_request_impl_t request; |
|
char *res = http_decode_request(&request, &frag); |
|
if (!res) { |
|
reply_out->code = 400; |
|
return -1; |
|
} |
|
|
|
ecs_http_request_entry_t *entry = |
|
http_find_request_entry(srv, request.res, request.req_len); |
|
if (entry) { |
|
reply_out->body = ECS_STRBUF_INIT; |
|
reply_out->code = entry->code; |
|
reply_out->content_type = "application/json"; |
|
reply_out->headers = ECS_STRBUF_INIT; |
|
reply_out->status = "OK"; |
|
ecs_strbuf_appendstrn(&reply_out->body, |
|
entry->content, entry->content_length); |
|
} else { |
|
http_do_request(srv, reply_out, &request); |
|
|
|
if (request.pub.method == EcsHttpGet) { |
|
http_insert_request_entry(srv, &request, reply_out); |
|
} |
|
} |
|
|
|
ecs_os_free(res); |
|
|
|
http_purge_request_cache(srv, false); |
|
|
|
return (reply_out->code >= 400) ? -1 : 0; |
|
} |
|
|
|
int ecs_http_server_request( |
|
ecs_http_server_t* srv, |
|
const char *method, |
|
const char *req, |
|
ecs_http_reply_t *reply_out) |
|
{ |
|
const char *http_ver = " HTTP/1.1\r\n\r\n"; |
|
int32_t method_len = ecs_os_strlen(method); |
|
int32_t req_len = ecs_os_strlen(req); |
|
int32_t http_ver_len = ecs_os_strlen(http_ver); |
|
|
|
int32_t len = method_len + req_len + http_ver_len + 1; |
|
if (method_len + req_len + http_ver_len >= 1024) { |
|
ecs_err("HTTP request too long"); |
|
return -1; |
|
} |
|
|
|
char reqstr[1024]; |
|
char *ptr = reqstr; |
|
ecs_os_memcpy(ptr, method, method_len); ptr += method_len; |
|
ptr[0] = ' '; ptr ++; |
|
ecs_os_memcpy(ptr, req, req_len); ptr += req_len; |
|
ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; |
|
ptr[0] = '\n'; |
|
|
|
return ecs_http_server_http_request(srv, reqstr, len, reply_out); |
|
} |
|
|
|
void* ecs_http_server_ctx( |
|
ecs_http_server_t* srv) |
|
{ |
|
return srv->ctx; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/journal.c |
|
* @brief Journal addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_JOURNAL |
|
|
|
static |
|
char* flecs_journal_entitystr( |
|
ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
char *path; |
|
const char *_path = ecs_get_symbol(world, entity); |
|
if (_path && !strchr(_path, '.')) { |
|
path = ecs_asprintf("#[blue]%s", _path); |
|
} else { |
|
uint32_t gen = entity >> 32; |
|
if (gen) { |
|
path = ecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); |
|
} else { |
|
path = ecs_asprintf("#[normal]_%u", (uint32_t)entity); |
|
} |
|
} |
|
return path; |
|
} |
|
|
|
static |
|
char* flecs_journal_idstr( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
if (ECS_IS_PAIR(id)) { |
|
char *first_path = flecs_journal_entitystr(world, |
|
ecs_pair_first(world, id)); |
|
char *second_path = flecs_journal_entitystr(world, |
|
ecs_pair_second(world, id)); |
|
char *result = ecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", |
|
first_path, second_path); |
|
ecs_os_free(first_path); |
|
ecs_os_free(second_path); |
|
return result; |
|
} else if (!(id & ECS_ID_FLAGS_MASK)) { |
|
return flecs_journal_entitystr(world, id); |
|
} else { |
|
return ecs_id_str(world, id); |
|
} |
|
} |
|
|
|
static int flecs_journal_sp = 0; |
|
|
|
void flecs_journal_begin( |
|
ecs_world_t *world, |
|
ecs_journal_kind_t kind, |
|
ecs_entity_t entity, |
|
ecs_type_t *add, |
|
ecs_type_t *remove) |
|
{ |
|
flecs_journal_sp ++; |
|
|
|
if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { |
|
return; |
|
} |
|
|
|
char *path = NULL; |
|
char *var_id = NULL; |
|
if (entity) { |
|
if (kind != EcsJournalDeleteWith && kind != EcsJournalRemoveAll) { |
|
path = ecs_get_fullpath(world, entity); |
|
var_id = flecs_journal_entitystr(world, entity); |
|
} else { |
|
path = ecs_id_str(world, entity); |
|
var_id = flecs_journal_idstr(world, entity); |
|
} |
|
} |
|
|
|
if (kind == EcsJournalNew) { |
|
ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id); |
|
ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); |
|
ecs_print(4, "#[green]ecs_entity_t %s;", var_id); |
|
ecs_print(4, "#[magenta]#endif"); |
|
ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " |
|
"#[grey] // %s = new()", var_id, path); |
|
} |
|
if (add) { |
|
for (int i = 0; i < add->count; i ++) { |
|
char *jidstr = flecs_journal_idstr(world, add->array[i]); |
|
char *idstr = ecs_id_str(world, add->array[i]); |
|
ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); " |
|
"#[grey] // add(%s, %s)", var_id, jidstr, |
|
path, idstr); |
|
ecs_os_free(idstr); |
|
ecs_os_free(jidstr); |
|
} |
|
} |
|
if (remove) { |
|
for (int i = 0; i < remove->count; i ++) { |
|
char *jidstr = flecs_journal_idstr(world, remove->array[i]); |
|
char *idstr = ecs_id_str(world, remove->array[i]); |
|
ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " |
|
"#[grey] // remove(%s, %s)", var_id, jidstr, |
|
path, idstr); |
|
ecs_os_free(idstr); |
|
ecs_os_free(jidstr); |
|
} |
|
} |
|
if (kind == EcsJournalClear) { |
|
ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " |
|
"#[grey] // clear(%s)", var_id, path); |
|
} else if (kind == EcsJournalDelete) { |
|
ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " |
|
"#[grey] // delete(%s)", var_id, path); |
|
} else if (kind == EcsJournalDeleteWith) { |
|
ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " |
|
"#[grey] // delete_with(%s)", var_id, path); |
|
} else if (kind == EcsJournalRemoveAll) { |
|
ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " |
|
"#[grey] // remove_all(%s)", var_id, path); |
|
} else if (kind == EcsJournalTableEvents) { |
|
ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " |
|
"EcsAperiodicEmptyTables);"); |
|
} |
|
ecs_os_free(var_id); |
|
ecs_os_free(path); |
|
ecs_log_push(); |
|
} |
|
|
|
void flecs_journal_end(void) { |
|
flecs_journal_sp --; |
|
ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_log_pop(); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/log.c |
|
* @brief Log addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_LOG |
|
|
|
#include <ctype.h> |
|
|
|
void flecs_colorize_buf( |
|
char *msg, |
|
bool enable_colors, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
char *ptr, ch, prev = '\0'; |
|
bool isNum = false; |
|
char isStr = '\0'; |
|
bool isVar = false; |
|
bool overrideColor = false; |
|
bool autoColor = true; |
|
bool dontAppend = false; |
|
|
|
for (ptr = msg; (ch = *ptr); ptr++) { |
|
dontAppend = false; |
|
|
|
if (!overrideColor) { |
|
if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); |
|
isNum = false; |
|
} |
|
if (isStr && (isStr == ch) && prev != '\\') { |
|
isStr = '\0'; |
|
} else if (((ch == '\'') || (ch == '"')) && !isStr && |
|
!isalpha(prev) && (prev != '\\')) |
|
{ |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); |
|
isStr = ch; |
|
} |
|
|
|
if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || |
|
(ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && |
|
!isalpha(prev) && !isdigit(prev) && (prev != '_') && |
|
(prev != '.')) |
|
{ |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); |
|
isNum = true; |
|
} |
|
|
|
if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); |
|
isVar = false; |
|
} |
|
|
|
if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); |
|
isVar = true; |
|
} |
|
} |
|
|
|
if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { |
|
bool isColor = true; |
|
overrideColor = true; |
|
|
|
/* Custom colors */ |
|
if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { |
|
autoColor = false; |
|
} else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); |
|
} else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED); |
|
} else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE); |
|
} else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA); |
|
} else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); |
|
} else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW); |
|
} else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY); |
|
} else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); |
|
} else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD); |
|
} else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); |
|
} else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { |
|
overrideColor = false; |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); |
|
} else { |
|
isColor = false; |
|
overrideColor = false; |
|
} |
|
|
|
if (isColor) { |
|
ptr += 2; |
|
while ((ch = *ptr) != ']') ptr ++; |
|
dontAppend = true; |
|
} |
|
if (!autoColor) { |
|
overrideColor = true; |
|
} |
|
} |
|
|
|
if (ch == '\n') { |
|
if (isNum || isStr || isVar || overrideColor) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); |
|
overrideColor = false; |
|
isNum = false; |
|
isStr = false; |
|
isVar = false; |
|
} |
|
} |
|
|
|
if (!dontAppend) { |
|
ecs_strbuf_appendstrn(buf, ptr, 1); |
|
} |
|
|
|
if (!overrideColor) { |
|
if (((ch == '\'') || (ch == '"')) && !isStr) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); |
|
} |
|
} |
|
|
|
prev = ch; |
|
} |
|
|
|
if (isNum || isStr || isVar || overrideColor) { |
|
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); |
|
} |
|
} |
|
|
|
void ecs_printv_( |
|
int level, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
va_list args) |
|
{ |
|
(void)level; |
|
(void)line; |
|
|
|
ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; |
|
|
|
/* Apply color. Even if we don't want color, we still need to call the |
|
* colorize function to get rid of the color tags (e.g. #[green]) */ |
|
char *msg_nocolor = ecs_vasprintf(fmt, args); |
|
flecs_colorize_buf(msg_nocolor, |
|
ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf); |
|
ecs_os_free(msg_nocolor); |
|
|
|
char *msg = ecs_strbuf_get(&msg_buf); |
|
|
|
if (msg) { |
|
ecs_os_api.log_(level, file, line, msg); |
|
ecs_os_free(msg); |
|
} else { |
|
ecs_os_api.log_(level, file, line, ""); |
|
} |
|
} |
|
|
|
void ecs_print_( |
|
int level, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
...) |
|
{ |
|
va_list args; |
|
va_start(args, fmt); |
|
ecs_printv_(level, file, line, fmt, args); |
|
va_end(args); |
|
} |
|
|
|
void ecs_logv_( |
|
int level, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
va_list args) |
|
{ |
|
if (level > ecs_os_api.log_level_) { |
|
return; |
|
} |
|
|
|
ecs_printv_(level, file, line, fmt, args); |
|
} |
|
|
|
void ecs_log_( |
|
int level, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
...) |
|
{ |
|
if (level > ecs_os_api.log_level_) { |
|
return; |
|
} |
|
|
|
va_list args; |
|
va_start(args, fmt); |
|
ecs_printv_(level, file, line, fmt, args); |
|
va_end(args); |
|
} |
|
|
|
|
|
void ecs_log_push_( |
|
int32_t level) |
|
{ |
|
if (level <= ecs_os_api.log_level_) { |
|
ecs_os_api.log_indent_ ++; |
|
} |
|
} |
|
|
|
void ecs_log_pop_( |
|
int32_t level) |
|
{ |
|
if (level <= ecs_os_api.log_level_) { |
|
ecs_os_api.log_indent_ --; |
|
ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
void ecs_parser_errorv_( |
|
const char *name, |
|
const char *expr, |
|
int64_t column_arg, |
|
const char *fmt, |
|
va_list args) |
|
{ |
|
if (column_arg > 65536) { |
|
/* Limit column size, which prevents the code from throwing up when the |
|
* function is called with (expr - ptr), and expr is NULL. */ |
|
column_arg = 0; |
|
} |
|
int32_t column = flecs_itoi32(column_arg); |
|
|
|
if (ecs_os_api.log_level_ >= -2) { |
|
ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; |
|
|
|
ecs_strbuf_vappend(&msg_buf, fmt, args); |
|
|
|
if (expr) { |
|
ecs_strbuf_appendch(&msg_buf, '\n'); |
|
|
|
/* Find start of line by taking column and looking for the |
|
* last occurring newline */ |
|
if (column != -1) { |
|
const char *ptr = &expr[column]; |
|
while (ptr[0] != '\n' && ptr > expr) { |
|
ptr --; |
|
} |
|
|
|
if (ptr == expr) { |
|
/* ptr is already at start of line */ |
|
} else { |
|
column -= (int32_t)(ptr - expr + 1); |
|
expr = ptr + 1; |
|
} |
|
} |
|
|
|
/* Strip newlines from current statement, if any */ |
|
char *newline_ptr = strchr(expr, '\n'); |
|
if (newline_ptr) { |
|
/* Strip newline from expr */ |
|
ecs_strbuf_appendstrn(&msg_buf, expr, |
|
(int32_t)(newline_ptr - expr)); |
|
} else { |
|
ecs_strbuf_appendstr(&msg_buf, expr); |
|
} |
|
|
|
ecs_strbuf_appendch(&msg_buf, '\n'); |
|
|
|
if (column != -1) { |
|
int32_t c; |
|
for (c = 0; c < column; c ++) { |
|
ecs_strbuf_appendch(&msg_buf, ' '); |
|
} |
|
ecs_strbuf_appendch(&msg_buf, '^'); |
|
} |
|
} |
|
|
|
char *msg = ecs_strbuf_get(&msg_buf); |
|
ecs_os_err(name, 0, msg); |
|
ecs_os_free(msg); |
|
} |
|
} |
|
|
|
void ecs_parser_error_( |
|
const char *name, |
|
const char *expr, |
|
int64_t column, |
|
const char *fmt, |
|
...) |
|
{ |
|
if (ecs_os_api.log_level_ >= -2) { |
|
va_list args; |
|
va_start(args, fmt); |
|
ecs_parser_errorv_(name, expr, column, fmt, args); |
|
va_end(args); |
|
} |
|
} |
|
|
|
void ecs_abort_( |
|
int32_t err, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
...) |
|
{ |
|
if (fmt) { |
|
va_list args; |
|
va_start(args, fmt); |
|
char *msg = ecs_vasprintf(fmt, args); |
|
va_end(args); |
|
ecs_fatal_(file, line, "%s (%s)", msg, ecs_strerror(err)); |
|
ecs_os_free(msg); |
|
} else { |
|
ecs_fatal_(file, line, "%s", ecs_strerror(err)); |
|
} |
|
ecs_os_api.log_last_error_ = err; |
|
} |
|
|
|
void ecs_assert_log_( |
|
int32_t err, |
|
const char *cond_str, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
...) |
|
{ |
|
if (fmt) { |
|
va_list args; |
|
va_start(args, fmt); |
|
char *msg = ecs_vasprintf(fmt, args); |
|
va_end(args); |
|
ecs_fatal_(file, line, "assert: %s %s (%s)", |
|
cond_str, msg, ecs_strerror(err)); |
|
ecs_os_free(msg); |
|
} else { |
|
ecs_fatal_(file, line, "assert: %s %s", |
|
cond_str, ecs_strerror(err)); |
|
} |
|
ecs_os_api.log_last_error_ = err; |
|
} |
|
|
|
void ecs_deprecated_( |
|
const char *file, |
|
int32_t line, |
|
const char *msg) |
|
{ |
|
ecs_err_(file, line, "%s", msg); |
|
} |
|
|
|
bool ecs_should_log(int32_t level) { |
|
# if !defined(FLECS_LOG_3) |
|
if (level == 3) { |
|
return false; |
|
} |
|
# endif |
|
# if !defined(FLECS_LOG_2) |
|
if (level == 2) { |
|
return false; |
|
} |
|
# endif |
|
# if !defined(FLECS_LOG_1) |
|
if (level == 1) { |
|
return false; |
|
} |
|
# endif |
|
|
|
return level <= ecs_os_api.log_level_; |
|
} |
|
|
|
#define ECS_ERR_STR(code) case code: return &(#code[4]) |
|
|
|
const char* ecs_strerror( |
|
int32_t error_code) |
|
{ |
|
switch (error_code) { |
|
ECS_ERR_STR(ECS_INVALID_PARAMETER); |
|
ECS_ERR_STR(ECS_NOT_A_COMPONENT); |
|
ECS_ERR_STR(ECS_INTERNAL_ERROR); |
|
ECS_ERR_STR(ECS_ALREADY_DEFINED); |
|
ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); |
|
ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); |
|
ECS_ERR_STR(ECS_NAME_IN_USE); |
|
ECS_ERR_STR(ECS_OUT_OF_MEMORY); |
|
ECS_ERR_STR(ECS_DOUBLE_FREE); |
|
ECS_ERR_STR(ECS_OPERATION_FAILED); |
|
ECS_ERR_STR(ECS_INVALID_CONVERSION); |
|
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_LEAK_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); |
|
ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); |
|
ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); |
|
ECS_ERR_STR(ECS_INVALID_FROM_WORKER); |
|
ECS_ERR_STR(ECS_OUT_OF_RANGE); |
|
ECS_ERR_STR(ECS_MISSING_OS_API); |
|
ECS_ERR_STR(ECS_UNSUPPORTED); |
|
ECS_ERR_STR(ECS_ACCESS_VIOLATION); |
|
ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); |
|
ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); |
|
ECS_ERR_STR(ECS_INCONSISTENT_NAME); |
|
ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); |
|
ECS_ERR_STR(ECS_INVALID_OPERATION); |
|
ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); |
|
ECS_ERR_STR(ECS_LOCKED_STORAGE); |
|
ECS_ERR_STR(ECS_ID_IN_USE); |
|
} |
|
|
|
return "unknown error code"; |
|
} |
|
|
|
#else |
|
|
|
/* Empty bodies for when logging is disabled */ |
|
|
|
void ecs_log_( |
|
int32_t level, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
...) |
|
{ |
|
(void)level; |
|
(void)file; |
|
(void)line; |
|
(void)fmt; |
|
} |
|
|
|
void ecs_parser_error_( |
|
const char *name, |
|
const char *expr, |
|
int64_t column, |
|
const char *fmt, |
|
...) |
|
{ |
|
(void)name; |
|
(void)expr; |
|
(void)column; |
|
(void)fmt; |
|
} |
|
|
|
void ecs_parser_errorv_( |
|
const char *name, |
|
const char *expr, |
|
int64_t column, |
|
const char *fmt, |
|
va_list args) |
|
{ |
|
(void)name; |
|
(void)expr; |
|
(void)column; |
|
(void)fmt; |
|
(void)args; |
|
} |
|
|
|
void ecs_abort_( |
|
int32_t error_code, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
...) |
|
{ |
|
(void)error_code; |
|
(void)file; |
|
(void)line; |
|
(void)fmt; |
|
} |
|
|
|
void ecs_assert_log_( |
|
int32_t error_code, |
|
const char *condition_str, |
|
const char *file, |
|
int32_t line, |
|
const char *fmt, |
|
...) |
|
{ |
|
(void)error_code; |
|
(void)condition_str; |
|
(void)file; |
|
(void)line; |
|
(void)fmt; |
|
} |
|
|
|
#endif |
|
|
|
int ecs_log_get_level(void) { |
|
return ecs_os_api.log_level_; |
|
} |
|
|
|
int ecs_log_set_level( |
|
int level) |
|
{ |
|
int prev = level; |
|
ecs_os_api.log_level_ = level; |
|
return prev; |
|
} |
|
|
|
bool ecs_log_enable_colors( |
|
bool enabled) |
|
{ |
|
bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; |
|
ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); |
|
return prev; |
|
} |
|
|
|
bool ecs_log_enable_timestamp( |
|
bool enabled) |
|
{ |
|
bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; |
|
ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); |
|
return prev; |
|
} |
|
|
|
bool ecs_log_enable_timedelta( |
|
bool enabled) |
|
{ |
|
bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; |
|
ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); |
|
return prev; |
|
} |
|
|
|
int ecs_log_last_error(void) |
|
{ |
|
int result = ecs_os_api.log_last_error_; |
|
ecs_os_api.log_last_error_ = 0; |
|
return result; |
|
} |
|
|
|
/** |
|
* @file addons/meta_c.c |
|
* @brief C utilities for meta addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_META_C |
|
|
|
#include <ctype.h> |
|
|
|
#define ECS_META_IDENTIFIER_LENGTH (256) |
|
|
|
#define ecs_meta_error(ctx, ptr, ...)\ |
|
ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); |
|
|
|
typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; |
|
|
|
typedef struct meta_parse_ctx_t { |
|
const char *name; |
|
const char *desc; |
|
} meta_parse_ctx_t; |
|
|
|
typedef struct meta_type_t { |
|
ecs_meta_token_t type; |
|
ecs_meta_token_t params; |
|
bool is_const; |
|
bool is_ptr; |
|
} meta_type_t; |
|
|
|
typedef struct meta_member_t { |
|
meta_type_t type; |
|
ecs_meta_token_t name; |
|
int64_t count; |
|
bool is_partial; |
|
} meta_member_t; |
|
|
|
typedef struct meta_constant_t { |
|
ecs_meta_token_t name; |
|
int64_t value; |
|
bool is_value_set; |
|
} meta_constant_t; |
|
|
|
typedef struct meta_params_t { |
|
meta_type_t key_type; |
|
meta_type_t type; |
|
int64_t count; |
|
bool is_key_value; |
|
bool is_fixed_size; |
|
} meta_params_t; |
|
|
|
static |
|
const char* skip_scope(const char *ptr, meta_parse_ctx_t *ctx) { |
|
/* Keep track of which characters were used to open the scope */ |
|
char stack[256]; |
|
int32_t sp = 0; |
|
char ch; |
|
|
|
while ((ch = *ptr)) { |
|
if (ch == '(' || ch == '<') { |
|
stack[sp] = ch; |
|
|
|
sp ++; |
|
if (sp >= 256) { |
|
ecs_meta_error(ctx, ptr, "maximum level of nesting reached"); |
|
goto error; |
|
} |
|
} else if (ch == ')' || ch == '>') { |
|
sp --; |
|
if ((sp < 0) || (ch == '>' && stack[sp] != '<') || |
|
(ch == ')' && stack[sp] != '(')) |
|
{ |
|
ecs_meta_error(ctx, ptr, "mismatching %c in identifier", ch); |
|
goto error; |
|
} |
|
} |
|
|
|
ptr ++; |
|
|
|
if (!sp) { |
|
break; |
|
} |
|
} |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* parse_c_digit( |
|
const char *ptr, |
|
int64_t *value_out) |
|
{ |
|
char token[24]; |
|
ptr = ecs_parse_ws_eol(ptr); |
|
ptr = ecs_parse_digit(ptr, token); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
*value_out = strtol(token, NULL, 0); |
|
|
|
return ecs_parse_ws_eol(ptr); |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* parse_c_identifier( |
|
const char *ptr, |
|
char *buff, |
|
char *params, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
char *bptr = buff, ch; |
|
|
|
if (params) { |
|
params[0] = '\0'; |
|
} |
|
|
|
/* Ignore whitespaces */ |
|
ptr = ecs_parse_ws_eol(ptr); |
|
ch = *ptr; |
|
|
|
if (!isalpha(ch) && (ch != '_')) { |
|
ecs_meta_error(ctx, ptr, "invalid identifier (starts with '%c')", ch); |
|
goto error; |
|
} |
|
|
|
while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && |
|
ch != '>' && ch != '}' && ch != '*') |
|
{ |
|
/* Type definitions can contain macros or templates */ |
|
if (ch == '(' || ch == '<') { |
|
if (!params) { |
|
ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); |
|
goto error; |
|
} |
|
|
|
const char *end = skip_scope(ptr, ctx); |
|
ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); |
|
params[end - ptr] = '\0'; |
|
|
|
ptr = end; |
|
} else { |
|
*bptr = ch; |
|
bptr ++; |
|
ptr ++; |
|
} |
|
} |
|
|
|
*bptr = '\0'; |
|
|
|
if (!ch) { |
|
ecs_meta_error(ctx, ptr, "unexpected end of token"); |
|
goto error; |
|
} |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char * meta_open_scope( |
|
const char *ptr, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
/* Skip initial whitespaces */ |
|
ptr = ecs_parse_ws_eol(ptr); |
|
|
|
/* Is this the start of the type definition? */ |
|
if (ctx->desc == ptr) { |
|
if (*ptr != '{') { |
|
ecs_meta_error(ctx, ptr, "missing '{' in struct definition"); |
|
goto error; |
|
} |
|
|
|
ptr ++; |
|
ptr = ecs_parse_ws_eol(ptr); |
|
} |
|
|
|
/* Is this the end of the type definition? */ |
|
if (!*ptr) { |
|
ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); |
|
goto error; |
|
} |
|
|
|
/* Is this the end of the type definition? */ |
|
if (*ptr == '}') { |
|
ptr = ecs_parse_ws_eol(ptr + 1); |
|
if (*ptr) { |
|
ecs_meta_error(ctx, ptr, |
|
"stray characters after struct definition"); |
|
goto error; |
|
} |
|
return NULL; |
|
} |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* meta_parse_constant( |
|
const char *ptr, |
|
meta_constant_t *token, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
ptr = meta_open_scope(ptr, ctx); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
token->is_value_set = false; |
|
|
|
/* Parse token, constant identifier */ |
|
ptr = parse_c_identifier(ptr, token->name, NULL, ctx); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
/* Explicit value assignment */ |
|
if (*ptr == '=') { |
|
int64_t value = 0; |
|
ptr = parse_c_digit(ptr + 1, &value); |
|
token->value = value; |
|
token->is_value_set = true; |
|
} |
|
|
|
/* Expect a ',' or '}' */ |
|
if (*ptr != ',' && *ptr != '}') { |
|
ecs_meta_error(ctx, ptr, "missing , after enum constant"); |
|
goto error; |
|
} |
|
|
|
if (*ptr == ',') { |
|
return ptr + 1; |
|
} else { |
|
return ptr; |
|
} |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* meta_parse_type( |
|
const char *ptr, |
|
meta_type_t *token, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
token->is_ptr = false; |
|
token->is_const = false; |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
|
|
/* Parse token, expect type identifier or ECS_PROPERTY */ |
|
ptr = parse_c_identifier(ptr, token->type, token->params, ctx); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
if (!strcmp(token->type, "ECS_PRIVATE")) { |
|
/* Members from this point are not stored in metadata */ |
|
ptr += ecs_os_strlen(ptr); |
|
goto done; |
|
} |
|
|
|
/* If token is const, set const flag and continue parsing type */ |
|
if (!strcmp(token->type, "const")) { |
|
token->is_const = true; |
|
|
|
/* Parse type after const */ |
|
ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); |
|
} |
|
|
|
/* Check if type is a pointer */ |
|
ptr = ecs_parse_ws_eol(ptr); |
|
if (*ptr == '*') { |
|
token->is_ptr = true; |
|
ptr ++; |
|
} |
|
|
|
done: |
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* meta_parse_member( |
|
const char *ptr, |
|
meta_member_t *token, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
ptr = meta_open_scope(ptr, ctx); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
token->count = 1; |
|
token->is_partial = false; |
|
|
|
/* Parse member type */ |
|
ptr = meta_parse_type(ptr, &token->type, ctx); |
|
if (!ptr) { |
|
token->is_partial = true; |
|
goto error; |
|
} |
|
|
|
if (!ptr[0]) { |
|
return ptr; |
|
} |
|
|
|
/* Next token is the identifier */ |
|
ptr = parse_c_identifier(ptr, token->name, NULL, ctx); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
/* Skip whitespace between member and [ or ; */ |
|
ptr = ecs_parse_ws_eol(ptr); |
|
|
|
/* Check if this is an array */ |
|
char *array_start = strchr(token->name, '['); |
|
if (!array_start) { |
|
/* If the [ was separated by a space, it will not be parsed as part of |
|
* the name */ |
|
if (*ptr == '[') { |
|
/* safe, will not be modified */ |
|
array_start = ECS_CONST_CAST(char*, ptr); |
|
} |
|
} |
|
|
|
if (array_start) { |
|
/* Check if the [ matches with a ] */ |
|
char *array_end = strchr(array_start, ']'); |
|
if (!array_end) { |
|
ecs_meta_error(ctx, ptr, "missing ']'"); |
|
goto error; |
|
|
|
} else if (array_end - array_start == 0) { |
|
ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); |
|
goto error; |
|
} |
|
|
|
token->count = atoi(array_start + 1); |
|
|
|
if (array_start == ptr) { |
|
/* If [ was found after name, continue parsing after ] */ |
|
ptr = array_end + 1; |
|
} else { |
|
/* If [ was fonud in name, replace it with 0 terminator */ |
|
array_start[0] = '\0'; |
|
} |
|
} |
|
|
|
/* Expect a ; */ |
|
if (*ptr != ';') { |
|
ecs_meta_error(ctx, ptr, "missing ; after member declaration"); |
|
goto error; |
|
} |
|
|
|
return ptr + 1; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
int meta_parse_desc( |
|
const char *ptr, |
|
meta_params_t *token, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
token->is_key_value = false; |
|
token->is_fixed_size = false; |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
if (*ptr != '(' && *ptr != '<') { |
|
ecs_meta_error(ctx, ptr, |
|
"expected '(' at start of collection definition"); |
|
goto error; |
|
} |
|
|
|
ptr ++; |
|
|
|
/* Parse type identifier */ |
|
ptr = meta_parse_type(ptr, &token->type, ctx); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
|
|
/* If next token is a ',' the first type was a key type */ |
|
if (*ptr == ',') { |
|
ptr = ecs_parse_ws_eol(ptr + 1); |
|
|
|
if (isdigit(*ptr)) { |
|
int64_t value; |
|
ptr = parse_c_digit(ptr, &value); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
token->count = value; |
|
token->is_fixed_size = true; |
|
} else { |
|
token->key_type = token->type; |
|
|
|
/* Parse element type */ |
|
ptr = meta_parse_type(ptr, &token->type, ctx); |
|
ptr = ecs_parse_ws_eol(ptr); |
|
|
|
token->is_key_value = true; |
|
} |
|
} |
|
|
|
if (*ptr != ')' && *ptr != '>') { |
|
ecs_meta_error(ctx, ptr, |
|
"expected ')' at end of collection definition"); |
|
goto error; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
ecs_entity_t meta_lookup( |
|
ecs_world_t *world, |
|
meta_type_t *token, |
|
const char *ptr, |
|
int64_t count, |
|
meta_parse_ctx_t *ctx); |
|
|
|
static |
|
ecs_entity_t meta_lookup_array( |
|
ecs_world_t *world, |
|
ecs_entity_t e, |
|
const char *params_decl, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
meta_parse_ctx_t param_ctx = { |
|
.name = ctx->name, |
|
.desc = params_decl |
|
}; |
|
|
|
meta_params_t params; |
|
if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { |
|
goto error; |
|
} |
|
if (!params.is_fixed_size) { |
|
ecs_meta_error(ctx, params_decl, "missing size for array"); |
|
goto error; |
|
} |
|
|
|
if (!params.count) { |
|
ecs_meta_error(ctx, params_decl, "invalid array size"); |
|
goto error; |
|
} |
|
|
|
ecs_entity_t element_type = ecs_lookup_symbol( |
|
world, params.type.type, true, true); |
|
if (!element_type) { |
|
ecs_meta_error(ctx, params_decl, "unknown element type '%s'", |
|
params.type.type); |
|
} |
|
|
|
if (!e) { |
|
e = ecs_new_id(world); |
|
} |
|
|
|
ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); |
|
|
|
return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); |
|
error: |
|
return 0; |
|
} |
|
|
|
static |
|
ecs_entity_t meta_lookup_vector( |
|
ecs_world_t *world, |
|
ecs_entity_t e, |
|
const char *params_decl, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
meta_parse_ctx_t param_ctx = { |
|
.name = ctx->name, |
|
.desc = params_decl |
|
}; |
|
|
|
meta_params_t params; |
|
if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { |
|
goto error; |
|
} |
|
|
|
if (params.is_key_value) { |
|
ecs_meta_error(ctx, params_decl, |
|
"unexpected key value parameters for vector"); |
|
goto error; |
|
} |
|
|
|
ecs_entity_t element_type = meta_lookup( |
|
world, ¶ms.type, params_decl, 1, ¶m_ctx); |
|
|
|
if (!e) { |
|
e = ecs_new_id(world); |
|
} |
|
|
|
return ecs_set(world, e, EcsVector, { element_type }); |
|
error: |
|
return 0; |
|
} |
|
|
|
static |
|
ecs_entity_t meta_lookup_bitmask( |
|
ecs_world_t *world, |
|
ecs_entity_t e, |
|
const char *params_decl, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
(void)e; |
|
|
|
meta_parse_ctx_t param_ctx = { |
|
.name = ctx->name, |
|
.desc = params_decl |
|
}; |
|
|
|
meta_params_t params; |
|
if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { |
|
goto error; |
|
} |
|
|
|
if (params.is_key_value) { |
|
ecs_meta_error(ctx, params_decl, |
|
"unexpected key value parameters for bitmask"); |
|
goto error; |
|
} |
|
|
|
if (params.is_fixed_size) { |
|
ecs_meta_error(ctx, params_decl, |
|
"unexpected size for bitmask"); |
|
goto error; |
|
} |
|
|
|
ecs_entity_t bitmask_type = meta_lookup( |
|
world, ¶ms.type, params_decl, 1, ¶m_ctx); |
|
ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
#ifndef FLECS_NDEBUG |
|
/* Make sure this is a bitmask type */ |
|
const EcsMetaType *type_ptr = ecs_get(world, bitmask_type, EcsMetaType); |
|
ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); |
|
#endif |
|
|
|
return bitmask_type; |
|
error: |
|
return 0; |
|
} |
|
|
|
static |
|
ecs_entity_t meta_lookup( |
|
ecs_world_t *world, |
|
meta_type_t *token, |
|
const char *ptr, |
|
int64_t count, |
|
meta_parse_ctx_t *ctx) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
const char *typename = token->type; |
|
ecs_entity_t type = 0; |
|
|
|
/* Parse vector type */ |
|
if (!token->is_ptr) { |
|
if (!ecs_os_strcmp(typename, "ecs_array")) { |
|
type = meta_lookup_array(world, 0, token->params, ctx); |
|
|
|
} else if (!ecs_os_strcmp(typename, "ecs_vector") || |
|
!ecs_os_strcmp(typename, "flecs::vector")) |
|
{ |
|
type = meta_lookup_vector(world, 0, token->params, ctx); |
|
|
|
} else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { |
|
type = meta_lookup_bitmask(world, 0, token->params, ctx); |
|
|
|
} else if (!ecs_os_strcmp(typename, "flecs::byte")) { |
|
type = ecs_id(ecs_byte_t); |
|
|
|
} else if (!ecs_os_strcmp(typename, "char")) { |
|
type = ecs_id(ecs_char_t); |
|
|
|
} else if (!ecs_os_strcmp(typename, "bool") || |
|
!ecs_os_strcmp(typename, "_Bool")) |
|
{ |
|
type = ecs_id(ecs_bool_t); |
|
|
|
} else if (!ecs_os_strcmp(typename, "int8_t")) { |
|
type = ecs_id(ecs_i8_t); |
|
} else if (!ecs_os_strcmp(typename, "int16_t")) { |
|
type = ecs_id(ecs_i16_t); |
|
} else if (!ecs_os_strcmp(typename, "int32_t")) { |
|
type = ecs_id(ecs_i32_t); |
|
} else if (!ecs_os_strcmp(typename, "int64_t")) { |
|
type = ecs_id(ecs_i64_t); |
|
|
|
} else if (!ecs_os_strcmp(typename, "uint8_t")) { |
|
type = ecs_id(ecs_u8_t); |
|
} else if (!ecs_os_strcmp(typename, "uint16_t")) { |
|
type = ecs_id(ecs_u16_t); |
|
} else if (!ecs_os_strcmp(typename, "uint32_t")) { |
|
type = ecs_id(ecs_u32_t); |
|
} else if (!ecs_os_strcmp(typename, "uint64_t")) { |
|
type = ecs_id(ecs_u64_t); |
|
|
|
} else if (!ecs_os_strcmp(typename, "float")) { |
|
type = ecs_id(ecs_f32_t); |
|
} else if (!ecs_os_strcmp(typename, "double")) { |
|
type = ecs_id(ecs_f64_t); |
|
|
|
} else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { |
|
type = ecs_id(ecs_entity_t); |
|
|
|
} else if (!ecs_os_strcmp(typename, "ecs_id_t")) { |
|
type = ecs_id(ecs_id_t); |
|
|
|
} else if (!ecs_os_strcmp(typename, "char*")) { |
|
type = ecs_id(ecs_string_t); |
|
} else { |
|
type = ecs_lookup_symbol(world, typename, true, true); |
|
} |
|
} else { |
|
if (!ecs_os_strcmp(typename, "char")) { |
|
typename = "flecs.meta.string"; |
|
} else |
|
if (token->is_ptr) { |
|
typename = "flecs.meta.uptr"; |
|
} else |
|
if (!ecs_os_strcmp(typename, "char*") || |
|
!ecs_os_strcmp(typename, "flecs::string")) |
|
{ |
|
typename = "flecs.meta.string"; |
|
} |
|
|
|
type = ecs_lookup_symbol(world, typename, true, true); |
|
} |
|
|
|
if (count != 1) { |
|
ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); |
|
|
|
type = ecs_set(world, 0, EcsArray, {type, (int32_t)count}); |
|
} |
|
|
|
if (!type) { |
|
ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); |
|
goto error; |
|
} |
|
|
|
return type; |
|
error: |
|
return 0; |
|
} |
|
|
|
static |
|
int meta_parse_struct( |
|
ecs_world_t *world, |
|
ecs_entity_t t, |
|
const char *desc) |
|
{ |
|
const char *ptr = desc; |
|
const char *name = ecs_get_name(world, t); |
|
|
|
meta_member_t token; |
|
meta_parse_ctx_t ctx = { |
|
.name = name, |
|
.desc = ptr |
|
}; |
|
|
|
ecs_entity_t old_scope = ecs_set_scope(world, t); |
|
|
|
while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { |
|
ecs_entity_t m = ecs_entity(world, { |
|
.name = token.name |
|
}); |
|
|
|
ecs_entity_t type = meta_lookup( |
|
world, &token.type, ptr, 1, &ctx); |
|
if (!type) { |
|
goto error; |
|
} |
|
|
|
ecs_set(world, m, EcsMember, { |
|
.type = type, |
|
.count = (ecs_size_t)token.count |
|
}); |
|
} |
|
|
|
ecs_set_scope(world, old_scope); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int meta_parse_constants( |
|
ecs_world_t *world, |
|
ecs_entity_t t, |
|
const char *desc, |
|
bool is_bitmask) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
const char *ptr = desc; |
|
const char *name = ecs_get_name(world, t); |
|
int32_t name_len = ecs_os_strlen(name); |
|
const ecs_world_info_t *info = ecs_get_world_info(world); |
|
const char *name_prefix = info->name_prefix; |
|
int32_t name_prefix_len = name_prefix ? ecs_os_strlen(name_prefix) : 0; |
|
|
|
meta_parse_ctx_t ctx = { |
|
.name = name, |
|
.desc = ptr |
|
}; |
|
|
|
meta_constant_t token; |
|
int64_t last_value = 0; |
|
|
|
ecs_entity_t old_scope = ecs_set_scope(world, t); |
|
|
|
while ((ptr = meta_parse_constant(ptr, &token, &ctx))) { |
|
if (token.is_value_set) { |
|
last_value = token.value; |
|
} else if (is_bitmask) { |
|
ecs_meta_error(&ctx, ptr, |
|
"bitmask requires explicit value assignment"); |
|
goto error; |
|
} |
|
|
|
if (name_prefix) { |
|
if (!ecs_os_strncmp(token.name, name_prefix, name_prefix_len)) { |
|
ecs_os_memmove(token.name, token.name + name_prefix_len, |
|
ecs_os_strlen(token.name) - name_prefix_len + 1); |
|
} |
|
} |
|
|
|
if (!ecs_os_strncmp(token.name, name, name_len)) { |
|
ecs_os_memmove(token.name, token.name + name_len, |
|
ecs_os_strlen(token.name) - name_len + 1); |
|
} |
|
|
|
ecs_entity_t c = ecs_entity(world, { |
|
.name = token.name |
|
}); |
|
|
|
if (!is_bitmask) { |
|
ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, |
|
{(ecs_i32_t)last_value}); |
|
} else { |
|
ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, |
|
{(ecs_u32_t)last_value}); |
|
} |
|
|
|
last_value ++; |
|
} |
|
|
|
ecs_set_scope(world, old_scope); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int meta_parse_enum( |
|
ecs_world_t *world, |
|
ecs_entity_t t, |
|
const char *desc) |
|
{ |
|
ecs_add(world, t, EcsEnum); |
|
return meta_parse_constants(world, t, desc, false); |
|
} |
|
|
|
static |
|
int meta_parse_bitmask( |
|
ecs_world_t *world, |
|
ecs_entity_t t, |
|
const char *desc) |
|
{ |
|
ecs_add(world, t, EcsBitmask); |
|
return meta_parse_constants(world, t, desc, true); |
|
} |
|
|
|
int ecs_meta_from_desc( |
|
ecs_world_t *world, |
|
ecs_entity_t component, |
|
ecs_type_kind_t kind, |
|
const char *desc) |
|
{ |
|
switch(kind) { |
|
case EcsStructType: |
|
if (meta_parse_struct(world, component, desc)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsEnumType: |
|
if (meta_parse_enum(world, component, desc)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsBitmaskType: |
|
if (meta_parse_bitmask(world, component, desc)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsPrimitiveType: |
|
case EcsArrayType: |
|
case EcsVectorType: |
|
case EcsOpaqueType: |
|
break; |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/metrics.c |
|
* @brief Metrics addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_METRICS |
|
|
|
/* Public components */ |
|
ECS_COMPONENT_DECLARE(FlecsMetrics); |
|
ECS_TAG_DECLARE(EcsMetricInstance); |
|
ECS_COMPONENT_DECLARE(EcsMetricValue); |
|
ECS_COMPONENT_DECLARE(EcsMetricSource); |
|
ECS_TAG_DECLARE(EcsMetric); |
|
ECS_TAG_DECLARE(EcsCounter); |
|
ECS_TAG_DECLARE(EcsCounterIncrement); |
|
ECS_TAG_DECLARE(EcsCounterId); |
|
ECS_TAG_DECLARE(EcsGauge); |
|
|
|
/* Internal components */ |
|
static ECS_COMPONENT_DECLARE(EcsMetricMember); |
|
static ECS_COMPONENT_DECLARE(EcsMetricId); |
|
static ECS_COMPONENT_DECLARE(EcsMetricOneOf); |
|
static ECS_COMPONENT_DECLARE(EcsMetricCountIds); |
|
static ECS_COMPONENT_DECLARE(EcsMetricCountTargets); |
|
static ECS_COMPONENT_DECLARE(EcsMetricMemberInstance); |
|
static ECS_COMPONENT_DECLARE(EcsMetricIdInstance); |
|
static ECS_COMPONENT_DECLARE(EcsMetricOneOfInstance); |
|
|
|
/** Context for metric */ |
|
typedef struct { |
|
ecs_entity_t metric; /**< Metric entity */ |
|
ecs_entity_t kind; /**< Metric kind (gauge, counter) */ |
|
} ecs_metric_ctx_t; |
|
|
|
/** Context for metric that monitors member */ |
|
typedef struct { |
|
ecs_metric_ctx_t metric; |
|
ecs_primitive_kind_t type_kind; /**< Primitive type kind of member */ |
|
uint16_t offset; /**< Offset of member in component */ |
|
} ecs_member_metric_ctx_t; |
|
|
|
/** Context for metric that monitors whether entity has id */ |
|
typedef struct { |
|
ecs_metric_ctx_t metric; |
|
ecs_id_record_t *idr; /**< Id record for monitored component */ |
|
} ecs_id_metric_ctx_t; |
|
|
|
/** Context for metric that monitors whether entity has pair target */ |
|
typedef struct { |
|
ecs_metric_ctx_t metric; |
|
ecs_id_record_t *idr; /**< Id record for monitored component */ |
|
ecs_size_t size; /**< Size of metric type */ |
|
ecs_map_t target_offset; /**< Pair target to metric type offset */ |
|
} ecs_oneof_metric_ctx_t; |
|
|
|
/** Context for metric that monitors how many entities have a pair target */ |
|
typedef struct { |
|
ecs_metric_ctx_t metric; |
|
ecs_id_record_t *idr; /**< Id record for monitored component */ |
|
ecs_map_t targets; /**< Map of counters for each target */ |
|
} ecs_count_targets_metric_ctx_t; |
|
|
|
/** Stores context shared for all instances of member metric */ |
|
typedef struct { |
|
ecs_member_metric_ctx_t *ctx; |
|
} EcsMetricMember; |
|
|
|
/** Stores context shared for all instances of id metric */ |
|
typedef struct { |
|
ecs_id_metric_ctx_t *ctx; |
|
} EcsMetricId; |
|
|
|
/** Stores context shared for all instances of oneof metric */ |
|
typedef struct { |
|
ecs_oneof_metric_ctx_t *ctx; |
|
} EcsMetricOneOf; |
|
|
|
/** Stores context shared for all instances of id counter metric */ |
|
typedef struct { |
|
ecs_id_t id; |
|
} EcsMetricCountIds; |
|
|
|
/** Stores context shared for all instances of target counter metric */ |
|
typedef struct { |
|
ecs_count_targets_metric_ctx_t *ctx; |
|
} EcsMetricCountTargets; |
|
|
|
/** Instance of member metric */ |
|
typedef struct { |
|
ecs_ref_t ref; |
|
ecs_member_metric_ctx_t *ctx; |
|
} EcsMetricMemberInstance; |
|
|
|
/** Instance of id metric */ |
|
typedef struct { |
|
ecs_record_t *r; |
|
ecs_id_metric_ctx_t *ctx; |
|
} EcsMetricIdInstance; |
|
|
|
/** Instance of oneof metric */ |
|
typedef struct { |
|
ecs_record_t *r; |
|
ecs_oneof_metric_ctx_t *ctx; |
|
} EcsMetricOneOfInstance; |
|
|
|
/** Component lifecycle */ |
|
|
|
static ECS_DTOR(EcsMetricMember, ptr, { |
|
ecs_os_free(ptr->ctx); |
|
}) |
|
|
|
static ECS_MOVE(EcsMetricMember, dst, src, { |
|
*dst = *src; |
|
src->ctx = NULL; |
|
}) |
|
|
|
static ECS_DTOR(EcsMetricId, ptr, { |
|
ecs_os_free(ptr->ctx); |
|
}) |
|
|
|
static ECS_MOVE(EcsMetricId, dst, src, { |
|
*dst = *src; |
|
src->ctx = NULL; |
|
}) |
|
|
|
static ECS_DTOR(EcsMetricOneOf, ptr, { |
|
if (ptr->ctx) { |
|
ecs_map_fini(&ptr->ctx->target_offset); |
|
ecs_os_free(ptr->ctx); |
|
} |
|
}) |
|
|
|
static ECS_MOVE(EcsMetricOneOf, dst, src, { |
|
*dst = *src; |
|
src->ctx = NULL; |
|
}) |
|
|
|
static ECS_DTOR(EcsMetricCountTargets, ptr, { |
|
if (ptr->ctx) { |
|
ecs_map_fini(&ptr->ctx->targets); |
|
ecs_os_free(ptr->ctx); |
|
} |
|
}) |
|
|
|
static ECS_MOVE(EcsMetricCountTargets, dst, src, { |
|
*dst = *src; |
|
src->ctx = NULL; |
|
}) |
|
|
|
/** Observer used for creating new instances of member metric */ |
|
static void flecs_metrics_on_member_metric(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
ecs_member_metric_ctx_t *ctx = it->ctx; |
|
ecs_id_t id = ecs_field_id(it, 1); |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); |
|
|
|
EcsMetricMemberInstance *src = ecs_emplace( |
|
world, m, EcsMetricMemberInstance); |
|
src->ref = ecs_ref_init_id(world, e, id); |
|
src->ctx = ctx; |
|
ecs_modified(world, m, EcsMetricMemberInstance); |
|
ecs_set(world, m, EcsMetricValue, { 0 }); |
|
ecs_set(world, m, EcsMetricSource, { e }); |
|
ecs_add(world, m, EcsMetricInstance); |
|
ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); |
|
} |
|
} |
|
|
|
/** Observer used for creating new instances of id metric */ |
|
static void flecs_metrics_on_id_metric(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
ecs_id_metric_ctx_t *ctx = it->ctx; |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); |
|
|
|
EcsMetricIdInstance *src = ecs_emplace(world, m, EcsMetricIdInstance); |
|
src->r = ecs_record_find(world, e); |
|
src->ctx = ctx; |
|
ecs_modified(world, m, EcsMetricIdInstance); |
|
ecs_set(world, m, EcsMetricValue, { 0 }); |
|
ecs_set(world, m, EcsMetricSource, { e }); |
|
ecs_add(world, m, EcsMetricInstance); |
|
ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); |
|
} |
|
} |
|
|
|
/** Observer used for creating new instances of oneof metric */ |
|
static void flecs_metrics_on_oneof_metric(ecs_iter_t *it) { |
|
if (it->event == EcsOnRemove) { |
|
return; |
|
} |
|
|
|
ecs_world_t *world = it->world; |
|
ecs_oneof_metric_ctx_t *ctx = it->ctx; |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); |
|
|
|
EcsMetricOneOfInstance *src = ecs_emplace(world, m, EcsMetricOneOfInstance); |
|
src->r = ecs_record_find(world, e); |
|
src->ctx = ctx; |
|
ecs_modified(world, m, EcsMetricOneOfInstance); |
|
ecs_add_pair(world, m, ctx->metric.metric, ecs_id(EcsMetricValue)); |
|
ecs_set(world, m, EcsMetricSource, { e }); |
|
ecs_add(world, m, EcsMetricInstance); |
|
ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); |
|
} |
|
} |
|
|
|
/** Set doc name of metric instance to name of source entity */ |
|
#ifdef FLECS_DOC |
|
static void SetMetricDocName(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1); |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t src_e = src[i].entity; |
|
const char *name = ecs_get_name(world, src_e); |
|
if (name) { |
|
ecs_doc_set_name(world, it->entities[i], name); |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
/** Delete metric instances for entities that are no longer alive */ |
|
static void ClearMetricInstance(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1); |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t src_e = src[i].entity; |
|
if (!ecs_is_alive(world, src_e)) { |
|
ecs_delete(world, it->entities[i]); |
|
} |
|
} |
|
} |
|
|
|
/** Update member metric */ |
|
static void UpdateMemberInstance(ecs_iter_t *it, bool counter) { |
|
ecs_world_t *world = it->real_world; |
|
EcsMetricValue *m = ecs_field(it, EcsMetricValue, 1); |
|
EcsMetricMemberInstance *mi = ecs_field(it, EcsMetricMemberInstance, 2); |
|
ecs_ftime_t dt = it->delta_time; |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_member_metric_ctx_t *ctx = mi[i].ctx; |
|
ecs_ref_t *ref = &mi[i].ref; |
|
const void *ptr = ecs_ref_get_id(world, ref, ref->id); |
|
if (ptr) { |
|
ptr = ECS_OFFSET(ptr, ctx->offset); |
|
if (!counter) { |
|
m[i].value = ecs_meta_ptr_to_float(ctx->type_kind, ptr); |
|
} else { |
|
m[i].value += |
|
ecs_meta_ptr_to_float(ctx->type_kind, ptr) * (double)dt; |
|
} |
|
} else { |
|
ecs_delete(it->world, it->entities[i]); |
|
} |
|
} |
|
} |
|
|
|
static void UpdateGaugeMemberInstance(ecs_iter_t *it) { |
|
UpdateMemberInstance(it, false); |
|
} |
|
|
|
static void UpdateCounterMemberInstance(ecs_iter_t *it) { |
|
UpdateMemberInstance(it, false); |
|
} |
|
|
|
static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { |
|
UpdateMemberInstance(it, true); |
|
} |
|
|
|
/** Update id metric */ |
|
static void UpdateIdInstance(ecs_iter_t *it, bool counter) { |
|
ecs_world_t *world = it->real_world; |
|
EcsMetricValue *m = ecs_field(it, EcsMetricValue, 1); |
|
EcsMetricIdInstance *mi = ecs_field(it, EcsMetricIdInstance, 2); |
|
ecs_ftime_t dt = it->delta_time; |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_table_t *table = mi[i].r->table; |
|
if (!table) { |
|
ecs_delete(it->world, it->entities[i]); |
|
continue; |
|
} |
|
|
|
ecs_id_metric_ctx_t *ctx = mi[i].ctx; |
|
ecs_id_record_t *idr = ctx->idr; |
|
if (flecs_search_w_idr(world, table, idr->id, NULL, idr) != -1) { |
|
if (!counter) { |
|
m[i].value = 1.0; |
|
} else { |
|
m[i].value += 1.0 * (double)dt; |
|
} |
|
} else { |
|
ecs_delete(it->world, it->entities[i]); |
|
} |
|
} |
|
} |
|
|
|
static void UpdateGaugeIdInstance(ecs_iter_t *it) { |
|
UpdateIdInstance(it, false); |
|
} |
|
|
|
static void UpdateCounterIdInstance(ecs_iter_t *it) { |
|
UpdateIdInstance(it, true); |
|
} |
|
|
|
/** Update oneof metric */ |
|
static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) { |
|
ecs_world_t *world = it->real_world; |
|
ecs_table_t *table = it->table; |
|
void *m = ecs_table_get_column(table, |
|
ecs_table_type_to_column_index(table, it->columns[0] - 1), it->offset); |
|
EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 2); |
|
ecs_ftime_t dt = it->delta_time; |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_oneof_metric_ctx_t *ctx = mi[i].ctx; |
|
ecs_table_t *mtable = mi[i].r->table; |
|
|
|
double *value = ECS_ELEM(m, ctx->size, i); |
|
if (!counter) { |
|
ecs_os_memset(value, 0, ctx->size); |
|
} |
|
|
|
if (!mtable) { |
|
ecs_delete(it->world, it->entities[i]); |
|
continue; |
|
} |
|
|
|
ecs_id_record_t *idr = ctx->idr; |
|
ecs_id_t id; |
|
if (flecs_search_w_idr(world, mtable, idr->id, &id, idr) == -1) { |
|
ecs_delete(it->world, it->entities[i]); |
|
continue; |
|
} |
|
|
|
ecs_entity_t tgt = ECS_PAIR_SECOND(id); |
|
uint64_t *offset = ecs_map_get(&ctx->target_offset, tgt); |
|
if (!offset) { |
|
ecs_err("unexpected relationship target for metric"); |
|
continue; |
|
} |
|
|
|
value = ECS_OFFSET(value, *offset); |
|
|
|
if (!counter) { |
|
*value = 1.0; |
|
} else { |
|
*value += 1.0 * (double)dt; |
|
} |
|
} |
|
} |
|
|
|
static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { |
|
UpdateOneOfInstance(it, false); |
|
} |
|
|
|
static void UpdateCounterOneOfInstance(ecs_iter_t *it) { |
|
UpdateOneOfInstance(it, true); |
|
} |
|
|
|
static void UpdateCountTargets(ecs_iter_t *it) { |
|
ecs_world_t *world = it->real_world; |
|
EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 1); |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_count_targets_metric_ctx_t *ctx = m[i].ctx; |
|
ecs_id_record_t *cur = ctx->idr; |
|
while ((cur = cur->first.next)) { |
|
ecs_id_t id = cur->id; |
|
ecs_entity_t *mi = ecs_map_ensure(&ctx->targets, id); |
|
if (!mi[0]) { |
|
mi[0] = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); |
|
ecs_entity_t tgt = ecs_pair_second(world, cur->id); |
|
const char *name = ecs_get_name(world, tgt); |
|
if (name) { |
|
ecs_set_name(world, mi[0], name); |
|
} |
|
|
|
EcsMetricSource *source = ecs_get_mut( |
|
world, mi[0], EcsMetricSource); |
|
source->entity = tgt; |
|
} |
|
|
|
EcsMetricValue *value = ecs_get_mut(world, mi[0], EcsMetricValue); |
|
value->value += (double)ecs_count_id(world, cur->id) * |
|
(double)it->delta_system_time; |
|
} |
|
} |
|
} |
|
|
|
static void UpdateCountIds(ecs_iter_t *it) { |
|
ecs_world_t *world = it->real_world; |
|
EcsMetricCountIds *m = ecs_field(it, EcsMetricCountIds, 1); |
|
EcsMetricValue *v = ecs_field(it, EcsMetricValue, 2); |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
v[i].value += (double)ecs_count_id(world, m[i].id) * |
|
(double)it->delta_system_time; |
|
} |
|
} |
|
|
|
/** Initialize member metric */ |
|
static |
|
int flecs_member_metric_init( |
|
ecs_world_t *world, |
|
ecs_entity_t metric, |
|
const ecs_metric_desc_t *desc) |
|
{ |
|
ecs_entity_t type = 0, member_type = 0, member = 0, id = 0; |
|
uintptr_t offset = 0; |
|
|
|
if (desc->dotmember) { |
|
if (!desc->id) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
ecs_err("missing id for metric '%s' with member '%s", |
|
metric_name, desc->dotmember); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
|
|
if (desc->member) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
ecs_err("cannot set both member and dotmember for metric '%s'", |
|
metric_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
|
|
type = ecs_get_typeid(world, desc->id); |
|
|
|
ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); |
|
if (ecs_meta_push(&cur)) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
ecs_err("invalid type for metric '%s'", metric_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
if (ecs_meta_dotmember(&cur, desc->dotmember)) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
ecs_err("invalid dotmember '%s' for metric '%s'", |
|
desc->dotmember, metric_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
|
|
id = desc->id; |
|
member_type = ecs_meta_get_type(&cur); |
|
offset = (uintptr_t)ecs_meta_get_ptr(&cur); |
|
member = ecs_meta_get_member_id(&cur); |
|
} else { |
|
const EcsMember *m = ecs_get(world, desc->member, EcsMember); |
|
if (!m) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
char *member_name = ecs_get_fullpath(world, desc->member); |
|
ecs_err("entity '%s' provided for metric '%s' is not a member", |
|
member_name, metric_name); |
|
ecs_os_free(member_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
|
|
type = ecs_get_parent(world, desc->member); |
|
if (!type) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
char *member_name = ecs_get_fullpath(world, desc->member); |
|
ecs_err("member '%s' provided for metric '%s' is not part of a type", |
|
member_name, metric_name); |
|
ecs_os_free(member_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
|
|
id = type; |
|
if (desc->id) { |
|
if (type != ecs_get_typeid(world, desc->id)) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
char *member_name = ecs_get_fullpath(world, desc->member); |
|
char *id_name = ecs_get_fullpath(world, desc->id); |
|
ecs_err("member '%s' for metric '%s' is not of type '%s'", |
|
member_name, metric_name, id_name); |
|
ecs_os_free(id_name); |
|
ecs_os_free(member_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
id = desc->id; |
|
} |
|
|
|
member = desc->member; |
|
member_type = m->type; |
|
offset = flecs_ito(uintptr_t, m->offset); |
|
} |
|
|
|
const EcsPrimitive *p = ecs_get(world, member_type, EcsPrimitive); |
|
if (!p) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
char *member_name = ecs_get_fullpath(world, desc->member); |
|
ecs_err("member '%s' provided for metric '%s' must have primitive type", |
|
member_name, metric_name); |
|
ecs_os_free(member_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
|
|
const EcsMetaType *mt = ecs_get(world, type, EcsMetaType); |
|
if (!mt) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
char *member_name = ecs_get_fullpath(world, desc->member); |
|
ecs_err("parent of member '%s' for metric '%s' is not a type", |
|
member_name, metric_name); |
|
ecs_os_free(member_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
|
|
if (mt->kind != EcsStructType) { |
|
char *metric_name = ecs_get_fullpath(world, metric); |
|
char *member_name = ecs_get_fullpath(world, desc->member); |
|
ecs_err("parent of member '%s' for metric '%s' is not a struct", |
|
member_name, metric_name); |
|
ecs_os_free(member_name); |
|
ecs_os_free(metric_name); |
|
goto error; |
|
} |
|
|
|
ecs_member_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_member_metric_ctx_t); |
|
ctx->metric.metric = metric; |
|
ctx->metric.kind = desc->kind; |
|
ctx->type_kind = p->kind; |
|
ctx->offset = flecs_uto(uint16_t, offset); |
|
|
|
ecs_observer(world, { |
|
.entity = metric, |
|
.events = { EcsOnAdd }, |
|
.filter.terms[0] = { |
|
.id = id, |
|
.src.flags = EcsSelf, |
|
.inout = EcsInOutNone |
|
}, |
|
.callback = flecs_metrics_on_member_metric, |
|
.yield_existing = true, |
|
.ctx = ctx |
|
}); |
|
|
|
ecs_set_pair(world, metric, EcsMetricMember, member, { .ctx = ctx }); |
|
ecs_add_pair(world, metric, EcsMetric, desc->kind); |
|
ecs_add_id(world, metric, EcsMetric); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/** Update id metric */ |
|
static |
|
int flecs_id_metric_init( |
|
ecs_world_t *world, |
|
ecs_entity_t metric, |
|
const ecs_metric_desc_t *desc) |
|
{ |
|
ecs_id_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_id_metric_ctx_t); |
|
ctx->metric.metric = metric; |
|
ctx->metric.kind = desc->kind; |
|
ctx->idr = flecs_id_record_ensure(world, desc->id); |
|
ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_observer(world, { |
|
.entity = metric, |
|
.events = { EcsOnAdd }, |
|
.filter.terms[0] = { |
|
.id = desc->id, |
|
.src.flags = EcsSelf, |
|
.inout = EcsInOutNone |
|
}, |
|
.callback = flecs_metrics_on_id_metric, |
|
.yield_existing = true, |
|
.ctx = ctx |
|
}); |
|
|
|
ecs_set(world, metric, EcsMetricId, { .ctx = ctx }); |
|
ecs_add_pair(world, metric, EcsMetric, desc->kind); |
|
ecs_add_id(world, metric, EcsMetric); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/** Update oneof metric */ |
|
static |
|
int flecs_oneof_metric_init( |
|
ecs_world_t *world, |
|
ecs_entity_t metric, |
|
ecs_entity_t scope, |
|
const ecs_metric_desc_t *desc) |
|
{ |
|
ecs_oneof_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_oneof_metric_ctx_t); |
|
ctx->metric.metric = metric; |
|
ctx->metric.kind = desc->kind; |
|
ctx->idr = flecs_id_record_ensure(world, desc->id); |
|
ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_map_init(&ctx->target_offset, NULL); |
|
|
|
/* Add member for each child of oneof to metric, so it can be used as metric |
|
* instance type that holds values for all targets */ |
|
ecs_iter_t it = ecs_children(world, scope); |
|
uint64_t offset = 0; |
|
while (ecs_children_next(&it)) { |
|
int32_t i, count = it.count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t tgt = it.entities[i]; |
|
const char *name = ecs_get_name(world, tgt); |
|
if (!name) { |
|
/* Member must have name */ |
|
continue; |
|
} |
|
|
|
char *to_snake_case = flecs_to_snake_case(name); |
|
|
|
ecs_entity_t mbr = ecs_entity(world, { |
|
.name = to_snake_case, |
|
.add = { ecs_childof(metric) } |
|
}); |
|
|
|
ecs_os_free(to_snake_case); |
|
|
|
ecs_set(world, mbr, EcsMember, { |
|
.type = ecs_id(ecs_f64_t), |
|
.unit = EcsSeconds |
|
}); |
|
|
|
/* Truncate upper 32 bits of target so we can lookup the offset |
|
* with the id we get from the pair */ |
|
ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset; |
|
|
|
offset += sizeof(double); |
|
} |
|
} |
|
|
|
ctx->size = flecs_uto(ecs_size_t, offset); |
|
|
|
ecs_observer(world, { |
|
.entity = metric, |
|
.events = { EcsMonitor }, |
|
.filter.terms[0] = { |
|
.id = desc->id, |
|
.src.flags = EcsSelf, |
|
.inout = EcsInOutNone |
|
}, |
|
.callback = flecs_metrics_on_oneof_metric, |
|
.yield_existing = true, |
|
.ctx = ctx |
|
}); |
|
|
|
ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); |
|
ecs_add_pair(world, metric, EcsMetric, desc->kind); |
|
ecs_add_id(world, metric, EcsMetric); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int flecs_count_id_targets_metric_init( |
|
ecs_world_t *world, |
|
ecs_entity_t metric, |
|
const ecs_metric_desc_t *desc) |
|
{ |
|
ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t); |
|
ctx->metric.metric = metric; |
|
ctx->metric.kind = desc->kind; |
|
ctx->idr = flecs_id_record_ensure(world, desc->id); |
|
ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_map_init(&ctx->targets, NULL); |
|
|
|
ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); |
|
ecs_add_pair(world, metric, EcsMetric, desc->kind); |
|
ecs_add_id(world, metric, EcsMetric); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int flecs_count_ids_metric_init( |
|
ecs_world_t *world, |
|
ecs_entity_t metric, |
|
const ecs_metric_desc_t *desc) |
|
{ |
|
ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id }); |
|
ecs_set(world, metric, EcsMetricValue, { .value = 0 }); |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_metric_init( |
|
ecs_world_t *world, |
|
const ecs_metric_desc_t *desc) |
|
{ |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_entity_t result = desc->entity; |
|
if (!result) { |
|
result = ecs_new_id(world); |
|
} |
|
|
|
ecs_entity_t kind = desc->kind; |
|
if (!kind) { |
|
ecs_err("missing metric kind"); |
|
goto error; |
|
} |
|
|
|
if (kind != EcsGauge && |
|
kind != EcsCounter && |
|
kind != EcsCounterId && |
|
kind != EcsCounterIncrement) |
|
{ |
|
ecs_err("invalid metric kind %s", ecs_get_fullpath(world, kind)); |
|
goto error; |
|
} |
|
|
|
if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) { |
|
ecs_err("CounterIncrement can only be used in combination with member"); |
|
goto error; |
|
} |
|
|
|
if (kind == EcsCounterId && (desc->member || desc->dotmember)) { |
|
ecs_err("CounterId cannot be used in combination with member"); |
|
goto error; |
|
} |
|
|
|
if (desc->brief) { |
|
#ifdef FLECS_DOC |
|
ecs_doc_set_brief(world, result, desc->brief); |
|
#else |
|
ecs_warn("FLECS_DOC is not enabled, ignoring metrics brief"); |
|
#endif |
|
} |
|
|
|
if (desc->member || desc->dotmember) { |
|
if (flecs_member_metric_init(world, result, desc)) { |
|
goto error; |
|
} |
|
} else if (desc->id) { |
|
if (desc->targets) { |
|
if (!ecs_id_is_pair(desc->id)) { |
|
ecs_err("cannot specify targets for id that is not a pair"); |
|
goto error; |
|
} |
|
if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) { |
|
ecs_err("first element of pair cannot be wildcard with " |
|
" targets enabled"); |
|
goto error; |
|
} |
|
if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) { |
|
ecs_err("second element of pair must be wildcard with " |
|
" targets enabled"); |
|
goto error; |
|
} |
|
|
|
if (kind == EcsCounterId) { |
|
if (flecs_count_id_targets_metric_init(world, result, desc)) { |
|
goto error; |
|
} |
|
} else { |
|
ecs_entity_t first = ecs_pair_first(world, desc->id); |
|
ecs_entity_t scope = flecs_get_oneof(world, first); |
|
if (!scope) { |
|
ecs_err("first element of pair must have OneOf with " |
|
" targets enabled"); |
|
goto error; |
|
} |
|
|
|
if (flecs_oneof_metric_init(world, result, scope, desc)) { |
|
goto error; |
|
} |
|
} |
|
} else { |
|
if (kind == EcsCounterId) { |
|
if (flecs_count_ids_metric_init(world, result, desc)) { |
|
goto error; |
|
} |
|
} else { |
|
if (flecs_id_metric_init(world, result, desc)) { |
|
goto error; |
|
} |
|
} |
|
} |
|
} else { |
|
ecs_err("missing source specified for metric"); |
|
goto error; |
|
} |
|
|
|
return result; |
|
error: |
|
if (result && result != desc->entity) { |
|
ecs_delete(world, result); |
|
} |
|
return 0; |
|
} |
|
|
|
void FlecsMetricsImport(ecs_world_t *world) { |
|
ECS_MODULE_DEFINE(world, FlecsMetrics); |
|
|
|
ECS_IMPORT(world, FlecsPipeline); |
|
ECS_IMPORT(world, FlecsMeta); |
|
ECS_IMPORT(world, FlecsUnits); |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
ECS_TAG_DEFINE(world, EcsMetric); |
|
ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric); |
|
ECS_TAG_DEFINE(world, EcsCounter); |
|
ECS_TAG_DEFINE(world, EcsCounterIncrement); |
|
ECS_TAG_DEFINE(world, EcsCounterId); |
|
ECS_TAG_DEFINE(world, EcsGauge); |
|
ecs_set_scope(world, old_scope); |
|
|
|
ecs_set_name_prefix(world, "EcsMetric"); |
|
ECS_TAG_DEFINE(world, EcsMetricInstance); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricValue); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricSource); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricMember); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricId); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricOneOf); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricCountIds); |
|
ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets); |
|
|
|
ecs_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate); |
|
ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate); |
|
ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate); |
|
|
|
ecs_struct(world, { |
|
.entity = ecs_id(EcsMetricValue), |
|
.members = { |
|
{ .name = "value", .type = ecs_id(ecs_f64_t) } |
|
} |
|
}); |
|
|
|
ecs_struct(world, { |
|
.entity = ecs_id(EcsMetricSource), |
|
.members = { |
|
{ .name = "entity", .type = ecs_id(ecs_entity_t) } |
|
} |
|
}); |
|
|
|
ecs_set_hooks(world, EcsMetricMember, { |
|
.ctor = ecs_default_ctor, |
|
.dtor = ecs_dtor(EcsMetricMember), |
|
.move = ecs_move(EcsMetricMember) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsMetricId, { |
|
.ctor = ecs_default_ctor, |
|
.dtor = ecs_dtor(EcsMetricId), |
|
.move = ecs_move(EcsMetricId) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsMetricOneOf, { |
|
.ctor = ecs_default_ctor, |
|
.dtor = ecs_dtor(EcsMetricOneOf), |
|
.move = ecs_move(EcsMetricOneOf) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsMetricCountTargets, { |
|
.ctor = ecs_default_ctor, |
|
.dtor = ecs_dtor(EcsMetricCountTargets), |
|
.move = ecs_move(EcsMetricCountTargets) |
|
}); |
|
|
|
ecs_add_id(world, EcsMetric, EcsOneOf); |
|
|
|
#ifdef FLECS_DOC |
|
ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, EcsMetricSource); |
|
#endif |
|
|
|
ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, |
|
[in] Source); |
|
|
|
ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, |
|
[out] Value, |
|
[in] MemberInstance, |
|
[none] (Metric, Gauge)); |
|
|
|
ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore, |
|
[out] Value, |
|
[in] MemberInstance, |
|
[none] (Metric, Counter)); |
|
|
|
ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore, |
|
[out] Value, |
|
[in] MemberInstance, |
|
[none] (Metric, CounterIncrement)); |
|
|
|
ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore, |
|
[out] Value, |
|
[in] IdInstance, |
|
[none] (Metric, Gauge)); |
|
|
|
ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore, |
|
[inout] Value, |
|
[in] IdInstance, |
|
[none] (Metric, Counter)); |
|
|
|
ECS_SYSTEM(world, UpdateGaugeOneOfInstance, EcsPreStore, |
|
[none] (_, Value), |
|
[in] OneOfInstance, |
|
[none] (Metric, Gauge)); |
|
|
|
ECS_SYSTEM(world, UpdateCounterOneOfInstance, EcsPreStore, |
|
[none] (_, Value), |
|
[in] OneOfInstance, |
|
[none] (Metric, Counter)); |
|
|
|
ECS_SYSTEM(world, UpdateCountIds, EcsPreStore, |
|
[inout] CountIds, Value); |
|
|
|
ECS_SYSTEM(world, UpdateCountTargets, EcsPreStore, |
|
[inout] CountTargets); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/module.c |
|
* @brief Module addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_MODULE |
|
|
|
#include <ctype.h> |
|
|
|
char* ecs_module_path_from_c( |
|
const char *c_name) |
|
{ |
|
ecs_strbuf_t str = ECS_STRBUF_INIT; |
|
const char *ptr; |
|
char ch; |
|
|
|
for (ptr = c_name; (ch = *ptr); ptr++) { |
|
if (isupper(ch)) { |
|
ch = flecs_ito(char, tolower(ch)); |
|
if (ptr != c_name) { |
|
ecs_strbuf_appendstrn(&str, ".", 1); |
|
} |
|
} |
|
|
|
ecs_strbuf_appendstrn(&str, &ch, 1); |
|
} |
|
|
|
return ecs_strbuf_get(&str); |
|
} |
|
|
|
ecs_entity_t ecs_import( |
|
ecs_world_t *world, |
|
ecs_module_action_t module, |
|
const char *module_name) |
|
{ |
|
ecs_check(!(world->flags & EcsWorldReadonly), |
|
ECS_INVALID_WHILE_READONLY, NULL); |
|
|
|
ecs_entity_t old_scope = ecs_set_scope(world, 0); |
|
const char *old_name_prefix = world->info.name_prefix; |
|
|
|
char *path = ecs_module_path_from_c(module_name); |
|
ecs_entity_t e = ecs_lookup_fullpath(world, path); |
|
ecs_os_free(path); |
|
|
|
if (!e) { |
|
ecs_trace("#[magenta]import#[reset] %s", module_name); |
|
ecs_log_push(); |
|
|
|
/* Load module */ |
|
module(world); |
|
|
|
/* Lookup module entity (must be registered by module) */ |
|
e = ecs_lookup_fullpath(world, module_name); |
|
ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); |
|
|
|
ecs_log_pop(); |
|
} |
|
|
|
/* Restore to previous state */ |
|
ecs_set_scope(world, old_scope); |
|
world->info.name_prefix = old_name_prefix; |
|
|
|
return e; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_import_c( |
|
ecs_world_t *world, |
|
ecs_module_action_t module, |
|
const char *c_name) |
|
{ |
|
char *name = ecs_module_path_from_c(c_name); |
|
ecs_entity_t e = ecs_import(world, module, name); |
|
ecs_os_free(name); |
|
return e; |
|
} |
|
|
|
ecs_entity_t ecs_import_from_library( |
|
ecs_world_t *world, |
|
const char *library_name, |
|
const char *module_name) |
|
{ |
|
ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
char *import_func = ECS_CONST_CAST(char*, module_name); |
|
char *module = ECS_CONST_CAST(char*, module_name); |
|
|
|
if (!ecs_os_has_modules() || !ecs_os_has_dl()) { |
|
ecs_err( |
|
"library loading not supported, set module_to_dl, dlopen, dlclose " |
|
"and dlproc os API callbacks first"); |
|
return 0; |
|
} |
|
|
|
/* If no module name is specified, try default naming convention for loading |
|
* the main module from the library */ |
|
if (!import_func) { |
|
import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); |
|
ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
|
|
const char *ptr; |
|
char ch, *bptr = import_func; |
|
bool capitalize = true; |
|
for (ptr = library_name; (ch = *ptr); ptr ++) { |
|
if (ch == '.') { |
|
capitalize = true; |
|
} else { |
|
if (capitalize) { |
|
*bptr = flecs_ito(char, toupper(ch)); |
|
bptr ++; |
|
capitalize = false; |
|
} else { |
|
*bptr = flecs_ito(char, tolower(ch)); |
|
bptr ++; |
|
} |
|
} |
|
} |
|
|
|
*bptr = '\0'; |
|
|
|
module = ecs_os_strdup(import_func); |
|
ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
|
|
ecs_os_strcat(bptr, "Import"); |
|
} |
|
|
|
char *library_filename = ecs_os_module_to_dl(library_name); |
|
if (!library_filename) { |
|
ecs_err("failed to find library file for '%s'", library_name); |
|
if (module != module_name) { |
|
ecs_os_free(module); |
|
} |
|
return 0; |
|
} else { |
|
ecs_trace("found file '%s' for library '%s'", |
|
library_filename, library_name); |
|
} |
|
|
|
ecs_os_dl_t dl = ecs_os_dlopen(library_filename); |
|
if (!dl) { |
|
ecs_err("failed to load library '%s' ('%s')", |
|
library_name, library_filename); |
|
|
|
ecs_os_free(library_filename); |
|
|
|
if (module != module_name) { |
|
ecs_os_free(module); |
|
} |
|
|
|
return 0; |
|
} else { |
|
ecs_trace("library '%s' ('%s') loaded", |
|
library_name, library_filename); |
|
} |
|
|
|
ecs_module_action_t action = (ecs_module_action_t) |
|
ecs_os_dlproc(dl, import_func); |
|
if (!action) { |
|
ecs_err("failed to load import function %s from library %s", |
|
import_func, library_name); |
|
ecs_os_free(library_filename); |
|
ecs_os_dlclose(dl); |
|
return 0; |
|
} else { |
|
ecs_trace("found import function '%s' in library '%s' for module '%s'", |
|
import_func, library_name, module); |
|
} |
|
|
|
/* Do not free id, as it will be stored as the component identifier */ |
|
ecs_entity_t result = ecs_import(world, action, module); |
|
|
|
if (import_func != module_name) { |
|
ecs_os_free(import_func); |
|
} |
|
|
|
if (module != module_name) { |
|
ecs_os_free(module); |
|
} |
|
|
|
ecs_os_free(library_filename); |
|
|
|
return result; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_module_init( |
|
ecs_world_t *world, |
|
const char *c_name, |
|
const ecs_component_desc_t *desc) |
|
{ |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_entity_t old_scope = ecs_set_scope(world, 0); |
|
|
|
ecs_entity_t e = desc->entity; |
|
if (!e) { |
|
char *module_path = ecs_module_path_from_c(c_name); |
|
e = ecs_new_from_fullpath(world, module_path); |
|
ecs_set_symbol(world, e, module_path); |
|
ecs_os_free(module_path); |
|
} else if (!ecs_exists(world, e)) { |
|
char *module_path = ecs_module_path_from_c(c_name); |
|
ecs_ensure(world, e); |
|
ecs_add_fullpath(world, e, module_path); |
|
ecs_set_symbol(world, e, module_path); |
|
ecs_os_free(module_path); |
|
} |
|
|
|
ecs_add_id(world, e, EcsModule); |
|
|
|
ecs_component_desc_t private_desc = *desc; |
|
private_desc.entity = e; |
|
|
|
if (desc->type.size) { |
|
ecs_entity_t result = ecs_component_init(world, &private_desc); |
|
ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); |
|
(void)result; |
|
} |
|
|
|
ecs_set_scope(world, old_scope); |
|
|
|
return e; |
|
error: |
|
return 0; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/monitor.c |
|
* @brief Monitor addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_MONITOR |
|
|
|
ECS_COMPONENT_DECLARE(FlecsMonitor); |
|
ECS_COMPONENT_DECLARE(EcsWorldStats); |
|
ECS_COMPONENT_DECLARE(EcsWorldSummary); |
|
ECS_COMPONENT_DECLARE(EcsPipelineStats); |
|
|
|
ecs_entity_t EcsPeriod1s = 0; |
|
ecs_entity_t EcsPeriod1m = 0; |
|
ecs_entity_t EcsPeriod1h = 0; |
|
ecs_entity_t EcsPeriod1d = 0; |
|
ecs_entity_t EcsPeriod1w = 0; |
|
|
|
static int32_t flecs_day_interval_count = 24; |
|
static int32_t flecs_week_interval_count = 168; |
|
|
|
static |
|
ECS_COPY(EcsPipelineStats, dst, src, { |
|
(void)dst; |
|
(void)src; |
|
ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); |
|
}) |
|
|
|
static |
|
ECS_MOVE(EcsPipelineStats, dst, src, { |
|
ecs_os_memcpy_t(dst, src, EcsPipelineStats); |
|
ecs_os_zeromem(src); |
|
}) |
|
|
|
static |
|
ECS_DTOR(EcsPipelineStats, ptr, { |
|
ecs_pipeline_stats_fini(&ptr->stats); |
|
}) |
|
|
|
static |
|
void UpdateWorldSummary(ecs_iter_t *it) { |
|
EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 1); |
|
|
|
const ecs_world_info_t *info = ecs_get_world_info(it->world); |
|
|
|
int32_t i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
summary[i].target_fps = (double)info->target_fps; |
|
|
|
summary[i].frame_time_last = (double)info->frame_time_total - summary[i].frame_time_total; |
|
summary[i].system_time_last = (double)info->system_time_total - summary[i].system_time_total; |
|
summary[i].merge_time_last = (double)info->merge_time_total - summary[i].merge_time_total; |
|
|
|
summary[i].frame_time_total = (double)info->frame_time_total; |
|
summary[i].system_time_total = (double)info->system_time_total; |
|
summary[i].merge_time_total = (double)info->merge_time_total; |
|
} |
|
} |
|
|
|
static |
|
void MonitorStats(ecs_iter_t *it) { |
|
ecs_world_t *world = it->real_world; |
|
|
|
EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 1); |
|
ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); |
|
void *stats = ECS_OFFSET_T(hdr, EcsStatsHeader); |
|
|
|
ecs_ftime_t elapsed = hdr->elapsed; |
|
hdr->elapsed += it->delta_time; |
|
|
|
int32_t t_last = (int32_t)(elapsed * 60); |
|
int32_t t_next = (int32_t)(hdr->elapsed * 60); |
|
int32_t i, dif = t_last - t_next; |
|
|
|
ecs_world_stats_t last_world = {0}; |
|
ecs_pipeline_stats_t last_pipeline = {0}; |
|
void *last = NULL; |
|
|
|
if (!dif) { |
|
/* Copy last value so we can pass it to reduce_last */ |
|
if (kind == ecs_id(EcsWorldStats)) { |
|
last = &last_world; |
|
ecs_world_stats_copy_last(&last_world, stats); |
|
} else if (kind == ecs_id(EcsPipelineStats)) { |
|
last = &last_pipeline; |
|
ecs_pipeline_stats_copy_last(&last_pipeline, stats); |
|
} |
|
} |
|
|
|
if (kind == ecs_id(EcsWorldStats)) { |
|
ecs_world_stats_get(world, stats); |
|
} else if (kind == ecs_id(EcsPipelineStats)) { |
|
ecs_pipeline_stats_get(world, ecs_get_pipeline(world), stats); |
|
} |
|
|
|
if (!dif) { |
|
/* Still in same interval, combine with last measurement */ |
|
hdr->reduce_count ++; |
|
if (kind == ecs_id(EcsWorldStats)) { |
|
ecs_world_stats_reduce_last(stats, last, hdr->reduce_count); |
|
} else if (kind == ecs_id(EcsPipelineStats)) { |
|
ecs_pipeline_stats_reduce_last(stats, last, hdr->reduce_count); |
|
} |
|
} else if (dif > 1) { |
|
/* More than 16ms has passed, backfill */ |
|
for (i = 1; i < dif; i ++) { |
|
if (kind == ecs_id(EcsWorldStats)) { |
|
ecs_world_stats_repeat_last(stats); |
|
} else if (kind == ecs_id(EcsPipelineStats)) { |
|
ecs_world_stats_repeat_last(stats); |
|
} |
|
} |
|
hdr->reduce_count = 0; |
|
} |
|
|
|
if (last && kind == ecs_id(EcsPipelineStats)) { |
|
ecs_pipeline_stats_fini(last); |
|
} |
|
} |
|
|
|
static |
|
void ReduceStats(ecs_iter_t *it) { |
|
void *dst = ecs_field_w_size(it, 0, 1); |
|
void *src = ecs_field_w_size(it, 0, 2); |
|
|
|
ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); |
|
|
|
dst = ECS_OFFSET_T(dst, EcsStatsHeader); |
|
src = ECS_OFFSET_T(src, EcsStatsHeader); |
|
|
|
if (kind == ecs_id(EcsWorldStats)) { |
|
ecs_world_stats_reduce(dst, src); |
|
} else if (kind == ecs_id(EcsPipelineStats)) { |
|
ecs_pipeline_stats_reduce(dst, src); |
|
} |
|
} |
|
|
|
static |
|
void AggregateStats(ecs_iter_t *it) { |
|
int32_t interval = *(int32_t*)it->ctx; |
|
|
|
EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 1); |
|
EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 2); |
|
|
|
void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); |
|
void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); |
|
|
|
ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); |
|
|
|
ecs_world_stats_t last_world = {0}; |
|
ecs_pipeline_stats_t last_pipeline = {0}; |
|
void *last = NULL; |
|
|
|
if (dst_hdr->reduce_count != 0) { |
|
/* Copy last value so we can pass it to reduce_last */ |
|
if (kind == ecs_id(EcsWorldStats)) { |
|
last_world.t = 0; |
|
ecs_world_stats_copy_last(&last_world, dst); |
|
last = &last_world; |
|
} else if (kind == ecs_id(EcsPipelineStats)) { |
|
last_pipeline.t = 0; |
|
ecs_pipeline_stats_copy_last(&last_pipeline, dst); |
|
last = &last_pipeline; |
|
} |
|
} |
|
|
|
/* Reduce from minutes to the current day */ |
|
if (kind == ecs_id(EcsWorldStats)) { |
|
ecs_world_stats_reduce(dst, src); |
|
} else if (kind == ecs_id(EcsPipelineStats)) { |
|
ecs_pipeline_stats_reduce(dst, src); |
|
} |
|
|
|
if (dst_hdr->reduce_count != 0) { |
|
if (kind == ecs_id(EcsWorldStats)) { |
|
ecs_world_stats_reduce_last(dst, last, dst_hdr->reduce_count); |
|
} else if (kind == ecs_id(EcsPipelineStats)) { |
|
ecs_pipeline_stats_reduce_last(dst, last, dst_hdr->reduce_count); |
|
} |
|
} |
|
|
|
/* A day has 60 24 minute intervals */ |
|
dst_hdr->reduce_count ++; |
|
if (dst_hdr->reduce_count >= interval) { |
|
dst_hdr->reduce_count = 0; |
|
} |
|
|
|
if (last && kind == ecs_id(EcsPipelineStats)) { |
|
ecs_pipeline_stats_fini(last); |
|
} |
|
} |
|
|
|
static |
|
void flecs_stats_monitor_import( |
|
ecs_world_t *world, |
|
ecs_id_t kind, |
|
size_t size) |
|
{ |
|
ecs_entity_t prev = ecs_set_scope(world, kind); |
|
|
|
// Called each frame, collects 60 measurements per second |
|
ecs_system(world, { |
|
.entity = ecs_entity(world, { .name = "Monitor1s", .add = {ecs_dependson(EcsPreFrame)} }), |
|
.query.filter.terms = {{ |
|
.id = ecs_pair(kind, EcsPeriod1s), |
|
.src.id = EcsWorld |
|
}}, |
|
.callback = MonitorStats |
|
}); |
|
|
|
// Called each second, reduces into 60 measurements per minute |
|
ecs_entity_t mw1m = ecs_system(world, { |
|
.entity = ecs_entity(world, { .name = "Monitor1m", .add = {ecs_dependson(EcsPreFrame)} }), |
|
.query.filter.terms = {{ |
|
.id = ecs_pair(kind, EcsPeriod1m), |
|
.src.id = EcsWorld |
|
}, { |
|
.id = ecs_pair(kind, EcsPeriod1s), |
|
.src.id = EcsWorld |
|
}}, |
|
.callback = ReduceStats, |
|
.interval = 1.0 |
|
}); |
|
|
|
// Called each minute, reduces into 60 measurements per hour |
|
ecs_system(world, { |
|
.entity = ecs_entity(world, { .name = "Monitor1h", .add = {ecs_dependson(EcsPreFrame)} }), |
|
.query.filter.terms = {{ |
|
.id = ecs_pair(kind, EcsPeriod1h), |
|
.src.id = EcsWorld |
|
}, { |
|
.id = ecs_pair(kind, EcsPeriod1m), |
|
.src.id = EcsWorld |
|
}}, |
|
.callback = ReduceStats, |
|
.rate = 60, |
|
.tick_source = mw1m |
|
}); |
|
|
|
// Called each minute, reduces into 60 measurements per day |
|
ecs_system(world, { |
|
.entity = ecs_entity(world, { .name = "Monitor1d", .add = {ecs_dependson(EcsPreFrame)} }), |
|
.query.filter.terms = {{ |
|
.id = ecs_pair(kind, EcsPeriod1d), |
|
.src.id = EcsWorld |
|
}, { |
|
.id = ecs_pair(kind, EcsPeriod1m), |
|
.src.id = EcsWorld |
|
}}, |
|
.callback = AggregateStats, |
|
.rate = 60, |
|
.tick_source = mw1m, |
|
.ctx = &flecs_day_interval_count |
|
}); |
|
|
|
// Called each hour, reduces into 60 measurements per week |
|
ecs_system(world, { |
|
.entity = ecs_entity(world, { .name = "Monitor1w", .add = {ecs_dependson(EcsPreFrame)} }), |
|
.query.filter.terms = {{ |
|
.id = ecs_pair(kind, EcsPeriod1w), |
|
.src.id = EcsWorld |
|
}, { |
|
.id = ecs_pair(kind, EcsPeriod1h), |
|
.src.id = EcsWorld |
|
}}, |
|
.callback = AggregateStats, |
|
.rate = 60, |
|
.tick_source = mw1m, |
|
.ctx = &flecs_week_interval_count |
|
}); |
|
|
|
ecs_set_scope(world, prev); |
|
|
|
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1s), size, NULL); |
|
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1m), size, NULL); |
|
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1h), size, NULL); |
|
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1d), size, NULL); |
|
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1w), size, NULL); |
|
} |
|
|
|
static |
|
void flecs_world_monitor_import( |
|
ecs_world_t *world) |
|
{ |
|
ECS_COMPONENT_DEFINE(world, EcsWorldStats); |
|
|
|
flecs_stats_monitor_import(world, ecs_id(EcsWorldStats), |
|
sizeof(EcsWorldStats)); |
|
} |
|
|
|
static |
|
void flecs_pipeline_monitor_import( |
|
ecs_world_t *world) |
|
{ |
|
ECS_COMPONENT_DEFINE(world, EcsPipelineStats); |
|
|
|
ecs_set_hooks(world, EcsPipelineStats, { |
|
.ctor = ecs_default_ctor, |
|
.copy = ecs_copy(EcsPipelineStats), |
|
.move = ecs_move(EcsPipelineStats), |
|
.dtor = ecs_dtor(EcsPipelineStats) |
|
}); |
|
|
|
flecs_stats_monitor_import(world, ecs_id(EcsPipelineStats), |
|
sizeof(EcsPipelineStats)); |
|
} |
|
|
|
void FlecsMonitorImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE_DEFINE(world, FlecsMonitor); |
|
ECS_IMPORT(world, FlecsPipeline); |
|
ECS_IMPORT(world, FlecsTimer); |
|
#ifdef FLECS_META |
|
ECS_IMPORT(world, FlecsMeta); |
|
#endif |
|
#ifdef FLECS_UNITS |
|
ECS_IMPORT(world, FlecsUnits); |
|
#endif |
|
#ifdef FLECS_COREDOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
ecs_doc_set_brief(world, ecs_id(FlecsMonitor), |
|
"Module that automatically monitors statistics for the world & systems"); |
|
#endif |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
EcsPeriod1s = ecs_new_entity(world, "EcsPeriod1s"); |
|
EcsPeriod1m = ecs_new_entity(world, "EcsPeriod1m"); |
|
EcsPeriod1h = ecs_new_entity(world, "EcsPeriod1h"); |
|
EcsPeriod1d = ecs_new_entity(world, "EcsPeriod1d"); |
|
EcsPeriod1w = ecs_new_entity(world, "EcsPeriod1w"); |
|
|
|
ECS_COMPONENT_DEFINE(world, EcsWorldSummary); |
|
|
|
#if defined(FLECS_META) && defined(FLECS_UNITS) |
|
ecs_struct(world, { |
|
.entity = ecs_id(EcsWorldSummary), |
|
.members = { |
|
{ .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, |
|
{ .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, |
|
{ .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, |
|
{ .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, |
|
{ .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, |
|
{ .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, |
|
{ .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds } |
|
} |
|
}); |
|
#endif |
|
|
|
ecs_system(world, { |
|
.entity = ecs_entity(world, { |
|
.name = "UpdateWorldSummary", |
|
.add = {ecs_dependson(EcsPreFrame)} |
|
}), |
|
.query.filter.terms[0] = { .id = ecs_id(EcsWorldSummary) }, |
|
.callback = UpdateWorldSummary |
|
}); |
|
|
|
ECS_SYSTEM(world, UpdateWorldSummary, EcsPreFrame, WorldSummary); |
|
ecs_set(world, EcsWorld, EcsWorldSummary, {0}); |
|
|
|
flecs_world_monitor_import(world); |
|
flecs_pipeline_monitor_import(world); |
|
|
|
if (ecs_os_has_time()) { |
|
ecs_measure_frame_time(world, true); |
|
ecs_measure_system_time(world, true); |
|
} |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/parser.c |
|
* @brief Parser addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_PARSER |
|
|
|
#include <ctype.h> |
|
|
|
#define TOK_COLON ':' |
|
#define TOK_AND ',' |
|
#define TOK_OR "||" |
|
#define TOK_NOT '!' |
|
#define TOK_OPTIONAL '?' |
|
#define TOK_BITWISE_OR '|' |
|
#define TOK_BRACKET_OPEN '[' |
|
#define TOK_BRACKET_CLOSE ']' |
|
#define TOK_SCOPE_OPEN '{' |
|
#define TOK_SCOPE_CLOSE '}' |
|
#define TOK_VARIABLE '$' |
|
#define TOK_PAREN_OPEN '(' |
|
#define TOK_PAREN_CLOSE ')' |
|
#define TOK_EQ "==" |
|
#define TOK_NEQ "!=" |
|
#define TOK_MATCH "~=" |
|
#define TOK_EXPR_STRING '"' |
|
|
|
#define TOK_SELF "self" |
|
#define TOK_UP "up" |
|
#define TOK_DOWN "down" |
|
#define TOK_CASCADE "cascade" |
|
#define TOK_PARENT "parent" |
|
#define TOK_DESC "desc" |
|
|
|
#define TOK_OVERRIDE "OVERRIDE" |
|
#define TOK_ROLE_AND "AND" |
|
#define TOK_ROLE_OR "OR" |
|
#define TOK_ROLE_NOT "NOT" |
|
#define TOK_ROLE_TOGGLE "TOGGLE" |
|
|
|
#define TOK_IN "in" |
|
#define TOK_OUT "out" |
|
#define TOK_INOUT "inout" |
|
#define TOK_INOUT_NONE "none" |
|
|
|
static |
|
const ecs_id_t ECS_OR = (1ull << 59); |
|
|
|
static |
|
const ecs_id_t ECS_NOT = (1ull << 58); |
|
|
|
#define ECS_MAX_TOKEN_SIZE (256) |
|
|
|
typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; |
|
|
|
const char* ecs_parse_ws_eol( |
|
const char *ptr) |
|
{ |
|
while (isspace(*ptr)) { |
|
ptr ++; |
|
} |
|
|
|
return ptr; |
|
} |
|
|
|
const char* ecs_parse_ws( |
|
const char *ptr) |
|
{ |
|
while ((*ptr != '\n') && isspace(*ptr)) { |
|
ptr ++; |
|
} |
|
|
|
return ptr; |
|
} |
|
|
|
const char* ecs_parse_digit( |
|
const char *ptr, |
|
char *token) |
|
{ |
|
char *tptr = token; |
|
char ch = ptr[0]; |
|
|
|
if (!isdigit(ch) && ch != '-') { |
|
ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); |
|
return NULL; |
|
} |
|
|
|
tptr[0] = ch; |
|
tptr ++; |
|
ptr ++; |
|
|
|
for (; (ch = *ptr); ptr ++) { |
|
if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { |
|
break; |
|
} |
|
|
|
tptr[0] = ch; |
|
tptr ++; |
|
} |
|
|
|
tptr[0] = '\0'; |
|
|
|
return ptr; |
|
} |
|
|
|
/* -- Private functions -- */ |
|
|
|
bool flecs_isident( |
|
char ch) |
|
{ |
|
return isalpha(ch) || (ch == '_'); |
|
} |
|
|
|
static |
|
bool flecs_valid_identifier_start_char( |
|
char ch) |
|
{ |
|
if (ch && (flecs_isident(ch) || (ch == '*') || |
|
(ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_valid_token_start_char( |
|
char ch) |
|
{ |
|
if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') |
|
|| (ch == '[') || (ch == ']') || (ch == '`') || |
|
flecs_valid_identifier_start_char(ch)) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_valid_token_char( |
|
char ch) |
|
{ |
|
if (ch && (flecs_isident(ch) || isdigit(ch) || ch == '.' || ch == '"')) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_valid_operator_char( |
|
char ch) |
|
{ |
|
if (ch == TOK_OPTIONAL || ch == TOK_NOT) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
const char* ecs_parse_token( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
char *token_out, |
|
char delim) |
|
{ |
|
int64_t column = ptr - expr; |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
char *tptr = token_out, ch = ptr[0]; |
|
|
|
if (!flecs_valid_token_start_char(ch)) { |
|
if (ch == '\0' || ch == '\n') { |
|
ecs_parser_error(name, expr, column, |
|
"unexpected end of expression"); |
|
} else { |
|
ecs_parser_error(name, expr, column, |
|
"invalid start of token '%s'", ptr); |
|
} |
|
return NULL; |
|
} |
|
|
|
tptr[0] = ch; |
|
tptr ++; |
|
ptr ++; |
|
|
|
if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { |
|
tptr[0] = 0; |
|
return ptr; |
|
} |
|
|
|
int tmpl_nesting = 0; |
|
bool in_str = ch == '"'; |
|
|
|
for (; (ch = *ptr); ptr ++) { |
|
if (ch == '<') { |
|
tmpl_nesting ++; |
|
} else if (ch == '>') { |
|
if (!tmpl_nesting) { |
|
break; |
|
} |
|
tmpl_nesting --; |
|
} else if (ch == '"') { |
|
in_str = !in_str; |
|
} else |
|
if (!flecs_valid_token_char(ch) && !in_str) { |
|
break; |
|
} |
|
if (delim && (ch == delim)) { |
|
break; |
|
} |
|
|
|
tptr[0] = ch; |
|
tptr ++; |
|
} |
|
|
|
tptr[0] = '\0'; |
|
|
|
if (tmpl_nesting != 0) { |
|
ecs_parser_error(name, expr, column, |
|
"identifier '%s' has mismatching < > pairs", ptr); |
|
return NULL; |
|
} |
|
|
|
const char *next_ptr = ecs_parse_ws(ptr); |
|
if (next_ptr[0] == ':' && next_ptr != ptr) { |
|
/* Whitespace between token and : is significant */ |
|
ptr = next_ptr - 1; |
|
} else { |
|
ptr = next_ptr; |
|
} |
|
|
|
return ptr; |
|
} |
|
|
|
const char* ecs_parse_identifier( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
char *token_out) |
|
{ |
|
if (!flecs_valid_identifier_start_char(ptr[0]) && (ptr[0] != '"')) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected start of identifier"); |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_token(name, expr, ptr, token_out, 0); |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
int flecs_parse_identifier( |
|
const char *token, |
|
ecs_term_id_t *out) |
|
{ |
|
const char *tptr = token; |
|
if (tptr[0] == TOK_VARIABLE && tptr[1]) { |
|
out->flags |= EcsIsVariable; |
|
tptr ++; |
|
} |
|
if (tptr[0] == TOK_EXPR_STRING && tptr[1]) { |
|
out->flags |= EcsIsName; |
|
tptr ++; |
|
if (tptr[0] == TOK_NOT) { |
|
/* Already parsed */ |
|
tptr ++; |
|
} |
|
} |
|
|
|
char *name = ecs_os_strdup(tptr); |
|
out->name = name; |
|
|
|
ecs_size_t len = ecs_os_strlen(name); |
|
if (out->flags & EcsIsName) { |
|
if (name[len - 1] != TOK_EXPR_STRING) { |
|
ecs_parser_error(NULL, token, 0, "missing '\"' at end of string"); |
|
return -1; |
|
} else { |
|
name[len - 1] = '\0'; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_parse_role( |
|
const char *name, |
|
const char *sig, |
|
int64_t column, |
|
const char *token) |
|
{ |
|
if (!ecs_os_strcmp(token, TOK_ROLE_AND)) { |
|
return ECS_AND; |
|
} else if (!ecs_os_strcmp(token, TOK_ROLE_OR)) { |
|
return ECS_OR; |
|
} else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { |
|
return ECS_NOT; |
|
} else if (!ecs_os_strcmp(token, TOK_OVERRIDE)) { |
|
return ECS_OVERRIDE; |
|
} else if (!ecs_os_strcmp(token, TOK_ROLE_TOGGLE)) { |
|
return ECS_TOGGLE; |
|
} else { |
|
ecs_parser_error(name, sig, column, "invalid role '%s'", token); |
|
return 0; |
|
} |
|
} |
|
|
|
static |
|
ecs_oper_kind_t flecs_parse_operator( |
|
char ch) |
|
{ |
|
if (ch == TOK_OPTIONAL) { |
|
return EcsOptional; |
|
} else if (ch == TOK_NOT) { |
|
return EcsNot; |
|
} else { |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
static |
|
const char* flecs_parse_annotation( |
|
const char *name, |
|
const char *sig, |
|
int64_t column, |
|
const char *ptr, |
|
ecs_inout_kind_t *inout_kind_out) |
|
{ |
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
|
|
ptr = ecs_parse_identifier(name, sig, ptr, token); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
if (!ecs_os_strcmp(token, TOK_IN)) { |
|
*inout_kind_out = EcsIn; |
|
} else |
|
if (!ecs_os_strcmp(token, TOK_OUT)) { |
|
*inout_kind_out = EcsOut; |
|
} else |
|
if (!ecs_os_strcmp(token, TOK_INOUT)) { |
|
*inout_kind_out = EcsInOut; |
|
} else if (!ecs_os_strcmp(token, TOK_INOUT_NONE)) { |
|
*inout_kind_out = EcsInOutNone; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
|
|
if (ptr[0] != TOK_BRACKET_CLOSE) { |
|
ecs_parser_error(name, sig, column, "expected ]"); |
|
return NULL; |
|
} |
|
|
|
return ptr + 1; |
|
} |
|
|
|
static |
|
uint8_t flecs_parse_set_token( |
|
const char *token) |
|
{ |
|
if (!ecs_os_strcmp(token, TOK_SELF)) { |
|
return EcsSelf; |
|
} else if (!ecs_os_strcmp(token, TOK_UP)) { |
|
return EcsUp; |
|
} else if (!ecs_os_strcmp(token, TOK_DOWN)) { |
|
return EcsDown; |
|
} else if (!ecs_os_strcmp(token, TOK_CASCADE)) { |
|
return EcsCascade; |
|
} else if (!ecs_os_strcmp(token, TOK_DESC)) { |
|
return EcsDesc; |
|
} else if (!ecs_os_strcmp(token, TOK_PARENT)) { |
|
return EcsParent; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
static |
|
const char* flecs_parse_term_flags( |
|
const ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
int64_t column, |
|
const char *ptr, |
|
char *token, |
|
ecs_term_id_t *id, |
|
char tok_end) |
|
{ |
|
char token_buf[ECS_MAX_TOKEN_SIZE] = {0}; |
|
if (!token) { |
|
token = token_buf; |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
} |
|
|
|
do { |
|
uint8_t tok = flecs_parse_set_token(token); |
|
if (!tok) { |
|
ecs_parser_error(name, expr, column, |
|
"invalid set token '%s'", token); |
|
return NULL; |
|
} |
|
|
|
if (id->flags & tok) { |
|
ecs_parser_error(name, expr, column, |
|
"duplicate set token '%s'", token); |
|
return NULL; |
|
} |
|
|
|
id->flags |= tok; |
|
|
|
if (ptr[0] == TOK_PAREN_OPEN) { |
|
ptr ++; |
|
|
|
/* Relationship (overrides IsA default) */ |
|
if (!isdigit(ptr[0]) && flecs_valid_token_start_char(ptr[0])) { |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
id->trav = ecs_lookup_fullpath(world, token); |
|
if (!id->trav) { |
|
ecs_parser_error(name, expr, column, |
|
"unresolved identifier '%s'", token); |
|
return NULL; |
|
} |
|
|
|
if (ptr[0] == TOK_AND) { |
|
ptr = ecs_parse_ws(ptr + 1); |
|
} else if (ptr[0] != TOK_PAREN_CLOSE) { |
|
ecs_parser_error(name, expr, column, |
|
"expected ',' or ')'"); |
|
return NULL; |
|
} |
|
} |
|
|
|
if (ptr[0] != TOK_PAREN_CLOSE) { |
|
ecs_parser_error(name, expr, column, "expected ')', got '%c'", |
|
ptr[0]); |
|
return NULL; |
|
} else { |
|
ptr = ecs_parse_ws(ptr + 1); |
|
if (ptr[0] != tok_end && ptr[0] != TOK_AND && ptr[0] != 0) { |
|
ecs_parser_error(name, expr, column, |
|
"expected end of set expr"); |
|
return NULL; |
|
} |
|
} |
|
} |
|
|
|
/* Next token in set expression */ |
|
if (ptr[0] == TOK_BITWISE_OR) { |
|
ptr ++; |
|
if (flecs_valid_token_start_char(ptr[0])) { |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
} |
|
|
|
/* End of set expression */ |
|
} else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { |
|
break; |
|
} |
|
} while (true); |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* flecs_parse_arguments( |
|
const ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
int64_t column, |
|
const char *ptr, |
|
char *token, |
|
ecs_term_t *term, |
|
ecs_oper_kind_t *extra_oper, |
|
ecs_term_id_t *extra_args) |
|
{ |
|
(void)column; |
|
|
|
int32_t i, arg = 0; |
|
|
|
if (extra_args) { |
|
ecs_os_memset_n(extra_args, 0, ecs_term_id_t, ECS_PARSER_MAX_ARGS); |
|
} |
|
|
|
if (!term) { |
|
arg = 2; |
|
} |
|
|
|
do { |
|
if (flecs_valid_token_start_char(ptr[0])) { |
|
if ((arg == ECS_PARSER_MAX_ARGS) || (!extra_args && arg == 2)) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"too many arguments in term"); |
|
goto error; |
|
} |
|
|
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
|
|
ecs_term_id_t *term_id = NULL; |
|
if (arg == 0) { |
|
term_id = &term->src; |
|
} else if (arg == 1) { |
|
term_id = &term->second; |
|
} else { |
|
term_id = &extra_args[arg - 2]; |
|
} |
|
|
|
/* If token is a colon, the token is an identifier followed by a |
|
* set expression. */ |
|
if (ptr[0] == TOK_COLON) { |
|
if (flecs_parse_identifier(token, term_id)) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid identifier '%s'", token); |
|
goto error; |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr + 1); |
|
ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, |
|
NULL, term_id, TOK_PAREN_CLOSE); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
/* Check for term flags */ |
|
} else if (!ecs_os_strcmp(token, TOK_CASCADE) || |
|
!ecs_os_strcmp(token, TOK_DESC) || |
|
!ecs_os_strcmp(token, TOK_SELF) || |
|
!ecs_os_strcmp(token, TOK_UP) || |
|
!ecs_os_strcmp(token, TOK_DOWN) || |
|
!(ecs_os_strcmp(token, TOK_PARENT))) |
|
{ |
|
ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, |
|
token, term_id, TOK_PAREN_CLOSE); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
/* Regular identifier */ |
|
} else if (flecs_parse_identifier(token, term_id)) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid identifier '%s'", token); |
|
goto error; |
|
} |
|
|
|
if (ptr[0] == TOK_AND) { |
|
if (extra_oper && *extra_oper != EcsAnd) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"cannot mix ',' and '||' in term arguments"); |
|
goto error; |
|
} |
|
ptr = ecs_parse_ws_eol(ptr + 1); |
|
|
|
if (term) { |
|
term->id_flags = ECS_PAIR; |
|
} |
|
|
|
} else if (ptr[0] == TOK_PAREN_CLOSE) { |
|
ptr = ecs_parse_ws(ptr + 1); |
|
break; |
|
|
|
} else if (extra_oper && ptr[0] == TOK_OR[0] && ptr[1] == TOK_OR[1]){ |
|
if (arg >= 2 && *extra_oper != EcsOr) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"cannot mix ',' and '||' in term arguments"); |
|
goto error; |
|
} |
|
|
|
*extra_oper = EcsOr; |
|
ptr = ecs_parse_ws_eol(ptr + 2); |
|
|
|
if (term) { |
|
term->id_flags = ECS_PAIR; |
|
} |
|
} else { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected ',' or ')'"); |
|
goto error; |
|
} |
|
|
|
} else { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected identifier or set expression"); |
|
goto error; |
|
} |
|
|
|
arg ++; |
|
|
|
} while (true); |
|
|
|
return ptr; |
|
error: |
|
if (term && term->src.name) { |
|
ecs_os_free(ECS_CONST_CAST(char*, term->src.name)); |
|
term->src.name = NULL; |
|
} |
|
if (term && term->second.name) { |
|
ecs_os_free(ECS_CONST_CAST(char*, term->second.name)); |
|
term->second.name = NULL; |
|
} |
|
if (extra_args) { |
|
for (i = 2; i < arg + 1; i ++) { |
|
if (extra_args[i - 2].name) { |
|
ecs_os_free(ECS_CONST_CAST(char*, extra_args[i - 2].name)); |
|
extra_args[i - 2].name = NULL; |
|
} |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
static |
|
void flecs_parser_unexpected_char( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
char ch) |
|
{ |
|
if (ch && (ch != '\n')) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unexpected character '%c'", ch); |
|
} else { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unexpected end of term"); |
|
} |
|
} |
|
|
|
static |
|
const char* flecs_parse_term( |
|
const ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
ecs_term_t *term_out, |
|
ecs_oper_kind_t *extra_oper, |
|
ecs_term_id_t *extra_args) |
|
{ |
|
const char *ptr = expr; |
|
char token[ECS_MAX_TOKEN_SIZE] = {0}; |
|
ecs_term_t term = { .move = true /* parser never owns resources */ }; |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
|
|
/* Inout specifiers always come first */ |
|
if (ptr[0] == TOK_BRACKET_OPEN) { |
|
ptr = flecs_parse_annotation(name, expr, (ptr - expr), ptr + 1, &term.inout); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
ptr = ecs_parse_ws_eol(ptr); |
|
} |
|
|
|
if (flecs_valid_operator_char(ptr[0])) { |
|
term.oper = flecs_parse_operator(ptr[0]); |
|
ptr = ecs_parse_ws(ptr + 1); |
|
} |
|
|
|
/* If next token is the start of an identifier, it could be either a type |
|
* role, source or component identifier */ |
|
if (flecs_valid_identifier_start_char(ptr[0])) { |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
/* Is token a type role? */ |
|
if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { |
|
ptr ++; |
|
goto flecs_parse_role; |
|
} |
|
|
|
/* Is token a predicate? */ |
|
if (ptr[0] == TOK_PAREN_OPEN) { |
|
goto parse_predicate; |
|
} |
|
|
|
/* Next token must be a predicate */ |
|
goto parse_predicate; |
|
|
|
/* Pair with implicit subject */ |
|
} else if (ptr[0] == TOK_PAREN_OPEN) { |
|
goto parse_pair; |
|
|
|
/* Open query scope */ |
|
} else if (ptr[0] == TOK_SCOPE_OPEN) { |
|
term.first.id = EcsScopeOpen; |
|
term.src.id = 0; |
|
term.src.flags = EcsIsEntity; |
|
term.inout = EcsInOutNone; |
|
goto parse_done; |
|
|
|
/* Close query scope */ |
|
} else if (ptr[0] == TOK_SCOPE_CLOSE) { |
|
term.first.id = EcsScopeClose; |
|
term.src.id = 0; |
|
term.src.flags = EcsIsEntity; |
|
term.inout = EcsInOutNone; |
|
ptr = ecs_parse_ws(ptr + 1); |
|
goto parse_done; |
|
|
|
/* Nothing else expected here */ |
|
} else { |
|
flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); |
|
goto error; |
|
} |
|
|
|
flecs_parse_role: |
|
term.id_flags = flecs_parse_role(name, expr, (ptr - expr), token); |
|
if (!term.id_flags) { |
|
goto error; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
|
|
/* If next token is the source token, this is an empty source */ |
|
if (flecs_valid_token_start_char(ptr[0])) { |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
/* If not, it's a predicate */ |
|
goto parse_predicate; |
|
|
|
} else if (ptr[0] == TOK_PAREN_OPEN) { |
|
goto parse_pair; |
|
} else { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected identifier after role"); |
|
goto error; |
|
} |
|
|
|
parse_predicate: |
|
if (flecs_parse_identifier(token, &term.first)) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid identifier '%s'", token); |
|
goto error; |
|
} |
|
|
|
/* Set expression */ |
|
if (ptr[0] == TOK_COLON) { |
|
ptr = ecs_parse_ws(ptr + 1); |
|
ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, |
|
&term.first, TOK_COLON); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
|
|
if (ptr[0] == TOK_AND || !ptr[0]) { |
|
goto parse_done; |
|
} |
|
|
|
if (ptr[0] != TOK_COLON) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unexpected token '%c' after predicate set expression", ptr[0]); |
|
goto error; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr + 1); |
|
} else if (!ecs_os_strncmp(ptr, TOK_EQ, 2)) { |
|
ptr = ecs_parse_ws(ptr + 2); |
|
goto parse_eq; |
|
} else if (!ecs_os_strncmp(ptr, TOK_NEQ, 2)) { |
|
ptr = ecs_parse_ws(ptr + 2); |
|
goto parse_neq; |
|
} else if (!ecs_os_strncmp(ptr, TOK_MATCH, 2)) { |
|
ptr = ecs_parse_ws(ptr + 2); |
|
goto parse_match; |
|
} else { |
|
ptr = ecs_parse_ws(ptr); |
|
} |
|
|
|
if (ptr[0] == TOK_PAREN_OPEN) { |
|
ptr = ecs_parse_ws_eol(ptr + 1); |
|
if (ptr[0] == TOK_PAREN_CLOSE) { |
|
term.src.flags = EcsIsEntity; |
|
term.src.id = 0; |
|
ptr ++; |
|
ptr = ecs_parse_ws(ptr); |
|
} else { |
|
ptr = flecs_parse_arguments(world, name, expr, (ptr - expr), ptr, |
|
token, &term, extra_oper, extra_args); |
|
} |
|
|
|
goto parse_done; |
|
} |
|
|
|
goto parse_done; |
|
|
|
parse_eq: |
|
term.src = term.first; |
|
term.first = (ecs_term_id_t){0}; |
|
term.first.id = EcsPredEq; |
|
goto parse_right_operand; |
|
|
|
parse_neq: |
|
term.src = term.first; |
|
term.first = (ecs_term_id_t){0}; |
|
term.first.id = EcsPredEq; |
|
if (term.oper != EcsAnd) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid operator combination"); |
|
goto error; |
|
} |
|
term.oper = EcsNot; |
|
goto parse_right_operand; |
|
|
|
parse_match: |
|
term.src = term.first; |
|
term.first = (ecs_term_id_t){0}; |
|
term.first.id = EcsPredMatch; |
|
goto parse_right_operand; |
|
|
|
parse_right_operand: |
|
if (flecs_valid_token_start_char(ptr[0])) { |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
if (term.first.id == EcsPredMatch) { |
|
if (token[0] == '"' && token[1] == '!') { |
|
term.oper = EcsNot; |
|
} |
|
} |
|
|
|
if (flecs_parse_identifier(token, &term.second)) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid identifier '%s'", token); |
|
goto error; |
|
} |
|
|
|
term.src.flags &= ~EcsTraverseFlags; |
|
term.src.flags |= EcsSelf; |
|
term.inout = EcsInOutNone; |
|
} else { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected identifier"); |
|
goto error; |
|
} |
|
goto parse_done; |
|
parse_pair: |
|
ptr = ecs_parse_identifier(name, expr, ptr + 1, token); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
if (ptr[0] == TOK_COLON) { |
|
ptr = ecs_parse_ws(ptr + 1); |
|
ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, |
|
NULL, &term.first, TOK_PAREN_CLOSE); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
} |
|
|
|
if (ptr[0] == TOK_AND) { |
|
ptr = ecs_parse_ws(ptr + 1); |
|
if (ptr[0] == TOK_PAREN_CLOSE) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected identifier for second element of pair"); |
|
goto error; |
|
} |
|
|
|
term.src.id = EcsThis; |
|
term.src.flags |= EcsIsVariable; |
|
goto parse_pair_predicate; |
|
} else if (ptr[0] == TOK_PAREN_CLOSE) { |
|
term.src.id = EcsThis; |
|
term.src.flags |= EcsIsVariable; |
|
goto parse_pair_predicate; |
|
} else { |
|
flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); |
|
goto error; |
|
} |
|
|
|
parse_pair_predicate: |
|
if (flecs_parse_identifier(token, &term.first)) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid identifier '%s'", token); |
|
goto error; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
if (flecs_valid_token_start_char(ptr[0])) { |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
if (ptr[0] == TOK_COLON) { |
|
ptr = ecs_parse_ws(ptr + 1); |
|
ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, |
|
NULL, &term.second, TOK_PAREN_CLOSE); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
} |
|
|
|
if (ptr[0] == TOK_PAREN_CLOSE || ptr[0] == TOK_AND || ptr[0] == TOK_OR[0]) { |
|
goto parse_pair_object; |
|
} else { |
|
flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); |
|
goto error; |
|
} |
|
} else if (ptr[0] == TOK_PAREN_CLOSE) { |
|
/* No object */ |
|
ptr ++; |
|
goto parse_done; |
|
} else { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected pair object or ')'"); |
|
goto error; |
|
} |
|
|
|
parse_pair_object: |
|
if (flecs_parse_identifier(token, &term.second)) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid identifier '%s'", token); |
|
goto error; |
|
} |
|
|
|
if (term.id_flags == 0) { |
|
term.id_flags = ECS_PAIR; |
|
} |
|
|
|
if (ptr[0] == TOK_AND) { |
|
ptr = ecs_parse_ws(ptr + 1); |
|
ptr = flecs_parse_arguments(world, name, expr, (ptr - expr), ptr, token, |
|
NULL, extra_oper, extra_args); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
} else if (extra_oper && ptr[0] == TOK_OR[0] && ptr[1] == TOK_OR[1]) { |
|
ptr = ecs_parse_ws_eol(ptr + 2); |
|
*extra_oper = EcsOr; |
|
ptr = flecs_parse_arguments(world, name, expr, (ptr - expr), ptr, token, |
|
NULL, extra_oper, extra_args); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
} else { |
|
ptr ++; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
goto parse_done; |
|
|
|
parse_done: |
|
*term_out = term; |
|
return ptr; |
|
|
|
error: |
|
ecs_term_fini(&term); |
|
*term_out = (ecs_term_t){0}; |
|
return NULL; |
|
} |
|
|
|
static |
|
bool flecs_is_valid_end_of_term( |
|
const char *ptr) |
|
{ |
|
if ((ptr[0] == TOK_AND) || /* another term with And operator */ |
|
(ptr[0] == TOK_OR[0]) || /* another term with Or operator */ |
|
(ptr[0] == '\n') || /* newlines are valid */ |
|
(ptr[0] == '\0') || /* end of string */ |
|
(ptr[0] == '/') || /* comment (in plecs) */ |
|
(ptr[0] == '{') || /* scope (in plecs) */ |
|
(ptr[0] == '}') || |
|
(ptr[0] == '[') || /* collection scope (in plecs) */ |
|
(ptr[0] == ']') || |
|
(ptr[0] == ':') || /* inheritance (in plecs) */ |
|
(ptr[0] == '=')) /* assignment (in plecs) */ |
|
{ |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
char* ecs_parse_term( |
|
const ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
ecs_term_t *term, |
|
ecs_oper_kind_t *extra_oper, |
|
ecs_term_id_t *extra_args, |
|
bool allow_newline) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (extra_oper) { |
|
*extra_oper = EcsAnd; |
|
} |
|
|
|
ecs_term_id_t *src = &term->src; |
|
|
|
if (ptr != expr) { |
|
if (ptr[0]) { |
|
if (ptr[0] == ',') { |
|
ptr ++; |
|
} else if (ptr[0] == '|') { |
|
ptr += 2; |
|
} else if (ptr[0] == '{') { |
|
ptr ++; |
|
} else if (ptr[0] == '}') { |
|
/* nothing to be done */ |
|
} else { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid preceding token"); |
|
} |
|
} |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
if (!ptr[0]) { |
|
*term = (ecs_term_t){0}; |
|
return ECS_CONST_CAST(char*, ptr); |
|
} |
|
|
|
if (ptr == expr && !strcmp(expr, "0")) { |
|
return ECS_CONST_CAST(char*, &ptr[1]); |
|
} |
|
|
|
/* Parse next element */ |
|
ptr = flecs_parse_term(world, name, ptr, term, extra_oper, extra_args); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
/* Check for $() notation */ |
|
if (term->first.name && !ecs_os_strcmp(term->first.name, "$")) { |
|
if (term->src.name) { |
|
/* Safe, parser owns name */ |
|
ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); |
|
|
|
term->first = term->src; |
|
|
|
if (term->second.name) { |
|
term->src = term->second; |
|
} else { |
|
term->src.id = EcsThis; |
|
term->src.name = NULL; |
|
term->src.flags |= EcsIsVariable; |
|
} |
|
|
|
const char *var_name = strrchr(term->first.name, '.'); |
|
if (var_name) { |
|
var_name ++; |
|
} else { |
|
var_name = term->first.name; |
|
} |
|
|
|
term->second.name = ecs_os_strdup(var_name); |
|
term->second.flags |= EcsIsVariable; |
|
} |
|
} |
|
|
|
/* Post-parse consistency checks */ |
|
|
|
/* If next token is OR, term is part of an OR expression */ |
|
if (!ecs_os_strncmp(ptr, TOK_OR, 2)) { |
|
/* An OR operator must always follow an AND or another OR */ |
|
if (term->oper != EcsAnd) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"cannot combine || with other operators"); |
|
goto error; |
|
} |
|
|
|
term->oper = EcsOr; |
|
} |
|
|
|
/* Term must either end in end of expression, AND or OR token */ |
|
if (!flecs_is_valid_end_of_term(ptr)) { |
|
if (!flecs_isident(ptr[0]) || ((ptr != expr) && (ptr[-1] != ' '))) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected end of expression or next term"); |
|
goto error; |
|
} |
|
} |
|
|
|
/* If the term just contained a 0, the expression has nothing. Ensure |
|
* that after the 0 nothing else follows */ |
|
if (term->first.name && !ecs_os_strcmp(term->first.name, "0")) { |
|
if (ptr[0]) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unexpected term after 0"); |
|
goto error; |
|
} |
|
|
|
if (src->flags != 0) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid combination of 0 with non-default subject"); |
|
goto error; |
|
} |
|
|
|
src->flags = EcsIsEntity; |
|
src->id = 0; |
|
/* Safe, parser owns string */ |
|
ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); |
|
term->first.name = NULL; |
|
} |
|
|
|
/* Cannot combine EcsIsEntity/0 with operators other than AND */ |
|
if (term->oper != EcsAnd && ecs_term_match_0(term)) { |
|
if (term->first.id != EcsScopeOpen && term->first.id != EcsScopeClose) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"invalid operator for empty source"); |
|
goto error; |
|
} |
|
} |
|
|
|
/* Automatically assign This if entity is not assigned and the set is |
|
* nothing */ |
|
if (!(src->flags & EcsIsEntity)) { |
|
if (!src->name) { |
|
if (!src->id) { |
|
src->id = EcsThis; |
|
src->flags |= EcsIsVariable; |
|
} |
|
} |
|
} |
|
|
|
if (src->name && !ecs_os_strcmp(src->name, "0")) { |
|
src->id = 0; |
|
src->flags = EcsIsEntity; |
|
} |
|
|
|
/* Process role */ |
|
if (term->id_flags == ECS_AND) { |
|
term->oper = EcsAndFrom; |
|
term->id_flags = 0; |
|
} else if (term->id_flags == ECS_OR) { |
|
term->oper = EcsOrFrom; |
|
term->id_flags = 0; |
|
} else if (term->id_flags == ECS_NOT) { |
|
term->oper = EcsNotFrom; |
|
term->id_flags = 0; |
|
} |
|
|
|
if (allow_newline) { |
|
ptr = ecs_parse_ws_eol(ptr); |
|
} else { |
|
ptr = ecs_parse_ws(ptr); |
|
} |
|
|
|
return ECS_CONST_CAST(char*, ptr); |
|
error: |
|
if (term) { |
|
ecs_term_fini(term); |
|
} |
|
return NULL; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/plecs.c |
|
* @brief Plecs addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_PLECS |
|
|
|
ECS_COMPONENT_DECLARE(EcsScript); |
|
|
|
#include <ctype.h> |
|
|
|
#define TOK_NEWLINE '\n' |
|
#define TOK_USING "using" |
|
#define TOK_MODULE "module" |
|
#define TOK_WITH "with" |
|
#define TOK_CONST "const" |
|
#define TOK_PROP "prop" |
|
#define TOK_ASSEMBLY "assembly" |
|
|
|
#define STACK_MAX_SIZE (64) |
|
|
|
typedef struct { |
|
ecs_value_t value; |
|
bool owned; |
|
} plecs_with_value_t; |
|
|
|
typedef struct { |
|
const char *name; |
|
const char *code; |
|
|
|
ecs_entity_t last_predicate; |
|
ecs_entity_t last_subject; |
|
ecs_entity_t last_object; |
|
|
|
ecs_id_t last_assign_id; |
|
ecs_entity_t assign_to; |
|
|
|
ecs_entity_t scope[STACK_MAX_SIZE]; |
|
ecs_entity_t default_scope_type[STACK_MAX_SIZE]; |
|
ecs_entity_t with[STACK_MAX_SIZE]; |
|
ecs_entity_t using[STACK_MAX_SIZE]; |
|
int32_t with_frames[STACK_MAX_SIZE]; |
|
plecs_with_value_t with_value_frames[STACK_MAX_SIZE]; |
|
int32_t using_frames[STACK_MAX_SIZE]; |
|
int32_t sp; |
|
int32_t with_frame; |
|
int32_t using_frame; |
|
ecs_entity_t global_with; |
|
ecs_entity_t assembly; |
|
const char *assembly_start, *assembly_stop; |
|
|
|
char *annot[STACK_MAX_SIZE]; |
|
int32_t annot_count; |
|
|
|
ecs_vars_t vars; |
|
char var_name[256]; |
|
ecs_entity_t var_type; |
|
|
|
bool with_stmt; |
|
bool scope_assign_stmt; |
|
bool assign_stmt; |
|
bool assembly_stmt; |
|
bool assembly_instance; |
|
bool isa_stmt; |
|
bool decl_stmt; |
|
bool decl_type; |
|
bool var_stmt; |
|
bool var_is_prop; |
|
bool is_module; |
|
|
|
int32_t errors; |
|
} plecs_state_t; |
|
|
|
static |
|
int flecs_plecs_parse( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
ecs_vars_t *vars, |
|
ecs_entity_t script, |
|
ecs_entity_t instance); |
|
|
|
static void flecs_dtor_script(EcsScript *ptr) { |
|
ecs_os_free(ptr->script); |
|
ecs_vec_fini_t(NULL, &ptr->using_, ecs_entity_t); |
|
|
|
int i, count = ptr->prop_defaults.count; |
|
ecs_value_t *values = ptr->prop_defaults.array; |
|
for (i = 0; i < count; i ++) { |
|
ecs_value_free(ptr->world, values[i].type, values[i].ptr); |
|
} |
|
|
|
ecs_vec_fini_t(NULL, &ptr->prop_defaults, ecs_value_t); |
|
} |
|
|
|
static |
|
ECS_MOVE(EcsScript, dst, src, { |
|
flecs_dtor_script(dst); |
|
dst->using_ = src->using_; |
|
dst->prop_defaults = src->prop_defaults; |
|
dst->script = src->script; |
|
dst->world = src->world; |
|
ecs_os_zeromem(&src->using_); |
|
ecs_os_zeromem(&src->prop_defaults); |
|
src->script = NULL; |
|
src->world = NULL; |
|
}) |
|
|
|
static |
|
ECS_DTOR(EcsScript, ptr, { |
|
flecs_dtor_script(ptr); |
|
}) |
|
|
|
/* Assembly ctor to initialize with default property values */ |
|
static |
|
void flecs_assembly_ctor( |
|
void *ptr, |
|
int32_t count, |
|
const ecs_type_info_t *ti) |
|
{ |
|
ecs_world_t *world = ti->hooks.ctx; |
|
ecs_entity_t assembly = ti->component; |
|
const EcsStruct *st = ecs_get(world, assembly, EcsStruct); |
|
|
|
if (!st) { |
|
ecs_err("assembly '%s' is not a struct, cannot construct", ti->name); |
|
return; |
|
} |
|
|
|
const EcsScript *script = ecs_get(world, assembly, EcsScript); |
|
if (!script) { |
|
ecs_err("assembly '%s' is not a script, cannot construct", ti->name); |
|
return; |
|
} |
|
|
|
if (st->members.count != script->prop_defaults.count) { |
|
ecs_err("number of props (%d) of assembly '%s' does not match members" |
|
" (%d), cannot construct", script->prop_defaults.count, |
|
ti->name, st->members.count); |
|
return; |
|
} |
|
|
|
const ecs_member_t *members = st->members.array; |
|
int32_t i, m, member_count = st->members.count; |
|
ecs_value_t *values = script->prop_defaults.array; |
|
for (m = 0; m < member_count; m ++) { |
|
const ecs_member_t *member = &members[m]; |
|
ecs_value_t *value = &values[m]; |
|
const ecs_type_info_t *mti = ecs_get_type_info(world, member->type); |
|
if (!mti) { |
|
ecs_err("failed to get type info for prop '%s' of assembly '%s'", |
|
member->name, ti->name); |
|
return; |
|
} |
|
|
|
for (i = 0; i < count; i ++) { |
|
void *el = ECS_ELEM(ptr, ti->size, i); |
|
ecs_value_copy_w_type_info(world, mti, |
|
ECS_OFFSET(el, member->offset), value->ptr); |
|
} |
|
} |
|
} |
|
|
|
/* Assembly on_set handler to update contents for new property values */ |
|
static |
|
void flecs_assembly_on_set( |
|
ecs_iter_t *it) |
|
{ |
|
if (it->table->flags & EcsTableIsPrefab) { |
|
/* Don't instantiate assemblies for prefabs */ |
|
return; |
|
} |
|
|
|
ecs_world_t *world = it->world; |
|
ecs_entity_t assembly = ecs_field_id(it, 1); |
|
const char *name = ecs_get_name(world, assembly); |
|
ecs_record_t *r = ecs_record_find(world, assembly); |
|
|
|
const EcsComponent *ct = ecs_record_get(world, r, EcsComponent); |
|
ecs_get(world, assembly, EcsComponent); |
|
if (!ct) { |
|
ecs_err("assembly '%s' is not a component", name); |
|
return; |
|
} |
|
|
|
const EcsStruct *st = ecs_record_get(world, r, EcsStruct); |
|
if (!st) { |
|
ecs_err("assembly '%s' is not a struct", name); |
|
return; |
|
} |
|
|
|
const EcsScript *script = ecs_record_get(world, r, EcsScript); |
|
if (!script) { |
|
ecs_err("assembly '%s' is missing a script", name); |
|
return; |
|
} |
|
|
|
void *data = ecs_field_w_size(it, flecs_ito(size_t, ct->size), 1); |
|
|
|
int32_t i, m; |
|
for (i = 0; i < it->count; i ++) { |
|
/* Create variables to hold assembly properties */ |
|
ecs_vars_t vars = {0}; |
|
ecs_vars_init(world, &vars); |
|
|
|
/* Populate properties from assembly members */ |
|
const ecs_member_t *members = st->members.array; |
|
for (m = 0; m < st->members.count; m ++) { |
|
const ecs_member_t *member = &members[m]; |
|
|
|
ecs_value_t v = {0}; /* Prevent allocating value */ |
|
ecs_expr_var_t *var = ecs_vars_declare_w_value( |
|
&vars, member->name, &v); |
|
if (var == NULL) { |
|
ecs_err("could not create prop '%s' for assembly '%s'", |
|
member->name, name); |
|
break; |
|
} |
|
|
|
/* Assign assembly property from assembly instance */ |
|
var->value.type = member->type; |
|
var->value.ptr = ECS_OFFSET(data, member->offset); |
|
var->owned = false; |
|
} |
|
|
|
/* Populate $this variable with instance entity */ |
|
ecs_entity_t instance = it->entities[i]; |
|
ecs_value_t v = {0}; |
|
ecs_expr_var_t *var = ecs_vars_declare_w_value( |
|
&vars, "this", &v); |
|
var->value.type = ecs_id(ecs_entity_t); |
|
var->value.ptr = &instance; |
|
var->owned = false; |
|
|
|
/* Update script with new code/properties */ |
|
ecs_script_update(world, assembly, instance, script->script, &vars); |
|
ecs_vars_fini(&vars); |
|
|
|
if (ecs_record_has_id(world, r, EcsFlatten)) { |
|
ecs_flatten(it->real_world, ecs_childof(instance), NULL); |
|
} |
|
|
|
data = ECS_OFFSET(data, ct->size); |
|
} |
|
} |
|
|
|
/* Delete contents of assembly instance */ |
|
static |
|
void flecs_assembly_on_remove( |
|
ecs_iter_t *it) |
|
{ |
|
int32_t i; |
|
for (i = 0; i < it->count; i ++) { |
|
ecs_entity_t instance = it->entities[i]; |
|
ecs_script_clear(it->world, 0, instance); |
|
} |
|
} |
|
|
|
/* Set default property values on assembly Script component */ |
|
static |
|
int flecs_assembly_init_defaults( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
ecs_entity_t assembly, |
|
EcsScript *script, |
|
plecs_state_t *state) |
|
{ |
|
const EcsStruct *st = ecs_get(world, assembly, EcsStruct); |
|
int32_t i, count = st->members.count; |
|
const ecs_member_t *members = st->members.array; |
|
|
|
ecs_vec_init_t(NULL, &script->prop_defaults, ecs_value_t, count); |
|
|
|
for (i = 0; i < count; i ++) { |
|
const ecs_member_t *member = &members[i]; |
|
ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, member->name); |
|
if (!var) { |
|
char *assembly_name = ecs_get_fullpath(world, assembly); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing property '%s' for assembly '%s'", |
|
member->name, assembly_name); |
|
ecs_os_free(assembly_name); |
|
return -1; |
|
} |
|
|
|
if (member->type != var->value.type) { |
|
char *assembly_name = ecs_get_fullpath(world, assembly); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"property '%s' for assembly '%s' has mismatching type", |
|
member->name, assembly_name); |
|
ecs_os_free(assembly_name); |
|
return -1; |
|
} |
|
|
|
ecs_value_t *pv = ecs_vec_append_t(NULL, |
|
&script->prop_defaults, ecs_value_t); |
|
pv->type = member->type; |
|
pv->ptr = var->value.ptr; |
|
var->owned = false; /* Transfer ownership */ |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Create new assembly */ |
|
static |
|
int flecs_assembly_create( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
ecs_entity_t assembly, |
|
char *script_code, |
|
plecs_state_t *state) |
|
{ |
|
const EcsStruct *st = ecs_get(world, assembly, EcsStruct); |
|
if (!st || !st->members.count) { |
|
char *assembly_name = ecs_get_fullpath(world, assembly); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"assembly '%s' has no properties", assembly_name); |
|
ecs_os_free(assembly_name); |
|
ecs_os_free(script_code); |
|
return -1; |
|
} |
|
|
|
ecs_add_id(world, assembly, EcsAlwaysOverride); |
|
|
|
EcsScript *script = ecs_get_mut(world, assembly, EcsScript); |
|
flecs_dtor_script(script); |
|
script->world = world; |
|
script->script = script_code; |
|
ecs_vec_reset_t(NULL, &script->using_, ecs_entity_t); |
|
|
|
ecs_entity_t scope = ecs_get_scope(world); |
|
if (scope && (scope = ecs_get_target(world, scope, EcsChildOf, 0))) { |
|
ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = scope; |
|
} |
|
|
|
int i, count = state->using_frame; |
|
for (i = 0; i < count; i ++) { |
|
ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = |
|
state->using[i]; |
|
} |
|
|
|
if (flecs_assembly_init_defaults( |
|
world, name, expr, ptr, assembly, script, state)) |
|
{ |
|
return -1; |
|
} |
|
|
|
ecs_modified(world, assembly, EcsScript); |
|
|
|
ecs_set_hooks_id(world, assembly, &(ecs_type_hooks_t) { |
|
.ctor = flecs_assembly_ctor, |
|
.on_set = flecs_assembly_on_set, |
|
.on_remove = flecs_assembly_on_remove, |
|
.ctx = world |
|
}); |
|
|
|
return 0; |
|
} |
|
|
|
/* Parser */ |
|
|
|
static |
|
bool plecs_is_newline_comment( |
|
const char *ptr) |
|
{ |
|
if (ptr[0] == '/' && ptr[1] == '/') { |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
static |
|
const char* plecs_parse_fluff( |
|
const char *ptr) |
|
{ |
|
do { |
|
/* Skip whitespaces before checking for a comment */ |
|
ptr = ecs_parse_ws(ptr); |
|
|
|
/* Newline comment, skip until newline character */ |
|
if (plecs_is_newline_comment(ptr)) { |
|
ptr += 2; |
|
|
|
while (ptr[0] && ptr[0] != TOK_NEWLINE) { |
|
ptr ++; |
|
} |
|
} |
|
|
|
/* If a newline character is found, skip it */ |
|
if (ptr[0] == TOK_NEWLINE) { |
|
ptr ++; |
|
} |
|
|
|
} while (isspace(ptr[0]) || plecs_is_newline_comment(ptr)); |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
ecs_entity_t plecs_lookup( |
|
const ecs_world_t *world, |
|
const char *path, |
|
plecs_state_t *state, |
|
ecs_entity_t rel, |
|
bool is_subject) |
|
{ |
|
ecs_entity_t e = 0; |
|
|
|
if (!is_subject) { |
|
ecs_entity_t oneof = 0; |
|
if (rel) { |
|
if (ecs_has_id(world, rel, EcsOneOf)) { |
|
oneof = rel; |
|
} else { |
|
oneof = ecs_get_target(world, rel, EcsOneOf, 0); |
|
} |
|
if (oneof) { |
|
return ecs_lookup_path_w_sep( |
|
world, oneof, path, NULL, NULL, false); |
|
} |
|
} |
|
int using_scope = state->using_frame - 1; |
|
for (; using_scope >= 0; using_scope--) { |
|
e = ecs_lookup_path_w_sep( |
|
world, state->using[using_scope], path, NULL, NULL, false); |
|
if (e) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (!e) { |
|
e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); |
|
} |
|
|
|
return e; |
|
} |
|
|
|
/* Lookup action used for deserializing entity refs in component values */ |
|
static |
|
ecs_entity_t plecs_lookup_action( |
|
const ecs_world_t *world, |
|
const char *path, |
|
void *ctx) |
|
{ |
|
return plecs_lookup(world, path, ctx, 0, false); |
|
} |
|
|
|
static |
|
void plecs_apply_with_frame( |
|
ecs_world_t *world, |
|
plecs_state_t *state, |
|
ecs_entity_t e) |
|
{ |
|
int32_t i, frame_count = state->with_frames[state->sp]; |
|
for (i = 0; i < frame_count; i ++) { |
|
ecs_id_t id = state->with[i]; |
|
plecs_with_value_t *v = &state->with_value_frames[i]; |
|
if (v->value.type) { |
|
void *ptr = ecs_get_mut_id(world, e, id); |
|
ecs_value_copy(world, v->value.type, ptr, v->value.ptr); |
|
ecs_modified_id(world, e, id); |
|
} else { |
|
ecs_add_id(world, e, id); |
|
} |
|
} |
|
} |
|
|
|
static |
|
ecs_entity_t plecs_ensure_entity( |
|
ecs_world_t *world, |
|
plecs_state_t *state, |
|
const char *path, |
|
ecs_entity_t rel, |
|
bool is_subject) |
|
{ |
|
if (!path) { |
|
return 0; |
|
} |
|
|
|
ecs_entity_t e = 0; |
|
bool is_anonymous = !ecs_os_strcmp(path, "_"); |
|
bool is_new = false; |
|
if (is_anonymous) { |
|
path = NULL; |
|
e = ecs_new_id(world); |
|
is_new = true; |
|
} |
|
|
|
if (!e) { |
|
e = plecs_lookup(world, path, state, rel, is_subject); |
|
} |
|
|
|
if (!e) { |
|
is_new = true; |
|
if (rel && flecs_get_oneof(world, rel)) { |
|
/* If relationship has oneof and entity was not found, don't proceed |
|
* with creating an entity as this can cause asserts later on */ |
|
char *relstr = ecs_get_fullpath(world, rel); |
|
ecs_parser_error(state->name, 0, 0, |
|
"invalid identifier '%s' for relationship '%s'", path, relstr); |
|
ecs_os_free(relstr); |
|
return 0; |
|
} |
|
|
|
ecs_entity_t prev_scope = 0; |
|
ecs_entity_t prev_with = 0; |
|
if (!is_subject) { |
|
/* Don't apply scope/with for non-subject entities */ |
|
prev_scope = ecs_set_scope(world, 0); |
|
prev_with = ecs_set_with(world, 0); |
|
} |
|
|
|
e = ecs_add_path(world, e, 0, path); |
|
ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (prev_scope) { |
|
ecs_set_scope(world, prev_scope); |
|
} |
|
if (prev_with) { |
|
ecs_set_with(world, prev_with); |
|
} |
|
} else { |
|
/* If entity exists, make sure it gets the right scope and with */ |
|
if (is_subject) { |
|
ecs_entity_t scope = ecs_get_scope(world); |
|
if (scope) { |
|
ecs_add_pair(world, e, EcsChildOf, scope); |
|
} |
|
|
|
ecs_entity_t with = ecs_get_with(world); |
|
if (with) { |
|
ecs_add_id(world, e, with); |
|
} |
|
} |
|
} |
|
|
|
if (is_new) { |
|
if (state->assembly && !state->assembly_instance) { |
|
ecs_add_id(world, e, EcsPrefab); |
|
} |
|
|
|
if (state->global_with) { |
|
ecs_add_id(world, e, state->global_with); |
|
} |
|
} |
|
|
|
return e; |
|
} |
|
|
|
static |
|
ecs_entity_t plecs_ensure_term_id( |
|
ecs_world_t *world, |
|
plecs_state_t *state, |
|
ecs_term_id_t *term_id, |
|
const char *expr, |
|
int64_t column, |
|
ecs_entity_t pred, |
|
bool is_subject) |
|
{ |
|
ecs_entity_t result = 0; |
|
const char *name = term_id->name; |
|
if (term_id->flags & EcsIsVariable) { |
|
if (name != NULL) { |
|
ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, name); |
|
if (!var) { |
|
ecs_parser_error(name, expr, column, |
|
"unresolved variable '%s'", name); |
|
return 0; |
|
} |
|
if (var->value.type != ecs_id(ecs_entity_t)) { |
|
ecs_parser_error(name, expr, column, |
|
"variable '%s' is not an entity", name); |
|
return 0; |
|
} |
|
result = *(ecs_entity_t*)var->value.ptr; |
|
if (!result) { |
|
ecs_parser_error(name, expr, column, |
|
"variable '%s' is not initialized with valid entity", name); |
|
return 0; |
|
} |
|
} else if (term_id->id) { |
|
result = term_id->id; |
|
} else { |
|
ecs_parser_error(name, expr, column, "invalid variable in term"); |
|
return 0; |
|
} |
|
} else { |
|
result = plecs_ensure_entity(world, state, name, pred, is_subject); |
|
} |
|
return result; |
|
} |
|
|
|
static |
|
bool plecs_pred_is_subj( |
|
ecs_term_t *term, |
|
plecs_state_t *state) |
|
{ |
|
if (term->src.name != NULL) { |
|
return false; |
|
} |
|
if (term->second.name != NULL) { |
|
return false; |
|
} |
|
if (ecs_term_match_0(term)) { |
|
return false; |
|
} |
|
if (state->with_stmt) { |
|
return false; |
|
} |
|
if (state->assign_stmt) { |
|
return false; |
|
} |
|
if (state->isa_stmt) { |
|
return false; |
|
} |
|
if (state->decl_type) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/* Set masks aren't useful in plecs, so translate them back to entity names */ |
|
static |
|
const char* plecs_set_mask_to_name( |
|
ecs_flags32_t flags) |
|
{ |
|
flags &= EcsTraverseFlags; |
|
if (flags == EcsSelf) { |
|
return "self"; |
|
} else if (flags == EcsUp) { |
|
return "up"; |
|
} else if (flags == EcsDown) { |
|
return "down"; |
|
} else if (flags == EcsCascade || flags == (EcsUp|EcsCascade)) { |
|
return "cascade"; |
|
} else if (flags == EcsParent) { |
|
return "parent"; |
|
} |
|
return NULL; |
|
} |
|
|
|
static |
|
char* plecs_trim_annot( |
|
char *annot) |
|
{ |
|
annot = ECS_CONST_CAST(char*, ecs_parse_ws(annot)); |
|
int32_t len = ecs_os_strlen(annot) - 1; |
|
while (isspace(annot[len]) && (len > 0)) { |
|
annot[len] = '\0'; |
|
len --; |
|
} |
|
return annot; |
|
} |
|
|
|
static |
|
void plecs_apply_annotations( |
|
ecs_world_t *world, |
|
ecs_entity_t subj, |
|
plecs_state_t *state) |
|
{ |
|
(void)world; |
|
(void)subj; |
|
(void)state; |
|
#ifdef FLECS_DOC |
|
int32_t i = 0, count = state->annot_count; |
|
for (i = 0; i < count; i ++) { |
|
char *annot = state->annot[i]; |
|
if (!ecs_os_strncmp(annot, "@brief ", 7)) { |
|
annot = plecs_trim_annot(annot + 7); |
|
ecs_doc_set_brief(world, subj, annot); |
|
} else if (!ecs_os_strncmp(annot, "@link ", 6)) { |
|
annot = plecs_trim_annot(annot + 6); |
|
ecs_doc_set_link(world, subj, annot); |
|
} else if (!ecs_os_strncmp(annot, "@name ", 6)) { |
|
annot = plecs_trim_annot(annot + 6); |
|
ecs_doc_set_name(world, subj, annot); |
|
} else if (!ecs_os_strncmp(annot, "@color ", 7)) { |
|
annot = plecs_trim_annot(annot + 7); |
|
ecs_doc_set_color(world, subj, annot); |
|
} |
|
} |
|
#else |
|
ecs_warn("cannot apply annotations, doc addon is missing"); |
|
#endif |
|
} |
|
|
|
static |
|
int plecs_create_term( |
|
ecs_world_t *world, |
|
ecs_term_t *term, |
|
const char *name, |
|
const char *expr, |
|
int64_t column, |
|
plecs_state_t *state) |
|
{ |
|
state->last_subject = 0; |
|
state->last_predicate = 0; |
|
state->last_object = 0; |
|
state->last_assign_id = 0; |
|
|
|
const char *subj_name = term->src.name; |
|
if (!subj_name) { |
|
subj_name = plecs_set_mask_to_name(term->src.flags); |
|
} |
|
|
|
if (!ecs_term_id_is_set(&term->first)) { |
|
ecs_parser_error(name, expr, column, "missing term in expression"); |
|
return -1; |
|
} |
|
|
|
if (state->assign_stmt && !ecs_term_match_this(term)) { |
|
ecs_parser_error(name, expr, column, |
|
"invalid statement in assign statement"); |
|
return -1; |
|
} |
|
|
|
bool pred_as_subj = plecs_pred_is_subj(term, state); |
|
ecs_entity_t subj = 0, obj = 0, pred = plecs_ensure_term_id( |
|
world, state, &term->first, expr, column, 0, pred_as_subj); |
|
if (!pred) { |
|
return -1; |
|
} |
|
|
|
subj = plecs_ensure_entity(world, state, subj_name, pred, true); |
|
|
|
if (ecs_term_id_is_set(&term->second)) { |
|
obj = plecs_ensure_term_id(world, state, &term->second, expr, column, |
|
pred, !state->assign_stmt && !state->with_stmt); |
|
if (!obj) { |
|
return -1; |
|
} |
|
} |
|
|
|
if (state->assign_stmt || state->isa_stmt) { |
|
subj = state->assign_to; |
|
} |
|
|
|
if (state->isa_stmt && obj) { |
|
ecs_parser_error(name, expr, column, |
|
"invalid object in inheritance statement"); |
|
return -1; |
|
} |
|
|
|
if (state->isa_stmt) { |
|
pred = ecs_pair(EcsIsA, pred); |
|
} |
|
|
|
if (subj == EcsVariable) { |
|
subj = pred; |
|
} |
|
|
|
if (subj) { |
|
ecs_id_t id; |
|
if (!obj) { |
|
id = term->id_flags | pred; |
|
} else { |
|
id = term->id_flags | ecs_pair(pred, obj); |
|
state->last_object = obj; |
|
} |
|
state->last_assign_id = id; |
|
state->last_predicate = pred; |
|
state->last_subject = subj; |
|
ecs_add_id(world, subj, id); |
|
|
|
pred_as_subj = false; |
|
} else { |
|
if (!obj) { |
|
/* If no subject or object were provided, use predicate as subj |
|
* unless the expression explicitly excluded the subject */ |
|
if (pred_as_subj) { |
|
state->last_subject = pred; |
|
subj = pred; |
|
} else { |
|
state->last_predicate = pred; |
|
pred_as_subj = false; |
|
} |
|
} else { |
|
state->last_predicate = pred; |
|
state->last_object = obj; |
|
pred_as_subj = false; |
|
} |
|
} |
|
|
|
/* If this is a with clause (the list of entities between 'with' and scope |
|
* open), add subject to the array of with frames */ |
|
if (state->with_stmt) { |
|
ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_t id; |
|
|
|
if (obj) { |
|
id = ecs_pair(pred, obj); |
|
} else { |
|
id = pred; |
|
} |
|
|
|
state->with[state->with_frame ++] = id; |
|
} else { |
|
if (subj && !state->scope_assign_stmt) { |
|
plecs_apply_with_frame(world, state, subj); |
|
} |
|
} |
|
|
|
/* If an id was provided by itself, add default scope type to it */ |
|
ecs_entity_t default_scope_type = state->default_scope_type[state->sp]; |
|
if (pred_as_subj && default_scope_type) { |
|
ecs_add_id(world, subj, default_scope_type); |
|
} |
|
|
|
/* If annotations preceded the statement, append */ |
|
if (!state->decl_type && state->annot_count) { |
|
if (!subj) { |
|
ecs_parser_error(name, expr, column, |
|
"missing subject for annotations"); |
|
return -1; |
|
} |
|
|
|
plecs_apply_annotations(world, subj, state); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
const char* plecs_parse_inherit_stmt( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
if (state->isa_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"cannot nest inheritance"); |
|
return NULL; |
|
} |
|
|
|
if (!state->last_subject) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing entity to assign inheritance to"); |
|
return NULL; |
|
} |
|
|
|
state->isa_stmt = true; |
|
state->assign_to = state->last_subject; |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_assign_var_expr( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state, |
|
ecs_expr_var_t *var) |
|
{ |
|
ecs_value_t value = {0}; |
|
|
|
if (state->last_assign_id) { |
|
value.type = state->last_assign_id; |
|
value.ptr = ecs_value_new(world, state->last_assign_id); |
|
if (!var && state->assembly_instance) { |
|
var = ecs_vars_lookup(&state->vars, state->var_name); |
|
} |
|
} |
|
|
|
ptr = ecs_parse_expr(world, ptr, &value, |
|
&(ecs_parse_expr_desc_t){ |
|
.name = name, |
|
.expr = expr, |
|
.lookup_action = plecs_lookup_action, |
|
.lookup_ctx = state, |
|
.vars = &state->vars |
|
}); |
|
if (!ptr) { |
|
if (state->last_assign_id) { |
|
ecs_value_free(world, value.type, value.ptr); |
|
} |
|
goto error; |
|
} |
|
|
|
if (var) { |
|
bool ignore = state->var_is_prop && state->assembly_instance; |
|
if (!ignore) { |
|
if (var->value.ptr) { |
|
ecs_value_free(world, var->value.type, var->value.ptr); |
|
var->value.ptr = value.ptr; |
|
var->value.type = value.type; |
|
} |
|
} else { |
|
ecs_value_free(world, value.type, value.ptr); |
|
} |
|
} else { |
|
var = ecs_vars_declare_w_value( |
|
&state->vars, state->var_name, &value); |
|
if (!var) { |
|
goto error; |
|
} |
|
} |
|
|
|
state->var_is_prop = false; |
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* plecs_parse_assign_expr( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state, |
|
ecs_expr_var_t *var) |
|
{ |
|
(void)world; |
|
|
|
if (state->var_stmt) { |
|
return plecs_parse_assign_var_expr(world, name, expr, ptr, state, var); |
|
} |
|
|
|
if (!state->assign_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unexpected value outside of assignment statement"); |
|
return NULL; |
|
} |
|
|
|
ecs_id_t assign_id = state->last_assign_id; |
|
if (!assign_id) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing type for assignment statement"); |
|
return NULL; |
|
} |
|
|
|
ecs_entity_t assign_to = state->assign_to; |
|
if (!assign_to) { |
|
assign_to = state->last_subject; |
|
} |
|
|
|
if (!assign_to) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing entity to assign to"); |
|
return NULL; |
|
} |
|
|
|
ecs_entity_t type = ecs_get_typeid(world, assign_id); |
|
if (!type) { |
|
char *id_str = ecs_id_str(world, assign_id); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid assignment, '%s' is not a type", id_str); |
|
ecs_os_free(id_str); |
|
return NULL; |
|
} |
|
|
|
if (assign_to == EcsVariable) { |
|
assign_to = type; |
|
} |
|
|
|
void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); |
|
|
|
ptr = ecs_parse_expr(world, ptr, &(ecs_value_t){type, value_ptr}, |
|
&(ecs_parse_expr_desc_t){ |
|
.name = name, |
|
.expr = expr, |
|
.lookup_action = plecs_lookup_action, |
|
.lookup_ctx = state, |
|
.vars = &state->vars |
|
}); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
ecs_modified_id(world, assign_to, assign_id); |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_assign_stmt( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
(void)world; |
|
|
|
state->isa_stmt = false; |
|
|
|
/* Component scope (add components to entity) */ |
|
if (!state->assign_to) { |
|
if (!state->last_subject) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing entity to assign to"); |
|
return NULL; |
|
} |
|
state->assign_to = state->last_subject; |
|
} |
|
|
|
if (state->assign_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid assign statement in assign statement"); |
|
return NULL; |
|
} |
|
|
|
state->assign_stmt = true; |
|
|
|
/* Assignment without a preceding component */ |
|
if (ptr[0] == '{') { |
|
ecs_entity_t type = 0; |
|
|
|
/* If we're in a scope & last_subject is a type, assign to scope */ |
|
if (ecs_get_scope(world) != 0) { |
|
type = ecs_get_typeid(world, state->last_subject); |
|
if (type != 0) { |
|
type = state->last_subject; |
|
} |
|
} |
|
|
|
/* If type hasn't been set yet, check if scope has default type */ |
|
if (!type && !state->scope_assign_stmt) { |
|
type = state->default_scope_type[state->sp]; |
|
} |
|
|
|
/* If no type has been found still, check if last with id is a type */ |
|
if (!type && !state->scope_assign_stmt) { |
|
int32_t with_frame_count = state->with_frames[state->sp]; |
|
if (with_frame_count) { |
|
type = state->with[with_frame_count - 1]; |
|
} |
|
} |
|
|
|
if (!type) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing type for assignment"); |
|
return NULL; |
|
} |
|
|
|
state->last_assign_id = type; |
|
} |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_assign_with_stmt( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
int32_t with_frame = state->with_frame - 1; |
|
if (with_frame < 0) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing type in with value"); |
|
return NULL; |
|
} |
|
|
|
ecs_id_t id = state->with[with_frame]; |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
const ecs_type_info_t *ti = idr ? idr->type_info : NULL; |
|
if (!ti) { |
|
char *typename = ecs_id_str(world, id); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"id '%s' in with value is not a type", typename); |
|
ecs_os_free(typename); |
|
return NULL; |
|
} |
|
|
|
plecs_with_value_t *v = &state->with_value_frames[with_frame]; |
|
v->value.type = ti->component; |
|
v->value.ptr = ecs_value_new(world, ti->component); |
|
v->owned = true; |
|
if (!v->value.ptr) { |
|
char *typename = ecs_id_str(world, id); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"failed to create value for '%s'", typename); |
|
ecs_os_free(typename); |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_expr(world, ptr, &v->value, |
|
&(ecs_parse_expr_desc_t){ |
|
.name = name, |
|
.expr = expr, |
|
.lookup_action = plecs_lookup_action, |
|
.lookup_ctx = state, |
|
.vars = &state->vars |
|
}); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_assign_with_var( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(state->with_stmt, ECS_INTERNAL_ERROR, NULL); |
|
|
|
char var_name[ECS_MAX_TOKEN_SIZE]; |
|
const char *tmp = ptr; |
|
ptr = ecs_parse_token(name, expr, ptr + 1, var_name, 0); |
|
if (!ptr) { |
|
ecs_parser_error(name, expr, tmp - expr, |
|
"unresolved variable '%s'", var_name); |
|
return NULL; |
|
} |
|
|
|
ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, var_name); |
|
if (!var) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved variable '%s'", var_name); |
|
return NULL; |
|
} |
|
|
|
int32_t with_frame = state->with_frame; |
|
state->with[with_frame] = var->value.type; |
|
state->with_value_frames[with_frame].value = var->value; |
|
state->with_value_frames[with_frame].owned = false; |
|
state->with_frame ++; |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_var_as_component( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!state->var_stmt, ECS_INTERNAL_ERROR, NULL); |
|
char var_name[ECS_MAX_TOKEN_SIZE]; |
|
const char *tmp = ptr; |
|
ptr = ecs_parse_token(name, expr, ptr + 1, var_name, 0); |
|
if (!ptr) { |
|
ecs_parser_error(name, expr, tmp - expr, |
|
"unresolved variable '%s'", var_name); |
|
return NULL; |
|
} |
|
|
|
ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, var_name); |
|
if (!var) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved variable '%s'", var_name); |
|
return NULL; |
|
} |
|
|
|
if (!state->assign_to) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing lvalue for variable assignment '%s'", var_name); |
|
return NULL; |
|
} |
|
|
|
/* Use type of variable as component */ |
|
ecs_entity_t type = var->value.type; |
|
ecs_entity_t assign_to = state->assign_to; |
|
if (!assign_to) { |
|
assign_to = state->last_subject; |
|
} |
|
|
|
void *dst = ecs_get_mut_id(world, assign_to, type); |
|
if (!dst) { |
|
char *type_name = ecs_get_fullpath(world, type); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"failed to obtain component for type '%s' of variable '%s'", |
|
type_name, var_name); |
|
ecs_os_free(type_name); |
|
return NULL; |
|
} |
|
|
|
if (ecs_value_copy(world, type, dst, var->value.ptr)) { |
|
char *type_name = ecs_get_fullpath(world, type); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"failed to copy value for variable '%s' of type '%s'", |
|
var_name, type_name); |
|
ecs_os_free(type_name); |
|
return NULL; |
|
} |
|
|
|
ecs_modified_id(world, assign_to, type); |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
void plecs_push_using( |
|
ecs_entity_t scope, |
|
plecs_state_t *state) |
|
{ |
|
for (int i = 0; i < state->using_frame; i ++) { |
|
if (state->using[i] == scope) { |
|
return; |
|
} |
|
} |
|
|
|
state->using[state->using_frame ++] = scope; |
|
} |
|
|
|
static |
|
const char* plecs_parse_using_stmt( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
if (state->isa_stmt || state->assign_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid usage of using keyword"); |
|
return NULL; |
|
} |
|
|
|
char using_path[ECS_MAX_TOKEN_SIZE]; |
|
const char *tmp = ptr + 1; |
|
ptr = ecs_parse_token(name, expr, ptr + 5, using_path, 0); |
|
if (!ptr) { |
|
ecs_parser_error(name, expr, tmp - expr, |
|
"expected identifier for using statement"); |
|
return NULL; |
|
} |
|
|
|
ecs_size_t len = ecs_os_strlen(using_path); |
|
if (!len) { |
|
ecs_parser_error(name, expr, tmp - expr, |
|
"missing identifier for using statement"); |
|
return NULL; |
|
} |
|
|
|
/* Lookahead as * is not matched by parse_token */ |
|
if (ptr[0] == '*') { |
|
using_path[len] = '*'; |
|
using_path[len + 1] = '\0'; |
|
len ++; |
|
ptr ++; |
|
} |
|
|
|
ecs_entity_t scope; |
|
if (len > 2 && !ecs_os_strcmp(&using_path[len - 2], ".*")) { |
|
using_path[len - 2] = '\0'; |
|
scope = ecs_lookup_fullpath(world, using_path); |
|
if (!scope) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved identifier '%s' in using statement", using_path); |
|
return NULL; |
|
} |
|
|
|
/* Add each child of the scope to using stack */ |
|
ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t){ |
|
.id = ecs_childof(scope) }); |
|
while (ecs_term_next(&it)) { |
|
int32_t i, count = it.count; |
|
for (i = 0; i < count; i ++) { |
|
plecs_push_using(it.entities[i], state); |
|
} |
|
} |
|
} else { |
|
scope = plecs_ensure_entity(world, state, using_path, 0, false); |
|
if (!scope) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved identifier '%s' in using statement", using_path); |
|
return NULL; |
|
} |
|
|
|
plecs_push_using(scope, state); |
|
} |
|
|
|
state->using_frames[state->sp] = state->using_frame; |
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_module_stmt( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
const char *expr_start = ecs_parse_ws_eol(expr); |
|
if (expr_start != ptr) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"module must be first statement of script"); |
|
return NULL; |
|
} |
|
|
|
char module_path[ECS_MAX_TOKEN_SIZE]; |
|
const char *tmp = ptr + 1; |
|
ptr = ecs_parse_token(name, expr, ptr + 6, module_path, 0); |
|
if (!ptr) { |
|
ecs_parser_error(name, expr, tmp - expr, |
|
"expected identifier for module statement"); |
|
return NULL; |
|
} |
|
|
|
ecs_component_desc_t desc = {0}; |
|
desc.entity = ecs_entity(world, { .name = module_path }); |
|
ecs_entity_t module = ecs_module_init(world, NULL, &desc); |
|
if (!module) { |
|
return NULL; |
|
} |
|
|
|
state->is_module = true; |
|
state->sp ++; |
|
state->scope[state->sp] = module; |
|
ecs_set_scope(world, module); |
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_with_stmt( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
if (state->isa_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid with after inheritance"); |
|
return NULL; |
|
} |
|
|
|
if (state->assign_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid with in assign_stmt"); |
|
return NULL; |
|
} |
|
|
|
/* Add following expressions to with list */ |
|
state->with_stmt = true; |
|
return ptr + 5; |
|
} |
|
|
|
static |
|
const char* plecs_parse_assembly_stmt( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
if (state->isa_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid with after inheritance"); |
|
return NULL; |
|
} |
|
|
|
if (state->assign_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid with in assign_stmt"); |
|
return NULL; |
|
} |
|
|
|
state->assembly_stmt = true; |
|
|
|
return ptr + 9; |
|
} |
|
|
|
static |
|
const char* plecs_parse_var_type( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state, |
|
ecs_entity_t *type_out) |
|
{ |
|
char prop_type_name[ECS_MAX_TOKEN_SIZE]; |
|
const char *tmp = ptr + 1; |
|
ptr = ecs_parse_token(name, expr, ptr + 1, prop_type_name, 0); |
|
if (!ptr) { |
|
ecs_parser_error(name, expr, tmp - expr, |
|
"expected type for prop declaration"); |
|
return NULL; |
|
} |
|
|
|
ecs_entity_t prop_type = plecs_lookup(world, prop_type_name, state, 0, false); |
|
if (!prop_type) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved property type '%s'", prop_type_name); |
|
return NULL; |
|
} |
|
|
|
*type_out = prop_type; |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_const_stmt( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
ptr = ecs_parse_token(name, expr, ptr + 5, state->var_name, 0); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
|
|
if (ptr[0] == ':') { |
|
ptr = plecs_parse_var_type( |
|
world, name, expr, ptr, state, &state->last_assign_id); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
} |
|
|
|
if (ptr[0] != '=') { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"expected '=' after const declaration"); |
|
return NULL; |
|
} |
|
|
|
state->var_stmt = true; |
|
return ptr + 1; |
|
} |
|
|
|
static |
|
const char* plecs_parse_prop_stmt( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
char prop_name[ECS_MAX_TOKEN_SIZE]; |
|
ptr = ecs_parse_token(name, expr, ptr + 5, prop_name, 0); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_ws(ptr); |
|
|
|
if (ptr[0] != ':') { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"expected ':' after prop declaration"); |
|
return NULL; |
|
} |
|
|
|
ecs_entity_t prop_type; |
|
ptr = plecs_parse_var_type(world, name, expr, ptr, state, &prop_type); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
ecs_entity_t assembly = state->assembly; |
|
if (!assembly) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unexpected prop '%s' outside of assembly", prop_name); |
|
return NULL; |
|
} |
|
|
|
if (!state->assembly_instance) { |
|
ecs_entity_t prop_member = ecs_entity(world, { |
|
.name = prop_name, |
|
.add = { ecs_childof(assembly) } |
|
}); |
|
|
|
if (!prop_member) { |
|
return NULL; |
|
} |
|
|
|
ecs_set(world, prop_member, EcsMember, { |
|
.type = prop_type |
|
}); |
|
} |
|
|
|
if (ptr[0] != '=') { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"expected '=' after prop type"); |
|
return NULL; |
|
} |
|
|
|
ecs_os_strcpy(state->var_name, prop_name); |
|
state->last_assign_id = prop_type; |
|
state->var_stmt = true; |
|
state->var_is_prop = true; |
|
|
|
return plecs_parse_fluff(ptr + 1); |
|
} |
|
|
|
static |
|
const char* plecs_parse_scope_open( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
state->isa_stmt = false; |
|
|
|
if (state->assign_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid scope in assign_stmt"); |
|
return NULL; |
|
} |
|
|
|
state->sp ++; |
|
|
|
ecs_entity_t scope = 0; |
|
ecs_entity_t default_scope_type = 0; |
|
bool assembly_stmt = false; |
|
|
|
if (!state->with_stmt) { |
|
if (state->last_subject) { |
|
scope = state->last_subject; |
|
ecs_set_scope(world, state->last_subject); |
|
|
|
/* Check if scope has a default child component */ |
|
ecs_entity_t def_type_src = ecs_get_target_for_id(world, scope, |
|
0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); |
|
|
|
if (def_type_src) { |
|
default_scope_type = ecs_get_target( |
|
world, def_type_src, EcsDefaultChildComponent, 0); |
|
} |
|
} else { |
|
if (state->last_object) { |
|
scope = ecs_pair( |
|
state->last_predicate, state->last_object); |
|
ecs_set_with(world, scope); |
|
} else { |
|
if (state->last_predicate) { |
|
scope = ecs_pair(EcsChildOf, state->last_predicate); |
|
} |
|
ecs_set_scope(world, state->last_predicate); |
|
} |
|
} |
|
|
|
state->scope[state->sp] = scope; |
|
state->default_scope_type[state->sp] = default_scope_type; |
|
|
|
if (state->assembly_stmt) { |
|
assembly_stmt = true; |
|
if (state->assembly) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid nested assembly"); |
|
return NULL; |
|
} |
|
state->assembly = scope; |
|
state->assembly_stmt = false; |
|
state->assembly_start = ptr; |
|
} |
|
} else { |
|
state->scope[state->sp] = state->scope[state->sp - 1]; |
|
state->default_scope_type[state->sp] = |
|
state->default_scope_type[state->sp - 1]; |
|
state->assign_to = 0; |
|
} |
|
|
|
state->using_frames[state->sp] = state->using_frame; |
|
state->with_frames[state->sp] = state->with_frame; |
|
state->with_stmt = false; |
|
|
|
ecs_vars_push(&state->vars); |
|
|
|
/* Declare variable to hold assembly instance during instantiation */ |
|
if (assembly_stmt) { |
|
ecs_value_t val = {0}; |
|
ecs_expr_var_t *var = ecs_vars_declare_w_value( |
|
&state->vars, "this", &val); |
|
var->value.ptr = ECS_CONST_CAST(void*, &EcsThis); /* Dummy value */ |
|
var->value.type = ecs_id(ecs_entity_t); |
|
var->owned = false; |
|
} |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
void plecs_free_with_frame( |
|
ecs_world_t *world, |
|
plecs_state_t *state) |
|
{ |
|
int32_t i, prev_with = state->with_frames[state->sp]; |
|
for (i = prev_with; i < state->with_frame; i ++) { |
|
plecs_with_value_t *v = &state->with_value_frames[i]; |
|
if (!v->owned) { |
|
continue; |
|
} |
|
if (v->value.type) { |
|
ecs_value_free(world, v->value.type, v->value.ptr); |
|
v->value.type = 0; |
|
v->value.ptr = NULL; |
|
v->owned = false; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void plecs_free_all_with_frames( |
|
ecs_world_t *world, |
|
plecs_state_t *state) |
|
{ |
|
int32_t i; |
|
for (i = state->sp - 1; i >= 0; i --) { |
|
state->sp = i; |
|
plecs_free_with_frame(world, state); |
|
} |
|
} |
|
|
|
static |
|
const char* plecs_parse_scope_close( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
if (state->isa_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid '}' after inheritance statement"); |
|
return NULL; |
|
} |
|
|
|
if (state->assign_stmt) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unfinished assignment before }"); |
|
return NULL; |
|
} |
|
|
|
ecs_entity_t cur = state->scope[state->sp], assembly = state->assembly; |
|
if (state->sp && (cur == state->scope[state->sp - 1])) { |
|
/* Previous scope is also from the assembly, not found the end yet */ |
|
cur = 0; |
|
} |
|
if (cur && cur == assembly) { |
|
ecs_size_t assembly_len = flecs_ito(ecs_size_t, ptr - state->assembly_start); |
|
if (assembly_len) { |
|
assembly_len --; |
|
char *script = ecs_os_malloc_n(char, assembly_len + 1); |
|
ecs_os_memcpy(script, state->assembly_start, assembly_len); |
|
script[assembly_len] = '\0'; |
|
state->assembly = 0; |
|
state->assembly_start = NULL; |
|
if (flecs_assembly_create(world, name, expr, ptr, assembly, script, state)) { |
|
return NULL; |
|
} |
|
} else { |
|
ecs_parser_error(name, expr, ptr - expr, "empty assembly"); |
|
return NULL; |
|
} |
|
} |
|
|
|
state->scope[state->sp] = 0; |
|
state->default_scope_type[state->sp] = 0; |
|
state->sp --; |
|
|
|
if (state->sp < 0) { |
|
ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); |
|
return NULL; |
|
} |
|
|
|
ecs_id_t id = state->scope[state->sp]; |
|
if (!id || ECS_HAS_ID_FLAG(id, PAIR)) { |
|
ecs_set_with(world, id); |
|
} |
|
|
|
if (!id || !ECS_HAS_ID_FLAG(id, PAIR)) { |
|
ecs_set_scope(world, id); |
|
} |
|
|
|
plecs_free_with_frame(world, state); |
|
|
|
state->with_frame = state->with_frames[state->sp]; |
|
state->using_frame = state->using_frames[state->sp]; |
|
state->last_subject = 0; |
|
state->assign_stmt = false; |
|
|
|
ecs_vars_pop(&state->vars); |
|
|
|
return plecs_parse_fluff(ptr + 1); |
|
} |
|
|
|
static |
|
const char *plecs_parse_plecs_term( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
ecs_term_t term = {0}; |
|
ecs_entity_t decl_id = 0; |
|
if (state->decl_stmt) { |
|
decl_id = state->last_predicate; |
|
} |
|
|
|
ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL, NULL, false); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
if (flecs_isident(ptr[0])) { |
|
state->decl_type = true; |
|
} |
|
|
|
if (!ecs_term_is_initialized(&term)) { |
|
ecs_parser_error(name, expr, ptr - expr, "expected identifier"); |
|
return NULL; /* No term found */ |
|
} |
|
|
|
if (plecs_create_term(world, &term, name, expr, (ptr - expr), state)) { |
|
ecs_term_fini(&term); |
|
return NULL; /* Failed to create term */ |
|
} |
|
|
|
if (decl_id && state->last_subject) { |
|
ecs_add_id(world, state->last_subject, decl_id); |
|
} |
|
|
|
state->decl_type = false; |
|
|
|
ecs_term_fini(&term); |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* plecs_parse_annotation( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
do { |
|
if(state->annot_count >= STACK_MAX_SIZE) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"max number of annotations reached"); |
|
return NULL; |
|
} |
|
|
|
char ch; |
|
const char *start = ptr; |
|
for (; (ch = *ptr) && ch != '\n'; ptr ++) { } |
|
|
|
int32_t len = (int32_t)(ptr - start); |
|
char *annot = ecs_os_malloc_n(char, len + 1); |
|
ecs_os_memcpy_n(annot, start, char, len); |
|
annot[len] = '\0'; |
|
|
|
state->annot[state->annot_count] = annot; |
|
state->annot_count ++; |
|
|
|
ptr = plecs_parse_fluff(ptr); |
|
} while (ptr[0] == '@'); |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
void plecs_clear_annotations( |
|
plecs_state_t *state) |
|
{ |
|
int32_t i, count = state->annot_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_os_free(state->annot[i]); |
|
} |
|
state->annot_count = 0; |
|
} |
|
|
|
static |
|
const char* plecs_parse_stmt( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
plecs_state_t *state) |
|
{ |
|
state->assign_stmt = false; |
|
state->scope_assign_stmt = false; |
|
state->isa_stmt = false; |
|
state->with_stmt = false; |
|
state->decl_stmt = false; |
|
state->var_stmt = false; |
|
state->last_subject = 0; |
|
state->last_predicate = 0; |
|
state->last_object = 0; |
|
state->assign_to = 0; |
|
state->last_assign_id = 0; |
|
|
|
plecs_clear_annotations(state); |
|
|
|
ptr = plecs_parse_fluff(ptr); |
|
|
|
char ch = ptr[0]; |
|
|
|
if (!ch) { |
|
goto done; |
|
} else if (ch == '{') { |
|
ptr = plecs_parse_fluff(ptr + 1); |
|
goto scope_open; |
|
} else if (ch == '}') { |
|
goto scope_close; |
|
} else if (ch == '-') { |
|
ptr = plecs_parse_fluff(ptr + 1); |
|
state->assign_to = ecs_get_scope(world); |
|
state->scope_assign_stmt = true; |
|
goto assign_stmt; |
|
} else if (ch == '@') { |
|
ptr = plecs_parse_annotation(name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto term_expr; |
|
} else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { |
|
ptr = plecs_parse_using_stmt(world, name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto done; |
|
} else if (!ecs_os_strncmp(ptr, TOK_MODULE " ", 6)) { |
|
ptr = plecs_parse_module_stmt(world, name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto done; |
|
} else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { |
|
ptr = plecs_parse_with_stmt(name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto term_expr; |
|
} else if (!ecs_os_strncmp(ptr, TOK_CONST " ", 6)) { |
|
ptr = plecs_parse_const_stmt(world, name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto assign_expr; |
|
} else if (!ecs_os_strncmp(ptr, TOK_ASSEMBLY " ", 9)) { |
|
ptr = plecs_parse_assembly_stmt(name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto decl_stmt; |
|
} else if (!ecs_os_strncmp(ptr, TOK_PROP " ", 5)) { |
|
ptr = plecs_parse_prop_stmt(world, name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto assign_expr; |
|
} else { |
|
goto term_expr; |
|
} |
|
|
|
term_expr: |
|
if (!ptr[0]) { |
|
goto done; |
|
} |
|
|
|
if (ptr[0] == '$' && !isspace(ptr[1])) { |
|
if (state->with_stmt) { |
|
ptr = plecs_parse_assign_with_var(name, expr, ptr, state); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
} else if (!state->var_stmt) { |
|
goto assign_var_as_component; |
|
} |
|
} else if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) { |
|
goto error; |
|
} |
|
|
|
|
|
const char *tptr = ecs_parse_ws(ptr); |
|
if (flecs_isident(tptr[0])) { |
|
if (state->decl_stmt) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unexpected ' ' in declaration statement"); |
|
goto error; |
|
} |
|
ptr = tptr; |
|
goto decl_stmt; |
|
} |
|
|
|
next_term: |
|
ptr = plecs_parse_fluff(ptr); |
|
|
|
if (ptr[0] == ':' && ptr[1] == '-') { |
|
ptr = plecs_parse_fluff(ptr + 2); |
|
goto assign_stmt; |
|
} else if (ptr[0] == ':') { |
|
ptr = plecs_parse_fluff(ptr + 1); |
|
goto inherit_stmt; |
|
} else if (ptr[0] == ',') { |
|
ptr = plecs_parse_fluff(ptr + 1); |
|
goto term_expr; |
|
} else if (ptr[0] == '{' || ptr[0] == '[') { |
|
if (state->assign_stmt) { |
|
goto assign_expr; |
|
} else if (state->with_stmt && !isspace(ptr[-1])) { |
|
/* If this is a { in a with statement which directly follows a |
|
* non-whitespace character, the with id has a value */ |
|
ptr = plecs_parse_assign_with_stmt(world, name, expr, ptr, state); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
goto next_term; |
|
} else { |
|
ptr = plecs_parse_fluff(ptr + 1); |
|
goto scope_open; |
|
} |
|
} |
|
|
|
state->assign_stmt = false; |
|
goto done; |
|
|
|
decl_stmt: |
|
state->decl_stmt = true; |
|
goto term_expr; |
|
|
|
inherit_stmt: |
|
ptr = plecs_parse_inherit_stmt(name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
|
|
/* Expect base identifier */ |
|
goto term_expr; |
|
|
|
assign_stmt: |
|
ptr = plecs_parse_assign_stmt(world, name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
|
|
ptr = plecs_parse_fluff(ptr); |
|
|
|
/* Assignment without a preceding component */ |
|
if (ptr[0] == '{' || ptr[0] == '[') { |
|
goto assign_expr; |
|
} |
|
|
|
/* Expect component identifiers */ |
|
goto term_expr; |
|
|
|
assign_expr: |
|
ptr = plecs_parse_assign_expr(world, name, expr, ptr, state, NULL); |
|
if (!ptr) goto error; |
|
|
|
ptr = plecs_parse_fluff(ptr); |
|
if (ptr[0] == ',') { |
|
ptr ++; |
|
goto term_expr; |
|
} else if (ptr[0] == '{') { |
|
if (state->var_stmt) { |
|
ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, state->var_name); |
|
if (var && var->value.type == ecs_id(ecs_entity_t)) { |
|
ecs_assert(var->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
/* The code contained an entity{...} variable assignment, use |
|
* the assigned entity id as type for parsing the expression */ |
|
state->last_assign_id = *(ecs_entity_t*)var->value.ptr; |
|
ptr = plecs_parse_assign_expr(world, name, expr, ptr, state, var); |
|
goto done; |
|
} |
|
} |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unexpected '{' after assignment"); |
|
goto error; |
|
} |
|
|
|
state->assign_stmt = false; |
|
state->assign_to = 0; |
|
goto done; |
|
|
|
assign_var_as_component: { |
|
ptr = plecs_parse_var_as_component(world, name, expr, ptr, state); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
state->assign_stmt = false; |
|
state->assign_to = 0; |
|
goto done; |
|
} |
|
|
|
scope_open: |
|
ptr = plecs_parse_scope_open(world, name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto done; |
|
|
|
scope_close: |
|
ptr = plecs_parse_scope_close(world, name, expr, ptr, state); |
|
if (!ptr) goto error; |
|
goto done; |
|
|
|
done: |
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
int flecs_plecs_parse( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
ecs_vars_t *vars, |
|
ecs_entity_t script, |
|
ecs_entity_t instance) |
|
{ |
|
const char *ptr = expr; |
|
ecs_term_t term = {0}; |
|
plecs_state_t state = {0}; |
|
|
|
if (!expr) { |
|
return 0; |
|
} |
|
|
|
state.scope[0] = 0; |
|
ecs_entity_t prev_scope = ecs_set_scope(world, 0); |
|
ecs_entity_t prev_with = ecs_set_with(world, 0); |
|
|
|
if (ECS_IS_PAIR(prev_with) && ECS_PAIR_FIRST(prev_with) == EcsChildOf) { |
|
ecs_set_scope(world, ECS_PAIR_SECOND(prev_with)); |
|
state.scope[0] = ecs_pair_second(world, prev_with); |
|
} else { |
|
state.global_with = prev_with; |
|
} |
|
|
|
ecs_vars_init(world, &state.vars); |
|
|
|
if (script) { |
|
const EcsScript *s = ecs_get(world, script, EcsScript); |
|
if (!s) { |
|
ecs_err("%s: provided script entity is not a script", name); |
|
goto error; |
|
} |
|
if (s && ecs_has(world, script, EcsStruct)) { |
|
state.assembly = script; |
|
state.assembly_instance = true; |
|
|
|
if (s->using_.count) { |
|
ecs_os_memcpy_n(state.using, s->using_.array, |
|
ecs_entity_t, s->using_.count); |
|
state.using_frame = s->using_.count; |
|
state.using_frames[0] = s->using_.count; |
|
} |
|
|
|
if (instance) { |
|
ecs_set_scope(world, instance); |
|
} |
|
} |
|
} |
|
|
|
if (vars) { |
|
state.vars.root.parent = vars->cur; |
|
} |
|
|
|
do { |
|
expr = ptr = plecs_parse_stmt(world, name, expr, ptr, &state); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
|
|
if (!ptr[0]) { |
|
break; /* End of expression */ |
|
} |
|
} while (true); |
|
|
|
ecs_set_scope(world, prev_scope); |
|
ecs_set_with(world, prev_with); |
|
plecs_clear_annotations(&state); |
|
|
|
if (state.is_module) { |
|
state.sp --; |
|
} |
|
|
|
if (state.sp != 0) { |
|
ecs_parser_error(name, expr, 0, "missing end of scope"); |
|
goto error; |
|
} |
|
|
|
if (state.assign_stmt) { |
|
ecs_parser_error(name, expr, 0, "unfinished assignment"); |
|
goto error; |
|
} |
|
|
|
if (state.errors) { |
|
goto error; |
|
} |
|
|
|
ecs_vars_fini(&state.vars); |
|
|
|
return 0; |
|
error: |
|
plecs_free_all_with_frames(world, &state); |
|
ecs_vars_fini(&state.vars); |
|
ecs_set_scope(world, state.scope[0]); |
|
ecs_set_with(world, prev_with); |
|
ecs_term_fini(&term); |
|
return -1; |
|
} |
|
|
|
int ecs_plecs_from_str( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr) |
|
{ |
|
return flecs_plecs_parse(world, name, expr, NULL, 0, 0); |
|
} |
|
|
|
static |
|
char* flecs_load_from_file( |
|
const char *filename) |
|
{ |
|
FILE* file; |
|
char* content = NULL; |
|
int32_t bytes; |
|
size_t size; |
|
|
|
/* Open file for reading */ |
|
ecs_os_fopen(&file, filename, "r"); |
|
if (!file) { |
|
ecs_err("%s (%s)", ecs_os_strerror(errno), filename); |
|
goto error; |
|
} |
|
|
|
/* Determine file size */ |
|
fseek(file, 0 , SEEK_END); |
|
bytes = (int32_t)ftell(file); |
|
if (bytes == -1) { |
|
goto error; |
|
} |
|
rewind(file); |
|
|
|
/* Load contents in memory */ |
|
content = ecs_os_malloc(bytes + 1); |
|
size = (size_t)bytes; |
|
if (!(size = fread(content, 1, size, file)) && bytes) { |
|
ecs_err("%s: read zero bytes instead of %d", filename, size); |
|
ecs_os_free(content); |
|
content = NULL; |
|
goto error; |
|
} else { |
|
content[size] = '\0'; |
|
} |
|
|
|
fclose(file); |
|
|
|
return content; |
|
error: |
|
ecs_os_free(content); |
|
return NULL; |
|
} |
|
|
|
int ecs_plecs_from_file( |
|
ecs_world_t *world, |
|
const char *filename) |
|
{ |
|
char *script = flecs_load_from_file(filename); |
|
if (!script) { |
|
return -1; |
|
} |
|
|
|
int result = ecs_plecs_from_str(world, filename, script); |
|
ecs_os_free(script); |
|
return result; |
|
} |
|
|
|
static |
|
ecs_id_t flecs_script_tag( |
|
ecs_entity_t script, |
|
ecs_entity_t instance) |
|
{ |
|
if (!instance) { |
|
return ecs_pair_t(EcsScript, script); |
|
} else { |
|
return ecs_pair(EcsChildOf, instance); |
|
} |
|
} |
|
|
|
void ecs_script_clear( |
|
ecs_world_t *world, |
|
ecs_entity_t script, |
|
ecs_entity_t instance) |
|
{ |
|
ecs_delete_with(world, flecs_script_tag(script, instance)); |
|
} |
|
|
|
int ecs_script_update( |
|
ecs_world_t *world, |
|
ecs_entity_t e, |
|
ecs_entity_t instance, |
|
const char *script, |
|
ecs_vars_t *vars) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int result = 0; |
|
bool is_defer = ecs_is_deferred(world); |
|
ecs_suspend_readonly_state_t srs; |
|
ecs_world_t *real_world = NULL; |
|
if (is_defer) { |
|
ecs_assert(ecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); |
|
real_world = flecs_suspend_readonly(world, &srs); |
|
ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
ecs_script_clear(world, e, instance); |
|
|
|
EcsScript *s = ecs_get_mut(world, e, EcsScript); |
|
if (!s->script || ecs_os_strcmp(s->script, script)) { |
|
s->script = ecs_os_strdup(script); |
|
ecs_modified(world, e, EcsScript); |
|
} |
|
|
|
ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); |
|
if (flecs_plecs_parse(world, ecs_get_name(world, e), script, vars, e, instance)) { |
|
ecs_delete_with(world, ecs_pair_t(EcsScript, e)); |
|
result = -1; |
|
} |
|
ecs_set_with(world, prev); |
|
|
|
if (is_defer) { |
|
flecs_resume_readonly(real_world, &srs); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
ecs_entity_t ecs_script_init( |
|
ecs_world_t *world, |
|
const ecs_script_desc_t *desc) |
|
{ |
|
const char *script = NULL; |
|
ecs_entity_t e = desc->entity; |
|
|
|
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_check(desc != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!e) { |
|
if (desc->filename) { |
|
e = ecs_new_from_path_w_sep(world, 0, desc->filename, "/", NULL); |
|
} else { |
|
e = ecs_new_id(world); |
|
} |
|
} |
|
|
|
script = desc->str; |
|
if (!script && desc->filename) { |
|
script = flecs_load_from_file(desc->filename); |
|
if (!script) { |
|
goto error; |
|
} |
|
} |
|
|
|
if (ecs_script_update(world, e, 0, script, NULL)) { |
|
goto error; |
|
} |
|
|
|
if (script != desc->str) { |
|
/* Safe cast, only happens when script is loaded from file */ |
|
ecs_os_free(ECS_CONST_CAST(char*, script)); |
|
} |
|
|
|
return e; |
|
error: |
|
if (script != desc->str) { |
|
/* Safe cast, only happens when script is loaded from file */ |
|
ecs_os_free(ECS_CONST_CAST(char*, script)); |
|
} |
|
if (!desc->entity) { |
|
ecs_delete(world, e); |
|
} |
|
return 0; |
|
} |
|
|
|
void FlecsScriptImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsScript); |
|
ECS_IMPORT(world, FlecsMeta); |
|
#ifdef FLECS_COREDOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
ecs_doc_set_brief(world, ecs_id(FlecsScript), |
|
"Module with components for managing Flecs scripts"); |
|
#endif |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
ECS_COMPONENT_DEFINE(world, EcsScript); |
|
|
|
ecs_set_hooks(world, EcsScript, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsScript), |
|
.dtor = ecs_dtor(EcsScript) |
|
}); |
|
|
|
ecs_add_id(world, ecs_id(EcsScript), EcsTag); |
|
ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); |
|
|
|
ecs_struct(world, { |
|
.entity = ecs_id(EcsScript), |
|
.members = { |
|
{ .name = "using", .type = ecs_vector(world, { |
|
.entity = ecs_entity(world, { .name = "UsingVector" }), |
|
.type = ecs_id(ecs_entity_t) |
|
}), |
|
.count = 0 |
|
}, |
|
{ .name = "script", .type = ecs_id(ecs_string_t), .count = 0 } |
|
} |
|
}); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/rest.c |
|
* @brief Rest addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_REST |
|
/** |
|
* @file json/json.h |
|
* @brief Internal functions for JSON addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_JSON |
|
|
|
/* Deserialize from JSON */ |
|
typedef enum ecs_json_token_t { |
|
JsonObjectOpen, |
|
JsonObjectClose, |
|
JsonArrayOpen, |
|
JsonArrayClose, |
|
JsonColon, |
|
JsonComma, |
|
JsonNumber, |
|
JsonString, |
|
JsonTrue, |
|
JsonFalse, |
|
JsonNull, |
|
JsonLargeInt, |
|
JsonLargeString, |
|
JsonInvalid |
|
} ecs_json_token_t; |
|
|
|
typedef struct ecs_json_value_ser_ctx_t { |
|
ecs_entity_t type; |
|
const EcsMetaTypeSerialized *ser; |
|
char *id_label; |
|
bool initialized; |
|
} ecs_json_value_ser_ctx_t; |
|
|
|
/* Cached data for serializer */ |
|
typedef struct ecs_json_ser_ctx_t { |
|
ecs_id_record_t *idr_doc_name; |
|
ecs_id_record_t *idr_doc_color; |
|
ecs_json_value_ser_ctx_t value_ctx[64]; |
|
} ecs_json_ser_ctx_t; |
|
|
|
const char* flecs_json_parse( |
|
const char *json, |
|
ecs_json_token_t *token_kind, |
|
char *token); |
|
|
|
const char* flecs_json_parse_large_string( |
|
const char *json, |
|
ecs_strbuf_t *buf); |
|
|
|
const char* flecs_json_expect( |
|
const char *json, |
|
ecs_json_token_t token_kind, |
|
char *token, |
|
const ecs_from_json_desc_t *desc); |
|
|
|
const char* flecs_json_expect_member( |
|
const char *json, |
|
char *token, |
|
const ecs_from_json_desc_t *desc); |
|
|
|
const char* flecs_json_expect_member_name( |
|
const char *json, |
|
char *token, |
|
const char *member_name, |
|
const ecs_from_json_desc_t *desc); |
|
|
|
const char* flecs_json_skip_object( |
|
const char *json, |
|
char *token, |
|
const ecs_from_json_desc_t *desc); |
|
|
|
const char* flecs_json_skip_array( |
|
const char *json, |
|
char *token, |
|
const ecs_from_json_desc_t *desc); |
|
|
|
/* Serialize to JSON */ |
|
void flecs_json_next( |
|
ecs_strbuf_t *buf); |
|
|
|
void flecs_json_number( |
|
ecs_strbuf_t *buf, |
|
double value); |
|
|
|
void flecs_json_true( |
|
ecs_strbuf_t *buf); |
|
|
|
void flecs_json_false( |
|
ecs_strbuf_t *buf); |
|
|
|
void flecs_json_bool( |
|
ecs_strbuf_t *buf, |
|
bool value); |
|
|
|
void flecs_json_array_push( |
|
ecs_strbuf_t *buf); |
|
|
|
void flecs_json_array_pop( |
|
ecs_strbuf_t *buf); |
|
|
|
void flecs_json_object_push( |
|
ecs_strbuf_t *buf); |
|
|
|
void flecs_json_object_pop( |
|
ecs_strbuf_t *buf); |
|
|
|
void flecs_json_string( |
|
ecs_strbuf_t *buf, |
|
const char *value); |
|
|
|
void flecs_json_string_escape( |
|
ecs_strbuf_t *buf, |
|
const char *value); |
|
|
|
void flecs_json_member( |
|
ecs_strbuf_t *buf, |
|
const char *name); |
|
|
|
void flecs_json_membern( |
|
ecs_strbuf_t *buf, |
|
const char *name, |
|
int32_t name_len); |
|
|
|
#define flecs_json_memberl(buf, name)\ |
|
flecs_json_membern(buf, name, sizeof(name) - 1) |
|
|
|
void flecs_json_path( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_entity_t e); |
|
|
|
void flecs_json_label( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_entity_t e); |
|
|
|
void flecs_json_color( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_entity_t e); |
|
|
|
void flecs_json_id( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_id_t id); |
|
|
|
void flecs_json_id_member( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_id_t id); |
|
|
|
ecs_primitive_kind_t flecs_json_op_to_primitive_kind( |
|
ecs_meta_type_op_kind_t kind); |
|
|
|
bool flecs_json_serialize_get_field_ctx( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
int32_t f, |
|
ecs_json_ser_ctx_t *ser_ctx); |
|
|
|
int flecs_json_serialize_iter_result_rows( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc, |
|
ecs_json_ser_ctx_t *ser_ctx); |
|
|
|
bool flecs_json_serialize_iter_result_is_set( |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf); |
|
|
|
bool flecs_json_skip_variable( |
|
const char *name); |
|
|
|
int json_ser_type( |
|
const ecs_world_t *world, |
|
const ecs_vec_t *ser, |
|
const void *base, |
|
ecs_strbuf_t *str); |
|
|
|
#endif |
|
|
|
|
|
static ECS_TAG_DECLARE(EcsRestPlecs); |
|
|
|
typedef struct { |
|
ecs_world_t *world; |
|
ecs_http_server_t *srv; |
|
int32_t rc; |
|
} ecs_rest_ctx_t; |
|
|
|
static ECS_COPY(EcsRest, dst, src, { |
|
ecs_rest_ctx_t *impl = src->impl; |
|
if (impl) { |
|
impl->rc ++; |
|
} |
|
|
|
ecs_os_strset(&dst->ipaddr, src->ipaddr); |
|
dst->port = src->port; |
|
dst->impl = impl; |
|
}) |
|
|
|
static ECS_MOVE(EcsRest, dst, src, { |
|
*dst = *src; |
|
src->ipaddr = NULL; |
|
src->impl = NULL; |
|
}) |
|
|
|
static ECS_DTOR(EcsRest, ptr, { |
|
ecs_rest_ctx_t *impl = ptr->impl; |
|
if (impl) { |
|
impl->rc --; |
|
if (!impl->rc) { |
|
ecs_http_server_fini(impl->srv); |
|
ecs_os_free(impl); |
|
} |
|
} |
|
ecs_os_free(ptr->ipaddr); |
|
}) |
|
|
|
static char *rest_last_err; |
|
static ecs_os_api_log_t rest_prev_log; |
|
|
|
static |
|
void flecs_rest_capture_log( |
|
int32_t level, |
|
const char *file, |
|
int32_t line, |
|
const char *msg) |
|
{ |
|
(void)file; (void)line; |
|
|
|
#ifdef FLECS_DEBUG |
|
if (level < 0) { |
|
/* Also log to previous log function in debug mode */ |
|
if (rest_prev_log) { |
|
ecs_log_enable_colors(true); |
|
rest_prev_log(level, file, line, msg); |
|
ecs_log_enable_colors(false); |
|
} |
|
} |
|
#endif |
|
|
|
if (!rest_last_err && level < 0) { |
|
rest_last_err = ecs_os_strdup(msg); |
|
} |
|
} |
|
|
|
static |
|
char* flecs_rest_get_captured_log(void) { |
|
char *result = rest_last_err; |
|
rest_last_err = NULL; |
|
return result; |
|
} |
|
|
|
static |
|
void flecs_reply_verror( |
|
ecs_http_reply_t *reply, |
|
const char *fmt, |
|
va_list args) |
|
{ |
|
ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); |
|
ecs_strbuf_vappend(&reply->body, fmt, args); |
|
ecs_strbuf_appendlit(&reply->body, "\"}"); |
|
} |
|
|
|
static |
|
void flecs_reply_error( |
|
ecs_http_reply_t *reply, |
|
const char *fmt, |
|
...) |
|
{ |
|
va_list args; |
|
va_start(args, fmt); |
|
flecs_reply_verror(reply, fmt, args); |
|
va_end(args); |
|
} |
|
|
|
static |
|
void flecs_rest_bool_param( |
|
const ecs_http_request_t *req, |
|
const char *name, |
|
bool *value_out) |
|
{ |
|
const char *value = ecs_http_get_param(req, name); |
|
if (value) { |
|
if (!ecs_os_strcmp(value, "true")) { |
|
value_out[0] = true; |
|
} else { |
|
value_out[0] = false; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_rest_int_param( |
|
const ecs_http_request_t *req, |
|
const char *name, |
|
int32_t *value_out) |
|
{ |
|
const char *value = ecs_http_get_param(req, name); |
|
if (value) { |
|
*value_out = atoi(value); |
|
} |
|
} |
|
|
|
static |
|
void flecs_rest_string_param( |
|
const ecs_http_request_t *req, |
|
const char *name, |
|
char **value_out) |
|
{ |
|
const char *value = ecs_http_get_param(req, name); |
|
if (value) { |
|
*value_out = ECS_CONST_CAST(char*, value); |
|
} |
|
} |
|
|
|
static |
|
void flecs_rest_parse_json_ser_entity_params( |
|
ecs_world_t *world, |
|
ecs_entity_to_json_desc_t *desc, |
|
const ecs_http_request_t *req) |
|
{ |
|
flecs_rest_bool_param(req, "path", &desc->serialize_path); |
|
flecs_rest_bool_param(req, "label", &desc->serialize_label); |
|
flecs_rest_bool_param(req, "brief", &desc->serialize_brief); |
|
flecs_rest_bool_param(req, "link", &desc->serialize_link); |
|
flecs_rest_bool_param(req, "color", &desc->serialize_color); |
|
flecs_rest_bool_param(req, "ids", &desc->serialize_ids); |
|
flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); |
|
flecs_rest_bool_param(req, "base", &desc->serialize_base); |
|
flecs_rest_bool_param(req, "values", &desc->serialize_values); |
|
flecs_rest_bool_param(req, "private", &desc->serialize_private); |
|
flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); |
|
flecs_rest_bool_param(req, "matches", &desc->serialize_matches); |
|
flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts); |
|
|
|
char *rel = NULL; |
|
flecs_rest_string_param(req, "refs", &rel); |
|
if (rel) { |
|
desc->serialize_refs = ecs_lookup_fullpath(world, rel); |
|
} |
|
} |
|
|
|
static |
|
void flecs_rest_parse_json_ser_iter_params( |
|
ecs_iter_to_json_desc_t *desc, |
|
const ecs_http_request_t *req) |
|
{ |
|
flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); |
|
flecs_rest_bool_param(req, "term_labels", &desc->serialize_term_labels); |
|
flecs_rest_bool_param(req, "ids", &desc->serialize_ids); |
|
flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); |
|
flecs_rest_bool_param(req, "sources", &desc->serialize_sources); |
|
flecs_rest_bool_param(req, "variables", &desc->serialize_variables); |
|
flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set); |
|
flecs_rest_bool_param(req, "values", &desc->serialize_values); |
|
flecs_rest_bool_param(req, "private", &desc->serialize_private); |
|
flecs_rest_bool_param(req, "entities", &desc->serialize_entities); |
|
flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); |
|
flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); |
|
flecs_rest_bool_param(req, "variable_ids", &desc->serialize_variable_ids); |
|
flecs_rest_bool_param(req, "colors", &desc->serialize_colors); |
|
flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration); |
|
flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); |
|
flecs_rest_bool_param(req, "field_info", &desc->serialize_field_info); |
|
flecs_rest_bool_param(req, "table", &desc->serialize_table); |
|
flecs_rest_bool_param(req, "rows", &desc->serialize_rows); |
|
bool results = true; |
|
flecs_rest_bool_param(req, "results", &results); |
|
desc->dont_serialize_results = !results; |
|
} |
|
|
|
static |
|
bool flecs_rest_reply_entity( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
char *path = &req->path[7]; |
|
ecs_dbg_2("rest: request entity '%s'", path); |
|
|
|
ecs_entity_t e = ecs_lookup_path_w_sep( |
|
world, 0, path, "/", NULL, false); |
|
if (!e) { |
|
ecs_dbg_2("rest: entity '%s' not found", path); |
|
flecs_reply_error(reply, "entity '%s' not found", path); |
|
reply->code = 404; |
|
return true; |
|
} |
|
|
|
ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; |
|
flecs_rest_parse_json_ser_entity_params(world, &desc, req); |
|
if (ecs_entity_to_json_buf(world, e, &reply->body, &desc) != 0) { |
|
ecs_strbuf_reset(&reply->body); |
|
reply->code = 500; |
|
reply->status = "Internal server error"; |
|
return true; |
|
} |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rest_reply_world( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
(void)req; |
|
if (ecs_world_to_json_buf(world, &reply->body, NULL) != 0) { |
|
ecs_strbuf_reset(&reply->body); |
|
reply->code = 500; |
|
reply->status = "Internal server error"; |
|
return true; |
|
} |
|
return true; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_rest_entity_from_path( |
|
ecs_world_t *world, |
|
ecs_http_reply_t *reply, |
|
const char *path) |
|
{ |
|
ecs_entity_t e = ecs_lookup_path_w_sep( |
|
world, 0, path, "/", NULL, false); |
|
if (!e) { |
|
flecs_reply_error(reply, "entity '%s' not found", path); |
|
reply->code = 404; |
|
} |
|
return e; |
|
} |
|
|
|
static |
|
bool flecs_rest_set( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply, |
|
const char *path) |
|
{ |
|
ecs_entity_t e; |
|
if (!(e = flecs_rest_entity_from_path(world, reply, path))) { |
|
return true; |
|
} |
|
|
|
const char *data = ecs_http_get_param(req, "data"); |
|
ecs_from_json_desc_t desc = {0}; |
|
desc.expr = data; |
|
desc.name = path; |
|
if (ecs_entity_from_json(world, e, data, &desc) == NULL) { |
|
flecs_reply_error(reply, "invalid request"); |
|
reply->code = 400; |
|
return true; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rest_delete( |
|
ecs_world_t *world, |
|
ecs_http_reply_t *reply, |
|
const char *path) |
|
{ |
|
ecs_entity_t e; |
|
if (!(e = flecs_rest_entity_from_path(world, reply, path))) { |
|
return true; |
|
} |
|
|
|
ecs_delete(world, e); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rest_enable( |
|
ecs_world_t *world, |
|
ecs_http_reply_t *reply, |
|
const char *path, |
|
bool enable) |
|
{ |
|
ecs_entity_t e; |
|
if (!(e = flecs_rest_entity_from_path(world, reply, path))) { |
|
return true; |
|
} |
|
|
|
ecs_enable(world, e, enable); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rest_script( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
(void)world; |
|
(void)req; |
|
(void)reply; |
|
#ifdef FLECS_PLECS |
|
const char *data = ecs_http_get_param(req, "data"); |
|
if (!data) { |
|
flecs_reply_error(reply, "missing data parameter"); |
|
return true; |
|
} |
|
|
|
bool prev_color = ecs_log_enable_colors(false); |
|
rest_prev_log = ecs_os_api.log_; |
|
ecs_os_api.log_ = flecs_rest_capture_log; |
|
|
|
ecs_entity_t script = ecs_script(world, { |
|
.entity = ecs_entity(world, { .name = "scripts.main" }), |
|
.str = data |
|
}); |
|
|
|
if (!script) { |
|
char *err = flecs_rest_get_captured_log(); |
|
char *escaped_err = ecs_astresc('"', err); |
|
flecs_reply_error(reply, escaped_err); |
|
reply->code = 400; /* bad request */ |
|
ecs_os_free(escaped_err); |
|
ecs_os_free(err); |
|
} |
|
|
|
ecs_os_api.log_ = rest_prev_log; |
|
ecs_log_enable_colors(prev_color); |
|
|
|
return true; |
|
#else |
|
return false; |
|
#endif |
|
} |
|
|
|
static |
|
void flecs_rest_reply_set_captured_log( |
|
ecs_http_reply_t *reply) |
|
{ |
|
char *err = flecs_rest_get_captured_log(); |
|
if (err) { |
|
char *escaped_err = ecs_astresc('"', err); |
|
flecs_reply_error(reply, escaped_err); |
|
ecs_os_free(escaped_err); |
|
ecs_os_free(err); |
|
} |
|
|
|
reply->code = 400; |
|
} |
|
|
|
static |
|
void flecs_rest_iter_to_reply( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply, |
|
ecs_iter_t *it) |
|
{ |
|
ecs_iter_to_json_desc_t desc = {0}; |
|
desc.serialize_entities = true; |
|
desc.serialize_variables = true; |
|
flecs_rest_parse_json_ser_iter_params(&desc, req); |
|
|
|
int32_t offset = 0; |
|
int32_t limit = 1000; |
|
|
|
flecs_rest_int_param(req, "offset", &offset); |
|
flecs_rest_int_param(req, "limit", &limit); |
|
|
|
if (offset < 0 || limit < 0) { |
|
flecs_reply_error(reply, "invalid offset/limit parameter"); |
|
return; |
|
} |
|
|
|
ecs_iter_t pit = ecs_page_iter(it, offset, limit); |
|
if (ecs_iter_to_json_buf(world, &pit, &reply->body, &desc)) { |
|
flecs_rest_reply_set_captured_log(reply); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rest_reply_existing_query( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply, |
|
const char *name) |
|
{ |
|
ecs_entity_t q = ecs_lookup_fullpath(world, name); |
|
if (!q) { |
|
flecs_reply_error(reply, "unresolved identifier '%s'", name); |
|
reply->code = 404; |
|
return true; |
|
} |
|
|
|
const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery); |
|
if (!poly) { |
|
flecs_reply_error(reply, |
|
"resolved identifier '%s' is not a query", name); |
|
reply->code = 400; |
|
return true; |
|
} |
|
|
|
ecs_iter_t it; |
|
ecs_iter_poly(world, poly->poly, &it, NULL); |
|
|
|
ecs_dbg_2("rest: request query '%s'", q); |
|
bool prev_color = ecs_log_enable_colors(false); |
|
rest_prev_log = ecs_os_api.log_; |
|
ecs_os_api.log_ = flecs_rest_capture_log; |
|
|
|
const char *vars = ecs_http_get_param(req, "vars"); |
|
if (vars) { |
|
if (!ecs_poly_is(poly->poly, ecs_rule_t)) { |
|
flecs_reply_error(reply, |
|
"variables are only supported for rule queries"); |
|
reply->code = 400; |
|
return true; |
|
} |
|
if (ecs_rule_parse_vars(poly->poly, &it, vars) == NULL) { |
|
flecs_rest_reply_set_captured_log(reply); |
|
return true; |
|
} |
|
} |
|
|
|
flecs_rest_iter_to_reply(world, req, reply, &it); |
|
|
|
ecs_os_api.log_ = rest_prev_log; |
|
ecs_log_enable_colors(prev_color); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rest_reply_query( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
const char *q_name = ecs_http_get_param(req, "name"); |
|
if (q_name) { |
|
return flecs_rest_reply_existing_query(world, req, reply, q_name); |
|
} |
|
|
|
const char *q = ecs_http_get_param(req, "q"); |
|
if (!q) { |
|
ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); |
|
reply->code = 400; /* bad request */ |
|
return true; |
|
} |
|
|
|
bool try = false; |
|
flecs_rest_bool_param(req, "try", &try); |
|
|
|
ecs_dbg_2("rest: request query '%s'", q); |
|
bool prev_color = ecs_log_enable_colors(false); |
|
rest_prev_log = ecs_os_api.log_; |
|
ecs_os_api.log_ = flecs_rest_capture_log; |
|
|
|
ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ |
|
.expr = q |
|
}); |
|
if (!r) { |
|
flecs_rest_reply_set_captured_log(reply); |
|
if (try) { |
|
/* If client is trying queries, don't spam console with errors */ |
|
reply->code = 200; |
|
} |
|
} else { |
|
ecs_iter_t it = ecs_rule_iter(world, r); |
|
flecs_rest_iter_to_reply(world, req, reply, &it); |
|
ecs_rule_fini(r); |
|
} |
|
|
|
ecs_os_api.log_ = rest_prev_log; |
|
ecs_log_enable_colors(prev_color); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rest_reply_query_plan( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
const char *q_name = ecs_http_get_param(req, "name"); |
|
if (q_name) { |
|
reply->code = 400; |
|
ecs_strbuf_appendlit(&reply->body, |
|
"query plan endpoint unsupported for named queries"); |
|
return true; |
|
} |
|
|
|
const char *q = ecs_http_get_param(req, "q"); |
|
if (!q) { |
|
ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); |
|
reply->code = 400; /* bad request */ |
|
return true; |
|
} |
|
|
|
bool try = false; |
|
flecs_rest_bool_param(req, "try", &try); |
|
|
|
ecs_dbg_2("rest: request query plan for '%s'", q); |
|
bool prev_color = ecs_log_enable_colors(false); |
|
rest_prev_log = ecs_os_api.log_; |
|
ecs_os_api.log_ = flecs_rest_capture_log; |
|
|
|
ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ |
|
.expr = q |
|
}); |
|
if (!r) { |
|
flecs_rest_reply_set_captured_log(reply); |
|
if (try) { |
|
/* If client is trying queries, don't spam console with errors */ |
|
reply->code = 200; |
|
} |
|
} else { |
|
ecs_log_enable_colors(true); |
|
char *plan = ecs_rule_str(r); |
|
ecs_strbuf_appendlit(&reply->body, "{\"content\":"); |
|
flecs_json_string_escape(&reply->body, plan); |
|
ecs_strbuf_appendlit(&reply->body, "}"); |
|
ecs_os_free(plan); |
|
ecs_rule_fini(r); |
|
} |
|
|
|
ecs_os_api.log_ = rest_prev_log; |
|
ecs_log_enable_colors(prev_color); |
|
|
|
return true; |
|
} |
|
|
|
#ifdef FLECS_MONITOR |
|
|
|
static |
|
void flecs_rest_array_append_( |
|
ecs_strbuf_t *reply, |
|
const char *field, |
|
int32_t field_len, |
|
const ecs_float_t *values, |
|
int32_t t) |
|
{ |
|
ecs_strbuf_list_appendch(reply, '"'); |
|
ecs_strbuf_appendstrn(reply, field, field_len); |
|
ecs_strbuf_appendlit(reply, "\":"); |
|
ecs_strbuf_list_push(reply, "[", ","); |
|
|
|
int32_t i; |
|
for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { |
|
int32_t index = i % ECS_STAT_WINDOW; |
|
ecs_strbuf_list_next(reply); |
|
ecs_strbuf_appendflt(reply, (double)values[index], '"'); |
|
} |
|
|
|
ecs_strbuf_list_pop(reply, "]"); |
|
} |
|
|
|
#define flecs_rest_array_append(reply, field, values, t)\ |
|
flecs_rest_array_append_(reply, field, sizeof(field) - 1, values, t) |
|
|
|
static |
|
void flecs_rest_gauge_append( |
|
ecs_strbuf_t *reply, |
|
const ecs_metric_t *m, |
|
const char *field, |
|
int32_t field_len, |
|
int32_t t, |
|
const char *brief, |
|
int32_t brief_len) |
|
{ |
|
ecs_strbuf_list_appendch(reply, '"'); |
|
ecs_strbuf_appendstrn(reply, field, field_len); |
|
ecs_strbuf_appendlit(reply, "\":"); |
|
ecs_strbuf_list_push(reply, "{", ","); |
|
|
|
flecs_rest_array_append(reply, "avg", m->gauge.avg, t); |
|
flecs_rest_array_append(reply, "min", m->gauge.min, t); |
|
flecs_rest_array_append(reply, "max", m->gauge.max, t); |
|
|
|
if (brief) { |
|
ecs_strbuf_list_appendlit(reply, "\"brief\":\""); |
|
ecs_strbuf_appendstrn(reply, brief, brief_len); |
|
ecs_strbuf_appendch(reply, '"'); |
|
} |
|
|
|
ecs_strbuf_list_pop(reply, "}"); |
|
} |
|
|
|
static |
|
void flecs_rest_counter_append( |
|
ecs_strbuf_t *reply, |
|
const ecs_metric_t *m, |
|
const char *field, |
|
int32_t field_len, |
|
int32_t t, |
|
const char *brief, |
|
int32_t brief_len) |
|
{ |
|
flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); |
|
} |
|
|
|
#define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ |
|
flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) |
|
|
|
#define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ |
|
flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) |
|
|
|
#define ECS_GAUGE_APPEND(reply, s, field, brief)\ |
|
ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) |
|
|
|
#define ECS_COUNTER_APPEND(reply, s, field, brief)\ |
|
ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) |
|
|
|
static |
|
void flecs_world_stats_to_json( |
|
ecs_strbuf_t *reply, |
|
const EcsWorldStats *monitor_stats) |
|
{ |
|
const ecs_world_stats_t *stats = &monitor_stats->stats; |
|
|
|
ecs_strbuf_list_push(reply, "{", ","); |
|
ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); |
|
ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); |
|
|
|
ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); |
|
ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); |
|
ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); |
|
ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); |
|
ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); |
|
ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); |
|
|
|
ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.get_mut_count, "Get_mut commands executed"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); |
|
ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); |
|
|
|
ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); |
|
ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); |
|
ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); |
|
ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); |
|
ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); |
|
ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); |
|
|
|
ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); |
|
ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); |
|
ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); |
|
ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); |
|
|
|
ECS_GAUGE_APPEND(reply, stats, components.tag_count, "Tag ids in use"); |
|
ECS_GAUGE_APPEND(reply, stats, components.component_count, "Component ids in use"); |
|
ECS_GAUGE_APPEND(reply, stats, components.pair_count, "Pair ids in use"); |
|
ECS_GAUGE_APPEND(reply, stats, components.type_count, "Registered component types"); |
|
ECS_COUNTER_APPEND(reply, stats, components.create_count, "Number of new component, tag and pair ids created"); |
|
ECS_COUNTER_APPEND(reply, stats, components.delete_count, "Number of component, pair and tag ids deleted"); |
|
|
|
ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); |
|
ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); |
|
ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); |
|
|
|
ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); |
|
ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); |
|
ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); |
|
ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); |
|
ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); |
|
ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); |
|
ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); |
|
ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); |
|
ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); |
|
ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); |
|
|
|
ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); |
|
ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); |
|
ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); |
|
ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); |
|
ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); |
|
ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); |
|
ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); |
|
ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); |
|
ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); |
|
|
|
ecs_strbuf_list_pop(reply, "}"); |
|
} |
|
|
|
static |
|
void flecs_system_stats_to_json( |
|
ecs_world_t *world, |
|
ecs_strbuf_t *reply, |
|
ecs_entity_t system, |
|
const ecs_system_stats_t *stats) |
|
{ |
|
ecs_strbuf_list_push(reply, "{", ","); |
|
ecs_strbuf_list_appendlit(reply, "\"name\":\""); |
|
ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply); |
|
ecs_strbuf_appendch(reply, '"'); |
|
|
|
if (!stats->task) { |
|
ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); |
|
ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); |
|
} |
|
|
|
ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); |
|
ecs_strbuf_list_pop(reply, "}"); |
|
} |
|
|
|
static |
|
void flecs_pipeline_stats_to_json( |
|
ecs_world_t *world, |
|
ecs_strbuf_t *reply, |
|
const EcsPipelineStats *stats) |
|
{ |
|
ecs_strbuf_list_push(reply, "[", ","); |
|
|
|
int32_t i, count = ecs_vec_count(&stats->stats.systems), sync_cur = 0; |
|
ecs_entity_t *ids = ecs_vec_first_t(&stats->stats.systems, ecs_entity_t); |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t id = ids[i]; |
|
|
|
ecs_strbuf_list_next(reply); |
|
|
|
if (id) { |
|
ecs_system_stats_t *sys_stats = ecs_map_get_deref( |
|
&stats->stats.system_stats, ecs_system_stats_t, id); |
|
flecs_system_stats_to_json(world, reply, id, sys_stats); |
|
} else { |
|
/* Sync point */ |
|
ecs_strbuf_list_push(reply, "{", ","); |
|
ecs_sync_stats_t *sync_stats = ecs_vec_get_t( |
|
&stats->stats.sync_points, ecs_sync_stats_t, sync_cur); |
|
|
|
ecs_strbuf_list_appendlit(reply, "\"system_count\":"); |
|
ecs_strbuf_appendint(reply, sync_stats->system_count); |
|
|
|
ecs_strbuf_list_appendlit(reply, "\"multi_threaded\":"); |
|
ecs_strbuf_appendbool(reply, sync_stats->multi_threaded); |
|
|
|
ecs_strbuf_list_appendlit(reply, "\"no_readonly\":"); |
|
ecs_strbuf_appendbool(reply, sync_stats->no_readonly); |
|
|
|
ECS_GAUGE_APPEND_T(reply, sync_stats, |
|
time_spent, stats->stats.t, ""); |
|
ECS_GAUGE_APPEND_T(reply, sync_stats, |
|
commands_enqueued, stats->stats.t, ""); |
|
|
|
ecs_strbuf_list_pop(reply, "}"); |
|
sync_cur ++; |
|
} |
|
} |
|
|
|
ecs_strbuf_list_pop(reply, "]"); |
|
} |
|
|
|
static |
|
bool flecs_rest_reply_stats( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
char *period_str = NULL; |
|
flecs_rest_string_param(req, "period", &period_str); |
|
char *category = &req->path[6]; |
|
|
|
ecs_entity_t period = EcsPeriod1s; |
|
if (period_str) { |
|
char *period_name = ecs_asprintf("Period%s", period_str); |
|
period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); |
|
ecs_os_free(period_name); |
|
if (!period) { |
|
flecs_reply_error(reply, "bad request (invalid period string)"); |
|
reply->code = 400; |
|
return false; |
|
} |
|
} |
|
|
|
if (!ecs_os_strcmp(category, "world")) { |
|
const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, |
|
EcsWorldStats, period); |
|
flecs_world_stats_to_json(&reply->body, stats); |
|
return true; |
|
|
|
} else if (!ecs_os_strcmp(category, "pipeline")) { |
|
const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, |
|
EcsPipelineStats, period); |
|
flecs_pipeline_stats_to_json(world, &reply->body, stats); |
|
return true; |
|
|
|
} else { |
|
flecs_reply_error(reply, "bad request (unsupported category)"); |
|
reply->code = 400; |
|
return false; |
|
} |
|
} |
|
#else |
|
static |
|
bool flecs_rest_reply_stats( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
(void)world; |
|
(void)req; |
|
(void)reply; |
|
return false; |
|
} |
|
#endif |
|
|
|
static |
|
void flecs_rest_reply_table_append_type( |
|
ecs_world_t *world, |
|
ecs_strbuf_t *reply, |
|
const ecs_table_t *table) |
|
{ |
|
ecs_strbuf_list_push(reply, "[", ","); |
|
int32_t i, count = table->type.count; |
|
ecs_id_t *ids = table->type.array; |
|
for (i = 0; i < count; i ++) { |
|
ecs_strbuf_list_next(reply); |
|
ecs_strbuf_appendch(reply, '"'); |
|
ecs_id_str_buf(world, ids[i], reply); |
|
ecs_strbuf_appendch(reply, '"'); |
|
} |
|
ecs_strbuf_list_pop(reply, "]"); |
|
} |
|
|
|
static |
|
void flecs_rest_reply_table_append_memory( |
|
ecs_strbuf_t *reply, |
|
const ecs_table_t *table) |
|
{ |
|
int32_t used = 0, allocated = 0; |
|
|
|
used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); |
|
allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); |
|
|
|
int32_t i, storage_count = table->column_count; |
|
ecs_column_t *columns = table->data.columns; |
|
|
|
for (i = 0; i < storage_count; i ++) { |
|
used += columns[i].data.count * columns[i].ti->size; |
|
allocated += columns[i].data.size * columns[i].ti->size; |
|
} |
|
|
|
ecs_strbuf_list_push(reply, "{", ","); |
|
ecs_strbuf_list_append(reply, "\"used\":%d", used); |
|
ecs_strbuf_list_append(reply, "\"allocated\":%d", allocated); |
|
ecs_strbuf_list_pop(reply, "}"); |
|
} |
|
|
|
static |
|
void flecs_rest_reply_table_append( |
|
ecs_world_t *world, |
|
ecs_strbuf_t *reply, |
|
const ecs_table_t *table) |
|
{ |
|
ecs_strbuf_list_next(reply); |
|
ecs_strbuf_list_push(reply, "{", ","); |
|
ecs_strbuf_list_append(reply, "\"id\":%u", (uint32_t)table->id); |
|
ecs_strbuf_list_appendstr(reply, "\"type\":"); |
|
flecs_rest_reply_table_append_type(world, reply, table); |
|
ecs_strbuf_list_append(reply, "\"count\":%d", ecs_table_count(table)); |
|
ecs_strbuf_list_append(reply, "\"memory\":"); |
|
flecs_rest_reply_table_append_memory(reply, table); |
|
ecs_strbuf_list_pop(reply, "}"); |
|
} |
|
|
|
static |
|
bool flecs_rest_reply_tables( |
|
ecs_world_t *world, |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply) |
|
{ |
|
(void)req; |
|
|
|
ecs_strbuf_list_push(&reply->body, "[", ","); |
|
ecs_sparse_t *tables = &world->store.tables; |
|
int32_t i, count = flecs_sparse_count(tables); |
|
for (i = 0; i < count; i ++) { |
|
ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); |
|
flecs_rest_reply_table_append(world, &reply->body, table); |
|
} |
|
ecs_strbuf_list_pop(&reply->body, "]"); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rest_reply( |
|
const ecs_http_request_t* req, |
|
ecs_http_reply_t *reply, |
|
void *ctx) |
|
{ |
|
ecs_rest_ctx_t *impl = ctx; |
|
ecs_world_t *world = impl->world; |
|
|
|
if (req->path == NULL) { |
|
ecs_dbg("rest: bad request (missing path)"); |
|
flecs_reply_error(reply, "bad request (missing path)"); |
|
reply->code = 400; |
|
return false; |
|
} |
|
|
|
if (req->method == EcsHttpGet) { |
|
/* Entity endpoint */ |
|
if (!ecs_os_strncmp(req->path, "entity/", 7)) { |
|
return flecs_rest_reply_entity(world, req, reply); |
|
|
|
/* Query endpoint */ |
|
} else if (!ecs_os_strcmp(req->path, "query")) { |
|
return flecs_rest_reply_query(world, req, reply); |
|
|
|
/* Query plan endpoint */ |
|
} else if (!ecs_os_strcmp(req->path, "query_plan")) { |
|
return flecs_rest_reply_query_plan(world, req, reply); |
|
|
|
/* World endpoint */ |
|
} else if (!ecs_os_strcmp(req->path, "world")) { |
|
return flecs_rest_reply_world(world, req, reply); |
|
|
|
/* Stats endpoint */ |
|
} else if (!ecs_os_strncmp(req->path, "stats/", 6)) { |
|
return flecs_rest_reply_stats(world, req, reply); |
|
|
|
/* Tables endpoint */ |
|
} else if (!ecs_os_strncmp(req->path, "tables", 6)) { |
|
return flecs_rest_reply_tables(world, req, reply); |
|
} |
|
|
|
} else if (req->method == EcsHttpPut) { |
|
/* Set endpoint */ |
|
if (!ecs_os_strncmp(req->path, "set/", 4)) { |
|
return flecs_rest_set(world, req, reply, &req->path[4]); |
|
|
|
/* Delete endpoint */ |
|
} else if (!ecs_os_strncmp(req->path, "delete/", 7)) { |
|
return flecs_rest_delete(world, reply, &req->path[7]); |
|
|
|
/* Enable endpoint */ |
|
} else if (!ecs_os_strncmp(req->path, "enable/", 7)) { |
|
return flecs_rest_enable(world, reply, &req->path[7], true); |
|
|
|
/* Disable endpoint */ |
|
} else if (!ecs_os_strncmp(req->path, "disable/", 8)) { |
|
return flecs_rest_enable(world, reply, &req->path[8], false); |
|
|
|
/* Script endpoint */ |
|
} else if (!ecs_os_strncmp(req->path, "script", 6)) { |
|
return flecs_rest_script(world, req, reply); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
ecs_http_server_t* ecs_rest_server_init( |
|
ecs_world_t *world, |
|
const ecs_http_server_desc_t *desc) |
|
{ |
|
ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); |
|
ecs_http_server_desc_t private_desc = {0}; |
|
if (desc) { |
|
private_desc = *desc; |
|
} |
|
private_desc.callback = flecs_rest_reply; |
|
private_desc.ctx = srv_ctx; |
|
|
|
ecs_http_server_t *srv = ecs_http_server_init(&private_desc); |
|
if (!srv) { |
|
ecs_os_free(srv_ctx); |
|
return NULL; |
|
} |
|
|
|
srv_ctx->world = world; |
|
srv_ctx->srv = srv; |
|
srv_ctx->rc = 1; |
|
srv_ctx->srv = srv; |
|
return srv; |
|
} |
|
|
|
void ecs_rest_server_fini( |
|
ecs_http_server_t *srv) |
|
{ |
|
ecs_rest_ctx_t *srv_ctx = ecs_http_server_ctx(srv); |
|
ecs_os_free(srv_ctx); |
|
ecs_http_server_fini(srv); |
|
} |
|
|
|
static |
|
void flecs_on_set_rest(ecs_iter_t *it) { |
|
EcsRest *rest = it->ptrs[0]; |
|
|
|
int i; |
|
for(i = 0; i < it->count; i ++) { |
|
if (!rest[i].port) { |
|
rest[i].port = ECS_REST_DEFAULT_PORT; |
|
} |
|
|
|
ecs_http_server_t *srv = ecs_rest_server_init(it->real_world, |
|
&(ecs_http_server_desc_t){ |
|
.ipaddr = rest[i].ipaddr, |
|
.port = rest[i].port, |
|
.cache_timeout = 1.0 |
|
}); |
|
|
|
if (!srv) { |
|
const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; |
|
ecs_err("failed to create REST server on %s:%u", |
|
ipaddr, rest[i].port); |
|
continue; |
|
} |
|
|
|
rest[i].impl = ecs_http_server_ctx(srv); |
|
|
|
ecs_http_server_start(srv); |
|
} |
|
} |
|
|
|
static |
|
void DequeueRest(ecs_iter_t *it) { |
|
EcsRest *rest = ecs_field(it, EcsRest, 1); |
|
|
|
if (it->delta_system_time > (ecs_ftime_t)1.0) { |
|
ecs_warn( |
|
"detected large progress interval (%.2fs), REST request may timeout", |
|
(double)it->delta_system_time); |
|
} |
|
|
|
int32_t i; |
|
for(i = 0; i < it->count; i ++) { |
|
ecs_rest_ctx_t *ctx = rest[i].impl; |
|
if (ctx) { |
|
ecs_http_server_dequeue(ctx->srv, it->delta_time); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void DisableRest(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
|
|
ecs_iter_t rit = ecs_term_iter(world, &(ecs_term_t){ |
|
.id = ecs_id(EcsRest), |
|
.src.flags = EcsSelf |
|
}); |
|
|
|
if (it->event == EcsOnAdd) { |
|
/* REST module was disabled */ |
|
while (ecs_term_next(&rit)) { |
|
EcsRest *rest = ecs_field(&rit, EcsRest, 1); |
|
int i; |
|
for (i = 0; i < rit.count; i ++) { |
|
ecs_rest_ctx_t *ctx = rest[i].impl; |
|
ecs_http_server_stop(ctx->srv); |
|
} |
|
} |
|
} else if (it->event == EcsOnRemove) { |
|
/* REST module was enabled */ |
|
while (ecs_term_next(&rit)) { |
|
EcsRest *rest = ecs_field(&rit, EcsRest, 1); |
|
int i; |
|
for (i = 0; i < rit.count; i ++) { |
|
ecs_rest_ctx_t *ctx = rest[i].impl; |
|
ecs_http_server_start(ctx->srv); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void FlecsRestImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsRest); |
|
|
|
ECS_IMPORT(world, FlecsPipeline); |
|
#ifdef FLECS_PLECS |
|
ECS_IMPORT(world, FlecsScript); |
|
#endif |
|
#ifdef FLECS_COREDOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
ecs_doc_set_brief(world, ecs_id(FlecsRest), |
|
"Module that implements Flecs REST API"); |
|
#endif |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
flecs_bootstrap_component(world, EcsRest); |
|
|
|
ecs_set_hooks(world, EcsRest, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsRest), |
|
.copy = ecs_copy(EcsRest), |
|
.dtor = ecs_dtor(EcsRest), |
|
.on_set = flecs_on_set_rest |
|
}); |
|
|
|
ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); |
|
|
|
ecs_system(world, { |
|
.entity = ecs_id(DequeueRest), |
|
.no_readonly = true |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter = { |
|
.terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} |
|
}, |
|
.events = {EcsOnAdd, EcsOnRemove}, |
|
.callback = DisableRest |
|
}); |
|
|
|
ecs_set_name_prefix(world, "EcsRest"); |
|
ECS_TAG_DEFINE(world, EcsRestPlecs); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/snapshot.c |
|
* @brief Snapshot addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_SNAPSHOT |
|
|
|
|
|
/* World snapshot */ |
|
struct ecs_snapshot_t { |
|
ecs_world_t *world; |
|
ecs_entity_index_t entity_index; |
|
ecs_vec_t tables; |
|
uint64_t last_id; |
|
}; |
|
|
|
/** Small footprint data structure for storing data associated with a table. */ |
|
typedef struct ecs_table_leaf_t { |
|
ecs_table_t *table; |
|
ecs_type_t type; |
|
ecs_data_t *data; |
|
} ecs_table_leaf_t; |
|
|
|
static |
|
ecs_data_t* flecs_duplicate_data( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *main_data) |
|
{ |
|
int32_t count = ecs_table_count(table); |
|
if (!count) { |
|
return NULL; |
|
} |
|
|
|
ecs_data_t *result = ecs_os_calloc_t(ecs_data_t); |
|
int32_t i, column_count = table->column_count; |
|
result->columns = flecs_wdup_n(world, ecs_column_t, column_count, |
|
main_data->columns); |
|
|
|
/* Copy entities */ |
|
ecs_allocator_t *a = &world->allocator; |
|
result->entities = ecs_vec_copy_shrink_t(a, &main_data->entities, ecs_entity_t); |
|
|
|
/* Copy each column */ |
|
for (i = 0; i < column_count; i ++) { |
|
ecs_column_t *column = &result->columns[i]; |
|
ecs_type_info_t *ti = column->ti; |
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t size = ti->size; |
|
ecs_copy_t copy = ti->hooks.copy_ctor; |
|
if (copy) { |
|
void *src_ptr = ecs_vec_first(&column->data); |
|
|
|
ecs_vec_t dst; |
|
ecs_vec_init(a, &dst, size, count); |
|
ecs_vec_set_count(a, &dst, size, count); |
|
void *dst_ptr = ecs_vec_first(&dst); |
|
|
|
copy(dst_ptr, src_ptr, count, ti); |
|
|
|
column->data = dst; |
|
} else { |
|
column->data = ecs_vec_copy_shrink(a, &column->data, size); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static |
|
void snapshot_table( |
|
const ecs_world_t *world, |
|
ecs_snapshot_t *snapshot, |
|
ecs_table_t *table) |
|
{ |
|
if (table->flags & EcsTableHasBuiltins) { |
|
return; |
|
} |
|
|
|
ecs_table_leaf_t *l = ecs_vec_get_t( |
|
&snapshot->tables, ecs_table_leaf_t, (int32_t)table->id); |
|
ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
l->table = table; |
|
l->type = flecs_type_copy( |
|
ECS_CONST_CAST(ecs_world_t*, world), &table->type); |
|
l->data = flecs_duplicate_data( |
|
ECS_CONST_CAST(ecs_world_t*, world), table, &table->data); |
|
} |
|
|
|
static |
|
ecs_snapshot_t* snapshot_create( |
|
const ecs_world_t *world, |
|
const ecs_entity_index_t *entity_index, |
|
ecs_iter_t *iter, |
|
ecs_iter_next_action_t next) |
|
{ |
|
ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); |
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
|
|
ecs_run_aperiodic(ECS_CONST_CAST(ecs_world_t*, world), 0); |
|
|
|
result->world = ECS_CONST_CAST(ecs_world_t*, world); |
|
|
|
/* If no iterator is provided, the snapshot will be taken of the entire |
|
* world, and we can simply copy the entity index as it will be restored |
|
* entirely upon snapshote restore. */ |
|
if (!iter && entity_index) { |
|
flecs_entities_copy(&result->entity_index, entity_index); |
|
} |
|
|
|
/* Create vector with as many elements as tables, so we can store the |
|
* snapshot tables at their element ids. When restoring a snapshot, the code |
|
* will run a diff between the tables in the world and the snapshot, to see |
|
* which of the world tables still exist, no longer exist, or need to be |
|
* deleted. */ |
|
uint64_t t, table_count = flecs_sparse_last_id(&world->store.tables) + 1; |
|
ecs_vec_init_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count); |
|
ecs_vec_set_count_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count); |
|
ecs_table_leaf_t *arr = ecs_vec_first_t(&result->tables, ecs_table_leaf_t); |
|
|
|
/* Array may have holes, so initialize with 0 */ |
|
ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); |
|
|
|
/* Iterate tables in iterator */ |
|
if (iter) { |
|
while (next(iter)) { |
|
ecs_table_t *table = iter->table; |
|
snapshot_table(world, result, table); |
|
} |
|
} else { |
|
for (t = 1; t < table_count; t ++) { |
|
ecs_table_t *table = flecs_sparse_get_t( |
|
&world->store.tables, ecs_table_t, t); |
|
snapshot_table(world, result, table); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
/** Create a snapshot */ |
|
ecs_snapshot_t* ecs_snapshot_take( |
|
ecs_world_t *stage) |
|
{ |
|
const ecs_world_t *world = ecs_get_world(stage); |
|
|
|
ecs_snapshot_t *result = snapshot_create( |
|
world, ecs_eis(world), NULL, NULL); |
|
|
|
result->last_id = flecs_entities_max_id(world); |
|
|
|
return result; |
|
} |
|
|
|
/** Create a filtered snapshot */ |
|
ecs_snapshot_t* ecs_snapshot_take_w_iter( |
|
ecs_iter_t *iter) |
|
{ |
|
ecs_world_t *world = iter->world; |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_snapshot_t *result = snapshot_create( |
|
world, ecs_eis(world), iter, iter ? iter->next : NULL); |
|
|
|
result->last_id = flecs_entities_max_id(world); |
|
|
|
return result; |
|
} |
|
|
|
/* Restoring an unfiltered snapshot restores the world to the exact state it was |
|
* when the snapshot was taken. */ |
|
static |
|
void restore_unfiltered( |
|
ecs_world_t *world, |
|
ecs_snapshot_t *snapshot) |
|
{ |
|
flecs_entity_index_restore(ecs_eis(world), &snapshot->entity_index); |
|
flecs_entity_index_fini(&snapshot->entity_index); |
|
|
|
flecs_entities_max_id(world) = snapshot->last_id; |
|
|
|
ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); |
|
int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables); |
|
int32_t snapshot_count = ecs_vec_count(&snapshot->tables); |
|
|
|
for (i = 1; i <= count; i ++) { |
|
ecs_table_t *world_table = flecs_sparse_get_t( |
|
&world->store.tables, ecs_table_t, (uint32_t)i); |
|
|
|
if (world_table && (world_table->flags & EcsTableHasBuiltins)) { |
|
continue; |
|
} |
|
|
|
ecs_table_leaf_t *snapshot_table = NULL; |
|
if (i < snapshot_count) { |
|
snapshot_table = &leafs[i]; |
|
if (!snapshot_table->table) { |
|
snapshot_table = NULL; |
|
} |
|
} |
|
|
|
/* If the world table no longer exists but the snapshot table does, |
|
* reinsert it */ |
|
if (!world_table && snapshot_table) { |
|
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) { |
|
flecs_table_replace_data(world, table, snapshot_table->data); |
|
} |
|
|
|
/* If the world table still exists, replace its data */ |
|
} else if (world_table && snapshot_table) { |
|
ecs_assert(snapshot_table->table == world_table, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (snapshot_table->data) { |
|
flecs_table_replace_data( |
|
world, world_table, snapshot_table->data); |
|
} else { |
|
flecs_table_clear_data( |
|
world, world_table, &world_table->data); |
|
flecs_table_init_data(world, world_table); |
|
} |
|
|
|
/* If the snapshot table doesn't exist, this table was created after the |
|
* snapshot was taken and needs to be deleted */ |
|
} else if (world_table && !snapshot_table) { |
|
/* Deleting a table invokes OnRemove triggers & updates the entity |
|
* index. That is not what we want, since entities may no longer be |
|
* valid (if they don't exist in the snapshot) or may have been |
|
* 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->data); |
|
flecs_delete_table(world, world_table); |
|
|
|
/* If there is no world & snapshot table, nothing needs to be done */ |
|
} else { } |
|
|
|
if (snapshot_table) { |
|
ecs_os_free(snapshot_table->data); |
|
flecs_type_free(world, &snapshot_table->type); |
|
} |
|
} |
|
|
|
/* Now that all tables have been restored and world is in a consistent |
|
* state, run OnSet systems */ |
|
int32_t world_count = flecs_sparse_count(&world->store.tables); |
|
for (i = 0; i < world_count; i ++) { |
|
ecs_table_t *table = flecs_sparse_get_dense_t( |
|
&world->store.tables, ecs_table_t, i); |
|
if (table->flags & EcsTableHasBuiltins) { |
|
continue; |
|
} |
|
|
|
int32_t tcount = ecs_table_count(table); |
|
if (tcount) { |
|
int32_t j, storage_count = table->column_count; |
|
for (j = 0; j < storage_count; j ++) { |
|
ecs_type_t type = { |
|
.array = &table->data.columns[j].id, |
|
.count = 1 |
|
}; |
|
flecs_notify_on_set(world, table, 0, tcount, &type, true); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Restoring a filtered snapshots only restores the entities in the snapshot |
|
* to their previous state. */ |
|
static |
|
void restore_filtered( |
|
ecs_world_t *world, |
|
ecs_snapshot_t *snapshot) |
|
{ |
|
ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); |
|
int32_t l = 0, snapshot_count = ecs_vec_count(&snapshot->tables); |
|
|
|
for (l = 0; l < snapshot_count; l ++) { |
|
ecs_table_leaf_t *snapshot_table = &leafs[l]; |
|
ecs_table_t *table = snapshot_table->table; |
|
|
|
if (!table) { |
|
continue; |
|
} |
|
|
|
ecs_data_t *data = snapshot_table->data; |
|
if (!data) { |
|
flecs_type_free(world, &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_vec_count(&data->entities); |
|
ecs_entity_t *entities = ecs_vec_first( |
|
&snapshot_table->data->entities); |
|
for (i = 0; i < entity_count; i ++) { |
|
ecs_entity_t e = entities[i]; |
|
ecs_record_t *r = flecs_entities_try(world, e); |
|
if (r && r->table) { |
|
flecs_table_delete(world, r->table, |
|
ECS_RECORD_TO_ROW(r->row), true); |
|
} else { |
|
/* Make sure that the entity has the same generation count */ |
|
flecs_entities_set_generation(world, e); |
|
} |
|
} |
|
|
|
/* Merge data from snapshot table with world table */ |
|
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->data, snapshot_table->data); |
|
|
|
/* Run OnSet systems for merged entities */ |
|
if (new_count) { |
|
int32_t j, storage_count = table->column_count; |
|
for (j = 0; j < storage_count; j ++) { |
|
ecs_type_t type = { |
|
.array = &table->data.columns[j].id, |
|
.count = 1 |
|
}; |
|
flecs_notify_on_set( |
|
world, table, old_count, new_count, &type, true); |
|
} |
|
} |
|
|
|
flecs_wfree_n(world, ecs_column_t, table->column_count, |
|
snapshot_table->data->columns); |
|
ecs_os_free(snapshot_table->data); |
|
flecs_type_free(world, &snapshot_table->type); |
|
} |
|
} |
|
|
|
/** Restore a snapshot */ |
|
void ecs_snapshot_restore( |
|
ecs_world_t *world, |
|
ecs_snapshot_t *snapshot) |
|
{ |
|
ecs_run_aperiodic(world, 0); |
|
|
|
if (flecs_entity_index_count(&snapshot->entity_index) > 0) { |
|
/* Unfiltered snapshots have a copy of the entity index which is |
|
* copied back entirely when the snapshot is restored */ |
|
restore_unfiltered(world, snapshot); |
|
} else { |
|
restore_filtered(world, snapshot); |
|
} |
|
|
|
ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); |
|
|
|
ecs_os_free(snapshot); |
|
} |
|
|
|
ecs_iter_t ecs_snapshot_iter( |
|
ecs_snapshot_t *snapshot) |
|
{ |
|
ecs_snapshot_iter_t iter = { |
|
.tables = snapshot->tables, |
|
.index = 0 |
|
}; |
|
|
|
return (ecs_iter_t){ |
|
.world = snapshot->world, |
|
.table_count = ecs_vec_count(&snapshot->tables), |
|
.priv.iter.snapshot = iter, |
|
.next = ecs_snapshot_next |
|
}; |
|
} |
|
|
|
bool ecs_snapshot_next( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_snapshot_iter_t *iter = &it->priv.iter.snapshot; |
|
ecs_table_leaf_t *tables = ecs_vec_first_t(&iter->tables, ecs_table_leaf_t); |
|
int32_t count = ecs_vec_count(&iter->tables); |
|
int32_t i; |
|
|
|
for (i = iter->index; i < count; i ++) { |
|
ecs_table_t *table = tables[i].table; |
|
if (!table) { |
|
continue; |
|
} |
|
|
|
ecs_data_t *data = tables[i].data; |
|
|
|
it->table = table; |
|
it->count = ecs_table_count(table); |
|
if (data) { |
|
it->entities = ecs_vec_first(&data->entities); |
|
} else { |
|
it->entities = NULL; |
|
} |
|
|
|
ECS_BIT_SET(it->flags, EcsIterIsValid); |
|
iter->index = i + 1; |
|
|
|
goto yield; |
|
} |
|
|
|
ECS_BIT_CLEAR(it->flags, EcsIterIsValid); |
|
return false; |
|
|
|
yield: |
|
ECS_BIT_CLEAR(it->flags, EcsIterIsValid); |
|
return true; |
|
} |
|
|
|
/** Cleanup snapshot */ |
|
void ecs_snapshot_free( |
|
ecs_snapshot_t *snapshot) |
|
{ |
|
flecs_entity_index_fini(&snapshot->entity_index); |
|
|
|
ecs_table_leaf_t *tables = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); |
|
int32_t i, count = ecs_vec_count(&snapshot->tables); |
|
for (i = 0; i < count; i ++) { |
|
ecs_table_leaf_t *snapshot_table = &tables[i]; |
|
ecs_table_t *table = snapshot_table->table; |
|
if (table) { |
|
ecs_data_t *data = snapshot_table->data; |
|
if (data) { |
|
flecs_table_clear_data(snapshot->world, table, data); |
|
ecs_os_free(data); |
|
} |
|
flecs_type_free(snapshot->world, &snapshot_table->type); |
|
} |
|
} |
|
|
|
ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); |
|
ecs_os_free(snapshot); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/stats.c |
|
* @brief Stats addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_SYSTEM |
|
/** |
|
* @file addons/system/system.h |
|
* @brief Internal types and functions for system addon. |
|
*/ |
|
|
|
#ifndef FLECS_SYSTEM_PRIVATE_H |
|
#define FLECS_SYSTEM_PRIVATE_H |
|
|
|
#ifdef FLECS_SYSTEM |
|
|
|
|
|
#define ecs_system_t_magic (0x65637383) |
|
#define ecs_system_t_tag EcsSystem |
|
|
|
extern ecs_mixins_t ecs_system_t_mixins; |
|
|
|
typedef struct ecs_system_t { |
|
ecs_header_t hdr; |
|
|
|
ecs_run_action_t run; /* See ecs_system_desc_t */ |
|
ecs_iter_action_t action; /* See ecs_system_desc_t */ |
|
|
|
ecs_query_t *query; /* System query */ |
|
ecs_entity_t query_entity; /* Entity associated with query */ |
|
ecs_entity_t tick_source; /* Tick source associated with system */ |
|
|
|
/* Schedule parameters */ |
|
bool multi_threaded; |
|
bool no_readonly; |
|
|
|
int64_t invoke_count; /* Number of times system is invoked */ |
|
ecs_ftime_t time_spent; /* Time spent on running system */ |
|
ecs_ftime_t time_passed; /* Time passed since last invocation */ |
|
int64_t last_frame; /* Last frame for which the system was considered */ |
|
|
|
void *ctx; /* Userdata for system */ |
|
void *binding_ctx; /* Optional language binding context */ |
|
|
|
ecs_ctx_free_t ctx_free; |
|
ecs_ctx_free_t binding_ctx_free; |
|
|
|
/* Mixins */ |
|
ecs_world_t *world; |
|
ecs_entity_t entity; |
|
ecs_poly_dtor_t dtor; |
|
} ecs_system_t; |
|
|
|
/* Invoked when system becomes active / inactive */ |
|
void ecs_system_activate( |
|
ecs_world_t *world, |
|
ecs_entity_t system, |
|
bool activate, |
|
const ecs_system_t *system_data); |
|
|
|
/* Internal function to run a system */ |
|
ecs_entity_t ecs_run_intern( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_entity_t system, |
|
ecs_system_t *system_data, |
|
int32_t stage_current, |
|
int32_t stage_count, |
|
ecs_ftime_t delta_time, |
|
int32_t offset, |
|
int32_t limit, |
|
void *param); |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
#ifdef FLECS_PIPELINE |
|
/** |
|
* @file addons/pipeline/pipeline.h |
|
* @brief Internal functions/types for pipeline addon. |
|
*/ |
|
|
|
#ifndef FLECS_PIPELINE_PRIVATE_H |
|
#define FLECS_PIPELINE_PRIVATE_H |
|
|
|
|
|
/** Instruction data for pipeline. |
|
* This type is the element type in the "ops" vector of a pipeline. */ |
|
typedef struct ecs_pipeline_op_t { |
|
int32_t offset; /* Offset in systems vector */ |
|
int32_t count; /* Number of systems to run before next op */ |
|
double time_spent; /* Time spent merging commands for sync point */ |
|
int64_t commands_enqueued; /* Number of commands enqueued for sync point */ |
|
bool multi_threaded; /* Whether systems can be ran multi threaded */ |
|
bool no_readonly; /* Whether systems are staged or not */ |
|
} ecs_pipeline_op_t; |
|
|
|
struct ecs_pipeline_state_t { |
|
ecs_query_t *query; /* Pipeline query */ |
|
ecs_vec_t ops; /* Pipeline schedule */ |
|
ecs_vec_t systems; /* Vector with system ids */ |
|
|
|
ecs_entity_t last_system; /* Last system ran by pipeline */ |
|
ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ |
|
int32_t match_count; /* Used to track of rebuild is necessary */ |
|
int32_t rebuild_count; /* Number of pipeline rebuilds */ |
|
ecs_iter_t *iters; /* Iterator for worker(s) */ |
|
int32_t iter_count; |
|
|
|
/* Members for continuing pipeline iteration after pipeline rebuild */ |
|
ecs_pipeline_op_t *cur_op; /* Current pipeline op */ |
|
int32_t cur_i; /* Index in current result */ |
|
int32_t ran_since_merge; /* Index in current op */ |
|
bool no_readonly; /* Is pipeline in readonly mode */ |
|
}; |
|
|
|
typedef struct EcsPipeline { |
|
/* Stable ptr so threads can safely access while entity/components move */ |
|
ecs_pipeline_state_t *state; |
|
} EcsPipeline; |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
//// Pipeline API |
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
bool flecs_pipeline_update( |
|
ecs_world_t *world, |
|
ecs_pipeline_state_t *pq, |
|
bool start_of_frame); |
|
|
|
void flecs_run_pipeline( |
|
ecs_world_t *world, |
|
ecs_pipeline_state_t *pq, |
|
ecs_ftime_t delta_time); |
|
|
|
int32_t flecs_run_pipeline_ops( |
|
ecs_world_t* world, |
|
ecs_stage_t* stage, |
|
int32_t stage_index, |
|
int32_t stage_count, |
|
ecs_ftime_t delta_time); |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
//// Worker API |
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void flecs_workers_progress( |
|
ecs_world_t *world, |
|
ecs_pipeline_state_t *pq, |
|
ecs_ftime_t delta_time); |
|
|
|
void flecs_create_worker_threads( |
|
ecs_world_t *world); |
|
|
|
void flecs_join_worker_threads( |
|
ecs_world_t *world); |
|
|
|
void flecs_signal_workers( |
|
ecs_world_t *world); |
|
|
|
void flecs_wait_for_sync( |
|
ecs_world_t *world); |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
#ifdef FLECS_STATS |
|
|
|
#define ECS_GAUGE_RECORD(m, t, value)\ |
|
flecs_gauge_record(m, t, (ecs_float_t)(value)) |
|
|
|
#define ECS_COUNTER_RECORD(m, t, value)\ |
|
flecs_counter_record(m, t, (double)(value)) |
|
|
|
#define ECS_METRIC_FIRST(stats)\ |
|
ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) |
|
|
|
#define ECS_METRIC_LAST(stats)\ |
|
ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) |
|
|
|
static |
|
int32_t t_next( |
|
int32_t t) |
|
{ |
|
return (t + 1) % ECS_STAT_WINDOW; |
|
} |
|
|
|
static |
|
int32_t t_prev( |
|
int32_t t) |
|
{ |
|
return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; |
|
} |
|
|
|
static |
|
void flecs_gauge_record( |
|
ecs_metric_t *m, |
|
int32_t t, |
|
ecs_float_t value) |
|
{ |
|
m->gauge.avg[t] = value; |
|
m->gauge.min[t] = value; |
|
m->gauge.max[t] = value; |
|
} |
|
|
|
static |
|
double flecs_counter_record( |
|
ecs_metric_t *m, |
|
int32_t t, |
|
double value) |
|
{ |
|
int32_t tp = t_prev(t); |
|
double prev = m->counter.value[tp]; |
|
m->counter.value[t] = value; |
|
double gauge_value = value - prev; |
|
if (gauge_value < 0) { |
|
gauge_value = 0; /* Counters are monotonically increasing */ |
|
} |
|
flecs_gauge_record(m, t, (ecs_float_t)gauge_value); |
|
return gauge_value; |
|
} |
|
|
|
static |
|
void flecs_metric_print( |
|
const char *name, |
|
ecs_float_t value) |
|
{ |
|
ecs_size_t len = ecs_os_strlen(name); |
|
ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); |
|
} |
|
|
|
static |
|
void flecs_gauge_print( |
|
const char *name, |
|
int32_t t, |
|
const ecs_metric_t *m) |
|
{ |
|
flecs_metric_print(name, m->gauge.avg[t]); |
|
} |
|
|
|
static |
|
void flecs_counter_print( |
|
const char *name, |
|
int32_t t, |
|
const ecs_metric_t *m) |
|
{ |
|
flecs_metric_print(name, m->counter.rate.avg[t]); |
|
} |
|
|
|
void ecs_metric_reduce( |
|
ecs_metric_t *dst, |
|
const ecs_metric_t *src, |
|
int32_t t_dst, |
|
int32_t t_src) |
|
{ |
|
ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
bool min_set = false; |
|
dst->gauge.avg[t_dst] = 0; |
|
dst->gauge.min[t_dst] = 0; |
|
dst->gauge.max[t_dst] = 0; |
|
|
|
ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; |
|
|
|
int32_t i; |
|
for (i = 0; i < ECS_STAT_WINDOW; i ++) { |
|
int32_t t = (t_src + i) % ECS_STAT_WINDOW; |
|
dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; |
|
|
|
if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { |
|
dst->gauge.min[t_dst] = src->gauge.min[t]; |
|
min_set = true; |
|
} |
|
if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { |
|
dst->gauge.max[t_dst] = src->gauge.max[t]; |
|
} |
|
} |
|
|
|
dst->counter.value[t_dst] = src->counter.value[t_src]; |
|
|
|
error: |
|
return; |
|
} |
|
|
|
void ecs_metric_reduce_last( |
|
ecs_metric_t *m, |
|
int32_t prev, |
|
int32_t count) |
|
{ |
|
ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); |
|
int32_t t = t_next(prev); |
|
|
|
if (m->gauge.min[t] < m->gauge.min[prev]) { |
|
m->gauge.min[prev] = m->gauge.min[t]; |
|
} |
|
|
|
if (m->gauge.max[t] > m->gauge.max[prev]) { |
|
m->gauge.max[prev] = m->gauge.max[t]; |
|
} |
|
|
|
ecs_float_t fcount = (ecs_float_t)(count + 1); |
|
ecs_float_t cur = m->gauge.avg[prev]; |
|
ecs_float_t next = m->gauge.avg[t]; |
|
|
|
cur *= ((fcount - 1) / fcount); |
|
next *= 1 / fcount; |
|
|
|
m->gauge.avg[prev] = cur + next; |
|
m->counter.value[prev] = m->counter.value[t]; |
|
|
|
error: |
|
return; |
|
} |
|
|
|
void ecs_metric_copy( |
|
ecs_metric_t *m, |
|
int32_t dst, |
|
int32_t src) |
|
{ |
|
ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); |
|
|
|
m->gauge.avg[dst] = m->gauge.avg[src]; |
|
m->gauge.min[dst] = m->gauge.min[src]; |
|
m->gauge.max[dst] = m->gauge.max[src]; |
|
m->counter.value[dst] = m->counter.value[src]; |
|
|
|
error: |
|
return; |
|
} |
|
|
|
static |
|
void flecs_stats_reduce( |
|
ecs_metric_t *dst_cur, |
|
ecs_metric_t *dst_last, |
|
ecs_metric_t *src_cur, |
|
int32_t t_dst, |
|
int32_t t_src) |
|
{ |
|
for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { |
|
ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); |
|
} |
|
} |
|
|
|
static |
|
void flecs_stats_reduce_last( |
|
ecs_metric_t *dst_cur, |
|
ecs_metric_t *dst_last, |
|
ecs_metric_t *src_cur, |
|
int32_t t_dst, |
|
int32_t t_src, |
|
int32_t count) |
|
{ |
|
int32_t t_dst_next = t_next(t_dst); |
|
for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { |
|
/* Reduce into previous value */ |
|
ecs_metric_reduce_last(dst_cur, t_dst, count); |
|
|
|
/* Restore old value */ |
|
dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; |
|
dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; |
|
dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; |
|
dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; |
|
} |
|
} |
|
|
|
static |
|
void flecs_stats_repeat_last( |
|
ecs_metric_t *cur, |
|
ecs_metric_t *last, |
|
int32_t t) |
|
{ |
|
int32_t prev = t_prev(t); |
|
for (; cur <= last; cur ++) { |
|
ecs_metric_copy(cur, t, prev); |
|
} |
|
} |
|
|
|
static |
|
void flecs_stats_copy_last( |
|
ecs_metric_t *dst_cur, |
|
ecs_metric_t *dst_last, |
|
ecs_metric_t *src_cur, |
|
int32_t t_dst, |
|
int32_t t_src) |
|
{ |
|
for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { |
|
dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; |
|
dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; |
|
dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; |
|
dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; |
|
} |
|
} |
|
|
|
void ecs_world_stats_get( |
|
const ecs_world_t *world, |
|
ecs_world_stats_t *s) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
int32_t t = s->t = t_next(s->t); |
|
|
|
double delta_frame_count = |
|
ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); |
|
ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); |
|
ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); |
|
ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); |
|
ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); |
|
ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); |
|
ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); |
|
|
|
double delta_world_time = |
|
ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); |
|
ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); |
|
ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); |
|
ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); |
|
ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); |
|
ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); |
|
ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); |
|
ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); |
|
if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { |
|
ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); |
|
} else { |
|
ECS_GAUGE_RECORD(&s->performance.fps, t, 0); |
|
} |
|
|
|
ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); |
|
ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); |
|
|
|
ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); |
|
ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); |
|
ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); |
|
ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_sparse_count(&world->type_info)); |
|
ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); |
|
ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); |
|
|
|
ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); |
|
ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); |
|
if (ecs_is_alive(world, EcsSystem)) { |
|
ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); |
|
} |
|
ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); |
|
ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); |
|
ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); |
|
ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); |
|
|
|
ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); |
|
ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); |
|
ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); |
|
ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); |
|
ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); |
|
ECS_COUNTER_RECORD(&s->commands.get_mut_count, t, world->info.cmd.get_mut_count); |
|
ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); |
|
ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); |
|
ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); |
|
ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); |
|
ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); |
|
|
|
int64_t outstanding_allocs = ecs_os_api_malloc_count + |
|
ecs_os_api_calloc_count - ecs_os_api_free_count; |
|
ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); |
|
ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); |
|
ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); |
|
ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); |
|
|
|
outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; |
|
ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); |
|
ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); |
|
ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); |
|
|
|
outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; |
|
ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); |
|
ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); |
|
ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); |
|
|
|
#ifdef FLECS_HTTP |
|
ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); |
|
ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); |
|
ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); |
|
ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); |
|
ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); |
|
ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); |
|
ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); |
|
ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); |
|
ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); |
|
#endif |
|
|
|
error: |
|
return; |
|
} |
|
|
|
void ecs_world_stats_reduce( |
|
ecs_world_stats_t *dst, |
|
const ecs_world_stats_t *src) |
|
{ |
|
flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); |
|
} |
|
|
|
void ecs_world_stats_reduce_last( |
|
ecs_world_stats_t *dst, |
|
const ecs_world_stats_t *src, |
|
int32_t count) |
|
{ |
|
flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); |
|
} |
|
|
|
void ecs_world_stats_repeat_last( |
|
ecs_world_stats_t *stats) |
|
{ |
|
flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), |
|
(stats->t = t_next(stats->t))); |
|
} |
|
|
|
void ecs_world_stats_copy_last( |
|
ecs_world_stats_t *dst, |
|
const ecs_world_stats_t *src) |
|
{ |
|
flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); |
|
} |
|
|
|
void ecs_query_stats_get( |
|
const ecs_world_t *world, |
|
const ecs_query_t *query, |
|
ecs_query_stats_t *s) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); |
|
(void)world; |
|
|
|
int32_t t = s->t = t_next(s->t); |
|
|
|
if (query->filter.flags & EcsFilterMatchThis) { |
|
ECS_GAUGE_RECORD(&s->matched_entity_count, t, |
|
ecs_query_entity_count(query)); |
|
ECS_GAUGE_RECORD(&s->matched_table_count, t, |
|
ecs_query_table_count(query)); |
|
ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, |
|
ecs_query_empty_table_count(query)); |
|
} else { |
|
ECS_GAUGE_RECORD(&s->matched_entity_count, t, 0); |
|
ECS_GAUGE_RECORD(&s->matched_table_count, t, 0); |
|
ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, 0); |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
void ecs_query_stats_reduce( |
|
ecs_query_stats_t *dst, |
|
const ecs_query_stats_t *src) |
|
{ |
|
flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); |
|
} |
|
|
|
void ecs_query_stats_reduce_last( |
|
ecs_query_stats_t *dst, |
|
const ecs_query_stats_t *src, |
|
int32_t count) |
|
{ |
|
flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); |
|
} |
|
|
|
void ecs_query_stats_repeat_last( |
|
ecs_query_stats_t *stats) |
|
{ |
|
flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), |
|
(stats->t = t_next(stats->t))); |
|
} |
|
|
|
void ecs_query_stats_copy_last( |
|
ecs_query_stats_t *dst, |
|
const ecs_query_stats_t *src) |
|
{ |
|
flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); |
|
} |
|
|
|
#ifdef FLECS_SYSTEM |
|
|
|
bool ecs_system_stats_get( |
|
const ecs_world_t *world, |
|
ecs_entity_t system, |
|
ecs_system_stats_t *s) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
const ecs_system_t *ptr = ecs_poly_get(world, system, ecs_system_t); |
|
if (!ptr) { |
|
return false; |
|
} |
|
|
|
ecs_query_stats_get(world, ptr->query, &s->query); |
|
int32_t t = s->query.t; |
|
|
|
ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); |
|
ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); |
|
|
|
s->task = !(ptr->query->filter.flags & EcsFilterMatchThis); |
|
|
|
return true; |
|
error: |
|
return false; |
|
} |
|
|
|
void ecs_system_stats_reduce( |
|
ecs_system_stats_t *dst, |
|
const ecs_system_stats_t *src) |
|
{ |
|
ecs_query_stats_reduce(&dst->query, &src->query); |
|
dst->task = src->task; |
|
flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), dst->query.t, src->query.t); |
|
} |
|
|
|
void ecs_system_stats_reduce_last( |
|
ecs_system_stats_t *dst, |
|
const ecs_system_stats_t *src, |
|
int32_t count) |
|
{ |
|
ecs_query_stats_reduce_last(&dst->query, &src->query, count); |
|
dst->task = src->task; |
|
flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); |
|
} |
|
|
|
void ecs_system_stats_repeat_last( |
|
ecs_system_stats_t *stats) |
|
{ |
|
ecs_query_stats_repeat_last(&stats->query); |
|
flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), |
|
(stats->query.t)); |
|
} |
|
|
|
void ecs_system_stats_copy_last( |
|
ecs_system_stats_t *dst, |
|
const ecs_system_stats_t *src) |
|
{ |
|
ecs_query_stats_copy_last(&dst->query, &src->query); |
|
dst->task = src->task; |
|
flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), |
|
ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); |
|
} |
|
|
|
#endif |
|
|
|
#ifdef FLECS_PIPELINE |
|
|
|
bool ecs_pipeline_stats_get( |
|
ecs_world_t *stage, |
|
ecs_entity_t pipeline, |
|
ecs_pipeline_stats_t *s) |
|
{ |
|
ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const ecs_world_t *world = ecs_get_world(stage); |
|
const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); |
|
if (!pqc) { |
|
return false; |
|
} |
|
ecs_pipeline_state_t *pq = pqc->state; |
|
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t sys_count = 0, active_sys_count = 0; |
|
|
|
/* Count number of active systems */ |
|
ecs_iter_t it = ecs_query_iter(stage, pq->query); |
|
while (ecs_query_next(&it)) { |
|
if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { |
|
continue; |
|
} |
|
active_sys_count += it.count; |
|
} |
|
|
|
/* Count total number of systems in pipeline */ |
|
it = ecs_query_iter(stage, pq->query); |
|
while (ecs_query_next(&it)) { |
|
sys_count += it.count; |
|
} |
|
|
|
/* Also count synchronization points */ |
|
ecs_vec_t *ops = &pq->ops; |
|
ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); |
|
ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); |
|
int32_t pip_count = active_sys_count + ecs_vec_count(ops); |
|
|
|
if (!sys_count) { |
|
return false; |
|
} |
|
|
|
if (ecs_map_is_init(&s->system_stats) && !sys_count) { |
|
ecs_map_fini(&s->system_stats); |
|
} |
|
ecs_map_init_if(&s->system_stats, NULL); |
|
|
|
if (op) { |
|
ecs_entity_t *systems = NULL; |
|
if (pip_count) { |
|
ecs_vec_init_if_t(&s->systems, ecs_entity_t); |
|
ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); |
|
systems = ecs_vec_first_t(&s->systems, ecs_entity_t); |
|
|
|
/* Populate systems vector, keep track of sync points */ |
|
it = ecs_query_iter(stage, pq->query); |
|
|
|
int32_t i, i_system = 0, ran_since_merge = 0; |
|
while (ecs_query_next(&it)) { |
|
if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { |
|
continue; |
|
} |
|
|
|
for (i = 0; i < it.count; i ++) { |
|
systems[i_system ++] = it.entities[i]; |
|
ran_since_merge ++; |
|
if (op != op_last && ran_since_merge == op->count) { |
|
ran_since_merge = 0; |
|
op++; |
|
systems[i_system ++] = 0; /* 0 indicates a merge point */ |
|
} |
|
} |
|
} |
|
|
|
systems[i_system ++] = 0; /* Last merge */ |
|
ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); |
|
} else { |
|
ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); |
|
} |
|
|
|
/* Get sync point statistics */ |
|
int32_t i, count = ecs_vec_count(ops); |
|
if (count) { |
|
ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); |
|
ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); |
|
op = ecs_vec_first_t(ops, ecs_pipeline_op_t); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_pipeline_op_t *cur = &op[i]; |
|
ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, |
|
ecs_sync_stats_t, i); |
|
|
|
ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); |
|
ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, |
|
cur->commands_enqueued); |
|
|
|
el->system_count = cur->count; |
|
el->multi_threaded = cur->multi_threaded; |
|
el->no_readonly = cur->no_readonly; |
|
} |
|
} |
|
} |
|
|
|
/* Separately populate system stats map from build query, which includes |
|
* systems that aren't currently active */ |
|
it = ecs_query_iter(stage, pq->query); |
|
while (ecs_query_next(&it)) { |
|
int32_t i; |
|
for (i = 0; i < it.count; i ++) { |
|
ecs_system_stats_t *stats = ecs_map_ensure_alloc_t(&s->system_stats, |
|
ecs_system_stats_t, it.entities[i]); |
|
stats->query.t = s->t; |
|
ecs_system_stats_get(world, it.entities[i], stats); |
|
} |
|
} |
|
|
|
s->t = t_next(s->t); |
|
|
|
return true; |
|
error: |
|
return false; |
|
} |
|
|
|
void ecs_pipeline_stats_fini( |
|
ecs_pipeline_stats_t *stats) |
|
{ |
|
ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); |
|
while (ecs_map_next(&it)) { |
|
ecs_system_stats_t *elem = ecs_map_ptr(&it); |
|
ecs_os_free(elem); |
|
} |
|
ecs_map_fini(&stats->system_stats); |
|
ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); |
|
ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); |
|
} |
|
|
|
void ecs_pipeline_stats_reduce( |
|
ecs_pipeline_stats_t *dst, |
|
const ecs_pipeline_stats_t *src) |
|
{ |
|
int32_t system_count = ecs_vec_count(&src->systems); |
|
ecs_vec_init_if_t(&dst->systems, ecs_entity_t); |
|
ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); |
|
ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); |
|
ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); |
|
ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); |
|
|
|
int32_t i, sync_count = ecs_vec_count(&src->sync_points); |
|
ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); |
|
ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); |
|
ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); |
|
ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); |
|
for (i = 0; i < sync_count; i ++) { |
|
ecs_sync_stats_t *dst_el = &dst_syncs[i]; |
|
ecs_sync_stats_t *src_el = &src_syncs[i]; |
|
flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), |
|
ECS_METRIC_FIRST(src_el), dst->t, src->t); |
|
dst_el->system_count = src_el->system_count; |
|
dst_el->multi_threaded = src_el->multi_threaded; |
|
dst_el->no_readonly = src_el->no_readonly; |
|
} |
|
|
|
ecs_map_init_if(&dst->system_stats, NULL); |
|
ecs_map_iter_t it = ecs_map_iter(&src->system_stats); |
|
|
|
while (ecs_map_next(&it)) { |
|
ecs_system_stats_t *sys_src = ecs_map_ptr(&it); |
|
ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, |
|
ecs_system_stats_t, ecs_map_key(&it)); |
|
sys_dst->query.t = dst->t; |
|
ecs_system_stats_reduce(sys_dst, sys_src); |
|
} |
|
dst->t = t_next(dst->t); |
|
} |
|
|
|
void ecs_pipeline_stats_reduce_last( |
|
ecs_pipeline_stats_t *dst, |
|
const ecs_pipeline_stats_t *src, |
|
int32_t count) |
|
{ |
|
int32_t i, sync_count = ecs_vec_count(&src->sync_points); |
|
ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); |
|
ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); |
|
|
|
for (i = 0; i < sync_count; i ++) { |
|
ecs_sync_stats_t *dst_el = &dst_syncs[i]; |
|
ecs_sync_stats_t *src_el = &src_syncs[i]; |
|
flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), |
|
ECS_METRIC_FIRST(src_el), dst->t, src->t, count); |
|
dst_el->system_count = src_el->system_count; |
|
dst_el->multi_threaded = src_el->multi_threaded; |
|
dst_el->no_readonly = src_el->no_readonly; |
|
} |
|
|
|
ecs_map_init_if(&dst->system_stats, NULL); |
|
ecs_map_iter_t it = ecs_map_iter(&src->system_stats); |
|
while (ecs_map_next(&it)) { |
|
ecs_system_stats_t *sys_src = ecs_map_ptr(&it); |
|
ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, |
|
ecs_system_stats_t, ecs_map_key(&it)); |
|
sys_dst->query.t = dst->t; |
|
ecs_system_stats_reduce_last(sys_dst, sys_src, count); |
|
} |
|
dst->t = t_prev(dst->t); |
|
} |
|
|
|
void ecs_pipeline_stats_repeat_last( |
|
ecs_pipeline_stats_t *stats) |
|
{ |
|
int32_t i, sync_count = ecs_vec_count(&stats->sync_points); |
|
ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); |
|
|
|
for (i = 0; i < sync_count; i ++) { |
|
ecs_sync_stats_t *el = &syncs[i]; |
|
flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), |
|
(stats->t)); |
|
} |
|
|
|
ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); |
|
while (ecs_map_next(&it)) { |
|
ecs_system_stats_t *sys = ecs_map_ptr(&it); |
|
sys->query.t = stats->t; |
|
ecs_system_stats_repeat_last(sys); |
|
} |
|
stats->t = t_next(stats->t); |
|
} |
|
|
|
void ecs_pipeline_stats_copy_last( |
|
ecs_pipeline_stats_t *dst, |
|
const ecs_pipeline_stats_t *src) |
|
{ |
|
int32_t i, sync_count = ecs_vec_count(&src->sync_points); |
|
ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); |
|
ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); |
|
ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); |
|
ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); |
|
|
|
for (i = 0; i < sync_count; i ++) { |
|
ecs_sync_stats_t *dst_el = &dst_syncs[i]; |
|
ecs_sync_stats_t *src_el = &src_syncs[i]; |
|
flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), |
|
ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); |
|
dst_el->system_count = src_el->system_count; |
|
dst_el->multi_threaded = src_el->multi_threaded; |
|
dst_el->no_readonly = src_el->no_readonly; |
|
} |
|
|
|
ecs_map_init_if(&dst->system_stats, NULL); |
|
|
|
ecs_map_iter_t it = ecs_map_iter(&src->system_stats); |
|
while (ecs_map_next(&it)) { |
|
ecs_system_stats_t *sys_src = ecs_map_ptr(&it); |
|
ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, |
|
ecs_system_stats_t, ecs_map_key(&it)); |
|
sys_dst->query.t = dst->t; |
|
ecs_system_stats_copy_last(sys_dst, sys_src); |
|
} |
|
} |
|
|
|
#endif |
|
|
|
void ecs_world_stats_log( |
|
const ecs_world_t *world, |
|
const ecs_world_stats_t *s) |
|
{ |
|
int32_t t = s->t; |
|
|
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
flecs_counter_print("Frame", t, &s->frame.frame_count); |
|
ecs_trace("-------------------------------------"); |
|
flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); |
|
flecs_counter_print("systems ran", t, &s->frame.systems_ran); |
|
ecs_trace(""); |
|
flecs_metric_print("target FPS", world->info.target_fps); |
|
flecs_metric_print("time scale", world->info.time_scale); |
|
ecs_trace(""); |
|
flecs_gauge_print("actual FPS", t, &s->performance.fps); |
|
flecs_counter_print("frame time", t, &s->performance.frame_time); |
|
flecs_counter_print("system time", t, &s->performance.system_time); |
|
flecs_counter_print("merge time", t, &s->performance.merge_time); |
|
flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); |
|
ecs_trace(""); |
|
flecs_gauge_print("tag id count", t, &s->components.tag_count); |
|
flecs_gauge_print("component id count", t, &s->components.component_count); |
|
flecs_gauge_print("pair id count", t, &s->components.pair_count); |
|
flecs_gauge_print("type count", t, &s->components.type_count); |
|
flecs_counter_print("id create count", t, &s->components.create_count); |
|
flecs_counter_print("id delete count", t, &s->components.delete_count); |
|
ecs_trace(""); |
|
flecs_gauge_print("alive entity count", t, &s->entities.count); |
|
flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); |
|
ecs_trace(""); |
|
flecs_gauge_print("query count", t, &s->queries.query_count); |
|
flecs_gauge_print("observer count", t, &s->queries.observer_count); |
|
flecs_gauge_print("system count", t, &s->queries.system_count); |
|
ecs_trace(""); |
|
flecs_gauge_print("table count", t, &s->tables.count); |
|
flecs_gauge_print("empty table count", t, &s->tables.empty_count); |
|
flecs_counter_print("table create count", t, &s->tables.create_count); |
|
flecs_counter_print("table delete count", t, &s->tables.delete_count); |
|
ecs_trace(""); |
|
flecs_counter_print("add commands", t, &s->commands.add_count); |
|
flecs_counter_print("remove commands", t, &s->commands.remove_count); |
|
flecs_counter_print("delete commands", t, &s->commands.delete_count); |
|
flecs_counter_print("clear commands", t, &s->commands.clear_count); |
|
flecs_counter_print("set commands", t, &s->commands.set_count); |
|
flecs_counter_print("get_mut commands", t, &s->commands.get_mut_count); |
|
flecs_counter_print("modified commands", t, &s->commands.modified_count); |
|
flecs_counter_print("other commands", t, &s->commands.other_count); |
|
flecs_counter_print("discarded commands", t, &s->commands.discard_count); |
|
flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); |
|
flecs_counter_print("batched commands", t, &s->commands.batched_count); |
|
ecs_trace(""); |
|
|
|
error: |
|
return; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/timer.c |
|
* @brief Timer addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_TIMER |
|
|
|
static |
|
void AddTickSource(ecs_iter_t *it) { |
|
int32_t i; |
|
for (i = 0; i < it->count; i ++) { |
|
ecs_set(it->world, it->entities[i], EcsTickSource, {0}); |
|
} |
|
} |
|
|
|
static |
|
void ProgressTimers(ecs_iter_t *it) { |
|
EcsTimer *timer = ecs_field(it, EcsTimer, 1); |
|
EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 2); |
|
|
|
ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int i; |
|
for (i = 0; i < it->count; i ++) { |
|
tick_source[i].tick = false; |
|
|
|
if (!timer[i].active) { |
|
continue; |
|
} |
|
|
|
const ecs_world_info_t *info = ecs_get_world_info(it->world); |
|
ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; |
|
ecs_ftime_t timeout = timer[i].timeout; |
|
|
|
if (time_elapsed >= timeout) { |
|
ecs_ftime_t t = time_elapsed - timeout; |
|
if (t > timeout) { |
|
t = 0; |
|
} |
|
|
|
timer[i].time = t; /* Initialize with remainder */ |
|
tick_source[i].tick = true; |
|
tick_source[i].time_elapsed = time_elapsed - timer[i].overshoot; |
|
timer[i].overshoot = t; |
|
|
|
if (timer[i].single_shot) { |
|
timer[i].active = false; |
|
} |
|
} else { |
|
timer[i].time = time_elapsed; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void ProgressRateFilters(ecs_iter_t *it) { |
|
EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 1); |
|
EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 2); |
|
|
|
int i; |
|
for (i = 0; i < it->count; i ++) { |
|
ecs_entity_t src = filter[i].src; |
|
bool inc = false; |
|
|
|
filter[i].time_elapsed += it->delta_time; |
|
|
|
if (src) { |
|
const EcsTickSource *tick_src = ecs_get( |
|
it->world, src, EcsTickSource); |
|
if (tick_src) { |
|
inc = tick_src->tick; |
|
} else { |
|
inc = true; |
|
} |
|
} else { |
|
inc = true; |
|
} |
|
|
|
if (inc) { |
|
filter[i].tick_count ++; |
|
bool triggered = !(filter[i].tick_count % filter[i].rate); |
|
tick_dst[i].tick = triggered; |
|
tick_dst[i].time_elapsed = filter[i].time_elapsed; |
|
|
|
if (triggered) { |
|
filter[i].time_elapsed = 0; |
|
} |
|
} else { |
|
tick_dst[i].tick = false; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void ProgressTickSource(ecs_iter_t *it) { |
|
EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 1); |
|
|
|
/* If tick source has no filters, tick unconditionally */ |
|
int i; |
|
for (i = 0; i < it->count; i ++) { |
|
tick_src[i].tick = true; |
|
tick_src[i].time_elapsed = it->delta_time; |
|
} |
|
} |
|
|
|
ecs_entity_t ecs_set_timeout( |
|
ecs_world_t *world, |
|
ecs_entity_t timer, |
|
ecs_ftime_t timeout) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
timer = ecs_set(world, timer, EcsTimer, { |
|
.timeout = timeout, |
|
.single_shot = true, |
|
.active = true |
|
}); |
|
|
|
ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); |
|
if (system_data) { |
|
system_data->tick_source = timer; |
|
} |
|
|
|
error: |
|
return timer; |
|
} |
|
|
|
ecs_ftime_t ecs_get_timeout( |
|
const ecs_world_t *world, |
|
ecs_entity_t timer) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
const EcsTimer *value = ecs_get(world, timer, EcsTimer); |
|
if (value) { |
|
return value->timeout; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_set_interval( |
|
ecs_world_t *world, |
|
ecs_entity_t timer, |
|
ecs_ftime_t interval) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!timer) { |
|
timer = ecs_new(world, EcsTimer); |
|
} |
|
|
|
EcsTimer *t = ecs_get_mut(world, timer, EcsTimer); |
|
ecs_check(t != NULL, ECS_INVALID_PARAMETER, NULL); |
|
t->timeout = interval; |
|
t->active = true; |
|
ecs_modified(world, timer, EcsTimer); |
|
|
|
ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); |
|
if (system_data) { |
|
system_data->tick_source = timer; |
|
} |
|
error: |
|
return timer; |
|
} |
|
|
|
ecs_ftime_t ecs_get_interval( |
|
const ecs_world_t *world, |
|
ecs_entity_t timer) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (!timer) { |
|
return 0; |
|
} |
|
|
|
const EcsTimer *value = ecs_get(world, timer, EcsTimer); |
|
if (value) { |
|
return value->timeout; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
void ecs_start_timer( |
|
ecs_world_t *world, |
|
ecs_entity_t timer) |
|
{ |
|
EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); |
|
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ptr->active = true; |
|
ptr->time = 0; |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_stop_timer( |
|
ecs_world_t *world, |
|
ecs_entity_t timer) |
|
{ |
|
EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); |
|
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ptr->active = false; |
|
error: |
|
return; |
|
} |
|
|
|
void ecs_reset_timer( |
|
ecs_world_t *world, |
|
ecs_entity_t timer) |
|
{ |
|
EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); |
|
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ptr->time = 0; |
|
error: |
|
return; |
|
} |
|
|
|
ecs_entity_t ecs_set_rate( |
|
ecs_world_t *world, |
|
ecs_entity_t filter, |
|
int32_t rate, |
|
ecs_entity_t source) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
filter = ecs_set(world, filter, EcsRateFilter, { |
|
.rate = rate, |
|
.src = source |
|
}); |
|
|
|
ecs_system_t *system_data = ecs_poly_get(world, filter, ecs_system_t); |
|
if (system_data) { |
|
system_data->tick_source = filter; |
|
} |
|
|
|
error: |
|
return filter; |
|
} |
|
|
|
void ecs_set_tick_source( |
|
ecs_world_t *world, |
|
ecs_entity_t system, |
|
ecs_entity_t tick_source) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); |
|
ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
system_data->tick_source = tick_source; |
|
error: |
|
return; |
|
} |
|
|
|
static |
|
void RandomizeTimers(ecs_iter_t *it) { |
|
EcsTimer *timer = ecs_field(it, EcsTimer, 1); |
|
int32_t i; |
|
for (i = 0; i < it->count; i ++) { |
|
timer[i].time = |
|
((ecs_ftime_t)rand() / (ecs_ftime_t)RAND_MAX) * timer[i].timeout; |
|
} |
|
} |
|
|
|
void ecs_randomize_timers( |
|
ecs_world_t *world) |
|
{ |
|
ecs_observer(world, { |
|
.entity = ecs_entity(world, { .name = "flecs.timer.RandomizeTimers" }), |
|
.filter.terms = {{ |
|
.id = ecs_id(EcsTimer) |
|
}}, |
|
.events = {EcsOnSet}, |
|
.yield_existing = true, |
|
.callback = RandomizeTimers |
|
}); |
|
} |
|
|
|
void FlecsTimerImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsTimer); |
|
ECS_IMPORT(world, FlecsPipeline); |
|
#ifdef FLECS_COREDOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
ecs_doc_set_brief(world, ecs_id(FlecsTimer), |
|
"Module that implements system timers (used by .interval)"); |
|
#endif |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
flecs_bootstrap_component(world, EcsTimer); |
|
flecs_bootstrap_component(world, EcsRateFilter); |
|
|
|
ecs_set_hooks(world, EcsTimer, { |
|
.ctor = ecs_default_ctor |
|
}); |
|
|
|
/* Add EcsTickSource to timers and rate filters */ |
|
ecs_system(world, { |
|
.entity = ecs_entity(world, {.name = "AddTickSource", .add = { ecs_dependson(EcsPreFrame) }}), |
|
.query.filter.terms = { |
|
{ .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn }, |
|
{ .id = ecs_id(EcsRateFilter), .oper = EcsAnd, .inout = EcsIn }, |
|
{ .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} |
|
}, |
|
.callback = AddTickSource |
|
}); |
|
|
|
/* Timer handling */ |
|
ecs_system(world, { |
|
.entity = ecs_entity(world, {.name = "ProgressTimers", .add = { ecs_dependson(EcsPreFrame)}}), |
|
.query.filter.terms = { |
|
{ .id = ecs_id(EcsTimer) }, |
|
{ .id = ecs_id(EcsTickSource) } |
|
}, |
|
.callback = ProgressTimers |
|
}); |
|
|
|
/* Rate filter handling */ |
|
ecs_system(world, { |
|
.entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = { ecs_dependson(EcsPreFrame)}}), |
|
.query.filter.terms = { |
|
{ .id = ecs_id(EcsRateFilter), .inout = EcsIn }, |
|
{ .id = ecs_id(EcsTickSource), .inout = EcsOut } |
|
}, |
|
.callback = ProgressRateFilters |
|
}); |
|
|
|
/* TickSource without a timer or rate filter just increases each frame */ |
|
ecs_system(world, { |
|
.entity = ecs_entity(world, { .name = "ProgressTickSource", .add = { ecs_dependson(EcsPreFrame)}}), |
|
.query.filter.terms = { |
|
{ .id = ecs_id(EcsTickSource), .inout = EcsOut }, |
|
{ .id = ecs_id(EcsRateFilter), .oper = EcsNot }, |
|
{ .id = ecs_id(EcsTimer), .oper = EcsNot } |
|
}, |
|
.callback = ProgressTickSource |
|
}); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/units.c |
|
* @brief Units addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_UNITS |
|
|
|
void FlecsUnitsImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsUnits); |
|
|
|
#ifdef FLECS_COREDOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
ecs_doc_set_brief(world, ecs_id(FlecsUnits), |
|
"Module with (amongst others) SI units for annotating component members"); |
|
#endif |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
EcsUnitPrefixes = ecs_entity(world, { |
|
.name = "prefixes", |
|
.add = { EcsModule } |
|
}); |
|
|
|
/* Initialize unit prefixes */ |
|
|
|
ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); |
|
|
|
EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Yocto" }), |
|
.symbol = "y", |
|
.translation = { .factor = 10, .power = -24 } |
|
}); |
|
EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Zepto" }), |
|
.symbol = "z", |
|
.translation = { .factor = 10, .power = -21 } |
|
}); |
|
EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Atto" }), |
|
.symbol = "a", |
|
.translation = { .factor = 10, .power = -18 } |
|
}); |
|
EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Femto" }), |
|
.symbol = "a", |
|
.translation = { .factor = 10, .power = -15 } |
|
}); |
|
EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Pico" }), |
|
.symbol = "p", |
|
.translation = { .factor = 10, .power = -12 } |
|
}); |
|
EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Nano" }), |
|
.symbol = "n", |
|
.translation = { .factor = 10, .power = -9 } |
|
}); |
|
EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Micro" }), |
|
.symbol = "μ", |
|
.translation = { .factor = 10, .power = -6 } |
|
}); |
|
EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Milli" }), |
|
.symbol = "m", |
|
.translation = { .factor = 10, .power = -3 } |
|
}); |
|
EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Centi" }), |
|
.symbol = "c", |
|
.translation = { .factor = 10, .power = -2 } |
|
}); |
|
EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Deci" }), |
|
.symbol = "d", |
|
.translation = { .factor = 10, .power = -1 } |
|
}); |
|
EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Deca" }), |
|
.symbol = "da", |
|
.translation = { .factor = 10, .power = 1 } |
|
}); |
|
EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Hecto" }), |
|
.symbol = "h", |
|
.translation = { .factor = 10, .power = 2 } |
|
}); |
|
EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Kilo" }), |
|
.symbol = "k", |
|
.translation = { .factor = 10, .power = 3 } |
|
}); |
|
EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Mega" }), |
|
.symbol = "M", |
|
.translation = { .factor = 10, .power = 6 } |
|
}); |
|
EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Giga" }), |
|
.symbol = "G", |
|
.translation = { .factor = 10, .power = 9 } |
|
}); |
|
EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Tera" }), |
|
.symbol = "T", |
|
.translation = { .factor = 10, .power = 12 } |
|
}); |
|
EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Peta" }), |
|
.symbol = "P", |
|
.translation = { .factor = 10, .power = 15 } |
|
}); |
|
EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Exa" }), |
|
.symbol = "E", |
|
.translation = { .factor = 10, .power = 18 } |
|
}); |
|
EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Zetta" }), |
|
.symbol = "Z", |
|
.translation = { .factor = 10, .power = 21 } |
|
}); |
|
EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Yotta" }), |
|
.symbol = "Y", |
|
.translation = { .factor = 10, .power = 24 } |
|
}); |
|
|
|
EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Kibi" }), |
|
.symbol = "Ki", |
|
.translation = { .factor = 1024, .power = 1 } |
|
}); |
|
EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Mebi" }), |
|
.symbol = "Mi", |
|
.translation = { .factor = 1024, .power = 2 } |
|
}); |
|
EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Gibi" }), |
|
.symbol = "Gi", |
|
.translation = { .factor = 1024, .power = 3 } |
|
}); |
|
EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Tebi" }), |
|
.symbol = "Ti", |
|
.translation = { .factor = 1024, .power = 4 } |
|
}); |
|
EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Pebi" }), |
|
.symbol = "Pi", |
|
.translation = { .factor = 1024, .power = 5 } |
|
}); |
|
EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Exbi" }), |
|
.symbol = "Ei", |
|
.translation = { .factor = 1024, .power = 6 } |
|
}); |
|
EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Zebi" }), |
|
.symbol = "Zi", |
|
.translation = { .factor = 1024, .power = 7 } |
|
}); |
|
EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Yobi" }), |
|
.symbol = "Yi", |
|
.translation = { .factor = 1024, .power = 8 } |
|
}); |
|
|
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Duration units */ |
|
|
|
EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Duration" }); |
|
prev_scope = ecs_set_scope(world, EcsDuration); |
|
|
|
EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Seconds" }), |
|
.quantity = EcsDuration, |
|
.symbol = "s" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsSeconds, |
|
.kind = EcsF32 |
|
}); |
|
EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "PicoSeconds" }), |
|
.quantity = EcsDuration, |
|
.base = EcsSeconds, |
|
.prefix = EcsPico }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsPicoSeconds, |
|
.kind = EcsF32 |
|
}); |
|
|
|
|
|
EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "NanoSeconds" }), |
|
.quantity = EcsDuration, |
|
.base = EcsSeconds, |
|
.prefix = EcsNano }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsNanoSeconds, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MicroSeconds" }), |
|
.quantity = EcsDuration, |
|
.base = EcsSeconds, |
|
.prefix = EcsMicro }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMicroSeconds, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MilliSeconds" }), |
|
.quantity = EcsDuration, |
|
.base = EcsSeconds, |
|
.prefix = EcsMilli }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMilliSeconds, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Minutes" }), |
|
.quantity = EcsDuration, |
|
.base = EcsSeconds, |
|
.symbol = "min", |
|
.translation = { .factor = 60, .power = 1 } }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMinutes, |
|
.kind = EcsU32 |
|
}); |
|
|
|
EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Hours" }), |
|
.quantity = EcsDuration, |
|
.base = EcsMinutes, |
|
.symbol = "h", |
|
.translation = { .factor = 60, .power = 1 } }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsHours, |
|
.kind = EcsU32 |
|
}); |
|
|
|
EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Days" }), |
|
.quantity = EcsDuration, |
|
.base = EcsHours, |
|
.symbol = "d", |
|
.translation = { .factor = 24, .power = 1 } }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsDays, |
|
.kind = EcsU32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Time units */ |
|
|
|
EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Time" }); |
|
prev_scope = ecs_set_scope(world, EcsTime); |
|
|
|
EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Date" }), |
|
.quantity = EcsTime }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsDate, |
|
.kind = EcsU32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Mass units */ |
|
|
|
EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Mass" }); |
|
prev_scope = ecs_set_scope(world, EcsMass); |
|
EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Grams" }), |
|
.quantity = EcsMass, |
|
.symbol = "g" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsGrams, |
|
.kind = EcsF32 |
|
}); |
|
EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloGrams" }), |
|
.quantity = EcsMass, |
|
.prefix = EcsKilo, |
|
.base = EcsGrams }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloGrams, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Electric current units */ |
|
|
|
EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "ElectricCurrent" }); |
|
prev_scope = ecs_set_scope(world, EcsElectricCurrent); |
|
EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Ampere" }), |
|
.quantity = EcsElectricCurrent, |
|
.symbol = "A" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsAmpere, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Amount of substance units */ |
|
|
|
EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Amount" }); |
|
prev_scope = ecs_set_scope(world, EcsAmount); |
|
EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Mole" }), |
|
.quantity = EcsAmount, |
|
.symbol = "mol" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMole, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Luminous intensity units */ |
|
|
|
EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "LuminousIntensity" }); |
|
prev_scope = ecs_set_scope(world, EcsLuminousIntensity); |
|
EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Candela" }), |
|
.quantity = EcsLuminousIntensity, |
|
.symbol = "cd" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsCandela, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Force units */ |
|
|
|
EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Force" }); |
|
prev_scope = ecs_set_scope(world, EcsForce); |
|
EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Newton" }), |
|
.quantity = EcsForce, |
|
.symbol = "N" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsNewton, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Length units */ |
|
|
|
EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Length" }); |
|
prev_scope = ecs_set_scope(world, EcsLength); |
|
EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Meters" }), |
|
.quantity = EcsLength, |
|
.symbol = "m" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMeters, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "PicoMeters" }), |
|
.quantity = EcsLength, |
|
.base = EcsMeters, |
|
.prefix = EcsPico }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsPicoMeters, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "NanoMeters" }), |
|
.quantity = EcsLength, |
|
.base = EcsMeters, |
|
.prefix = EcsNano }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsNanoMeters, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MicroMeters" }), |
|
.quantity = EcsLength, |
|
.base = EcsMeters, |
|
.prefix = EcsMicro }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMicroMeters, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MilliMeters" }), |
|
.quantity = EcsLength, |
|
.base = EcsMeters, |
|
.prefix = EcsMilli }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMilliMeters, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "CentiMeters" }), |
|
.quantity = EcsLength, |
|
.base = EcsMeters, |
|
.prefix = EcsCenti }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsCentiMeters, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloMeters" }), |
|
.quantity = EcsLength, |
|
.base = EcsMeters, |
|
.prefix = EcsKilo }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloMeters, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Miles" }), |
|
.quantity = EcsLength, |
|
.symbol = "mi" |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMiles, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsPixels = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Pixels" }), |
|
.quantity = EcsLength, |
|
.symbol = "px" |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsPixels, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Pressure units */ |
|
|
|
EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Pressure" }); |
|
prev_scope = ecs_set_scope(world, EcsPressure); |
|
EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Pascal" }), |
|
.quantity = EcsPressure, |
|
.symbol = "Pa" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsPascal, |
|
.kind = EcsF32 |
|
}); |
|
EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Bar" }), |
|
.quantity = EcsPressure, |
|
.symbol = "bar" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsBar, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Speed units */ |
|
|
|
EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Speed" }); |
|
prev_scope = ecs_set_scope(world, EcsSpeed); |
|
EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MetersPerSecond" }), |
|
.quantity = EcsSpeed, |
|
.base = EcsMeters, |
|
.over = EcsSeconds }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMetersPerSecond, |
|
.kind = EcsF32 |
|
}); |
|
EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloMetersPerSecond" }), |
|
.quantity = EcsSpeed, |
|
.base = EcsKiloMeters, |
|
.over = EcsSeconds }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloMetersPerSecond, |
|
.kind = EcsF32 |
|
}); |
|
EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloMetersPerHour" }), |
|
.quantity = EcsSpeed, |
|
.base = EcsKiloMeters, |
|
.over = EcsHours }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloMetersPerHour, |
|
.kind = EcsF32 |
|
}); |
|
EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MilesPerHour" }), |
|
.quantity = EcsSpeed, |
|
.base = EcsMiles, |
|
.over = EcsHours }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMilesPerHour, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Acceleration */ |
|
|
|
EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Acceleration" }), |
|
.base = EcsMetersPerSecond, |
|
.over = EcsSeconds }); |
|
ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.id = EcsAcceleration |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsAcceleration, |
|
.kind = EcsF32 |
|
}); |
|
|
|
/* Temperature units */ |
|
|
|
EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Temperature" }); |
|
prev_scope = ecs_set_scope(world, EcsTemperature); |
|
EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Kelvin" }), |
|
.quantity = EcsTemperature, |
|
.symbol = "K" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKelvin, |
|
.kind = EcsF32 |
|
}); |
|
EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Celsius" }), |
|
.quantity = EcsTemperature, |
|
.symbol = "°C" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsCelsius, |
|
.kind = EcsF32 |
|
}); |
|
EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Fahrenheit" }), |
|
.quantity = EcsTemperature, |
|
.symbol = "F" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsFahrenheit, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Data units */ |
|
|
|
EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Data" }); |
|
prev_scope = ecs_set_scope(world, EcsData); |
|
|
|
EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Bits" }), |
|
.quantity = EcsData, |
|
.symbol = "bit" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsBits, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloBits" }), |
|
.quantity = EcsData, |
|
.base = EcsBits, |
|
.prefix = EcsKilo }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloBits, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MegaBits" }), |
|
.quantity = EcsData, |
|
.base = EcsBits, |
|
.prefix = EcsMega }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMegaBits, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "GigaBits" }), |
|
.quantity = EcsData, |
|
.base = EcsBits, |
|
.prefix = EcsGiga }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsGigaBits, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Bytes" }), |
|
.quantity = EcsData, |
|
.symbol = "B", |
|
.base = EcsBits, |
|
.translation = { .factor = 8, .power = 1 } }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsBytes, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloBytes" }), |
|
.quantity = EcsData, |
|
.base = EcsBytes, |
|
.prefix = EcsKilo }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloBytes, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MegaBytes" }), |
|
.quantity = EcsData, |
|
.base = EcsBytes, |
|
.prefix = EcsMega }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMegaBytes, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "GigaBytes" }), |
|
.quantity = EcsData, |
|
.base = EcsBytes, |
|
.prefix = EcsGiga }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsGigaBytes, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KibiBytes" }), |
|
.quantity = EcsData, |
|
.base = EcsBytes, |
|
.prefix = EcsKibi }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKibiBytes, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MebiBytes" }), |
|
.quantity = EcsData, |
|
.base = EcsBytes, |
|
.prefix = EcsMebi }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMebiBytes, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "GibiBytes" }), |
|
.quantity = EcsData, |
|
.base = EcsBytes, |
|
.prefix = EcsGibi }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsGibiBytes, |
|
.kind = EcsU64 |
|
}); |
|
|
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* DataRate units */ |
|
|
|
EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "DataRate" }); |
|
prev_scope = ecs_set_scope(world, EcsDataRate); |
|
|
|
EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "BitsPerSecond" }), |
|
.quantity = EcsDataRate, |
|
.base = EcsBits, |
|
.over = EcsSeconds }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsBitsPerSecond, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloBitsPerSecond" }), |
|
.quantity = EcsDataRate, |
|
.base = EcsKiloBits, |
|
.over = EcsSeconds |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloBitsPerSecond, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MegaBitsPerSecond" }), |
|
.quantity = EcsDataRate, |
|
.base = EcsMegaBits, |
|
.over = EcsSeconds |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMegaBitsPerSecond, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "GigaBitsPerSecond" }), |
|
.quantity = EcsDataRate, |
|
.base = EcsGigaBits, |
|
.over = EcsSeconds |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsGigaBitsPerSecond, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "BytesPerSecond" }), |
|
.quantity = EcsDataRate, |
|
.base = EcsBytes, |
|
.over = EcsSeconds }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsBytesPerSecond, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloBytesPerSecond" }), |
|
.quantity = EcsDataRate, |
|
.base = EcsKiloBytes, |
|
.over = EcsSeconds |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloBytesPerSecond, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MegaBytesPerSecond" }), |
|
.quantity = EcsDataRate, |
|
.base = EcsMegaBytes, |
|
.over = EcsSeconds |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMegaBytesPerSecond, |
|
.kind = EcsU64 |
|
}); |
|
|
|
EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "GigaBytesPerSecond" }), |
|
.quantity = EcsDataRate, |
|
.base = EcsGigaBytes, |
|
.over = EcsSeconds |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsGigaBytesPerSecond, |
|
.kind = EcsU64 |
|
}); |
|
|
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Percentage */ |
|
|
|
EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Percentage" }); |
|
ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = EcsPercentage, |
|
.symbol = "%" |
|
}); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsPercentage, |
|
.kind = EcsF32 |
|
}); |
|
|
|
/* Angles */ |
|
|
|
EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Angle" }); |
|
prev_scope = ecs_set_scope(world, EcsAngle); |
|
EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Radians" }), |
|
.quantity = EcsAngle, |
|
.symbol = "rad" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsRadians, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Degrees" }), |
|
.quantity = EcsAngle, |
|
.symbol = "°" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsDegrees, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* DeciBel */ |
|
|
|
EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Bel" }), |
|
.symbol = "B" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsBel, |
|
.kind = EcsF32 |
|
}); |
|
EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "DeciBel" }), |
|
.prefix = EcsDeci, |
|
.base = EcsBel }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsDeciBel, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Frequency" }); |
|
prev_scope = ecs_set_scope(world, EcsFrequency); |
|
|
|
EcsHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Hertz" }), |
|
.quantity = EcsFrequency, |
|
.symbol = "Hz" }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsHertz, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsKiloHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "KiloHertz" }), |
|
.prefix = EcsKilo, |
|
.base = EcsHertz }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsKiloHertz, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsMegaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "MegaHertz" }), |
|
.prefix = EcsMega, |
|
.base = EcsHertz }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsMegaHertz, |
|
.kind = EcsF32 |
|
}); |
|
|
|
EcsGigaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "GigaHertz" }), |
|
.prefix = EcsGiga, |
|
.base = EcsHertz }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsGigaHertz, |
|
.kind = EcsF32 |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){ |
|
.name = "Uri" }); |
|
prev_scope = ecs_set_scope(world, EcsUri); |
|
|
|
EcsUriHyperlink = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Hyperlink" }), |
|
.quantity = EcsUri }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsUriHyperlink, |
|
.kind = EcsString |
|
}); |
|
|
|
EcsUriImage = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "Image" }), |
|
.quantity = EcsUri }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsUriImage, |
|
.kind = EcsString |
|
}); |
|
|
|
EcsUriFile = ecs_unit_init(world, &(ecs_unit_desc_t){ |
|
.entity = ecs_entity(world, { .name = "File" }), |
|
.quantity = EcsUri }); |
|
ecs_primitive_init(world, &(ecs_primitive_desc_t){ |
|
.entity = EcsUriFile, |
|
.kind = EcsString |
|
}); |
|
ecs_set_scope(world, prev_scope); |
|
|
|
/* Documentation */ |
|
#ifdef FLECS_DOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
|
|
ecs_doc_set_brief(world, EcsDuration, |
|
"Time amount (e.g. \"20 seconds\", \"2 hours\")"); |
|
ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); |
|
ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); |
|
ecs_doc_set_brief(world, EcsHours, "60 minutes"); |
|
ecs_doc_set_brief(world, EcsDays, "24 hours"); |
|
|
|
ecs_doc_set_brief(world, EcsTime, |
|
"Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); |
|
ecs_doc_set_brief(world, EcsDate, |
|
"Seconds passed since January 1st 1970"); |
|
|
|
ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); |
|
|
|
ecs_doc_set_brief(world, EcsElectricCurrent, |
|
"Units of electrical current (e.g. \"2 ampere\")"); |
|
|
|
ecs_doc_set_brief(world, EcsAmount, |
|
"Units of amount of substance (e.g. \"2 mole\")"); |
|
|
|
ecs_doc_set_brief(world, EcsLuminousIntensity, |
|
"Units of luminous intensity (e.g. \"1 candela\")"); |
|
|
|
ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); |
|
|
|
ecs_doc_set_brief(world, EcsLength, |
|
"Units of length (e.g. \"5 meters\", \"20 miles\")"); |
|
|
|
ecs_doc_set_brief(world, EcsPressure, |
|
"Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); |
|
|
|
ecs_doc_set_brief(world, EcsSpeed, |
|
"Units of movement (e.g. \"5 meters/second\")"); |
|
|
|
ecs_doc_set_brief(world, EcsAcceleration, |
|
"Unit of speed increase (e.g. \"5 meters/second/second\")"); |
|
|
|
ecs_doc_set_brief(world, EcsTemperature, |
|
"Units of temperature (e.g. \"5 degrees Celsius\")"); |
|
|
|
ecs_doc_set_brief(world, EcsData, |
|
"Units of information (e.g. \"8 bits\", \"100 megabytes\")"); |
|
|
|
ecs_doc_set_brief(world, EcsDataRate, |
|
"Units of data transmission (e.g. \"100 megabits/second\")"); |
|
|
|
ecs_doc_set_brief(world, EcsAngle, |
|
"Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); |
|
|
|
ecs_doc_set_brief(world, EcsFrequency, |
|
"The number of occurrences of a repeating event per unit of time."); |
|
|
|
ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); |
|
#endif |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file datastructures/allocator.c |
|
* @brief Allocator for any size. |
|
* |
|
* Allocators create a block allocator for each requested size. |
|
*/ |
|
|
|
|
|
static |
|
ecs_size_t flecs_allocator_size( |
|
ecs_size_t size) |
|
{ |
|
return ECS_ALIGN(size, 16); |
|
} |
|
|
|
static |
|
ecs_size_t flecs_allocator_size_hash( |
|
ecs_size_t size) |
|
{ |
|
return size >> 4; |
|
} |
|
|
|
void flecs_allocator_init( |
|
ecs_allocator_t *a) |
|
{ |
|
flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t, |
|
FLECS_SPARSE_PAGE_SIZE); |
|
flecs_sparse_init_t(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t); |
|
} |
|
|
|
void flecs_allocator_fini( |
|
ecs_allocator_t *a) |
|
{ |
|
int32_t i = 0, count = flecs_sparse_count(&a->sizes); |
|
for (i = 0; i < count; i ++) { |
|
ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( |
|
&a->sizes, ecs_block_allocator_t, i); |
|
flecs_ballocator_fini(ba); |
|
} |
|
flecs_sparse_fini(&a->sizes); |
|
flecs_ballocator_fini(&a->chunks); |
|
} |
|
|
|
ecs_block_allocator_t* flecs_allocator_get( |
|
ecs_allocator_t *a, |
|
ecs_size_t size) |
|
{ |
|
ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); |
|
if (!size) { |
|
return NULL; |
|
} |
|
|
|
ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(size <= flecs_allocator_size(size), ECS_INTERNAL_ERROR, NULL); |
|
size = flecs_allocator_size(size); |
|
ecs_size_t hash = flecs_allocator_size_hash(size); |
|
ecs_block_allocator_t *result = flecs_sparse_get_any_t(&a->sizes, |
|
ecs_block_allocator_t, (uint32_t)hash); |
|
|
|
if (!result) { |
|
result = flecs_sparse_ensure_fast_t(&a->sizes, |
|
ecs_block_allocator_t, (uint32_t)hash); |
|
flecs_ballocator_init(result, size); |
|
} |
|
|
|
ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return result; |
|
} |
|
|
|
char* flecs_strdup( |
|
ecs_allocator_t *a, |
|
const char* str) |
|
{ |
|
ecs_size_t len = ecs_os_strlen(str); |
|
char *result = flecs_alloc_n(a, char, len + 1); |
|
ecs_os_memcpy(result, str, len + 1); |
|
return result; |
|
} |
|
|
|
void flecs_strfree( |
|
ecs_allocator_t *a, |
|
char* str) |
|
{ |
|
ecs_size_t len = ecs_os_strlen(str); |
|
flecs_free_n(a, char, len + 1, str); |
|
} |
|
|
|
void* flecs_dup( |
|
ecs_allocator_t *a, |
|
ecs_size_t size, |
|
const void *src) |
|
{ |
|
ecs_block_allocator_t *ba = flecs_allocator_get(a, size); |
|
if (ba) { |
|
void *dst = flecs_balloc(ba); |
|
ecs_os_memcpy(dst, src, size); |
|
return dst; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
/** |
|
* @file datastructures/bitset.c |
|
* @brief Bitset data structure. |
|
* |
|
* Simple bitset implementation. The bitset allows for storage of arbitrary |
|
* numbers of bits. |
|
*/ |
|
|
|
|
|
static |
|
void ensure( |
|
ecs_bitset_t *bs, |
|
ecs_size_t size) |
|
{ |
|
if (!bs->size) { |
|
int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); |
|
bs->size = ((size - 1) / 64 + 1) * 64; |
|
bs->data = ecs_os_calloc(new_size); |
|
} else if (size > bs->size) { |
|
int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); |
|
bs->size = ((size - 1) / 64 + 1) * 64; |
|
int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); |
|
bs->data = ecs_os_realloc(bs->data, new_size); |
|
ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); |
|
} |
|
} |
|
|
|
void flecs_bitset_init( |
|
ecs_bitset_t* bs) |
|
{ |
|
bs->size = 0; |
|
bs->count = 0; |
|
bs->data = NULL; |
|
} |
|
|
|
void flecs_bitset_ensure( |
|
ecs_bitset_t *bs, |
|
int32_t count) |
|
{ |
|
if (count > bs->count) { |
|
bs->count = count; |
|
ensure(bs, count); |
|
} |
|
} |
|
|
|
void flecs_bitset_fini( |
|
ecs_bitset_t *bs) |
|
{ |
|
ecs_os_free(bs->data); |
|
bs->data = NULL; |
|
bs->count = 0; |
|
} |
|
|
|
void flecs_bitset_addn( |
|
ecs_bitset_t *bs, |
|
int32_t count) |
|
{ |
|
int32_t elem = bs->count += count; |
|
ensure(bs, elem); |
|
} |
|
|
|
void flecs_bitset_set( |
|
ecs_bitset_t *bs, |
|
int32_t elem, |
|
bool value) |
|
{ |
|
ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); |
|
uint32_t hi = ((uint32_t)elem) >> 6; |
|
uint32_t lo = ((uint32_t)elem) & 0x3F; |
|
uint64_t v = bs->data[hi]; |
|
bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); |
|
error: |
|
return; |
|
} |
|
|
|
bool flecs_bitset_get( |
|
const ecs_bitset_t *bs, |
|
int32_t elem) |
|
{ |
|
ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); |
|
return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); |
|
error: |
|
return false; |
|
} |
|
|
|
int32_t flecs_bitset_count( |
|
const ecs_bitset_t *bs) |
|
{ |
|
return bs->count; |
|
} |
|
|
|
void flecs_bitset_remove( |
|
ecs_bitset_t *bs, |
|
int32_t elem) |
|
{ |
|
ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); |
|
int32_t last = bs->count - 1; |
|
bool last_value = flecs_bitset_get(bs, last); |
|
flecs_bitset_set(bs, elem, last_value); |
|
flecs_bitset_set(bs, last, 0); |
|
bs->count --; |
|
error: |
|
return; |
|
} |
|
|
|
void flecs_bitset_swap( |
|
ecs_bitset_t *bs, |
|
int32_t elem_a, |
|
int32_t elem_b) |
|
{ |
|
ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); |
|
|
|
bool a = flecs_bitset_get(bs, elem_a); |
|
bool b = flecs_bitset_get(bs, elem_b); |
|
flecs_bitset_set(bs, elem_a, b); |
|
flecs_bitset_set(bs, elem_b, a); |
|
error: |
|
return; |
|
} |
|
|
|
/** |
|
* @file datastructures/block_allocator.c |
|
* @brief Block allocator. |
|
* |
|
* A block allocator is an allocator for a fixed size that allocates blocks of |
|
* memory with N elements of the requested size. |
|
*/ |
|
|
|
|
|
// #ifdef FLECS_SANITIZE |
|
// #define FLECS_MEMSET_UNINITIALIZED |
|
// #endif |
|
|
|
int64_t ecs_block_allocator_alloc_count = 0; |
|
int64_t ecs_block_allocator_free_count = 0; |
|
|
|
static |
|
ecs_block_allocator_chunk_header_t* flecs_balloc_block( |
|
ecs_block_allocator_t *allocator) |
|
{ |
|
if (!allocator->chunk_size) { |
|
return NULL; |
|
} |
|
|
|
ecs_block_allocator_block_t *block = |
|
ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) + |
|
allocator->block_size); |
|
ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block, |
|
ECS_SIZEOF(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; |
|
} |
|
|
|
ecs_os_linc(&ecs_block_allocator_alloc_count); |
|
|
|
chunk->next = NULL; |
|
return first_chunk; |
|
} |
|
|
|
void flecs_ballocator_init( |
|
ecs_block_allocator_t *ba, |
|
ecs_size_t size) |
|
{ |
|
ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); |
|
ba->data_size = size; |
|
#ifdef FLECS_SANITIZE |
|
size += ECS_SIZEOF(int64_t); |
|
#endif |
|
ba->chunk_size = ECS_ALIGN(size, 16); |
|
ba->chunks_per_block = ECS_MAX(4096 / ba->chunk_size, 1); |
|
ba->block_size = ba->chunks_per_block * ba->chunk_size; |
|
ba->head = NULL; |
|
ba->block_head = NULL; |
|
ba->block_tail = NULL; |
|
} |
|
|
|
ecs_block_allocator_t* flecs_ballocator_new( |
|
ecs_size_t size) |
|
{ |
|
ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); |
|
flecs_ballocator_init(result, size); |
|
return result; |
|
} |
|
|
|
void flecs_ballocator_fini( |
|
ecs_block_allocator_t *ba) |
|
{ |
|
ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
#ifdef FLECS_SANITIZE |
|
ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, |
|
"(size = %u)", (uint32_t)ba->data_size); |
|
#endif |
|
|
|
ecs_block_allocator_block_t *block; |
|
for (block = ba->block_head; block;) { |
|
ecs_block_allocator_block_t *next = block->next; |
|
ecs_os_free(block); |
|
ecs_os_linc(&ecs_block_allocator_free_count); |
|
block = next; |
|
} |
|
ba->block_head = NULL; |
|
} |
|
|
|
void flecs_ballocator_free( |
|
ecs_block_allocator_t *ba) |
|
{ |
|
flecs_ballocator_fini(ba); |
|
ecs_os_free(ba); |
|
} |
|
|
|
void* flecs_balloc( |
|
ecs_block_allocator_t *ba) |
|
{ |
|
void *result; |
|
#ifdef FLECS_USE_OS_ALLOC |
|
result = ecs_os_malloc(ba->data_size); |
|
#else |
|
|
|
if (!ba) return NULL; |
|
|
|
if (!ba->head) { |
|
ba->head = flecs_balloc_block(ba); |
|
} |
|
|
|
result = ba->head; |
|
ba->head = ba->head->next; |
|
|
|
#ifdef FLECS_SANITIZE |
|
ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); |
|
ba->alloc_count ++; |
|
*(int64_t*)result = ba->chunk_size; |
|
result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); |
|
#endif |
|
#endif |
|
|
|
#ifdef FLECS_MEMSET_UNINITIALIZED |
|
ecs_os_memset(result, 0xAA, ba->data_size); |
|
#endif |
|
|
|
return result; |
|
} |
|
|
|
void* flecs_bcalloc( |
|
ecs_block_allocator_t *ba) |
|
{ |
|
#ifdef FLECS_USE_OS_ALLOC |
|
return ecs_os_calloc(ba->data_size); |
|
#endif |
|
|
|
if (!ba) return NULL; |
|
void *result = flecs_balloc(ba); |
|
ecs_os_memset(result, 0, ba->data_size); |
|
return result; |
|
} |
|
|
|
void flecs_bfree( |
|
ecs_block_allocator_t *ba, |
|
void *memory) |
|
{ |
|
#ifdef FLECS_USE_OS_ALLOC |
|
ecs_os_free(memory); |
|
return; |
|
#endif |
|
|
|
if (!ba) { |
|
ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); |
|
return; |
|
} |
|
if (memory == NULL) { |
|
return; |
|
} |
|
|
|
#ifdef FLECS_SANITIZE |
|
memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); |
|
if (*(int64_t*)memory != ba->chunk_size) { |
|
ecs_err("chunk %p returned to wrong allocator " |
|
"(chunk = %ub, allocator = %ub)", |
|
memory, *(int64_t*)memory, ba->chunk_size); |
|
ecs_abort(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
ba->alloc_count --; |
|
#endif |
|
|
|
ecs_block_allocator_chunk_header_t *chunk = memory; |
|
chunk->next = ba->head; |
|
ba->head = chunk; |
|
ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); |
|
} |
|
|
|
void* flecs_brealloc( |
|
ecs_block_allocator_t *dst, |
|
ecs_block_allocator_t *src, |
|
void *memory) |
|
{ |
|
void *result; |
|
#ifdef FLECS_USE_OS_ALLOC |
|
result = ecs_os_realloc(memory, dst->data_size); |
|
#else |
|
if (dst == src) { |
|
return memory; |
|
} |
|
|
|
result = flecs_balloc(dst); |
|
if (result && src) { |
|
ecs_size_t size = src->data_size; |
|
if (dst->data_size < size) { |
|
size = dst->data_size; |
|
} |
|
ecs_os_memcpy(result, memory, size); |
|
} |
|
flecs_bfree(src, memory); |
|
#endif |
|
#ifdef FLECS_MEMSET_UNINITIALIZED |
|
if (dst && src && (dst->data_size > src->data_size)) { |
|
ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA, |
|
dst->data_size - src->data_size); |
|
} else if (dst && !src) { |
|
ecs_os_memset(result, 0xAA, dst->data_size); |
|
} |
|
#endif |
|
|
|
return result; |
|
} |
|
|
|
void* flecs_bdup( |
|
ecs_block_allocator_t *ba, |
|
void *memory) |
|
{ |
|
#ifdef FLECS_USE_OS_ALLOC |
|
if (memory && ba->chunk_size) { |
|
return ecs_os_memdup(memory, ba->data_size); |
|
} else { |
|
return NULL; |
|
} |
|
#endif |
|
|
|
void *result = flecs_balloc(ba); |
|
if (result) { |
|
ecs_os_memcpy(result, memory, ba->data_size); |
|
} |
|
return result; |
|
} |
|
|
|
// This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) |
|
// main repo: https://github.com/wangyi-fudan/wyhash |
|
// author: 王一 Wang Yi |
|
// contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, |
|
// Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, |
|
// Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, |
|
// hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric |
|
|
|
/* quick example: |
|
string s="fjsakfdsjkf"; |
|
uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); |
|
*/ |
|
|
|
|
|
#ifndef WYHASH_CONDOM |
|
//protections that produce different results: |
|
//1: normal valid behavior |
|
//2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" |
|
#define WYHASH_CONDOM 1 |
|
#endif |
|
|
|
#ifndef WYHASH_32BIT_MUM |
|
//0: normal version, slow on 32 bit systems |
|
//1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function |
|
#define WYHASH_32BIT_MUM 0 |
|
#endif |
|
|
|
//includes |
|
#include <stdint.h> |
|
#include <string.h> |
|
#if defined(_MSC_VER) && defined(_M_X64) |
|
#include <intrin.h> |
|
#pragma intrinsic(_umul128) |
|
#endif |
|
|
|
//likely and unlikely macros |
|
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) |
|
#define likely_(x) __builtin_expect(x,1) |
|
#define unlikely_(x) __builtin_expect(x,0) |
|
#else |
|
#define likely_(x) (x) |
|
#define unlikely_(x) (x) |
|
#endif |
|
|
|
//128bit multiply function |
|
static inline void wymum_(uint64_t *A, uint64_t *B){ |
|
#if(WYHASH_32BIT_MUM) |
|
uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; |
|
#if(WYHASH_CONDOM>1) |
|
*A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; |
|
#else |
|
*A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; |
|
#endif |
|
#elif defined(__SIZEOF_INT128__) |
|
__uint128_t r=*A; r*=*B; |
|
#if(WYHASH_CONDOM>1) |
|
*A^=(uint64_t)r; *B^=(uint64_t)(r>>64); |
|
#else |
|
*A=(uint64_t)r; *B=(uint64_t)(r>>64); |
|
#endif |
|
#elif defined(_MSC_VER) && defined(_M_X64) |
|
#if(WYHASH_CONDOM>1) |
|
uint64_t a, b; |
|
a=_umul128(*A,*B,&b); |
|
*A^=a; *B^=b; |
|
#else |
|
*A=_umul128(*A,*B,B); |
|
#endif |
|
#else |
|
uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; |
|
uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t<rl; |
|
lo=t+(rm1<<32); c+=lo<t; hi=rh+(rm0>>32)+(rm1>>32)+c; |
|
#if(WYHASH_CONDOM>1) |
|
*A^=lo; *B^=hi; |
|
#else |
|
*A=lo; *B=hi; |
|
#endif |
|
#endif |
|
} |
|
|
|
//multiply and xor mix function, aka MUM |
|
static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } |
|
|
|
//endian macros |
|
#ifndef WYHASH_LITTLE_ENDIAN |
|
#if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) |
|
#define WYHASH_LITTLE_ENDIAN 1 |
|
#elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) |
|
#define WYHASH_LITTLE_ENDIAN 0 |
|
#else |
|
#warning could not determine endianness! Falling back to little endian. |
|
#define WYHASH_LITTLE_ENDIAN 1 |
|
#endif |
|
#endif |
|
|
|
//read functions |
|
#if (WYHASH_LITTLE_ENDIAN) |
|
static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} |
|
static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} |
|
#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) |
|
static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} |
|
static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} |
|
#elif defined(_MSC_VER) |
|
static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} |
|
static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} |
|
#else |
|
static inline uint64_t wyr8_(const uint8_t *p) { |
|
uint64_t v; memcpy(&v, p, 8); |
|
return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); |
|
} |
|
static inline uint64_t wyr4_(const uint8_t *p) { |
|
uint32_t v; memcpy(&v, p, 4); |
|
return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); |
|
} |
|
#endif |
|
static inline uint64_t wyr3_(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} |
|
|
|
//wyhash main function |
|
static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ |
|
const uint8_t *p=(const uint8_t *)key; seed^=wymix_(seed^secret[0],secret[1]); uint64_t a, b; |
|
if(likely_(len<=16)){ |
|
if(likely_(len>=4)){ a=(wyr4_(p)<<32)|wyr4_(p+((len>>3)<<2)); b=(wyr4_(p+len-4)<<32)|wyr4_(p+len-4-((len>>3)<<2)); } |
|
else if(likely_(len>0)){ a=wyr3_(p,len); b=0;} |
|
else a=b=0; |
|
} |
|
else{ |
|
size_t i=len; |
|
if(unlikely_(i>48)){ |
|
uint64_t see1=seed, see2=seed; |
|
do{ |
|
seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); |
|
see1=wymix_(wyr8_(p+16)^secret[2],wyr8_(p+24)^see1); |
|
see2=wymix_(wyr8_(p+32)^secret[3],wyr8_(p+40)^see2); |
|
p+=48; i-=48; |
|
}while(likely_(i>48)); |
|
seed^=see1^see2; |
|
} |
|
while(unlikely_(i>16)){ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); i-=16; p+=16; } |
|
a=wyr8_(p+i-16); b=wyr8_(p+i-8); |
|
} |
|
a^=secret[1]; b^=seed; wymum_(&a,&b); |
|
return wymix_(a^secret[0]^len,b^secret[1]); |
|
} |
|
|
|
//the default secret parameters |
|
static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; |
|
|
|
uint64_t flecs_hash( |
|
const void *data, |
|
ecs_size_t length) |
|
{ |
|
return wyhash(data, flecs_ito(size_t, length), 0, wyp_); |
|
} |
|
|
|
/** |
|
* @file datastructures/hashmap.c |
|
* @brief Hashmap data structure. |
|
* |
|
* The hashmap data structure is built on top of the map data structure. Where |
|
* the map data structure can only work with 64bit key values, the hashmap can |
|
* hash keys of any size, and handles collisions between hashes. |
|
*/ |
|
|
|
|
|
static |
|
int32_t flecs_hashmap_find_key( |
|
const ecs_hashmap_t *map, |
|
ecs_vec_t *keys, |
|
ecs_size_t key_size, |
|
const void *key) |
|
{ |
|
int32_t i, count = ecs_vec_count(keys); |
|
void *key_array = ecs_vec_first(keys); |
|
for (i = 0; i < count; i ++) { |
|
void *key_ptr = ECS_OFFSET(key_array, key_size * i); |
|
if (map->compare(key_ptr, key) == 0) { |
|
return i; |
|
} |
|
} |
|
return -1; |
|
} |
|
|
|
void flecs_hashmap_init_( |
|
ecs_hashmap_t *map, |
|
ecs_size_t key_size, |
|
ecs_size_t value_size, |
|
ecs_hash_value_action_t hash, |
|
ecs_compare_action_t compare, |
|
ecs_allocator_t *allocator) |
|
{ |
|
map->key_size = key_size; |
|
map->value_size = value_size; |
|
map->hash = hash; |
|
map->compare = compare; |
|
flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t); |
|
ecs_map_init(&map->impl, allocator); |
|
} |
|
|
|
void flecs_hashmap_fini( |
|
ecs_hashmap_t *map) |
|
{ |
|
ecs_allocator_t *a = map->impl.allocator; |
|
ecs_map_iter_t it = ecs_map_iter(&map->impl); |
|
|
|
while (ecs_map_next(&it)) { |
|
ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); |
|
ecs_vec_fini(a, &bucket->keys, map->key_size); |
|
ecs_vec_fini(a, &bucket->values, map->value_size); |
|
#ifdef FLECS_SANITIZE |
|
flecs_bfree(&map->bucket_allocator, bucket); |
|
#endif |
|
} |
|
|
|
flecs_ballocator_fini(&map->bucket_allocator); |
|
ecs_map_fini(&map->impl); |
|
} |
|
|
|
void flecs_hashmap_copy( |
|
ecs_hashmap_t *dst, |
|
const ecs_hashmap_t *src) |
|
{ |
|
ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); |
|
|
|
flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, |
|
src->compare, src->impl.allocator); |
|
ecs_map_copy(&dst->impl, &src->impl); |
|
|
|
ecs_allocator_t *a = dst->impl.allocator; |
|
ecs_map_iter_t it = ecs_map_iter(&dst->impl); |
|
while (ecs_map_next(&it)) { |
|
ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t); |
|
ecs_hm_bucket_t *src_bucket = bucket_ptr[0]; |
|
ecs_hm_bucket_t *dst_bucket = flecs_balloc(&dst->bucket_allocator); |
|
bucket_ptr[0] = dst_bucket; |
|
dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size); |
|
dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size); |
|
} |
|
} |
|
|
|
void* flecs_hashmap_get_( |
|
const ecs_hashmap_t *map, |
|
ecs_size_t key_size, |
|
const void *key, |
|
ecs_size_t value_size) |
|
{ |
|
ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); |
|
|
|
uint64_t hash = map->hash(key); |
|
ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, |
|
ecs_hm_bucket_t, hash); |
|
if (!bucket) { |
|
return NULL; |
|
} |
|
|
|
int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); |
|
if (index == -1) { |
|
return NULL; |
|
} |
|
|
|
return ecs_vec_get(&bucket->values, value_size, index); |
|
} |
|
|
|
flecs_hashmap_result_t flecs_hashmap_ensure_( |
|
ecs_hashmap_t *map, |
|
ecs_size_t key_size, |
|
const void *key, |
|
ecs_size_t value_size) |
|
{ |
|
ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); |
|
|
|
uint64_t hash = map->hash(key); |
|
ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); |
|
ecs_hm_bucket_t *bucket = r[0]; |
|
if (!bucket) { |
|
bucket = r[0] = flecs_bcalloc(&map->bucket_allocator); |
|
} |
|
|
|
ecs_allocator_t *a = map->impl.allocator; |
|
void *value_ptr, *key_ptr; |
|
ecs_vec_t *keys = &bucket->keys; |
|
ecs_vec_t *values = &bucket->values; |
|
if (!keys->array) { |
|
keys = ecs_vec_init(a, &bucket->keys, key_size, 1); |
|
values = ecs_vec_init(a, &bucket->values, value_size, 1); |
|
key_ptr = ecs_vec_append(a, keys, key_size); |
|
value_ptr = ecs_vec_append(a, values, value_size); |
|
ecs_os_memcpy(key_ptr, key, key_size); |
|
ecs_os_memset(value_ptr, 0, value_size); |
|
} else { |
|
int32_t index = flecs_hashmap_find_key(map, keys, key_size, key); |
|
if (index == -1) { |
|
key_ptr = ecs_vec_append(a, keys, key_size); |
|
value_ptr = ecs_vec_append(a, values, value_size); |
|
ecs_os_memcpy(key_ptr, key, key_size); |
|
ecs_os_memset(value_ptr, 0, value_size); |
|
} else { |
|
key_ptr = ecs_vec_get(keys, key_size, index); |
|
value_ptr = ecs_vec_get(values, value_size, index); |
|
} |
|
} |
|
|
|
return (flecs_hashmap_result_t){ |
|
.key = key_ptr, .value = value_ptr, .hash = hash |
|
}; |
|
} |
|
|
|
void flecs_hashmap_set_( |
|
ecs_hashmap_t *map, |
|
ecs_size_t key_size, |
|
void *key, |
|
ecs_size_t value_size, |
|
const void *value) |
|
{ |
|
void *value_ptr = flecs_hashmap_ensure_(map, key_size, key, value_size).value; |
|
ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_os_memcpy(value_ptr, value, value_size); |
|
} |
|
|
|
ecs_hm_bucket_t* flecs_hashmap_get_bucket( |
|
const ecs_hashmap_t *map, |
|
uint64_t hash) |
|
{ |
|
ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); |
|
} |
|
|
|
void flecs_hm_bucket_remove( |
|
ecs_hashmap_t *map, |
|
ecs_hm_bucket_t *bucket, |
|
uint64_t hash, |
|
int32_t index) |
|
{ |
|
ecs_vec_remove(&bucket->keys, map->key_size, index); |
|
ecs_vec_remove(&bucket->values, map->value_size, index); |
|
|
|
if (!ecs_vec_count(&bucket->keys)) { |
|
ecs_allocator_t *a = map->impl.allocator; |
|
ecs_vec_fini(a, &bucket->keys, map->key_size); |
|
ecs_vec_fini(a, &bucket->values, map->value_size); |
|
ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash); |
|
ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b; |
|
flecs_bfree(&map->bucket_allocator, bucket); |
|
} |
|
} |
|
|
|
void flecs_hashmap_remove_w_hash_( |
|
ecs_hashmap_t *map, |
|
ecs_size_t key_size, |
|
const void *key, |
|
ecs_size_t value_size, |
|
uint64_t hash) |
|
{ |
|
ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); |
|
(void)value_size; |
|
|
|
ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, |
|
ecs_hm_bucket_t, hash); |
|
if (!bucket) { |
|
return; |
|
} |
|
|
|
int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); |
|
if (index == -1) { |
|
return; |
|
} |
|
|
|
flecs_hm_bucket_remove(map, bucket, hash, index); |
|
} |
|
|
|
void flecs_hashmap_remove_( |
|
ecs_hashmap_t *map, |
|
ecs_size_t key_size, |
|
const void *key, |
|
ecs_size_t value_size) |
|
{ |
|
ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); |
|
|
|
uint64_t hash = map->hash(key); |
|
flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash); |
|
} |
|
|
|
flecs_hashmap_iter_t flecs_hashmap_iter( |
|
ecs_hashmap_t *map) |
|
{ |
|
return (flecs_hashmap_iter_t){ |
|
.it = ecs_map_iter(&map->impl) |
|
}; |
|
} |
|
|
|
void* flecs_hashmap_next_( |
|
flecs_hashmap_iter_t *it, |
|
ecs_size_t key_size, |
|
void *key_out, |
|
ecs_size_t value_size) |
|
{ |
|
int32_t index = ++ it->index; |
|
ecs_hm_bucket_t *bucket = it->bucket; |
|
while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) { |
|
ecs_map_next(&it->it); |
|
bucket = it->bucket = ecs_map_ptr(&it->it); |
|
if (!bucket) { |
|
return NULL; |
|
} |
|
index = it->index = 0; |
|
} |
|
|
|
if (key_out) { |
|
*(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); |
|
} |
|
|
|
return ecs_vec_get(&bucket->values, value_size, index); |
|
} |
|
|
|
/** |
|
* @file datastructures/map.c |
|
* @brief Map data structure. |
|
* |
|
* Map data structure for 64bit keys and dynamic payload size. |
|
*/ |
|
|
|
|
|
/* The ratio used to determine whether the map should flecs_map_rehash. If |
|
* (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ |
|
#define ECS_LOAD_FACTOR (12) |
|
#define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c) |
|
|
|
static |
|
uint8_t flecs_log2(uint32_t v) { |
|
static const uint8_t log2table[32] = |
|
{0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, |
|
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; |
|
|
|
v |= v >> 1; |
|
v |= v >> 2; |
|
v |= v >> 4; |
|
v |= v >> 8; |
|
v |= v >> 16; |
|
return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; |
|
} |
|
|
|
/* Get bucket count for number of elements */ |
|
static |
|
int32_t flecs_map_get_bucket_count( |
|
int32_t count) |
|
{ |
|
return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); |
|
} |
|
|
|
/* Get bucket shift amount for a given bucket count */ |
|
static |
|
uint8_t flecs_map_get_bucket_shift ( |
|
int32_t bucket_count) |
|
{ |
|
return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); |
|
} |
|
|
|
/* Get bucket index for provided map key */ |
|
static |
|
int32_t flecs_map_get_bucket_index( |
|
uint16_t bucket_shift, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); |
|
return (int32_t)((11400714819323198485ull * key) >> bucket_shift); |
|
} |
|
|
|
/* Get bucket for key */ |
|
static |
|
ecs_bucket_t* flecs_map_get_bucket( |
|
const ecs_map_t *map, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); |
|
int32_t bucket_id = flecs_map_get_bucket_index(map->bucket_shift, key); |
|
ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); |
|
return &map->buckets[bucket_id]; |
|
} |
|
|
|
/* Add element to bucket */ |
|
static |
|
ecs_map_val_t* flecs_map_bucket_add( |
|
ecs_block_allocator_t *allocator, |
|
ecs_bucket_t *bucket, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_bucket_entry_t *new_entry = flecs_balloc(allocator); |
|
new_entry->key = key; |
|
new_entry->next = bucket->first; |
|
bucket->first = new_entry; |
|
return &new_entry->value; |
|
} |
|
|
|
/* Remove element from bucket */ |
|
static |
|
ecs_map_val_t flecs_map_bucket_remove( |
|
ecs_map_t *map, |
|
ecs_bucket_t *bucket, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_bucket_entry_t *entry; |
|
for (entry = bucket->first; entry; entry = entry->next) { |
|
if (entry->key == key) { |
|
ecs_map_val_t value = entry->value; |
|
ecs_bucket_entry_t **next_holder = &bucket->first; |
|
while(*next_holder != entry) { |
|
next_holder = &(*next_holder)->next; |
|
} |
|
*next_holder = entry->next; |
|
flecs_bfree(map->entry_allocator, entry); |
|
map->count --; |
|
return value; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Free contents of bucket */ |
|
static |
|
void flecs_map_bucket_clear( |
|
ecs_block_allocator_t *allocator, |
|
ecs_bucket_t *bucket) |
|
{ |
|
ecs_bucket_entry_t *entry = bucket->first; |
|
while(entry) { |
|
ecs_bucket_entry_t *next = entry->next; |
|
flecs_bfree(allocator, entry); |
|
entry = next; |
|
} |
|
} |
|
|
|
/* Get payload pointer for key from bucket */ |
|
static |
|
ecs_map_val_t* flecs_map_bucket_get( |
|
ecs_bucket_t *bucket, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_bucket_entry_t *entry; |
|
for (entry = bucket->first; entry; entry = entry->next) { |
|
if (entry->key == key) { |
|
return &entry->value; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
/* Grow number of buckets */ |
|
static |
|
void flecs_map_rehash( |
|
ecs_map_t *map, |
|
int32_t count) |
|
{ |
|
count = flecs_next_pow_of_2(count); |
|
if (count < 2) { |
|
count = 2; |
|
} |
|
ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t old_count = map->bucket_count; |
|
ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); |
|
|
|
if (map->allocator) { |
|
map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, count); |
|
} else { |
|
map->buckets = ecs_os_calloc_n(ecs_bucket_t, count); |
|
} |
|
map->bucket_count = count; |
|
map->bucket_shift = flecs_map_get_bucket_shift(count); |
|
|
|
/* Remap old bucket entries to new buckets */ |
|
for (b = buckets; b < end; b++) { |
|
ecs_bucket_entry_t* entry; |
|
for (entry = b->first; entry;) { |
|
ecs_bucket_entry_t* next = entry->next; |
|
int32_t bucket_index = flecs_map_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; |
|
} |
|
} |
|
|
|
if (map->allocator) { |
|
flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets); |
|
} else { |
|
ecs_os_free(buckets); |
|
} |
|
} |
|
|
|
void ecs_map_params_init( |
|
ecs_map_params_t *params, |
|
ecs_allocator_t *allocator) |
|
{ |
|
params->allocator = allocator; |
|
flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); |
|
} |
|
|
|
void ecs_map_params_fini( |
|
ecs_map_params_t *params) |
|
{ |
|
flecs_ballocator_fini(¶ms->entry_allocator); |
|
} |
|
|
|
void ecs_map_init_w_params( |
|
ecs_map_t *result, |
|
ecs_map_params_t *params) |
|
{ |
|
ecs_os_zeromem(result); |
|
|
|
result->allocator = params->allocator; |
|
|
|
if (params->entry_allocator.chunk_size) { |
|
result->entry_allocator = ¶ms->entry_allocator; |
|
result->shared_allocator = true; |
|
} else { |
|
result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t); |
|
} |
|
|
|
flecs_map_rehash(result, 0); |
|
} |
|
|
|
void ecs_map_init_w_params_if( |
|
ecs_map_t *result, |
|
ecs_map_params_t *params) |
|
{ |
|
if (!ecs_map_is_init(result)) { |
|
ecs_map_init_w_params(result, params); |
|
} |
|
} |
|
|
|
void ecs_map_init( |
|
ecs_map_t *result, |
|
ecs_allocator_t *allocator) |
|
{ |
|
ecs_map_init_w_params(result, &(ecs_map_params_t) { |
|
.allocator = allocator |
|
}); |
|
} |
|
|
|
void ecs_map_init_if( |
|
ecs_map_t *result, |
|
ecs_allocator_t *allocator) |
|
{ |
|
if (!ecs_map_is_init(result)) { |
|
ecs_map_init(result, allocator); |
|
} |
|
} |
|
|
|
void ecs_map_fini( |
|
ecs_map_t *map) |
|
{ |
|
if (!ecs_map_is_init(map)) { |
|
return; |
|
} |
|
|
|
bool sanitize = false; |
|
#ifdef FLECS_SANITIZE |
|
sanitize = true; |
|
#endif |
|
|
|
/* Free buckets in sanitized mode, so we can replace the allocator with |
|
* regular malloc/free and use asan/valgrind to find memory errors. */ |
|
ecs_allocator_t *a = map->allocator; |
|
ecs_block_allocator_t *ea = map->entry_allocator; |
|
if (map->shared_allocator || sanitize) { |
|
ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; |
|
while (bucket != end) { |
|
flecs_map_bucket_clear(ea, bucket); |
|
bucket ++; |
|
} |
|
} |
|
|
|
if (ea && !map->shared_allocator) { |
|
flecs_ballocator_free(ea); |
|
map->entry_allocator = NULL; |
|
} |
|
if (a) { |
|
flecs_free_n(a, ecs_bucket_t, map->bucket_count, map->buckets); |
|
} else { |
|
ecs_os_free(map->buckets); |
|
} |
|
|
|
map->bucket_shift = 0; |
|
} |
|
|
|
ecs_map_val_t* ecs_map_get( |
|
const ecs_map_t *map, |
|
ecs_map_key_t key) |
|
{ |
|
return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); |
|
} |
|
|
|
void* ecs_map_get_deref_( |
|
const ecs_map_t *map, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_map_val_t* ptr = flecs_map_bucket_get( |
|
flecs_map_get_bucket(map, key), key); |
|
if (ptr) { |
|
return (void*)(uintptr_t)ptr[0]; |
|
} |
|
return NULL; |
|
} |
|
|
|
void ecs_map_insert( |
|
ecs_map_t *map, |
|
ecs_map_key_t key, |
|
ecs_map_val_t value) |
|
{ |
|
ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); |
|
int32_t map_count = ++map->count; |
|
int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); |
|
int32_t bucket_count = map->bucket_count; |
|
if (tgt_bucket_count > bucket_count) { |
|
flecs_map_rehash(map, tgt_bucket_count); |
|
} |
|
|
|
ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); |
|
flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; |
|
} |
|
|
|
void* ecs_map_insert_alloc( |
|
ecs_map_t *map, |
|
ecs_size_t elem_size, |
|
ecs_map_key_t key) |
|
{ |
|
void *elem = ecs_os_calloc(elem_size); |
|
ecs_map_insert_ptr(map, key, (uintptr_t)elem); |
|
return elem; |
|
} |
|
|
|
ecs_map_val_t* ecs_map_ensure( |
|
ecs_map_t *map, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); |
|
ecs_map_val_t *result = flecs_map_bucket_get(bucket, key); |
|
if (result) { |
|
return result; |
|
} |
|
|
|
int32_t map_count = ++map->count; |
|
int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); |
|
int32_t bucket_count = map->bucket_count; |
|
if (tgt_bucket_count > bucket_count) { |
|
flecs_map_rehash(map, tgt_bucket_count); |
|
bucket = flecs_map_get_bucket(map, key); |
|
} |
|
|
|
ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); |
|
*v = 0; |
|
return v; |
|
} |
|
|
|
void* ecs_map_ensure_alloc( |
|
ecs_map_t *map, |
|
ecs_size_t elem_size, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_map_val_t *val = ecs_map_ensure(map, key); |
|
if (!*val) { |
|
void *elem = ecs_os_calloc(elem_size); |
|
*val = (ecs_map_val_t)(uintptr_t)elem; |
|
return elem; |
|
} else { |
|
return (void*)(uintptr_t)*val; |
|
} |
|
} |
|
|
|
ecs_map_val_t ecs_map_remove( |
|
ecs_map_t *map, |
|
ecs_map_key_t key) |
|
{ |
|
return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); |
|
} |
|
|
|
void ecs_map_remove_free( |
|
ecs_map_t *map, |
|
ecs_map_key_t key) |
|
{ |
|
ecs_map_val_t val = ecs_map_remove(map, key); |
|
if (val) { |
|
ecs_os_free((void*)(uintptr_t)val); |
|
} |
|
} |
|
|
|
void ecs_map_clear( |
|
ecs_map_t *map) |
|
{ |
|
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); |
|
int32_t i, count = map->bucket_count; |
|
for (i = 0; i < count; i ++) { |
|
flecs_map_bucket_clear(map->entry_allocator, &map->buckets[i]); |
|
} |
|
if (map->allocator) { |
|
flecs_free_n(map->allocator, ecs_bucket_t, count, map->buckets); |
|
} else { |
|
ecs_os_free(map->buckets); |
|
} |
|
map->buckets = NULL; |
|
map->bucket_count = 0; |
|
map->count = 0; |
|
flecs_map_rehash(map, 2); |
|
} |
|
|
|
ecs_map_iter_t ecs_map_iter( |
|
const ecs_map_t *map) |
|
{ |
|
if (ecs_map_is_init(map)) { |
|
return (ecs_map_iter_t){ |
|
.map = map, |
|
.bucket = NULL, |
|
.entry = NULL |
|
}; |
|
} else { |
|
return (ecs_map_iter_t){ 0 }; |
|
} |
|
} |
|
|
|
bool ecs_map_next( |
|
ecs_map_iter_t *iter) |
|
{ |
|
const ecs_map_t *map = iter->map; |
|
ecs_bucket_t *end; |
|
if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { |
|
return false; |
|
} |
|
|
|
ecs_bucket_entry_t *entry = NULL; |
|
if (!iter->bucket) { |
|
for (iter->bucket = map->buckets; |
|
iter->bucket != end; |
|
++iter->bucket) |
|
{ |
|
if (iter->bucket->first) { |
|
entry = iter->bucket->first; |
|
break; |
|
} |
|
} |
|
if (iter->bucket == end) { |
|
return false; |
|
} |
|
} else if ((entry = iter->entry) == NULL) { |
|
do { |
|
++iter->bucket; |
|
if (iter->bucket == end) { |
|
return false; |
|
} |
|
} while(!iter->bucket->first); |
|
entry = iter->bucket->first; |
|
} |
|
|
|
ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); |
|
iter->entry = entry->next; |
|
iter->res = &entry->key; |
|
|
|
return true; |
|
} |
|
|
|
void ecs_map_copy( |
|
ecs_map_t *dst, |
|
const ecs_map_t *src) |
|
{ |
|
if (ecs_map_is_init(dst)) { |
|
ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_map_fini(dst); |
|
} |
|
|
|
if (!ecs_map_is_init(src)) { |
|
return; |
|
} |
|
|
|
ecs_map_init(dst, src->allocator); |
|
|
|
ecs_map_iter_t it = ecs_map_iter(src); |
|
while (ecs_map_next(&it)) { |
|
ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it)); |
|
} |
|
} |
|
|
|
/** |
|
* @file datastructures/name_index.c |
|
* @brief Data structure for resolving 64bit keys by string (name). |
|
*/ |
|
|
|
|
|
static |
|
uint64_t flecs_name_index_hash( |
|
const void *ptr) |
|
{ |
|
const ecs_hashed_string_t *str = ptr; |
|
ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); |
|
return str->hash; |
|
} |
|
|
|
static |
|
int flecs_name_index_compare( |
|
const void *ptr1, |
|
const void *ptr2) |
|
{ |
|
const ecs_hashed_string_t *str1 = ptr1; |
|
const ecs_hashed_string_t *str2 = ptr2; |
|
ecs_size_t len1 = str1->length; |
|
ecs_size_t len2 = str2->length; |
|
if (len1 != len2) { |
|
return (len1 > len2) - (len1 < len2); |
|
} |
|
|
|
return ecs_os_memcmp(str1->value, str2->value, len1); |
|
} |
|
|
|
void flecs_name_index_init( |
|
ecs_hashmap_t *hm, |
|
ecs_allocator_t *allocator) |
|
{ |
|
flecs_hashmap_init_(hm, |
|
ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), |
|
flecs_name_index_hash, |
|
flecs_name_index_compare, |
|
allocator); |
|
} |
|
|
|
void flecs_name_index_init_if( |
|
ecs_hashmap_t *hm, |
|
ecs_allocator_t *allocator) |
|
{ |
|
if (!hm->compare) { |
|
flecs_name_index_init(hm, allocator); |
|
} |
|
} |
|
|
|
bool flecs_name_index_is_init( |
|
const ecs_hashmap_t *hm) |
|
{ |
|
return hm->compare != NULL; |
|
} |
|
|
|
ecs_hashmap_t* flecs_name_index_new( |
|
ecs_world_t *world, |
|
ecs_allocator_t *allocator) |
|
{ |
|
ecs_hashmap_t *result = flecs_bcalloc(&world->allocators.hashmap); |
|
flecs_name_index_init(result, allocator); |
|
result->hashmap_allocator = &world->allocators.hashmap; |
|
return result; |
|
} |
|
|
|
void flecs_name_index_fini( |
|
ecs_hashmap_t *map) |
|
{ |
|
flecs_hashmap_fini(map); |
|
} |
|
|
|
void flecs_name_index_free( |
|
ecs_hashmap_t *map) |
|
{ |
|
if (map) { |
|
flecs_name_index_fini(map); |
|
flecs_bfree(map->hashmap_allocator, map); |
|
} |
|
} |
|
|
|
ecs_hashmap_t* flecs_name_index_copy( |
|
ecs_hashmap_t *map) |
|
{ |
|
ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator); |
|
result->hashmap_allocator = map->hashmap_allocator; |
|
flecs_hashmap_copy(result, map); |
|
return result; |
|
} |
|
|
|
ecs_hashed_string_t flecs_get_hashed_string( |
|
const char *name, |
|
ecs_size_t length, |
|
uint64_t hash) |
|
{ |
|
if (!length) { |
|
length = ecs_os_strlen(name); |
|
} else { |
|
ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (!hash) { |
|
hash = flecs_hash(name, length); |
|
} else { |
|
ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
return (ecs_hashed_string_t) { |
|
.value = ECS_CONST_CAST(char*, name), |
|
.length = length, |
|
.hash = hash |
|
}; |
|
} |
|
|
|
const uint64_t* flecs_name_index_find_ptr( |
|
const ecs_hashmap_t *map, |
|
const char *name, |
|
ecs_size_t length, |
|
uint64_t hash) |
|
{ |
|
ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); |
|
ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); |
|
if (!b) { |
|
return NULL; |
|
} |
|
|
|
ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); |
|
int32_t i, count = ecs_vec_count(&b->keys); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_hashed_string_t *key = &keys[i]; |
|
ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (hs.length != key->length) { |
|
continue; |
|
} |
|
|
|
if (!ecs_os_strcmp(name, key->value)) { |
|
uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); |
|
ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return e; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
uint64_t flecs_name_index_find( |
|
const ecs_hashmap_t *map, |
|
const char *name, |
|
ecs_size_t length, |
|
uint64_t hash) |
|
{ |
|
const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); |
|
if (id) { |
|
return id[0]; |
|
} |
|
return 0; |
|
} |
|
|
|
void flecs_name_index_remove( |
|
ecs_hashmap_t *map, |
|
uint64_t e, |
|
uint64_t hash) |
|
{ |
|
ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); |
|
if (!b) { |
|
return; |
|
} |
|
|
|
uint64_t *ids = ecs_vec_first(&b->values); |
|
int32_t i, count = ecs_vec_count(&b->values); |
|
for (i = 0; i < count; i ++) { |
|
if (ids[i] == e) { |
|
flecs_hm_bucket_remove(map, b, hash, i); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
void flecs_name_index_update_name( |
|
ecs_hashmap_t *map, |
|
uint64_t e, |
|
uint64_t hash, |
|
const char *name) |
|
{ |
|
ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); |
|
if (!b) { |
|
return; |
|
} |
|
|
|
uint64_t *ids = ecs_vec_first(&b->values); |
|
int32_t i, count = ecs_vec_count(&b->values); |
|
for (i = 0; i < count; i ++) { |
|
if (ids[i] == e) { |
|
ecs_hashed_string_t *key = ecs_vec_get_t( |
|
&b->keys, ecs_hashed_string_t, i); |
|
key->value = ECS_CONST_CAST(char*, name); |
|
ecs_assert(ecs_os_strlen(name) == key->length, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(flecs_hash(name, key->length) == key->hash, |
|
ECS_INTERNAL_ERROR, NULL); |
|
return; |
|
} |
|
} |
|
|
|
/* Record must already have been in the index */ |
|
ecs_abort(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
void flecs_name_index_ensure( |
|
ecs_hashmap_t *map, |
|
uint64_t id, |
|
const char *name, |
|
ecs_size_t length, |
|
uint64_t hash) |
|
{ |
|
ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); |
|
|
|
uint64_t existing = flecs_name_index_find( |
|
map, name, key.length, key.hash); |
|
if (existing) { |
|
if (existing != id) { |
|
ecs_abort(ECS_ALREADY_DEFINED, |
|
"conflicting id registered with name '%s'", name); |
|
} |
|
} |
|
|
|
flecs_hashmap_result_t hmr = flecs_hashmap_ensure( |
|
map, &key, uint64_t); |
|
*((uint64_t*)hmr.value) = id; |
|
error: |
|
return; |
|
} |
|
|
|
/** |
|
* @file datastructures/sparse.c |
|
* @brief Sparse set data structure. |
|
*/ |
|
|
|
|
|
/** Compute the page index from an id by stripping the first 12 bits */ |
|
#define PAGE(index) ((int32_t)((uint32_t)index >> FLECS_SPARSE_PAGE_BITS)) |
|
|
|
/** This computes the offset of an index inside a page */ |
|
#define OFFSET(index) ((int32_t)index & (FLECS_SPARSE_PAGE_SIZE - 1)) |
|
|
|
/* Utility to get a pointer to the payload */ |
|
#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) |
|
|
|
typedef struct ecs_page_t { |
|
int32_t *sparse; /* Sparse array with indices to dense array */ |
|
void *data; /* Store data in sparse array to reduce |
|
* indirection and provide stable pointers. */ |
|
} ecs_page_t; |
|
|
|
static |
|
ecs_page_t* flecs_sparse_page_new( |
|
ecs_sparse_t *sparse, |
|
int32_t page_index) |
|
{ |
|
ecs_allocator_t *a = sparse->allocator; |
|
ecs_block_allocator_t *ca = sparse->page_allocator; |
|
int32_t count = ecs_vec_count(&sparse->pages); |
|
ecs_page_t *pages; |
|
|
|
if (count <= page_index) { |
|
ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1); |
|
pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); |
|
ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count)); |
|
} else { |
|
pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); |
|
} |
|
|
|
ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_page_t *result = &pages[page_index]; |
|
ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Initialize sparse array with zero's, as zero is used to indicate that the |
|
* sparse element has not been paired with a dense element. Use zero |
|
* as this means we can take advantage of calloc having a possibly better |
|
* performance than malloc + memset. */ |
|
result->sparse = ca ? flecs_bcalloc(ca) |
|
: ecs_os_calloc_n(int32_t, FLECS_SPARSE_PAGE_SIZE); |
|
|
|
/* Initialize the data array with zero's to guarantee that data is |
|
* always initialized. When an entry is removed, data is reset back to |
|
* zero. Initialize now, as this can take advantage of calloc. */ |
|
result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE) |
|
: ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE); |
|
|
|
ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return result; |
|
} |
|
|
|
static |
|
void flecs_sparse_page_free( |
|
ecs_sparse_t *sparse, |
|
ecs_page_t *page) |
|
{ |
|
ecs_allocator_t *a = sparse->allocator; |
|
ecs_block_allocator_t *ca = sparse->page_allocator; |
|
|
|
if (ca) { |
|
flecs_bfree(ca, page->sparse); |
|
} else { |
|
ecs_os_free(page->sparse); |
|
} |
|
if (a) { |
|
flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data); |
|
} else { |
|
ecs_os_free(page->data); |
|
} |
|
} |
|
|
|
static |
|
ecs_page_t* flecs_sparse_get_page( |
|
const ecs_sparse_t *sparse, |
|
int32_t page_index) |
|
{ |
|
ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL); |
|
if (page_index >= ecs_vec_count(&sparse->pages)) { |
|
return NULL; |
|
} |
|
return ecs_vec_get_t(&sparse->pages, ecs_page_t, page_index); |
|
} |
|
|
|
static |
|
ecs_page_t* flecs_sparse_get_or_create_page( |
|
ecs_sparse_t *sparse, |
|
int32_t page_index) |
|
{ |
|
ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); |
|
if (page && page->sparse) { |
|
return page; |
|
} |
|
|
|
return flecs_sparse_page_new(sparse, page_index); |
|
} |
|
|
|
static |
|
void flecs_sparse_grow_dense( |
|
ecs_sparse_t *sparse) |
|
{ |
|
ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); |
|
} |
|
|
|
static |
|
uint64_t flecs_sparse_strip_generation( |
|
uint64_t *index_out) |
|
{ |
|
uint64_t index = *index_out; |
|
uint64_t gen = index & ECS_GENERATION_MASK; |
|
/* Make sure there's no junk in the id */ |
|
ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), |
|
ECS_INVALID_PARAMETER, NULL); |
|
*index_out -= gen; |
|
return gen; |
|
} |
|
|
|
static |
|
void flecs_sparse_assign_index( |
|
ecs_page_t * page, |
|
uint64_t * dense_array, |
|
uint64_t index, |
|
int32_t dense) |
|
{ |
|
/* Initialize sparse-dense pair. This assigns the dense index to the sparse |
|
* array, and the sparse index to the dense array .*/ |
|
page->sparse[OFFSET(index)] = dense; |
|
dense_array[dense] = index; |
|
} |
|
|
|
static |
|
uint64_t flecs_sparse_inc_gen( |
|
uint64_t index) |
|
{ |
|
/* When an index is deleted, its generation is increased so that we can do |
|
* liveliness checking while recycling ids */ |
|
return ECS_GENERATION_INC(index); |
|
} |
|
|
|
static |
|
uint64_t flecs_sparse_inc_id( |
|
ecs_sparse_t *sparse) |
|
{ |
|
/* Generate a new id. The last issued id could be stored in an external |
|
* variable, such as is the case with the last issued entity id, which is |
|
* stored on the world. */ |
|
return ++ sparse->max_id; |
|
} |
|
|
|
static |
|
uint64_t flecs_sparse_get_id( |
|
const ecs_sparse_t *sparse) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return sparse->max_id; |
|
} |
|
|
|
static |
|
void flecs_sparse_set_id( |
|
ecs_sparse_t *sparse, |
|
uint64_t value) |
|
{ |
|
/* Sometimes the max id needs to be assigned directly, which typically |
|
* happens when the API calls get_or_create for an id that hasn't been |
|
* issued before. */ |
|
sparse->max_id = value; |
|
} |
|
|
|
/* Pair dense id with new sparse id */ |
|
static |
|
uint64_t flecs_sparse_create_id( |
|
ecs_sparse_t *sparse, |
|
int32_t dense) |
|
{ |
|
uint64_t index = flecs_sparse_inc_id(sparse); |
|
flecs_sparse_grow_dense(sparse); |
|
|
|
ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); |
|
ecs_assert(page->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
flecs_sparse_assign_index(page, dense_array, index, dense); |
|
|
|
return index; |
|
} |
|
|
|
/* Create new id */ |
|
static |
|
uint64_t flecs_sparse_new_index( |
|
ecs_sparse_t *sparse) |
|
{ |
|
int32_t dense_count = ecs_vec_count(&sparse->dense); |
|
int32_t count = sparse->count ++; |
|
|
|
ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); |
|
if (count < dense_count) { |
|
/* If there are unused elements in the dense array, return first */ |
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
return dense_array[count]; |
|
} else { |
|
return flecs_sparse_create_id(sparse, count); |
|
} |
|
} |
|
|
|
/* Get value from sparse set when it is guaranteed that the value exists. This |
|
* function is used when values are obtained using a dense index */ |
|
static |
|
void* flecs_sparse_get_sparse( |
|
const ecs_sparse_t *sparse, |
|
int32_t dense, |
|
uint64_t index) |
|
{ |
|
flecs_sparse_strip_generation(&index); |
|
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); |
|
if (!page || !page->sparse) { |
|
return NULL; |
|
} |
|
|
|
int32_t offset = OFFSET(index); |
|
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); |
|
(void)dense; |
|
|
|
return DATA(page->data, sparse->size, offset); |
|
} |
|
|
|
/* Swap dense elements. A swap occurs when an element is removed, or when a |
|
* removed element is recycled. */ |
|
static |
|
void flecs_sparse_swap_dense( |
|
ecs_sparse_t * sparse, |
|
ecs_page_t * page_a, |
|
int32_t a, |
|
int32_t b) |
|
{ |
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
uint64_t index_a = dense_array[a]; |
|
uint64_t index_b = dense_array[b]; |
|
|
|
ecs_page_t *page_b = flecs_sparse_get_or_create_page(sparse, PAGE(index_b)); |
|
flecs_sparse_assign_index(page_a, dense_array, index_a, b); |
|
flecs_sparse_assign_index(page_b, dense_array, index_b, a); |
|
} |
|
|
|
void flecs_sparse_init( |
|
ecs_sparse_t *result, |
|
struct ecs_allocator_t *allocator, |
|
ecs_block_allocator_t *page_allocator, |
|
ecs_size_t size) |
|
{ |
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
result->size = size; |
|
result->max_id = UINT64_MAX; |
|
result->allocator = allocator; |
|
result->page_allocator = page_allocator; |
|
|
|
ecs_vec_init_t(allocator, &result->pages, ecs_page_t, 0); |
|
ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); |
|
result->dense.count = 1; |
|
|
|
/* Consume first value in dense array as 0 is used in the sparse array to |
|
* indicate that a sparse element hasn't been paired yet. */ |
|
ecs_vec_first_t(&result->dense, uint64_t)[0] = 0; |
|
|
|
result->count = 1; |
|
} |
|
|
|
void flecs_sparse_clear( |
|
ecs_sparse_t *sparse) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
int32_t i, count = ecs_vec_count(&sparse->pages); |
|
ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); |
|
for (i = 0; i < count; i ++) { |
|
int32_t *indices = pages[i].sparse; |
|
if (indices) { |
|
ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE); |
|
} |
|
} |
|
|
|
ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); |
|
|
|
sparse->count = 1; |
|
sparse->max_id = 0; |
|
} |
|
|
|
void flecs_sparse_fini( |
|
ecs_sparse_t *sparse) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t i, count = ecs_vec_count(&sparse->pages); |
|
ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); |
|
for (i = 0; i < count; i ++) { |
|
flecs_sparse_page_free(sparse, &pages[i]); |
|
} |
|
|
|
ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); |
|
ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); |
|
} |
|
|
|
uint64_t flecs_sparse_new_id( |
|
ecs_sparse_t *sparse) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
return flecs_sparse_new_index(sparse); |
|
} |
|
|
|
void* flecs_sparse_add( |
|
ecs_sparse_t *sparse, |
|
ecs_size_t size) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); |
|
uint64_t index = flecs_sparse_new_index(sparse); |
|
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); |
|
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return DATA(page->data, size, OFFSET(index)); |
|
} |
|
|
|
uint64_t flecs_sparse_last_id( |
|
const ecs_sparse_t *sparse) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); |
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
return dense_array[sparse->count - 1]; |
|
} |
|
|
|
void* flecs_sparse_ensure( |
|
ecs_sparse_t *sparse, |
|
ecs_size_t size, |
|
uint64_t index) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); |
|
(void)size; |
|
|
|
uint64_t gen = flecs_sparse_strip_generation(&index); |
|
ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); |
|
int32_t offset = OFFSET(index); |
|
int32_t dense = page->sparse[offset]; |
|
|
|
if (dense) { |
|
/* Check if element is alive. If element is not alive, update indices so |
|
* that the first unused dense element points to the sparse element. */ |
|
int32_t count = sparse->count; |
|
if (dense >= count) { |
|
/* If dense is not alive, swap it with the first unused element. */ |
|
flecs_sparse_swap_dense(sparse, page, dense, count); |
|
dense = count; |
|
|
|
/* First unused element is now last used element */ |
|
sparse->count ++; |
|
} else { |
|
/* Dense is already alive, nothing to be done */ |
|
} |
|
|
|
/* Ensure provided generation matches current. Only allow mismatching |
|
* generations if the provided generation count is 0. This allows for |
|
* using the ensure function in combination with ids that have their |
|
* generation stripped. */ |
|
#ifdef FLECS_DEBUG |
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); |
|
#endif |
|
} else { |
|
/* Element is not paired yet. Must add a new element to dense array */ |
|
flecs_sparse_grow_dense(sparse); |
|
|
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
int32_t dense_count = ecs_vec_count(&sparse->dense) - 1; |
|
int32_t count = sparse->count ++; |
|
|
|
/* If index is larger than max id, update max id */ |
|
if (index >= flecs_sparse_get_id(sparse)) { |
|
flecs_sparse_set_id(sparse, index); |
|
} |
|
|
|
if (count < dense_count) { |
|
/* If there are unused elements in the list, move the first unused |
|
* element to the end of the list */ |
|
uint64_t unused = dense_array[count]; |
|
ecs_page_t *unused_page = flecs_sparse_get_or_create_page(sparse, PAGE(unused)); |
|
flecs_sparse_assign_index(unused_page, dense_array, unused, dense_count); |
|
} |
|
|
|
flecs_sparse_assign_index(page, dense_array, index, count); |
|
dense_array[count] |= gen; |
|
} |
|
|
|
return DATA(page->data, sparse->size, offset); |
|
} |
|
|
|
void* flecs_sparse_ensure_fast( |
|
ecs_sparse_t *sparse, |
|
ecs_size_t size, |
|
uint64_t index_long) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); |
|
(void)size; |
|
|
|
uint32_t index = (uint32_t)index_long; |
|
ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); |
|
int32_t offset = OFFSET(index); |
|
int32_t dense = page->sparse[offset]; |
|
int32_t count = sparse->count; |
|
|
|
if (!dense) { |
|
/* Element is not paired yet. Must add a new element to dense array */ |
|
sparse->count = count + 1; |
|
if (count == ecs_vec_count(&sparse->dense)) { |
|
flecs_sparse_grow_dense(sparse); |
|
} |
|
|
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
flecs_sparse_assign_index(page, dense_array, index, count); |
|
} |
|
|
|
return DATA(page->data, sparse->size, offset); |
|
} |
|
|
|
void flecs_sparse_remove( |
|
ecs_sparse_t *sparse, |
|
ecs_size_t size, |
|
uint64_t index) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); |
|
(void)size; |
|
|
|
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); |
|
if (!page || !page->sparse) { |
|
return; |
|
} |
|
|
|
uint64_t gen = flecs_sparse_strip_generation(&index); |
|
int32_t offset = OFFSET(index); |
|
int32_t dense = page->sparse[offset]; |
|
|
|
if (dense) { |
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; |
|
if (gen != cur_gen) { |
|
/* Generation doesn't match which means that the provided entity is |
|
* already not alive. */ |
|
return; |
|
} |
|
|
|
/* Increase generation */ |
|
dense_array[dense] = index | flecs_sparse_inc_gen(cur_gen); |
|
|
|
int32_t count = sparse->count; |
|
|
|
if (dense == (count - 1)) { |
|
/* If dense is the last used element, simply decrease count */ |
|
sparse->count --; |
|
} else if (dense < count) { |
|
/* If element is alive, move it to unused elements */ |
|
flecs_sparse_swap_dense(sparse, page, dense, count - 1); |
|
sparse->count --; |
|
} else { |
|
/* Element is not alive, nothing to be done */ |
|
return; |
|
} |
|
|
|
/* Reset memory to zero on remove */ |
|
void *ptr = DATA(page->data, sparse->size, offset); |
|
ecs_os_memset(ptr, 0, size); |
|
} else { |
|
/* Element is not paired and thus not alive, nothing to be done */ |
|
return; |
|
} |
|
} |
|
|
|
void flecs_sparse_set_generation( |
|
ecs_sparse_t *sparse, |
|
uint64_t index) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); |
|
|
|
uint64_t index_w_gen = index; |
|
flecs_sparse_strip_generation(&index); |
|
int32_t offset = OFFSET(index); |
|
int32_t dense = page->sparse[offset]; |
|
|
|
if (dense) { |
|
/* Increase generation */ |
|
ecs_vec_get_t(&sparse->dense, uint64_t, dense)[0] = index_w_gen; |
|
} else { |
|
/* Element is not paired and thus not alive, nothing to be done */ |
|
} |
|
} |
|
|
|
void* flecs_sparse_get_dense( |
|
const ecs_sparse_t *sparse, |
|
ecs_size_t size, |
|
int32_t dense_index) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); |
|
(void)size; |
|
|
|
dense_index ++; |
|
|
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
return flecs_sparse_get_sparse(sparse, dense_index, dense_array[dense_index]); |
|
} |
|
|
|
bool flecs_sparse_is_alive( |
|
const ecs_sparse_t *sparse, |
|
uint64_t index) |
|
{ |
|
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); |
|
if (!page || !page->sparse) { |
|
return false; |
|
} |
|
|
|
int32_t offset = OFFSET(index); |
|
int32_t dense = page->sparse[offset]; |
|
if (!dense || (dense >= sparse->count)) { |
|
return false; |
|
} |
|
|
|
uint64_t gen = flecs_sparse_strip_generation(&index); |
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; |
|
|
|
if (cur_gen != gen) { |
|
return false; |
|
} |
|
|
|
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); |
|
return true; |
|
} |
|
|
|
void* flecs_sparse_try( |
|
const ecs_sparse_t *sparse, |
|
ecs_size_t size, |
|
uint64_t index) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); |
|
(void)size; |
|
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); |
|
if (!page || !page->sparse) { |
|
return NULL; |
|
} |
|
|
|
int32_t offset = OFFSET(index); |
|
int32_t dense = page->sparse[offset]; |
|
if (!dense || (dense >= sparse->count)) { |
|
return NULL; |
|
} |
|
|
|
uint64_t gen = flecs_sparse_strip_generation(&index); |
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; |
|
if (cur_gen != gen) { |
|
return NULL; |
|
} |
|
|
|
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); |
|
return DATA(page->data, sparse->size, offset); |
|
} |
|
|
|
void* flecs_sparse_get( |
|
const ecs_sparse_t *sparse, |
|
ecs_size_t size, |
|
uint64_t index) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); |
|
(void)size; |
|
|
|
ecs_page_t *page = ecs_vec_get_t(&sparse->pages, ecs_page_t, PAGE(index)); |
|
int32_t offset = OFFSET(index); |
|
int32_t dense = page->sparse[offset]; |
|
ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
uint64_t gen = flecs_sparse_strip_generation(&index); |
|
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); |
|
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; |
|
(void)cur_gen; (void)gen; |
|
|
|
ecs_assert(cur_gen == gen, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(dense < sparse->count, ECS_INTERNAL_ERROR, NULL); |
|
return DATA(page->data, sparse->size, offset); |
|
} |
|
|
|
void* flecs_sparse_get_any( |
|
const ecs_sparse_t *sparse, |
|
ecs_size_t size, |
|
uint64_t index) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); |
|
(void)size; |
|
|
|
flecs_sparse_strip_generation(&index); |
|
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); |
|
if (!page || !page->sparse) { |
|
return NULL; |
|
} |
|
|
|
int32_t offset = OFFSET(index); |
|
int32_t dense = page->sparse[offset]; |
|
bool in_use = dense && (dense < sparse->count); |
|
if (!in_use) { |
|
return NULL; |
|
} |
|
|
|
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); |
|
return DATA(page->data, sparse->size, offset); |
|
} |
|
|
|
int32_t flecs_sparse_count( |
|
const ecs_sparse_t *sparse) |
|
{ |
|
if (!sparse || !sparse->count) { |
|
return 0; |
|
} |
|
|
|
return sparse->count - 1; |
|
} |
|
|
|
const uint64_t* flecs_sparse_ids( |
|
const ecs_sparse_t *sparse) |
|
{ |
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); |
|
if (sparse->dense.array) { |
|
return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
void ecs_sparse_init( |
|
ecs_sparse_t *sparse, |
|
ecs_size_t elem_size) |
|
{ |
|
flecs_sparse_init(sparse, NULL, NULL, elem_size); |
|
} |
|
|
|
void* ecs_sparse_add( |
|
ecs_sparse_t *sparse, |
|
ecs_size_t elem_size) |
|
{ |
|
return flecs_sparse_add(sparse, elem_size); |
|
} |
|
|
|
uint64_t ecs_sparse_last_id( |
|
const ecs_sparse_t *sparse) |
|
{ |
|
return flecs_sparse_last_id(sparse); |
|
} |
|
|
|
int32_t ecs_sparse_count( |
|
const ecs_sparse_t *sparse) |
|
{ |
|
return flecs_sparse_count(sparse); |
|
} |
|
|
|
void* ecs_sparse_get_dense( |
|
const ecs_sparse_t *sparse, |
|
ecs_size_t elem_size, |
|
int32_t index) |
|
{ |
|
return flecs_sparse_get_dense(sparse, elem_size, index); |
|
} |
|
|
|
void* ecs_sparse_get( |
|
const ecs_sparse_t *sparse, |
|
ecs_size_t elem_size, |
|
uint64_t id) |
|
{ |
|
return flecs_sparse_get(sparse, elem_size, id); |
|
} |
|
|
|
/** |
|
* @file datastructures/stack_allocator.c |
|
* @brief Stack allocator. |
|
* |
|
* The stack allocator enables pushing and popping values to a stack, and has |
|
* a lower overhead when compared to block allocators. A stack allocator is a |
|
* good fit for small temporary allocations. |
|
* |
|
* The stack allocator allocates memory in pages. If the requested size of an |
|
* allocation exceeds the page size, a regular allocator is used instead. |
|
*/ |
|
|
|
|
|
#define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) |
|
|
|
int64_t ecs_stack_allocator_alloc_count = 0; |
|
int64_t ecs_stack_allocator_free_count = 0; |
|
|
|
static |
|
ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { |
|
ecs_stack_page_t *result = ecs_os_malloc( |
|
FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE); |
|
result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); |
|
result->next = NULL; |
|
result->id = page_id + 1; |
|
ecs_os_linc(&ecs_stack_allocator_alloc_count); |
|
return result; |
|
} |
|
|
|
void* flecs_stack_alloc( |
|
ecs_stack_t *stack, |
|
ecs_size_t size, |
|
ecs_size_t align) |
|
{ |
|
ecs_stack_page_t *page = stack->tail_page; |
|
if (page == &stack->first && !page->data) { |
|
page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); |
|
ecs_os_linc(&ecs_stack_allocator_alloc_count); |
|
} |
|
|
|
int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); |
|
int16_t next_sp = flecs_ito(int16_t, sp + size); |
|
void *result = NULL; |
|
|
|
if (next_sp > ECS_STACK_PAGE_SIZE) { |
|
if (size > ECS_STACK_PAGE_SIZE) { |
|
result = ecs_os_malloc(size); /* Too large for page */ |
|
goto done; |
|
} |
|
|
|
if (page->next) { |
|
page = page->next; |
|
} else { |
|
page = page->next = flecs_stack_page_new(page->id); |
|
} |
|
sp = 0; |
|
next_sp = flecs_ito(int16_t, size); |
|
stack->tail_page = page; |
|
} |
|
|
|
page->sp = next_sp; |
|
result = ECS_OFFSET(page->data, sp); |
|
|
|
done: |
|
#ifdef FLECS_SANITIZE |
|
ecs_os_memset(result, 0xAA, size); |
|
#endif |
|
return result; |
|
} |
|
|
|
void* flecs_stack_calloc( |
|
ecs_stack_t *stack, |
|
ecs_size_t size, |
|
ecs_size_t align) |
|
{ |
|
void *ptr = flecs_stack_alloc(stack, size, align); |
|
ecs_os_memset(ptr, 0, size); |
|
return ptr; |
|
} |
|
|
|
void flecs_stack_free( |
|
void *ptr, |
|
ecs_size_t size) |
|
{ |
|
if (size > ECS_STACK_PAGE_SIZE) { |
|
ecs_os_free(ptr); |
|
} |
|
} |
|
|
|
ecs_stack_cursor_t* flecs_stack_get_cursor( |
|
ecs_stack_t *stack) |
|
{ |
|
ecs_stack_page_t *page = stack->tail_page; |
|
int16_t sp = stack->tail_page->sp; |
|
ecs_stack_cursor_t *result = flecs_stack_alloc_t(stack, ecs_stack_cursor_t); |
|
result->page = page; |
|
result->sp = sp; |
|
result->is_free = false; |
|
|
|
#ifdef FLECS_DEBUG |
|
++ stack->cursor_count; |
|
result->owner = stack; |
|
#endif |
|
|
|
result->prev = stack->tail_cursor; |
|
stack->tail_cursor = result; |
|
return result; |
|
} |
|
|
|
void flecs_stack_restore_cursor( |
|
ecs_stack_t *stack, |
|
ecs_stack_cursor_t *cursor) |
|
{ |
|
if (!cursor) { |
|
return; |
|
} |
|
|
|
ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, NULL); |
|
ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, NULL); |
|
ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, NULL); |
|
|
|
cursor->is_free = true; |
|
|
|
#ifdef FLECS_DEBUG |
|
-- stack->cursor_count; |
|
#endif |
|
|
|
/* If cursor is not the last on the stack no memory should be freed */ |
|
if (cursor != stack->tail_cursor) { |
|
return; |
|
} |
|
|
|
/* Iterate freed cursors to know how much memory we can free */ |
|
do { |
|
ecs_stack_cursor_t* prev = cursor->prev; |
|
if (!prev || !prev->is_free) { |
|
break; /* Found active cursor, free up until this point */ |
|
} |
|
cursor = prev; |
|
} while (cursor); |
|
|
|
stack->tail_cursor = cursor->prev; |
|
stack->tail_page = cursor->page; |
|
stack->tail_page->sp = cursor->sp; |
|
|
|
/* If the cursor count is zero, stack should be empty |
|
* if the cursor count is non-zero, stack should not be empty */ |
|
ecs_dbg_assert((stack->cursor_count == 0) == |
|
(stack->tail_page == &stack->first && stack->tail_page->sp == 0), |
|
ECS_LEAK_DETECTED, NULL); |
|
} |
|
|
|
void flecs_stack_reset( |
|
ecs_stack_t *stack) |
|
{ |
|
ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL); |
|
stack->tail_page = &stack->first; |
|
stack->first.sp = 0; |
|
stack->tail_cursor = NULL; |
|
} |
|
|
|
void flecs_stack_init( |
|
ecs_stack_t *stack) |
|
{ |
|
ecs_os_zeromem(stack); |
|
stack->tail_page = &stack->first; |
|
stack->first.data = NULL; |
|
} |
|
|
|
void flecs_stack_fini( |
|
ecs_stack_t *stack) |
|
{ |
|
ecs_stack_page_t *next, *cur = &stack->first; |
|
ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL); |
|
ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, NULL); |
|
ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, NULL); |
|
|
|
do { |
|
next = cur->next; |
|
if (cur == &stack->first) { |
|
if (cur->data) { |
|
ecs_os_linc(&ecs_stack_allocator_free_count); |
|
} |
|
ecs_os_free(cur->data); |
|
} else { |
|
ecs_os_linc(&ecs_stack_allocator_free_count); |
|
ecs_os_free(cur); |
|
} |
|
} while ((cur = next)); |
|
} |
|
|
|
/** |
|
* @file datastructures/strbuf.c |
|
* @brief Utility for constructing strings. |
|
* |
|
* A buffer builds up a list of elements which individually can be up to N bytes |
|
* large. While appending, data is added to these elements. More elements are |
|
* added on the fly when needed. When an application calls ecs_strbuf_get, all |
|
* elements are combined in one string and the element administration is freed. |
|
* |
|
* This approach prevents reallocs of large blocks of memory, and therefore |
|
* copying large blocks of memory when appending to a large buffer. A buffer |
|
* preallocates some memory for the element overhead so that for small strings |
|
* there is hardly any overhead, while for large strings the overhead is offset |
|
* by the reduced time spent on copying memory. |
|
* |
|
* The functionality provided by strbuf is similar to std::stringstream. |
|
*/ |
|
|
|
#include <math.h> |
|
|
|
/** |
|
* stm32tpl -- STM32 C++ Template Peripheral Library |
|
* Visit https://github.com/antongus/stm32tpl for new versions |
|
* |
|
* Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA |
|
*/ |
|
|
|
#define MAX_PRECISION (10) |
|
#define EXP_THRESHOLD (3) |
|
#define INT64_MAX_F ((double)INT64_MAX) |
|
|
|
static const double rounders[MAX_PRECISION + 1] = |
|
{ |
|
0.5, // 0 |
|
0.05, // 1 |
|
0.005, // 2 |
|
0.0005, // 3 |
|
0.00005, // 4 |
|
0.000005, // 5 |
|
0.0000005, // 6 |
|
0.00000005, // 7 |
|
0.000000005, // 8 |
|
0.0000000005, // 9 |
|
0.00000000005 // 10 |
|
}; |
|
|
|
static |
|
char* flecs_strbuf_itoa( |
|
char *buf, |
|
int64_t v) |
|
{ |
|
char *ptr = buf; |
|
char * p1; |
|
char c; |
|
|
|
if (!v) { |
|
*ptr++ = '0'; |
|
} else { |
|
if (v < 0) { |
|
ptr[0] = '-'; |
|
ptr ++; |
|
v *= -1; |
|
} |
|
|
|
char *p = ptr; |
|
while (v) { |
|
int64_t vdiv = v / 10; |
|
int64_t vmod = v - (vdiv * 10); |
|
p[0] = (char)('0' + vmod); |
|
p ++; |
|
v = vdiv; |
|
} |
|
|
|
p1 = p; |
|
|
|
while (p > ptr) { |
|
c = *--p; |
|
*p = *ptr; |
|
*ptr++ = c; |
|
} |
|
ptr = p1; |
|
} |
|
return ptr; |
|
} |
|
|
|
static |
|
void flecs_strbuf_ftoa( |
|
ecs_strbuf_t *out, |
|
double f, |
|
int precision, |
|
char nan_delim) |
|
{ |
|
char buf[64]; |
|
char * ptr = buf; |
|
char c; |
|
int64_t intPart; |
|
int64_t exp = 0; |
|
|
|
if (ecs_os_isnan(f)) { |
|
if (nan_delim) { |
|
ecs_strbuf_appendch(out, nan_delim); |
|
ecs_strbuf_appendlit(out, "NaN"); |
|
ecs_strbuf_appendch(out, nan_delim); |
|
return; |
|
} else { |
|
ecs_strbuf_appendlit(out, "NaN"); |
|
return; |
|
} |
|
} |
|
if (ecs_os_isinf(f)) { |
|
if (nan_delim) { |
|
ecs_strbuf_appendch(out, nan_delim); |
|
ecs_strbuf_appendlit(out, "Inf"); |
|
ecs_strbuf_appendch(out, nan_delim); |
|
return; |
|
} else { |
|
ecs_strbuf_appendlit(out, "Inf"); |
|
return; |
|
} |
|
} |
|
|
|
if (precision > MAX_PRECISION) { |
|
precision = MAX_PRECISION; |
|
} |
|
|
|
if (f < 0) { |
|
f = -f; |
|
*ptr++ = '-'; |
|
} |
|
|
|
if (precision < 0) { |
|
if (f < 1.0) precision = 6; |
|
else if (f < 10.0) precision = 5; |
|
else if (f < 100.0) precision = 4; |
|
else if (f < 1000.0) precision = 3; |
|
else if (f < 10000.0) precision = 2; |
|
else if (f < 100000.0) precision = 1; |
|
else precision = 0; |
|
} |
|
|
|
if (precision) { |
|
f += rounders[precision]; |
|
} |
|
|
|
/* Make sure that number can be represented as 64bit int, increase exp */ |
|
while (f > INT64_MAX_F) { |
|
f /= 1000 * 1000 * 1000; |
|
exp += 9; |
|
} |
|
|
|
intPart = (int64_t)f; |
|
f -= (double)intPart; |
|
|
|
ptr = flecs_strbuf_itoa(ptr, intPart); |
|
|
|
if (precision) { |
|
*ptr++ = '.'; |
|
while (precision--) { |
|
f *= 10.0; |
|
c = (char)f; |
|
*ptr++ = (char)('0' + c); |
|
f -= c; |
|
} |
|
} |
|
*ptr = 0; |
|
|
|
/* Remove trailing 0s */ |
|
while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { |
|
ptr[-1] = '\0'; |
|
ptr --; |
|
} |
|
if (ptr != buf && ptr[-1] == '.') { |
|
ptr[-1] = '\0'; |
|
ptr --; |
|
} |
|
|
|
/* If 0s before . exceed threshold, convert to exponent to save space |
|
* without losing precision. */ |
|
char *cur = ptr; |
|
while ((&cur[-1] != buf) && (cur[-1] == '0')) { |
|
cur --; |
|
} |
|
|
|
if (exp || ((ptr - cur) > EXP_THRESHOLD)) { |
|
cur[0] = '\0'; |
|
exp += (ptr - cur); |
|
ptr = cur; |
|
} |
|
|
|
if (exp) { |
|
char *p1 = &buf[1]; |
|
if (nan_delim) { |
|
ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); |
|
buf[0] = nan_delim; |
|
p1 ++; |
|
} |
|
|
|
/* Make sure that exp starts after first character */ |
|
c = p1[0]; |
|
|
|
if (c) { |
|
p1[0] = '.'; |
|
do { |
|
char t = (++p1)[0]; |
|
if (t == '.') { |
|
exp ++; |
|
p1 --; |
|
break; |
|
} |
|
p1[0] = c; |
|
c = t; |
|
exp ++; |
|
} while (c); |
|
ptr = p1 + 1; |
|
} else { |
|
ptr = p1; |
|
} |
|
|
|
ptr[0] = 'e'; |
|
ptr = flecs_strbuf_itoa(ptr + 1, exp); |
|
|
|
if (nan_delim) { |
|
ptr[0] = nan_delim; |
|
ptr ++; |
|
} |
|
|
|
ptr[0] = '\0'; |
|
} |
|
|
|
ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); |
|
} |
|
|
|
/* Add an extra element to the buffer */ |
|
static |
|
void flecs_strbuf_grow( |
|
ecs_strbuf_t *b) |
|
{ |
|
if (!b->content) { |
|
b->content = b->small_string; |
|
b->size = ECS_STRBUF_SMALL_STRING_SIZE; |
|
} else if (b->content == b->small_string) { |
|
b->size *= 2; |
|
b->content = ecs_os_malloc_n(char, b->size); |
|
ecs_os_memcpy(b->content, b->small_string, b->length); |
|
} else { |
|
b->size *= 2; |
|
if (b->size < 16) b->size = 16; |
|
b->content = ecs_os_realloc_n(b->content, char, b->size); |
|
} |
|
} |
|
|
|
static |
|
char* flecs_strbuf_ptr( |
|
ecs_strbuf_t *b) |
|
{ |
|
ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return &b->content[b->length]; |
|
} |
|
|
|
/* Append a format string to a buffer */ |
|
static |
|
void flecs_strbuf_vappend( |
|
ecs_strbuf_t *b, |
|
const char* str, |
|
va_list args) |
|
{ |
|
va_list arg_cpy; |
|
|
|
if (!str) { |
|
return; |
|
} |
|
|
|
/* Compute the memory required to add the string to the buffer. If user |
|
* provided buffer, use space left in buffer, otherwise use space left in |
|
* current element. */ |
|
int32_t mem_left = b->size - b->length; |
|
int32_t mem_required; |
|
|
|
va_copy(arg_cpy, args); |
|
|
|
if (b->content) { |
|
mem_required = vsnprintf( |
|
flecs_strbuf_ptr(b), |
|
flecs_itosize(mem_left), str, args); |
|
} else { |
|
mem_required = vsnprintf(NULL, 0, str, args); |
|
mem_left = 0; |
|
} |
|
|
|
ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if ((mem_required + 1) >= mem_left) { |
|
while ((mem_required + 1) >= mem_left) { |
|
flecs_strbuf_grow(b); |
|
mem_left = b->size - b->length; |
|
} |
|
vsnprintf(flecs_strbuf_ptr(b), |
|
flecs_itosize(mem_required + 1), str, arg_cpy); |
|
} |
|
|
|
b->length += mem_required; |
|
|
|
va_end(arg_cpy); |
|
} |
|
|
|
static |
|
void flecs_strbuf_appendstr( |
|
ecs_strbuf_t *b, |
|
const char* str, |
|
int n) |
|
{ |
|
int32_t mem_left = b->size - b->length; |
|
while (n >= mem_left) { |
|
flecs_strbuf_grow(b); |
|
mem_left = b->size - b->length; |
|
} |
|
|
|
ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); |
|
b->length += n; |
|
} |
|
|
|
static |
|
void flecs_strbuf_appendch( |
|
ecs_strbuf_t *b, |
|
char ch) |
|
{ |
|
if (b->size == b->length) { |
|
flecs_strbuf_grow(b); |
|
} |
|
|
|
flecs_strbuf_ptr(b)[0] = ch; |
|
b->length ++; |
|
} |
|
|
|
void ecs_strbuf_vappend( |
|
ecs_strbuf_t *b, |
|
const char* fmt, |
|
va_list args) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_strbuf_vappend(b, fmt, args); |
|
} |
|
|
|
void ecs_strbuf_append( |
|
ecs_strbuf_t *b, |
|
const char* fmt, |
|
...) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
va_list args; |
|
va_start(args, fmt); |
|
flecs_strbuf_vappend(b, fmt, args); |
|
va_end(args); |
|
} |
|
|
|
void ecs_strbuf_appendstrn( |
|
ecs_strbuf_t *b, |
|
const char* str, |
|
int32_t len) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_strbuf_appendstr(b, str, len); |
|
} |
|
|
|
void ecs_strbuf_appendch( |
|
ecs_strbuf_t *b, |
|
char ch) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_strbuf_appendch(b, ch); |
|
} |
|
|
|
void ecs_strbuf_appendint( |
|
ecs_strbuf_t *b, |
|
int64_t v) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
char numbuf[32]; |
|
char *ptr = flecs_strbuf_itoa(numbuf, v); |
|
ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); |
|
} |
|
|
|
void ecs_strbuf_appendflt( |
|
ecs_strbuf_t *b, |
|
double flt, |
|
char nan_delim) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_strbuf_ftoa(b, flt, 10, nan_delim); |
|
} |
|
|
|
void ecs_strbuf_appendbool( |
|
ecs_strbuf_t *buffer, |
|
bool v) |
|
{ |
|
ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); |
|
if (v) { |
|
ecs_strbuf_appendlit(buffer, "true"); |
|
} else { |
|
ecs_strbuf_appendlit(buffer, "false"); |
|
} |
|
} |
|
|
|
void ecs_strbuf_appendstr( |
|
ecs_strbuf_t *b, |
|
const char* str) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); |
|
} |
|
|
|
void ecs_strbuf_mergebuff( |
|
ecs_strbuf_t *b, |
|
ecs_strbuf_t *src) |
|
{ |
|
if (src->content) { |
|
ecs_strbuf_appendstr(b, src->content); |
|
} |
|
ecs_strbuf_reset(src); |
|
} |
|
|
|
char* ecs_strbuf_get( |
|
ecs_strbuf_t *b) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
char *result = b->content; |
|
if (!result) { |
|
return NULL; |
|
} |
|
|
|
ecs_strbuf_appendch(b, '\0'); |
|
result = b->content; |
|
|
|
if (result == b->small_string) { |
|
result = ecs_os_memdup_n(result, char, b->length + 1); |
|
} |
|
|
|
b->length = 0; |
|
b->content = NULL; |
|
b->size = 0; |
|
b->list_sp = 0; |
|
return result; |
|
} |
|
|
|
char* ecs_strbuf_get_small( |
|
ecs_strbuf_t *b) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
char *result = b->content; |
|
result[b->length] = '\0'; |
|
b->length = 0; |
|
b->content = NULL; |
|
b->size = 0; |
|
return result; |
|
} |
|
|
|
void ecs_strbuf_reset( |
|
ecs_strbuf_t *b) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
if (b->content && b->content != b->small_string) { |
|
ecs_os_free(b->content); |
|
} |
|
*b = ECS_STRBUF_INIT; |
|
} |
|
|
|
void ecs_strbuf_list_push( |
|
ecs_strbuf_t *b, |
|
const char *list_open, |
|
const char *separator) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, NULL); |
|
b->list_sp ++; |
|
ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH, |
|
ECS_INVALID_OPERATION, NULL); |
|
|
|
b->list_stack[b->list_sp].count = 0; |
|
b->list_stack[b->list_sp].separator = separator; |
|
|
|
if (list_open) { |
|
char ch = list_open[0]; |
|
if (ch && !list_open[1]) { |
|
ecs_strbuf_appendch(b, ch); |
|
} else { |
|
ecs_strbuf_appendstr(b, list_open); |
|
} |
|
} |
|
} |
|
|
|
void ecs_strbuf_list_pop( |
|
ecs_strbuf_t *b, |
|
const char *list_close) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(b->list_sp > 0, ECS_INVALID_OPERATION, NULL); |
|
|
|
b->list_sp --; |
|
|
|
if (list_close) { |
|
char ch = list_close[0]; |
|
if (ch && !list_close[1]) { |
|
ecs_strbuf_appendch(b, list_close[0]); |
|
} else { |
|
ecs_strbuf_appendstr(b, list_close); |
|
} |
|
} |
|
} |
|
|
|
void ecs_strbuf_list_next( |
|
ecs_strbuf_t *b) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
int32_t list_sp = b->list_sp; |
|
if (b->list_stack[list_sp].count != 0) { |
|
const char *sep = b->list_stack[list_sp].separator; |
|
if (sep && !sep[1]) { |
|
ecs_strbuf_appendch(b, sep[0]); |
|
} else { |
|
ecs_strbuf_appendstr(b, sep); |
|
} |
|
} |
|
b->list_stack[list_sp].count ++; |
|
} |
|
|
|
void ecs_strbuf_list_appendch( |
|
ecs_strbuf_t *b, |
|
char ch) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_strbuf_list_next(b); |
|
flecs_strbuf_appendch(b, ch); |
|
} |
|
|
|
void ecs_strbuf_list_append( |
|
ecs_strbuf_t *b, |
|
const char *fmt, |
|
...) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_strbuf_list_next(b); |
|
|
|
va_list args; |
|
va_start(args, fmt); |
|
flecs_strbuf_vappend(b, fmt, args); |
|
va_end(args); |
|
} |
|
|
|
void ecs_strbuf_list_appendstr( |
|
ecs_strbuf_t *b, |
|
const char *str) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_strbuf_list_next(b); |
|
ecs_strbuf_appendstr(b, str); |
|
} |
|
|
|
void ecs_strbuf_list_appendstrn( |
|
ecs_strbuf_t *b, |
|
const char *str, |
|
int32_t n) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_strbuf_list_next(b); |
|
ecs_strbuf_appendstrn(b, str, n); |
|
} |
|
|
|
int32_t ecs_strbuf_written( |
|
const ecs_strbuf_t *b) |
|
{ |
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); |
|
return b->length; |
|
} |
|
|
|
/** |
|
* @file datastructures/switch_list.c |
|
* @brief Interleaved linked list for storing mutually exclusive values. |
|
* |
|
* Data structure that stores N interleaved linked lists in an array. |
|
* This allows for efficient storage of elements with mutually exclusive values. |
|
* Each linked list has a header element which points to the index in the array |
|
* that stores the first node of the list. Each list node points to the next |
|
* array element. |
|
* |
|
* The data structure allows for efficient storage and retrieval for values with |
|
* mutually exclusive values, such as enumeration values. The linked list allows |
|
* an application to obtain all elements for a given (enumeration) value without |
|
* having to search. |
|
* |
|
* While the list accepts 64 bit values, it only uses the lower 32bits of the |
|
* value for selecting the correct linked list. |
|
* |
|
* The switch list is used to store union relationships. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_SANITIZE |
|
static |
|
void flecs_switch_verify_nodes( |
|
ecs_switch_header_t *hdr, |
|
ecs_switch_node_t *nodes) |
|
{ |
|
if (!hdr) { |
|
return; |
|
} |
|
|
|
int32_t prev = -1, elem = hdr->element, count = 0; |
|
while (elem != -1) { |
|
ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); |
|
prev = elem; |
|
elem = nodes[elem].next; |
|
count ++; |
|
} |
|
|
|
ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
#else |
|
#define flecs_switch_verify_nodes(hdr, nodes) |
|
#endif |
|
|
|
static |
|
ecs_switch_header_t* flecs_switch_get_header( |
|
const ecs_switch_t *sw, |
|
uint64_t value) |
|
{ |
|
if (value == 0) { |
|
return NULL; |
|
} |
|
return (ecs_switch_header_t*)ecs_map_get(&sw->hdrs, value); |
|
} |
|
|
|
static |
|
ecs_switch_header_t *flecs_switch_ensure_header( |
|
ecs_switch_t *sw, |
|
uint64_t value) |
|
{ |
|
ecs_switch_header_t *node = flecs_switch_get_header(sw, value); |
|
if (!node && (value != 0)) { |
|
node = (ecs_switch_header_t*)ecs_map_ensure(&sw->hdrs, value); |
|
node->count = 0; |
|
node->element = -1; |
|
} |
|
|
|
return node; |
|
} |
|
|
|
static |
|
void flecs_switch_remove_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); |
|
|
|
/* Update previous node/header */ |
|
if (hdr->element == element) { |
|
ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); |
|
/* If this is the first node, update the header */ |
|
hdr->element = node->next; |
|
} else { |
|
/* 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); |
|
ecs_switch_node_t *prev_node = &nodes[node->prev]; |
|
prev_node->next = node->next; |
|
} |
|
|
|
/* Update next node */ |
|
int32_t next = node->next; |
|
if (next != -1) { |
|
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 */ |
|
ecs_switch_node_t *next_node = &nodes[next]; |
|
next_node->prev = node->prev; |
|
} |
|
|
|
/* Decrease count of current header */ |
|
hdr->count --; |
|
ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
void flecs_switch_init( |
|
ecs_switch_t *sw, |
|
ecs_allocator_t *allocator, |
|
int32_t elements) |
|
{ |
|
ecs_map_init(&sw->hdrs, allocator); |
|
ecs_vec_init_t(allocator, &sw->nodes, ecs_switch_node_t, elements); |
|
ecs_vec_init_t(allocator, &sw->values, uint64_t, elements); |
|
|
|
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); |
|
uint64_t *values = ecs_vec_first(&sw->values); |
|
|
|
int i; |
|
for (i = 0; i < elements; i ++) { |
|
nodes[i].prev = -1; |
|
nodes[i].next = -1; |
|
values[i] = 0; |
|
} |
|
} |
|
|
|
void flecs_switch_clear( |
|
ecs_switch_t *sw) |
|
{ |
|
ecs_map_clear(&sw->hdrs); |
|
ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); |
|
ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t); |
|
} |
|
|
|
void flecs_switch_fini( |
|
ecs_switch_t *sw) |
|
{ |
|
ecs_map_fini(&sw->hdrs); |
|
ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); |
|
ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t); |
|
} |
|
|
|
void flecs_switch_add( |
|
ecs_switch_t *sw) |
|
{ |
|
ecs_switch_node_t *node = ecs_vec_append_t(sw->hdrs.allocator, |
|
&sw->nodes, ecs_switch_node_t); |
|
uint64_t *value = ecs_vec_append_t(sw->hdrs.allocator, |
|
&sw->values, uint64_t); |
|
node->prev = -1; |
|
node->next = -1; |
|
*value = 0; |
|
} |
|
|
|
void flecs_switch_set_count( |
|
ecs_switch_t *sw, |
|
int32_t count) |
|
{ |
|
int32_t old_count = ecs_vec_count(&sw->nodes); |
|
if (old_count == count) { |
|
return; |
|
} |
|
|
|
ecs_vec_set_count_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t, count); |
|
ecs_vec_set_count_t(sw->hdrs.allocator, &sw->values, uint64_t, count); |
|
|
|
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); |
|
uint64_t *values = ecs_vec_first(&sw->values); |
|
|
|
int32_t i; |
|
for (i = old_count; i < count; i ++) { |
|
ecs_switch_node_t *node = &nodes[i]; |
|
node->prev = -1; |
|
node->next = -1; |
|
values[i] = 0; |
|
} |
|
} |
|
|
|
int32_t flecs_switch_count( |
|
ecs_switch_t *sw) |
|
{ |
|
ecs_assert(ecs_vec_count(&sw->values) == ecs_vec_count(&sw->nodes), |
|
ECS_INTERNAL_ERROR, NULL); |
|
return ecs_vec_count(&sw->values); |
|
} |
|
|
|
void flecs_switch_ensure( |
|
ecs_switch_t *sw, |
|
int32_t count) |
|
{ |
|
int32_t old_count = ecs_vec_count(&sw->nodes); |
|
if (old_count >= count) { |
|
return; |
|
} |
|
|
|
flecs_switch_set_count(sw, count); |
|
} |
|
|
|
void flecs_switch_addn( |
|
ecs_switch_t *sw, |
|
int32_t count) |
|
{ |
|
int32_t old_count = ecs_vec_count(&sw->nodes); |
|
flecs_switch_set_count(sw, old_count + count); |
|
} |
|
|
|
void flecs_switch_set( |
|
ecs_switch_t *sw, |
|
int32_t element, |
|
uint64_t value) |
|
{ |
|
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
uint64_t *values = ecs_vec_first(&sw->values); |
|
uint64_t cur_value = values[element]; |
|
|
|
/* If the node is already assigned to the value, nothing to be done */ |
|
if (cur_value == value) { |
|
return; |
|
} |
|
|
|
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); |
|
ecs_switch_node_t *node = &nodes[element]; |
|
|
|
ecs_switch_header_t *dst_hdr = flecs_switch_ensure_header(sw, value); |
|
ecs_switch_header_t *cur_hdr = flecs_switch_get_header(sw, cur_value); |
|
|
|
flecs_switch_verify_nodes(cur_hdr, nodes); |
|
flecs_switch_verify_nodes(dst_hdr, nodes); |
|
|
|
/* If value is not 0, and dst_hdr is NULL, then this is not a valid value |
|
* for this switch */ |
|
ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (cur_hdr) { |
|
flecs_switch_remove_node(cur_hdr, nodes, node, element); |
|
} |
|
|
|
/* Now update the node itself by adding it as the first node of dst */ |
|
node->prev = -1; |
|
values[element] = value; |
|
|
|
if (dst_hdr) { |
|
node->next = dst_hdr->element; |
|
|
|
/* Also update the dst header */ |
|
int32_t first = dst_hdr->element; |
|
if (first != -1) { |
|
ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_switch_node_t *first_node = &nodes[first]; |
|
first_node->prev = element; |
|
} |
|
|
|
dst_hdr->element = element; |
|
dst_hdr->count ++; |
|
} |
|
} |
|
|
|
void flecs_switch_remove( |
|
ecs_switch_t *sw, |
|
int32_t elem) |
|
{ |
|
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(elem < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(elem >= 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
uint64_t *values = ecs_vec_first(&sw->values); |
|
uint64_t value = values[elem]; |
|
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); |
|
ecs_switch_node_t *node = &nodes[elem]; |
|
|
|
/* If node is currently assigned to a case, remove it from the list */ |
|
if (value != 0) { |
|
ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); |
|
ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_switch_verify_nodes(hdr, nodes); |
|
flecs_switch_remove_node(hdr, nodes, node, elem); |
|
} |
|
|
|
int32_t last_elem = ecs_vec_count(&sw->nodes) - 1; |
|
if (last_elem != elem) { |
|
ecs_switch_node_t *last = ecs_vec_last_t(&sw->nodes, ecs_switch_node_t); |
|
int32_t next = last->next, prev = last->prev; |
|
if (next != -1) { |
|
ecs_switch_node_t *n = &nodes[next]; |
|
n->prev = elem; |
|
} |
|
|
|
if (prev != -1) { |
|
ecs_switch_node_t *n = &nodes[prev]; |
|
n->next = elem; |
|
} else { |
|
ecs_switch_header_t *hdr = flecs_switch_get_header(sw, values[last_elem]); |
|
if (hdr && hdr->element != -1) { |
|
ecs_assert(hdr->element == last_elem, |
|
ECS_INTERNAL_ERROR, NULL); |
|
hdr->element = elem; |
|
} |
|
} |
|
} |
|
|
|
/* Remove element from arrays */ |
|
ecs_vec_remove_t(&sw->nodes, ecs_switch_node_t, elem); |
|
ecs_vec_remove_t(&sw->values, uint64_t, elem); |
|
} |
|
|
|
uint64_t flecs_switch_get( |
|
const ecs_switch_t *sw, |
|
int32_t element) |
|
{ |
|
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
uint64_t *values = ecs_vec_first(&sw->values); |
|
return values[element]; |
|
} |
|
|
|
ecs_vec_t* flecs_switch_values( |
|
const ecs_switch_t *sw) |
|
{ |
|
return ECS_CONST_CAST(ecs_vec_t*, &sw->values); |
|
} |
|
|
|
int32_t flecs_switch_case_count( |
|
const ecs_switch_t *sw, |
|
uint64_t value) |
|
{ |
|
ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); |
|
if (!hdr) { |
|
return 0; |
|
} |
|
|
|
return hdr->count; |
|
} |
|
|
|
void flecs_switch_swap( |
|
ecs_switch_t *sw, |
|
int32_t elem_1, |
|
int32_t elem_2) |
|
{ |
|
uint64_t v1 = flecs_switch_get(sw, elem_1); |
|
uint64_t v2 = flecs_switch_get(sw, elem_2); |
|
|
|
flecs_switch_set(sw, elem_2, v1); |
|
flecs_switch_set(sw, elem_1, v2); |
|
} |
|
|
|
int32_t flecs_switch_first( |
|
const ecs_switch_t *sw, |
|
uint64_t value) |
|
{ |
|
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); |
|
if (!hdr) { |
|
return -1; |
|
} |
|
|
|
return hdr->element; |
|
} |
|
|
|
int32_t flecs_switch_next( |
|
const ecs_switch_t *sw, |
|
int32_t element) |
|
{ |
|
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); |
|
|
|
return nodes[element].next; |
|
} |
|
|
|
/** |
|
* @file datastructures/vec.c |
|
* @brief Vector with allocator support. |
|
*/ |
|
|
|
|
|
ecs_vec_t* ecs_vec_init( |
|
ecs_allocator_t *allocator, |
|
ecs_vec_t *v, |
|
ecs_size_t size, |
|
int32_t elem_count) |
|
{ |
|
ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); |
|
v->array = NULL; |
|
v->count = 0; |
|
if (elem_count) { |
|
if (allocator) { |
|
v->array = flecs_alloc(allocator, size * elem_count); |
|
} else { |
|
v->array = ecs_os_malloc(size * elem_count); |
|
} |
|
} |
|
v->size = elem_count; |
|
#ifdef FLECS_SANITIZE |
|
v->elem_size = size; |
|
#endif |
|
return v; |
|
} |
|
|
|
void ecs_vec_init_if( |
|
ecs_vec_t *vec, |
|
ecs_size_t size) |
|
{ |
|
ecs_san_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); |
|
(void)vec; |
|
(void)size; |
|
#ifdef FLECS_SANITIZE |
|
if (!vec->elem_size) { |
|
ecs_assert(vec->count == 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(vec->size == 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(vec->array == NULL, ECS_INTERNAL_ERROR, NULL); |
|
vec->elem_size = size; |
|
} |
|
#endif |
|
} |
|
|
|
void ecs_vec_fini( |
|
ecs_allocator_t *allocator, |
|
ecs_vec_t *v, |
|
ecs_size_t size) |
|
{ |
|
if (v->array) { |
|
ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
if (allocator) { |
|
flecs_free(allocator, size * v->size, v->array); |
|
} else { |
|
ecs_os_free(v->array); |
|
} |
|
v->array = NULL; |
|
v->count = 0; |
|
v->size = 0; |
|
} |
|
} |
|
|
|
ecs_vec_t* ecs_vec_reset( |
|
ecs_allocator_t *allocator, |
|
ecs_vec_t *v, |
|
ecs_size_t size) |
|
{ |
|
if (!v->size) { |
|
ecs_vec_init(allocator, v, size, 0); |
|
} else { |
|
ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); |
|
ecs_vec_clear(v); |
|
} |
|
return v; |
|
} |
|
|
|
void ecs_vec_clear( |
|
ecs_vec_t *vec) |
|
{ |
|
vec->count = 0; |
|
} |
|
|
|
ecs_vec_t ecs_vec_copy( |
|
ecs_allocator_t *allocator, |
|
const ecs_vec_t *v, |
|
ecs_size_t size) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
void *array; |
|
if (allocator) { |
|
array = flecs_dup(allocator, size * v->size, v->array); |
|
} else { |
|
array = ecs_os_memdup(v->array, size * v->size); |
|
} |
|
return (ecs_vec_t) { |
|
.count = v->count, |
|
.size = v->size, |
|
.array = array |
|
#ifdef FLECS_SANITIZE |
|
, .elem_size = size |
|
#endif |
|
}; |
|
} |
|
|
|
ecs_vec_t ecs_vec_copy_shrink( |
|
ecs_allocator_t *allocator, |
|
const ecs_vec_t *v, |
|
ecs_size_t size) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
int32_t count = v->count; |
|
void *array = NULL; |
|
if (count) { |
|
if (allocator) { |
|
array = flecs_dup(allocator, size * count, v->array); |
|
} else { |
|
array = ecs_os_memdup(v->array, size * count); |
|
} |
|
} |
|
return (ecs_vec_t) { |
|
.count = count, |
|
.size = count, |
|
.array = array |
|
#ifdef FLECS_SANITIZE |
|
, .elem_size = size |
|
#endif |
|
}; |
|
} |
|
|
|
void ecs_vec_reclaim( |
|
ecs_allocator_t *allocator, |
|
ecs_vec_t *v, |
|
ecs_size_t size) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
int32_t count = v->count; |
|
if (count < v->size) { |
|
if (count) { |
|
if (allocator) { |
|
v->array = flecs_realloc( |
|
allocator, size * count, size * v->size, v->array); |
|
} else { |
|
v->array = ecs_os_realloc(v->array, size * count); |
|
} |
|
v->size = count; |
|
} else { |
|
ecs_vec_fini(allocator, v, size); |
|
} |
|
} |
|
} |
|
|
|
void ecs_vec_set_size( |
|
ecs_allocator_t *allocator, |
|
ecs_vec_t *v, |
|
ecs_size_t size, |
|
int32_t elem_count) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
if (v->size != elem_count) { |
|
if (elem_count < v->count) { |
|
elem_count = v->count; |
|
} |
|
|
|
elem_count = flecs_next_pow_of_2(elem_count); |
|
if (elem_count < 2) { |
|
elem_count = 2; |
|
} |
|
if (elem_count != v->size) { |
|
if (allocator) { |
|
v->array = flecs_realloc( |
|
allocator, size * elem_count, size * v->size, v->array); |
|
} else { |
|
v->array = ecs_os_realloc(v->array, size * elem_count); |
|
} |
|
v->size = elem_count; |
|
} |
|
} |
|
} |
|
|
|
void ecs_vec_set_min_size( |
|
struct ecs_allocator_t *allocator, |
|
ecs_vec_t *vec, |
|
ecs_size_t size, |
|
int32_t elem_count) |
|
{ |
|
if (elem_count > vec->size) { |
|
ecs_vec_set_size(allocator, vec, size, elem_count); |
|
} |
|
} |
|
|
|
void ecs_vec_set_min_count( |
|
struct ecs_allocator_t *allocator, |
|
ecs_vec_t *vec, |
|
ecs_size_t size, |
|
int32_t elem_count) |
|
{ |
|
ecs_vec_set_min_size(allocator, vec, size, elem_count); |
|
if (vec->count < elem_count) { |
|
vec->count = elem_count; |
|
} |
|
} |
|
|
|
void ecs_vec_set_min_count_zeromem( |
|
struct ecs_allocator_t *allocator, |
|
ecs_vec_t *vec, |
|
ecs_size_t size, |
|
int32_t elem_count) |
|
{ |
|
int32_t count = vec->count; |
|
if (count < elem_count) { |
|
ecs_vec_set_min_count(allocator, vec, size, elem_count); |
|
ecs_os_memset(ECS_ELEM(vec->array, size, count), 0, |
|
size * (elem_count - count)); |
|
} |
|
} |
|
|
|
void ecs_vec_set_count( |
|
ecs_allocator_t *allocator, |
|
ecs_vec_t *v, |
|
ecs_size_t size, |
|
int32_t elem_count) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
if (v->count != elem_count) { |
|
if (v->size < elem_count) { |
|
ecs_vec_set_size(allocator, v, size, elem_count); |
|
} |
|
|
|
v->count = elem_count; |
|
} |
|
} |
|
|
|
void* ecs_vec_grow( |
|
ecs_allocator_t *allocator, |
|
ecs_vec_t *v, |
|
ecs_size_t size, |
|
int32_t elem_count) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); |
|
int32_t count = v->count; |
|
ecs_vec_set_count(allocator, v, size, count + elem_count); |
|
return ECS_ELEM(v->array, size, count); |
|
} |
|
|
|
void* ecs_vec_append( |
|
ecs_allocator_t *allocator, |
|
ecs_vec_t *v, |
|
ecs_size_t size) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
int32_t count = v->count; |
|
if (v->size == count) { |
|
ecs_vec_set_size(allocator, v, size, count + 1); |
|
} |
|
v->count = count + 1; |
|
return ECS_ELEM(v->array, size, count); |
|
} |
|
|
|
void ecs_vec_remove( |
|
ecs_vec_t *v, |
|
ecs_size_t size, |
|
int32_t index) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); |
|
if (index == --v->count) { |
|
return; |
|
} |
|
|
|
ecs_os_memcpy( |
|
ECS_ELEM(v->array, size, index), |
|
ECS_ELEM(v->array, size, v->count), |
|
size); |
|
} |
|
|
|
void ecs_vec_remove_last( |
|
ecs_vec_t *v) |
|
{ |
|
v->count --; |
|
} |
|
|
|
int32_t ecs_vec_count( |
|
const ecs_vec_t *v) |
|
{ |
|
return v->count; |
|
} |
|
|
|
int32_t ecs_vec_size( |
|
const ecs_vec_t *v) |
|
{ |
|
return v->size; |
|
} |
|
|
|
void* ecs_vec_get( |
|
const ecs_vec_t *v, |
|
ecs_size_t size, |
|
int32_t index) |
|
{ |
|
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); |
|
return ECS_ELEM(v->array, size, index); |
|
} |
|
|
|
void* ecs_vec_last( |
|
const ecs_vec_t *v, |
|
ecs_size_t size) |
|
{ |
|
ecs_san_assert(!v->elem_size || size == v->elem_size, |
|
ECS_INVALID_PARAMETER, NULL); |
|
return ECS_ELEM(v->array, size, v->count - 1); |
|
} |
|
|
|
void* ecs_vec_first( |
|
const ecs_vec_t *v) |
|
{ |
|
return v->array; |
|
} |
|
|
|
|
|
static |
|
ecs_entity_index_page_t* flecs_entity_index_ensure_page( |
|
ecs_entity_index_t *index, |
|
uint32_t id) |
|
{ |
|
int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); |
|
if (page_index >= ecs_vec_count(&index->pages)) { |
|
ecs_vec_set_min_count_zeromem_t(index->allocator, &index->pages, |
|
ecs_entity_index_page_t*, page_index + 1); |
|
} |
|
|
|
ecs_entity_index_page_t **page_ptr = ecs_vec_get_t(&index->pages, |
|
ecs_entity_index_page_t*, page_index); |
|
ecs_entity_index_page_t *page = *page_ptr; |
|
if (!page) { |
|
page = *page_ptr = flecs_bcalloc(&index->page_allocator); |
|
ecs_assert(page != NULL, ECS_OUT_OF_MEMORY, NULL); |
|
} |
|
|
|
return page; |
|
} |
|
|
|
void flecs_entity_index_init( |
|
ecs_allocator_t *allocator, |
|
ecs_entity_index_t *index) |
|
{ |
|
index->allocator = allocator; |
|
index->alive_count = 1; |
|
ecs_vec_init_t(allocator, &index->dense, uint64_t, 1); |
|
ecs_vec_set_count_t(allocator, &index->dense, uint64_t, 1); |
|
ecs_vec_init_t(allocator, &index->pages, ecs_entity_index_page_t*, 0); |
|
flecs_ballocator_init(&index->page_allocator, |
|
ECS_SIZEOF(ecs_entity_index_page_t)); |
|
} |
|
|
|
void flecs_entity_index_fini( |
|
ecs_entity_index_t *index) |
|
{ |
|
ecs_vec_fini_t(index->allocator, &index->dense, uint64_t); |
|
#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) |
|
int32_t i, count = ecs_vec_count(&index->pages); |
|
ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages); |
|
for (i = 0; i < count; i ++) { |
|
flecs_bfree(&index->page_allocator, pages[i]); |
|
} |
|
#endif |
|
ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); |
|
flecs_ballocator_fini(&index->page_allocator); |
|
} |
|
|
|
ecs_record_t* flecs_entity_index_get_any( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
uint32_t id = (uint32_t)entity; |
|
int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); |
|
ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, |
|
ecs_entity_index_page_t*, page_index)[0]; |
|
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; |
|
ecs_assert(r->dense != 0, ECS_INVALID_PARAMETER, NULL); |
|
return r; |
|
} |
|
|
|
ecs_record_t* flecs_entity_index_get( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
ecs_record_t *r = flecs_entity_index_get_any(index, entity); |
|
ecs_assert(r->dense < index->alive_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity, |
|
ECS_INVALID_PARAMETER, NULL); |
|
return r; |
|
} |
|
|
|
ecs_record_t* flecs_entity_index_try_get_any( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
uint32_t id = (uint32_t)entity; |
|
int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); |
|
if (page_index >= ecs_vec_count(&index->pages)) { |
|
return NULL; |
|
} |
|
|
|
ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, |
|
ecs_entity_index_page_t*, page_index)[0]; |
|
if (!page) { |
|
return NULL; |
|
} |
|
|
|
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; |
|
if (!r->dense) { |
|
return NULL; |
|
} |
|
|
|
return r; |
|
} |
|
|
|
ecs_record_t* flecs_entity_index_try_get( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); |
|
if (r) { |
|
if (r->dense >= index->alive_count) { |
|
return NULL; |
|
} |
|
if (ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] != entity) { |
|
return NULL; |
|
} |
|
} |
|
return r; |
|
} |
|
|
|
ecs_record_t* flecs_entity_index_ensure( |
|
ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
uint32_t id = (uint32_t)entity; |
|
ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); |
|
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; |
|
|
|
int32_t dense = r->dense; |
|
if (dense) { |
|
/* Entity is already alive, nothing to be done */ |
|
if (dense < index->alive_count) { |
|
ecs_assert( |
|
ecs_vec_get_t(&index->dense, uint64_t, dense)[0] == entity, |
|
ECS_INTERNAL_ERROR, NULL); |
|
return r; |
|
} |
|
} else { |
|
/* Entity doesn't have a dense index yet */ |
|
ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = entity; |
|
r->dense = dense = ecs_vec_count(&index->dense) - 1; |
|
index->max_id = id > index->max_id ? id : index->max_id; |
|
} |
|
|
|
ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Entity is not alive, swap with first not alive element */ |
|
uint64_t *ids = ecs_vec_first(&index->dense); |
|
uint64_t e_swap = ids[index->alive_count]; |
|
ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); |
|
ecs_assert(r_swap->dense == index->alive_count, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
r_swap->dense = dense; |
|
r->dense = index->alive_count; |
|
ids[dense] = e_swap; |
|
ids[index->alive_count ++] = entity; |
|
|
|
ecs_assert(flecs_entity_index_is_alive(index, entity), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
return r; |
|
} |
|
|
|
void flecs_entity_index_remove( |
|
ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
ecs_record_t *r = flecs_entity_index_try_get(index, entity); |
|
if (!r) { |
|
/* Entity is not alive or doesn't exist, nothing to be done */ |
|
return; |
|
} |
|
|
|
int32_t dense = r->dense; |
|
int32_t i_swap = -- index->alive_count; |
|
uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap); |
|
uint64_t e_swap = e_swap_ptr[0]; |
|
ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); |
|
ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL); |
|
|
|
r_swap->dense = dense; |
|
r->table = NULL; |
|
r->idr = NULL; |
|
r->row = 0; |
|
r->dense = i_swap; |
|
ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = e_swap; |
|
e_swap_ptr[0] = ECS_GENERATION_INC(entity); |
|
ecs_assert(!flecs_entity_index_is_alive(index, entity), |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
void flecs_entity_index_set_generation( |
|
ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); |
|
if (r) { |
|
ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] = entity; |
|
} |
|
} |
|
|
|
uint64_t flecs_entity_index_get_generation( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); |
|
if (r) { |
|
return ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0]; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
bool flecs_entity_index_is_alive( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
return flecs_entity_index_try_get(index, entity) != NULL; |
|
} |
|
|
|
bool flecs_entity_index_is_valid( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
uint32_t id = (uint32_t)entity; |
|
ecs_record_t *r = flecs_entity_index_try_get_any(index, id); |
|
if (!r || !r->dense) { |
|
/* Doesn't exist yet, so is valid */ |
|
return true; |
|
} |
|
|
|
/* If the id exists, it must be alive */ |
|
return r->dense < index->alive_count; |
|
} |
|
|
|
bool flecs_entity_index_exists( |
|
const ecs_entity_index_t *index, |
|
uint64_t entity) |
|
{ |
|
return flecs_entity_index_try_get_any(index, entity) != NULL; |
|
} |
|
|
|
uint64_t flecs_entity_index_new_id( |
|
ecs_entity_index_t *index) |
|
{ |
|
if (index->alive_count != ecs_vec_count(&index->dense)) { |
|
/* Recycle id */ |
|
return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0]; |
|
} |
|
|
|
/* Create new id */ |
|
uint32_t id = (uint32_t)++ index->max_id; |
|
ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; |
|
|
|
ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); |
|
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; |
|
r->dense = index->alive_count ++; |
|
ecs_assert(index->alive_count == ecs_vec_count(&index->dense), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
return id; |
|
} |
|
|
|
uint64_t* flecs_entity_index_new_ids( |
|
ecs_entity_index_t *index, |
|
int32_t count) |
|
{ |
|
int32_t alive_count = index->alive_count; |
|
int32_t new_count = alive_count + count; |
|
int32_t dense_count = ecs_vec_count(&index->dense); |
|
|
|
if (new_count < dense_count) { |
|
/* Recycle ids */ |
|
index->alive_count = new_count; |
|
return ecs_vec_get_t(&index->dense, uint64_t, alive_count); |
|
} |
|
|
|
/* Allocate new ids */ |
|
ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, new_count); |
|
int32_t i, to_add = new_count - dense_count; |
|
for (i = 0; i < to_add; i ++) { |
|
uint32_t id = (uint32_t)++ index->max_id; |
|
int32_t dense = dense_count + i; |
|
ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = id; |
|
ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); |
|
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; |
|
r->dense = dense; |
|
} |
|
|
|
index->alive_count = new_count; |
|
return ecs_vec_get_t(&index->dense, uint64_t, alive_count); |
|
} |
|
|
|
void flecs_entity_index_set_size( |
|
ecs_entity_index_t *index, |
|
int32_t size) |
|
{ |
|
ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size); |
|
} |
|
|
|
int32_t flecs_entity_index_count( |
|
const ecs_entity_index_t *index) |
|
{ |
|
return index->alive_count - 1; |
|
} |
|
|
|
int32_t flecs_entity_index_size( |
|
const ecs_entity_index_t *index) |
|
{ |
|
return ecs_vec_count(&index->dense) - 1; |
|
} |
|
|
|
int32_t flecs_entity_index_not_alive_count( |
|
const ecs_entity_index_t *index) |
|
{ |
|
return ecs_vec_count(&index->dense) - index->alive_count; |
|
} |
|
|
|
void flecs_entity_index_clear( |
|
ecs_entity_index_t *index) |
|
{ |
|
int32_t i, count = ecs_vec_count(&index->pages); |
|
ecs_entity_index_page_t **pages = ecs_vec_first_t(&index->pages, |
|
ecs_entity_index_page_t*); |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_index_page_t *page = pages[i]; |
|
if (page) { |
|
ecs_os_zeromem(page); |
|
} |
|
} |
|
|
|
ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); |
|
|
|
index->alive_count = 1; |
|
index->max_id = 0; |
|
} |
|
|
|
const uint64_t* flecs_entity_index_ids( |
|
const ecs_entity_index_t *index) |
|
{ |
|
return ecs_vec_get_t(&index->dense, uint64_t, 1); |
|
} |
|
|
|
static |
|
void flecs_entity_index_copy_intern( |
|
ecs_entity_index_t * dst, |
|
const ecs_entity_index_t * src) |
|
{ |
|
flecs_entity_index_set_size(dst, flecs_entity_index_size(src)); |
|
const uint64_t *ids = flecs_entity_index_ids(src); |
|
|
|
int32_t i, count = src->alive_count; |
|
for (i = 0; i < count - 1; i ++) { |
|
uint64_t id = ids[i]; |
|
ecs_record_t *src_ptr = flecs_entity_index_get(src, id); |
|
ecs_record_t *dst_ptr = flecs_entity_index_ensure(dst, id); |
|
flecs_entity_index_set_generation(dst, id); |
|
ecs_os_memcpy_t(dst_ptr, src_ptr, ecs_record_t); |
|
} |
|
|
|
dst->max_id = src->max_id; |
|
|
|
ecs_assert(src->alive_count == dst->alive_count, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
void flecs_entity_index_copy( |
|
ecs_entity_index_t *dst, |
|
const ecs_entity_index_t *src) |
|
{ |
|
if (!src) { |
|
return; |
|
} |
|
|
|
flecs_entity_index_init(src->allocator, dst); |
|
flecs_entity_index_copy_intern(dst, src); |
|
} |
|
|
|
void flecs_entity_index_restore( |
|
ecs_entity_index_t *dst, |
|
const ecs_entity_index_t *src) |
|
{ |
|
if (!src) { |
|
return; |
|
} |
|
|
|
flecs_entity_index_clear(dst); |
|
flecs_entity_index_copy_intern(dst, src); |
|
} |
|
|
|
/** |
|
* @file id_index.c |
|
* @brief Index for looking up tables by (component) id. |
|
* |
|
* An id record stores the administration for an in use (component) id, that is |
|
* an id that has been used in tables. |
|
* |
|
* An id record contains a table cache, which stores the list of tables that |
|
* have the id. Each entry in the cache (a table record) stores the first |
|
* occurrence of the id in the table and the number of occurrences of the id in |
|
* the table (in the case of wildcard ids). |
|
* |
|
* Id records are used in lots of scenarios, like uncached queries, or for |
|
* getting a component array/component for an entity. |
|
*/ |
|
|
|
|
|
static |
|
ecs_id_record_elem_t* flecs_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 flecs_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 = flecs_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 = flecs_id_record_elem(idr, elem, cur); |
|
cur_elem->prev = idr; |
|
} |
|
head_elem->next = idr; |
|
} |
|
|
|
static |
|
void flecs_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 = flecs_id_record_elem(idr, elem, prev); |
|
prev_elem->next = next; |
|
if (next) { |
|
ecs_id_record_elem_t *next_elem = flecs_id_record_elem(idr, elem, next); |
|
next_elem->prev = prev; |
|
} |
|
} |
|
|
|
static |
|
void flecs_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_id_record_ensure(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); |
|
flecs_id_record_elem_insert(widr, idr, &idr->first); |
|
} else { |
|
ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, |
|
ECS_INTERNAL_ERROR, NULL); |
|
flecs_id_record_elem_insert(widr, idr, &idr->second); |
|
|
|
if (idr->flags & EcsIdTraversable) { |
|
flecs_id_record_elem_insert(widr, idr, &idr->trav); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_remove_id_elem( |
|
ecs_id_record_t *idr, |
|
ecs_id_t wildcard) |
|
{ |
|
ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { |
|
ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, |
|
ECS_INTERNAL_ERROR, NULL); |
|
flecs_id_record_elem_remove(idr, &idr->first); |
|
} else { |
|
ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, |
|
ECS_INTERNAL_ERROR, NULL); |
|
flecs_id_record_elem_remove(idr, &idr->second); |
|
|
|
if (idr->flags & EcsIdTraversable) { |
|
flecs_id_record_elem_remove(idr, &idr->trav); |
|
} |
|
} |
|
} |
|
|
|
static |
|
ecs_id_t flecs_id_record_hash( |
|
ecs_id_t id) |
|
{ |
|
id = ecs_strip_generation(id); |
|
if (ECS_IS_PAIR(id)) { |
|
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; |
|
} |
|
|
|
static |
|
ecs_id_record_t* flecs_id_record_new( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_id_record_t *idr, *idr_t = NULL; |
|
ecs_id_t hash = flecs_id_record_hash(id); |
|
if (hash >= FLECS_HI_ID_RECORD_ID) { |
|
idr = flecs_bcalloc(&world->allocators.id_record); |
|
ecs_map_insert_ptr(&world->id_index_hi, hash, idr); |
|
} else { |
|
idr = &world->id_index_lo[hash]; |
|
ecs_os_zeromem(idr); |
|
} |
|
|
|
ecs_table_cache_init(world, &idr->cache); |
|
|
|
idr->id = id; |
|
idr->refcount = 1; |
|
idr->reachable.current = -1; |
|
|
|
bool is_wildcard = ecs_id_is_wildcard(id); |
|
bool is_pair = ECS_IS_PAIR(id); |
|
|
|
ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; |
|
if (is_pair) { |
|
// rel = ecs_pair_first(world, id); |
|
rel = ECS_PAIR_FIRST(id); |
|
rel = flecs_entities_get_generation(world, rel); |
|
ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Relationship object can be 0, as tables without a ChildOf |
|
* relationship are added to the (ChildOf, 0) id record */ |
|
tgt = ECS_PAIR_SECOND(id); |
|
|
|
#ifdef FLECS_DEBUG |
|
/* Check constraints */ |
|
if (tgt) { |
|
tgt = flecs_entities_get_generation(world, tgt); |
|
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
if (tgt && !ecs_id_is_wildcard(tgt)) { |
|
/* Check if target of relationship satisfies OneOf property */ |
|
ecs_entity_t oneof = flecs_get_oneof(world, rel); |
|
ecs_check( !oneof || ecs_has_pair(world, tgt, EcsChildOf, oneof), |
|
ECS_CONSTRAINT_VIOLATED, NULL); |
|
(void)oneof; |
|
|
|
/* Check if we're not trying to inherit from a final target */ |
|
if (rel == EcsIsA) { |
|
bool is_final = ecs_has_id(world, tgt, EcsFinal); |
|
ecs_check(!is_final, ECS_CONSTRAINT_VIOLATED, |
|
"cannot inherit from final entity"); |
|
(void)is_final; |
|
} |
|
} |
|
#endif |
|
|
|
if (!is_wildcard && (rel != EcsFlag)) { |
|
/* Inherit flags from (relationship, *) record */ |
|
ecs_id_record_t *idr_r = flecs_id_record_ensure( |
|
world, ecs_pair(rel, EcsWildcard)); |
|
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 relationships for an object, |
|
* or all objects for a relationship. */ |
|
flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); |
|
|
|
idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt)); |
|
flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t); |
|
|
|
if (rel == EcsUnion) { |
|
idr->flags |= EcsIdUnion; |
|
} |
|
} |
|
} else { |
|
rel = id & ECS_COMPONENT_MASK; |
|
ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
/* Initialize type info if id is not a tag */ |
|
if (!is_wildcard && (!role || is_pair)) { |
|
if (!(idr->flags & EcsIdTag)) { |
|
const ecs_type_info_t *ti = flecs_type_info_get(world, rel); |
|
if (!ti && tgt) { |
|
ti = flecs_type_info_get(world, tgt); |
|
} |
|
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, EcsEntityIsId); |
|
if (tgt) { |
|
/* Flag for OnDeleteTarget policies */ |
|
ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt); |
|
ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
flecs_record_add_flag(tgt_r, EcsEntityIsTarget); |
|
if (idr->flags & EcsIdTraversable) { |
|
/* Flag used to determine if object should be traversed when |
|
* propagating events or with super/subset queries */ |
|
flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); |
|
|
|
/* Add reference to (*, tgt) id record to entity record */ |
|
tgt_r->idr = idr_t; |
|
} |
|
} |
|
|
|
ecs_observable_t *o = &world->observable; |
|
idr->flags |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; |
|
idr->flags |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; |
|
idr->flags |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; |
|
idr->flags |= flecs_observers_exist(o, id, EcsUnSet) * EcsIdHasUnSet; |
|
idr->flags |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill; |
|
idr->flags |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty; |
|
idr->flags |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; |
|
idr->flags |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; |
|
|
|
if (ecs_should_log_1()) { |
|
char *id_str = ecs_id_str(world, id); |
|
ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); |
|
ecs_os_free(id_str); |
|
} |
|
|
|
/* Update counters */ |
|
world->info.id_create_total ++; |
|
world->info.component_id_count += idr->type_info != NULL; |
|
world->info.tag_id_count += idr->type_info == NULL; |
|
world->info.pair_id_count += is_pair; |
|
|
|
return idr; |
|
#ifdef FLECS_DEBUG |
|
error: |
|
return NULL; |
|
#endif |
|
} |
|
|
|
static |
|
void flecs_id_record_assert_empty( |
|
ecs_id_record_t *idr) |
|
{ |
|
(void)idr; |
|
ecs_assert(flecs_table_cache_count(&idr->cache) == 0, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0, |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
static |
|
void flecs_id_record_free( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_t id = idr->id; |
|
|
|
flecs_id_record_assert_empty(idr); |
|
|
|
/* Id is still in use by a filter, query, rule or observer */ |
|
ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0), |
|
ECS_ID_IN_USE, "cannot delete id that is queried for"); |
|
|
|
if (ECS_IS_PAIR(id)) { |
|
ecs_entity_t rel = ECS_PAIR_FIRST(id); |
|
ecs_entity_t tgt = ECS_PAIR_SECOND(id); |
|
if (!ecs_id_is_wildcard(id)) { |
|
if (ECS_PAIR_FIRST(id) != EcsFlag) { |
|
/* If id is not a wildcard, remove it from the wildcard lists */ |
|
flecs_remove_id_elem(idr, ecs_pair(rel, EcsWildcard)); |
|
flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, tgt)); |
|
} |
|
} else { |
|
ecs_log_push_2(); |
|
|
|
/* If id is a wildcard, it means that all id records that match the |
|
* wildcard are also empty, so release them */ |
|
if (ECS_PAIR_FIRST(id) == EcsWildcard) { |
|
/* Iterate (*, Target) list */ |
|
ecs_id_record_t *cur, *next = idr->second.next; |
|
while ((cur = next)) { |
|
flecs_id_record_assert_empty(cur); |
|
next = cur->second.next; |
|
flecs_id_record_release(world, cur); |
|
} |
|
} else { |
|
/* Iterate (Relationship, *) list */ |
|
ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_id_record_t *cur, *next = idr->first.next; |
|
while ((cur = next)) { |
|
flecs_id_record_assert_empty(cur); |
|
next = cur->first.next; |
|
flecs_id_record_release(world, cur); |
|
} |
|
} |
|
|
|
ecs_log_pop_2(); |
|
} |
|
} |
|
|
|
/* Update counters */ |
|
world->info.id_delete_total ++; |
|
world->info.pair_id_count -= ECS_IS_PAIR(id); |
|
world->info.component_id_count -= idr->type_info != NULL; |
|
world->info.tag_id_count -= idr->type_info == NULL; |
|
|
|
/* Unregister the id record from the world & free resources */ |
|
ecs_table_cache_fini(&idr->cache); |
|
flecs_name_index_free(idr->name_index); |
|
ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t); |
|
|
|
ecs_id_t hash = flecs_id_record_hash(id); |
|
if (hash >= FLECS_HI_ID_RECORD_ID) { |
|
ecs_map_remove(&world->id_index_hi, hash); |
|
flecs_bfree(&world->allocators.id_record, idr); |
|
} else { |
|
idr->id = 0; /* Tombstone */ |
|
} |
|
|
|
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); |
|
} |
|
} |
|
|
|
ecs_id_record_t* flecs_id_record_ensure( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
idr = flecs_id_record_new(world, id); |
|
} |
|
return idr; |
|
} |
|
|
|
ecs_id_record_t* flecs_id_record_get( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
if (id == ecs_pair(EcsIsA, EcsWildcard)) { |
|
return world->idr_isa_wildcard; |
|
} else if (id == ecs_pair(EcsChildOf, EcsWildcard)) { |
|
return world->idr_childof_wildcard; |
|
} else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { |
|
return world->idr_identifier_name; |
|
} |
|
|
|
ecs_id_t hash = flecs_id_record_hash(id); |
|
ecs_id_record_t *idr = NULL; |
|
if (hash >= FLECS_HI_ID_RECORD_ID) { |
|
idr = ecs_map_get_deref(&world->id_index_hi, ecs_id_record_t, hash); |
|
} else { |
|
idr = &world->id_index_lo[hash]; |
|
if (!idr->id) { |
|
idr = NULL; |
|
} |
|
} |
|
|
|
return idr; |
|
} |
|
|
|
ecs_id_record_t* flecs_query_id_record_get( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
ecs_entity_t first = ECS_PAIR_FIRST(id); |
|
if (ECS_IS_PAIR(id) && (first != EcsWildcard)) { |
|
idr = flecs_id_record_get(world, ecs_pair(EcsUnion, first)); |
|
} |
|
return idr; |
|
} |
|
if (ECS_IS_PAIR(id) && |
|
ECS_PAIR_SECOND(id) == EcsWildcard && |
|
(idr->flags & EcsIdUnion)) |
|
{ |
|
idr = flecs_id_record_get(world, |
|
ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); |
|
} |
|
|
|
return idr; |
|
} |
|
|
|
void flecs_id_record_claim( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr) |
|
{ |
|
(void)world; |
|
idr->refcount ++; |
|
} |
|
|
|
int32_t flecs_id_record_release( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr) |
|
{ |
|
int32_t rc = -- idr->refcount; |
|
ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!rc) { |
|
flecs_id_record_free(world, idr); |
|
} |
|
|
|
return rc; |
|
} |
|
|
|
void flecs_id_record_release_tables( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr) |
|
{ |
|
/* Cache should not contain tables that aren't empty */ |
|
ecs_assert(flecs_table_cache_count(&idr->cache) == 0, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_cache_iter_t it; |
|
if (flecs_table_cache_empty_iter(&idr->cache, &it)) { |
|
ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
/* Release current table */ |
|
flecs_table_free(world, tr->hdr.table); |
|
} |
|
} |
|
} |
|
|
|
bool flecs_id_record_set_type_info( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr, |
|
const ecs_type_info_t *ti) |
|
{ |
|
bool is_wildcard = ecs_id_is_wildcard(idr->id); |
|
if (!is_wildcard) { |
|
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_id_record_name_index_ensure( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr) |
|
{ |
|
ecs_hashmap_t *map = idr->name_index; |
|
if (!map) { |
|
map = idr->name_index = flecs_name_index_new(world, &world->allocator); |
|
} |
|
|
|
return map; |
|
} |
|
|
|
ecs_hashmap_t* flecs_id_name_index_ensure( |
|
ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return flecs_id_record_name_index_ensure(world, idr); |
|
} |
|
|
|
ecs_hashmap_t* flecs_id_name_index_get( |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
return NULL; |
|
} |
|
|
|
return idr->name_index; |
|
} |
|
|
|
ecs_table_record_t* flecs_table_record_get( |
|
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_id_record_get(world, id); |
|
if (!idr) { |
|
return NULL; |
|
} |
|
|
|
return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); |
|
} |
|
|
|
ecs_table_record_t* flecs_id_record_get_table( |
|
const ecs_id_record_t *idr, |
|
const ecs_table_t *table) |
|
{ |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); |
|
} |
|
|
|
void flecs_init_id_records( |
|
ecs_world_t *world) |
|
{ |
|
/* Cache often used id records on world */ |
|
world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); |
|
world->idr_wildcard_wildcard = flecs_id_record_ensure(world, |
|
ecs_pair(EcsWildcard, EcsWildcard)); |
|
world->idr_any = flecs_id_record_ensure(world, EcsAny); |
|
world->idr_isa_wildcard = flecs_id_record_ensure(world, |
|
ecs_pair(EcsIsA, EcsWildcard)); |
|
} |
|
|
|
void flecs_fini_id_records( |
|
ecs_world_t *world) |
|
{ |
|
/* Loop & delete first element until there are no elements left. Id records |
|
* can recursively delete each other, this ensures we always have a |
|
* valid iterator. */ |
|
while (ecs_map_count(&world->id_index_hi) > 0) { |
|
ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi); |
|
ecs_map_next(&it); |
|
flecs_id_record_release(world, ecs_map_ptr(&it)); |
|
} |
|
|
|
int32_t i; |
|
for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) { |
|
ecs_id_record_t *idr = &world->id_index_lo[i]; |
|
if (idr->id) { |
|
flecs_id_record_release(world, idr); |
|
} |
|
} |
|
|
|
ecs_assert(ecs_map_count(&world->id_index_hi) == 0, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_map_fini(&world->id_index_hi); |
|
ecs_os_free(world->id_index_lo); |
|
} |
|
|
|
/** |
|
* @file table.c |
|
* @brief Table storage implementation. |
|
* |
|
* Tables are the data structure that store the component data. Tables have |
|
* columns for each component in the table, and rows for each entity stored in |
|
* the table. Once created, the component list for a table doesn't change, but |
|
* entities can move from one table to another. |
|
* |
|
* Each table has a type, which is a vector with the (component) ids in the |
|
* table. The vector is sorted by id, which ensures that there can be only one |
|
* table for each unique combination of components. |
|
* |
|
* Not all ids in a table have to be components. Tags are ids that have no |
|
* data type associated with them, and as a result don't need to be explicitly |
|
* stored beyond an element in the table type. To save space and speed up table |
|
* creation, each table has a reference to a "storage table", which is a table |
|
* that only includes component ids (so excluding tags). |
|
* |
|
* Note that the actual data is not stored on the storage table. The storage |
|
* table is only used for sharing administration. A column_map member maps |
|
* between column indices of the table and its storage table. Tables are |
|
* refcounted, which ensures that storage tables won't be deleted if other |
|
* tables have references to it. |
|
*/ |
|
|
|
|
|
/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as |
|
* this can severely slow down many ECS operations. */ |
|
#ifdef FLECS_SANITIZE |
|
static |
|
void flecs_table_check_sanity(ecs_table_t *table) { |
|
int32_t size = ecs_vec_size(&table->data.entities); |
|
int32_t count = ecs_vec_count(&table->data.entities); |
|
|
|
int32_t i; |
|
int32_t sw_offset = table->_ ? table->_->sw_offset : 0; |
|
int32_t sw_count = table->_ ? table->_->sw_count : 0; |
|
int32_t bs_offset = table->_ ? table->_->bs_offset : 0; |
|
int32_t bs_count = table->_ ? table->_->bs_count : 0; |
|
int32_t type_count = table->type.count; |
|
ecs_id_t *ids = table->type.array; |
|
|
|
ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (table->column_count) { |
|
int32_t column_count = table->column_count; |
|
ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t *column_map = table->column_map; |
|
ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
for (i = 0; i < column_count; i ++) { |
|
ecs_vec_t *column = &table->data.columns[i].data; |
|
ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); |
|
int32_t column_map_id = column_map[i + type_count]; |
|
ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} else { |
|
ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (sw_count) { |
|
ecs_assert(table->_->sw_columns != NULL, |
|
ECS_INTERNAL_ERROR, NULL); |
|
for (i = 0; i < sw_count; i ++) { |
|
ecs_switch_t *sw = &table->_->sw_columns[i]; |
|
ecs_assert(ecs_vec_count(&sw->values) == count, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion, |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
if (bs_count) { |
|
ecs_assert(table->_->bs_columns != NULL, |
|
ECS_INTERNAL_ERROR, NULL); |
|
for (i = 0; i < bs_count; i ++) { |
|
ecs_bitset_t *bs = &table->_->bs_columns[i]; |
|
ecs_assert(flecs_bitset_count(bs) == count, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), |
|
ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
ecs_assert((table->_->traversable_count == 0) || |
|
(table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); |
|
} |
|
#else |
|
#define flecs_table_check_sanity(table) |
|
#endif |
|
|
|
/* Set flags for type hooks so table operations can quickly check whether a |
|
* fast or complex operation that invokes hooks is required. */ |
|
static |
|
ecs_flags32_t flecs_type_info_flags( |
|
const ecs_type_info_t *ti) |
|
{ |
|
ecs_flags32_t flags = 0; |
|
|
|
if (ti->hooks.ctor) { |
|
flags |= EcsTableHasCtors; |
|
} |
|
if (ti->hooks.on_add) { |
|
flags |= EcsTableHasCtors; |
|
} |
|
if (ti->hooks.dtor) { |
|
flags |= EcsTableHasDtors; |
|
} |
|
if (ti->hooks.on_remove) { |
|
flags |= EcsTableHasDtors; |
|
} |
|
if (ti->hooks.copy) { |
|
flags |= EcsTableHasCopy; |
|
} |
|
if (ti->hooks.move) { |
|
flags |= EcsTableHasMove; |
|
} |
|
|
|
return flags; |
|
} |
|
|
|
static |
|
void flecs_table_init_columns( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t column_count) |
|
{ |
|
if (!column_count) { |
|
return; |
|
} |
|
|
|
int32_t i, cur = 0, ids_count = table->type.count; |
|
ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); |
|
table->data.columns = columns; |
|
|
|
ecs_id_t *ids = table->type.array; |
|
ecs_table_record_t *records = table->_->records; |
|
int32_t *t2s = table->column_map; |
|
int32_t *s2t = &table->column_map[ids_count]; |
|
|
|
for (i = 0; i < ids_count; i ++) { |
|
ecs_table_record_t *tr = &records[i]; |
|
ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; |
|
const ecs_type_info_t *ti = idr->type_info; |
|
if (!ti) { |
|
t2s[i] = -1; |
|
continue; |
|
} |
|
|
|
t2s[i] = cur; |
|
s2t[cur] = i; |
|
tr->column = flecs_ito(int16_t, cur); |
|
|
|
columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); |
|
columns[cur].id = ids[i]; |
|
columns[cur].size = ti->size; |
|
|
|
if (ECS_IS_PAIR(ids[i])) { |
|
ecs_table_record_t *wc_tr = flecs_id_record_get_table( |
|
idr->parent, table); |
|
if (wc_tr->index == tr->index) { |
|
wc_tr->column = tr->column; |
|
} |
|
} |
|
|
|
#ifdef FLECS_DEBUG |
|
ecs_vec_init(NULL, &columns[cur].data, ti->size, 0); |
|
#endif |
|
|
|
table->flags |= flecs_type_info_flags(ti); |
|
cur ++; |
|
} |
|
} |
|
|
|
/* Initialize table storage */ |
|
void flecs_table_init_data( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_data_t *storage = &table->data; |
|
ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0); |
|
|
|
flecs_table_init_columns(world, table, table->column_count); |
|
|
|
ecs_table__t *meta = table->_; |
|
int32_t i, sw_count = meta->sw_count; |
|
int32_t bs_count = meta->bs_count; |
|
|
|
if (sw_count) { |
|
meta->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count); |
|
for (i = 0; i < sw_count; i ++) { |
|
flecs_switch_init(&meta->sw_columns[i], |
|
&world->allocator, 0); |
|
} |
|
} |
|
|
|
if (bs_count) { |
|
meta->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); |
|
for (i = 0; i < bs_count; i ++) { |
|
flecs_bitset_init(&meta->bs_columns[i]); |
|
} |
|
} |
|
} |
|
|
|
/* Initialize table flags. Table flags are used in lots of scenarios to quickly |
|
* check the features of a table without having to inspect the table type. Table |
|
* flags are typically used to early-out of potentially expensive operations. */ |
|
static |
|
void flecs_table_init_flags( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_id_t *ids = table->type.array; |
|
int32_t count = table->type.count; |
|
|
|
int32_t i; |
|
for (i = 0; i < count; i ++) { |
|
ecs_id_t id = ids[i]; |
|
|
|
if (id <= EcsLastInternalComponentId) { |
|
table->flags |= EcsTableHasBuiltins; |
|
} |
|
|
|
if (id == EcsModule) { |
|
table->flags |= EcsTableHasBuiltins; |
|
table->flags |= EcsTableHasModule; |
|
} else if (id == EcsPrefab) { |
|
table->flags |= EcsTableIsPrefab; |
|
} else if (id == EcsDisabled) { |
|
table->flags |= EcsTableIsDisabled; |
|
} else { |
|
if (ECS_IS_PAIR(id)) { |
|
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 (id == ecs_pair_t(EcsIdentifier, EcsName)) { |
|
table->flags |= EcsTableHasName; |
|
} else if (r == EcsUnion) { |
|
ecs_table__t *meta = table->_; |
|
table->flags |= EcsTableHasUnion; |
|
|
|
if (!meta->sw_count) { |
|
meta->sw_offset = flecs_ito(int16_t, i); |
|
} |
|
meta->sw_count ++; |
|
} else if (r == ecs_id(EcsTarget)) { |
|
ecs_table__t *meta = table->_; |
|
table->flags |= EcsTableHasTarget; |
|
meta->ft_offset = flecs_ito(int16_t, i); |
|
} else if (r == ecs_id(EcsPoly)) { |
|
table->flags |= EcsTableHasBuiltins; |
|
} |
|
} else { |
|
if (ECS_HAS_ID_FLAG(id, TOGGLE)) { |
|
ecs_table__t *meta = table->_; |
|
table->flags |= EcsTableHasToggle; |
|
|
|
if (!meta->bs_count) { |
|
meta->bs_offset = flecs_ito(int16_t, i); |
|
} |
|
meta->bs_count ++; |
|
} |
|
if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { |
|
table->flags |= EcsTableHasOverrides; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Utility function that appends an element to the table record array */ |
|
static |
|
void flecs_table_append_to_records( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_vec_t *records, |
|
ecs_id_t id, |
|
int32_t column) |
|
{ |
|
/* To avoid a quadratic search, use the O(1) lookup that the index |
|
* already provides. */ |
|
ecs_id_record_t *idr = flecs_id_record_ensure(world, id); |
|
ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table( |
|
idr, table); |
|
if (!tr) { |
|
tr = ecs_vec_append_t(&world->allocator, records, ecs_table_record_t); |
|
tr->index = flecs_ito(int16_t, column); |
|
tr->count = 1; |
|
|
|
ecs_table_cache_insert(&idr->cache, table, &tr->hdr); |
|
} else { |
|
tr->count ++; |
|
} |
|
|
|
ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
/* Main table initialization function */ |
|
void flecs_table_init( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_table_t *from) |
|
{ |
|
/* Make sure table->flags is initialized */ |
|
flecs_table_init_flags(world, table); |
|
|
|
/* The following code walks the table type to discover which id records the |
|
* table needs to register table records with. |
|
* |
|
* In addition to registering itself with id records for each id in the |
|
* table type, a table also registers itself with wildcard id records. For |
|
* example, if a table contains (Eats, Apples), it will register itself with |
|
* wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it |
|
* easier for wildcard queries to find the relevant tables. */ |
|
|
|
int32_t dst_i = 0, dst_count = table->type.count; |
|
int32_t src_i = 0, src_count = 0; |
|
ecs_id_t *dst_ids = table->type.array; |
|
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_allocator_t *a = &world->allocator; |
|
ecs_vec_t *records = &world->store.records; |
|
ecs_vec_reset_t(a, records, ecs_table_record_t); |
|
ecs_id_record_t *idr, *childof_idr = NULL; |
|
|
|
int32_t last_id = -1; /* Track last regular (non-pair) id */ |
|
int32_t first_pair = -1; /* Track the first pair in the table */ |
|
int32_t first_role = -1; /* Track first id with role */ |
|
|
|
/* 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]; |
|
if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { |
|
first_pair = dst_i; |
|
} |
|
if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { |
|
last_id = dst_i; |
|
} else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { |
|
first_role = dst_i; |
|
} |
|
} |
|
|
|
/* 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) { |
|
ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; |
|
} else if (dst_id < src_id) { |
|
idr = flecs_id_record_ensure(world, dst_id); |
|
} |
|
if (idr) { |
|
tr = ecs_vec_append_t(a, records, ecs_table_record_t); |
|
tr->hdr.cache = (ecs_table_cache_t*)idr; |
|
tr->index = flecs_ito(int16_t, dst_i); |
|
tr->count = 1; |
|
} |
|
|
|
dst_i += dst_id <= src_id; |
|
src_i += dst_id >= src_id; |
|
} |
|
|
|
/* 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_vec_append_t(a, records, ecs_table_record_t); |
|
idr = flecs_id_record_ensure(world, dst_id); |
|
tr->hdr.cache = (ecs_table_cache_t*)idr; |
|
ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
tr->index = flecs_ito(int16_t, dst_i); |
|
tr->count = 1; |
|
} |
|
|
|
/* 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 */ |
|
if (first_role != -1 || first_pair != -1) { |
|
int32_t start = first_role; |
|
if (first_pair != -1 && (start != -1 || first_pair < start)) { |
|
start = first_pair; |
|
} |
|
|
|
/* Total number of records can never be higher than |
|
* - number of regular (non-pair) ids + |
|
* - three records for pairs: (R,T), (R,*), (*,T) |
|
* - one wildcard (*), one any (_) and one pair wildcard (*,*) record |
|
* - one record for (ChildOf, 0) |
|
*/ |
|
int32_t flag_id_count = dst_count - start; |
|
int32_t record_count = start + 3 * flag_id_count + 3 + 1; |
|
ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_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]; |
|
if (!ECS_IS_PAIR(id)) { |
|
ecs_entity_t first = 0; |
|
ecs_entity_t second = 0; |
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
first = ECS_PAIR_FIRST(id); |
|
second = ECS_PAIR_SECOND(id); |
|
} else { |
|
first = id & ECS_COMPONENT_MASK; |
|
} |
|
if (first) { |
|
flecs_table_append_to_records(world, table, records, |
|
ecs_pair(EcsFlag, first), dst_i); |
|
} |
|
if (second) { |
|
flecs_table_append_to_records(world, table, records, |
|
ecs_pair(EcsFlag, second), dst_i); |
|
} |
|
} |
|
} |
|
} |
|
|
|
int32_t last_pair = -1; |
|
bool has_childof = table->flags & EcsTableHasChildOf; |
|
if (first_pair != -1) { |
|
/* Add a (Relationship, *) 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_IS_PAIR(dst_id)) { |
|
break; /* no more pairs */ |
|
} |
|
if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ |
|
tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); |
|
|
|
ecs_id_record_t *p_idr = (ecs_id_record_t*)tr->hdr.cache; |
|
r = ECS_PAIR_FIRST(dst_id); |
|
if (r == EcsChildOf) { |
|
childof_idr = p_idr; |
|
} |
|
|
|
idr = p_idr->parent; /* (R, *) */ |
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
tr = ecs_vec_append_t(a, records, ecs_table_record_t); |
|
tr->hdr.cache = (ecs_table_cache_t*)idr; |
|
tr->index = flecs_ito(int16_t, dst_i); |
|
tr->count = 0; |
|
} |
|
|
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
tr->count ++; |
|
} |
|
|
|
last_pair = dst_i; |
|
|
|
/* Add a (*, Target) record for each relationship target. Type |
|
* ids are sorted relationship-first, so we can't simply do a single linear |
|
* scan to find all occurrences for a target. */ |
|
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)); |
|
|
|
flecs_table_append_to_records( |
|
world, table, records, tgt_id, dst_i); |
|
} |
|
} |
|
|
|
/* Lastly, add records for all-wildcard ids */ |
|
if (last_id >= 0) { |
|
tr = ecs_vec_append_t(a, records, ecs_table_record_t); |
|
tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; |
|
tr->index = 0; |
|
tr->count = flecs_ito(int16_t, last_id + 1); |
|
} |
|
if (last_pair - first_pair) { |
|
tr = ecs_vec_append_t(a, records, ecs_table_record_t); |
|
tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; |
|
tr->index = flecs_ito(int16_t, first_pair); |
|
tr->count = flecs_ito(int16_t, last_pair - first_pair); |
|
} |
|
if (dst_count) { |
|
tr = ecs_vec_append_t(a, records, ecs_table_record_t); |
|
tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; |
|
tr->index = 0; |
|
tr->count = 1; |
|
} |
|
if (dst_count && !has_childof) { |
|
tr = ecs_vec_append_t(a, records, ecs_table_record_t); |
|
childof_idr = world->idr_childof_0; |
|
tr->hdr.cache = (ecs_table_cache_t*)childof_idr; |
|
tr->index = 0; |
|
tr->count = 1; |
|
} |
|
|
|
/* Now that all records have been added, copy them to array */ |
|
int32_t i, dst_record_count = ecs_vec_count(records); |
|
ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, |
|
dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); |
|
table->_->record_count = flecs_ito(int16_t, dst_record_count); |
|
table->_->records = dst_tr; |
|
int32_t column_count = 0; |
|
|
|
/* 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 (ecs_table_cache_get(&idr->cache, table)) { |
|
/* 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); |
|
} |
|
|
|
/* Claim id record so it stays alive as long as the table exists */ |
|
flecs_id_record_claim(world, idr); |
|
|
|
/* Initialize event flags */ |
|
table->flags |= idr->flags & EcsIdEventMask; |
|
|
|
/* Initialize column index (will be overwritten by init_columns) */ |
|
tr->column = -1; |
|
|
|
if (idr->flags & EcsIdAlwaysOverride) { |
|
table->flags |= EcsTableHasOverrides; |
|
} |
|
|
|
if ((i < table->type.count) && (idr->type_info != NULL)) { |
|
column_count ++; |
|
} |
|
} |
|
|
|
if (column_count) { |
|
table->column_map = flecs_walloc_n(world, int32_t, |
|
dst_count + column_count); |
|
} |
|
table->column_count = flecs_ito(int16_t, column_count); |
|
flecs_table_init_data(world, table); |
|
|
|
if (table->flags & EcsTableHasName) { |
|
ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
table->_->name_index = |
|
flecs_id_record_name_index_ensure(world, childof_idr); |
|
ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (table->flags & EcsTableHasOnTableCreate) { |
|
flecs_emit(world, world, &(ecs_event_desc_t) { |
|
.ids = &table->type, |
|
.event = EcsOnTableCreate, |
|
.table = table, |
|
.flags = EcsEventTableOnly, |
|
.observable = world |
|
}); |
|
} |
|
} |
|
|
|
/* Unregister table from id records */ |
|
static |
|
void flecs_table_records_unregister( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
uint64_t table_id = table->id; |
|
int32_t i, count = table->_->record_count; |
|
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 = ((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); |
|
ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, |
|
ECS_INTERNAL_ERROR, NULL); |
|
(void)id; |
|
|
|
ecs_table_cache_remove(cache, table_id, &tr->hdr); |
|
flecs_id_record_release(world, (ecs_id_record_t*)cache); |
|
} |
|
|
|
flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); |
|
} |
|
|
|
/* Keep track for what kind of builtin events observers are registered that can |
|
* potentially match the table. This allows code to early out of calling the |
|
* emit function that notifies observers. */ |
|
static |
|
void flecs_table_add_trigger_flags( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_entity_t event) |
|
{ |
|
(void)world; |
|
|
|
if (event == EcsOnAdd) { |
|
table->flags |= EcsTableHasOnAdd; |
|
} else if (event == EcsOnRemove) { |
|
table->flags |= EcsTableHasOnRemove; |
|
} else if (event == EcsOnSet) { |
|
table->flags |= EcsTableHasOnSet; |
|
} else if (event == EcsUnSet) { |
|
table->flags |= EcsTableHasUnSet; |
|
} else if (event == EcsOnTableFill) { |
|
table->flags |= EcsTableHasOnTableFill; |
|
} else if (event == EcsOnTableEmpty) { |
|
table->flags |= EcsTableHasOnTableEmpty; |
|
} |
|
} |
|
|
|
/* Invoke OnRemove observers for all entities in table. Useful during table |
|
* deletion or when clearing entities from a table. */ |
|
static |
|
void flecs_table_notify_on_remove( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data) |
|
{ |
|
int32_t count = data->entities.count; |
|
if (count) { |
|
flecs_notify_on_remove(world, table, NULL, 0, count, &table->type); |
|
} |
|
} |
|
|
|
/* Invoke type hook for entities in table */ |
|
static |
|
void flecs_table_invoke_hook( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_iter_action_t callback, |
|
ecs_entity_t event, |
|
ecs_column_t *column, |
|
ecs_entity_t *entities, |
|
int32_t row, |
|
int32_t count) |
|
{ |
|
void *ptr = ecs_vec_get(&column->data, column->size, row); |
|
flecs_invoke_hook(world, table, count, row, entities, ptr, column->id, |
|
column->ti, event, callback); |
|
} |
|
|
|
/* Construct components */ |
|
static |
|
void flecs_table_invoke_ctor( |
|
ecs_column_t *column, |
|
int32_t row, |
|
int32_t count) |
|
{ |
|
ecs_type_info_t *ti = column->ti; |
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_xtor_t ctor = ti->hooks.ctor; |
|
if (ctor) { |
|
void *ptr = ecs_vec_get(&column->data, column->size, row); |
|
ctor(ptr, count, ti); |
|
} |
|
} |
|
|
|
/* Destruct components */ |
|
static |
|
void flecs_table_invoke_dtor( |
|
ecs_column_t *column, |
|
int32_t row, |
|
int32_t count) |
|
{ |
|
ecs_type_info_t *ti = column->ti; |
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_xtor_t dtor = ti->hooks.dtor; |
|
if (dtor) { |
|
void *ptr = ecs_vec_get(&column->data, column->size, row); |
|
dtor(ptr, count, ti); |
|
} |
|
} |
|
|
|
/* Run hooks that get invoked when component is added to entity */ |
|
static |
|
void flecs_table_invoke_add_hooks( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_column_t *column, |
|
ecs_entity_t *entities, |
|
int32_t row, |
|
int32_t count, |
|
bool construct) |
|
{ |
|
ecs_type_info_t *ti = column->ti; |
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (construct) { |
|
flecs_table_invoke_ctor(column, row, count); |
|
} |
|
|
|
ecs_iter_action_t on_add = ti->hooks.on_add; |
|
if (on_add) { |
|
flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, |
|
entities, row, count); |
|
} |
|
} |
|
|
|
/* Run hooks that get invoked when component is removed from entity */ |
|
static |
|
void flecs_table_invoke_remove_hooks( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_column_t *column, |
|
ecs_entity_t *entities, |
|
int32_t row, |
|
int32_t count, |
|
bool dtor) |
|
{ |
|
ecs_type_info_t *ti = column->ti; |
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_iter_action_t on_remove = ti->hooks.on_remove; |
|
if (on_remove) { |
|
flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, |
|
entities, row, count); |
|
} |
|
|
|
if (dtor) { |
|
flecs_table_invoke_dtor(column, row, count); |
|
} |
|
} |
|
|
|
/* Destruct all components and/or delete all entities in table in range */ |
|
static |
|
void flecs_table_dtor_all( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data, |
|
int32_t row, |
|
int32_t count, |
|
bool update_entity_index, |
|
bool is_delete) |
|
{ |
|
/* Can't delete and not update the entity index */ |
|
ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t ids_count = table->column_count; |
|
ecs_entity_t *entities = data->entities.array; |
|
int32_t i, c, end = row + count; |
|
|
|
if (is_delete && table->_->traversable_count) { |
|
/* If table contains monitored entities with traversable relationships, |
|
* make sure to invalidate observer cache */ |
|
flecs_emit_propagate_invalidate(world, table, row, count); |
|
} |
|
|
|
/* If table has components with destructors, iterate component columns */ |
|
if (table->flags & EcsTableHasDtors) { |
|
/* Throw up a lock just to be sure */ |
|
table->_->lock = true; |
|
|
|
/* Run on_remove callbacks first before destructing components */ |
|
for (c = 0; c < ids_count; c++) { |
|
ecs_column_t *column = &data->columns[c]; |
|
ecs_iter_action_t on_remove = column->ti->hooks.on_remove; |
|
if (on_remove) { |
|
flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, |
|
column, &entities[row], row, count); |
|
} |
|
} |
|
|
|
/* Destruct components */ |
|
for (c = 0; c < ids_count; c++) { |
|
flecs_table_invoke_dtor(&data->columns[c], row, count); |
|
} |
|
|
|
/* Iterate entities first, then components. This ensures that only one |
|
* entity is invalidated at a time, which ensures that destructors can |
|
* safely access other entities. */ |
|
for (i = row; i < end; i ++) { |
|
/* Update entity index after invoking destructors so that entity can |
|
* be safely used in destructor callbacks. */ |
|
if (update_entity_index) { |
|
ecs_entity_t e = entities[i]; |
|
ecs_assert(!e || ecs_is_valid(world, e), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (is_delete) { |
|
flecs_entities_remove(world, e); |
|
ecs_assert(ecs_is_valid(world, e) == false, |
|
ECS_INTERNAL_ERROR, NULL); |
|
} else { |
|
// If this is not a delete, clear the entity index record |
|
ecs_record_t *record = flecs_entities_get(world, e); |
|
record->table = NULL; |
|
record->row = 0; |
|
} |
|
} else { |
|
/* This should only happen in rare cases, such as when the data |
|
* cleaned up is not part of the world (like with snapshots) */ |
|
} |
|
} |
|
|
|
table->_->lock = false; |
|
|
|
/* If table does not have destructors, just update entity index */ |
|
} else if (update_entity_index) { |
|
if (is_delete) { |
|
for (i = row; i < end; i ++) { |
|
ecs_entity_t e = entities[i]; |
|
ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); |
|
flecs_entities_remove(world, e); |
|
ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} else { |
|
for (i = row; i < end; i ++) { |
|
ecs_entity_t e = entities[i]; |
|
ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); |
|
ecs_record_t *record = flecs_entities_get(world, e); |
|
record->table = NULL; |
|
record->row = record->row & ECS_ROW_FLAGS_MASK; |
|
(void)e; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Cleanup table storage */ |
|
static |
|
void flecs_table_fini_data( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data, |
|
bool do_on_remove, |
|
bool update_entity_index, |
|
bool is_delete, |
|
bool deactivate) |
|
{ |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
|
|
if (!data) { |
|
return; |
|
} |
|
|
|
if (do_on_remove) { |
|
flecs_table_notify_on_remove(world, table, data); |
|
} |
|
|
|
int32_t count = flecs_table_data_count(data); |
|
if (count) { |
|
flecs_table_dtor_all(world, table, data, 0, count, |
|
update_entity_index, is_delete); |
|
} |
|
|
|
ecs_column_t *columns = data->columns; |
|
if (columns) { |
|
int32_t c, column_count = table->column_count; |
|
for (c = 0; c < column_count; c ++) { |
|
/* Sanity check */ |
|
ecs_assert(columns[c].data.count == data->entities.count, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_vec_fini(&world->allocator, |
|
&columns[c].data, columns[c].size); |
|
} |
|
flecs_wfree_n(world, ecs_column_t, column_count, columns); |
|
data->columns = NULL; |
|
} |
|
|
|
ecs_table__t *meta = table->_; |
|
ecs_switch_t *sw_columns = meta->sw_columns; |
|
if (sw_columns) { |
|
int32_t c, column_count = meta->sw_count; |
|
for (c = 0; c < column_count; c ++) { |
|
flecs_switch_fini(&sw_columns[c]); |
|
} |
|
flecs_wfree_n(world, ecs_switch_t, column_count, sw_columns); |
|
meta->sw_columns = NULL; |
|
} |
|
|
|
ecs_bitset_t *bs_columns = meta->bs_columns; |
|
if (bs_columns) { |
|
int32_t c, column_count = meta->bs_count; |
|
for (c = 0; c < column_count; c ++) { |
|
flecs_bitset_fini(&bs_columns[c]); |
|
} |
|
flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); |
|
meta->bs_columns = NULL; |
|
} |
|
|
|
ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); |
|
|
|
if (deactivate && count) { |
|
flecs_table_set_empty(world, table); |
|
} |
|
|
|
table->_->traversable_count = 0; |
|
table->flags &= ~EcsTableHasTraversable; |
|
} |
|
|
|
ecs_vec_t* flecs_table_entities( |
|
ecs_table_t *table) |
|
{ |
|
return &table->data.entities; |
|
} |
|
|
|
ecs_entity_t* flecs_table_entities_array( |
|
ecs_table_t *table) |
|
{ |
|
return ecs_vec_first(flecs_table_entities(table)); |
|
} |
|
|
|
/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ |
|
void flecs_table_clear_data( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data) |
|
{ |
|
flecs_table_fini_data(world, table, data, false, false, false, false); |
|
} |
|
|
|
/* Cleanup, no OnRemove, clear entity index, deactivate table */ |
|
void flecs_table_clear_entities_silent( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
flecs_table_fini_data(world, table, &table->data, false, true, false, true); |
|
} |
|
|
|
/* Cleanup, run OnRemove, clear entity index, deactivate table */ |
|
void flecs_table_clear_entities( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
flecs_table_fini_data(world, table, &table->data, true, true, false, true); |
|
} |
|
|
|
/* Cleanup, run OnRemove, delete from entity index, deactivate table */ |
|
void flecs_table_delete_entities( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
flecs_table_fini_data(world, table, &table->data, true, true, true, true); |
|
} |
|
|
|
/* Unset all components in table. This function is called before a table is |
|
* deleted, and invokes all UnSet handlers, if any */ |
|
void flecs_table_remove_actions( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
(void)world; |
|
flecs_table_notify_on_remove(world, table, &table->data); |
|
} |
|
|
|
/* Free table resources. */ |
|
void flecs_table_free( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
bool is_root = table == &world->store.root; |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), |
|
ECS_INTERNAL_ERROR, NULL); |
|
(void)world; |
|
|
|
if (!is_root && !(world->flags & EcsWorldQuit)) { |
|
if (table->flags & EcsTableHasOnTableDelete) { |
|
flecs_emit(world, world, &(ecs_event_desc_t) { |
|
.ids = &table->type, |
|
.event = EcsOnTableDelete, |
|
.table = table, |
|
.flags = EcsEventTableOnly, |
|
.observable = world |
|
}); |
|
} |
|
} |
|
|
|
if (ecs_should_log_2()) { |
|
char *expr = ecs_type_str(world, &table->type); |
|
ecs_dbg_2( |
|
"#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", |
|
expr, table->id); |
|
ecs_os_free(expr); |
|
ecs_log_push_2(); |
|
} |
|
|
|
world->info.empty_table_count -= (ecs_table_count(table) == 0); |
|
|
|
/* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ |
|
flecs_table_fini_data(world, table, &table->data, false, true, true, false); |
|
flecs_table_clear_edges(world, table); |
|
|
|
if (!is_root) { |
|
ecs_type_t ids = { |
|
.array = table->type.array, |
|
.count = table->type.count |
|
}; |
|
|
|
flecs_hashmap_remove_w_hash( |
|
&world->store.table_map, &ids, ecs_table_t*, table->_->hash); |
|
} |
|
|
|
flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); |
|
flecs_wfree_n(world, int32_t, table->column_count + table->type.count, |
|
table->column_map); |
|
flecs_table_records_unregister(world, table); |
|
|
|
/* Update counters */ |
|
world->info.table_count --; |
|
world->info.table_delete_total ++; |
|
|
|
flecs_free_t(&world->allocator, ecs_table__t, table->_); |
|
|
|
if (!(world->flags & EcsWorldFini)) { |
|
ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); |
|
flecs_table_free_type(world, table); |
|
flecs_sparse_remove_t(&world->store.tables, ecs_table_t, table->id); |
|
} |
|
|
|
ecs_log_pop_2(); |
|
} |
|
|
|
/* Free table type. Do this separately from freeing the table as types can be |
|
* in use by application destructors. */ |
|
void flecs_table_free_type( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); |
|
} |
|
|
|
/* Reset a table to its initial state. */ |
|
void flecs_table_reset( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
flecs_table_clear_edges(world, table); |
|
} |
|
|
|
/* Keep track of number of traversable entities in table. A traversable entity |
|
* is an entity used as target in a pair with a traversable relationship. The |
|
* traversable count and flag are used by code to early out of mechanisms like |
|
* event propagation and recursive cleanup. */ |
|
void flecs_table_traversable_add( |
|
ecs_table_t *table, |
|
int32_t value) |
|
{ |
|
int32_t result = table->_->traversable_count += value; |
|
ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); |
|
if (result == 0) { |
|
table->flags &= ~EcsTableHasTraversable; |
|
} else if (result == value) { |
|
table->flags |= EcsTableHasTraversable; |
|
} |
|
} |
|
|
|
/* Mark table column dirty. This usually happens as the result of a set |
|
* operation, or iteration of a query with [out] fields. */ |
|
static |
|
void flecs_table_mark_table_dirty( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t index) |
|
{ |
|
(void)world; |
|
if (table->dirty_state) { |
|
table->dirty_state[index] ++; |
|
} |
|
} |
|
|
|
/* Mark table component dirty */ |
|
void flecs_table_mark_dirty( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_entity_t component) |
|
{ |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (table->dirty_state) { |
|
ecs_id_record_t *idr = flecs_id_record_get(world, component); |
|
if (!idr) { |
|
return; |
|
} |
|
|
|
const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (!tr || tr->column == -1) { |
|
return; |
|
} |
|
|
|
table->dirty_state[tr->column + 1] ++; |
|
} |
|
} |
|
|
|
/* Get (or create) dirty state of table. Used by queries for change tracking */ |
|
int32_t* flecs_table_get_dirty_state( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!table->dirty_state) { |
|
int32_t column_count = table->column_count; |
|
table->dirty_state = flecs_alloc_n(&world->allocator, |
|
int32_t, column_count + 1); |
|
ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); |
|
for (int i = 0; i < column_count + 1; i ++) { |
|
table->dirty_state[i] = 1; |
|
} |
|
} |
|
return table->dirty_state; |
|
} |
|
|
|
/* Table move logic for switch (union relationship) column */ |
|
static |
|
void flecs_table_move_switch_columns( |
|
ecs_table_t *dst_table, |
|
int32_t dst_index, |
|
ecs_table_t *src_table, |
|
int32_t src_index, |
|
int32_t count, |
|
bool clear) |
|
{ |
|
ecs_table__t *dst_meta = dst_table->_; |
|
ecs_table__t *src_meta = src_table->_; |
|
if (!dst_meta && !src_meta) { |
|
return; |
|
} |
|
|
|
int32_t i_old = 0, src_column_count = src_meta ? src_meta->sw_count : 0; |
|
int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->sw_count : 0; |
|
if (!src_column_count && !dst_column_count) { |
|
return; |
|
} |
|
|
|
ecs_switch_t *src_columns = src_meta ? src_meta->sw_columns : NULL; |
|
ecs_switch_t *dst_columns = dst_meta ? dst_meta->sw_columns : NULL; |
|
|
|
ecs_type_t dst_type = dst_table->type; |
|
ecs_type_t src_type = src_table->type; |
|
|
|
int32_t offset_new = dst_meta ? dst_meta->sw_offset : 0; |
|
int32_t offset_old = src_meta ? src_meta->sw_offset : 0; |
|
|
|
ecs_id_t *dst_ids = dst_type.array; |
|
ecs_id_t *src_ids = src_type.array; |
|
|
|
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]; |
|
ecs_switch_t *dst_switch = &dst_columns[i_new]; |
|
|
|
flecs_switch_ensure(dst_switch, dst_index + count); |
|
|
|
int i; |
|
for (i = 0; i < count; i ++) { |
|
uint64_t value = flecs_switch_get(src_switch, src_index + i); |
|
flecs_switch_set(dst_switch, dst_index + i, value); |
|
} |
|
|
|
if (clear) { |
|
ecs_assert(count == flecs_switch_count(src_switch), |
|
ECS_INTERNAL_ERROR, NULL); |
|
flecs_switch_clear(src_switch); |
|
} |
|
} else if (dst_id > src_id) { |
|
ecs_switch_t *src_switch = &src_columns[i_old]; |
|
flecs_switch_clear(src_switch); |
|
} |
|
|
|
i_new += dst_id <= src_id; |
|
i_old += dst_id >= src_id; |
|
} |
|
|
|
/* Clear remaining columns */ |
|
if (clear) { |
|
for (; (i_old < src_column_count); i_old ++) { |
|
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); |
|
} |
|
} |
|
} |
|
|
|
/* Table move logic for bitset (toggle component) column */ |
|
static |
|
void flecs_table_move_bitset_columns( |
|
ecs_table_t *dst_table, |
|
int32_t dst_index, |
|
ecs_table_t *src_table, |
|
int32_t src_index, |
|
int32_t count, |
|
bool clear) |
|
{ |
|
ecs_table__t *dst_meta = dst_table->_; |
|
ecs_table__t *src_meta = src_table->_; |
|
if (!dst_meta && !src_meta) { |
|
return; |
|
} |
|
|
|
int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; |
|
int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; |
|
|
|
if (!src_column_count && !dst_column_count) { |
|
return; |
|
} |
|
|
|
ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; |
|
ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; |
|
|
|
ecs_type_t dst_type = dst_table->type; |
|
ecs_type_t src_type = src_table->type; |
|
|
|
int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; |
|
int32_t offset_old = src_meta ? src_meta->bs_offset : 0; |
|
|
|
ecs_id_t *dst_ids = dst_type.array; |
|
ecs_id_t *src_ids = src_type.array; |
|
|
|
for (; (i_new < dst_column_count) && (i_old < src_column_count);) { |
|
ecs_id_t dst_id = dst_ids[i_new + offset_new]; |
|
ecs_id_t src_id = src_ids[i_old + offset_old]; |
|
|
|
if (dst_id == src_id) { |
|
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); |
|
|
|
int i; |
|
for (i = 0; i < count; i ++) { |
|
uint64_t value = flecs_bitset_get(src_bs, src_index + i); |
|
flecs_bitset_set(dst_bs, dst_index + i, value); |
|
} |
|
|
|
if (clear) { |
|
ecs_assert(count == flecs_bitset_count(src_bs), |
|
ECS_INTERNAL_ERROR, NULL); |
|
flecs_bitset_fini(src_bs); |
|
} |
|
} else if (dst_id > src_id) { |
|
ecs_bitset_t *src_bs = &src_columns[i_old]; |
|
flecs_bitset_fini(src_bs); |
|
} |
|
|
|
i_new += dst_id <= src_id; |
|
i_old += dst_id >= src_id; |
|
} |
|
|
|
/* Clear remaining columns */ |
|
if (clear) { |
|
for (; (i_old < src_column_count); i_old ++) { |
|
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); |
|
} |
|
} |
|
} |
|
|
|
/* Grow table column. When a column needs to be reallocated this function takes |
|
* care of correctly invoking ctor/move/dtor hooks. */ |
|
static |
|
void flecs_table_grow_column( |
|
ecs_world_t *world, |
|
ecs_column_t *column, |
|
int32_t to_add, |
|
int32_t dst_size, |
|
bool construct) |
|
{ |
|
ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_type_info_t *ti = column->ti; |
|
int32_t size = column->size; |
|
int32_t count = column->data.count; |
|
int32_t src_size = column->data.size; |
|
int32_t dst_count = count + to_add; |
|
bool can_realloc = dst_size != src_size; |
|
void *result = NULL; |
|
|
|
ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* 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->hooks.ctor_move_dtor)) { |
|
ecs_xtor_t ctor = ti->hooks.ctor; |
|
ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Create vector */ |
|
ecs_vec_t dst; |
|
ecs_vec_init(&world->allocator, &dst, size, dst_size); |
|
dst.count = dst_count; |
|
|
|
void *src_buffer = column->data.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_ELEM(dst_buffer, size, count); |
|
ctor(result, to_add, ti); |
|
} |
|
|
|
/* Free old vector */ |
|
ecs_vec_fini(&world->allocator, &column->data, size); |
|
|
|
column->data = dst; |
|
} else { |
|
/* If array won't realloc or has no move, simply add new elements */ |
|
if (can_realloc) { |
|
ecs_vec_set_size(&world->allocator, &column->data, size, dst_size); |
|
} |
|
|
|
result = ecs_vec_grow(&world->allocator, &column->data, size, to_add); |
|
|
|
ecs_xtor_t ctor; |
|
if (construct && (ctor = ti->hooks.ctor)) { |
|
/* If new elements need to be constructed and component has a |
|
* constructor, construct */ |
|
ctor(result, to_add, ti); |
|
} |
|
} |
|
|
|
ecs_assert(column->data.size == dst_size, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
/* Grow all data structures in a table */ |
|
static |
|
int32_t flecs_table_grow_data( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data, |
|
int32_t to_add, |
|
int32_t size, |
|
const ecs_entity_t *ids) |
|
{ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t cur_count = flecs_table_data_count(data); |
|
int32_t column_count = table->column_count; |
|
|
|
/* Add entity to column with entity ids */ |
|
ecs_vec_set_size_t(&world->allocator, &data->entities, ecs_entity_t, size); |
|
ecs_entity_t *e = ecs_vec_last_t(&data->entities, ecs_entity_t) + 1; |
|
data->entities.count += to_add; |
|
if (data->entities.size > size) { |
|
size = data->entities.size; |
|
} |
|
|
|
/* Initialize entity ids and record ptrs */ |
|
int32_t i; |
|
if (ids) { |
|
ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); |
|
} else { |
|
ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); |
|
} |
|
|
|
/* Add elements to each column array */ |
|
ecs_column_t *columns = data->columns; |
|
for (i = 0; i < column_count; i ++) { |
|
flecs_table_grow_column(world, &columns[i], to_add, size, true); |
|
ecs_assert(columns[i].data.size == size, ECS_INTERNAL_ERROR, NULL); |
|
flecs_table_invoke_add_hooks(world, table, &columns[i], e, |
|
cur_count, to_add, false); |
|
} |
|
|
|
ecs_table__t *meta = table->_; |
|
int32_t sw_count = meta->sw_count; |
|
int32_t bs_count = meta->bs_count; |
|
ecs_switch_t *sw_columns = meta->sw_columns; |
|
ecs_bitset_t *bs_columns = meta->bs_columns; |
|
|
|
/* Add elements to each switch column */ |
|
for (i = 0; i < sw_count; i ++) { |
|
ecs_switch_t *sw = &sw_columns[i]; |
|
flecs_switch_addn(sw, to_add); |
|
} |
|
|
|
/* Add elements to each bitset column */ |
|
for (i = 0; i < bs_count; i ++) { |
|
ecs_bitset_t *bs = &bs_columns[i]; |
|
flecs_bitset_addn(bs, to_add); |
|
} |
|
|
|
/* If the table is monitored indicate that there has been a change */ |
|
flecs_table_mark_table_dirty(world, table, 0); |
|
|
|
if (!(world->flags & EcsWorldReadonly) && !cur_count) { |
|
flecs_table_set_empty(world, table); |
|
} |
|
|
|
/* Return index of first added entity */ |
|
return cur_count; |
|
} |
|
|
|
/* Append operation for tables that don't have any complex logic */ |
|
static |
|
void flecs_table_fast_append( |
|
ecs_world_t *world, |
|
ecs_column_t *columns, |
|
int32_t count) |
|
{ |
|
/* Add elements to each column array */ |
|
int32_t i; |
|
for (i = 0; i < count; i ++) { |
|
ecs_column_t *column = &columns[i]; |
|
ecs_vec_append(&world->allocator, &column->data, column->size); |
|
} |
|
} |
|
|
|
/* Append entity to table */ |
|
int32_t flecs_table_append( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_entity_t entity, |
|
bool construct, |
|
bool on_add) |
|
{ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
ecs_assert(!(table->flags & EcsTableHasTarget), |
|
ECS_INVALID_OPERATION, NULL); |
|
|
|
flecs_table_check_sanity(table); |
|
|
|
/* Get count & size before growing entities array. This tells us whether the |
|
* arrays will realloc */ |
|
ecs_data_t *data = &table->data; |
|
int32_t count = data->entities.count; |
|
int32_t column_count = table->column_count; |
|
ecs_column_t *columns = table->data.columns; |
|
|
|
/* Grow buffer with entity ids, set new element to new entity */ |
|
ecs_entity_t *e = ecs_vec_append_t(&world->allocator, |
|
&data->entities, ecs_entity_t); |
|
ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); |
|
*e = entity; |
|
|
|
/* If the table is monitored indicate that there has been a change */ |
|
flecs_table_mark_table_dirty(world, table, 0); |
|
ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Fast path: no switch columns, no lifecycle actions */ |
|
if (!(table->flags & EcsTableIsComplex)) { |
|
flecs_table_fast_append(world, columns, column_count); |
|
if (!count) { |
|
flecs_table_set_empty(world, table); /* See below */ |
|
} |
|
return count; |
|
} |
|
|
|
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. */ |
|
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]; |
|
flecs_table_grow_column(world, column, 1, size, construct); |
|
|
|
ecs_iter_action_t on_add_hook; |
|
if (on_add && (on_add_hook = column->ti->hooks.on_add)) { |
|
flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, |
|
&entities[count], count, 1); |
|
} |
|
|
|
ecs_assert(columns[i].data.size == |
|
data->entities.size, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(columns[i].data.count == |
|
data->entities.count, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
ecs_table__t *meta = table->_; |
|
int32_t sw_count = meta->sw_count; |
|
int32_t bs_count = meta->bs_count; |
|
ecs_switch_t *sw_columns = meta->sw_columns; |
|
ecs_bitset_t *bs_columns = meta->bs_columns; |
|
|
|
/* Add element to each switch column */ |
|
for (i = 0; i < sw_count; i ++) { |
|
ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_switch_t *sw = &sw_columns[i]; |
|
flecs_switch_add(sw); |
|
} |
|
|
|
/* Add element to each bitset column */ |
|
for (i = 0; i < bs_count; i ++) { |
|
ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_bitset_t *bs = &bs_columns[i]; |
|
flecs_bitset_addn(bs, 1); |
|
} |
|
|
|
/* If this is the first entity in this table, signal queries so that the |
|
* table moves from an inactive table to an active table. */ |
|
if (!count) { |
|
flecs_table_set_empty(world, table); |
|
} |
|
|
|
flecs_table_check_sanity(table); |
|
|
|
return count; |
|
} |
|
|
|
/* Delete last operation for tables that don't have any complex logic */ |
|
static |
|
void flecs_table_fast_delete_last( |
|
ecs_column_t *columns, |
|
int32_t column_count) |
|
{ |
|
int i; |
|
for (i = 0; i < column_count; i ++) { |
|
ecs_vec_remove_last(&columns[i].data); |
|
} |
|
} |
|
|
|
/* Delete operation for tables that don't have any complex logic */ |
|
static |
|
void flecs_table_fast_delete( |
|
ecs_column_t *columns, |
|
int32_t column_count, |
|
int32_t index) |
|
{ |
|
int i; |
|
for (i = 0; i < column_count; i ++) { |
|
ecs_column_t *column = &columns[i]; |
|
ecs_vec_remove(&column->data, column->size, index); |
|
} |
|
} |
|
|
|
/* Delete entity from table */ |
|
void flecs_table_delete( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t index, |
|
bool destruct) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
ecs_assert(!(table->flags & EcsTableHasTarget), |
|
ECS_INVALID_OPERATION, NULL); |
|
|
|
flecs_table_check_sanity(table); |
|
|
|
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 = 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_vec_remove_last(&data->entities); |
|
|
|
/* Update record of moved entity in entity index */ |
|
if (index != count) { |
|
ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); |
|
if (record_to_move) { |
|
uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; |
|
record_to_move->row = ECS_ROW_TO_RECORD(index, row_flags); |
|
ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
/* If the table is monitored indicate that there has been a change */ |
|
flecs_table_mark_table_dirty(world, table, 0); |
|
|
|
/* If table is empty, deactivate it */ |
|
if (!count) { |
|
flecs_table_set_empty(world, table); |
|
} |
|
|
|
/* Destruct component data */ |
|
ecs_column_t *columns = data->columns; |
|
int32_t column_count = table->column_count; |
|
int32_t i; |
|
|
|
/* If this is a table without lifecycle callbacks or special columns, take |
|
* fast path that just remove an element from the array(s) */ |
|
if (!(table->flags & EcsTableIsComplex)) { |
|
if (index == count) { |
|
flecs_table_fast_delete_last(columns, column_count); |
|
} else { |
|
flecs_table_fast_delete(columns, column_count, index); |
|
} |
|
|
|
flecs_table_check_sanity(table); |
|
return; |
|
} |
|
|
|
/* Last element, destruct & remove */ |
|
if (index == count) { |
|
/* If table has component destructors, invoke */ |
|
if (destruct && (table->flags & EcsTableHasDtors)) { |
|
for (i = 0; i < column_count; i ++) { |
|
flecs_table_invoke_remove_hooks(world, table, &columns[i], |
|
&entity_to_delete, index, 1, true); |
|
} |
|
} |
|
|
|
flecs_table_fast_delete_last(columns, column_count); |
|
|
|
/* Not last element, move last element to deleted element & destruct */ |
|
} else { |
|
/* If table has component destructors, invoke */ |
|
if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { |
|
for (i = 0; i < column_count; i ++) { |
|
ecs_column_t *column = &columns[i]; |
|
ecs_type_info_t *ti = column->ti; |
|
ecs_size_t size = column->size; |
|
void *dst = ecs_vec_get(&column->data, size, index); |
|
void *src = ecs_vec_last(&column->data, size); |
|
|
|
ecs_iter_action_t on_remove = ti->hooks.on_remove; |
|
if (destruct && on_remove) { |
|
flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, |
|
column, &entity_to_delete, index, 1); |
|
} |
|
|
|
ecs_move_t move_dtor = ti->hooks.move_dtor; |
|
if (move_dtor) { |
|
move_dtor(dst, src, 1, ti); |
|
} else { |
|
ecs_os_memcpy(dst, src, size); |
|
} |
|
|
|
ecs_vec_remove_last(&column->data); |
|
} |
|
} else { |
|
flecs_table_fast_delete(columns, column_count, index); |
|
} |
|
} |
|
|
|
/* Remove elements from switch columns */ |
|
ecs_table__t *meta = table->_; |
|
ecs_switch_t *sw_columns = meta->sw_columns; |
|
int32_t sw_count = meta->sw_count; |
|
for (i = 0; i < sw_count; i ++) { |
|
flecs_switch_remove(&sw_columns[i], index); |
|
} |
|
|
|
/* Remove elements from bitset columns */ |
|
ecs_bitset_t *bs_columns = meta->bs_columns; |
|
int32_t bs_count = meta->bs_count; |
|
for (i = 0; i < bs_count; i ++) { |
|
flecs_bitset_remove(&bs_columns[i], index); |
|
} |
|
|
|
flecs_table_check_sanity(table); |
|
} |
|
|
|
/* Move operation for tables that don't have any complex logic */ |
|
static |
|
void flecs_table_fast_move( |
|
ecs_table_t *dst_table, |
|
int32_t dst_index, |
|
ecs_table_t *src_table, |
|
int32_t src_index) |
|
{ |
|
int32_t i_new = 0, dst_column_count = dst_table->column_count; |
|
int32_t i_old = 0, src_column_count = src_table->column_count; |
|
|
|
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_column_t *dst_column = &dst_columns[i_new]; |
|
ecs_column_t *src_column = &src_columns[i_old]; |
|
ecs_id_t dst_id = dst_column->id; |
|
ecs_id_t src_id = src_column->id; |
|
|
|
if (dst_id == src_id) { |
|
int32_t size = dst_column->size; |
|
void *dst = ecs_vec_get(&dst_column->data, size, dst_index); |
|
void *src = ecs_vec_get(&src_column->data, size, src_index); |
|
ecs_os_memcpy(dst, src, size); |
|
} |
|
|
|
i_new += dst_id <= src_id; |
|
i_old += dst_id >= src_id; |
|
} |
|
} |
|
|
|
/* Move entity from src to dst table */ |
|
void flecs_table_move( |
|
ecs_world_t *world, |
|
ecs_entity_t dst_entity, |
|
ecs_entity_t src_entity, |
|
ecs_table_t *dst_table, |
|
int32_t dst_index, |
|
ecs_table_t *src_table, |
|
int32_t src_index, |
|
bool construct) |
|
{ |
|
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
|
|
ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_table_check_sanity(dst_table); |
|
flecs_table_check_sanity(src_table); |
|
|
|
if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { |
|
flecs_table_fast_move(dst_table, dst_index, src_table, src_index); |
|
flecs_table_check_sanity(dst_table); |
|
flecs_table_check_sanity(src_table); |
|
return; |
|
} |
|
|
|
flecs_table_move_switch_columns(dst_table, dst_index, src_table, src_index, 1, false); |
|
flecs_table_move_bitset_columns(dst_table, dst_index, src_table, src_index, 1, false); |
|
|
|
/* If the source and destination entities are the same, move component |
|
* between tables. If the entities are not the same (like when cloning) use |
|
* a copy. */ |
|
bool same_entity = dst_entity == src_entity; |
|
|
|
/* Call move_dtor for moved away from storage only if the entity is at the |
|
* last index in the source table. If it isn't the last entity, the last |
|
* entity in the table will be moved to the src storage, which will take |
|
* care of cleaning up resources. */ |
|
bool use_move_dtor = ecs_table_count(src_table) == (src_index + 1); |
|
|
|
int32_t i_new = 0, dst_column_count = dst_table->column_count; |
|
int32_t i_old = 0, src_column_count = src_table->column_count; |
|
|
|
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_column_t *dst_column = &dst_columns[i_new]; |
|
ecs_column_t *src_column = &src_columns[i_old]; |
|
ecs_id_t dst_id = dst_column->id; |
|
ecs_id_t src_id = src_column->id; |
|
|
|
if (dst_id == src_id) { |
|
int32_t size = dst_column->size; |
|
|
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); |
|
void *dst = ecs_vec_get(&dst_column->data, size, dst_index); |
|
void *src = ecs_vec_get(&src_column->data, size, src_index); |
|
ecs_type_info_t *ti = dst_column->ti; |
|
|
|
if (same_entity) { |
|
ecs_move_t move = ti->hooks.move_ctor; |
|
if (use_move_dtor || !move) { |
|
/* Also use move_dtor if component doesn't have a move_ctor |
|
* registered, to ensure that the dtor gets called to |
|
* cleanup resources. */ |
|
move = ti->hooks.ctor_move_dtor; |
|
} |
|
|
|
if (move) { |
|
move(dst, src, 1, ti); |
|
} else { |
|
ecs_os_memcpy(dst, src, size); |
|
} |
|
} else { |
|
ecs_copy_t copy = ti->hooks.copy_ctor; |
|
if (copy) { |
|
copy(dst, src, 1, ti); |
|
} else { |
|
ecs_os_memcpy(dst, src, size); |
|
} |
|
} |
|
} else { |
|
if (dst_id < src_id) { |
|
flecs_table_invoke_add_hooks(world, dst_table, |
|
dst_column, &dst_entity, dst_index, 1, construct); |
|
} else { |
|
flecs_table_invoke_remove_hooks(world, src_table, |
|
src_column, &src_entity, src_index, 1, use_move_dtor); |
|
} |
|
} |
|
|
|
i_new += dst_id <= src_id; |
|
i_old += dst_id >= src_id; |
|
} |
|
|
|
for (; (i_new < dst_column_count); i_new ++) { |
|
flecs_table_invoke_add_hooks(world, dst_table, &dst_columns[i_new], |
|
&dst_entity, dst_index, 1, construct); |
|
} |
|
|
|
for (; (i_old < src_column_count); i_old ++) { |
|
flecs_table_invoke_remove_hooks(world, src_table, &src_columns[i_old], |
|
&src_entity, src_index, 1, use_move_dtor); |
|
} |
|
|
|
flecs_table_check_sanity(dst_table); |
|
flecs_table_check_sanity(src_table); |
|
} |
|
|
|
/* Append n entities to table */ |
|
int32_t flecs_table_appendn( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data, |
|
int32_t to_add, |
|
const ecs_entity_t *ids) |
|
{ |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
|
|
flecs_table_check_sanity(table); |
|
int32_t cur_count = flecs_table_data_count(data); |
|
int32_t result = flecs_table_grow_data( |
|
world, table, data, to_add, cur_count + to_add, ids); |
|
flecs_table_check_sanity(table); |
|
|
|
return result; |
|
} |
|
|
|
/* Set allocated table size */ |
|
void flecs_table_set_size( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data, |
|
int32_t size) |
|
{ |
|
ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
|
|
flecs_table_check_sanity(table); |
|
|
|
int32_t cur_count = flecs_table_data_count(data); |
|
|
|
if (cur_count < size) { |
|
flecs_table_grow_data(world, table, data, 0, size, NULL); |
|
flecs_table_check_sanity(table); |
|
} |
|
} |
|
|
|
/* Shrink table storage to fit number of entities */ |
|
bool flecs_table_shrink( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
(void)world; |
|
|
|
flecs_table_check_sanity(table); |
|
|
|
ecs_data_t *data = &table->data; |
|
bool has_payload = data->entities.array != NULL; |
|
ecs_vec_reclaim_t(&world->allocator, &data->entities, ecs_entity_t); |
|
|
|
int32_t i, count = table->column_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_column_t *column = &data->columns[i]; |
|
ecs_vec_reclaim(&world->allocator, &column->data, column->size); |
|
} |
|
|
|
return has_payload; |
|
} |
|
|
|
/* Return number of entities in table */ |
|
int32_t flecs_table_data_count( |
|
const ecs_data_t *data) |
|
{ |
|
return data ? data->entities.count : 0; |
|
} |
|
|
|
/* Swap operation for switch (union relationship) columns */ |
|
static |
|
void flecs_table_swap_switch_columns( |
|
ecs_table_t *table, |
|
int32_t row_1, |
|
int32_t row_2) |
|
{ |
|
int32_t i = 0, column_count = table->_->sw_count; |
|
if (!column_count) { |
|
return; |
|
} |
|
|
|
ecs_switch_t *columns = table->_->sw_columns; |
|
|
|
for (i = 0; i < column_count; i ++) { |
|
ecs_switch_t *sw = &columns[i]; |
|
flecs_switch_swap(sw, row_1, row_2); |
|
} |
|
} |
|
|
|
/* Swap operation for bitset (toggle component) columns */ |
|
static |
|
void flecs_table_swap_bitset_columns( |
|
ecs_table_t *table, |
|
int32_t row_1, |
|
int32_t row_2) |
|
{ |
|
int32_t i = 0, column_count = table->_->bs_count; |
|
if (!column_count) { |
|
return; |
|
} |
|
|
|
ecs_bitset_t *columns = table->_->bs_columns; |
|
|
|
for (i = 0; i < column_count; i ++) { |
|
ecs_bitset_t *bs = &columns[i]; |
|
flecs_bitset_swap(bs, row_1, row_2); |
|
} |
|
} |
|
|
|
/* Swap two rows in a table. Used for table sorting. */ |
|
void flecs_table_swap( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t row_1, |
|
int32_t row_2) |
|
{ |
|
(void)world; |
|
|
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_table_check_sanity(table); |
|
|
|
if (row_1 == row_2) { |
|
return; |
|
} |
|
|
|
/* If the table is monitored indicate that there has been a change */ |
|
flecs_table_mark_table_dirty(world, table, 0); |
|
|
|
ecs_entity_t *entities = table->data.entities.array; |
|
ecs_entity_t e1 = entities[row_1]; |
|
ecs_entity_t e2 = entities[row_2]; |
|
|
|
ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); |
|
ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); |
|
|
|
ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Keep track of whether entity is watched */ |
|
uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); |
|
uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); |
|
|
|
/* Swap entities & records */ |
|
entities[row_1] = e2; |
|
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); |
|
|
|
flecs_table_swap_switch_columns(table, row_1, row_2); |
|
flecs_table_swap_bitset_columns(table, row_1, row_2); |
|
|
|
ecs_column_t *columns = table->data.columns; |
|
if (!columns) { |
|
flecs_table_check_sanity(table); |
|
return; |
|
} |
|
|
|
/* Find the maximum size of column elements |
|
* and allocate a temporary buffer for swapping */ |
|
int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; |
|
for (i = 0; i < column_count; i++) { |
|
temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].size); |
|
} |
|
|
|
void* tmp = ecs_os_alloca(temp_buffer_size); |
|
|
|
/* Swap columns */ |
|
for (i = 0; i < column_count; i ++) { |
|
int32_t size = columns[i].size; |
|
void *ptr = columns[i].data.array; |
|
|
|
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); |
|
ecs_os_memcpy(el_2, tmp, size); |
|
} |
|
|
|
flecs_table_check_sanity(table); |
|
} |
|
|
|
static |
|
void flecs_table_merge_vec( |
|
ecs_world_t *world, |
|
ecs_vec_t *dst, |
|
ecs_vec_t *src, |
|
int32_t size, |
|
int32_t elem_size) |
|
{ |
|
int32_t dst_count = dst->count; |
|
|
|
if (!dst_count) { |
|
ecs_vec_fini(&world->allocator, dst, size); |
|
*dst = *src; |
|
src->array = NULL; |
|
src->count = 0; |
|
src->size = 0; |
|
} else { |
|
int32_t src_count = src->count; |
|
|
|
if (elem_size) { |
|
ecs_vec_set_size(&world->allocator, |
|
dst, size, elem_size); |
|
} |
|
ecs_vec_set_count(&world->allocator, |
|
dst, size, dst_count + src_count); |
|
|
|
void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); |
|
void *src_ptr = src->array; |
|
ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); |
|
|
|
ecs_vec_fini(&world->allocator, src, size); |
|
} |
|
} |
|
|
|
/* Merge data from one table column into other table column */ |
|
static |
|
void flecs_table_merge_column( |
|
ecs_world_t *world, |
|
ecs_column_t *dst, |
|
ecs_column_t *src, |
|
int32_t column_size) |
|
{ |
|
ecs_size_t size = dst->size; |
|
int32_t dst_count = dst->data.count; |
|
|
|
if (!dst_count) { |
|
ecs_vec_fini(&world->allocator, &dst->data, size); |
|
*dst = *src; |
|
src->data.array = NULL; |
|
src->data.count = 0; |
|
src->data.size = 0; |
|
|
|
/* If the new table is not empty, copy the contents from the |
|
* src into the dst. */ |
|
} else { |
|
int32_t src_count = src->data.count; |
|
|
|
flecs_table_grow_column(world, dst, src_count, column_size, true); |
|
void *dst_ptr = ECS_ELEM(dst->data.array, size, dst_count); |
|
void *src_ptr = src->data.array; |
|
|
|
/* Move values into column */ |
|
ecs_type_info_t *ti = dst->ti; |
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_move_t move = ti->hooks.move_dtor; |
|
if (move) { |
|
move(dst_ptr, src_ptr, src_count, ti); |
|
} else { |
|
ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); |
|
} |
|
|
|
ecs_vec_fini(&world->allocator, &src->data, size); |
|
} |
|
} |
|
|
|
/* Merge storage of two tables. */ |
|
static |
|
void flecs_table_merge_data( |
|
ecs_world_t *world, |
|
ecs_table_t *dst_table, |
|
ecs_table_t *src_table, |
|
int32_t src_count, |
|
int32_t dst_count, |
|
ecs_data_t *src_data, |
|
ecs_data_t *dst_data) |
|
{ |
|
int32_t i_new = 0, dst_column_count = dst_table->column_count; |
|
int32_t i_old = 0, src_column_count = src_table->column_count; |
|
ecs_column_t *src_columns = src_data->columns; |
|
ecs_column_t *dst_columns = dst_data->columns; |
|
|
|
ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!src_count) { |
|
return; |
|
} |
|
|
|
/* Merge entities */ |
|
flecs_table_merge_vec(world, &dst_data->entities, &src_data->entities, |
|
ECS_SIZEOF(ecs_entity_t), 0); |
|
ecs_assert(dst_data->entities.count == src_count + dst_count, |
|
ECS_INTERNAL_ERROR, NULL); |
|
int32_t column_size = dst_data->entities.size; |
|
ecs_allocator_t *a = &world->allocator; |
|
|
|
for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { |
|
ecs_column_t *dst_column = &dst_columns[i_new]; |
|
ecs_column_t *src_column = &src_columns[i_old]; |
|
ecs_id_t dst_id = dst_column->id; |
|
ecs_id_t src_id = src_column->id; |
|
|
|
if (dst_id == src_id) { |
|
flecs_table_merge_column(world, dst_column, src_column, column_size); |
|
flecs_table_mark_table_dirty(world, dst_table, i_new + 1); |
|
i_new ++; |
|
i_old ++; |
|
} else if (dst_id < src_id) { |
|
/* New column, make sure vector is large enough. */ |
|
ecs_size_t size = dst_column->size; |
|
ecs_vec_set_size(a, &dst_column->data, size, column_size); |
|
ecs_vec_set_count(a, &dst_column->data, size, src_count + dst_count); |
|
flecs_table_invoke_ctor(dst_column, dst_count, src_count); |
|
i_new ++; |
|
} else if (dst_id > src_id) { |
|
/* Old column does not occur in new table, destruct */ |
|
flecs_table_invoke_dtor(src_column, 0, src_count); |
|
ecs_vec_fini(a, &src_column->data, src_column->size); |
|
i_old ++; |
|
} |
|
} |
|
|
|
flecs_table_move_switch_columns(dst_table, dst_count, src_table, 0, src_count, true); |
|
flecs_table_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]; |
|
int32_t size = column->size; |
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_vec_set_size(a, &column->data, size, column_size); |
|
ecs_vec_set_count(a, &column->data, size, src_count + dst_count); |
|
flecs_table_invoke_ctor(column, dst_count, src_count); |
|
} |
|
|
|
/* Destruct remaining columns */ |
|
for (; i_old < src_column_count; i_old ++) { |
|
ecs_column_t *column = &src_columns[i_old]; |
|
flecs_table_invoke_dtor(column, 0, src_count); |
|
ecs_vec_fini(a, &column->data, column->size); |
|
} |
|
|
|
/* Mark entity column as dirty */ |
|
flecs_table_mark_table_dirty(world, dst_table, 0); |
|
} |
|
|
|
/* Merge source table into destination table. This typically happens as result |
|
* of a bulk operation, like when a component is removed from all entities in |
|
* the source table (like for the Remove OnDelete policy). */ |
|
void flecs_table_merge( |
|
ecs_world_t *world, |
|
ecs_table_t *dst_table, |
|
ecs_table_t *src_table, |
|
ecs_data_t *dst_data, |
|
ecs_data_t *src_data) |
|
{ |
|
ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
|
|
flecs_table_check_sanity(src_table); |
|
flecs_table_check_sanity(dst_table); |
|
|
|
bool move_data = false; |
|
|
|
/* If there is nothing to merge to, just clear the old table */ |
|
if (!dst_table) { |
|
flecs_table_clear_data(world, src_table, src_data); |
|
flecs_table_check_sanity(src_table); |
|
return; |
|
} else { |
|
ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
} |
|
|
|
/* If there is no data to merge, drop out */ |
|
if (!src_data) { |
|
return; |
|
} |
|
|
|
if (!dst_data) { |
|
dst_data = &dst_table->data; |
|
if (dst_table == src_table) { |
|
move_data = true; |
|
} |
|
} |
|
|
|
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; |
|
|
|
/* First, update entity index so old entities point to new type */ |
|
int32_t i; |
|
for(i = 0; i < src_count; i ++) { |
|
ecs_record_t *record = flecs_entities_ensure(world, src_entities[i]); |
|
uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); |
|
record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); |
|
record->table = dst_table; |
|
} |
|
|
|
/* Merge table columns */ |
|
if (move_data) { |
|
*dst_data = *src_data; |
|
} else { |
|
flecs_table_merge_data(world, dst_table, src_table, src_count, dst_count, |
|
src_data, dst_data); |
|
} |
|
|
|
if (src_count) { |
|
if (!dst_count) { |
|
flecs_table_set_empty(world, dst_table); |
|
} |
|
flecs_table_set_empty(world, src_table); |
|
|
|
flecs_table_traversable_add(dst_table, src_table->_->traversable_count); |
|
flecs_table_traversable_add(src_table, -src_table->_->traversable_count); |
|
ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
flecs_table_check_sanity(src_table); |
|
flecs_table_check_sanity(dst_table); |
|
} |
|
|
|
/* Replace data with other data. Used by snapshots to restore previous state. */ |
|
void flecs_table_replace_data( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_data_t *data) |
|
{ |
|
int32_t prev_count = 0; |
|
ecs_data_t *table_data = &table->data; |
|
ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); |
|
|
|
flecs_table_check_sanity(table); |
|
|
|
prev_count = table_data->entities.count; |
|
flecs_table_notify_on_remove(world, table, table_data); |
|
flecs_table_clear_data(world, table, table_data); |
|
|
|
if (data) { |
|
table->data = *data; |
|
} else { |
|
flecs_table_init_data(world, table); |
|
} |
|
|
|
int32_t count = ecs_table_count(table); |
|
|
|
if (!prev_count && count) { |
|
flecs_table_set_empty(world, table); |
|
} else if (prev_count && !count) { |
|
flecs_table_set_empty(world, table); |
|
} |
|
|
|
flecs_table_check_sanity(table); |
|
} |
|
|
|
/* Internal mechanism for propagating information to tables */ |
|
void flecs_table_notify( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_table_event_t *event) |
|
{ |
|
if (world->flags & EcsWorldFini) { |
|
return; |
|
} |
|
|
|
switch(event->kind) { |
|
case EcsTableTriggersForId: |
|
flecs_table_add_trigger_flags(world, table, event->event); |
|
break; |
|
case EcsTableNoTriggersForId: |
|
break; |
|
} |
|
} |
|
|
|
/* -- Public API -- */ |
|
|
|
void ecs_table_lock( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
if (table) { |
|
if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { |
|
table->_->lock ++; |
|
} |
|
} |
|
} |
|
|
|
void ecs_table_unlock( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
if (table) { |
|
if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { |
|
table->_->lock --; |
|
ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, NULL); |
|
} |
|
} |
|
} |
|
|
|
const ecs_type_t* ecs_table_get_type( |
|
const ecs_table_t *table) |
|
{ |
|
if (table) { |
|
return &table->type; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
int32_t ecs_table_get_type_index( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_id_t id) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
return -1; |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (!tr) { |
|
return -1; |
|
} |
|
|
|
return tr->index; |
|
error: |
|
return -1; |
|
} |
|
|
|
int32_t ecs_table_get_column_index( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_id_t id) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id); |
|
if (!idr) { |
|
return -1; |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (!tr) { |
|
return -1; |
|
} |
|
|
|
return tr->column; |
|
error: |
|
return -1; |
|
} |
|
|
|
int32_t ecs_table_column_count( |
|
const ecs_table_t *table) |
|
{ |
|
return table->column_count; |
|
} |
|
|
|
int32_t ecs_table_type_to_column_index( |
|
const ecs_table_t *table, |
|
int32_t index) |
|
{ |
|
ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); |
|
int32_t *column_map = table->column_map; |
|
if (column_map) { |
|
return column_map[index]; |
|
} |
|
error: |
|
return -1; |
|
} |
|
|
|
int32_t ecs_table_column_to_type_index( |
|
const ecs_table_t *table, |
|
int32_t index) |
|
{ |
|
ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); |
|
int32_t offset = table->type.count; |
|
return table->column_map[offset + index]; |
|
error: |
|
return -1; |
|
} |
|
|
|
void* ecs_table_get_column( |
|
const ecs_table_t *table, |
|
int32_t index, |
|
int32_t offset) |
|
{ |
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_column_t *column = &table->data.columns[index]; |
|
void *result = column->data.array; |
|
if (offset) { |
|
result = ECS_ELEM(result, column->size, offset); |
|
} |
|
|
|
return result; |
|
error: |
|
return NULL; |
|
} |
|
|
|
void* ecs_table_get_id( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_id_t id, |
|
int32_t offset) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
int32_t index = ecs_table_get_column_index(world, table, id); |
|
if (index == -1) { |
|
return NULL; |
|
} |
|
|
|
return ecs_table_get_column(table, index, offset); |
|
error: |
|
return NULL; |
|
} |
|
|
|
size_t ecs_table_get_column_size( |
|
const ecs_table_t *table, |
|
int32_t column) |
|
{ |
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
return flecs_ito(size_t, table->data.columns[column].size); |
|
error: |
|
return 0; |
|
} |
|
|
|
int32_t ecs_table_count( |
|
const ecs_table_t *table) |
|
{ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return flecs_table_data_count(&table->data); |
|
} |
|
|
|
bool ecs_table_has_id( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_id_t id) |
|
{ |
|
return ecs_table_get_type_index(world, table, id) != -1; |
|
} |
|
|
|
int32_t ecs_table_get_depth( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_entity_t rel) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_id_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
return flecs_relation_depth(world, rel, table); |
|
error: |
|
return -1; |
|
} |
|
|
|
bool ecs_table_has_flags( |
|
ecs_table_t *table, |
|
ecs_flags32_t flags) |
|
{ |
|
return (table->flags & flags) == flags; |
|
} |
|
|
|
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; |
|
} |
|
|
|
void ecs_table_swap_rows( |
|
ecs_world_t* world, |
|
ecs_table_t* table, |
|
int32_t row_1, |
|
int32_t row_2) |
|
{ |
|
flecs_table_swap(world, table, row_1, row_2); |
|
} |
|
|
|
int32_t flecs_table_observed_count( |
|
const ecs_table_t *table) |
|
{ |
|
return table->_->traversable_count; |
|
} |
|
|
|
void* ecs_record_get_column( |
|
const ecs_record_t *r, |
|
int32_t index, |
|
size_t c_size) |
|
{ |
|
(void)c_size; |
|
ecs_table_t *table = r->table; |
|
|
|
ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); |
|
ecs_column_t *column = &table->data.columns[index]; |
|
ecs_size_t size = column->size; |
|
|
|
ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
return ecs_vec_get(&column->data, size, ECS_RECORD_TO_ROW(r->row)); |
|
error: |
|
return NULL; |
|
} |
|
|
|
ecs_record_t* ecs_record_find( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
world = ecs_get_world(world); |
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity); |
|
if (r) { |
|
return r; |
|
} |
|
error: |
|
return NULL; |
|
} |
|
|
|
/** |
|
* @file table_cache.c |
|
* @brief Data structure for fast table iteration/lookups. |
|
* |
|
* A table cache is a data structure that provides constant time operations for |
|
* insertion and removal of tables, and to testing whether a table is registered |
|
* with the cache. A table cache also provides functions to iterate the tables |
|
* in a cache. |
|
* |
|
* The world stores a table cache per (component) id inside the id record |
|
* administration. Cached queries store a table cache with matched tables. |
|
* |
|
* A table cache has separate lists for non-empty tables and empty tables. This |
|
* improves performance as applications don't waste time iterating empty tables. |
|
*/ |
|
|
|
|
|
static |
|
void flecs_table_cache_list_remove( |
|
ecs_table_cache_t *cache, |
|
ecs_table_cache_hdr_t *elem) |
|
{ |
|
ecs_table_cache_hdr_t *next = elem->next; |
|
ecs_table_cache_hdr_t *prev = elem->prev; |
|
|
|
if (next) { |
|
next->prev = prev; |
|
} |
|
if (prev) { |
|
prev->next = next; |
|
} |
|
|
|
cache->empty_tables.count -= !!elem->empty; |
|
cache->tables.count -= !elem->empty; |
|
|
|
if (cache->empty_tables.first == elem) { |
|
cache->empty_tables.first = next; |
|
} else if (cache->tables.first == elem) { |
|
cache->tables.first = next; |
|
} |
|
if (cache->empty_tables.last == elem) { |
|
cache->empty_tables.last = prev; |
|
} |
|
if (cache->tables.last == elem) { |
|
cache->tables.last = prev; |
|
} |
|
} |
|
|
|
static |
|
void flecs_table_cache_list_insert( |
|
ecs_table_cache_t *cache, |
|
ecs_table_cache_hdr_t *elem) |
|
{ |
|
ecs_table_cache_hdr_t *last; |
|
if (elem->empty) { |
|
last = cache->empty_tables.last; |
|
cache->empty_tables.last = elem; |
|
if ((++ cache->empty_tables.count) == 1) { |
|
cache->empty_tables.first = elem; |
|
} |
|
} else { |
|
last = cache->tables.last; |
|
cache->tables.last = elem; |
|
if ((++ cache->tables.count) == 1) { |
|
cache->tables.first = elem; |
|
} |
|
} |
|
|
|
elem->next = NULL; |
|
elem->prev = last; |
|
|
|
if (last) { |
|
last->next = elem; |
|
} |
|
} |
|
|
|
void ecs_table_cache_init( |
|
ecs_world_t *world, |
|
ecs_table_cache_t *cache) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_map_init_w_params(&cache->index, &world->allocators.ptr); |
|
} |
|
|
|
void ecs_table_cache_fini( |
|
ecs_table_cache_t *cache) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_map_fini(&cache->index); |
|
} |
|
|
|
bool ecs_table_cache_is_empty( |
|
const ecs_table_cache_t *cache) |
|
{ |
|
return ecs_map_count(&cache->index) == 0; |
|
} |
|
|
|
void ecs_table_cache_insert( |
|
ecs_table_cache_t *cache, |
|
const ecs_table_t *table, |
|
ecs_table_cache_hdr_t *result) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(ecs_table_cache_get(cache, table) == NULL, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
bool empty; |
|
if (!table) { |
|
empty = false; |
|
} else { |
|
empty = ecs_table_count(table) == 0; |
|
} |
|
|
|
result->cache = cache; |
|
result->table = ECS_CONST_CAST(ecs_table_t*, table); |
|
result->empty = empty; |
|
|
|
flecs_table_cache_list_insert(cache, result); |
|
|
|
if (table) { |
|
ecs_map_insert_ptr(&cache->index, table->id, result); |
|
} |
|
|
|
ecs_assert(empty || cache->tables.first != NULL, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(!empty || cache->empty_tables.first != NULL, |
|
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 **r = ecs_map_get_ref( |
|
&cache->index, ecs_table_cache_hdr_t, table->id); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_cache_hdr_t *old = *r; |
|
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; |
|
} |
|
|
|
*r = elem; |
|
elem->prev = prev; |
|
elem->next = next; |
|
} |
|
|
|
void* ecs_table_cache_get( |
|
const ecs_table_cache_t *cache, |
|
const ecs_table_t *table) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (table) { |
|
if (ecs_map_is_init(&cache->index)) { |
|
return ecs_map_get_deref(&cache->index, void**, table->id); |
|
} |
|
return NULL; |
|
} else { |
|
ecs_table_cache_hdr_t *elem = cache->tables.first; |
|
ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); |
|
return elem; |
|
} |
|
} |
|
|
|
void* ecs_table_cache_remove( |
|
ecs_table_cache_t *cache, |
|
uint64_t table_id, |
|
ecs_table_cache_hdr_t *elem) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(table_id != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_table_cache_list_remove(cache, elem); |
|
ecs_map_remove(&cache->index, table_id); |
|
|
|
return elem; |
|
} |
|
|
|
bool ecs_table_cache_set_empty( |
|
ecs_table_cache_t *cache, |
|
const ecs_table_t *table, |
|
bool empty) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, |
|
ecs_table_cache_hdr_t, table->id); |
|
if (!elem) { |
|
return false; |
|
} |
|
|
|
if (elem->empty == empty) { |
|
return false; |
|
} |
|
|
|
flecs_table_cache_list_remove(cache, elem); |
|
elem->empty = empty; |
|
flecs_table_cache_list_insert(cache, elem); |
|
|
|
return true; |
|
} |
|
|
|
bool flecs_table_cache_iter( |
|
ecs_table_cache_t *cache, |
|
ecs_table_cache_iter_t *out) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); |
|
out->next = cache->tables.first; |
|
out->next_list = NULL; |
|
out->cur = NULL; |
|
return out->next != NULL; |
|
} |
|
|
|
bool flecs_table_cache_empty_iter( |
|
ecs_table_cache_t *cache, |
|
ecs_table_cache_iter_t *out) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); |
|
out->next = cache->empty_tables.first; |
|
out->next_list = NULL; |
|
out->cur = NULL; |
|
return out->next != NULL; |
|
} |
|
|
|
bool flecs_table_cache_all_iter( |
|
ecs_table_cache_t *cache, |
|
ecs_table_cache_iter_t *out) |
|
{ |
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); |
|
out->next = cache->empty_tables.first; |
|
out->next_list = cache->tables.first; |
|
out->cur = NULL; |
|
return out->next != NULL || out->next_list != NULL; |
|
} |
|
|
|
ecs_table_cache_hdr_t* flecs_table_cache_next_( |
|
ecs_table_cache_iter_t *it) |
|
{ |
|
ecs_table_cache_hdr_t *next = it->next; |
|
if (!next) { |
|
next = it->next_list; |
|
it->next_list = NULL; |
|
if (!next) { |
|
return NULL; |
|
} |
|
} |
|
|
|
it->cur = next; |
|
it->next = next->next; |
|
return next; |
|
} |
|
|
|
/** |
|
* @file table_graph.c |
|
* @brief Data structure to speed up table transitions. |
|
* |
|
* The table graph is used to speed up finding tables in add/remove operations. |
|
* For example, if component C is added to an entity in table [A, B], the entity |
|
* must be moved to table [A, B, C]. The graph speeds this process up with an |
|
* edge for component C that connects [A, B] to [A, B, C]. |
|
*/ |
|
|
|
|
|
/* Id sequence (type) utilities */ |
|
|
|
static |
|
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; |
|
return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); |
|
} |
|
|
|
static |
|
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; |
|
|
|
if (count_1 != count_2) { |
|
return (count_1 > count_2) - (count_1 < count_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; !result && (i < count_1); i ++) { |
|
ecs_id_t id_1 = ids_1[i]; |
|
ecs_id_t id_2 = ids_2[i]; |
|
result = (id_1 > id_2) - (id_1 < id_2); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
void flecs_table_hashmap_init( |
|
ecs_world_t *world, |
|
ecs_hashmap_t *hm) |
|
{ |
|
flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, |
|
flecs_type_hash, flecs_type_compare, &world->allocator); |
|
} |
|
|
|
/* 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_id_t *array = type->array; |
|
int32_t i, count = type->count; |
|
|
|
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; |
|
} |
|
|
|
/* 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; |
|
} |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
return i - offset; |
|
} |
|
|
|
/* Create type from source type with id */ |
|
static |
|
int flecs_type_new_with( |
|
ecs_world_t *world, |
|
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; |
|
} |
|
|
|
int32_t dst_count = src->count + 1; |
|
ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); |
|
dst->count = dst_count; |
|
dst->array = dst_array; |
|
|
|
if (at) { |
|
ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); |
|
} |
|
|
|
int32_t remain = src->count - at; |
|
if (remain) { |
|
ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); |
|
} |
|
|
|
dst_array[at] = with; |
|
|
|
return 0; |
|
} |
|
|
|
/* Create type from source type without ids matching wildcard */ |
|
static |
|
int flecs_type_new_filtered( |
|
ecs_world_t *world, |
|
ecs_type_t *dst, |
|
const ecs_type_t *src, |
|
ecs_id_t wildcard, |
|
int32_t at) |
|
{ |
|
*dst = flecs_type_copy(world, src); |
|
ecs_id_t *dst_array = dst->array; |
|
ecs_id_t *src_array = src->array; |
|
if (at) { |
|
ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); |
|
} |
|
|
|
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 ++; |
|
} |
|
} |
|
|
|
dst->count = w; |
|
if (w != count) { |
|
dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Create type from source type without id */ |
|
static |
|
int flecs_type_new_without( |
|
ecs_world_t *world, |
|
ecs_type_t *dst, |
|
const ecs_type_t *src, |
|
ecs_id_t without) |
|
{ |
|
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_IS_PAIR(without)) { |
|
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(world, 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 = flecs_walloc_n(world, 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( |
|
ecs_world_t *world, |
|
const ecs_type_t *src) |
|
{ |
|
int32_t src_count = src->count; |
|
if (!src_count) { |
|
return (ecs_type_t){ 0 }; |
|
} |
|
|
|
ecs_id_t *ids = flecs_walloc_n(world, ecs_id_t, src_count); |
|
ecs_os_memcpy_n(ids, src->array, ecs_id_t, src_count); |
|
return (ecs_type_t) { |
|
.array = ids, |
|
.count = src_count |
|
}; |
|
} |
|
|
|
/* Free type */ |
|
void flecs_type_free( |
|
ecs_world_t *world, |
|
ecs_type_t *type) |
|
{ |
|
int32_t count = type->count; |
|
if (count) { |
|
flecs_wfree_n(world, ecs_id_t, type->count, type->array); |
|
} |
|
} |
|
|
|
/* Add to type */ |
|
static |
|
void flecs_type_add( |
|
ecs_world_t *world, |
|
ecs_type_t *type, |
|
ecs_id_t add) |
|
{ |
|
ecs_type_t new_type; |
|
int res = flecs_type_new_with(world, &new_type, type, add); |
|
if (res != -1) { |
|
flecs_type_free(world, type); |
|
type->array = new_type.array; |
|
type->count = new_type.count; |
|
} |
|
} |
|
|
|
/* Graph edge utilities */ |
|
|
|
void flecs_table_diff_builder_init( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *builder) |
|
{ |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_vec_init_t(a, &builder->added, ecs_id_t, 256); |
|
ecs_vec_init_t(a, &builder->removed, ecs_id_t, 256); |
|
} |
|
|
|
void flecs_table_diff_builder_fini( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *builder) |
|
{ |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_vec_fini_t(a, &builder->added, ecs_id_t); |
|
ecs_vec_fini_t(a, &builder->removed, ecs_id_t); |
|
} |
|
|
|
void flecs_table_diff_builder_clear( |
|
ecs_table_diff_builder_t *builder) |
|
{ |
|
ecs_vec_clear(&builder->added); |
|
ecs_vec_clear(&builder->removed); |
|
} |
|
|
|
static |
|
void flecs_table_diff_build_type( |
|
ecs_world_t *world, |
|
ecs_vec_t *vec, |
|
ecs_type_t *type, |
|
int32_t offset) |
|
{ |
|
int32_t count = vec->count - offset; |
|
ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); |
|
if (count) { |
|
type->array = flecs_wdup_n(world, ecs_id_t, count, |
|
ECS_ELEM_T(vec->array, ecs_id_t, offset)); |
|
type->count = count; |
|
ecs_vec_set_count_t(&world->allocator, vec, ecs_id_t, offset); |
|
} |
|
} |
|
|
|
void flecs_table_diff_build( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *builder, |
|
ecs_table_diff_t *diff, |
|
int32_t added_offset, |
|
int32_t removed_offset) |
|
{ |
|
flecs_table_diff_build_type(world, &builder->added, &diff->added, |
|
added_offset); |
|
flecs_table_diff_build_type(world, &builder->removed, &diff->removed, |
|
removed_offset); |
|
} |
|
|
|
void flecs_table_diff_build_noalloc( |
|
ecs_table_diff_builder_t *builder, |
|
ecs_table_diff_t *diff) |
|
{ |
|
diff->added = (ecs_type_t){ |
|
.array = builder->added.array, .count = builder->added.count }; |
|
diff->removed = (ecs_type_t){ |
|
.array = builder->removed.array, .count = builder->removed.count }; |
|
} |
|
|
|
static |
|
void flecs_table_diff_build_add_type_to_vec( |
|
ecs_world_t *world, |
|
ecs_vec_t *vec, |
|
ecs_type_t *add) |
|
{ |
|
if (!add || !add->count) { |
|
return; |
|
} |
|
|
|
int32_t offset = vec->count; |
|
ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count); |
|
ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset), |
|
add->array, ecs_id_t, add->count); |
|
} |
|
|
|
void flecs_table_diff_build_append_table( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *dst, |
|
ecs_table_diff_t *src) |
|
{ |
|
flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); |
|
flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); |
|
} |
|
|
|
static |
|
void flecs_table_diff_free( |
|
ecs_world_t *world, |
|
ecs_table_diff_t *diff) |
|
{ |
|
flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array); |
|
flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array); |
|
flecs_bfree(&world->allocators.table_diff, diff); |
|
} |
|
|
|
static |
|
ecs_graph_edge_t* flecs_table_ensure_hi_edge( |
|
ecs_world_t *world, |
|
ecs_graph_edges_t *edges, |
|
ecs_id_t id) |
|
{ |
|
if (!edges->hi) { |
|
edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t); |
|
ecs_map_init_w_params(edges->hi, &world->allocators.ptr); |
|
} |
|
|
|
ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id); |
|
ecs_graph_edge_t *edge = r[0]; |
|
if (edge) { |
|
return edge; |
|
} |
|
|
|
if (id < FLECS_HI_COMPONENT_ID) { |
|
edge = &edges->lo[id]; |
|
} else { |
|
edge = flecs_bcalloc(&world->allocators.graph_edge); |
|
} |
|
|
|
r[0] = edge; |
|
return edge; |
|
} |
|
|
|
static |
|
ecs_graph_edge_t* flecs_table_ensure_edge( |
|
ecs_world_t *world, |
|
ecs_graph_edges_t *edges, |
|
ecs_id_t id) |
|
{ |
|
ecs_graph_edge_t *edge; |
|
|
|
if (id < FLECS_HI_COMPONENT_ID) { |
|
if (!edges->lo) { |
|
edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo); |
|
} |
|
edge = &edges->lo[id]; |
|
} else { |
|
edge = flecs_table_ensure_hi_edge(world, edges, id); |
|
} |
|
|
|
return edge; |
|
} |
|
|
|
static |
|
void flecs_table_disconnect_edge( |
|
ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_graph_edge_t *edge) |
|
{ |
|
ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); |
|
(void)id; |
|
|
|
/* Remove backref from destination table */ |
|
ecs_graph_edge_hdr_t *next = edge->hdr.next; |
|
ecs_graph_edge_hdr_t *prev = edge->hdr.prev; |
|
|
|
if (next) { |
|
next->prev = prev; |
|
} |
|
if (prev) { |
|
prev->next = next; |
|
} |
|
|
|
/* Remove data associated with edge */ |
|
ecs_table_diff_t *diff = edge->diff; |
|
if (diff) { |
|
flecs_table_diff_free(world, diff); |
|
} |
|
|
|
/* If edge id is low, clear it from fast lookup array */ |
|
if (id < FLECS_HI_COMPONENT_ID) { |
|
ecs_os_memset_t(edge, 0, ecs_graph_edge_t); |
|
} else { |
|
flecs_bfree(&world->allocators.graph_edge, edge); |
|
} |
|
} |
|
|
|
static |
|
void flecs_table_remove_edge( |
|
ecs_world_t *world, |
|
ecs_graph_edges_t *edges, |
|
ecs_id_t id, |
|
ecs_graph_edge_t *edge) |
|
{ |
|
ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL); |
|
flecs_table_disconnect_edge(world, id, edge); |
|
ecs_map_remove(edges->hi, id); |
|
} |
|
|
|
static |
|
void flecs_table_init_edges( |
|
ecs_graph_edges_t *edges) |
|
{ |
|
edges->lo = NULL; |
|
edges->hi = NULL; |
|
} |
|
|
|
static |
|
void flecs_table_init_node( |
|
ecs_graph_node_t *node) |
|
{ |
|
flecs_table_init_edges(&node->add); |
|
flecs_table_init_edges(&node->remove); |
|
} |
|
|
|
bool flecs_table_records_update_empty( |
|
ecs_table_t *table) |
|
{ |
|
bool result = false; |
|
bool is_empty = ecs_table_count(table) == 0; |
|
|
|
int32_t i, count = table->_->record_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_table_record_t *tr = &table->_->records[i]; |
|
ecs_table_cache_t *cache = tr->hdr.cache; |
|
result |= ecs_table_cache_set_empty(cache, table, is_empty); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static |
|
void flecs_init_table( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_table_t *prev) |
|
{ |
|
table->flags = 0; |
|
table->dirty_state = NULL; |
|
table->_->lock = 0; |
|
table->_->generation = 0; |
|
|
|
flecs_table_init_node(&table->node); |
|
|
|
flecs_table_init(world, table, prev); |
|
} |
|
|
|
static |
|
ecs_table_t *flecs_create_table( |
|
ecs_world_t *world, |
|
ecs_type_t *type, |
|
flecs_hashmap_result_t table_elem, |
|
ecs_table_t *prev) |
|
{ |
|
ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t); |
|
ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); |
|
result->_ = flecs_calloc_t(&world->allocator, ecs_table__t); |
|
ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
result->id = flecs_sparse_last_id(&world->store.tables); |
|
result->type = *type; |
|
|
|
if (ecs_should_log_2()) { |
|
char *expr = ecs_type_str(world, &result->type); |
|
ecs_dbg_2( |
|
"#[green]table#[normal] [%s] #[green]created#[reset] with id %d", |
|
expr, result->id); |
|
ecs_os_free(expr); |
|
} |
|
|
|
ecs_log_push_2(); |
|
|
|
/* Store table in table hashmap */ |
|
*(ecs_table_t**)table_elem.value = result; |
|
|
|
/* Set keyvalue to one that has the same lifecycle as the table */ |
|
*(ecs_type_t*)table_elem.key = result->type; |
|
result->_->hash = table_elem.hash; |
|
|
|
flecs_init_table(world, result, prev); |
|
|
|
/* Update counters */ |
|
world->info.table_count ++; |
|
world->info.empty_table_count ++; |
|
world->info.table_create_total ++; |
|
|
|
ecs_log_pop_2(); |
|
|
|
return result; |
|
} |
|
|
|
static |
|
ecs_table_t* flecs_table_ensure( |
|
ecs_world_t *world, |
|
ecs_type_t *type, |
|
bool own_type, |
|
ecs_table_t *prev) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
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, type, ecs_table_t*); |
|
if ((table = *(ecs_table_t**)elem.value)) { |
|
if (own_type) { |
|
flecs_type_free(world, type); |
|
} |
|
return table; |
|
} |
|
|
|
/* If we get here, table needs to be created which is only allowed when the |
|
* application is not currently in progress */ |
|
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* If we get here, the table has not been found, so create it. */ |
|
if (own_type) { |
|
return flecs_create_table(world, type, elem, prev); |
|
} |
|
|
|
ecs_type_t copy = flecs_type_copy(world, type); |
|
return flecs_create_table(world, ©, elem, prev); |
|
} |
|
|
|
static |
|
void flecs_diff_insert_added( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *diff, |
|
ecs_id_t id) |
|
{ |
|
ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; |
|
} |
|
|
|
static |
|
void flecs_diff_insert_removed( |
|
ecs_world_t *world, |
|
ecs_table_diff_builder_t *diff, |
|
ecs_id_t id) |
|
{ |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; |
|
} |
|
|
|
static |
|
void flecs_compute_table_diff( |
|
ecs_world_t *world, |
|
ecs_table_t *node, |
|
ecs_table_t *next, |
|
ecs_graph_edge_t *edge, |
|
ecs_id_t id) |
|
{ |
|
if (ECS_IS_PAIR(id)) { |
|
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair( |
|
ECS_PAIR_FIRST(id), EcsWildcard)); |
|
if (idr->flags & EcsIdUnion) { |
|
if (node != next) { |
|
id = ecs_pair(EcsUnion, ECS_PAIR_FIRST(id)); |
|
} else { |
|
ecs_table_diff_t *diff = flecs_bcalloc( |
|
&world->allocators.table_diff); |
|
diff->added.count = 1; |
|
diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id); |
|
edge->diff = diff; |
|
return; |
|
} |
|
} |
|
} |
|
|
|
ecs_type_t node_type = node->type; |
|
ecs_type_t next_type = 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); |
|
|
|
/* First do a scan to see how big the diff is, so we don't have to realloc |
|
* or alloc more memory than required. */ |
|
for (; i_node < node_count && i_next < next_count; ) { |
|
ecs_id_t id_node = ids_node[i_node]; |
|
ecs_id_t id_next = ids_next[i_next]; |
|
|
|
bool added = id_next < id_node; |
|
bool removed = id_node < id_next; |
|
|
|
trivial_edge &= !added || id_next == id; |
|
trivial_edge &= !removed || id_node == id; |
|
|
|
added_count += added; |
|
removed_count += removed; |
|
|
|
i_node += id_node <= id_next; |
|
i_next += id_next <= id_node; |
|
} |
|
|
|
added_count += next_count - i_next; |
|
removed_count += node_count - i_node; |
|
|
|
trivial_edge &= (added_count + removed_count) <= 1 && |
|
!ecs_id_is_wildcard(id); |
|
|
|
if (trivial_edge) { |
|
/* If edge is trivial there's no need to create a diff element for it */ |
|
return; |
|
} |
|
|
|
ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; |
|
int32_t added_offset = builder->added.count; |
|
int32_t removed_offset = builder->removed.count; |
|
|
|
for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { |
|
ecs_id_t id_node = ids_node[i_node]; |
|
ecs_id_t id_next = ids_next[i_next]; |
|
|
|
if (id_next < id_node) { |
|
flecs_diff_insert_added(world, builder, id_next); |
|
} else if (id_node < id_next) { |
|
flecs_diff_insert_removed(world, builder, id_node); |
|
} |
|
|
|
i_node += id_node <= id_next; |
|
i_next += id_next <= id_node; |
|
} |
|
|
|
for (; i_next < next_count; i_next ++) { |
|
flecs_diff_insert_added(world, builder, ids_next[i_next]); |
|
} |
|
for (; i_node < node_count; i_node ++) { |
|
flecs_diff_insert_removed(world, builder, ids_node[i_node]); |
|
} |
|
|
|
ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); |
|
edge->diff = diff; |
|
flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); |
|
|
|
ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
static |
|
void flecs_add_overrides_for_base( |
|
ecs_world_t *world, |
|
ecs_type_t *dst_type, |
|
ecs_id_t pair) |
|
{ |
|
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_id_t *ids = base_table->type.array; |
|
|
|
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]; |
|
ecs_id_t to_add = 0; |
|
if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { |
|
to_add = id & ~ECS_OVERRIDE; |
|
} else { |
|
ecs_table_record_t *tr = &base_table->_->records[i]; |
|
ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; |
|
if (idr->flags & EcsIdAlwaysOverride) { |
|
to_add = id; |
|
} |
|
} |
|
if (to_add) { |
|
ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(to_add), EcsWildcard); |
|
bool exclusive = false; |
|
if (ECS_IS_PAIR(to_add)) { |
|
ecs_id_record_t *idr = flecs_id_record_get(world, wc); |
|
if (idr) { |
|
exclusive = (idr->flags & EcsIdExclusive) != 0; |
|
} |
|
} |
|
if (!exclusive) { |
|
flecs_type_add(world, dst_type, to_add); |
|
} else { |
|
int32_t column = flecs_type_find(dst_type, wc); |
|
if (column == -1) { |
|
flecs_type_add(world, dst_type, to_add); |
|
} else { |
|
dst_type->array[column] = to_add; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (flags & EcsTableHasIsA) { |
|
ecs_table_record_t *tr = flecs_id_record_get_table( |
|
world->idr_isa_wildcard, base_table); |
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
int32_t i = tr->index, end = i + tr->count; |
|
for (; i != end; i ++) { |
|
flecs_add_overrides_for_base(world, dst_type, ids[i]); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_add_with_property( |
|
ecs_world_t *world, |
|
ecs_id_record_t *idr_with_wildcard, |
|
ecs_type_t *dst_type, |
|
ecs_entity_t r, |
|
ecs_entity_t o) |
|
{ |
|
r = ecs_get_alive(world, r); |
|
|
|
/* Check if component/relationship 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; |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table( |
|
idr_with_wildcard, table); |
|
if (tr) { |
|
int32_t i = tr->index, end = i + tr->count; |
|
ecs_id_t *ids = table->type.array; |
|
|
|
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); |
|
} |
|
|
|
flecs_type_add(world, dst_type, a); |
|
flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); |
|
} |
|
} |
|
|
|
} |
|
|
|
static |
|
ecs_table_t* flecs_find_table_with( |
|
ecs_world_t *world, |
|
ecs_table_t *node, |
|
ecs_id_t with) |
|
{ |
|
ecs_ensure_id(world, with); |
|
|
|
ecs_id_record_t *idr = NULL; |
|
ecs_entity_t r = 0, o = 0; |
|
|
|
if (ECS_IS_PAIR(with)) { |
|
r = ECS_PAIR_FIRST(with); |
|
o = ECS_PAIR_SECOND(with); |
|
idr = flecs_id_record_ensure(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( |
|
world, &dst_type, &node->type, union_id); |
|
if (res == -1) { |
|
return node; |
|
} |
|
|
|
return flecs_table_ensure(world, &dst_type, true, node); |
|
} else if (idr->flags & EcsIdExclusive) { |
|
/* Relationship is exclusive, check if table already has it */ |
|
ecs_table_record_t *tr = flecs_id_record_get_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(world, &node->type); |
|
ecs_assert(dst_type.array != NULL, ECS_INTERNAL_ERROR, NULL); |
|
dst_type.array[tr->index] = with; |
|
return flecs_table_ensure(world, &dst_type, true, node); |
|
} |
|
} |
|
} else { |
|
idr = flecs_id_record_ensure(world, with); |
|
r = with; |
|
} |
|
|
|
/* Create sequence with new id */ |
|
ecs_type_t dst_type; |
|
int res = flecs_type_new_with(world, &dst_type, &node->type, with); |
|
if (res == -1) { |
|
return node; /* Current table already has id */ |
|
} |
|
|
|
if (r == EcsIsA) { |
|
/* If adding a prefab, check if prefab has overrides */ |
|
flecs_add_overrides_for_base(world, &dst_type, with); |
|
} else if (r == EcsChildOf) { |
|
o = ecs_get_alive(world, o); |
|
if (ecs_has_id(world, o, EcsPrefab)) { |
|
flecs_type_add(world, &dst_type, EcsPrefab); |
|
} |
|
} |
|
|
|
if (idr->flags & EcsIdWith) { |
|
ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(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 flecs_table_ensure(world, &dst_type, true, node); |
|
} |
|
|
|
static |
|
ecs_table_t* flecs_find_table_without( |
|
ecs_world_t *world, |
|
ecs_table_t *node, |
|
ecs_id_t without) |
|
{ |
|
if (ECS_IS_PAIR(without)) { |
|
ecs_entity_t r = 0; |
|
ecs_id_record_t *idr = NULL; |
|
r = ECS_PAIR_FIRST(without); |
|
idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); |
|
if (idr && idr->flags & EcsIdUnion) { |
|
without = ecs_pair(EcsUnion, r); |
|
} |
|
} |
|
|
|
/* Create sequence with new id */ |
|
ecs_type_t dst_type; |
|
int res = flecs_type_new_without(world, &dst_type, &node->type, without); |
|
if (res == -1) { |
|
return node; /* Current table does not have id */ |
|
} |
|
|
|
return flecs_table_ensure(world, &dst_type, true, node); |
|
} |
|
|
|
static |
|
void flecs_table_init_edge( |
|
ecs_table_t *table, |
|
ecs_graph_edge_t *edge, |
|
ecs_id_t id, |
|
ecs_table_t *to) |
|
{ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
edge->from = table; |
|
edge->to = to; |
|
edge->id = id; |
|
} |
|
|
|
static |
|
void flecs_init_edge_for_add( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_graph_edge_t *edge, |
|
ecs_id_t id, |
|
ecs_table_t *to) |
|
{ |
|
flecs_table_init_edge(table, edge, id, to); |
|
|
|
flecs_table_ensure_hi_edge(world, &table->node.add, id); |
|
|
|
if (table != to || table->flags & EcsTableHasUnion) { |
|
/* Add edges are appended to refs.next */ |
|
ecs_graph_edge_hdr_t *to_refs = &to->node.refs; |
|
ecs_graph_edge_hdr_t *next = to_refs->next; |
|
|
|
to_refs->next = &edge->hdr; |
|
edge->hdr.prev = to_refs; |
|
|
|
edge->hdr.next = next; |
|
if (next) { |
|
next->prev = &edge->hdr; |
|
} |
|
|
|
flecs_compute_table_diff(world, table, to, edge, id); |
|
} |
|
} |
|
|
|
static |
|
void flecs_init_edge_for_remove( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_graph_edge_t *edge, |
|
ecs_id_t id, |
|
ecs_table_t *to) |
|
{ |
|
flecs_table_init_edge(table, edge, id, to); |
|
|
|
flecs_table_ensure_hi_edge(world, &table->node.remove, id); |
|
|
|
if (table != to) { |
|
/* Remove edges are appended to refs.prev */ |
|
ecs_graph_edge_hdr_t *to_refs = &to->node.refs; |
|
ecs_graph_edge_hdr_t *prev = to_refs->prev; |
|
|
|
to_refs->prev = &edge->hdr; |
|
edge->hdr.next = to_refs; |
|
|
|
edge->hdr.prev = prev; |
|
if (prev) { |
|
prev->next = &edge->hdr; |
|
} |
|
|
|
flecs_compute_table_diff(world, table, to, edge, id); |
|
} |
|
} |
|
|
|
static |
|
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 = flecs_find_table_without(world, node, id); |
|
flecs_init_edge_for_remove(world, node, edge, id, to); |
|
return to; |
|
} |
|
|
|
static |
|
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 = flecs_find_table_with(world, node, id); |
|
flecs_init_edge_for_add(world, node, edge, id, to); |
|
return to; |
|
} |
|
|
|
ecs_table_t* flecs_table_traverse_remove( |
|
ecs_world_t *world, |
|
ecs_table_t *node, |
|
ecs_id_t *id_ptr, |
|
ecs_table_diff_t *diff) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
node = node ? node : &world->store.root; |
|
|
|
/* Removing 0 from an entity is not valid */ |
|
ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_id_t id = id_ptr[0]; |
|
ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.remove, id); |
|
ecs_table_t *to = edge->to; |
|
|
|
if (!to) { |
|
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); |
|
} |
|
|
|
if (node != to) { |
|
if (edge->diff) { |
|
*diff = *edge->diff; |
|
} else { |
|
diff->added.count = 0; |
|
diff->removed.array = id_ptr; |
|
diff->removed.count = 1; |
|
} |
|
} |
|
|
|
return to; |
|
error: |
|
return NULL; |
|
} |
|
|
|
ecs_table_t* flecs_table_traverse_add( |
|
ecs_world_t *world, |
|
ecs_table_t *node, |
|
ecs_id_t *id_ptr, |
|
ecs_table_diff_t *diff) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
node = node ? node : &world->store.root; |
|
|
|
/* Adding 0 to an entity is not valid */ |
|
ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_id_t id = id_ptr[0]; |
|
ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.add, id); |
|
ecs_table_t *to = edge->to; |
|
|
|
if (!to) { |
|
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); |
|
} |
|
|
|
if (node != to || edge->diff) { |
|
if (edge->diff) { |
|
*diff = *edge->diff; |
|
} else { |
|
diff->added.array = id_ptr; |
|
diff->added.count = 1; |
|
diff->removed.count = 0; |
|
} |
|
} |
|
|
|
return to; |
|
error: |
|
return NULL; |
|
} |
|
|
|
ecs_table_t* flecs_table_find_or_create( |
|
ecs_world_t *world, |
|
ecs_type_t *type) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
return flecs_table_ensure(world, type, false, NULL); |
|
} |
|
|
|
void flecs_init_root_table( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
world->store.root.type = (ecs_type_t){0}; |
|
world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); |
|
flecs_init_table(world, &world->store.root, NULL); |
|
|
|
/* Ensure table indices start at 1, as 0 is reserved for the root */ |
|
uint64_t new_id = flecs_sparse_new_id(&world->store.tables); |
|
ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); |
|
(void)new_id; |
|
} |
|
|
|
void flecs_table_clear_edges( |
|
ecs_world_t *world, |
|
ecs_table_t *table) |
|
{ |
|
(void)world; |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
ecs_log_push_1(); |
|
|
|
ecs_map_iter_t it; |
|
ecs_graph_node_t *table_node = &table->node; |
|
ecs_graph_edges_t *node_add = &table_node->add; |
|
ecs_graph_edges_t *node_remove = &table_node->remove; |
|
ecs_map_t *add_hi = node_add->hi; |
|
ecs_map_t *remove_hi = node_remove->hi; |
|
ecs_graph_edge_hdr_t *node_refs = &table_node->refs; |
|
|
|
/* Cleanup outgoing edges */ |
|
it = ecs_map_iter(add_hi); |
|
while (ecs_map_next(&it)) { |
|
flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); |
|
} |
|
|
|
it = ecs_map_iter(remove_hi); |
|
while (ecs_map_next(&it)) { |
|
flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); |
|
} |
|
|
|
/* Cleanup incoming add edges */ |
|
ecs_graph_edge_hdr_t *next, *cur = node_refs->next; |
|
if (cur) { |
|
do { |
|
ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; |
|
ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); |
|
next = cur->next; |
|
flecs_table_remove_edge(world, &edge->from->node.add, edge->id, edge); |
|
} while ((cur = next)); |
|
} |
|
|
|
/* Cleanup incoming remove edges */ |
|
cur = node_refs->prev; |
|
if (cur) { |
|
do { |
|
ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; |
|
ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); |
|
next = cur->prev; |
|
flecs_table_remove_edge(world, &edge->from->node.remove, edge->id, edge); |
|
} while ((cur = next)); |
|
} |
|
|
|
if (node_add->lo) { |
|
flecs_bfree(&world->allocators.graph_edge_lo, node_add->lo); |
|
} |
|
if (node_remove->lo) { |
|
flecs_bfree(&world->allocators.graph_edge_lo, node_remove->lo); |
|
} |
|
|
|
ecs_map_fini(add_hi); |
|
ecs_map_fini(remove_hi); |
|
flecs_free_t(&world->allocator, ecs_map_t, add_hi); |
|
flecs_free_t(&world->allocator, ecs_map_t, remove_hi); |
|
table_node->add.lo = NULL; |
|
table_node->remove.lo = NULL; |
|
table_node->add.hi = NULL; |
|
table_node->remove.hi = NULL; |
|
|
|
ecs_log_pop_1(); |
|
} |
|
|
|
/* Public convenience functions for traversing table graph */ |
|
ecs_table_t* ecs_table_add_id( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t id) |
|
{ |
|
ecs_table_diff_t diff; |
|
return flecs_table_traverse_add(world, table, &id, &diff); |
|
} |
|
|
|
ecs_table_t* ecs_table_remove_id( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_id_t id) |
|
{ |
|
ecs_table_diff_t diff; |
|
return flecs_table_traverse_remove(world, table, &id, &diff); |
|
} |
|
|
|
ecs_table_t* ecs_table_find( |
|
ecs_world_t *world, |
|
const ecs_id_t *ids, |
|
int32_t id_count) |
|
{ |
|
ecs_type_t type = { |
|
.array = ECS_CONST_CAST(ecs_id_t*, ids), |
|
.count = id_count |
|
}; |
|
return flecs_table_ensure(world, &type, false, NULL); |
|
} |
|
|
|
/** |
|
* @file expr/deserialize.c |
|
* @brief Deserialize flecs string format into (component) values. |
|
*/ |
|
|
|
#include <ctype.h> |
|
|
|
#ifdef FLECS_EXPR |
|
|
|
/* String deserializer for values & simple expressions */ |
|
|
|
/* Order in enumeration is important, as it is used for precedence */ |
|
typedef enum ecs_expr_oper_t { |
|
EcsExprOperUnknown, |
|
EcsLeftParen, |
|
EcsCondAnd, |
|
EcsCondOr, |
|
EcsCondEq, |
|
EcsCondNeq, |
|
EcsCondGt, |
|
EcsCondGtEq, |
|
EcsCondLt, |
|
EcsCondLtEq, |
|
EcsShiftLeft, |
|
EcsShiftRight, |
|
EcsAdd, |
|
EcsSub, |
|
EcsMul, |
|
EcsDiv, |
|
EcsMin |
|
} ecs_expr_oper_t; |
|
|
|
/* Used to track temporary values */ |
|
#define EXPR_MAX_STACK_SIZE (256) |
|
|
|
typedef struct ecs_expr_value_t { |
|
const ecs_type_info_t *ti; |
|
void *ptr; |
|
} ecs_expr_value_t; |
|
|
|
typedef struct ecs_value_stack_t { |
|
ecs_expr_value_t values[EXPR_MAX_STACK_SIZE]; |
|
ecs_stack_cursor_t *cursor; |
|
ecs_stack_t *stack; |
|
ecs_stage_t *stage; |
|
int32_t count; |
|
} ecs_value_stack_t; |
|
|
|
static |
|
const char* flecs_parse_expr( |
|
ecs_world_t *world, |
|
ecs_value_stack_t *stack, |
|
const char *ptr, |
|
ecs_value_t *value, |
|
ecs_expr_oper_t op, |
|
const ecs_parse_expr_desc_t *desc); |
|
|
|
static |
|
void* flecs_expr_value_new( |
|
ecs_value_stack_t *stack, |
|
ecs_entity_t type) |
|
{ |
|
ecs_stage_t *stage = stack->stage; |
|
ecs_world_t *world = stage->world; |
|
ecs_id_record_t *idr = flecs_id_record_get(world, type); |
|
if (!idr) { |
|
return NULL; |
|
} |
|
|
|
const ecs_type_info_t *ti = idr->type_info; |
|
if (!ti) { |
|
return NULL; |
|
} |
|
|
|
ecs_assert(ti->size != 0, ECS_INTERNAL_ERROR, NULL); |
|
void *result = flecs_stack_alloc(stack->stack, ti->size, ti->alignment); |
|
if (ti->hooks.ctor) { |
|
ti->hooks.ctor(result, 1, ti); |
|
} else { |
|
ecs_os_memset(result, 0, ti->size); |
|
} |
|
if (ti->hooks.dtor) { |
|
/* Track values that have destructors */ |
|
stack->values[stack->count].ti = ti; |
|
stack->values[stack->count].ptr = result; |
|
stack->count ++; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static |
|
const char* flecs_str_to_expr_oper( |
|
const char *str, |
|
ecs_expr_oper_t *op) |
|
{ |
|
if (!ecs_os_strncmp(str, "+", 1)) { |
|
*op = EcsAdd; |
|
return str + 1; |
|
} else if (!ecs_os_strncmp(str, "-", 1)) { |
|
*op = EcsSub; |
|
return str + 1; |
|
} else if (!ecs_os_strncmp(str, "*", 1)) { |
|
*op = EcsMul; |
|
return str + 1; |
|
} else if (!ecs_os_strncmp(str, "/", 1)) { |
|
*op = EcsDiv; |
|
return str + 1; |
|
} else if (!ecs_os_strncmp(str, "&&", 2)) { |
|
*op = EcsCondAnd; |
|
return str + 2; |
|
} else if (!ecs_os_strncmp(str, "||", 2)) { |
|
*op = EcsCondOr; |
|
return str + 2; |
|
} else if (!ecs_os_strncmp(str, "==", 2)) { |
|
*op = EcsCondEq; |
|
return str + 2; |
|
} else if (!ecs_os_strncmp(str, "!=", 2)) { |
|
*op = EcsCondNeq; |
|
return str + 2; |
|
} else if (!ecs_os_strncmp(str, ">=", 2)) { |
|
*op = EcsCondGtEq; |
|
return str + 2; |
|
} else if (!ecs_os_strncmp(str, "<=", 2)) { |
|
*op = EcsCondLtEq; |
|
return str + 2; |
|
} else if (!ecs_os_strncmp(str, ">>", 2)) { |
|
*op = EcsShiftRight; |
|
return str + 2; |
|
} else if (!ecs_os_strncmp(str, "<<", 2)) { |
|
*op = EcsShiftLeft; |
|
return str + 2; |
|
} else if (!ecs_os_strncmp(str, ">", 1)) { |
|
*op = EcsCondGt; |
|
return str + 1; |
|
} else if (!ecs_os_strncmp(str, "<", 1)) { |
|
*op = EcsCondLt; |
|
return str + 1; |
|
} |
|
|
|
*op = EcsExprOperUnknown; |
|
return NULL; |
|
} |
|
|
|
const char *ecs_parse_expr_token( |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
char *token) |
|
{ |
|
char *token_ptr = token; |
|
|
|
if (ptr[0] == '/') { |
|
char ch; |
|
if (ptr[1] == '/') { |
|
// Single line comment |
|
for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} |
|
token[0] = 0; |
|
return ecs_parse_ws_eol(ptr); |
|
} else if (ptr[1] == '*') { |
|
// Multi line comment |
|
for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { |
|
if (ch == '*' && ptr[1] == '/') { |
|
token[0] = 0; |
|
return ecs_parse_ws_eol(ptr + 2); |
|
} |
|
} |
|
|
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing */ for multiline comment"); |
|
return NULL; |
|
} |
|
} |
|
|
|
ecs_expr_oper_t op; |
|
if (ptr[0] == '(') { |
|
token[0] = '('; |
|
token[1] = 0; |
|
return ptr + 1; |
|
} else if (ptr[0] != '-') { |
|
const char *tptr = flecs_str_to_expr_oper(ptr, &op); |
|
if (tptr) { |
|
ecs_os_strncpy(token, ptr, tptr - ptr); |
|
return tptr; |
|
} |
|
} |
|
|
|
while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr, 0))) { |
|
if (ptr[0] == '|' && ptr[1] != '|') { |
|
token_ptr = &token_ptr[ecs_os_strlen(token_ptr)]; |
|
token_ptr[0] = '|'; |
|
token_ptr[1] = '\0'; |
|
token_ptr ++; |
|
ptr ++; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* flecs_parse_multiline_string( |
|
ecs_meta_cursor_t *cur, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr) |
|
{ |
|
/* Multiline string */ |
|
ecs_strbuf_t str = ECS_STRBUF_INIT; |
|
char ch; |
|
while ((ch = ptr[0]) && (ch != '`')) { |
|
if (ch == '\\' && ptr[1] == '`') { |
|
ch = '`'; |
|
ptr ++; |
|
} |
|
ecs_strbuf_appendch(&str, ch); |
|
ptr ++; |
|
} |
|
|
|
if (ch != '`') { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing '`' to close multiline string"); |
|
goto error; |
|
} |
|
char *strval = ecs_strbuf_get(&str); |
|
if (ecs_meta_set_string(cur, strval) != 0) { |
|
goto error; |
|
} |
|
ecs_os_free(strval); |
|
|
|
return ptr + 1; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
bool flecs_parse_is_float( |
|
const char *ptr) |
|
{ |
|
ecs_assert(isdigit(ptr[0]), ECS_INTERNAL_ERROR, NULL); |
|
char ch; |
|
while ((ch = (++ptr)[0])) { |
|
if (ch == '.' || ch == 'e') { |
|
return true; |
|
} |
|
if (!isdigit(ch)) { |
|
return false; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/* Attempt to resolve variable dotexpression to value (foo.bar) */ |
|
static |
|
ecs_value_t flecs_dotresolve_var( |
|
ecs_world_t *world, |
|
ecs_vars_t *vars, |
|
char *token) |
|
{ |
|
char *dot = strchr(token, '.'); |
|
if (!dot) { |
|
return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; |
|
} |
|
|
|
dot[0] = '\0'; |
|
|
|
const ecs_expr_var_t *var = ecs_vars_lookup(vars, token); |
|
if (!var) { |
|
return (ecs_value_t){0}; |
|
} |
|
|
|
ecs_meta_cursor_t cur = ecs_meta_cursor( |
|
world, var->value.type, var->value.ptr); |
|
ecs_meta_push(&cur); |
|
if (ecs_meta_dotmember(&cur, dot + 1) != 0) { |
|
return (ecs_value_t){0}; |
|
} |
|
|
|
return (ecs_value_t){ |
|
.ptr = ecs_meta_get_ptr(&cur), |
|
.type = ecs_meta_get_type(&cur) |
|
}; |
|
} |
|
|
|
static |
|
int flecs_meta_call( |
|
ecs_world_t *world, |
|
ecs_value_stack_t *stack, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
ecs_meta_cursor_t *cur, |
|
const char *function) |
|
{ |
|
ecs_entity_t type = ecs_meta_get_type(cur); |
|
void *value_ptr = ecs_meta_get_ptr(cur); |
|
|
|
if (!ecs_os_strcmp(function, "parent")) { |
|
if (type != ecs_id(ecs_entity_t)) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"parent() can only be called on entity"); |
|
return -1; |
|
} |
|
|
|
*(ecs_entity_t*)value_ptr = ecs_get_parent( |
|
world, *(ecs_entity_t*)value_ptr); |
|
} else if (!ecs_os_strcmp(function, "name")) { |
|
if (type != ecs_id(ecs_entity_t)) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"name() can only be called on entity"); |
|
return -1; |
|
} |
|
|
|
char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); |
|
*result = ecs_os_strdup(ecs_get_name(world, *(ecs_entity_t*)value_ptr)); |
|
*cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); |
|
} else if (!ecs_os_strcmp(function, "doc_name")) { |
|
#ifdef FLECS_DOC |
|
if (type != ecs_id(ecs_entity_t)) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"name() can only be called on entity"); |
|
return -1; |
|
} |
|
|
|
char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); |
|
*result = ecs_os_strdup(ecs_doc_get_name(world, *(ecs_entity_t*)value_ptr)); |
|
*cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); |
|
#else |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"doc_name() is not available without FLECS_DOC addon"); |
|
return -1; |
|
#endif |
|
} else { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unknown function '%s'", function); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Determine the type of an expression from the first character(s). This allows |
|
* us to initialize a storage for a type if none was provided. */ |
|
static |
|
ecs_entity_t flecs_parse_discover_type( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
ecs_entity_t input_type, |
|
const ecs_parse_expr_desc_t *desc) |
|
{ |
|
/* String literal */ |
|
if (ptr[0] == '"' || ptr[0] == '`') { |
|
if (input_type == ecs_id(ecs_char_t)) { |
|
return input_type; |
|
} |
|
return ecs_id(ecs_string_t); |
|
} |
|
|
|
/* Negative number literal */ |
|
if (ptr[0] == '-') { |
|
if (!isdigit(ptr[1])) { |
|
ecs_parser_error(name, expr, ptr - expr, "invalid literal"); |
|
return 0; |
|
} |
|
if (flecs_parse_is_float(ptr + 1)) { |
|
return ecs_id(ecs_f64_t); |
|
} else { |
|
return ecs_id(ecs_i64_t); |
|
} |
|
} |
|
|
|
/* Positive number literal */ |
|
if (isdigit(ptr[0])) { |
|
if (flecs_parse_is_float(ptr)) { |
|
return ecs_id(ecs_f64_t); |
|
} else { |
|
return ecs_id(ecs_u64_t); |
|
} |
|
} |
|
|
|
/* Variable */ |
|
if (ptr[0] == '$') { |
|
if (!desc || !desc->vars) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved variable (no variable scope)"); |
|
return 0; |
|
} |
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
if (ecs_parse_expr_token(name, expr, &ptr[1], token) == NULL) { |
|
return 0; |
|
} |
|
|
|
const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); |
|
if (!var) { |
|
ecs_size_t len = ecs_os_strlen(token); |
|
if (ptr[len + 1] == '(') { |
|
while ((len > 0) && (token[len] != '.')) { |
|
len --; |
|
} |
|
if (token[len] == '.') { |
|
token[len] = '\0'; |
|
} |
|
} |
|
|
|
ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); |
|
if (v.type) { |
|
return v.type; |
|
} |
|
|
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved variable '%s'", token); |
|
return 0; |
|
} |
|
return var->value.type; |
|
} |
|
|
|
/* Boolean */ |
|
if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { |
|
if (!isalpha(ptr[4]) && ptr[4] != '_') { |
|
return ecs_id(ecs_bool_t); |
|
} |
|
} |
|
if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { |
|
if (!isalpha(ptr[5]) && ptr[5] != '_') { |
|
return ecs_id(ecs_bool_t); |
|
} |
|
} |
|
|
|
/* Entity identifier */ |
|
if (isalpha(ptr[0])) { |
|
if (!input_type) { /* Identifier could also be enum/bitmask constant */ |
|
return ecs_id(ecs_entity_t); |
|
} |
|
} |
|
|
|
/* If no default type was provided we can't automatically deduce the type of |
|
* composite/collection expressions. */ |
|
if (!input_type) { |
|
if (ptr[0] == '{') { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unknown type for composite literal"); |
|
return 0; |
|
} |
|
|
|
if (ptr[0] == '[') { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unknown type for collection literal"); |
|
return 0; |
|
} |
|
|
|
ecs_parser_error(name, expr, ptr - expr, "invalid expression"); |
|
} |
|
|
|
return input_type; |
|
} |
|
|
|
/* Normalize types to their largest representation. |
|
* Rather than taking the original type of a value, use the largest |
|
* representation of the type so we don't have to worry about overflowing the |
|
* original type in the operation. */ |
|
static |
|
ecs_entity_t flecs_largest_type( |
|
const EcsPrimitive *type) |
|
{ |
|
switch(type->kind) { |
|
case EcsBool: return ecs_id(ecs_bool_t); |
|
case EcsChar: return ecs_id(ecs_char_t); |
|
case EcsByte: return ecs_id(ecs_u8_t); |
|
case EcsU8: return ecs_id(ecs_u64_t); |
|
case EcsU16: return ecs_id(ecs_u64_t); |
|
case EcsU32: return ecs_id(ecs_u64_t); |
|
case EcsU64: return ecs_id(ecs_u64_t); |
|
case EcsI8: return ecs_id(ecs_i64_t); |
|
case EcsI16: return ecs_id(ecs_i64_t); |
|
case EcsI32: return ecs_id(ecs_i64_t); |
|
case EcsI64: return ecs_id(ecs_i64_t); |
|
case EcsF32: return ecs_id(ecs_f64_t); |
|
case EcsF64: return ecs_id(ecs_f64_t); |
|
case EcsUPtr: return ecs_id(ecs_u64_t); |
|
case EcsIPtr: return ecs_id(ecs_i64_t); |
|
case EcsString: return ecs_id(ecs_string_t); |
|
case EcsEntity: return ecs_id(ecs_entity_t); |
|
case EcsId: return ecs_id(ecs_id_t); |
|
default: ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
/** Test if a normalized type can promote to another type in an expression */ |
|
static |
|
bool flecs_is_type_number( |
|
ecs_entity_t type) |
|
{ |
|
if (type == ecs_id(ecs_bool_t)) return false; |
|
else if (type == ecs_id(ecs_char_t)) return false; |
|
else if (type == ecs_id(ecs_u8_t)) return false; |
|
else if (type == ecs_id(ecs_u64_t)) return true; |
|
else if (type == ecs_id(ecs_i64_t)) return true; |
|
else if (type == ecs_id(ecs_f64_t)) return true; |
|
else if (type == ecs_id(ecs_string_t)) return false; |
|
else if (type == ecs_id(ecs_entity_t)) return false; |
|
else return false; |
|
} |
|
|
|
static |
|
bool flecs_oper_valid_for_type( |
|
ecs_entity_t type, |
|
ecs_expr_oper_t op) |
|
{ |
|
switch(op) { |
|
case EcsAdd: |
|
case EcsSub: |
|
case EcsMul: |
|
case EcsDiv: |
|
return flecs_is_type_number(type); |
|
case EcsCondEq: |
|
case EcsCondNeq: |
|
case EcsCondAnd: |
|
case EcsCondOr: |
|
case EcsCondGt: |
|
case EcsCondGtEq: |
|
case EcsCondLt: |
|
case EcsCondLtEq: |
|
return flecs_is_type_number(type) || |
|
(type == ecs_id(ecs_bool_t)) || |
|
(type == ecs_id(ecs_char_t)) || |
|
(type == ecs_id(ecs_entity_t)); |
|
case EcsShiftLeft: |
|
case EcsShiftRight: |
|
return (type == ecs_id(ecs_u64_t)); |
|
case EcsExprOperUnknown: |
|
case EcsLeftParen: |
|
case EcsMin: |
|
return false; |
|
default: |
|
ecs_abort(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
/** Promote type to most expressive (f64 > i64 > u64) */ |
|
static |
|
ecs_entity_t flecs_promote_type( |
|
ecs_entity_t type, |
|
ecs_entity_t promote_to) |
|
{ |
|
if (type == ecs_id(ecs_u64_t)) { |
|
return promote_to; |
|
} |
|
if (promote_to == ecs_id(ecs_u64_t)) { |
|
return type; |
|
} |
|
if (type == ecs_id(ecs_f64_t)) { |
|
return type; |
|
} |
|
if (promote_to == ecs_id(ecs_f64_t)) { |
|
return promote_to; |
|
} |
|
return ecs_id(ecs_i64_t); |
|
} |
|
|
|
static |
|
int flecs_oper_precedence( |
|
ecs_expr_oper_t left, |
|
ecs_expr_oper_t right) |
|
{ |
|
return (left > right) - (left < right); |
|
} |
|
|
|
static |
|
void flecs_value_cast( |
|
ecs_world_t *world, |
|
ecs_value_stack_t *stack, |
|
ecs_value_t *value, |
|
ecs_entity_t type) |
|
{ |
|
if (value->type == type) { |
|
return; |
|
} |
|
|
|
ecs_value_t result; |
|
result.type = type; |
|
result.ptr = flecs_expr_value_new(stack, type); |
|
|
|
if (value->ptr) { |
|
ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); |
|
ecs_meta_set_value(&cur, value); |
|
} |
|
|
|
*value = result; |
|
} |
|
|
|
static |
|
bool flecs_expr_op_is_equality( |
|
ecs_expr_oper_t op) |
|
{ |
|
switch(op) { |
|
case EcsCondEq: |
|
case EcsCondNeq: |
|
case EcsCondGt: |
|
case EcsCondGtEq: |
|
case EcsCondLt: |
|
case EcsCondLtEq: |
|
return true; |
|
case EcsCondAnd: |
|
case EcsCondOr: |
|
case EcsShiftLeft: |
|
case EcsShiftRight: |
|
case EcsAdd: |
|
case EcsSub: |
|
case EcsMul: |
|
case EcsDiv: |
|
case EcsExprOperUnknown: |
|
case EcsLeftParen: |
|
case EcsMin: |
|
return false; |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); |
|
} |
|
error: |
|
return false; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_binary_expr_type( |
|
ecs_world_t *world, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
ecs_value_t *lvalue, |
|
ecs_value_t *rvalue, |
|
ecs_expr_oper_t op, |
|
ecs_entity_t *operand_type_out) |
|
{ |
|
ecs_entity_t result_type = 0, operand_type = 0; |
|
|
|
switch(op) { |
|
case EcsDiv: |
|
/* Result type of a division is always a float */ |
|
*operand_type_out = ecs_id(ecs_f64_t); |
|
return ecs_id(ecs_f64_t); |
|
case EcsCondAnd: |
|
case EcsCondOr: |
|
/* Result type of a condition operator is always a bool */ |
|
*operand_type_out = ecs_id(ecs_bool_t); |
|
return ecs_id(ecs_bool_t); |
|
case EcsCondEq: |
|
case EcsCondNeq: |
|
case EcsCondGt: |
|
case EcsCondGtEq: |
|
case EcsCondLt: |
|
case EcsCondLtEq: |
|
/* Result type of equality operator is always bool, but operand types |
|
* should not be casted to bool */ |
|
result_type = ecs_id(ecs_bool_t); |
|
break; |
|
case EcsShiftLeft: |
|
case EcsShiftRight: |
|
case EcsAdd: |
|
case EcsSub: |
|
case EcsMul: |
|
case EcsExprOperUnknown: |
|
case EcsLeftParen: |
|
case EcsMin: |
|
break; |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); |
|
} |
|
|
|
/* Result type for arithmetic operators is determined by operands */ |
|
const EcsPrimitive *ltype_ptr = ecs_get(world, lvalue->type, EcsPrimitive); |
|
const EcsPrimitive *rtype_ptr = ecs_get(world, rvalue->type, EcsPrimitive); |
|
if (!ltype_ptr || !rtype_ptr) { |
|
char *lname = ecs_get_fullpath(world, lvalue->type); |
|
char *rname = ecs_get_fullpath(world, rvalue->type); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid non-primitive type in binary expression (%s, %s)", |
|
lname, rname); |
|
ecs_os_free(lname); |
|
ecs_os_free(rname); |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ltype = flecs_largest_type(ltype_ptr); |
|
ecs_entity_t rtype = flecs_largest_type(rtype_ptr); |
|
if (ltype == rtype) { |
|
operand_type = ltype; |
|
goto done; |
|
} |
|
|
|
if (flecs_expr_op_is_equality(op)) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"mismatching types in equality expression"); |
|
return 0; |
|
} |
|
|
|
if (!flecs_is_type_number(ltype) || !flecs_is_type_number(rtype)) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"incompatible types in binary expression"); |
|
return 0; |
|
} |
|
|
|
operand_type = flecs_promote_type(ltype, rtype); |
|
|
|
done: |
|
if (op == EcsSub && operand_type == ecs_id(ecs_u64_t)) { |
|
/* Result of subtracting two unsigned ints can be negative */ |
|
operand_type = ecs_id(ecs_i64_t); |
|
} |
|
|
|
if (!result_type) { |
|
result_type = operand_type; |
|
} |
|
|
|
*operand_type_out = operand_type; |
|
return result_type; |
|
error: |
|
return 0; |
|
} |
|
|
|
/* Macro's to let the compiler do the operations & conversion work for us */ |
|
|
|
#define ECS_VALUE_GET(value, T) (*(T*)value->ptr) |
|
|
|
#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ |
|
ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) |
|
|
|
#define ECS_BINARY_OP(left, right, result, op)\ |
|
if (left->type == ecs_id(ecs_u64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ |
|
} else if (left->type == ecs_id(ecs_i64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ |
|
} else if (left->type == ecs_id(ecs_f64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ |
|
} else {\ |
|
ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ |
|
} |
|
|
|
#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ |
|
if (left->type == ecs_id(ecs_u64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ |
|
} else if (left->type == ecs_id(ecs_i64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ |
|
} else if (left->type == ecs_id(ecs_f64_t)) { \ |
|
ecs_parser_error(name, expr, ptr - expr, "unsupported operator for floating point");\ |
|
return -1;\ |
|
} else if (left->type == ecs_id(ecs_u8_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ |
|
} else if (left->type == ecs_id(ecs_char_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ |
|
} else if (left->type == ecs_id(ecs_bool_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ |
|
} else {\ |
|
ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ |
|
} |
|
|
|
#define ECS_BINARY_COND_OP(left, right, result, op)\ |
|
if (left->type == ecs_id(ecs_u64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ |
|
} else if (left->type == ecs_id(ecs_i64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ |
|
} else if (left->type == ecs_id(ecs_f64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ |
|
} else if (left->type == ecs_id(ecs_u8_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ |
|
} else if (left->type == ecs_id(ecs_char_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ |
|
} else if (left->type == ecs_id(ecs_bool_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ |
|
} else {\ |
|
ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ |
|
} |
|
|
|
#define ECS_BINARY_BOOL_OP(left, right, result, op)\ |
|
if (left->type == ecs_id(ecs_bool_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ |
|
} else {\ |
|
ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ |
|
} |
|
|
|
#define ECS_BINARY_UINT_OP(left, right, result, op)\ |
|
if (left->type == ecs_id(ecs_u64_t)) { \ |
|
ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ |
|
} else {\ |
|
ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ |
|
} |
|
|
|
static |
|
int flecs_binary_expr_do( |
|
ecs_world_t *world, |
|
ecs_value_stack_t *stack, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
ecs_value_t *lvalue, |
|
ecs_value_t *rvalue, |
|
ecs_value_t *result, |
|
ecs_expr_oper_t op) |
|
{ |
|
/* Find expression type */ |
|
ecs_entity_t operand_type, type = flecs_binary_expr_type( |
|
world, name, expr, ptr, lvalue, rvalue, op, &operand_type); |
|
if (!type) { |
|
goto error; |
|
} |
|
|
|
if (!flecs_oper_valid_for_type(type, op)) { |
|
ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); |
|
goto error; |
|
} |
|
|
|
flecs_value_cast(world, stack, lvalue, operand_type); |
|
flecs_value_cast(world, stack, rvalue, operand_type); |
|
|
|
ecs_value_t *storage = result; |
|
ecs_value_t tmp_storage = {0}; |
|
if (result->type != type) { |
|
storage = &tmp_storage; |
|
storage->type = type; |
|
storage->ptr = flecs_expr_value_new(stack, type); |
|
} |
|
|
|
switch(op) { |
|
case EcsAdd: |
|
ECS_BINARY_OP(lvalue, rvalue, storage, +); |
|
break; |
|
case EcsSub: |
|
ECS_BINARY_OP(lvalue, rvalue, storage, -); |
|
break; |
|
case EcsMul: |
|
ECS_BINARY_OP(lvalue, rvalue, storage, *); |
|
break; |
|
case EcsDiv: |
|
ECS_BINARY_OP(lvalue, rvalue, storage, /); |
|
break; |
|
case EcsCondEq: |
|
ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, ==); |
|
break; |
|
case EcsCondNeq: |
|
ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, !=); |
|
break; |
|
case EcsCondGt: |
|
ECS_BINARY_COND_OP(lvalue, rvalue, storage, >); |
|
break; |
|
case EcsCondGtEq: |
|
ECS_BINARY_COND_OP(lvalue, rvalue, storage, >=); |
|
break; |
|
case EcsCondLt: |
|
ECS_BINARY_COND_OP(lvalue, rvalue, storage, <); |
|
break; |
|
case EcsCondLtEq: |
|
ECS_BINARY_COND_OP(lvalue, rvalue, storage, <=); |
|
break; |
|
case EcsCondAnd: |
|
ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, &&); |
|
break; |
|
case EcsCondOr: |
|
ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, ||); |
|
break; |
|
case EcsShiftLeft: |
|
ECS_BINARY_UINT_OP(lvalue, rvalue, storage, <<); |
|
break; |
|
case EcsShiftRight: |
|
ECS_BINARY_UINT_OP(lvalue, rvalue, storage, >>); |
|
break; |
|
case EcsExprOperUnknown: |
|
case EcsLeftParen: |
|
case EcsMin: |
|
ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); |
|
goto error; |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (storage->ptr != result->ptr) { |
|
if (!result->ptr) { |
|
*result = *storage; |
|
} else { |
|
ecs_meta_cursor_t cur = ecs_meta_cursor(world, |
|
result->type, result->ptr); |
|
ecs_meta_set_value(&cur, storage); |
|
} |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
const char* flecs_binary_expr_parse( |
|
ecs_world_t *world, |
|
ecs_value_stack_t *stack, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
ecs_value_t *lvalue, |
|
ecs_value_t *result, |
|
ecs_expr_oper_t left_op, |
|
const ecs_parse_expr_desc_t *desc) |
|
{ |
|
ecs_entity_t result_type = result->type; |
|
do { |
|
ecs_expr_oper_t op; |
|
ptr = flecs_str_to_expr_oper(ptr, &op); |
|
if (!ptr) { |
|
ecs_parser_error(name, expr, ptr - expr, "invalid operator"); |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
|
|
ecs_value_t rvalue = {0}; |
|
const char *rptr = flecs_parse_expr(world, stack, ptr, &rvalue, op, desc); |
|
if (!rptr) { |
|
return NULL; |
|
} |
|
|
|
if (flecs_binary_expr_do(world, stack, name, expr, ptr, |
|
lvalue, &rvalue, result, op)) |
|
{ |
|
return NULL; |
|
} |
|
|
|
ptr = rptr; |
|
|
|
ecs_expr_oper_t right_op; |
|
flecs_str_to_expr_oper(rptr, &right_op); |
|
if (right_op > left_op) { |
|
if (result_type) { |
|
/* If result was initialized, preserve its value */ |
|
lvalue->type = result->type; |
|
lvalue->ptr = flecs_expr_value_new(stack, lvalue->type); |
|
ecs_value_copy(world, lvalue->type, lvalue->ptr, result->ptr); |
|
continue; |
|
} else { |
|
/* Otherwise move result to lvalue */ |
|
*lvalue = *result; |
|
ecs_os_zeromem(result); |
|
continue; |
|
} |
|
} |
|
|
|
break; |
|
} while (true); |
|
|
|
return ptr; |
|
} |
|
|
|
static |
|
const char* flecs_funccall_parse( |
|
ecs_world_t *world, |
|
ecs_value_stack_t *stack, |
|
const char *name, |
|
const char *expr, |
|
const char *ptr, |
|
char *token, |
|
ecs_meta_cursor_t *cur, |
|
ecs_value_t *value, |
|
bool isvar, |
|
const ecs_parse_expr_desc_t *desc) |
|
{ |
|
char *sep = strrchr(token, '.'); |
|
if (!sep) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing object for function call '%s'", token); |
|
return NULL; |
|
} |
|
|
|
sep[0] = '\0'; |
|
const char *function = sep + 1; |
|
|
|
if (!isvar) { |
|
if (ecs_meta_set_string(cur, token) != 0) { |
|
goto error; |
|
} |
|
} else { |
|
const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); |
|
ecs_meta_set_value(cur, &var->value); |
|
} |
|
|
|
do { |
|
if (!function[0]) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing function name for function call '%s'", token); |
|
return NULL; |
|
} |
|
|
|
if (flecs_meta_call(world, stack, name, expr, ptr, cur, |
|
function) != 0) |
|
{ |
|
goto error; |
|
} |
|
|
|
ecs_entity_t type = ecs_meta_get_type(cur); |
|
value->ptr = ecs_meta_get_ptr(cur); |
|
value->type = type; |
|
|
|
ptr += 2; |
|
if (ptr[0] != '.') { |
|
break; |
|
} |
|
|
|
ptr ++; |
|
char *paren = strchr(ptr, '('); |
|
if (!paren) { |
|
break; |
|
} |
|
|
|
ecs_size_t len = flecs_ito(int32_t, paren - ptr); |
|
if (len >= ECS_MAX_TOKEN_SIZE) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"token exceeds maximum token size"); |
|
goto error; |
|
} |
|
|
|
ecs_os_strncpy(token, ptr, len); |
|
token[len] = '\0'; |
|
function = token; |
|
|
|
ptr = paren; |
|
} while (true); |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* flecs_parse_expr( |
|
ecs_world_t *world, |
|
ecs_value_stack_t *stack, |
|
const char *ptr, |
|
ecs_value_t *value, |
|
ecs_expr_oper_t left_op, |
|
const ecs_parse_expr_desc_t *desc) |
|
{ |
|
ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); |
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
int depth = 0; |
|
ecs_value_t result = {0}; |
|
ecs_meta_cursor_t cur = {0}; |
|
const char *name = desc ? desc->name : NULL; |
|
const char *expr = desc ? desc->expr : NULL; |
|
token[0] = '\0'; |
|
expr = expr ? expr : ptr; |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
|
|
/* Check for postfix operators */ |
|
ecs_expr_oper_t unary_op = EcsExprOperUnknown; |
|
if (ptr[0] == '-' && !isdigit(ptr[1])) { |
|
unary_op = EcsMin; |
|
ptr = ecs_parse_ws_eol(ptr + 1); |
|
} |
|
|
|
/* Initialize storage and cursor. If expression starts with a '(' storage |
|
* will be initialized by a nested expression */ |
|
if (ptr[0] != '(') { |
|
ecs_entity_t type = flecs_parse_discover_type( |
|
world, name, expr, ptr, value->type, desc); |
|
if (!type) { |
|
return NULL; |
|
} |
|
|
|
result.type = type; |
|
if (type != value->type) { |
|
result.ptr = flecs_expr_value_new(stack, type); |
|
} else { |
|
result.ptr = value->ptr; |
|
} |
|
|
|
cur = ecs_meta_cursor(world, result.type, result.ptr); |
|
if (!cur.valid) { |
|
return NULL; |
|
} |
|
|
|
cur.lookup_action = desc ? desc->lookup_action : NULL; |
|
cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; |
|
} |
|
|
|
/* Loop that parses all values in a value scope */ |
|
while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { |
|
/* Used to track of the result of the parsed token can be used as the |
|
* lvalue for a binary expression */ |
|
bool is_lvalue = false; |
|
bool newline = false; |
|
|
|
if (!token[0]) { |
|
/* Comment */ |
|
continue; |
|
} |
|
|
|
if (!ecs_os_strcmp(token, "(")) { |
|
ecs_value_t temp_result, *out; |
|
if (!depth) { |
|
out = &result; |
|
} else { |
|
temp_result.type = ecs_meta_get_type(&cur); |
|
temp_result.ptr = ecs_meta_get_ptr(&cur); |
|
out = &temp_result; |
|
} |
|
|
|
/* Parenthesis, parse nested expression */ |
|
ptr = flecs_parse_expr(world, stack, ptr, out, EcsLeftParen, desc); |
|
if (ptr[0] != ')') { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"missing closing parenthesis"); |
|
return NULL; |
|
} |
|
ptr = ecs_parse_ws(ptr + 1); |
|
is_lvalue = true; |
|
|
|
} else if (!ecs_os_strcmp(token, "{")) { |
|
/* Parse nested value scope */ |
|
ecs_entity_t scope_type = ecs_meta_get_type(&cur); |
|
|
|
depth ++; /* Keep track of depth so we know when parsing is done */ |
|
if (ecs_meta_push(&cur) != 0) { |
|
goto error; |
|
} |
|
|
|
if (ecs_meta_is_collection(&cur)) { |
|
char *path = ecs_get_fullpath(world, scope_type); |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"expected '[' for collection type '%s'", path); |
|
ecs_os_free(path); |
|
return NULL; |
|
} |
|
} |
|
|
|
else if (!ecs_os_strcmp(token, "}")) { |
|
depth --; |
|
|
|
if (ecs_meta_is_collection(&cur)) { |
|
ecs_parser_error(name, expr, ptr - expr, "expected ']'"); |
|
return NULL; |
|
} |
|
|
|
if (ecs_meta_pop(&cur) != 0) { |
|
goto error; |
|
} |
|
} |
|
|
|
else if (!ecs_os_strcmp(token, "[")) { |
|
/* Open collection value scope */ |
|
depth ++; |
|
if (ecs_meta_push(&cur) != 0) { |
|
goto error; |
|
} |
|
|
|
if (!ecs_meta_is_collection(&cur)) { |
|
ecs_parser_error(name, expr, ptr - expr, "unexpected '['"); |
|
return NULL; |
|
} |
|
} |
|
|
|
else if (!ecs_os_strcmp(token, "]")) { |
|
depth --; |
|
|
|
if (!ecs_meta_is_collection(&cur)) { |
|
ecs_parser_error(name, expr, ptr - expr, "expected '}'"); |
|
return NULL; |
|
} |
|
|
|
if (ecs_meta_pop(&cur) != 0) { |
|
goto error; |
|
} |
|
} |
|
|
|
else if (!ecs_os_strcmp(token, "-")) { |
|
if (unary_op != EcsExprOperUnknown) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unexpected unary operator"); |
|
return NULL; |
|
} |
|
unary_op = EcsMin; |
|
} |
|
|
|
else if (!ecs_os_strcmp(token, ",")) { |
|
/* Move to next field */ |
|
if (ecs_meta_next(&cur) != 0) { |
|
goto error; |
|
} |
|
} |
|
|
|
else if (!ecs_os_strcmp(token, "null")) { |
|
if (ecs_meta_set_null(&cur) != 0) { |
|
goto error; |
|
} |
|
|
|
is_lvalue = true; |
|
} |
|
|
|
else if (token[0] == '\"') { |
|
/* Regular string */ |
|
if (ecs_meta_set_string_literal(&cur, token) != 0) { |
|
goto error; |
|
} |
|
|
|
is_lvalue = true; |
|
} |
|
|
|
else if (!ecs_os_strcmp(token, "`")) { |
|
/* Multiline string */ |
|
if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { |
|
goto error; |
|
} |
|
|
|
is_lvalue = true; |
|
|
|
} else if (token[0] == '$') { |
|
/* Variable */ |
|
if (!desc || !desc->vars) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved variable '%s' (no variable scope)", token); |
|
return NULL; |
|
} |
|
|
|
if (!token[1]) { |
|
/* Empty name means default to assigned member */ |
|
const char *member = ecs_meta_get_member(&cur); |
|
if (!member) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"invalid default variable outside member assignment"); |
|
return NULL; |
|
} |
|
ecs_os_strcpy(&token[1], member); |
|
} |
|
|
|
const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, &token[1]); |
|
if (!var) { |
|
if (ptr[0] == '(') { |
|
/* Function */ |
|
ptr = flecs_funccall_parse(world, stack, name, expr, ptr, |
|
&token[1], &cur, &result, true, desc); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
} else { |
|
ecs_value_t v = flecs_dotresolve_var(world, desc->vars, &token[1]); |
|
if (!v.ptr) { |
|
ecs_parser_error(name, expr, ptr - expr, |
|
"unresolved variable '%s'", token); |
|
return NULL; |
|
} else { |
|
ecs_meta_set_value(&cur, &v); |
|
} |
|
} |
|
} else { |
|
ecs_meta_set_value(&cur, &var->value); |
|
} |
|
is_lvalue = true; |
|
|
|
} else { |
|
const char *tptr = ecs_parse_ws(ptr); |
|
for (; ptr != tptr; ptr ++) { |
|
if (ptr[0] == '\n') { |
|
newline = true; |
|
} |
|
} |
|
|
|
if (ptr[0] == ':') { |
|
/* Member assignment */ |
|
ptr ++; |
|
if (ecs_meta_dotmember(&cur, token) != 0) { |
|
goto error; |
|
} |
|
} else if (ptr[0] == '(') { |
|
/* Function */ |
|
ptr = flecs_funccall_parse(world, stack, name, expr, ptr, |
|
token, &cur, &result, false, desc); |
|
if (!ptr) { |
|
goto error; |
|
} |
|
} else { |
|
if (ecs_meta_set_string(&cur, token) != 0) { |
|
goto error; |
|
} |
|
} |
|
|
|
is_lvalue = true; |
|
} |
|
|
|
/* If lvalue was parsed, apply operators. Expressions cannot start |
|
* directly after a newline character. */ |
|
if (is_lvalue && !newline) { |
|
if (unary_op != EcsExprOperUnknown) { |
|
if (unary_op == EcsMin) { |
|
int64_t v = -1; |
|
ecs_value_t lvalue = {.type = ecs_id(ecs_i64_t), .ptr = &v}; |
|
ecs_value_t *out, rvalue, temp_out = {0}; |
|
|
|
if (!depth) { |
|
rvalue = result; |
|
ecs_os_zeromem(&result); |
|
out = &result; |
|
} else { |
|
ecs_entity_t cur_type = ecs_meta_get_type(&cur); |
|
void *cur_ptr = ecs_meta_get_ptr(&cur); |
|
rvalue.type = cur_type; |
|
rvalue.ptr = cur_ptr; |
|
temp_out.type = cur_type; |
|
temp_out.ptr = cur_ptr; |
|
out = &temp_out; |
|
} |
|
|
|
flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, |
|
&rvalue, out, EcsMul); |
|
} |
|
unary_op = 0; |
|
} |
|
|
|
ecs_expr_oper_t right_op; |
|
flecs_str_to_expr_oper(ptr, &right_op); |
|
if (right_op) { |
|
/* This is a binary expression, test precedence to determine if |
|
* it should be evaluated here */ |
|
if (flecs_oper_precedence(left_op, right_op) < 0) { |
|
ecs_value_t lvalue; |
|
ecs_value_t *op_result = &result; |
|
ecs_value_t temp_storage; |
|
if (!depth) { |
|
/* Root level value, move result to lvalue storage */ |
|
lvalue = result; |
|
ecs_os_zeromem(&result); |
|
} else { |
|
/* Not a root level value. Move the parsed lvalue to a |
|
* temporary storage, and initialize the result value |
|
* for the binary operation with the current cursor */ |
|
ecs_entity_t cur_type = ecs_meta_get_type(&cur); |
|
void *cur_ptr = ecs_meta_get_ptr(&cur); |
|
lvalue.type = cur_type; |
|
lvalue.ptr = flecs_expr_value_new(stack, cur_type); |
|
ecs_value_copy(world, cur_type, lvalue.ptr, cur_ptr); |
|
temp_storage.type = cur_type; |
|
temp_storage.ptr = cur_ptr; |
|
op_result = &temp_storage; |
|
} |
|
|
|
/* Do the binary expression */ |
|
ptr = flecs_binary_expr_parse(world, stack, name, expr, ptr, |
|
&lvalue, op_result, left_op, desc); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (!depth) { |
|
/* Reached the end of the root scope */ |
|
break; |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
} |
|
|
|
if (!value->ptr) { |
|
value->type = result.type; |
|
value->ptr = flecs_expr_value_new(stack, result.type); |
|
} |
|
|
|
if (value->ptr != result.ptr) { |
|
cur = ecs_meta_cursor(world, value->type, value->ptr); |
|
ecs_meta_set_value(&cur, &result); |
|
} |
|
|
|
ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
const char* ecs_parse_expr( |
|
ecs_world_t *world, |
|
const char *ptr, |
|
ecs_value_t *value, |
|
const ecs_parse_expr_desc_t *desc) |
|
{ |
|
/* Prepare storage for temporary values */ |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_value_stack_t stack; |
|
stack.count = 0; |
|
stack.stage = stage; |
|
stack.stack = &stage->allocators.deser_stack; |
|
stack.cursor = flecs_stack_get_cursor(stack.stack); |
|
|
|
/* Parse expression */ |
|
bool storage_provided = value->ptr != NULL; |
|
ptr = flecs_parse_expr(world, &stack, ptr, value, EcsExprOperUnknown, desc); |
|
|
|
/* If no result value was provided, allocate one as we can't return a |
|
* pointer to a temporary storage */ |
|
if (!storage_provided && value->ptr) { |
|
ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); |
|
void *temp_storage = value->ptr; |
|
value->ptr = ecs_value_new(world, value->type); |
|
ecs_value_move_ctor(world, value->type, value->ptr, temp_storage); |
|
} |
|
|
|
/* Cleanup temporary values */ |
|
int i; |
|
for (i = 0; i < stack.count; i ++) { |
|
const ecs_type_info_t *ti = stack.values[i].ti; |
|
ecs_assert(ti->hooks.dtor != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ti->hooks.dtor(stack.values[i].ptr, 1, ti); |
|
} |
|
flecs_stack_restore_cursor(stack.stack, stack.cursor); |
|
|
|
return ptr; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file expr/serialize.c |
|
* @brief Serialize (component) values to flecs string format. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_EXPR |
|
|
|
static |
|
int flecs_expr_ser_type( |
|
const ecs_world_t *world, |
|
const ecs_vec_t *ser, |
|
const void *base, |
|
ecs_strbuf_t *str, |
|
bool is_expr); |
|
|
|
static |
|
int flecs_expr_ser_type_ops( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *ops, |
|
int32_t op_count, |
|
const void *base, |
|
ecs_strbuf_t *str, |
|
int32_t in_array, |
|
bool is_expr); |
|
|
|
static |
|
int flecs_expr_ser_type_op( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *base, |
|
ecs_strbuf_t *str, |
|
bool is_expr); |
|
|
|
static |
|
ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { |
|
return kind - EcsOpPrimitive; |
|
} |
|
|
|
/* Serialize a primitive value */ |
|
static |
|
int flecs_expr_ser_primitive( |
|
const ecs_world_t *world, |
|
ecs_primitive_kind_t kind, |
|
const void *base, |
|
ecs_strbuf_t *str, |
|
bool is_expr) |
|
{ |
|
switch(kind) { |
|
case EcsBool: |
|
if (*(const bool*)base) { |
|
ecs_strbuf_appendlit(str, "true"); |
|
} else { |
|
ecs_strbuf_appendlit(str, "false"); |
|
} |
|
break; |
|
case EcsChar: { |
|
char chbuf[3]; |
|
char ch = *(const char*)base; |
|
if (ch) { |
|
ecs_chresc(chbuf, *(const char*)base, '"'); |
|
if (is_expr) ecs_strbuf_appendch(str, '"'); |
|
ecs_strbuf_appendstr(str, chbuf); |
|
if (is_expr) ecs_strbuf_appendch(str, '"'); |
|
} else { |
|
ecs_strbuf_appendch(str, '0'); |
|
} |
|
break; |
|
} |
|
case EcsByte: |
|
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); |
|
break; |
|
case EcsU8: |
|
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); |
|
break; |
|
case EcsU16: |
|
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint16_t*)base)); |
|
break; |
|
case EcsU32: |
|
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint32_t*)base)); |
|
break; |
|
case EcsU64: |
|
ecs_strbuf_append(str, "%llu", *(const uint64_t*)base); |
|
break; |
|
case EcsI8: |
|
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int8_t*)base)); |
|
break; |
|
case EcsI16: |
|
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int16_t*)base)); |
|
break; |
|
case EcsI32: |
|
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int32_t*)base)); |
|
break; |
|
case EcsI64: |
|
ecs_strbuf_appendint(str, *(const int64_t*)base); |
|
break; |
|
case EcsF32: |
|
ecs_strbuf_appendflt(str, (double)*(const float*)base, 0); |
|
break; |
|
case EcsF64: |
|
ecs_strbuf_appendflt(str, *(const double*)base, 0); |
|
break; |
|
case EcsIPtr: |
|
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const intptr_t*)base)); |
|
break; |
|
case EcsUPtr: |
|
ecs_strbuf_append(str, "%u", *(const uintptr_t*)base); |
|
break; |
|
case EcsString: { |
|
const char *value = *ECS_CONST_CAST(const char**, base); |
|
if (value) { |
|
if (!is_expr) { |
|
ecs_strbuf_appendstr(str, value); |
|
} else { |
|
ecs_size_t length = ecs_stresc(NULL, 0, '"', value); |
|
if (length == ecs_os_strlen(value)) { |
|
ecs_strbuf_appendch(str, '"'); |
|
ecs_strbuf_appendstrn(str, value, length); |
|
ecs_strbuf_appendch(str, '"'); |
|
} else { |
|
char *out = ecs_os_malloc(length + 3); |
|
ecs_stresc(out + 1, length, '"', value); |
|
out[0] = '"'; |
|
out[length + 1] = '"'; |
|
out[length + 2] = '\0'; |
|
ecs_strbuf_appendstr(str, out); |
|
ecs_os_free(out); |
|
} |
|
} |
|
} else { |
|
ecs_strbuf_appendlit(str, "null"); |
|
} |
|
break; |
|
} |
|
case EcsEntity: { |
|
ecs_entity_t e = *(const ecs_entity_t*)base; |
|
if (!e) { |
|
ecs_strbuf_appendch(str, '0'); |
|
} else { |
|
ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str); |
|
} |
|
break; |
|
} |
|
case EcsId: { |
|
ecs_id_t id = *(const ecs_id_t*)base; |
|
if (!id) { |
|
ecs_strbuf_appendch(str, '0'); |
|
} else { |
|
ecs_id_str_buf(world, id, str); |
|
} |
|
break; |
|
} |
|
default: |
|
ecs_err("invalid primitive kind"); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Serialize enumeration */ |
|
static |
|
int flecs_expr_ser_enum( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *base, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); |
|
ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
int32_t val = *(const int32_t*)base; |
|
|
|
/* Enumeration constants are stored in a map that is keyed on the |
|
* enumeration value. */ |
|
ecs_enum_constant_t *c = ecs_map_get_deref(&enum_type->constants, |
|
ecs_enum_constant_t, (ecs_map_key_t)val); |
|
if (!c) { |
|
char *path = ecs_get_fullpath(world, op->type); |
|
ecs_err("value %d is not valid for enum type '%s'", val, path); |
|
ecs_os_free(path); |
|
goto error; |
|
} |
|
|
|
ecs_strbuf_appendstr(str, ecs_get_name(world, c->constant)); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Serialize bitmask */ |
|
static |
|
int flecs_expr_ser_bitmask( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *ptr, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); |
|
ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); |
|
uint32_t value = *(const uint32_t*)ptr; |
|
|
|
ecs_strbuf_list_push(str, "", "|"); |
|
|
|
/* Multiple flags can be set at a given time. Iterate through all the flags |
|
* and append the ones that are set. */ |
|
ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); |
|
int count = 0; |
|
while (ecs_map_next(&it)) { |
|
ecs_bitmask_constant_t *c = ecs_map_ptr(&it); |
|
ecs_map_key_t key = ecs_map_key(&it); |
|
if ((value & key) == key) { |
|
ecs_strbuf_list_appendstr(str, ecs_get_name(world, c->constant)); |
|
count ++; |
|
value -= (uint32_t)key; |
|
} |
|
} |
|
|
|
if (value != 0) { |
|
/* All bits must have been matched by a constant */ |
|
char *path = ecs_get_fullpath(world, op->type); |
|
ecs_err( |
|
"value for bitmask %s contains bits (%u) that cannot be mapped to constant", |
|
path, value); |
|
ecs_os_free(path); |
|
goto error; |
|
} |
|
|
|
if (!count) { |
|
ecs_strbuf_list_appendstr(str, "0"); |
|
} |
|
|
|
ecs_strbuf_list_pop(str, ""); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Serialize elements of a contiguous array */ |
|
static |
|
int expr_ser_elements( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *ops, |
|
int32_t op_count, |
|
const void *base, |
|
int32_t elem_count, |
|
int32_t elem_size, |
|
ecs_strbuf_t *str, |
|
bool is_array) |
|
{ |
|
ecs_strbuf_list_push(str, "[", ", "); |
|
|
|
const void *ptr = base; |
|
|
|
int i; |
|
for (i = 0; i < elem_count; i ++) { |
|
ecs_strbuf_list_next(str); |
|
if (flecs_expr_ser_type_ops( |
|
world, ops, op_count, ptr, str, is_array, true)) |
|
{ |
|
return -1; |
|
} |
|
ptr = ECS_OFFSET(ptr, elem_size); |
|
} |
|
|
|
ecs_strbuf_list_pop(str, "]"); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int expr_ser_type_elements( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void *base, |
|
int32_t elem_count, |
|
ecs_strbuf_t *str, |
|
bool is_array) |
|
{ |
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, type, EcsMetaTypeSerialized); |
|
ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
const EcsComponent *comp = ecs_get(world, type, EcsComponent); |
|
ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); |
|
int32_t op_count = ecs_vec_count(&ser->ops); |
|
return expr_ser_elements( |
|
world, ops, op_count, base, elem_count, comp->size, str, is_array); |
|
} |
|
|
|
/* Serialize array */ |
|
static |
|
int expr_ser_array( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *ptr, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsArray *a = ecs_get(world, op->type, EcsArray); |
|
ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return expr_ser_type_elements( |
|
world, a->type, ptr, a->count, str, true); |
|
} |
|
|
|
/* Serialize vector */ |
|
static |
|
int expr_ser_vector( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *base, |
|
ecs_strbuf_t *str) |
|
{ |
|
const ecs_vec_t *value = base; |
|
const EcsVector *v = ecs_get(world, op->type, EcsVector); |
|
ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t count = ecs_vec_count(value); |
|
void *array = ecs_vec_first(value); |
|
|
|
/* Serialize contiguous buffer of vector */ |
|
return expr_ser_type_elements(world, v->type, array, count, str, false); |
|
} |
|
|
|
/* Forward serialization to the different type kinds */ |
|
static |
|
int flecs_expr_ser_type_op( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *ptr, |
|
ecs_strbuf_t *str, |
|
bool is_expr) |
|
{ |
|
switch(op->kind) { |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
/* Should not be parsed as single op */ |
|
ecs_throw(ECS_INVALID_PARAMETER, NULL); |
|
break; |
|
case EcsOpEnum: |
|
if (flecs_expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpBitmask: |
|
if (flecs_expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpArray: |
|
if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpVector: |
|
if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
case EcsOpString: |
|
case EcsOpOpaque: |
|
if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind), |
|
ECS_OFFSET(ptr, op->offset), str, is_expr)) |
|
{ |
|
/* Unknown operation */ |
|
ecs_err("unknown serializer operation kind (%d)", op->kind); |
|
goto error; |
|
} |
|
break; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Iterate over a slice of the type ops array */ |
|
static |
|
int flecs_expr_ser_type_ops( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *ops, |
|
int32_t op_count, |
|
const void *base, |
|
ecs_strbuf_t *str, |
|
int32_t in_array, |
|
bool is_expr) |
|
{ |
|
for (int i = 0; i < op_count; i ++) { |
|
ecs_meta_type_op_t *op = &ops[i]; |
|
|
|
if (in_array <= 0) { |
|
if (op->name) { |
|
ecs_strbuf_list_next(str); |
|
ecs_strbuf_append(str, "%s: ", op->name); |
|
} |
|
|
|
int32_t elem_count = op->count; |
|
if (elem_count > 1) { |
|
/* Serialize inline array */ |
|
if (expr_ser_elements(world, op, op->op_count, base, |
|
elem_count, op->size, str, true)) |
|
{ |
|
return -1; |
|
} |
|
|
|
i += op->op_count - 1; |
|
continue; |
|
} |
|
} |
|
|
|
switch(op->kind) { |
|
case EcsOpPush: |
|
ecs_strbuf_list_push(str, "{", ", "); |
|
in_array --; |
|
break; |
|
case EcsOpPop: |
|
ecs_strbuf_list_pop(str, "}"); |
|
in_array ++; |
|
break; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
case EcsOpString: |
|
case EcsOpOpaque: |
|
if (flecs_expr_ser_type_op(world, op, base, str, is_expr)) { |
|
goto error; |
|
} |
|
break; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
} |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Iterate over the type ops of a type */ |
|
static |
|
int flecs_expr_ser_type( |
|
const ecs_world_t *world, |
|
const ecs_vec_t *v_ops, |
|
const void *base, |
|
ecs_strbuf_t *str, |
|
bool is_expr) |
|
{ |
|
ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); |
|
int32_t count = ecs_vec_count(v_ops); |
|
return flecs_expr_ser_type_ops(world, ops, count, base, str, 0, is_expr); |
|
} |
|
|
|
int ecs_ptr_to_expr_buf( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void *ptr, |
|
ecs_strbuf_t *buf_out) |
|
{ |
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, type, EcsMetaTypeSerialized); |
|
if (ser == NULL) { |
|
char *path = ecs_get_fullpath(world, type); |
|
ecs_err("cannot serialize value for type '%s'", path); |
|
ecs_os_free(path); |
|
goto error; |
|
} |
|
|
|
if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, true)) { |
|
goto error; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
char* ecs_ptr_to_expr( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void* ptr) |
|
{ |
|
ecs_strbuf_t str = ECS_STRBUF_INIT; |
|
|
|
if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { |
|
ecs_strbuf_reset(&str); |
|
return NULL; |
|
} |
|
|
|
return ecs_strbuf_get(&str); |
|
} |
|
|
|
int ecs_ptr_to_str_buf( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void *ptr, |
|
ecs_strbuf_t *buf_out) |
|
{ |
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, type, EcsMetaTypeSerialized); |
|
if (ser == NULL) { |
|
char *path = ecs_get_fullpath(world, type); |
|
ecs_err("cannot serialize value for type '%s'", path); |
|
ecs_os_free(path); |
|
goto error; |
|
} |
|
|
|
if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, false)) { |
|
goto error; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
char* ecs_ptr_to_str( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void* ptr) |
|
{ |
|
ecs_strbuf_t str = ECS_STRBUF_INIT; |
|
|
|
if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) { |
|
ecs_strbuf_reset(&str); |
|
return NULL; |
|
} |
|
|
|
return ecs_strbuf_get(&str); |
|
} |
|
|
|
int ecs_primitive_to_expr_buf( |
|
const ecs_world_t *world, |
|
ecs_primitive_kind_t kind, |
|
const void *base, |
|
ecs_strbuf_t *str) |
|
{ |
|
return flecs_expr_ser_primitive(world, kind, base, str, true); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file expr/utils.c |
|
* @brief String parsing utilities. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_EXPR |
|
|
|
#include <ctype.h> |
|
|
|
char* ecs_chresc( |
|
char *out, |
|
char in, |
|
char delimiter) |
|
{ |
|
char *bptr = out; |
|
switch(in) { |
|
case '\a': |
|
*bptr++ = '\\'; |
|
*bptr = 'a'; |
|
break; |
|
case '\b': |
|
*bptr++ = '\\'; |
|
*bptr = 'b'; |
|
break; |
|
case '\f': |
|
*bptr++ = '\\'; |
|
*bptr = 'f'; |
|
break; |
|
case '\n': |
|
*bptr++ = '\\'; |
|
*bptr = 'n'; |
|
break; |
|
case '\r': |
|
*bptr++ = '\\'; |
|
*bptr = 'r'; |
|
break; |
|
case '\t': |
|
*bptr++ = '\\'; |
|
*bptr = 't'; |
|
break; |
|
case '\v': |
|
*bptr++ = '\\'; |
|
*bptr = 'v'; |
|
break; |
|
case '\\': |
|
*bptr++ = '\\'; |
|
*bptr = '\\'; |
|
break; |
|
case '\033': |
|
*bptr = '['; /* Used for terminal colors */ |
|
break; |
|
default: |
|
if (in == delimiter) { |
|
*bptr++ = '\\'; |
|
*bptr = delimiter; |
|
} else { |
|
*bptr = in; |
|
} |
|
break; |
|
} |
|
|
|
*(++bptr) = '\0'; |
|
|
|
return bptr; |
|
} |
|
|
|
const char* ecs_chrparse( |
|
const char *in, |
|
char *out) |
|
{ |
|
const char *result = in + 1; |
|
char ch; |
|
|
|
if (in[0] == '\\') { |
|
result ++; |
|
|
|
switch(in[1]) { |
|
case 'a': |
|
ch = '\a'; |
|
break; |
|
case 'b': |
|
ch = '\b'; |
|
break; |
|
case 'f': |
|
ch = '\f'; |
|
break; |
|
case 'n': |
|
ch = '\n'; |
|
break; |
|
case 'r': |
|
ch = '\r'; |
|
break; |
|
case 't': |
|
ch = '\t'; |
|
break; |
|
case 'v': |
|
ch = '\v'; |
|
break; |
|
case '\\': |
|
ch = '\\'; |
|
break; |
|
case '"': |
|
ch = '"'; |
|
break; |
|
case '0': |
|
ch = '\0'; |
|
break; |
|
case ' ': |
|
ch = ' '; |
|
break; |
|
case '$': |
|
ch = '$'; |
|
break; |
|
default: |
|
goto error; |
|
} |
|
} else { |
|
ch = in[0]; |
|
} |
|
|
|
if (out) { |
|
*out = ch; |
|
} |
|
|
|
return result; |
|
error: |
|
return NULL; |
|
} |
|
|
|
ecs_size_t ecs_stresc( |
|
char *out, |
|
ecs_size_t n, |
|
char delimiter, |
|
const char *in) |
|
{ |
|
const char *ptr = in; |
|
char ch, *bptr = out, buff[3]; |
|
ecs_size_t written = 0; |
|
while ((ch = *ptr++)) { |
|
if ((written += (ecs_size_t)(ecs_chresc( |
|
buff, ch, delimiter) - buff)) <= n) |
|
{ |
|
/* If size != 0, an out buffer must be provided. */ |
|
ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); |
|
*bptr++ = buff[0]; |
|
if ((ch = buff[1])) { |
|
*bptr = ch; |
|
bptr++; |
|
} |
|
} |
|
} |
|
|
|
if (bptr) { |
|
while (written < n) { |
|
*bptr = '\0'; |
|
bptr++; |
|
written++; |
|
} |
|
} |
|
return written; |
|
error: |
|
return 0; |
|
} |
|
|
|
char* ecs_astresc( |
|
char delimiter, |
|
const char *in) |
|
{ |
|
if (!in) { |
|
return NULL; |
|
} |
|
|
|
ecs_size_t len = ecs_stresc(NULL, 0, delimiter, in); |
|
char *out = ecs_os_malloc_n(char, len + 1); |
|
ecs_stresc(out, len, delimiter, in); |
|
out[len] = '\0'; |
|
return out; |
|
} |
|
|
|
static |
|
const char* flecs_parse_var_name( |
|
const char *ptr, |
|
char *token_out) |
|
{ |
|
char ch, *bptr = token_out; |
|
|
|
while ((ch = *ptr)) { |
|
if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { |
|
goto error; |
|
} |
|
|
|
if (isalpha(ch) || isdigit(ch) || ch == '_') { |
|
*bptr = ch; |
|
bptr ++; |
|
ptr ++; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
if (bptr == token_out) { |
|
goto error; |
|
} |
|
|
|
*bptr = '\0'; |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* flecs_parse_interpolated_str( |
|
const char *ptr, |
|
char *token_out) |
|
{ |
|
char ch, *bptr = token_out; |
|
|
|
while ((ch = *ptr)) { |
|
if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { |
|
goto error; |
|
} |
|
|
|
if (ch == '\\') { |
|
if (ptr[1] == '}') { |
|
*bptr = '}'; |
|
bptr ++; |
|
ptr += 2; |
|
continue; |
|
} |
|
} |
|
|
|
if (ch != '}') { |
|
*bptr = ch; |
|
bptr ++; |
|
ptr ++; |
|
} else { |
|
ptr ++; |
|
break; |
|
} |
|
} |
|
|
|
if (bptr == token_out) { |
|
goto error; |
|
} |
|
|
|
*bptr = '\0'; |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
char* ecs_interpolate_string( |
|
ecs_world_t *world, |
|
const char *str, |
|
const ecs_vars_t *vars) |
|
{ |
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
ecs_strbuf_t result = ECS_STRBUF_INIT; |
|
const char *ptr; |
|
char ch; |
|
|
|
for(ptr = str; (ch = *ptr); ptr++) { |
|
if (ch == '\\') { |
|
ptr ++; |
|
if (ptr[0] == '$') { |
|
ecs_strbuf_appendch(&result, '$'); |
|
continue; |
|
} |
|
if (ptr[0] == '\\') { |
|
ecs_strbuf_appendch(&result, '\\'); |
|
continue; |
|
} |
|
if (ptr[0] == '{') { |
|
ecs_strbuf_appendch(&result, '{'); |
|
continue; |
|
} |
|
if (ptr[0] == '}') { |
|
ecs_strbuf_appendch(&result, '}'); |
|
continue; |
|
} |
|
ptr --; |
|
} |
|
|
|
if (ch == '$') { |
|
ptr = flecs_parse_var_name(ptr + 1, token); |
|
if (!ptr) { |
|
ecs_parser_error(NULL, str, ptr - str, |
|
"invalid variable name '%s'", ptr); |
|
goto error; |
|
} |
|
|
|
ecs_expr_var_t *var = ecs_vars_lookup(vars, token); |
|
if (!var) { |
|
ecs_parser_error(NULL, str, ptr - str, |
|
"unresolved variable '%s'", token); |
|
goto error; |
|
} |
|
|
|
if (ecs_ptr_to_str_buf( |
|
world, var->value.type, var->value.ptr, &result)) |
|
{ |
|
goto error; |
|
} |
|
|
|
ptr --; |
|
} else if (ch == '{') { |
|
ptr = flecs_parse_interpolated_str(ptr + 1, token); |
|
if (!ptr) { |
|
ecs_parser_error(NULL, str, ptr - str, |
|
"invalid interpolated expression"); |
|
goto error; |
|
} |
|
|
|
ecs_parse_expr_desc_t expr_desc = { |
|
.vars = ECS_CONST_CAST(ecs_vars_t*, vars) |
|
}; |
|
ecs_value_t expr_result = {0}; |
|
if (!ecs_parse_expr(world, token, &expr_result, &expr_desc)) { |
|
goto error; |
|
} |
|
|
|
if (ecs_ptr_to_str_buf( |
|
world, expr_result.type, expr_result.ptr, &result)) |
|
{ |
|
goto error; |
|
} |
|
|
|
ecs_value_free(world, expr_result.type, expr_result.ptr); |
|
|
|
ptr --; |
|
} else { |
|
ecs_strbuf_appendch(&result, ch); |
|
} |
|
} |
|
|
|
return ecs_strbuf_get(&result); |
|
error: |
|
return NULL; |
|
} |
|
|
|
void ecs_iter_to_vars( |
|
const ecs_iter_t *it, |
|
ecs_vars_t *vars, |
|
int offset) |
|
{ |
|
ecs_check(vars != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(!offset || offset < it->count, ECS_INVALID_PARAMETER, NULL); |
|
|
|
/* Set variable for $this */ |
|
if (it->count) { |
|
ecs_expr_var_t *var = ecs_vars_lookup(vars, "this"); |
|
if (!var) { |
|
ecs_value_t v = { |
|
.ptr = &it->entities[offset], |
|
.type = ecs_id(ecs_entity_t) |
|
}; |
|
var = ecs_vars_declare_w_value(vars, "this", &v); |
|
var->owned = false; |
|
} else { |
|
var->value.ptr = &it->entities[offset]; |
|
} |
|
} |
|
|
|
/* Set variables for fields */ |
|
{ |
|
int32_t i, field_count = it->field_count; |
|
for (i = 0; i < field_count; i ++) { |
|
ecs_size_t size = it->sizes[i]; |
|
if (!size) { |
|
continue; |
|
} |
|
|
|
void *ptr = it->ptrs[i]; |
|
if (!ptr) { |
|
continue; |
|
} |
|
|
|
ptr = ECS_OFFSET(ptr, offset * size); |
|
|
|
char name[16]; |
|
ecs_os_sprintf(name, "%d", i + 1); |
|
ecs_expr_var_t *var = ecs_vars_lookup(vars, name); |
|
if (!var) { |
|
ecs_value_t v = { .ptr = ptr, .type = it->ids[i] }; |
|
var = ecs_vars_declare_w_value(vars, name, &v); |
|
var->owned = false; |
|
} else { |
|
ecs_check(var->value.type == it->ids[i], |
|
ECS_INVALID_PARAMETER, NULL); |
|
var->value.ptr = ptr; |
|
} |
|
} |
|
} |
|
|
|
/* Set variables for query variables */ |
|
{ |
|
int32_t i, var_count = it->variable_count; |
|
for (i = 1 /* skip this variable */ ; i < var_count; i ++) { |
|
ecs_entity_t *e_ptr = NULL; |
|
ecs_var_t *query_var = &it->variables[i]; |
|
if (query_var->entity) { |
|
e_ptr = &query_var->entity; |
|
} else { |
|
ecs_table_range_t *range = &query_var->range; |
|
if (range->count == 1) { |
|
ecs_entity_t *entities = range->table->data.entities.array; |
|
e_ptr = &entities[range->offset]; |
|
} |
|
} |
|
if (!e_ptr) { |
|
continue; |
|
} |
|
|
|
ecs_expr_var_t *var = ecs_vars_lookup(vars, it->variable_names[i]); |
|
if (!var) { |
|
ecs_value_t v = { .ptr = e_ptr, .type = ecs_id(ecs_entity_t) }; |
|
var = ecs_vars_declare_w_value(vars, it->variable_names[i], &v); |
|
var->owned = false; |
|
} else { |
|
ecs_check(var->value.type == ecs_id(ecs_entity_t), |
|
ECS_INVALID_PARAMETER, NULL); |
|
var->value.ptr = e_ptr; |
|
} |
|
} |
|
} |
|
|
|
error: |
|
return; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file expr/vars.c |
|
* @brief Utilities for variable substitution in flecs string expressions. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_EXPR |
|
|
|
static |
|
void flecs_expr_var_scope_init( |
|
ecs_world_t *world, |
|
ecs_expr_var_scope_t *scope, |
|
ecs_expr_var_scope_t *parent) |
|
{ |
|
flecs_name_index_init(&scope->var_index, &world->allocator); |
|
ecs_vec_init_t(&world->allocator, &scope->vars, ecs_expr_var_t, 0); |
|
scope->parent = parent; |
|
} |
|
|
|
static |
|
void flecs_expr_var_scope_fini( |
|
ecs_world_t *world, |
|
ecs_expr_var_scope_t *scope) |
|
{ |
|
ecs_vec_t *vars = &scope->vars; |
|
int32_t i, count = vars->count; |
|
for (i = 0; i < count; i++) { |
|
ecs_expr_var_t *var = ecs_vec_get_t(vars, ecs_expr_var_t, i); |
|
if (var->owned) { |
|
ecs_value_free(world, var->value.type, var->value.ptr); |
|
} |
|
flecs_strfree(&world->allocator, var->name); |
|
} |
|
|
|
ecs_vec_fini_t(&world->allocator, &scope->vars, ecs_expr_var_t); |
|
flecs_name_index_fini(&scope->var_index); |
|
} |
|
|
|
void ecs_vars_init( |
|
ecs_world_t *world, |
|
ecs_vars_t *vars) |
|
{ |
|
flecs_expr_var_scope_init(world, &vars->root, NULL); |
|
vars->world = world; |
|
vars->cur = &vars->root; |
|
} |
|
|
|
void ecs_vars_fini( |
|
ecs_vars_t *vars) |
|
{ |
|
ecs_expr_var_scope_t *cur = vars->cur, *next; |
|
do { |
|
next = cur->parent; |
|
flecs_expr_var_scope_fini(vars->world, cur); |
|
if (cur != &vars->root) { |
|
flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, cur); |
|
} else { |
|
break; |
|
} |
|
} while ((cur = next)); |
|
} |
|
|
|
void ecs_vars_push( |
|
ecs_vars_t *vars) |
|
{ |
|
ecs_expr_var_scope_t *scope = flecs_calloc_t(&vars->world->allocator, |
|
ecs_expr_var_scope_t); |
|
flecs_expr_var_scope_init(vars->world, scope, vars->cur); |
|
vars->cur = scope; |
|
} |
|
|
|
int ecs_vars_pop( |
|
ecs_vars_t *vars) |
|
{ |
|
ecs_expr_var_scope_t *scope = vars->cur; |
|
ecs_check(scope != &vars->root, ECS_INVALID_OPERATION, NULL); |
|
vars->cur = scope->parent; |
|
flecs_expr_var_scope_fini(vars->world, scope); |
|
flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, scope); |
|
return 0; |
|
error: |
|
return 1; |
|
} |
|
|
|
ecs_expr_var_t* ecs_vars_declare( |
|
ecs_vars_t *vars, |
|
const char *name, |
|
ecs_entity_t type) |
|
{ |
|
ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_expr_var_scope_t *scope = vars->cur; |
|
ecs_hashmap_t *var_index = &scope->var_index; |
|
|
|
if (flecs_name_index_find(var_index, name, 0, 0) != 0) { |
|
ecs_err("variable %s redeclared", name); |
|
goto error; |
|
} |
|
|
|
ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator, |
|
&scope->vars, ecs_expr_var_t); |
|
|
|
var->value.ptr = ecs_value_new(vars->world, type); |
|
if (!var->value.ptr) { |
|
goto error; |
|
} |
|
var->value.type = type; |
|
var->name = flecs_strdup(&vars->world->allocator, name); |
|
var->owned = true; |
|
|
|
flecs_name_index_ensure(var_index, |
|
flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0); |
|
return var; |
|
error: |
|
return NULL; |
|
} |
|
|
|
ecs_expr_var_t* ecs_vars_declare_w_value( |
|
ecs_vars_t *vars, |
|
const char *name, |
|
ecs_value_t *value) |
|
{ |
|
ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_expr_var_scope_t *scope = vars->cur; |
|
ecs_hashmap_t *var_index = &scope->var_index; |
|
|
|
if (flecs_name_index_find(var_index, name, 0, 0) != 0) { |
|
ecs_err("variable %s redeclared", name); |
|
ecs_value_free(vars->world, value->type, value->ptr); |
|
goto error; |
|
} |
|
|
|
ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator, |
|
&scope->vars, ecs_expr_var_t); |
|
var->value = *value; |
|
var->name = flecs_strdup(&vars->world->allocator, name); |
|
var->owned = true; |
|
value->ptr = NULL; /* Take ownership, prevent double free */ |
|
|
|
flecs_name_index_ensure(var_index, |
|
flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0); |
|
return var; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
ecs_expr_var_t* flecs_vars_scope_lookup( |
|
ecs_expr_var_scope_t *scope, |
|
const char *name) |
|
{ |
|
uint64_t var_id = flecs_name_index_find(&scope->var_index, name, 0, 0); |
|
if (var_id == 0) { |
|
if (scope->parent) { |
|
return flecs_vars_scope_lookup(scope->parent, name); |
|
} |
|
return NULL; |
|
} |
|
|
|
return ecs_vec_get_t(&scope->vars, ecs_expr_var_t, |
|
flecs_uto(int32_t, var_id - 1)); |
|
} |
|
|
|
ecs_expr_var_t* ecs_vars_lookup( |
|
const ecs_vars_t *vars, |
|
const char *name) |
|
{ |
|
return flecs_vars_scope_lookup(vars->cur, name); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file json/deserialize.c |
|
* @brief Deserialize JSON strings into (component) values. |
|
*/ |
|
|
|
#include <ctype.h> |
|
|
|
#ifdef FLECS_JSON |
|
|
|
static |
|
const char* flecs_json_parse_path( |
|
const ecs_world_t *world, |
|
const char *json, |
|
char *token, |
|
ecs_entity_t *out, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
json = flecs_json_expect(json, JsonString, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
ecs_entity_t result = ecs_lookup_fullpath(world, token); |
|
if (!result) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"unresolved identifier '%s'", token); |
|
goto error; |
|
} |
|
|
|
*out = result; |
|
|
|
return json; |
|
error: |
|
return NULL; |
|
} |
|
|
|
const char* ecs_ptr_from_json( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
void *ptr, |
|
const char *json, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
ecs_json_token_t token_kind = 0; |
|
char token_buffer[ECS_MAX_TOKEN_SIZE], t_lah[ECS_MAX_TOKEN_SIZE]; |
|
char *token = token_buffer; |
|
int depth = 0; |
|
|
|
const char *name = NULL; |
|
const char *expr = NULL; |
|
|
|
ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, ptr); |
|
if (cur.valid == false) { |
|
return NULL; |
|
} |
|
|
|
if (desc) { |
|
name = desc->name; |
|
expr = desc->expr; |
|
cur.lookup_action = desc->lookup_action; |
|
cur.lookup_ctx = desc->lookup_ctx; |
|
} |
|
|
|
while ((json = flecs_json_parse(json, &token_kind, token))) { |
|
if (token_kind == JsonLargeString) { |
|
ecs_strbuf_t large_token = ECS_STRBUF_INIT; |
|
json = flecs_json_parse_large_string(json, &large_token); |
|
if (!json) { |
|
break; |
|
} |
|
|
|
token = ecs_strbuf_get(&large_token); |
|
token_kind = JsonString; |
|
} |
|
|
|
if (token_kind == JsonObjectOpen) { |
|
depth ++; |
|
if (ecs_meta_push(&cur) != 0) { |
|
goto error; |
|
} |
|
|
|
if (ecs_meta_is_collection(&cur)) { |
|
ecs_parser_error(name, expr, json - expr, "expected '['"); |
|
return NULL; |
|
} |
|
} else if (token_kind == JsonObjectClose) { |
|
depth --; |
|
|
|
if (ecs_meta_is_collection(&cur)) { |
|
ecs_parser_error(name, expr, json - expr, "expected ']'"); |
|
return NULL; |
|
} |
|
|
|
if (ecs_meta_pop(&cur) != 0) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonArrayOpen) { |
|
depth ++; |
|
if (ecs_meta_push(&cur) != 0) { |
|
goto error; |
|
} |
|
|
|
if (!ecs_meta_is_collection(&cur)) { |
|
ecs_parser_error(name, expr, json - expr, "expected '{'"); |
|
return NULL; |
|
} |
|
} else if (token_kind == JsonArrayClose) { |
|
depth --; |
|
|
|
if (!ecs_meta_is_collection(&cur)) { |
|
ecs_parser_error(name, expr, json - expr, "expected '}'"); |
|
return NULL; |
|
} |
|
|
|
if (ecs_meta_pop(&cur) != 0) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonComma) { |
|
if (ecs_meta_next(&cur) != 0) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonNull) { |
|
if (ecs_meta_set_null(&cur) != 0) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonString) { |
|
const char *lah = flecs_json_parse( |
|
json, &token_kind, t_lah); |
|
if (token_kind == JsonColon) { |
|
/* Member assignment */ |
|
json = lah; |
|
if (ecs_meta_dotmember(&cur, token) != 0) { |
|
goto error; |
|
} |
|
} else { |
|
if (ecs_meta_set_string(&cur, token) != 0) { |
|
goto error; |
|
} |
|
} |
|
} else if (token_kind == JsonNumber) { |
|
double number = atof(token); |
|
if (ecs_meta_set_float(&cur, number) != 0) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonLargeInt) { |
|
int64_t number = flecs_ito(int64_t, atoll(token)); |
|
if (ecs_meta_set_int(&cur, number) != 0) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonNull) { |
|
if (ecs_meta_set_null(&cur) != 0) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonTrue) { |
|
if (ecs_meta_set_bool(&cur, true) != 0) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonFalse) { |
|
if (ecs_meta_set_bool(&cur, false) != 0) { |
|
goto error; |
|
} |
|
} else { |
|
goto error; |
|
} |
|
|
|
if (token != token_buffer) { |
|
ecs_os_free(token); |
|
token = token_buffer; |
|
} |
|
|
|
if (!depth) { |
|
break; |
|
} |
|
} |
|
|
|
return json; |
|
error: |
|
return NULL; |
|
} |
|
|
|
const char* ecs_entity_from_json( |
|
ecs_world_t *world, |
|
ecs_entity_t e, |
|
const char *json, |
|
const ecs_from_json_desc_t *desc_param) |
|
{ |
|
ecs_json_token_t token_kind = 0; |
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
|
|
ecs_from_json_desc_t desc = {0}; |
|
|
|
const char *name = NULL, *expr = json, *ids = NULL, *values = NULL, *lah; |
|
if (desc_param) { |
|
desc = *desc_param; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonObjectOpen, token, &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
lah = flecs_json_parse(json, &token_kind, token); |
|
if (!lah) { |
|
goto error; |
|
} |
|
|
|
if (token_kind == JsonObjectClose) { |
|
return lah; |
|
} |
|
|
|
json = flecs_json_expect_member(json, token, &desc); |
|
if (!json) { |
|
return NULL; |
|
} |
|
|
|
if (!ecs_os_strcmp(token, "path")) { |
|
json = flecs_json_expect(json, JsonString, token, &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
ecs_add_fullpath(world, e, token); |
|
|
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
if (token_kind == JsonObjectClose) { |
|
return json; |
|
} else if (token_kind != JsonComma) { |
|
ecs_parser_error(name, expr, json - expr, "unexpected character"); |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect_member_name(json, token, "ids", &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
} else if (ecs_os_strcmp(token, "ids")) { |
|
ecs_parser_error(name, expr, json - expr, "expected member 'ids'"); |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonArrayOpen, token, &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
ids = json; |
|
|
|
json = flecs_json_skip_array(json, token, &desc); |
|
if (!json) { |
|
return NULL; |
|
} |
|
|
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind != JsonObjectClose) { |
|
if (token_kind != JsonComma) { |
|
ecs_parser_error(name, expr, json - expr, "expected ','"); |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect_member_name(json, token, "values", &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonArrayOpen, token, &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
values = json; |
|
} |
|
|
|
do { |
|
ecs_entity_t first = 0, second = 0, type_id = 0; |
|
ecs_id_t id; |
|
|
|
ids = flecs_json_parse(ids, &token_kind, token); |
|
if (!ids) { |
|
goto error; |
|
} |
|
|
|
if (token_kind == JsonArrayClose) { |
|
if (values) { |
|
if (values[0] != ']') { |
|
ecs_parser_error(name, expr, values - expr, "expected ']'"); |
|
goto error; |
|
} |
|
json = ecs_parse_ws_eol(values + 1); |
|
} else { |
|
json = ids; |
|
} |
|
|
|
break; |
|
} else if (token_kind == JsonArrayOpen) { |
|
ids = flecs_json_parse_path(world, ids, token, &first, &desc); |
|
if (!ids) { |
|
goto error; |
|
} |
|
|
|
ids = flecs_json_parse(ids, &token_kind, token); |
|
if (!ids) { |
|
goto error; |
|
} |
|
|
|
if (token_kind == JsonComma) { |
|
/* Id is a pair*/ |
|
ids = flecs_json_parse_path(world, ids, token, &second, &desc); |
|
if (!ids) { |
|
goto error; |
|
} |
|
|
|
ids = flecs_json_expect(ids, JsonArrayClose, token, &desc); |
|
if (!ids) { |
|
goto error; |
|
} |
|
} else if (token_kind != JsonArrayClose) { |
|
ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); |
|
goto error; |
|
} |
|
|
|
lah = flecs_json_parse(ids, &token_kind, token); |
|
if (!lah) { |
|
goto error; |
|
} |
|
|
|
if (token_kind == JsonComma) { |
|
ids = lah; |
|
} else if (token_kind != JsonArrayClose) { |
|
ecs_parser_error(name, expr, lah - expr, "expected ',' or ']'"); |
|
goto error; |
|
} |
|
} else { |
|
ecs_parser_error(name, expr, lah - expr, "expected '[' or ']'"); |
|
goto error; |
|
} |
|
|
|
if (second) { |
|
id = ecs_pair(first, second); |
|
type_id = ecs_get_typeid(world, id); |
|
if (!type_id) { |
|
ecs_parser_error(name, expr, ids - expr, "id is not a type"); |
|
goto error; |
|
} |
|
} else { |
|
id = first; |
|
type_id = first; |
|
} |
|
|
|
/* Get mutable pointer */ |
|
void *comp_ptr = ecs_get_mut_id(world, e, id); |
|
if (!comp_ptr) { |
|
char *idstr = ecs_id_str(world, id); |
|
ecs_parser_error(name, expr, json - expr, |
|
"id '%s' is not a valid component", idstr); |
|
ecs_os_free(idstr); |
|
goto error; |
|
} |
|
|
|
if (values) { |
|
ecs_from_json_desc_t parse_desc = { |
|
.name = name, |
|
.expr = expr, |
|
}; |
|
|
|
values = ecs_ptr_from_json( |
|
world, type_id, comp_ptr, values, &parse_desc); |
|
if (!values) { |
|
goto error; |
|
} |
|
|
|
lah = flecs_json_parse(values, &token_kind, token); |
|
if (!lah) { |
|
goto error; |
|
} |
|
|
|
if (token_kind == JsonComma) { |
|
values = lah; |
|
} else if (token_kind != JsonArrayClose) { |
|
ecs_parser_error(name, expr, json - expr, |
|
"expected ',' or ']'"); |
|
goto error; |
|
} else { |
|
values = ecs_parse_ws_eol(values); |
|
} |
|
|
|
ecs_modified_id(world, e, id); |
|
} |
|
} while(ids[0]); |
|
|
|
return flecs_json_expect(json, JsonObjectClose, token, &desc); |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_json_new_id( |
|
ecs_world_t *world, |
|
ecs_entity_t ser_id) |
|
{ |
|
/* Try to honor low id requirements */ |
|
if (ser_id < FLECS_HI_COMPONENT_ID) { |
|
return ecs_new_low_id(world); |
|
} else { |
|
return ecs_new_id(world); |
|
} |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_json_lookup( |
|
ecs_world_t *world, |
|
ecs_entity_t parent, |
|
const char *name, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
ecs_entity_t scope = 0; |
|
if (parent) { |
|
scope = ecs_set_scope(world, parent); |
|
} |
|
ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); |
|
if (parent) { |
|
ecs_set_scope(world, scope); |
|
} |
|
return result; |
|
} |
|
|
|
static |
|
void flecs_json_mark_reserved( |
|
ecs_map_t *anonymous_ids, |
|
ecs_entity_t e) |
|
{ |
|
ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); |
|
ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); |
|
reserved[0] = 0; |
|
} |
|
|
|
static |
|
bool flecs_json_name_is_anonymous( |
|
const char *name) |
|
{ |
|
if (isdigit(name[0])) { |
|
const char *ptr; |
|
for (ptr = name + 1; *ptr; ptr ++) { |
|
if (!isdigit(*ptr)) { |
|
break; |
|
} |
|
} |
|
if (!(*ptr)) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_json_ensure_entity( |
|
ecs_world_t *world, |
|
const char *name, |
|
ecs_map_t *anonymous_ids) |
|
{ |
|
ecs_entity_t e = 0; |
|
|
|
if (flecs_json_name_is_anonymous(name)) { |
|
/* Anonymous entity, find or create mapping to new id */ |
|
ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(name)); |
|
ecs_entity_t *deser_id = ecs_map_get(anonymous_ids, ser_id); |
|
if (deser_id) { |
|
if (!deser_id[0]) { |
|
/* Id is already issued by deserializer, create new id */ |
|
deser_id[0] = flecs_json_new_id(world, ser_id); |
|
|
|
/* Mark new id as reserved */ |
|
flecs_json_mark_reserved(anonymous_ids, deser_id[0]); |
|
} else { |
|
/* Id mapping exists */ |
|
} |
|
} else { |
|
/* Id has not yet been issued by deserializer, which means it's safe |
|
* to use. This allows the deserializer to bind to existing |
|
* anonymous ids, as they will never be reissued. */ |
|
deser_id = ecs_map_ensure(anonymous_ids, ser_id); |
|
if (!ecs_exists(world, ser_id) || ecs_is_alive(world, ser_id)) { |
|
/* Only use existing id if it's alive or doesn't exist yet. The |
|
* id could have been recycled for another entity */ |
|
deser_id[0] = ser_id; |
|
ecs_ensure(world, ser_id); |
|
} else { |
|
/* If id exists and is not alive, create a new id */ |
|
deser_id[0] = flecs_json_new_id(world, ser_id); |
|
|
|
/* Mark new id as reserved */ |
|
flecs_json_mark_reserved(anonymous_ids, deser_id[0]); |
|
} |
|
} |
|
|
|
e = deser_id[0]; |
|
} else { |
|
e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, false); |
|
if (!e) { |
|
e = ecs_entity(world, { .name = name }); |
|
flecs_json_mark_reserved(anonymous_ids, e); |
|
} |
|
} |
|
|
|
return e; |
|
} |
|
|
|
static |
|
ecs_table_t* flecs_json_parse_table( |
|
ecs_world_t *world, |
|
const char *json, |
|
char *token, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
ecs_json_token_t token_kind = 0; |
|
ecs_table_t *table = NULL; |
|
|
|
do { |
|
ecs_id_t id = 0; |
|
json = flecs_json_expect(json, JsonArrayOpen, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonString, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
ecs_entity_t first = flecs_json_lookup(world, 0, token, desc); |
|
if (!first) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind == JsonComma) { |
|
json = flecs_json_expect(json, JsonString, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
ecs_entity_t second = flecs_json_lookup(world, 0, token, desc); |
|
if (!second) { |
|
goto error; |
|
} |
|
|
|
id = ecs_pair(first, second); |
|
|
|
json = flecs_json_expect(json, JsonArrayClose, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
} else if (token_kind == JsonArrayClose) { |
|
id = first; |
|
} else { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected ',' or ']"); |
|
goto error; |
|
} |
|
|
|
table = ecs_table_add_id(world, table, id); |
|
if (!table) { |
|
goto error; |
|
} |
|
|
|
const char *lah = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind == JsonComma) { |
|
json = lah; |
|
} else if (token_kind == JsonArrayClose) { |
|
break; |
|
} else { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected ',' or ']'"); |
|
goto error; |
|
} |
|
} while (json[0]); |
|
|
|
return table; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
int flecs_json_parse_entities( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
ecs_table_t *table, |
|
ecs_entity_t parent, |
|
const char *json, |
|
char *token, |
|
ecs_vec_t *records, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
char name_token[ECS_MAX_TOKEN_SIZE]; |
|
ecs_json_token_t token_kind = 0; |
|
ecs_vec_clear(records); |
|
|
|
do { |
|
json = flecs_json_parse(json, &token_kind, name_token); |
|
if (!json) { |
|
goto error; |
|
} |
|
if ((token_kind != JsonNumber) && (token_kind != JsonString)) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected number or string"); |
|
goto error; |
|
} |
|
|
|
ecs_entity_t e = flecs_json_lookup(world, parent, name_token, desc); |
|
ecs_record_t *r = flecs_entities_try(world, e); |
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (r->table != table) { |
|
bool cleared = false; |
|
if (r->table) { |
|
ecs_commit(world, e, r, r->table, NULL, &r->table->type); |
|
cleared = true; |
|
} |
|
ecs_commit(world, e, r, table, &table->type, NULL); |
|
if (cleared) { |
|
char *entity_name = strrchr(name_token, '.'); |
|
if (entity_name) { |
|
entity_name ++; |
|
} else { |
|
entity_name = name_token; |
|
} |
|
if (!flecs_json_name_is_anonymous(entity_name)) { |
|
ecs_set_name(world, e, entity_name); |
|
} |
|
} |
|
} |
|
|
|
ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); |
|
ecs_record_t** elem = ecs_vec_append_t(a, records, ecs_record_t*); |
|
*elem = r; |
|
|
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind == JsonArrayClose) { |
|
break; |
|
} else if (token_kind != JsonComma) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected ',' or ']'"); |
|
goto error; |
|
} |
|
} while(json[0]); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
const char* flecs_json_parse_column( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
int32_t index, |
|
const char *json, |
|
char *token, |
|
ecs_vec_t *records, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
if (!table->column_count) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"table has no components"); |
|
goto error; |
|
} |
|
|
|
if (index >= table->type.count) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"more value arrays than component columns in table"); |
|
goto error; |
|
} |
|
|
|
int32_t data_column = table->column_map[index]; |
|
if (data_column == -1) { |
|
char *table_str = ecs_table_str(world, table); |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"values provided for tag at column %d of table [%s]", |
|
index, table_str); |
|
|
|
ecs_os_free(table_str); |
|
goto error; |
|
} |
|
|
|
ecs_json_token_t token_kind = 0; |
|
ecs_column_t *column = &table->data.columns[data_column]; |
|
ecs_type_info_t *ti = column->ti; |
|
ecs_size_t size = ti->size; |
|
ecs_entity_t type = ti->component; |
|
ecs_record_t **record_array = ecs_vec_first_t(records, ecs_record_t*); |
|
int32_t entity = 0; |
|
|
|
do { |
|
ecs_record_t *r = record_array[entity]; |
|
int32_t row = ECS_RECORD_TO_ROW(r->row); |
|
|
|
void *ptr = ecs_vec_get(&column->data, size, row); |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
json = ecs_ptr_from_json(world, type, ptr, json, desc); |
|
if (!json) { |
|
break; |
|
} |
|
|
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind == JsonArrayClose) { |
|
break; |
|
} else if (token_kind != JsonComma) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected ',' or ']'"); |
|
} |
|
|
|
entity ++; |
|
} while (json[0]); |
|
|
|
return json; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* flecs_json_parse_values( |
|
ecs_world_t *world, |
|
ecs_table_t *table, |
|
const char *json, |
|
char *token, |
|
ecs_vec_t *records, |
|
ecs_vec_t *columns_set, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_json_token_t token_kind = 0; |
|
int32_t column = 0; |
|
|
|
ecs_vec_clear(columns_set); |
|
|
|
do { |
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
if (token_kind == JsonArrayClose) { |
|
break; |
|
} else if (token_kind == JsonArrayOpen) { |
|
json = flecs_json_parse_column(world, table, column, |
|
json, token, records, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
ecs_id_t *id_set = ecs_vec_append_t(a, columns_set, ecs_id_t); |
|
*id_set = table->type.array[column]; |
|
|
|
column ++; |
|
} else if (token_kind == JsonNumber) { |
|
if (!ecs_os_strcmp(token, "0")) { |
|
column ++; /* no data */ |
|
} else { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"unexpected number"); |
|
goto error; |
|
} |
|
} |
|
|
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind == JsonArrayClose) { |
|
break; |
|
} else if (token_kind != JsonComma) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected ',' or ']'"); |
|
goto error; |
|
} |
|
} while (json[0]); |
|
|
|
/* Send OnSet notifications */ |
|
ecs_defer_begin(world); |
|
ecs_type_t type = { |
|
.array = columns_set->array, |
|
.count = columns_set->count }; |
|
|
|
int32_t table_count = ecs_table_count(table); |
|
int32_t i, record_count = ecs_vec_count(records); |
|
|
|
/* If the entire table was inserted, send bulk notification */ |
|
if (table_count == ecs_vec_count(records)) { |
|
flecs_notify_on_set(world, table, 0, ecs_table_count(table), &type, true); |
|
} else { |
|
ecs_record_t **rvec = ecs_vec_first_t(records, ecs_record_t*); |
|
for (i = 0; i < record_count; i ++) { |
|
ecs_record_t *r = rvec[i]; |
|
int32_t row = ECS_RECORD_TO_ROW(r->row); |
|
flecs_notify_on_set(world, table, row, 1, &type, true); |
|
} |
|
} |
|
|
|
ecs_defer_end(world); |
|
|
|
return json; |
|
error: |
|
return NULL; |
|
} |
|
|
|
static |
|
const char* flecs_json_parse_result( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
const char *json, |
|
char *token, |
|
ecs_vec_t *records, |
|
ecs_vec_t *columns_set, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
ecs_json_token_t token_kind = 0; |
|
const char *ids = NULL, *values = NULL, *entities = NULL; |
|
|
|
json = flecs_json_expect(json, JsonObjectOpen, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect_member_name(json, token, "ids", desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonArrayOpen, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
ids = json; /* store start of ids array */ |
|
|
|
json = flecs_json_skip_array(json, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonComma, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect_member(json, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
ecs_entity_t parent = 0; |
|
if (!ecs_os_strcmp(token, "parent")) { |
|
json = flecs_json_expect(json, JsonString, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
parent = ecs_lookup_fullpath(world, token); |
|
|
|
json = flecs_json_expect(json, JsonComma, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect_member(json, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
} |
|
|
|
if (ecs_os_strcmp(token, "entities")) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected 'entities'"); |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonArrayOpen, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
entities = json; /* store start of entity id array */ |
|
|
|
json = flecs_json_skip_array(json, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind == JsonComma) { |
|
json = flecs_json_expect_member_name(json, token, "values", desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonArrayOpen, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
values = json; /* store start of entities array */ |
|
} else if (token_kind != JsonObjectClose) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected ',' or '}'"); |
|
goto error; |
|
} |
|
|
|
/* Find table from ids */ |
|
ecs_table_t *table = flecs_json_parse_table(world, ids, token, desc); |
|
if (!table) { |
|
goto error; |
|
} |
|
|
|
/* Add entities to table */ |
|
if (flecs_json_parse_entities(world, a, table, parent, |
|
entities, token, records, desc)) |
|
{ |
|
goto error; |
|
} |
|
|
|
/* Parse values */ |
|
if (values) { |
|
json = flecs_json_parse_values(world, table, values, token, |
|
records, columns_set, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonObjectClose, token, desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
} |
|
|
|
return json; |
|
error: |
|
return NULL; |
|
} |
|
|
|
const char* ecs_world_from_json( |
|
ecs_world_t *world, |
|
const char *json, |
|
const ecs_from_json_desc_t *desc_arg) |
|
{ |
|
ecs_json_token_t token_kind; |
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
|
|
ecs_from_json_desc_t desc = {0}; |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_vec_t records; |
|
ecs_vec_t columns_set; |
|
ecs_map_t anonymous_ids; |
|
ecs_vec_init_t(a, &records, ecs_record_t*, 0); |
|
ecs_vec_init_t(a, &columns_set, ecs_id_t, 0); |
|
ecs_map_init(&anonymous_ids, a); |
|
|
|
const char *name = NULL, *expr = json, *lah; |
|
if (desc_arg) { |
|
desc = *desc_arg; |
|
} |
|
|
|
if (!desc.lookup_action) { |
|
desc.lookup_action = (ecs_entity_t(*)( |
|
const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; |
|
desc.lookup_ctx = &anonymous_ids; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonObjectOpen, token, &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect_member_name(json, token, "results", &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_expect(json, JsonArrayOpen, token, &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
lah = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind == JsonArrayClose) { |
|
json = lah; |
|
goto end; |
|
} |
|
|
|
do { |
|
json = flecs_json_parse_result(world, a, json, token, |
|
&records, &columns_set, &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
json = flecs_json_parse(json, &token_kind, token); |
|
if (token_kind == JsonArrayClose) { |
|
break; |
|
} else if (token_kind != JsonComma) { |
|
ecs_parser_error(name, expr, json - expr, |
|
"expected ',' or ']'"); |
|
goto error; |
|
} |
|
} while(json && json[0]); |
|
|
|
end: |
|
ecs_vec_fini_t(a, &records, ecs_record_t*); |
|
ecs_vec_fini_t(a, &columns_set, ecs_id_t); |
|
ecs_map_fini(&anonymous_ids); |
|
|
|
json = flecs_json_expect(json, JsonObjectClose, token, &desc); |
|
if (!json) { |
|
goto error; |
|
} |
|
|
|
return json; |
|
error: |
|
ecs_vec_fini_t(a, &records, ecs_record_t*); |
|
ecs_vec_fini_t(a, &columns_set, ecs_id_t); |
|
ecs_map_fini(&anonymous_ids); |
|
|
|
return NULL; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file json/json.c |
|
* @brief JSON serializer utilities. |
|
*/ |
|
|
|
#include <ctype.h> |
|
|
|
#ifdef FLECS_JSON |
|
|
|
static |
|
const char* flecs_json_token_str( |
|
ecs_json_token_t token_kind) |
|
{ |
|
switch(token_kind) { |
|
case JsonObjectOpen: return "{"; |
|
case JsonObjectClose: return "}"; |
|
case JsonArrayOpen: return "["; |
|
case JsonArrayClose: return "]"; |
|
case JsonColon: return ":"; |
|
case JsonComma: return ","; |
|
case JsonNumber: return "number"; |
|
case JsonLargeInt: return "large integer"; |
|
case JsonLargeString: |
|
case JsonString: return "string"; |
|
case JsonTrue: return "true"; |
|
case JsonFalse: return "false"; |
|
case JsonNull: return "null"; |
|
case JsonInvalid: return "invalid"; |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
error: |
|
return "<<invalid token kind>>"; |
|
} |
|
|
|
const char* flecs_json_parse( |
|
const char *json, |
|
ecs_json_token_t *token_kind, |
|
char *token) |
|
{ |
|
json = ecs_parse_ws_eol(json); |
|
|
|
char ch = json[0]; |
|
|
|
if (ch == '{') { |
|
token_kind[0] = JsonObjectOpen; |
|
return json + 1; |
|
} else if (ch == '}') { |
|
token_kind[0] = JsonObjectClose; |
|
return json + 1; |
|
} else if (ch == '[') { |
|
token_kind[0] = JsonArrayOpen; |
|
return json + 1; |
|
} else if (ch == ']') { |
|
token_kind[0] = JsonArrayClose; |
|
return json + 1; |
|
} else if (ch == ':') { |
|
token_kind[0] = JsonColon; |
|
return json + 1; |
|
} else if (ch == ',') { |
|
token_kind[0] = JsonComma; |
|
return json + 1; |
|
} else if (ch == '"') { |
|
const char *start = json; |
|
char *token_ptr = token; |
|
json ++; |
|
for (; (ch = json[0]); ) { |
|
if (ch == '"') { |
|
json ++; |
|
token_ptr[0] = '\0'; |
|
break; |
|
} |
|
|
|
if (token_ptr - token >= ECS_MAX_TOKEN_SIZE) { |
|
/* Token doesn't fit in buffer, signal to app to try again with |
|
* dynamic buffer. */ |
|
token_kind[0] = JsonLargeString; |
|
return start; |
|
} |
|
|
|
json = ecs_chrparse(json, token_ptr ++); |
|
} |
|
|
|
if (!ch) { |
|
token_kind[0] = JsonInvalid; |
|
return NULL; |
|
} else { |
|
token_kind[0] = JsonString; |
|
return json; |
|
} |
|
} else if (isdigit(ch) || (ch == '-')) { |
|
token_kind[0] = JsonNumber; |
|
const char *result = ecs_parse_digit(json, token); |
|
|
|
/* Cheap initial check if parsed token could represent large int */ |
|
if (result - json > 15) { |
|
/* Less cheap secondary check to see if number is integer */ |
|
if (!strchr(token, '.')) { |
|
token_kind[0] = JsonLargeInt; |
|
} |
|
} |
|
|
|
return result; |
|
} else if (isalpha(ch)) { |
|
if (!ecs_os_strncmp(json, "null", 4)) { |
|
token_kind[0] = JsonNull; |
|
json += 4; |
|
} else |
|
if (!ecs_os_strncmp(json, "true", 4)) { |
|
token_kind[0] = JsonTrue; |
|
json += 4; |
|
} else |
|
if (!ecs_os_strncmp(json, "false", 5)) { |
|
token_kind[0] = JsonFalse; |
|
json += 5; |
|
} |
|
|
|
if (isalpha(json[0]) || isdigit(json[0])) { |
|
token_kind[0] = JsonInvalid; |
|
return NULL; |
|
} |
|
|
|
return json; |
|
} else { |
|
token_kind[0] = JsonInvalid; |
|
return NULL; |
|
} |
|
} |
|
|
|
const char* flecs_json_parse_large_string( |
|
const char *json, |
|
ecs_strbuf_t *buf) |
|
{ |
|
if (json[0] != '"') { |
|
return NULL; /* can only parse strings */ |
|
} |
|
|
|
char ch, ch_out; |
|
json ++; |
|
for (; (ch = json[0]); ) { |
|
if (ch == '"') { |
|
json ++; |
|
break; |
|
} |
|
|
|
json = ecs_chrparse(json, &ch_out); |
|
ecs_strbuf_appendch(buf, ch_out); |
|
} |
|
|
|
if (!ch) { |
|
return NULL; |
|
} else { |
|
return json; |
|
} |
|
} |
|
|
|
const char* flecs_json_expect( |
|
const char *json, |
|
ecs_json_token_t token_kind, |
|
char *token, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
ecs_json_token_t kind = 0; |
|
json = flecs_json_parse(json, &kind, token); |
|
if (kind == JsonInvalid) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid json"); |
|
return NULL; |
|
} else if (kind != token_kind) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected %s", |
|
flecs_json_token_str(token_kind)); |
|
return NULL; |
|
} |
|
return json; |
|
} |
|
|
|
const char* flecs_json_expect_member( |
|
const char *json, |
|
char *token, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
json = flecs_json_expect(json, JsonString, token, desc); |
|
if (!json) { |
|
return NULL; |
|
} |
|
json = flecs_json_expect(json, JsonColon, token, desc); |
|
if (!json) { |
|
return NULL; |
|
} |
|
return json; |
|
} |
|
|
|
const char* flecs_json_expect_member_name( |
|
const char *json, |
|
char *token, |
|
const char *member_name, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
json = flecs_json_expect_member(json, token, desc); |
|
if (!json) { |
|
return NULL; |
|
} |
|
if (ecs_os_strcmp(token, member_name)) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected member '%s'", member_name); |
|
return NULL; |
|
} |
|
return json; |
|
} |
|
|
|
const char* flecs_json_skip_object( |
|
const char *json, |
|
char *token, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
ecs_json_token_t token_kind = 0; |
|
|
|
while ((json = flecs_json_parse(json, &token_kind, token))) { |
|
if (token_kind == JsonObjectOpen) { |
|
json = flecs_json_skip_object(json, token, desc); |
|
} else if (token_kind == JsonArrayOpen) { |
|
json = flecs_json_skip_array(json, token, desc); |
|
} else if (token_kind == JsonObjectClose) { |
|
return json; |
|
} else if (token_kind == JsonArrayClose) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected }"); |
|
return NULL; |
|
} |
|
} |
|
|
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }"); |
|
return NULL; |
|
} |
|
|
|
const char* flecs_json_skip_array( |
|
const char *json, |
|
char *token, |
|
const ecs_from_json_desc_t *desc) |
|
{ |
|
ecs_json_token_t token_kind = 0; |
|
|
|
while ((json = flecs_json_parse(json, &token_kind, token))) { |
|
if (token_kind == JsonObjectOpen) { |
|
json = flecs_json_skip_object(json, token, desc); |
|
} else if (token_kind == JsonArrayOpen) { |
|
json = flecs_json_skip_array(json, token, desc); |
|
} else if (token_kind == JsonObjectClose) { |
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, |
|
"expected ]"); |
|
return NULL; |
|
} else if (token_kind == JsonArrayClose) { |
|
return json; |
|
} |
|
} |
|
|
|
ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); |
|
return NULL; |
|
} |
|
|
|
void flecs_json_next( |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_strbuf_list_next(buf); |
|
} |
|
|
|
void flecs_json_number( |
|
ecs_strbuf_t *buf, |
|
double value) |
|
{ |
|
ecs_strbuf_appendflt(buf, value, '"'); |
|
} |
|
|
|
void flecs_json_true( |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_strbuf_appendlit(buf, "true"); |
|
} |
|
|
|
void flecs_json_false( |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_strbuf_appendlit(buf, "false"); |
|
} |
|
|
|
void flecs_json_bool( |
|
ecs_strbuf_t *buf, |
|
bool value) |
|
{ |
|
if (value) { |
|
flecs_json_true(buf); |
|
} else { |
|
flecs_json_false(buf); |
|
} |
|
} |
|
|
|
void flecs_json_array_push( |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_strbuf_list_push(buf, "[", ", "); |
|
} |
|
|
|
void flecs_json_array_pop( |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_strbuf_list_pop(buf, "]"); |
|
} |
|
|
|
void flecs_json_object_push( |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_strbuf_list_push(buf, "{", ", "); |
|
} |
|
|
|
void flecs_json_object_pop( |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_strbuf_list_pop(buf, "}"); |
|
} |
|
|
|
void flecs_json_string( |
|
ecs_strbuf_t *buf, |
|
const char *value) |
|
{ |
|
ecs_strbuf_appendch(buf, '"'); |
|
ecs_strbuf_appendstr(buf, value); |
|
ecs_strbuf_appendch(buf, '"'); |
|
} |
|
|
|
void flecs_json_string_escape( |
|
ecs_strbuf_t *buf, |
|
const char *value) |
|
{ |
|
ecs_size_t length = ecs_stresc(NULL, 0, '"', value); |
|
if (length == ecs_os_strlen(value)) { |
|
ecs_strbuf_appendch(buf, '"'); |
|
ecs_strbuf_appendstrn(buf, value, length); |
|
ecs_strbuf_appendch(buf, '"'); |
|
} else { |
|
char *out = ecs_os_malloc(length + 3); |
|
ecs_stresc(out + 1, length, '"', value); |
|
out[0] = '"'; |
|
out[length + 1] = '"'; |
|
out[length + 2] = '\0'; |
|
ecs_strbuf_appendstr(buf, out); |
|
ecs_os_free(out); |
|
} |
|
} |
|
|
|
void flecs_json_member( |
|
ecs_strbuf_t *buf, |
|
const char *name) |
|
{ |
|
flecs_json_membern(buf, name, ecs_os_strlen(name)); |
|
} |
|
|
|
void flecs_json_membern( |
|
ecs_strbuf_t *buf, |
|
const char *name, |
|
int32_t name_len) |
|
{ |
|
ecs_strbuf_list_appendch(buf, '"'); |
|
ecs_strbuf_appendstrn(buf, name, name_len); |
|
ecs_strbuf_appendlit(buf, "\":"); |
|
} |
|
|
|
void flecs_json_path( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_entity_t e) |
|
{ |
|
ecs_strbuf_appendch(buf, '"'); |
|
ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); |
|
ecs_strbuf_appendch(buf, '"'); |
|
} |
|
|
|
static |
|
const char* flecs_json_entity_label( |
|
const ecs_world_t *world, |
|
ecs_entity_t e) |
|
{ |
|
const char *lbl = NULL; |
|
if (!e) { |
|
return "0"; |
|
} |
|
#ifdef FLECS_DOC |
|
lbl = ecs_doc_get_name(world, e); |
|
#else |
|
lbl = ecs_get_name(world, e); |
|
#endif |
|
return lbl; |
|
} |
|
|
|
void flecs_json_label( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_entity_t e) |
|
{ |
|
const char *lbl = flecs_json_entity_label(world, e); |
|
if (lbl) { |
|
flecs_json_string_escape(buf, lbl); |
|
} else { |
|
ecs_strbuf_appendch(buf, '0'); |
|
} |
|
} |
|
|
|
void flecs_json_color( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_entity_t e) |
|
{ |
|
(void)world; |
|
(void)e; |
|
|
|
const char *color = NULL; |
|
#ifdef FLECS_DOC |
|
color = ecs_doc_get_color(world, e); |
|
#endif |
|
|
|
if (color) { |
|
ecs_strbuf_appendch(buf, '"'); |
|
ecs_strbuf_appendstr(buf, color); |
|
ecs_strbuf_appendch(buf, '"'); |
|
} else { |
|
ecs_strbuf_appendch(buf, '0'); |
|
} |
|
} |
|
|
|
void flecs_json_id( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
ecs_strbuf_appendch(buf, '['); |
|
|
|
if (ECS_IS_PAIR(id)) { |
|
ecs_entity_t first = ecs_pair_first(world, id); |
|
ecs_entity_t second = ecs_pair_second(world, id); |
|
ecs_strbuf_appendch(buf, '"'); |
|
ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); |
|
ecs_strbuf_appendch(buf, '"'); |
|
ecs_strbuf_appendch(buf, ','); |
|
ecs_strbuf_appendch(buf, '"'); |
|
ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); |
|
ecs_strbuf_appendch(buf, '"'); |
|
} else { |
|
ecs_strbuf_appendch(buf, '"'); |
|
ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); |
|
ecs_strbuf_appendch(buf, '"'); |
|
} |
|
|
|
ecs_strbuf_appendch(buf, ']'); |
|
} |
|
|
|
void flecs_json_id_member( |
|
ecs_strbuf_t *buf, |
|
const ecs_world_t *world, |
|
ecs_id_t id) |
|
{ |
|
if (ECS_IS_PAIR(id)) { |
|
ecs_strbuf_appendch(buf, '('); |
|
ecs_entity_t first = ecs_pair_first(world, id); |
|
ecs_entity_t second = ecs_pair_second(world, id); |
|
{ |
|
const char *lbl = flecs_json_entity_label(world, first); |
|
if (lbl) { |
|
ecs_strbuf_appendstr(buf, lbl); |
|
} |
|
} |
|
ecs_strbuf_appendch(buf, ','); |
|
{ |
|
const char *lbl = flecs_json_entity_label(world, second); |
|
if (lbl) { |
|
ecs_strbuf_appendstr(buf, lbl); |
|
} |
|
} |
|
ecs_strbuf_appendch(buf, ')'); |
|
} else { |
|
const char *lbl = flecs_json_entity_label(world, id & ECS_COMPONENT_MASK); |
|
if (lbl) { |
|
ecs_strbuf_appendstr(buf, lbl); |
|
} |
|
} |
|
} |
|
|
|
ecs_primitive_kind_t flecs_json_op_to_primitive_kind( |
|
ecs_meta_type_op_kind_t kind) |
|
{ |
|
return kind - EcsOpPrimitive; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file json/serialize.c |
|
* @brief Serialize (component) values to JSON strings. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_JSON |
|
|
|
static |
|
int json_ser_type_ops( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *ops, |
|
int32_t op_count, |
|
const void *base, |
|
ecs_strbuf_t *str, |
|
int32_t in_array); |
|
|
|
static |
|
int json_ser_type_op( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *base, |
|
ecs_strbuf_t *str); |
|
|
|
/* Serialize enumeration */ |
|
static |
|
int json_ser_enum( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *base, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); |
|
ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
int32_t value = *(const int32_t*)base; |
|
|
|
/* Enumeration constants are stored in a map that is keyed on the |
|
* enumeration value. */ |
|
ecs_enum_constant_t *constant = ecs_map_get_deref(&enum_type->constants, |
|
ecs_enum_constant_t, (ecs_map_key_t)value); |
|
if (!constant) { |
|
/* If the value is not found, it is not a valid enumeration constant */ |
|
char *name = ecs_get_fullpath(world, op->type); |
|
ecs_err("enumeration value '%d' of type '%s' is not a valid constant", |
|
value, name); |
|
ecs_os_free(name); |
|
goto error; |
|
} |
|
|
|
ecs_strbuf_appendch(str, '"'); |
|
ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); |
|
ecs_strbuf_appendch(str, '"'); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Serialize bitmask */ |
|
static |
|
int json_ser_bitmask( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *ptr, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); |
|
ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
uint32_t value = *(const uint32_t*)ptr; |
|
if (!value) { |
|
ecs_strbuf_appendch(str, '0'); |
|
return 0; |
|
} |
|
|
|
ecs_strbuf_list_push(str, "\"", "|"); |
|
|
|
/* Multiple flags can be set at a given time. Iterate through all the flags |
|
* and append the ones that are set. */ |
|
ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); |
|
while (ecs_map_next(&it)) { |
|
ecs_bitmask_constant_t *constant = ecs_map_ptr(&it); |
|
ecs_map_key_t key = ecs_map_key(&it); |
|
if ((value & key) == key) { |
|
ecs_strbuf_list_appendstr(str, |
|
ecs_get_name(world, constant->constant)); |
|
value -= (uint32_t)key; |
|
} |
|
} |
|
|
|
if (value != 0) { |
|
/* All bits must have been matched by a constant */ |
|
char *name = ecs_get_fullpath(world, op->type); |
|
ecs_err("bitmask value '%u' of type '%s' contains invalid/unknown bits", |
|
value, name); |
|
ecs_os_free(name); |
|
goto error; |
|
} |
|
|
|
ecs_strbuf_list_pop(str, "\""); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Serialize elements of a contiguous array */ |
|
static |
|
int json_ser_elements( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *ops, |
|
int32_t op_count, |
|
const void *base, |
|
int32_t elem_count, |
|
int32_t elem_size, |
|
ecs_strbuf_t *str, |
|
bool is_array) |
|
{ |
|
flecs_json_array_push(str); |
|
|
|
const void *ptr = base; |
|
|
|
int i; |
|
for (i = 0; i < elem_count; i ++) { |
|
ecs_strbuf_list_next(str); |
|
if (json_ser_type_ops(world, ops, op_count, ptr, str, is_array)) { |
|
return -1; |
|
} |
|
ptr = ECS_OFFSET(ptr, elem_size); |
|
} |
|
|
|
flecs_json_array_pop(str); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int json_ser_type_elements( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void *base, |
|
int32_t elem_count, |
|
ecs_strbuf_t *str, |
|
bool is_array) |
|
{ |
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, type, EcsMetaTypeSerialized); |
|
ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
const EcsComponent *comp = ecs_get(world, type, EcsComponent); |
|
ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); |
|
int32_t op_count = ecs_vec_count(&ser->ops); |
|
|
|
return json_ser_elements( |
|
world, ops, op_count, base, elem_count, comp->size, str, is_array); |
|
} |
|
|
|
/* Serialize array */ |
|
static |
|
int json_ser_array( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *ptr, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsArray *a = ecs_get(world, op->type, EcsArray); |
|
ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
return json_ser_type_elements( |
|
world, a->type, ptr, a->count, str, true); |
|
} |
|
|
|
/* Serialize vector */ |
|
static |
|
int json_ser_vector( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *base, |
|
ecs_strbuf_t *str) |
|
{ |
|
const ecs_vec_t *value = base; |
|
const EcsVector *v = ecs_get(world, op->type, EcsVector); |
|
ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t count = ecs_vec_count(value); |
|
void *array = ecs_vec_first(value); |
|
|
|
/* Serialize contiguous buffer of vector */ |
|
return json_ser_type_elements(world, v->type, array, count, str, false); |
|
} |
|
|
|
typedef struct json_serializer_ctx_t { |
|
ecs_strbuf_t *str; |
|
bool is_collection; |
|
bool is_struct; |
|
} json_serializer_ctx_t; |
|
|
|
static |
|
int json_ser_custom_value( |
|
const ecs_serializer_t *ser, |
|
ecs_entity_t type, |
|
const void *value) |
|
{ |
|
json_serializer_ctx_t *json_ser = ser->ctx; |
|
if (json_ser->is_collection) { |
|
ecs_strbuf_list_next(json_ser->str); |
|
} |
|
return ecs_ptr_to_json_buf(ser->world, type, value, json_ser->str); |
|
} |
|
|
|
static |
|
int json_ser_custom_member( |
|
const ecs_serializer_t *ser, |
|
const char *name) |
|
{ |
|
json_serializer_ctx_t *json_ser = ser->ctx; |
|
if (!json_ser->is_struct) { |
|
ecs_err("serializer::member can only be called for structs"); |
|
return -1; |
|
} |
|
flecs_json_member(json_ser->str, name); |
|
return 0; |
|
} |
|
|
|
static |
|
int json_ser_custom_type( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *base, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); |
|
ecs_assert(ct != NULL, ECS_INVALID_OPERATION, NULL); |
|
ecs_assert(ct->as_type != 0, ECS_INVALID_OPERATION, NULL); |
|
ecs_assert(ct->serialize != NULL, ECS_INVALID_OPERATION, |
|
ecs_get_name(world, op->type)); |
|
|
|
const EcsMetaType *pt = ecs_get(world, ct->as_type, EcsMetaType); |
|
ecs_assert(pt != NULL, ECS_INVALID_OPERATION, NULL); |
|
|
|
ecs_type_kind_t kind = pt->kind; |
|
bool is_collection = false; |
|
bool is_struct = false; |
|
|
|
if (kind == EcsStructType) { |
|
flecs_json_object_push(str); |
|
is_struct = true; |
|
} else if (kind == EcsArrayType || kind == EcsVectorType) { |
|
flecs_json_array_push(str); |
|
is_collection = true; |
|
} |
|
|
|
json_serializer_ctx_t json_ser = { |
|
.str = str, |
|
.is_struct = is_struct, |
|
.is_collection = is_collection |
|
}; |
|
|
|
ecs_serializer_t ser = { |
|
.world = world, |
|
.value = json_ser_custom_value, |
|
.member = json_ser_custom_member, |
|
.ctx = &json_ser |
|
}; |
|
|
|
if (ct->serialize(&ser, base)) { |
|
return -1; |
|
} |
|
|
|
if (kind == EcsStructType) { |
|
flecs_json_object_pop(str); |
|
} else if (kind == EcsArrayType || kind == EcsVectorType) { |
|
flecs_json_array_pop(str); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Forward serialization to the different type kinds */ |
|
static |
|
int json_ser_type_op( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
const void *ptr, |
|
ecs_strbuf_t *str) |
|
{ |
|
void *vptr = ECS_OFFSET(ptr, op->offset); |
|
bool large_int = false; |
|
if (op->kind == EcsOpI64) { |
|
if (*(int64_t*)vptr >= 2147483648) { |
|
large_int = true; |
|
} |
|
} else if (op->kind == EcsOpU64) { |
|
if (*(uint64_t*)vptr >= 2147483648) { |
|
large_int = true; |
|
} |
|
} |
|
|
|
if (large_int) { |
|
ecs_strbuf_appendch(str, '"'); |
|
} |
|
|
|
switch(op->kind) { |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
/* Should not be parsed as single op */ |
|
ecs_throw(ECS_INVALID_PARAMETER, NULL); |
|
break; |
|
case EcsOpF32: |
|
ecs_strbuf_appendflt(str, |
|
(ecs_f64_t)*(const ecs_f32_t*)vptr, '"'); |
|
break; |
|
case EcsOpF64: |
|
ecs_strbuf_appendflt(str, |
|
*(ecs_f64_t*)vptr, '"'); |
|
break; |
|
case EcsOpEnum: |
|
if (json_ser_enum(world, op, vptr, str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpBitmask: |
|
if (json_ser_bitmask(world, op, vptr, str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpArray: |
|
if (json_ser_array(world, op, vptr, str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpVector: |
|
if (json_ser_vector(world, op, vptr, str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpOpaque: |
|
if (json_ser_custom_type(world, op, vptr, str)) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpEntity: { |
|
ecs_entity_t e = *(const ecs_entity_t*)vptr; |
|
if (!e) { |
|
ecs_strbuf_appendch(str, '0'); |
|
} else { |
|
flecs_json_path(str, world, e); |
|
} |
|
break; |
|
} |
|
case EcsOpId: { |
|
ecs_id_t id = *(const ecs_id_t*)vptr; |
|
if (!id) { |
|
ecs_strbuf_appendch(str, '0'); |
|
} else { |
|
flecs_json_id(str, world, id); |
|
} |
|
break; |
|
} |
|
|
|
case EcsOpU64: |
|
case EcsOpI64: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpString: |
|
if (ecs_primitive_to_expr_buf(world, |
|
flecs_json_op_to_primitive_kind(op->kind), |
|
ECS_OFFSET(ptr, op->offset), str)) |
|
{ |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
break; |
|
|
|
case EcsOpPrimitive: |
|
case EcsOpScope: |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (large_int) { |
|
ecs_strbuf_appendch(str, '"'); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Iterate over a slice of the type ops array */ |
|
static |
|
int json_ser_type_ops( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *ops, |
|
int32_t op_count, |
|
const void *base, |
|
ecs_strbuf_t *str, |
|
int32_t in_array) |
|
{ |
|
for (int i = 0; i < op_count; i ++) { |
|
ecs_meta_type_op_t *op = &ops[i]; |
|
|
|
if (in_array <= 0) { |
|
if (op->name) { |
|
flecs_json_member(str, op->name); |
|
} |
|
|
|
int32_t elem_count = op->count; |
|
if (elem_count > 1) { |
|
/* Serialize inline array */ |
|
if (json_ser_elements(world, op, op->op_count, base, |
|
elem_count, op->size, str, true)) |
|
{ |
|
return -1; |
|
} |
|
|
|
i += op->op_count - 1; |
|
continue; |
|
} |
|
} |
|
|
|
switch(op->kind) { |
|
case EcsOpPush: |
|
flecs_json_object_push(str); |
|
in_array --; |
|
break; |
|
case EcsOpPop: |
|
flecs_json_object_pop(str); |
|
in_array ++; |
|
break; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
case EcsOpString: |
|
case EcsOpOpaque: |
|
if (json_ser_type_op(world, op, base, str)) { |
|
goto error; |
|
} |
|
break; |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Iterate over the type ops of a type */ |
|
int json_ser_type( |
|
const ecs_world_t *world, |
|
const ecs_vec_t *v_ops, |
|
const void *base, |
|
ecs_strbuf_t *str) |
|
{ |
|
ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); |
|
int32_t count = ecs_vec_count(v_ops); |
|
return json_ser_type_ops(world, ops, count, base, str, 0); |
|
} |
|
|
|
static |
|
int array_to_json_buf_w_type_data( |
|
const ecs_world_t *world, |
|
const void *ptr, |
|
int32_t count, |
|
ecs_strbuf_t *buf, |
|
const EcsComponent *comp, |
|
const EcsMetaTypeSerialized *ser) |
|
{ |
|
if (count) { |
|
ecs_size_t size = comp->size; |
|
|
|
flecs_json_array_push(buf); |
|
|
|
do { |
|
ecs_strbuf_list_next(buf); |
|
if (json_ser_type(world, &ser->ops, ptr, buf)) { |
|
return -1; |
|
} |
|
|
|
ptr = ECS_OFFSET(ptr, size); |
|
} while (-- count); |
|
|
|
flecs_json_array_pop(buf); |
|
} else { |
|
if (json_ser_type(world, &ser->ops, ptr, buf)) { |
|
return -1; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int ecs_array_to_json_buf( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void *ptr, |
|
int32_t count, |
|
ecs_strbuf_t *buf) |
|
{ |
|
const EcsComponent *comp = ecs_get(world, type, EcsComponent); |
|
if (!comp) { |
|
char *path = ecs_get_fullpath(world, type); |
|
ecs_err("cannot serialize to JSON, '%s' is not a component", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, type, EcsMetaTypeSerialized); |
|
if (!ser) { |
|
char *path = ecs_get_fullpath(world, type); |
|
ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); |
|
} |
|
|
|
char* ecs_array_to_json( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void* ptr, |
|
int32_t count) |
|
{ |
|
ecs_strbuf_t str = ECS_STRBUF_INIT; |
|
|
|
if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { |
|
ecs_strbuf_reset(&str); |
|
return NULL; |
|
} |
|
|
|
return ecs_strbuf_get(&str); |
|
} |
|
|
|
int ecs_ptr_to_json_buf( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void *ptr, |
|
ecs_strbuf_t *buf) |
|
{ |
|
return ecs_array_to_json_buf(world, type, ptr, 0, buf); |
|
} |
|
|
|
char* ecs_ptr_to_json( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
const void* ptr) |
|
{ |
|
return ecs_array_to_json(world, type, ptr, 0); |
|
} |
|
|
|
static |
|
bool flecs_json_skip_id( |
|
const ecs_world_t *world, |
|
ecs_id_t id, |
|
const ecs_entity_to_json_desc_t *desc, |
|
ecs_entity_t ent, |
|
ecs_entity_t inst, |
|
ecs_entity_t *pred_out, |
|
ecs_entity_t *obj_out, |
|
ecs_entity_t *role_out, |
|
bool *hidden_out) |
|
{ |
|
bool is_base = ent != inst; |
|
ecs_entity_t pred = 0, obj = 0, role = 0; |
|
bool hidden = false; |
|
|
|
if (ECS_HAS_ID_FLAG(id, PAIR)) { |
|
pred = ecs_pair_first(world, id); |
|
obj = ecs_pair_second(world, id); |
|
} else { |
|
pred = id & ECS_COMPONENT_MASK; |
|
if (id & ECS_ID_FLAGS_MASK) { |
|
role = id & ECS_ID_FLAGS_MASK; |
|
} |
|
} |
|
|
|
if (is_base) { |
|
if (ecs_has_id(world, pred, EcsDontInherit)) { |
|
return true; |
|
} |
|
} |
|
if (!desc || !desc->serialize_private) { |
|
if (ecs_has_id(world, pred, EcsPrivate)) { |
|
return true; |
|
} |
|
} |
|
if (is_base) { |
|
if (ecs_get_target_for_id(world, inst, EcsIsA, id) != ent) { |
|
hidden = true; |
|
} |
|
} |
|
if (hidden && (!desc || !desc->serialize_hidden)) { |
|
return true; |
|
} |
|
|
|
*pred_out = pred; |
|
*obj_out = obj; |
|
*role_out = role; |
|
if (hidden_out) *hidden_out = hidden; |
|
|
|
return false; |
|
} |
|
|
|
static |
|
int flecs_json_append_type_labels( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
const ecs_id_t *ids, |
|
int32_t count, |
|
ecs_entity_t ent, |
|
ecs_entity_t inst, |
|
const ecs_entity_to_json_desc_t *desc) |
|
{ |
|
(void)world; (void)buf; (void)ids; (void)count; (void)ent; (void)inst; |
|
(void)desc; |
|
|
|
#ifdef FLECS_DOC |
|
if (!desc || !desc->serialize_id_labels) { |
|
return 0; |
|
} |
|
|
|
flecs_json_memberl(buf, "id_labels"); |
|
flecs_json_array_push(buf); |
|
|
|
int32_t i; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t pred = 0, obj = 0, role = 0; |
|
if (flecs_json_skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { |
|
continue; |
|
} |
|
|
|
if (obj && (pred == EcsUnion)) { |
|
pred = obj; |
|
obj = ecs_get_target(world, ent, pred, 0); |
|
if (!ecs_is_alive(world, obj)) { |
|
/* Union relationships aren't automatically cleaned up, so they |
|
* can contain invalid entity ids. Don't serialize value until |
|
* relationship is valid again. */ |
|
continue; |
|
} |
|
} |
|
|
|
if (desc && desc->serialize_id_labels) { |
|
flecs_json_next(buf); |
|
|
|
flecs_json_array_push(buf); |
|
flecs_json_next(buf); |
|
flecs_json_label(buf, world, pred); |
|
if (obj) { |
|
flecs_json_next(buf); |
|
flecs_json_label(buf, world, obj); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
#endif |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_append_type_values( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
const ecs_id_t *ids, |
|
int32_t count, |
|
ecs_entity_t ent, |
|
ecs_entity_t inst, |
|
const ecs_entity_to_json_desc_t *desc) |
|
{ |
|
if (!desc || !desc->serialize_values) { |
|
return 0; |
|
} |
|
|
|
flecs_json_memberl(buf, "values"); |
|
flecs_json_array_push(buf); |
|
|
|
int32_t i; |
|
for (i = 0; i < count; i ++) { |
|
bool hidden; |
|
ecs_entity_t pred = 0, obj = 0, role = 0; |
|
ecs_id_t id = ids[i]; |
|
if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, |
|
&hidden)) |
|
{ |
|
continue; |
|
} |
|
|
|
if (!hidden) { |
|
bool serialized = false; |
|
ecs_entity_t typeid = ecs_get_typeid(world, id); |
|
if (typeid) { |
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, typeid, EcsMetaTypeSerialized); |
|
if (ser) { |
|
const void *ptr = ecs_get_id(world, ent, id); |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_json_next(buf); |
|
if (json_ser_type(world, &ser->ops, ptr, buf) != 0) { |
|
/* Entity contains invalid value */ |
|
return -1; |
|
} |
|
serialized = true; |
|
} |
|
} |
|
if (!serialized) { |
|
flecs_json_next(buf); |
|
flecs_json_number(buf, 0); |
|
} |
|
} else { |
|
if (!desc || desc->serialize_hidden) { |
|
flecs_json_next(buf); |
|
flecs_json_number(buf, 0); |
|
} |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_append_type_info( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
const ecs_id_t *ids, |
|
int32_t count, |
|
ecs_entity_t ent, |
|
ecs_entity_t inst, |
|
const ecs_entity_to_json_desc_t *desc) |
|
{ |
|
if (!desc || !desc->serialize_type_info) { |
|
return 0; |
|
} |
|
|
|
flecs_json_memberl(buf, "type_info"); |
|
flecs_json_array_push(buf); |
|
|
|
int32_t i; |
|
for (i = 0; i < count; i ++) { |
|
bool hidden; |
|
ecs_entity_t pred = 0, obj = 0, role = 0; |
|
ecs_id_t id = ids[i]; |
|
if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, |
|
&hidden)) |
|
{ |
|
continue; |
|
} |
|
|
|
if (!hidden) { |
|
ecs_entity_t typeid = ecs_get_typeid(world, id); |
|
if (typeid) { |
|
flecs_json_next(buf); |
|
if (ecs_type_info_to_json_buf(world, typeid, buf) != 0) { |
|
return -1; |
|
} |
|
} else { |
|
flecs_json_next(buf); |
|
flecs_json_number(buf, 0); |
|
} |
|
} else { |
|
if (!desc || desc->serialize_hidden) { |
|
flecs_json_next(buf); |
|
flecs_json_number(buf, 0); |
|
} |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_append_type_hidden( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
const ecs_id_t *ids, |
|
int32_t count, |
|
ecs_entity_t ent, |
|
ecs_entity_t inst, |
|
const ecs_entity_to_json_desc_t *desc) |
|
{ |
|
if (!desc || !desc->serialize_hidden) { |
|
return 0; |
|
} |
|
|
|
if (ent == inst) { |
|
return 0; /* if this is not a base, components are never hidden */ |
|
} |
|
|
|
flecs_json_memberl(buf, "hidden"); |
|
flecs_json_array_push(buf); |
|
|
|
int32_t i; |
|
for (i = 0; i < count; i ++) { |
|
bool hidden; |
|
ecs_entity_t pred = 0, obj = 0, role = 0; |
|
ecs_id_t id = ids[i]; |
|
if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, |
|
&hidden)) |
|
{ |
|
continue; |
|
} |
|
|
|
flecs_json_next(buf); |
|
flecs_json_bool(buf, hidden); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_append_type( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
ecs_entity_t ent, |
|
ecs_entity_t inst, |
|
const ecs_entity_to_json_desc_t *desc) |
|
{ |
|
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; |
|
} |
|
|
|
if (!desc || desc->serialize_ids) { |
|
flecs_json_memberl(buf, "ids"); |
|
flecs_json_array_push(buf); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t pred = 0, obj = 0, role = 0; |
|
if (flecs_json_skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { |
|
continue; |
|
} |
|
|
|
if (obj && (pred == EcsUnion)) { |
|
pred = obj; |
|
obj = ecs_get_target(world, ent, pred, 0); |
|
if (!ecs_is_alive(world, obj)) { |
|
/* Union relationships aren't automatically cleaned up, so they |
|
* can contain invalid entity ids. Don't serialize value until |
|
* relationship is valid again. */ |
|
continue; |
|
} |
|
} |
|
|
|
flecs_json_next(buf); |
|
flecs_json_array_push(buf); |
|
flecs_json_next(buf); |
|
flecs_json_path(buf, world, pred); |
|
if (obj || role) { |
|
flecs_json_next(buf); |
|
if (obj) { |
|
flecs_json_path(buf, world, obj); |
|
} else { |
|
flecs_json_number(buf, 0); |
|
} |
|
if (role) { |
|
flecs_json_next(buf); |
|
flecs_json_string(buf, ecs_id_flag_str(role)); |
|
} |
|
} |
|
flecs_json_array_pop(buf); |
|
} |
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
if (flecs_json_append_type_labels(world, buf, ids, count, ent, inst, desc)) { |
|
return -1; |
|
} |
|
|
|
if (flecs_json_append_type_values(world, buf, ids, count, ent, inst, desc)) { |
|
return -1; |
|
} |
|
|
|
if (flecs_json_append_type_info(world, buf, ids, count, ent, inst, desc)) { |
|
return -1; |
|
} |
|
|
|
if (flecs_json_append_type_hidden(world, buf, ids, count, ent, inst, desc)) { |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_append_base( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
ecs_entity_t ent, |
|
ecs_entity_t inst, |
|
const ecs_entity_to_json_desc_t *desc) |
|
{ |
|
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]; |
|
if (ECS_HAS_RELATION(id, EcsIsA)) { |
|
if (flecs_json_append_base(world, buf, ecs_pair_second(world, id), inst, desc)) |
|
{ |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
ecs_strbuf_list_next(buf); |
|
flecs_json_object_push(buf); |
|
flecs_json_memberl(buf, "path"); |
|
flecs_json_path(buf, world, ent); |
|
|
|
if (flecs_json_append_type(world, buf, ent, inst, desc)) { |
|
return -1; |
|
} |
|
|
|
flecs_json_object_pop(buf); |
|
|
|
return 0; |
|
} |
|
|
|
#ifdef FLECS_ALERTS |
|
static |
|
int flecs_json_serialize_entity_alerts( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
ecs_entity_t entity, |
|
const EcsAlertsActive *alerts, |
|
bool self) |
|
{ |
|
ecs_assert(alerts != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_map_iter_t it = ecs_map_iter(&alerts->alerts); |
|
while (ecs_map_next(&it)) { |
|
flecs_json_next(buf); |
|
flecs_json_object_push(buf); |
|
ecs_entity_t ai = ecs_map_value(&it); |
|
char *alert_name = ecs_get_fullpath(world, ai); |
|
flecs_json_memberl(buf, "alert"); |
|
flecs_json_string(buf, alert_name); |
|
ecs_os_free(alert_name); |
|
|
|
ecs_entity_t severity_id = ecs_get_target( |
|
world, ai, ecs_id(EcsAlert), 0); |
|
const char *severity = ecs_get_name(world, severity_id); |
|
|
|
const EcsAlertInstance *alert = ecs_get( |
|
world, ai, EcsAlertInstance); |
|
if (alert) { |
|
if (alert->message) { |
|
flecs_json_memberl(buf, "message"); |
|
flecs_json_string(buf, alert->message); |
|
} |
|
flecs_json_memberl(buf, "severity"); |
|
flecs_json_string(buf, severity); |
|
|
|
if (!self) { |
|
char *path = ecs_get_fullpath(world, entity); |
|
flecs_json_memberl(buf, "path"); |
|
flecs_json_string(buf, path); |
|
ecs_os_free(path); |
|
} |
|
} |
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_serialize_children_alerts( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_filter_t f = ECS_FILTER_INIT; |
|
ecs_filter(ECS_CONST_CAST(ecs_world_t*, world), { |
|
.storage = &f, |
|
.terms = {{ .id = ecs_pair(EcsChildOf, entity) }} |
|
}); |
|
|
|
ecs_iter_t it = ecs_filter_iter(world, &f); |
|
while (ecs_filter_next(&it)) { |
|
EcsAlertsActive *alerts = ecs_table_get_id( |
|
world, it.table, ecs_id(EcsAlertsActive), it.offset); |
|
|
|
int32_t i; |
|
for (i = 0; i < it.count; i ++) { |
|
ecs_entity_t child = it.entities[i]; |
|
if (alerts) { |
|
if (flecs_json_serialize_entity_alerts( |
|
world, buf, child, &alerts[i], false)) |
|
{ |
|
goto error; |
|
} |
|
} |
|
|
|
ecs_record_t *r = flecs_entities_get(world, it.entities[i]); |
|
if (r->row & EcsEntityIsTraversable) { |
|
if (flecs_json_serialize_children_alerts( |
|
world, buf, child)) |
|
{ |
|
goto error; |
|
} |
|
} |
|
} |
|
} |
|
|
|
ecs_filter_fini(&f); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
#endif |
|
|
|
static |
|
int flecs_json_serialize_alerts( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
ecs_entity_t entity) |
|
{ |
|
(void)world; |
|
(void)buf; |
|
(void)entity; |
|
|
|
#ifdef FLECS_ALERTS |
|
if (!ecs_id(EcsAlertsActive)) { |
|
return 0; /* Alert module not imported */ |
|
} |
|
|
|
flecs_json_memberl(buf, "alerts"); |
|
flecs_json_array_push(buf); |
|
const EcsAlertsActive *alerts = ecs_get(world, entity, EcsAlertsActive); |
|
if (alerts) { |
|
flecs_json_serialize_entity_alerts(world, buf, entity, alerts, true); |
|
} |
|
flecs_json_serialize_children_alerts(world, buf, entity); |
|
flecs_json_array_pop(buf); |
|
#endif |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_serialize_refs_idr( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
ecs_id_record_t *idr) |
|
{ |
|
char *id_str = ecs_id_str(world, ecs_pair_first(world, idr->id)); |
|
|
|
flecs_json_member(buf, id_str); |
|
ecs_os_free(id_str); |
|
|
|
flecs_json_array_push(buf); |
|
|
|
ecs_table_cache_iter_t it; |
|
if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { |
|
const ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
int32_t i, count = ecs_table_count(table); |
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities); |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = entities[i]; |
|
flecs_json_next(buf); |
|
flecs_json_path(buf, world, e); |
|
} |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_serialize_refs( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
ecs_entity_t entity, |
|
ecs_entity_t relationship) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, |
|
ecs_pair(relationship, entity)); |
|
|
|
if (idr) { |
|
if (relationship == EcsWildcard) { |
|
ecs_id_record_t *cur = idr; |
|
while ((cur = cur->second.next)) { |
|
flecs_json_serialize_refs_idr(world, buf, cur); |
|
} |
|
} else { |
|
flecs_json_serialize_refs_idr(world, buf, idr); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_serialize_matches( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *buf, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, |
|
ecs_pair_t(EcsPoly, EcsQuery)); |
|
|
|
if (idr) { |
|
ecs_table_cache_iter_t it; |
|
if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { |
|
const ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); |
|
|
|
int32_t i, count = ecs_table_count(table); |
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities); |
|
for (i = 0; i < count; i ++) { |
|
ecs_poly_t *q = queries[i].poly; |
|
ecs_iter_t qit; |
|
ecs_iter_poly(world, q, &qit, NULL); |
|
if (!qit.variables) { |
|
ecs_iter_fini(&qit); |
|
continue; |
|
} |
|
ecs_iter_set_var(&qit, 0, entity); |
|
if (ecs_iter_is_true(&qit)) { |
|
flecs_json_next(buf); |
|
flecs_json_path(buf, world, entities[i]); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int ecs_entity_to_json_buf( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
ecs_strbuf_t *buf, |
|
const ecs_entity_to_json_desc_t *desc) |
|
{ |
|
if (!entity || !ecs_is_valid(world, entity)) { |
|
return -1; |
|
} |
|
|
|
flecs_json_object_push(buf); |
|
|
|
if (!desc || desc->serialize_path) { |
|
flecs_json_memberl(buf, "path"); |
|
flecs_json_path(buf, world, entity); |
|
} |
|
|
|
#ifdef FLECS_DOC |
|
if (desc && desc->serialize_label) { |
|
flecs_json_memberl(buf, "label"); |
|
const char *doc_name = ecs_doc_get_name(world, entity); |
|
if (doc_name) { |
|
flecs_json_string_escape(buf, doc_name); |
|
} else { |
|
char num_buf[20]; |
|
ecs_os_sprintf(num_buf, "%u", (uint32_t)entity); |
|
flecs_json_string(buf, num_buf); |
|
} |
|
} |
|
|
|
if (desc && desc->serialize_brief) { |
|
const char *doc_brief = ecs_doc_get_brief(world, entity); |
|
if (doc_brief) { |
|
flecs_json_memberl(buf, "brief"); |
|
flecs_json_string_escape(buf, doc_brief); |
|
} |
|
} |
|
|
|
if (desc && desc->serialize_link) { |
|
const char *doc_link = ecs_doc_get_link(world, entity); |
|
if (doc_link) { |
|
flecs_json_memberl(buf, "link"); |
|
flecs_json_string_escape(buf, doc_link); |
|
} |
|
} |
|
|
|
if (desc && desc->serialize_color) { |
|
const char *doc_color = ecs_doc_get_color(world, entity); |
|
if (doc_color) { |
|
flecs_json_memberl(buf, "color"); |
|
flecs_json_string_escape(buf, doc_color); |
|
} |
|
} |
|
#endif |
|
|
|
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)) { |
|
flecs_json_memberl(buf, "is_a"); |
|
flecs_json_array_push(buf); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_id_t id = ids[i]; |
|
if (ECS_HAS_RELATION(id, EcsIsA)) { |
|
if (flecs_json_append_base( |
|
world, buf, ecs_pair_second(world, id), entity, desc)) |
|
{ |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
} |
|
|
|
if (flecs_json_append_type(world, buf, entity, entity, desc)) { |
|
goto error; |
|
} |
|
|
|
if (desc && desc->serialize_alerts) { |
|
if (flecs_json_serialize_alerts(world, buf, entity)) { |
|
goto error; |
|
} |
|
} |
|
|
|
if (desc && desc->serialize_refs) { |
|
flecs_json_memberl(buf, "refs"); |
|
flecs_json_object_push(buf); |
|
if (flecs_json_serialize_refs(world, buf, entity, desc->serialize_refs)) { |
|
goto error; |
|
} |
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
if (desc && desc->serialize_matches) { |
|
flecs_json_memberl(buf, "matches"); |
|
flecs_json_array_push(buf); |
|
if (flecs_json_serialize_matches(world, buf, entity)) { |
|
goto error; |
|
} |
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
flecs_json_object_pop(buf); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
char* ecs_entity_to_json( |
|
const ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const ecs_entity_to_json_desc_t *desc) |
|
{ |
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
|
|
if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { |
|
ecs_strbuf_reset(&buf); |
|
return NULL; |
|
} |
|
|
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
bool flecs_json_skip_variable( |
|
const char *name) |
|
{ |
|
if (!name || name[0] == '_' || !ecs_os_strcmp(name, "this")) { |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
void flecs_json_serialize_id( |
|
const ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_strbuf_t *buf) |
|
{ |
|
flecs_json_id(buf, world, id); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_id_label( |
|
const ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_entity_t pred = id, obj = 0; |
|
if (ECS_IS_PAIR(id)) { |
|
pred = ecs_pair_first(world, id); |
|
obj = ecs_pair_second(world, id); |
|
} |
|
|
|
flecs_json_array_push(buf); |
|
flecs_json_next(buf); |
|
flecs_json_label(buf, world, pred); |
|
if (obj) { |
|
flecs_json_next(buf); |
|
flecs_json_label(buf, world, obj); |
|
} |
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_ids( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
int32_t field_count = it->field_count; |
|
if (!field_count) { |
|
return; |
|
} |
|
|
|
flecs_json_memberl(buf, "ids"); |
|
flecs_json_array_push(buf); |
|
|
|
for (int i = 0; i < field_count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_serialize_id(world, it->terms[i].id, buf); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_id_labels( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
int32_t field_count = it->field_count; |
|
if (!field_count) { |
|
return; |
|
} |
|
|
|
flecs_json_memberl(buf, "id_labels"); |
|
flecs_json_array_push(buf); |
|
|
|
for (int i = 0; i < field_count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_serialize_id_label(world, it->terms[i].id, buf); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_id_str( |
|
const ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_strbuf_appendch(buf, '"'); |
|
if (ECS_IS_PAIR(id)) { |
|
ecs_entity_t first = ecs_pair_first(world, id); |
|
ecs_entity_t second = ecs_pair_first(world, id); |
|
ecs_strbuf_appendch(buf, '('); |
|
ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); |
|
ecs_strbuf_appendch(buf, ','); |
|
ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); |
|
ecs_strbuf_appendch(buf, ')'); |
|
} else { |
|
ecs_get_path_w_sep_buf( |
|
world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); |
|
} |
|
ecs_strbuf_appendch(buf, '"'); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_type_info( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
int32_t field_count = it->field_count; |
|
if (!field_count) { |
|
return; |
|
} |
|
|
|
if (it->flags & EcsIterNoData) { |
|
return; |
|
} |
|
|
|
flecs_json_memberl(buf, "type_info"); |
|
flecs_json_object_push(buf); |
|
|
|
for (int i = 0; i < field_count; i ++) { |
|
flecs_json_next(buf); |
|
ecs_entity_t typeid = 0; |
|
if (it->terms[i].inout != EcsInOutNone) { |
|
typeid = ecs_get_typeid(world, it->terms[i].id); |
|
} |
|
if (typeid) { |
|
flecs_json_serialize_id_str(world, typeid, buf); |
|
ecs_strbuf_appendch(buf, ':'); |
|
ecs_type_info_to_json_buf(world, typeid, buf); |
|
} else { |
|
flecs_json_serialize_id_str(world, it->terms[i].id, buf); |
|
ecs_strbuf_appendlit(buf, ":0"); |
|
} |
|
} |
|
|
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_field_info( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
ecs_json_ser_ctx_t *ctx) |
|
{ |
|
int32_t field_count = it->field_count; |
|
if (!field_count || !it->query) { |
|
return; |
|
} |
|
|
|
const ecs_filter_t *q = it->query; |
|
|
|
flecs_json_memberl(buf, "field_info"); |
|
flecs_json_array_push(buf); |
|
|
|
int f, t; |
|
for (f = 0; f < field_count; f ++) { |
|
flecs_json_next(buf); |
|
flecs_json_object_push(buf); |
|
flecs_json_memberl(buf, "id"); |
|
|
|
flecs_json_serialize_get_field_ctx(world, it, f, ctx); |
|
ecs_json_value_ser_ctx_t *value_ctx = &ctx->value_ctx[f]; |
|
|
|
if (value_ctx->id_label) { |
|
flecs_json_string(buf, value_ctx->id_label); |
|
|
|
const ecs_term_t *term = NULL; |
|
for (t = 0; t < q->term_count; t ++) { |
|
if (q->terms[t].field_index == f) { |
|
term = &q->terms[t]; |
|
break; |
|
} |
|
} |
|
|
|
ecs_assert(term != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (term->oper != EcsNot) { |
|
if (term->oper == EcsOptional) { |
|
flecs_json_memberl(buf, "optional"); |
|
flecs_json_bool(buf, true); |
|
} |
|
|
|
if (ECS_IS_PAIR(term->id)) { |
|
if (term->first.flags & EcsIsEntity && term->first.id) { |
|
if (ecs_has_id(world, term->first.id, EcsExclusive)) { |
|
flecs_json_memberl(buf, "exclusive"); |
|
flecs_json_bool(buf, true); |
|
} |
|
} |
|
} |
|
|
|
if (value_ctx->type) { |
|
flecs_json_memberl(buf, "type"); |
|
flecs_json_label(buf, world, value_ctx->type); |
|
} |
|
|
|
if (value_ctx->ser) { |
|
flecs_json_memberl(buf, "schema"); |
|
ecs_type_info_to_json_buf(world, value_ctx->type, buf); |
|
} |
|
} else { |
|
flecs_json_memberl(buf, "not"); |
|
flecs_json_bool(buf, true); |
|
} |
|
} else { |
|
ecs_strbuf_appendlit(buf, "0"); |
|
} |
|
|
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { |
|
char **variable_names = it->variable_names; |
|
int32_t var_count = it->variable_count; |
|
int32_t actual_count = 0; |
|
|
|
for (int i = 1; i < var_count; i ++) { |
|
const char *var_name = variable_names[i]; |
|
if (flecs_json_skip_variable(var_name)) continue; |
|
|
|
if (!actual_count) { |
|
flecs_json_memberl(buf, "vars"); |
|
flecs_json_array_push(buf); |
|
actual_count ++; |
|
} |
|
|
|
ecs_strbuf_list_next(buf); |
|
flecs_json_string(buf, var_name); |
|
} |
|
|
|
if (actual_count) { |
|
flecs_json_array_pop(buf); |
|
} |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_ids( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
flecs_json_memberl(buf, "ids"); |
|
flecs_json_array_push(buf); |
|
|
|
for (int i = 0; i < it->field_count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_serialize_id(world, ecs_field_id(it, i + 1), buf); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_id_labels( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
flecs_json_memberl(buf, "id_labels"); |
|
flecs_json_array_push(buf); |
|
|
|
for (int i = 0; i < it->field_count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_serialize_id_label(world, ecs_field_id(it, i + 1), buf); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_table_type( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc) |
|
{ |
|
if (!it->table) { |
|
return; |
|
} |
|
|
|
if (desc->serialize_ids) { |
|
flecs_json_memberl(buf, "ids"); |
|
flecs_json_array_push(buf); |
|
|
|
ecs_type_t *type = &it->table->type; |
|
for (int i = 0; i < type->count; i ++) { |
|
ecs_id_t id = type->array[i]; |
|
if (!desc->serialize_private) { |
|
ecs_entity_t e = id; |
|
if (ECS_IS_PAIR(id)) { |
|
e = ecs_pair_first(world, id); |
|
} |
|
if (ecs_owns_id(world, e, EcsPrivate)) { |
|
continue; |
|
} |
|
} |
|
flecs_json_next(buf); |
|
flecs_json_serialize_id(world, id, buf); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
if (desc->serialize_id_labels) { |
|
flecs_json_memberl(buf, "id_labels"); |
|
flecs_json_array_push(buf); |
|
|
|
ecs_type_t *type = &it->table->type; |
|
for (int i = 0; i < type->count; i ++) { |
|
ecs_id_t id = type->array[i]; |
|
if (!desc->serialize_private) { |
|
ecs_entity_t e = id; |
|
if (ECS_IS_PAIR(id)) { |
|
e = ecs_pair_first(world, id); |
|
} |
|
if (ecs_owns_id(world, e, EcsPrivate)) { |
|
continue; |
|
} |
|
} |
|
flecs_json_next(buf); |
|
flecs_json_serialize_id_label(world, id, buf); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_sources( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
flecs_json_memberl(buf, "sources"); |
|
flecs_json_array_push(buf); |
|
|
|
for (int i = 0; i < it->field_count; i ++) { |
|
flecs_json_next(buf); |
|
ecs_entity_t subj = it->sources[i]; |
|
if (subj) { |
|
flecs_json_path(buf, world, subj); |
|
} else { |
|
ecs_strbuf_appendch(buf, '0'); |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
bool flecs_json_serialize_iter_result_is_set( |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
if (!(it->flags & EcsIterHasCondSet)) { |
|
return false; |
|
} |
|
|
|
flecs_json_memberl(buf, "is_set"); |
|
flecs_json_array_push(buf); |
|
|
|
for (int i = 0; i < it->field_count; i ++) { |
|
ecs_strbuf_list_next(buf); |
|
if (ecs_field_is_set(it, i + 1)) { |
|
flecs_json_true(buf); |
|
} else { |
|
flecs_json_false(buf); |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_variables( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
char **variable_names = it->variable_names; |
|
ecs_var_t *variables = it->variables; |
|
int32_t var_count = it->variable_count; |
|
int32_t actual_count = 0; |
|
|
|
for (int i = 1; i < var_count; i ++) { |
|
const char *var_name = variable_names[i]; |
|
if (flecs_json_skip_variable(var_name)) continue; |
|
|
|
if (!actual_count) { |
|
flecs_json_memberl(buf, "vars"); |
|
flecs_json_array_push(buf); |
|
actual_count ++; |
|
} |
|
|
|
ecs_strbuf_list_next(buf); |
|
flecs_json_path(buf, world, variables[i].entity); |
|
} |
|
|
|
if (actual_count) { |
|
flecs_json_array_pop(buf); |
|
} |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_variable_labels( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
char **variable_names = it->variable_names; |
|
ecs_var_t *variables = it->variables; |
|
int32_t var_count = it->variable_count; |
|
int32_t actual_count = 0; |
|
|
|
for (int i = 1; i < var_count; i ++) { |
|
const char *var_name = variable_names[i]; |
|
if (flecs_json_skip_variable(var_name)) continue; |
|
|
|
if (!actual_count) { |
|
flecs_json_memberl(buf, "var_labels"); |
|
flecs_json_array_push(buf); |
|
actual_count ++; |
|
} |
|
|
|
ecs_strbuf_list_next(buf); |
|
flecs_json_label(buf, world, variables[i].entity); |
|
} |
|
|
|
if (actual_count) { |
|
flecs_json_array_pop(buf); |
|
} |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_variable_ids( |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
char **variable_names = it->variable_names; |
|
ecs_var_t *variables = it->variables; |
|
int32_t var_count = it->variable_count; |
|
int32_t actual_count = 0; |
|
|
|
for (int i = 1; i < var_count; i ++) { |
|
const char *var_name = variable_names[i]; |
|
if (flecs_json_skip_variable(var_name)) continue; |
|
|
|
if (!actual_count) { |
|
flecs_json_memberl(buf, "var_ids"); |
|
flecs_json_array_push(buf); |
|
actual_count ++; |
|
} |
|
|
|
ecs_strbuf_list_next(buf); |
|
flecs_json_number(buf, (double)variables[i].entity); |
|
} |
|
|
|
if (actual_count) { |
|
flecs_json_array_pop(buf); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_json_serialize_iter_result_entity_names( |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
EcsIdentifier *names = ecs_table_get_id(it->world, it->table, |
|
ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); |
|
if (!names) { |
|
return false; |
|
} |
|
|
|
int i; |
|
for (i = 0; i < it->count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_string(buf, names[i].value); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_entity_ids( |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
if (!it->count) { |
|
return; |
|
} |
|
|
|
flecs_json_memberl(buf, "entity_ids"); |
|
flecs_json_array_push(buf); |
|
|
|
ecs_entity_t *entities = it->entities; |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_number(buf, (double)(uint32_t)entities[i]); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_parent( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
ecs_table_t *table = it->table; |
|
if (!(table->flags & EcsTableHasChildOf)) { |
|
return; |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table( |
|
world->idr_childof_wildcard, it->table); |
|
if (tr == NULL) { |
|
return; |
|
} |
|
|
|
ecs_id_t id = table->type.array[tr->index]; |
|
ecs_entity_t parent = ecs_pair_second(world, id); |
|
char *path = ecs_get_fullpath(world, parent); |
|
flecs_json_memberl(buf, "parent"); |
|
flecs_json_string(buf, path); |
|
ecs_os_free(path); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_entities( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
if (!it->count) { |
|
return; |
|
} |
|
|
|
flecs_json_serialize_iter_result_parent(world, it, buf); |
|
|
|
flecs_json_memberl(buf, "entities"); |
|
flecs_json_array_push(buf); |
|
|
|
if (!flecs_json_serialize_iter_result_entity_names(it, buf)) { |
|
ecs_entity_t *entities = it->entities; |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_number(buf, (double)(uint32_t)entities[i]); |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_entity_labels( |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_json_ser_ctx_t *ser_ctx) |
|
{ |
|
(void)buf; |
|
(void)ser_ctx; |
|
if (!it->count) { |
|
return; |
|
} |
|
|
|
if (!ser_ctx->idr_doc_name) { |
|
return; |
|
} |
|
|
|
#ifdef FLECS_DOC |
|
ecs_table_t *table = it->table; |
|
ecs_table_record_t *tr = flecs_id_record_get_table( |
|
ser_ctx->idr_doc_name, table); |
|
if (tr == NULL) { |
|
return; |
|
} |
|
|
|
EcsDocDescription *labels = ecs_table_get_column( |
|
table, tr->column, it->offset); |
|
ecs_assert(labels != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_json_memberl(buf, "entity_labels"); |
|
flecs_json_array_push(buf); |
|
|
|
int i; |
|
for (i = 0; i < it->count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_string_escape(buf, labels[i].value); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
#endif |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_result_colors( |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_json_ser_ctx_t *ser_ctx) |
|
{ |
|
(void)buf; |
|
(void)ser_ctx; |
|
|
|
if (!it->count) { |
|
return; |
|
} |
|
|
|
#ifdef FLECS_DOC |
|
if (!ser_ctx->idr_doc_color) { |
|
return; |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table( |
|
ser_ctx->idr_doc_color, it->table); |
|
if (tr == NULL) { |
|
return; |
|
} |
|
|
|
EcsDocDescription *colors = ecs_table_get_column( |
|
it->table, tr->column, it->offset); |
|
ecs_assert(colors != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_json_memberl(buf, "colors"); |
|
flecs_json_array_push(buf); |
|
|
|
int i; |
|
for (i = 0; i < it->count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_string(buf, colors[i].value); |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
#endif |
|
} |
|
|
|
static |
|
int flecs_json_serialize_iter_result_values( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
if (!it->ptrs || (it->flags & EcsIterNoData)) { |
|
return 0; |
|
} |
|
|
|
flecs_json_memberl(buf, "values"); |
|
flecs_json_array_push(buf); |
|
|
|
int32_t i, term_count = it->field_count; |
|
for (i = 0; i < term_count; i ++) { |
|
ecs_strbuf_list_next(buf); |
|
|
|
const void *ptr = NULL; |
|
if (it->ptrs) { |
|
ptr = it->ptrs[i]; |
|
} |
|
|
|
if (!ptr) { |
|
/* No data in column. Append 0 if this is not an optional term */ |
|
if (ecs_field_is_set(it, i + 1)) { |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
} |
|
|
|
if (ecs_field_is_writeonly(it, i + 1)) { |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
|
|
/* Get component id (can be different in case of pairs) */ |
|
ecs_entity_t type = ecs_get_typeid(world, it->ids[i]); |
|
if (!type) { |
|
/* Odd, we have a ptr but no Component? Not the place of the |
|
* serializer to complain about that. */ |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
|
|
const EcsComponent *comp = ecs_get(world, type, EcsComponent); |
|
if (!comp) { |
|
/* Also odd, typeid but not a component? */ |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
|
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, type, EcsMetaTypeSerialized); |
|
if (!ser) { |
|
/* Not odd, component just has no reflection data */ |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
|
|
/* If term is not set, append empty array. This indicates that the term |
|
* could have had data but doesn't */ |
|
if (!ecs_field_is_set(it, i + 1)) { |
|
flecs_json_array_push(buf); |
|
flecs_json_array_pop(buf); |
|
continue; |
|
} |
|
|
|
if (ecs_field_is_self(it, i + 1)) { |
|
int32_t count = it->count; |
|
if (array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser)) { |
|
return -1; |
|
} |
|
} else { |
|
if (array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser)) { |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_serialize_iter_result_columns( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc) |
|
{ |
|
ecs_table_t *table = it->table; |
|
if (!table || !table->column_count) { |
|
return 0; |
|
} |
|
|
|
flecs_json_memberl(buf, "values"); |
|
flecs_json_array_push(buf); |
|
|
|
ecs_type_t *type = &table->type; |
|
int32_t *column_map = table->column_map; |
|
ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
for (int i = 0; i < type->count; i ++) { |
|
int32_t storage_column = -1; |
|
if (column_map) { |
|
storage_column = column_map[i]; |
|
} |
|
|
|
if (!desc->serialize_private) { |
|
ecs_id_t id = type->array[i]; |
|
ecs_entity_t e = id; |
|
if (ECS_IS_PAIR(id)) { |
|
e = ecs_pair_first(world, id); |
|
} |
|
if (ecs_owns_id(world, e, EcsPrivate)) { |
|
continue; |
|
} |
|
} |
|
|
|
ecs_strbuf_list_next(buf); |
|
|
|
if (storage_column == -1) { |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
|
|
ecs_entity_t typeid = table->data.columns[storage_column].ti->component; |
|
if (!typeid) { |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
|
|
const EcsComponent *comp = ecs_get(world, typeid, EcsComponent); |
|
if (!comp) { |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
|
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, typeid, EcsMetaTypeSerialized); |
|
if (!ser) { |
|
ecs_strbuf_appendch(buf, '0'); |
|
continue; |
|
} |
|
|
|
void *ptr = ecs_vec_first(&table->data.columns[storage_column].data); |
|
if (array_to_json_buf_w_type_data(world, ptr, it->count, buf, comp, ser)) { |
|
return -1; |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_serialize_iter_result( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc, |
|
ecs_json_ser_ctx_t *ser_ctx) |
|
{ |
|
if (desc && desc->serialize_rows) { |
|
if (flecs_json_serialize_iter_result_rows(world, it, buf, desc, ser_ctx)) { |
|
return -1; |
|
} |
|
} else { |
|
flecs_json_next(buf); |
|
flecs_json_object_push(buf); |
|
|
|
/* Each result can be matched with different component ids. Add them to |
|
* the result so clients know with which component an entity was matched */ |
|
if (desc && desc->serialize_table) { |
|
flecs_json_serialize_iter_result_table_type(world, it, buf, desc); |
|
} else { |
|
if (!desc || desc->serialize_ids) { |
|
flecs_json_serialize_iter_result_ids(world, it, buf); |
|
} |
|
if (desc && desc->serialize_id_labels) { |
|
flecs_json_serialize_iter_result_id_labels(world, it, buf); |
|
} |
|
} |
|
|
|
/* Include information on which entity the term is matched with */ |
|
if (!desc || (desc->serialize_sources && !desc->serialize_table)) { |
|
flecs_json_serialize_iter_result_sources(world, it, buf); |
|
} |
|
|
|
/* Write variable values for current result */ |
|
if (!desc || desc->serialize_variables) { |
|
flecs_json_serialize_iter_result_variables(world, it, buf); |
|
} |
|
|
|
/* Write labels for variables */ |
|
if (desc && desc->serialize_variable_labels) { |
|
flecs_json_serialize_iter_result_variable_labels(world, it, buf); |
|
} |
|
|
|
/* Write ids for variables */ |
|
if (desc && desc->serialize_variable_ids) { |
|
flecs_json_serialize_iter_result_variable_ids(it, buf); |
|
} |
|
|
|
/* Include information on which terms are set, to support optional terms */ |
|
if (!desc || (desc->serialize_is_set && !desc->serialize_table)) { |
|
flecs_json_serialize_iter_result_is_set(it, buf); |
|
} |
|
|
|
/* Write entity ids for current result (for queries with This terms) */ |
|
if (!desc || desc->serialize_entities) { |
|
flecs_json_serialize_iter_result_entities(world, it, buf); |
|
} |
|
|
|
/* Write ids for entities */ |
|
if (desc && desc->serialize_entity_ids) { |
|
flecs_json_serialize_iter_result_entity_ids(it, buf); |
|
} |
|
|
|
/* Write labels for entities */ |
|
if (desc && desc->serialize_entity_labels) { |
|
flecs_json_serialize_iter_result_entity_labels(it, buf, ser_ctx); |
|
} |
|
|
|
/* Write colors for entities */ |
|
if (desc && desc->serialize_colors) { |
|
flecs_json_serialize_iter_result_colors(it, buf, ser_ctx); |
|
} |
|
|
|
/* Serialize component values */ |
|
if (desc && desc->serialize_table) { |
|
if (flecs_json_serialize_iter_result_columns(world, it, buf, desc)) { |
|
return -1; |
|
} |
|
} else { |
|
if (!desc || desc->serialize_values) { |
|
if (flecs_json_serialize_iter_result_values(world, it, buf)) { |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
/* Add "alerts": true member if table has entities with active alerts */ |
|
#ifdef FLECS_ALERTS |
|
if (it->table && (ecs_id(EcsAlertsActive) != 0)) { |
|
/* Only add field if alerts addon is imported */ |
|
if (ecs_table_has_id(world, it->table, ecs_id(EcsAlertsActive))) { |
|
flecs_json_memberl(buf, "alerts"); |
|
flecs_json_true(buf); |
|
} |
|
} |
|
#endif |
|
|
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int ecs_iter_to_json_buf( |
|
const ecs_world_t *world, |
|
ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc) |
|
{ |
|
ecs_time_t duration = {0}; |
|
if (desc && desc->measure_eval_duration) { |
|
ecs_time_measure(&duration); |
|
} |
|
|
|
/* Cache id record for flecs.doc ids */ |
|
ecs_json_ser_ctx_t ser_ctx; |
|
ecs_os_zeromem(&ser_ctx); |
|
#ifdef FLECS_DOC |
|
ser_ctx.idr_doc_name = flecs_id_record_get(world, |
|
ecs_pair_t(EcsDocDescription, EcsName)); |
|
ser_ctx.idr_doc_color = flecs_id_record_get(world, |
|
ecs_pair_t(EcsDocDescription, EcsDocColor)); |
|
#endif |
|
|
|
flecs_json_object_push(buf); |
|
|
|
/* Serialize component ids of the terms (usually provided by query) */ |
|
if (!desc || !desc->serialize_rows) { |
|
if (!desc || desc->serialize_term_ids) { |
|
flecs_json_serialize_iter_ids(world, it, buf); |
|
} |
|
|
|
if (desc && desc->serialize_term_labels) { |
|
flecs_json_serialize_iter_id_labels(world, it, buf); |
|
} |
|
|
|
/* Serialize variable names, if iterator has any */ |
|
flecs_json_serialize_iter_variables(it, buf); |
|
} |
|
|
|
/* Serialize type info if enabled */ |
|
if (desc && desc->serialize_type_info) { |
|
flecs_json_serialize_type_info(world, it, buf); |
|
} |
|
|
|
/* Serialize field info if enabled */ |
|
if (desc && desc->serialize_field_info) { |
|
flecs_json_serialize_field_info(world, it, buf, &ser_ctx); |
|
} |
|
|
|
/* Serialize results */ |
|
if (!desc || !desc->dont_serialize_results) { |
|
flecs_json_memberl(buf, "results"); |
|
flecs_json_array_push(buf); |
|
|
|
/* Use instancing for improved performance */ |
|
ECS_BIT_SET(it->flags, EcsIterIsInstanced); |
|
|
|
/* If serializing entire table, don't bother letting the iterator populate |
|
* data fields as we'll be iterating all columns. */ |
|
if (desc && desc->serialize_table) { |
|
ECS_BIT_SET(it->flags, EcsIterNoData); |
|
} |
|
|
|
ecs_iter_next_action_t next = it->next; |
|
while (next(it)) { |
|
if (flecs_json_serialize_iter_result(world, it, buf, desc, &ser_ctx)) { |
|
ecs_strbuf_reset(buf); |
|
ecs_iter_fini(it); |
|
return -1; |
|
} |
|
} |
|
|
|
flecs_json_array_pop(buf); |
|
} else { |
|
ecs_iter_fini(it); |
|
} |
|
|
|
int32_t f, field_count = it->field_count; |
|
if (desc && (desc->serialize_rows || desc->serialize_values)) { |
|
for (f = 0; f < field_count; f ++) { |
|
ecs_os_free(ser_ctx.value_ctx[f].id_label); |
|
} |
|
} |
|
|
|
if (desc && desc->measure_eval_duration) { |
|
double dt = ecs_time_measure(&duration); |
|
flecs_json_memberl(buf, "eval_duration"); |
|
flecs_json_number(buf, dt); |
|
} |
|
|
|
flecs_json_object_pop(buf); |
|
|
|
return 0; |
|
} |
|
|
|
char* ecs_iter_to_json( |
|
const ecs_world_t *world, |
|
ecs_iter_t *it, |
|
const ecs_iter_to_json_desc_t *desc) |
|
{ |
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
|
|
if (ecs_iter_to_json_buf(world, it, &buf, desc)) { |
|
ecs_strbuf_reset(&buf); |
|
return NULL; |
|
} |
|
|
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
int ecs_world_to_json_buf( |
|
ecs_world_t *world, |
|
ecs_strbuf_t *buf_out, |
|
const ecs_world_to_json_desc_t *desc) |
|
{ |
|
ecs_filter_t f = ECS_FILTER_INIT; |
|
ecs_filter_desc_t filter_desc = {0}; |
|
filter_desc.storage = &f; |
|
|
|
if (desc && desc->serialize_builtin && desc->serialize_modules) { |
|
filter_desc.terms[0].id = EcsAny; |
|
} else { |
|
bool serialize_builtin = desc && desc->serialize_builtin; |
|
bool serialize_modules = desc && desc->serialize_modules; |
|
int32_t term_id = 0; |
|
|
|
if (!serialize_builtin) { |
|
filter_desc.terms[term_id].id = ecs_pair(EcsChildOf, EcsFlecs); |
|
filter_desc.terms[term_id].oper = EcsNot; |
|
filter_desc.terms[term_id].src.flags = EcsSelf | EcsParent; |
|
term_id ++; |
|
} |
|
if (!serialize_modules) { |
|
filter_desc.terms[term_id].id = EcsModule; |
|
filter_desc.terms[term_id].oper = EcsNot; |
|
filter_desc.terms[term_id].src.flags = EcsSelf | EcsParent; |
|
} |
|
} |
|
|
|
if (ecs_filter_init(world, &filter_desc) == NULL) { |
|
return -1; |
|
} |
|
|
|
ecs_iter_t it = ecs_filter_iter(world, &f); |
|
ecs_iter_to_json_desc_t json_desc = { |
|
.serialize_table = true, |
|
.serialize_ids = true, |
|
.serialize_entities = true, |
|
.serialize_private = true |
|
}; |
|
|
|
int ret = ecs_iter_to_json_buf(world, &it, buf_out, &json_desc); |
|
ecs_filter_fini(&f); |
|
return ret; |
|
} |
|
|
|
char* ecs_world_to_json( |
|
ecs_world_t *world, |
|
const ecs_world_to_json_desc_t *desc) |
|
{ |
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
|
|
if (ecs_world_to_json_buf(world, &buf, desc)) { |
|
ecs_strbuf_reset(&buf); |
|
return NULL; |
|
} |
|
|
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file json/serialize_iter_rows.c |
|
* @brief Serialize (component) values to JSON strings. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_JSON |
|
|
|
static |
|
bool flecs_json_serialize_row_tags( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
int32_t f, field_count = it->field_count; |
|
ecs_id_t *ids = it->ids; |
|
void **ptrs = it->ptrs; |
|
|
|
int32_t tag_count = 0; |
|
for (f = 0; f < field_count; f ++) { |
|
if (!ecs_field_is_set(it, f + 1)) { |
|
continue; |
|
} |
|
|
|
ecs_id_t id = ids[f]; |
|
if (ECS_IS_PAIR(id)) { |
|
continue; |
|
} |
|
|
|
if (ptrs[f]) { |
|
continue; /* Ignore components */ |
|
} |
|
|
|
if (!tag_count) { |
|
flecs_json_memberl(buf, "tags"); |
|
flecs_json_array_push(buf); |
|
} |
|
|
|
flecs_json_next(buf); |
|
flecs_json_label(buf, world, id); |
|
|
|
tag_count ++; |
|
} |
|
|
|
if (tag_count) { |
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
return tag_count != 0; |
|
} |
|
|
|
static |
|
bool flecs_json_serialize_row_pairs( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
int32_t f, field_count = it->field_count; |
|
ecs_id_t *ids = it->ids; |
|
|
|
int32_t pair_count = 0; |
|
for (f = 0; f < field_count; f ++) { |
|
if (!ecs_field_is_set(it, f + 1)) { |
|
continue; |
|
} |
|
|
|
ecs_id_t id = ids[f]; |
|
if (!ECS_IS_PAIR(id)) { |
|
continue; |
|
} |
|
|
|
if (!pair_count) { |
|
flecs_json_memberl(buf, "pairs"); |
|
flecs_json_object_push(buf); |
|
} |
|
|
|
flecs_json_next(buf); |
|
ecs_entity_t first = ecs_pair_first(world, id); |
|
ecs_entity_t second = ecs_pair_second(world, id); |
|
|
|
flecs_json_label(buf, world, first); |
|
ecs_strbuf_appendlit(buf, ":"); |
|
flecs_json_label(buf, world, second); |
|
|
|
pair_count ++; |
|
} |
|
|
|
if (pair_count) { |
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
return pair_count != 0; |
|
} |
|
|
|
static |
|
bool flecs_json_serialize_row_vars( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
char **variable_names = it->variable_names; |
|
int32_t var_count = it->variable_count; |
|
int32_t actual_count = 0; |
|
|
|
for (int i = 1; i < var_count; i ++) { |
|
const char *var_name = variable_names[i]; |
|
if (flecs_json_skip_variable(var_name)) continue; |
|
|
|
ecs_entity_t var = it->variables[i].entity; |
|
if (!var) { |
|
/* Can't happen, but not the place of the serializer to complain */ |
|
continue; |
|
} |
|
|
|
if (!actual_count) { |
|
flecs_json_memberl(buf, "vars"); |
|
flecs_json_object_push(buf); |
|
actual_count ++; |
|
} |
|
|
|
flecs_json_member(buf, var_name); |
|
flecs_json_label(buf, world, var); |
|
} |
|
|
|
if (actual_count) { |
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
return actual_count != 0; |
|
} |
|
|
|
static |
|
bool flecs_json_serialize_tags_pairs_vars( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf) |
|
{ |
|
bool result = false; |
|
ecs_strbuf_list_push(buf, "", ","); |
|
result |= flecs_json_serialize_row_tags(world, it, buf); |
|
result |= flecs_json_serialize_row_pairs(world, it, buf); |
|
result |= flecs_json_serialize_row_vars(world, it, buf); |
|
result |= flecs_json_serialize_iter_result_is_set(it, buf); |
|
ecs_strbuf_list_pop(buf, ""); |
|
if (!result) { |
|
ecs_strbuf_reset(buf); |
|
} |
|
return result; |
|
} |
|
|
|
static |
|
bool flecs_json_serialize_table_row_tags( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc) |
|
{ |
|
int32_t f, type_count = table->type.count; |
|
ecs_id_t *ids = table->type.array; |
|
int32_t *column_map = table->column_map; |
|
|
|
int32_t tag_count = 0; |
|
for (f = 0; f < type_count; f ++) { |
|
ecs_id_t id = ids[f]; |
|
if (ECS_IS_PAIR(id)) { |
|
continue; |
|
} |
|
|
|
if (column_map[f] != -1) { |
|
continue; /* Ignore components */ |
|
} |
|
|
|
if (!desc || !desc->serialize_private) { |
|
if (ecs_has_id(world, id & ECS_COMPONENT_MASK, EcsPrivate)) { |
|
continue; |
|
} |
|
} |
|
|
|
if (!tag_count) { |
|
flecs_json_memberl(buf, "tags"); |
|
flecs_json_array_push(buf); |
|
} |
|
|
|
flecs_json_next(buf); |
|
flecs_json_label(buf, world, id); |
|
|
|
tag_count ++; |
|
} |
|
|
|
if (tag_count) { |
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
return tag_count != 0; |
|
} |
|
|
|
static |
|
bool flecs_json_serialize_table_row_pairs( |
|
const ecs_world_t *world, |
|
const ecs_table_t *table, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc) |
|
{ |
|
int32_t f, type_count = table->type.count; |
|
ecs_id_t *ids = table->type.array; |
|
|
|
int32_t pair_count = 0; |
|
bool same_first = false; |
|
|
|
for (f = 0; f < type_count; f ++) { |
|
ecs_id_t id = ids[f]; |
|
if (!ECS_IS_PAIR(id)) { |
|
continue; |
|
} |
|
|
|
ecs_entity_t first = flecs_entities_get_generation( |
|
world, ECS_PAIR_FIRST(id)); |
|
if (!desc || !desc->serialize_private) { |
|
if (ecs_has_id(world, first, EcsPrivate)) { |
|
continue; |
|
} |
|
} |
|
|
|
if (!pair_count) { |
|
flecs_json_memberl(buf, "pairs"); |
|
flecs_json_object_push(buf); |
|
} |
|
|
|
ecs_entity_t second = flecs_entities_get_generation( |
|
world, ECS_PAIR_SECOND(id)); |
|
|
|
bool is_last = f == (type_count - 1); |
|
bool is_same = !is_last && |
|
(ECS_PAIR_FIRST(ids[f + 1]) == ECS_PAIR_FIRST(id)); |
|
|
|
if (same_first && f && ECS_PAIR_FIRST(ids[f - 1]) != ECS_PAIR_FIRST(id)) { |
|
/* New pair has different first elem, so close array */ |
|
flecs_json_array_pop(buf); |
|
same_first = false; |
|
} |
|
|
|
if (!same_first) { |
|
/* Only append pair label if we're not appending to array */ |
|
flecs_json_next(buf); |
|
flecs_json_label(buf, world, first); |
|
ecs_strbuf_appendlit(buf, ":"); |
|
|
|
/* Open array scope if this is a pair with multiple targets */ |
|
if (is_same) { |
|
flecs_json_array_push(buf); |
|
same_first = true; |
|
} |
|
} |
|
if (same_first) { |
|
flecs_json_next(buf); |
|
} |
|
|
|
flecs_json_label(buf, world, second); |
|
|
|
pair_count ++; |
|
} |
|
|
|
if (same_first) { |
|
flecs_json_array_pop(buf); |
|
} |
|
|
|
if (pair_count) { |
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
return pair_count != 0; |
|
} |
|
|
|
static |
|
bool flecs_json_serialize_table_tags_pairs_vars( |
|
const ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc) |
|
{ |
|
bool result = false; |
|
ecs_strbuf_list_push(buf, "", ","); |
|
result |= flecs_json_serialize_table_row_tags(world, table, buf, desc); |
|
result |= flecs_json_serialize_table_row_pairs(world, table, buf, desc); |
|
ecs_strbuf_list_pop(buf, ""); |
|
if (!result) { |
|
ecs_strbuf_reset(buf); |
|
} |
|
return result; |
|
} |
|
|
|
static |
|
bool flecs_json_serialize_get_value_ctx( |
|
const ecs_world_t *world, |
|
ecs_id_t id, |
|
ecs_json_value_ser_ctx_t *ctx) |
|
{ |
|
if (!ctx->initialized) { |
|
ctx->initialized = true; |
|
|
|
ecs_strbuf_t idlbl = ECS_STRBUF_INIT; |
|
flecs_json_id_member(&idlbl, world, id); |
|
ctx->id_label = ecs_strbuf_get(&idlbl); |
|
|
|
ecs_entity_t type = ecs_get_typeid(world, id); |
|
if (!type) { |
|
return false; |
|
} |
|
|
|
ctx->type = type; |
|
ctx->ser = ecs_get(world, type, EcsMetaTypeSerialized); |
|
if (!ctx->ser) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} else { |
|
return ctx->ser != NULL; |
|
} |
|
} |
|
|
|
bool flecs_json_serialize_get_field_ctx( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
int32_t f, |
|
ecs_json_ser_ctx_t *ser_ctx) |
|
{ |
|
ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; |
|
if (it->query) { |
|
return flecs_json_serialize_get_value_ctx( |
|
world, it->query->ids[f], value_ctx); |
|
} else if (it->ids[f]) { |
|
return flecs_json_serialize_get_value_ctx(world, it->ids[f], value_ctx); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
int flecs_json_serialize_row_components( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
ecs_json_ser_ctx_t *ser_ctx, |
|
int32_t row) |
|
{ |
|
void **ptrs = it->ptrs; |
|
if (!ptrs) { |
|
return 0; |
|
} |
|
|
|
int32_t f, field_count = it->field_count, component_count = 0; |
|
for (f = 0; f < field_count; f ++) { |
|
if (!ecs_field_is_set(it, f + 1)) { |
|
continue; |
|
} |
|
|
|
void *ptr = ptrs[f]; |
|
if (!ptr) { |
|
continue; |
|
} |
|
|
|
if (ecs_field_is_self(it, f + 1)) { |
|
ptr = ECS_ELEM(ptr, it->sizes[f], row); |
|
} |
|
|
|
ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; |
|
if (!flecs_json_serialize_get_value_ctx(world, it->ids[f], value_ctx)) { |
|
continue; |
|
} |
|
|
|
if (!component_count) { |
|
flecs_json_memberl(buf, "components"); |
|
flecs_json_object_push(buf); |
|
} |
|
|
|
flecs_json_member(buf, value_ctx->id_label); |
|
if (json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { |
|
return -1; |
|
} |
|
|
|
component_count ++; |
|
} |
|
|
|
if (component_count) { |
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_serialize_row_table_components( |
|
const ecs_world_t *world, |
|
ecs_table_t *table, |
|
ecs_strbuf_t *buf, |
|
ecs_json_value_ser_ctx_t *values_ctx, |
|
int32_t row, |
|
int32_t column_count) |
|
{ |
|
int32_t f, component_count = 0; |
|
for (f = 0; f < column_count; f ++) { |
|
ecs_column_t *column = &table->data.columns[f]; |
|
ecs_json_value_ser_ctx_t *value_ctx = &values_ctx[f]; |
|
if (!flecs_json_serialize_get_value_ctx(world, column->id, value_ctx)) { |
|
continue; |
|
} |
|
|
|
if (!component_count) { |
|
flecs_json_memberl(buf, "components"); |
|
flecs_json_object_push(buf); |
|
} |
|
|
|
void *ptr = ecs_vec_get(&column->data, column->size, row); |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(value_ctx->id_label != NULL, ECS_INTERNAL_ERROR, NULL); |
|
flecs_json_member(buf, value_ctx->id_label); |
|
if (json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { |
|
return -1; |
|
} |
|
|
|
component_count ++; |
|
} |
|
|
|
if (component_count) { |
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
void flecs_json_serialize_iter_this_row( |
|
const ecs_iter_t *it, |
|
const char *parent_path, |
|
const EcsIdentifier *names, |
|
int32_t row, |
|
ecs_strbuf_t *buf) |
|
{ |
|
if (parent_path) { |
|
flecs_json_memberl(buf, "parent"); |
|
flecs_json_string(buf, parent_path); |
|
} |
|
|
|
flecs_json_memberl(buf, "name"); |
|
if (names) { |
|
flecs_json_string(buf, names[row].value); |
|
} else { |
|
ecs_strbuf_appendlit(buf, "\""); |
|
ecs_strbuf_appendint(buf, flecs_uto(int32_t, it->entities[row])); |
|
ecs_strbuf_appendlit(buf, "\""); |
|
} |
|
} |
|
|
|
static |
|
int flecs_json_serialize_iter_result_query_rows( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
ecs_json_ser_ctx_t *ser_ctx, |
|
int32_t count, |
|
bool has_this, |
|
const char *parent_path, |
|
const EcsIdentifier *names) |
|
{ |
|
/* Serialize tags, pairs, vars once, since they're the same for each row */ |
|
ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; |
|
int32_t tags_pairs_vars_len = 0; |
|
char *tags_pairs_vars = NULL; |
|
if (flecs_json_serialize_tags_pairs_vars(world, it, &tags_pairs_vars_buf)) { |
|
tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); |
|
tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); |
|
} |
|
|
|
int32_t i; |
|
for (i = 0; i < count; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_object_push(buf); |
|
|
|
if (has_this) { |
|
flecs_json_serialize_iter_this_row( |
|
it, parent_path, names, i, buf); |
|
} |
|
|
|
if (tags_pairs_vars) { |
|
ecs_strbuf_list_appendstrn(buf, |
|
tags_pairs_vars, tags_pairs_vars_len); |
|
} |
|
|
|
if (flecs_json_serialize_row_components(world, it, buf, ser_ctx, i)) { |
|
return -1; |
|
} |
|
|
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
ecs_os_free(tags_pairs_vars); |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_json_serialize_iter_result_table_rows( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc, |
|
int32_t count, |
|
bool has_this, |
|
const char *parent_path, |
|
const EcsIdentifier *names) |
|
{ |
|
ecs_table_t *table = it->table; |
|
if (!table || !count) { |
|
return 0; |
|
} |
|
|
|
/* Serialize tags, pairs, vars once, since they're the same for each row */ |
|
ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; |
|
int32_t tags_pairs_vars_len = 0; |
|
char *tags_pairs_vars = NULL; |
|
if (flecs_json_serialize_table_tags_pairs_vars( |
|
world, table, &tags_pairs_vars_buf, desc)) |
|
{ |
|
tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); |
|
tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); |
|
} |
|
|
|
/* If one entity has more than 256 components (oof), bad luck */ |
|
ecs_json_value_ser_ctx_t values_ctx[256] = {{0}}; |
|
int32_t column_count = table->column_count; |
|
if (column_count > 256) { |
|
column_count = 256; |
|
} |
|
|
|
int32_t i, end = it->offset + count; |
|
int result = 0; |
|
for (i = it->offset; i < end; i ++) { |
|
flecs_json_next(buf); |
|
flecs_json_object_push(buf); |
|
|
|
if (has_this) { |
|
flecs_json_serialize_iter_this_row( |
|
it, parent_path, &names[-it->offset], i, buf); |
|
} |
|
|
|
if (tags_pairs_vars) { |
|
ecs_strbuf_list_appendstrn(buf, |
|
tags_pairs_vars, tags_pairs_vars_len); |
|
} |
|
|
|
if (flecs_json_serialize_row_table_components( |
|
world, table, buf, values_ctx, i, column_count)) |
|
{ |
|
result = -1; |
|
break; |
|
} |
|
|
|
flecs_json_object_pop(buf); |
|
} |
|
|
|
for (i = 0; i < column_count; i ++) { |
|
ecs_os_free(values_ctx[i].id_label); |
|
} |
|
|
|
ecs_os_free(tags_pairs_vars); |
|
|
|
return result; |
|
} |
|
|
|
int flecs_json_serialize_iter_result_rows( |
|
const ecs_world_t *world, |
|
const ecs_iter_t *it, |
|
ecs_strbuf_t *buf, |
|
const ecs_iter_to_json_desc_t *desc, |
|
ecs_json_ser_ctx_t *ser_ctx) |
|
{ |
|
char *parent_path = NULL; |
|
EcsIdentifier *names = NULL; |
|
|
|
int32_t count = it->count; |
|
bool has_this = true; |
|
if (!count) { |
|
count = 1; /* Query without this variable */ |
|
has_this = false; |
|
} else { |
|
ecs_table_t *table = it->table; |
|
if (table) { |
|
/* Get path to parent once for entire table */ |
|
if (table->flags & EcsTableHasChildOf) { |
|
const ecs_table_record_t *tr = flecs_table_record_get( |
|
world, table, ecs_pair(EcsChildOf, EcsWildcard)); |
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_entity_t parent = ecs_pair_second( |
|
world, table->type.array[tr->index]); |
|
parent_path = ecs_get_fullpath(world, parent); |
|
} |
|
|
|
/* Fetch name column once vs. calling ecs_get_name for each row */ |
|
if (table->flags & EcsTableHasName) { |
|
names = ecs_table_get_id(it->world, it->table, |
|
ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); |
|
} |
|
} else { |
|
/* Very rare case, but could happen if someone's using an iterator |
|
* to return empty entities. */ |
|
} |
|
} |
|
|
|
if (!desc->serialize_table) { |
|
flecs_json_serialize_iter_result_query_rows( |
|
world, it, buf, ser_ctx, count, has_this, parent_path, names); |
|
} else { |
|
flecs_json_serialize_iter_result_table_rows( |
|
world, it, buf, desc, count, has_this, parent_path, names); |
|
} |
|
|
|
ecs_os_free(parent_path); |
|
|
|
return 0; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file json/serialize_type_info.c |
|
* @brief Serialize type (reflection) information to JSON. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_JSON |
|
|
|
static |
|
int json_typeinfo_ser_type( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_strbuf_t *buf); |
|
|
|
static |
|
int json_typeinfo_ser_primitive( |
|
ecs_primitive_kind_t kind, |
|
ecs_strbuf_t *str) |
|
{ |
|
switch(kind) { |
|
case EcsBool: |
|
flecs_json_string(str, "bool"); |
|
break; |
|
case EcsChar: |
|
case EcsString: |
|
flecs_json_string(str, "text"); |
|
break; |
|
case EcsByte: |
|
flecs_json_string(str, "byte"); |
|
break; |
|
case EcsU8: |
|
case EcsU16: |
|
case EcsU32: |
|
case EcsU64: |
|
case EcsI8: |
|
case EcsI16: |
|
case EcsI32: |
|
case EcsI64: |
|
case EcsIPtr: |
|
case EcsUPtr: |
|
flecs_json_string(str, "int"); |
|
break; |
|
case EcsF32: |
|
case EcsF64: |
|
flecs_json_string(str, "float"); |
|
break; |
|
case EcsEntity: |
|
flecs_json_string(str, "entity"); |
|
break; |
|
case EcsId: |
|
flecs_json_string(str, "id"); |
|
break; |
|
default: |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
void json_typeinfo_ser_constants( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_strbuf_t *str) |
|
{ |
|
ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { |
|
.id = ecs_pair(EcsChildOf, type) |
|
}); |
|
|
|
while (ecs_term_next(&it)) { |
|
int32_t i, count = it.count; |
|
for (i = 0; i < count; i ++) { |
|
flecs_json_next(str); |
|
flecs_json_string(str, ecs_get_name(world, it.entities[i])); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void json_typeinfo_ser_enum( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_strbuf_t *str) |
|
{ |
|
ecs_strbuf_list_appendstr(str, "\"enum\""); |
|
json_typeinfo_ser_constants(world, type, str); |
|
} |
|
|
|
static |
|
void json_typeinfo_ser_bitmask( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_strbuf_t *str) |
|
{ |
|
ecs_strbuf_list_appendstr(str, "\"bitmask\""); |
|
json_typeinfo_ser_constants(world, type, str); |
|
} |
|
|
|
static |
|
int json_typeinfo_ser_array( |
|
const ecs_world_t *world, |
|
ecs_entity_t elem_type, |
|
int32_t count, |
|
ecs_strbuf_t *str) |
|
{ |
|
ecs_strbuf_list_appendstr(str, "\"array\""); |
|
|
|
flecs_json_next(str); |
|
if (json_typeinfo_ser_type(world, elem_type, str)) { |
|
goto error; |
|
} |
|
|
|
ecs_strbuf_list_append(str, "%u", count); |
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int json_typeinfo_ser_array_type( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsArray *arr = ecs_get(world, type, EcsArray); |
|
ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) { |
|
goto error; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int json_typeinfo_ser_vector( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_strbuf_t *str) |
|
{ |
|
const EcsVector *arr = ecs_get(world, type, EcsVector); |
|
ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_strbuf_list_appendstr(str, "\"vector\""); |
|
|
|
flecs_json_next(str); |
|
if (json_typeinfo_ser_type(world, arr->type, str)) { |
|
goto error; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Serialize unit information */ |
|
static |
|
int json_typeinfo_ser_unit( |
|
const ecs_world_t *world, |
|
ecs_strbuf_t *str, |
|
ecs_entity_t unit) |
|
{ |
|
flecs_json_memberl(str, "unit"); |
|
flecs_json_path(str, world, unit); |
|
|
|
const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); |
|
if (uptr) { |
|
if (uptr->symbol) { |
|
flecs_json_memberl(str, "symbol"); |
|
flecs_json_string(str, uptr->symbol); |
|
} |
|
ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); |
|
if (quantity) { |
|
flecs_json_memberl(str, "quantity"); |
|
flecs_json_path(str, world, quantity); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
void json_typeinfo_ser_range( |
|
ecs_strbuf_t *str, |
|
const char *kind, |
|
ecs_member_value_range_t *range) |
|
{ |
|
flecs_json_member(str, kind); |
|
flecs_json_array_push(str); |
|
flecs_json_next(str); |
|
flecs_json_number(str, range->min); |
|
flecs_json_next(str); |
|
flecs_json_number(str, range->max); |
|
flecs_json_array_pop(str); |
|
} |
|
|
|
/* Forward serialization to the different type kinds */ |
|
static |
|
int json_typeinfo_ser_type_op( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *op, |
|
ecs_strbuf_t *str, |
|
const EcsStruct *st) |
|
{ |
|
if (op->kind == EcsOpOpaque) { |
|
const EcsOpaque *ct = ecs_get(world, op->type, |
|
EcsOpaque); |
|
ecs_assert(ct != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return json_typeinfo_ser_type(world, ct->as_type, str); |
|
} |
|
|
|
flecs_json_array_push(str); |
|
|
|
switch(op->kind) { |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
/* Should not be parsed as single op */ |
|
ecs_throw(ECS_INVALID_PARAMETER, NULL); |
|
break; |
|
case EcsOpEnum: |
|
json_typeinfo_ser_enum(world, op->type, str); |
|
break; |
|
case EcsOpBitmask: |
|
json_typeinfo_ser_bitmask(world, op->type, str); |
|
break; |
|
case EcsOpArray: |
|
json_typeinfo_ser_array_type(world, op->type, str); |
|
break; |
|
case EcsOpVector: |
|
json_typeinfo_ser_vector(world, op->type, str); |
|
break; |
|
case EcsOpOpaque: |
|
/* Can't happen, already handled above */ |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
break; |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
case EcsOpString: |
|
if (json_typeinfo_ser_primitive( |
|
flecs_json_op_to_primitive_kind(op->kind), str)) |
|
{ |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
break; |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (st) { |
|
ecs_member_t *m = ecs_vec_get_t( |
|
&st->members, ecs_member_t, op->member_index); |
|
ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
bool value_range = ECS_NEQ(m->range.min, m->range.max); |
|
bool error_range = ECS_NEQ(m->error_range.min, m->error_range.max); |
|
bool warning_range = ECS_NEQ(m->warning_range.min, m->warning_range.max); |
|
|
|
ecs_entity_t unit = m->unit; |
|
if (unit || error_range || warning_range || value_range) { |
|
flecs_json_next(str); |
|
flecs_json_next(str); |
|
flecs_json_object_push(str); |
|
|
|
if (unit) { |
|
json_typeinfo_ser_unit(world, str, unit); |
|
} |
|
if (value_range) { |
|
json_typeinfo_ser_range(str, "range", &m->range); |
|
} |
|
if (error_range) { |
|
json_typeinfo_ser_range(str, "error_range", &m->error_range); |
|
} |
|
if (warning_range) { |
|
json_typeinfo_ser_range(str, "warning_range", &m->warning_range); |
|
} |
|
|
|
flecs_json_object_pop(str); |
|
} |
|
} |
|
|
|
flecs_json_array_pop(str); |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
/* Iterate over a slice of the type ops array */ |
|
static |
|
int json_typeinfo_ser_type_ops( |
|
const ecs_world_t *world, |
|
ecs_meta_type_op_t *ops, |
|
int32_t op_count, |
|
ecs_strbuf_t *str, |
|
const EcsStruct *st) |
|
{ |
|
const EcsStruct *stack[64] = {st}; |
|
int32_t sp = 1; |
|
|
|
for (int i = 0; i < op_count; i ++) { |
|
ecs_meta_type_op_t *op = &ops[i]; |
|
|
|
if (op != ops) { |
|
if (op->name) { |
|
flecs_json_member(str, op->name); |
|
} |
|
} |
|
|
|
int32_t elem_count = op->count; |
|
if (elem_count > 1) { |
|
flecs_json_array_push(str); |
|
json_typeinfo_ser_array(world, op->type, op->count, str); |
|
flecs_json_array_pop(str); |
|
i += op->op_count - 1; |
|
continue; |
|
} |
|
|
|
switch(op->kind) { |
|
case EcsOpPush: |
|
flecs_json_object_push(str); |
|
ecs_assert(sp < 63, ECS_INVALID_OPERATION, "type nesting too deep"); |
|
stack[sp ++] = ecs_get(world, op->type, EcsStruct); |
|
break; |
|
case EcsOpPop: |
|
flecs_json_object_pop(str); |
|
sp --; |
|
break; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
case EcsOpString: |
|
case EcsOpOpaque: |
|
if (json_typeinfo_ser_type_op(world, op, str, stack[sp - 1])) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpPrimitive: |
|
case EcsOpScope: |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int json_typeinfo_ser_type( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_strbuf_t *buf) |
|
{ |
|
const EcsComponent *comp = ecs_get(world, type, EcsComponent); |
|
if (!comp) { |
|
ecs_strbuf_appendch(buf, '0'); |
|
return 0; |
|
} |
|
|
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, type, EcsMetaTypeSerialized); |
|
if (!ser) { |
|
ecs_strbuf_appendch(buf, '0'); |
|
return 0; |
|
} |
|
|
|
const EcsStruct *st = ecs_get(world, type, EcsStruct); |
|
ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); |
|
int32_t count = ecs_vec_count(&ser->ops); |
|
|
|
return json_typeinfo_ser_type_ops(world, ops, count, buf, st); |
|
} |
|
|
|
int ecs_type_info_to_json_buf( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_strbuf_t *buf) |
|
{ |
|
return json_typeinfo_ser_type(world, type, buf); |
|
} |
|
|
|
char* ecs_type_info_to_json( |
|
const ecs_world_t *world, |
|
ecs_entity_t type) |
|
{ |
|
ecs_strbuf_t str = ECS_STRBUF_INIT; |
|
|
|
if (ecs_type_info_to_json_buf(world, type, &str) != 0) { |
|
ecs_strbuf_reset(&str); |
|
return NULL; |
|
} |
|
|
|
return ecs_strbuf_get(&str); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file meta/api.c |
|
* @brief API for creating entities with reflection data. |
|
*/ |
|
|
|
/** |
|
* @file meta/meta.h |
|
* @brief Private functions for meta addon. |
|
*/ |
|
|
|
#ifndef FLECS_META_PRIVATE_H |
|
#define FLECS_META_PRIVATE_H |
|
|
|
|
|
#ifdef FLECS_META |
|
|
|
void ecs_meta_type_serialized_init( |
|
ecs_iter_t *it); |
|
|
|
void ecs_meta_dtor_serialized( |
|
EcsMetaTypeSerialized *ptr); |
|
|
|
ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind( |
|
ecs_primitive_kind_t kind); |
|
|
|
bool flecs_unit_validate( |
|
ecs_world_t *world, |
|
ecs_entity_t t, |
|
EcsUnit *data); |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
#ifdef FLECS_META |
|
|
|
static |
|
bool flecs_type_is_number( |
|
ecs_world_t *world, |
|
ecs_entity_t type) |
|
{ |
|
const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); |
|
if (!p) { |
|
return false; |
|
} |
|
|
|
switch(p->kind) { |
|
case EcsChar: |
|
case EcsU8: |
|
case EcsU16: |
|
case EcsU32: |
|
case EcsU64: |
|
case EcsI8: |
|
case EcsI16: |
|
case EcsI32: |
|
case EcsI64: |
|
case EcsF32: |
|
case EcsF64: |
|
return true; |
|
|
|
case EcsBool: |
|
case EcsByte: |
|
case EcsUPtr: |
|
case EcsIPtr: |
|
case EcsString: |
|
case EcsEntity: |
|
case EcsId: |
|
return false; |
|
default: |
|
ecs_abort(ECS_INVALID_PARAMETER, NULL); |
|
} |
|
} |
|
|
|
ecs_entity_t ecs_primitive_init( |
|
ecs_world_t *world, |
|
const ecs_primitive_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_set(world, t, EcsPrimitive, { desc->kind }); |
|
|
|
flecs_resume_readonly(world, &rs); |
|
return t; |
|
} |
|
|
|
ecs_entity_t ecs_enum_init( |
|
ecs_world_t *world, |
|
const ecs_enum_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_add(world, t, EcsEnum); |
|
|
|
ecs_entity_t old_scope = ecs_set_scope(world, t); |
|
|
|
int i; |
|
for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { |
|
const ecs_enum_constant_t *m_desc = &desc->constants[i]; |
|
if (!m_desc->name) { |
|
break; |
|
} |
|
|
|
ecs_entity_t c = ecs_entity(world, { |
|
.name = m_desc->name |
|
}); |
|
|
|
if (!m_desc->value) { |
|
ecs_add_id(world, c, EcsConstant); |
|
} else { |
|
ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, |
|
{m_desc->value}); |
|
} |
|
} |
|
|
|
ecs_set_scope(world, old_scope); |
|
flecs_resume_readonly(world, &rs); |
|
|
|
if (i == 0) { |
|
ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); |
|
ecs_delete(world, t); |
|
return 0; |
|
} |
|
|
|
return t; |
|
} |
|
|
|
ecs_entity_t ecs_bitmask_init( |
|
ecs_world_t *world, |
|
const ecs_bitmask_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_add(world, t, EcsBitmask); |
|
|
|
ecs_entity_t old_scope = ecs_set_scope(world, t); |
|
|
|
int i; |
|
for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { |
|
const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; |
|
if (!m_desc->name) { |
|
break; |
|
} |
|
|
|
ecs_entity_t c = ecs_entity(world, { |
|
.name = m_desc->name |
|
}); |
|
|
|
if (!m_desc->value) { |
|
ecs_add_id(world, c, EcsConstant); |
|
} else { |
|
ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, |
|
{m_desc->value}); |
|
} |
|
} |
|
|
|
ecs_set_scope(world, old_scope); |
|
flecs_resume_readonly(world, &rs); |
|
|
|
if (i == 0) { |
|
ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); |
|
ecs_delete(world, t); |
|
return 0; |
|
} |
|
|
|
return t; |
|
} |
|
|
|
ecs_entity_t ecs_array_init( |
|
ecs_world_t *world, |
|
const ecs_array_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_set(world, t, EcsArray, { |
|
.type = desc->type, |
|
.count = desc->count |
|
}); |
|
|
|
flecs_resume_readonly(world, &rs); |
|
|
|
return t; |
|
} |
|
|
|
ecs_entity_t ecs_vector_init( |
|
ecs_world_t *world, |
|
const ecs_vector_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_set(world, t, EcsVector, { |
|
.type = desc->type |
|
}); |
|
|
|
flecs_resume_readonly(world, &rs); |
|
|
|
return t; |
|
} |
|
|
|
static |
|
bool flecs_member_range_overlaps( |
|
const ecs_member_value_range_t *range, |
|
const ecs_member_value_range_t *with) |
|
{ |
|
if (ECS_EQ(with->min, with->max)) { |
|
return false; |
|
} |
|
|
|
if (ECS_EQ(range->min, range->max)) { |
|
return false; |
|
} |
|
|
|
if (range->min < with->min || |
|
range->max > with->max) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
ecs_entity_t ecs_struct_init( |
|
ecs_world_t *world, |
|
const ecs_struct_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_entity_t old_scope = ecs_set_scope(world, t); |
|
|
|
int i; |
|
for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { |
|
const ecs_member_t *m_desc = &desc->members[i]; |
|
if (!m_desc->type) { |
|
break; |
|
} |
|
|
|
if (!m_desc->name) { |
|
ecs_err("member %d of struct '%s' does not have a name", i, |
|
ecs_get_name(world, t)); |
|
goto error; |
|
} |
|
|
|
ecs_entity_t m = ecs_entity(world, { |
|
.name = m_desc->name |
|
}); |
|
|
|
ecs_set(world, m, EcsMember, { |
|
.type = m_desc->type, |
|
.count = m_desc->count, |
|
.offset = m_desc->offset, |
|
.unit = m_desc->unit |
|
}); |
|
|
|
EcsMemberRanges *ranges = NULL; |
|
const ecs_member_value_range_t *range = &m_desc->range; |
|
const ecs_member_value_range_t *error = &m_desc->error_range; |
|
const ecs_member_value_range_t *warning = &m_desc->warning_range; |
|
if (ECS_NEQ(range->min, range->max)) { |
|
ranges = ecs_get_mut(world, m, EcsMemberRanges); |
|
if (range->min > range->max) { |
|
char *member_name = ecs_get_fullpath(world, m); |
|
ecs_err("member '%s' has an invalid value range [%f..%f]", |
|
member_name, range->min, range->max); |
|
ecs_os_free(member_name); |
|
goto error; |
|
} |
|
ranges->value.min = range->min; |
|
ranges->value.max = range->max; |
|
} |
|
if (ECS_NEQ(error->min, error->max)) { |
|
if (error->min > error->max) { |
|
char *member_name = ecs_get_fullpath(world, m); |
|
ecs_err("member '%s' has an invalid error range [%f..%f]", |
|
member_name, error->min, error->max); |
|
ecs_os_free(member_name); |
|
goto error; |
|
} |
|
if (flecs_member_range_overlaps(error, range)) { |
|
char *member_name = ecs_get_fullpath(world, m); |
|
ecs_err("error range of member '%s' overlaps with value range", |
|
member_name); |
|
ecs_os_free(member_name); |
|
goto error; |
|
} |
|
if (!ranges) { |
|
ranges = ecs_get_mut(world, m, EcsMemberRanges); |
|
} |
|
ranges->error.min = error->min; |
|
ranges->error.max = error->max; |
|
} |
|
|
|
if (ECS_NEQ(warning->min, warning->max)) { |
|
if (warning->min > warning->max) { |
|
char *member_name = ecs_get_fullpath(world, m); |
|
ecs_err("member '%s' has an invalid warning range [%f..%f]", |
|
member_name, warning->min, warning->max); |
|
ecs_os_free(member_name); |
|
goto error; |
|
} |
|
if (flecs_member_range_overlaps(warning, range)) { |
|
char *member_name = ecs_get_fullpath(world, m); |
|
ecs_err("warning range of member '%s' overlaps with value " |
|
"range", member_name); |
|
ecs_os_free(member_name); |
|
goto error; |
|
} |
|
if (flecs_member_range_overlaps(warning, error)) { |
|
char *member_name = ecs_get_fullpath(world, m); |
|
ecs_err("warning range of member '%s' overlaps with error " |
|
"range", member_name); |
|
ecs_os_free(member_name); |
|
goto error; |
|
} |
|
|
|
if (!ranges) { |
|
ranges = ecs_get_mut(world, m, EcsMemberRanges); |
|
} |
|
ranges->warning.min = warning->min; |
|
ranges->warning.max = warning->max; |
|
} |
|
|
|
if (ranges && !flecs_type_is_number(world, m_desc->type)) { |
|
char *member_name = ecs_get_fullpath(world, m); |
|
ecs_err("member '%s' has an value/error/warning range, but is not a " |
|
"number", member_name); |
|
ecs_os_free(member_name); |
|
goto error; |
|
} |
|
|
|
if (ranges) { |
|
ecs_modified(world, m, EcsMemberRanges); |
|
} |
|
} |
|
|
|
ecs_set_scope(world, old_scope); |
|
flecs_resume_readonly(world, &rs); |
|
|
|
if (i == 0) { |
|
ecs_err("struct '%s' has no members", ecs_get_name(world, t)); |
|
goto error; |
|
} |
|
|
|
if (!ecs_has(world, t, EcsStruct)) { |
|
goto error; |
|
} |
|
|
|
return t; |
|
error: |
|
flecs_resume_readonly(world, &rs); |
|
if (t) { |
|
ecs_delete(world, t); |
|
} |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_opaque_init( |
|
ecs_world_t *world, |
|
const ecs_opaque_desc_t *desc) |
|
{ |
|
ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_set_ptr(world, t, EcsOpaque, &desc->type); |
|
|
|
flecs_resume_readonly(world, &rs); |
|
|
|
return t; |
|
} |
|
|
|
ecs_entity_t ecs_unit_init( |
|
ecs_world_t *world, |
|
const ecs_unit_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_entity_t quantity = desc->quantity; |
|
if (quantity) { |
|
if (!ecs_has_id(world, quantity, EcsQuantity)) { |
|
ecs_err("entity '%s' for unit '%s' is not a quantity", |
|
ecs_get_name(world, quantity), ecs_get_name(world, t)); |
|
goto error; |
|
} |
|
|
|
ecs_add_pair(world, t, EcsQuantity, desc->quantity); |
|
} else { |
|
ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); |
|
} |
|
|
|
EcsUnit *value = ecs_get_mut(world, t, EcsUnit); |
|
value->base = desc->base; |
|
value->over = desc->over; |
|
value->translation = desc->translation; |
|
value->prefix = desc->prefix; |
|
ecs_os_strset(&value->symbol, desc->symbol); |
|
|
|
if (!flecs_unit_validate(world, t, value)) { |
|
goto error; |
|
} |
|
|
|
ecs_modified(world, t, EcsUnit); |
|
|
|
flecs_resume_readonly(world, &rs); |
|
return t; |
|
error: |
|
if (t) { |
|
ecs_delete(world, t); |
|
} |
|
flecs_resume_readonly(world, &rs); |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_unit_prefix_init( |
|
ecs_world_t *world, |
|
const ecs_unit_prefix_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = desc->entity; |
|
if (!t) { |
|
t = ecs_new_low_id(world); |
|
} |
|
|
|
ecs_set(world, t, EcsUnitPrefix, { |
|
.symbol = ECS_CONST_CAST(char*, desc->symbol), |
|
.translation = desc->translation |
|
}); |
|
|
|
flecs_resume_readonly(world, &rs); |
|
|
|
return t; |
|
} |
|
|
|
ecs_entity_t ecs_quantity_init( |
|
ecs_world_t *world, |
|
const ecs_entity_desc_t *desc) |
|
{ |
|
ecs_suspend_readonly_state_t rs; |
|
world = flecs_suspend_readonly(world, &rs); |
|
|
|
ecs_entity_t t = ecs_entity_init(world, desc); |
|
if (!t) { |
|
return 0; |
|
} |
|
|
|
ecs_add_id(world, t, EcsQuantity); |
|
|
|
flecs_resume_readonly(world, &rs); |
|
|
|
return t; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file meta/cursor.c |
|
* @brief API for assigning values of runtime types with reflection. |
|
*/ |
|
|
|
#include <ctype.h> |
|
|
|
#ifdef FLECS_META |
|
#ifdef FLECS_PARSER |
|
#endif |
|
|
|
static |
|
const char* flecs_meta_op_kind_str( |
|
ecs_meta_type_op_kind_t kind) |
|
{ |
|
switch(kind) { |
|
|
|
case EcsOpEnum: return "Enum"; |
|
case EcsOpBitmask: return "Bitmask"; |
|
case EcsOpArray: return "Array"; |
|
case EcsOpVector: return "Vector"; |
|
case EcsOpOpaque: return "Opaque"; |
|
case EcsOpPush: return "Push"; |
|
case EcsOpPop: return "Pop"; |
|
case EcsOpPrimitive: return "Primitive"; |
|
case EcsOpBool: return "Bool"; |
|
case EcsOpChar: return "Char"; |
|
case EcsOpByte: return "Byte"; |
|
case EcsOpU8: return "U8"; |
|
case EcsOpU16: return "U16"; |
|
case EcsOpU32: return "U32"; |
|
case EcsOpU64: return "U64"; |
|
case EcsOpI8: return "I8"; |
|
case EcsOpI16: return "I16"; |
|
case EcsOpI32: return "I32"; |
|
case EcsOpI64: return "I64"; |
|
case EcsOpF32: return "F32"; |
|
case EcsOpF64: return "F64"; |
|
case EcsOpUPtr: return "UPtr"; |
|
case EcsOpIPtr: return "IPtr"; |
|
case EcsOpString: return "String"; |
|
case EcsOpEntity: return "Entity"; |
|
case EcsOpId: return "Id"; |
|
case EcsOpScope: return "Scope"; |
|
default: return "<< invalid kind >>"; |
|
} |
|
} |
|
|
|
/* Get current scope */ |
|
static |
|
ecs_meta_scope_t* flecs_meta_cursor_get_scope( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); |
|
return ECS_CONST_CAST(ecs_meta_scope_t*, &cursor->scope[cursor->depth]); |
|
error: |
|
return NULL; |
|
} |
|
|
|
/* Restore scope, if dotmember was used */ |
|
static |
|
ecs_meta_scope_t* flecs_meta_cursor_restore_scope( |
|
ecs_meta_cursor_t *cursor, |
|
const ecs_meta_scope_t* scope) |
|
{ |
|
ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(scope != NULL, ECS_INVALID_PARAMETER, NULL); |
|
if (scope->prev_depth) { |
|
cursor->depth = scope->prev_depth; |
|
} |
|
error: |
|
return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; |
|
} |
|
|
|
/* Get current operation for scope */ |
|
static |
|
ecs_meta_type_op_t* flecs_meta_cursor_get_op( |
|
ecs_meta_scope_t *scope) |
|
{ |
|
ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, NULL); |
|
return &scope->ops[scope->op_cur]; |
|
} |
|
|
|
/* Get component for type in current scope */ |
|
static |
|
const EcsComponent* get_ecs_component( |
|
const ecs_world_t *world, |
|
ecs_meta_scope_t *scope) |
|
{ |
|
const EcsComponent *comp = scope->comp; |
|
if (!comp) { |
|
comp = scope->comp = ecs_get(world, scope->type, EcsComponent); |
|
ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
return comp; |
|
} |
|
|
|
/* Get size for type in current scope */ |
|
static |
|
ecs_size_t get_size( |
|
const ecs_world_t *world, |
|
ecs_meta_scope_t *scope) |
|
{ |
|
return get_ecs_component(world, scope)->size; |
|
} |
|
|
|
static |
|
int32_t get_elem_count( |
|
ecs_meta_scope_t *scope) |
|
{ |
|
const EcsOpaque *opaque = scope->opaque; |
|
|
|
if (scope->vector) { |
|
return ecs_vec_count(scope->vector); |
|
} else if (opaque && opaque->count) { |
|
return flecs_uto(int32_t, opaque->count(scope[-1].ptr)); |
|
} |
|
|
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
return op->count; |
|
} |
|
|
|
/* Get pointer to current field/element */ |
|
static |
|
ecs_meta_type_op_t* flecs_meta_cursor_get_ptr( |
|
const ecs_world_t *world, |
|
ecs_meta_scope_t *scope) |
|
{ |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
ecs_size_t size = get_size(world, scope); |
|
const EcsOpaque *opaque = scope->opaque; |
|
|
|
if (scope->vector) { |
|
ecs_vec_set_min_count(NULL, scope->vector, size, scope->elem_cur + 1); |
|
scope->ptr = ecs_vec_first(scope->vector); |
|
} else if (opaque) { |
|
if (scope->is_collection) { |
|
if (!opaque->ensure_element) { |
|
char *str = ecs_get_fullpath(world, scope->type); |
|
ecs_err("missing ensure_element for opaque type %s", str); |
|
ecs_os_free(str); |
|
return NULL; |
|
} |
|
scope->is_empty_scope = false; |
|
|
|
void *opaque_ptr = opaque->ensure_element( |
|
scope->ptr, flecs_ito(size_t, scope->elem_cur)); |
|
ecs_assert(opaque_ptr != NULL, ECS_INVALID_OPERATION, |
|
"ensure_element returned NULL"); |
|
return opaque_ptr; |
|
} else if (op->name) { |
|
if (!opaque->ensure_member) { |
|
char *str = ecs_get_fullpath(world, scope->type); |
|
ecs_err("missing ensure_member for opaque type %s", str); |
|
ecs_os_free(str); |
|
return NULL; |
|
} |
|
ecs_assert(scope->ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return opaque->ensure_member(scope->ptr, op->name); |
|
} else { |
|
ecs_err("invalid operation for opaque type"); |
|
return NULL; |
|
} |
|
} |
|
|
|
return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); |
|
} |
|
|
|
static |
|
int flecs_meta_cursor_push_type( |
|
const ecs_world_t *world, |
|
ecs_meta_scope_t *scope, |
|
ecs_entity_t type, |
|
void *ptr) |
|
{ |
|
const EcsMetaTypeSerialized *ser = ecs_get( |
|
world, type, EcsMetaTypeSerialized); |
|
if (ser == NULL) { |
|
char *str = ecs_id_str(world, type); |
|
ecs_err("cannot open scope for entity '%s' which is not a type", str); |
|
ecs_os_free(str); |
|
return -1; |
|
} |
|
|
|
scope[0] = (ecs_meta_scope_t) { |
|
.type = type, |
|
.ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t), |
|
.op_count = ecs_vec_count(&ser->ops), |
|
.ptr = ptr |
|
}; |
|
|
|
return 0; |
|
} |
|
|
|
ecs_meta_cursor_t ecs_meta_cursor( |
|
const ecs_world_t *world, |
|
ecs_entity_t type, |
|
void *ptr) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_meta_cursor_t result = { |
|
.world = world, |
|
.valid = true |
|
}; |
|
|
|
if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) != 0) { |
|
result.valid = false; |
|
} |
|
|
|
return result; |
|
error: |
|
return (ecs_meta_cursor_t){ 0 }; |
|
} |
|
|
|
void* ecs_meta_get_ptr( |
|
ecs_meta_cursor_t *cursor) |
|
{ |
|
return flecs_meta_cursor_get_ptr(cursor->world, |
|
flecs_meta_cursor_get_scope(cursor)); |
|
} |
|
|
|
int ecs_meta_next( |
|
ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
scope = flecs_meta_cursor_restore_scope(cursor, scope); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
|
|
if (scope->is_collection) { |
|
scope->elem_cur ++; |
|
scope->op_cur = 0; |
|
|
|
if (scope->opaque) { |
|
return 0; |
|
} |
|
|
|
if (scope->elem_cur >= get_elem_count(scope)) { |
|
ecs_err("out of collection bounds (%d)", scope->elem_cur); |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
scope->op_cur += op->op_count; |
|
|
|
if (scope->op_cur >= scope->op_count) { |
|
ecs_err("out of bounds"); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int ecs_meta_elem( |
|
ecs_meta_cursor_t *cursor, |
|
int32_t elem) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
if (!scope->is_collection) { |
|
ecs_err("ecs_meta_elem can be used for collections only"); |
|
return -1; |
|
} |
|
|
|
scope->elem_cur = elem; |
|
scope->op_cur = 0; |
|
|
|
if (scope->elem_cur >= get_elem_count(scope) || (scope->elem_cur < 0)) { |
|
ecs_err("out of collection bounds (%d)", scope->elem_cur); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int ecs_meta_member( |
|
ecs_meta_cursor_t *cursor, |
|
const char *name) |
|
{ |
|
if (cursor->depth == 0) { |
|
ecs_err("cannot move to member in root scope"); |
|
return -1; |
|
} |
|
|
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
scope = flecs_meta_cursor_restore_scope(cursor, scope); |
|
|
|
ecs_hashmap_t *members = scope->members; |
|
const ecs_world_t *world = cursor->world; |
|
|
|
if (!members) { |
|
ecs_err("cannot move to member '%s' for non-struct type", name); |
|
return -1; |
|
} |
|
|
|
const uint64_t *cur_ptr = flecs_name_index_find_ptr(members, name, 0, 0); |
|
if (!cur_ptr) { |
|
char *path = ecs_get_fullpath(world, scope->type); |
|
ecs_err("unknown member '%s' for type '%s'", name, path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); |
|
|
|
const EcsOpaque *opaque = scope->opaque; |
|
if (opaque) { |
|
if (!opaque->ensure_member) { |
|
char *str = ecs_get_fullpath(world, scope->type); |
|
ecs_err("missing ensure_member for opaque type %s", str); |
|
ecs_os_free(str); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int ecs_meta_dotmember( |
|
ecs_meta_cursor_t *cursor, |
|
const char *name) |
|
{ |
|
#ifdef FLECS_PARSER |
|
ecs_meta_scope_t *cur_scope = flecs_meta_cursor_get_scope(cursor); |
|
flecs_meta_cursor_restore_scope(cursor, cur_scope); |
|
|
|
int32_t prev_depth = cursor->depth; |
|
int dotcount = 0; |
|
|
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
const char *ptr = name; |
|
while ((ptr = ecs_parse_token(NULL, NULL, ptr, token, '.'))) { |
|
if (ptr[0] != '.' && ptr[0]) { |
|
ecs_parser_error(NULL, name, ptr - name, |
|
"expected '.' or end of string"); |
|
goto error; |
|
} |
|
|
|
if (dotcount) { |
|
ecs_meta_push(cursor); |
|
} |
|
|
|
if (ecs_meta_member(cursor, token)) { |
|
goto error; |
|
} |
|
|
|
if (!ptr[0]) { |
|
break; |
|
} |
|
|
|
ptr ++; /* Skip . */ |
|
|
|
dotcount ++; |
|
} |
|
|
|
cur_scope = flecs_meta_cursor_get_scope(cursor); |
|
if (dotcount) { |
|
cur_scope->prev_depth = prev_depth; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
#else |
|
(void)cursor; |
|
(void)name; |
|
ecs_err("the FLECS_PARSER addon is required for ecs_meta_dotmember"); |
|
return -1; |
|
#endif |
|
} |
|
|
|
int ecs_meta_push( |
|
ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
const ecs_world_t *world = cursor->world; |
|
|
|
if (cursor->depth == 0) { |
|
if (!cursor->is_primitive_scope) { |
|
if ((op->kind > EcsOpScope) && (op->count <= 1)) { |
|
cursor->is_primitive_scope = true; |
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
void *ptr = flecs_meta_cursor_get_ptr(world, scope); |
|
cursor->depth ++; |
|
ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, |
|
ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); |
|
|
|
/* If we're not already in an inline array and this operation is an inline |
|
* array, push a frame for the array. |
|
* Doing this first ensures that inline arrays take precedence over other |
|
* kinds of push operations, such as for a struct element type. */ |
|
if (!scope->is_inline_array && op->count > 1 && !scope->is_collection) { |
|
/* Push a frame just for the element type, with inline_array = true */ |
|
next_scope[0] = (ecs_meta_scope_t){ |
|
.ops = op, |
|
.op_count = op->op_count, |
|
.ptr = scope->ptr, |
|
.type = op->type, |
|
.is_collection = true, |
|
.is_inline_array = true |
|
}; |
|
|
|
/* With 'is_inline_array' set to true we ensure that we can never push |
|
* the same inline array twice */ |
|
return 0; |
|
} |
|
|
|
/* Operation-specific switch behavior */ |
|
switch(op->kind) { |
|
|
|
/* Struct push: this happens when pushing a struct member. */ |
|
case EcsOpPush: { |
|
const EcsOpaque *opaque = scope->opaque; |
|
if (opaque) { |
|
/* If this is a nested push for an opaque type, push the type of the |
|
* element instead of the next operation. This ensures that we won't |
|
* use flattened offsets for nested members. */ |
|
if (flecs_meta_cursor_push_type( |
|
world, next_scope, op->type, ptr) != 0) |
|
{ |
|
goto error; |
|
} |
|
|
|
/* Strip the Push operation since we already pushed */ |
|
next_scope->members = next_scope->ops[0].members; |
|
next_scope->ops = &next_scope->ops[1]; |
|
next_scope->op_count --; |
|
break; |
|
} |
|
|
|
/* The ops array contains a flattened list for all members and nested |
|
* members of a struct, so we can use (ops + 1) to initialize the ops |
|
* array of the next scope. */ |
|
next_scope[0] = (ecs_meta_scope_t) { |
|
.ops = &op[1], /* op after push */ |
|
.op_count = op->op_count - 1, /* don't include pop */ |
|
.ptr = scope->ptr, |
|
.type = op->type, |
|
.members = op->members |
|
}; |
|
break; |
|
} |
|
|
|
/* Array push for an array type. Arrays can be encoded in 2 ways: either by |
|
* setting the EcsMember::count member to a value >1, or by specifying an |
|
* array type as member type. This is the latter case. */ |
|
case EcsOpArray: { |
|
if (flecs_meta_cursor_push_type(world, next_scope, op->type, ptr) != 0) { |
|
goto error; |
|
} |
|
|
|
const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); |
|
next_scope->type = type_ptr->type; |
|
next_scope->is_collection = true; |
|
break; |
|
} |
|
|
|
/* Vector push */ |
|
case EcsOpVector: { |
|
next_scope->vector = ptr; |
|
if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { |
|
goto error; |
|
} |
|
|
|
const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); |
|
next_scope->type = type_ptr->type; |
|
next_scope->is_collection = true; |
|
break; |
|
} |
|
|
|
/* Opaque type push. Depending on the type the opaque type represents the |
|
* scope will be pushed as a struct or collection type. The type information |
|
* of the as_type is retained, as this is important for type checking and |
|
* for nested opaque type support. */ |
|
case EcsOpOpaque: { |
|
const EcsOpaque *type_ptr = ecs_get(world, op->type, EcsOpaque); |
|
ecs_entity_t as_type = type_ptr->as_type; |
|
const EcsMetaType *mtype_ptr = ecs_get(world, as_type, EcsMetaType); |
|
|
|
/* Check what kind of type the opaque type represents */ |
|
switch(mtype_ptr->kind) { |
|
|
|
/* Opaque vector support */ |
|
case EcsVectorType: { |
|
const EcsVector *vt = ecs_get(world, type_ptr->as_type, EcsVector); |
|
next_scope->type = vt->type; |
|
|
|
/* Push the element type of the vector type */ |
|
if (flecs_meta_cursor_push_type( |
|
world, next_scope, vt->type, NULL) != 0) |
|
{ |
|
goto error; |
|
} |
|
|
|
/* This tracks whether any data was assigned inside the scope. When |
|
* the scope is popped, and is_empty_scope is still true, the vector |
|
* will be resized to 0. */ |
|
next_scope->is_empty_scope = true; |
|
next_scope->is_collection = true; |
|
break; |
|
} |
|
|
|
/* Opaque array support */ |
|
case EcsArrayType: { |
|
const EcsArray *at = ecs_get(world, type_ptr->as_type, EcsArray); |
|
next_scope->type = at->type; |
|
|
|
/* Push the element type of the array type */ |
|
if (flecs_meta_cursor_push_type( |
|
world, next_scope, at->type, NULL) != 0) |
|
{ |
|
goto error; |
|
} |
|
|
|
/* Arrays are always a fixed size */ |
|
next_scope->is_empty_scope = false; |
|
next_scope->is_collection = true; |
|
break; |
|
} |
|
|
|
/* Opaque struct support */ |
|
case EcsStructType: |
|
/* Push struct type that represents the opaque type. This ensures |
|
* that the deserializer retains information about members and |
|
* member types, which is necessary for nested opaque types, and |
|
* allows for error checking. */ |
|
if (flecs_meta_cursor_push_type( |
|
world, next_scope, as_type, NULL) != 0) |
|
{ |
|
goto error; |
|
} |
|
|
|
/* Strip push op, since we already pushed */ |
|
next_scope->members = next_scope->ops[0].members; |
|
next_scope->ops = &next_scope->ops[1]; |
|
next_scope->op_count --; |
|
break; |
|
|
|
case EcsPrimitiveType: |
|
case EcsEnumType: |
|
case EcsBitmaskType: |
|
case EcsOpaqueType: |
|
default: |
|
break; |
|
} |
|
|
|
next_scope->ptr = ptr; |
|
next_scope->opaque = type_ptr; |
|
break; |
|
} |
|
|
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpString: |
|
case EcsOpEntity: |
|
case EcsOpId: { |
|
char *path = ecs_get_fullpath(world, scope->type); |
|
ecs_err("invalid push for type '%s'", path); |
|
ecs_os_free(path); |
|
goto error; |
|
} |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
} |
|
|
|
if (scope->is_collection && !scope->opaque) { |
|
next_scope->ptr = ECS_OFFSET(next_scope->ptr, |
|
scope->elem_cur * get_size(world, scope)); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_pop( |
|
ecs_meta_cursor_t *cursor) |
|
{ |
|
if (cursor->is_primitive_scope) { |
|
cursor->is_primitive_scope = false; |
|
return 0; |
|
} |
|
|
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
scope = flecs_meta_cursor_restore_scope(cursor, scope); |
|
cursor->depth --; |
|
if (cursor->depth < 0) { |
|
ecs_err("unexpected end of scope"); |
|
return -1; |
|
} |
|
|
|
ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(next_scope); |
|
|
|
if (!scope->is_inline_array) { |
|
if (op->kind == EcsOpPush) { |
|
next_scope->op_cur += op->op_count - 1; |
|
|
|
/* push + op_count should point to the operation after pop */ |
|
op = flecs_meta_cursor_get_op(next_scope); |
|
ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); |
|
} else if (op->kind == EcsOpArray || op->kind == EcsOpVector) { |
|
/* Collection type, nothing else to do */ |
|
} else if (op->kind == EcsOpOpaque) { |
|
const EcsOpaque *opaque = scope->opaque; |
|
if (scope->is_collection) { |
|
const EcsMetaType *mtype = ecs_get(cursor->world, |
|
opaque->as_type, EcsMetaType); |
|
ecs_assert(mtype != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* When popping a opaque collection type, call resize to make |
|
* sure the vector isn't larger than the number of elements we |
|
* deserialized. |
|
* If the opaque type represents an array, don't call resize. */ |
|
if (mtype->kind != EcsArrayType) { |
|
ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!opaque->resize) { |
|
char *str = ecs_get_fullpath(cursor->world, scope->type); |
|
ecs_err("missing resize for opaque type %s", str); |
|
ecs_os_free(str); |
|
return -1; |
|
} |
|
if (scope->is_empty_scope) { |
|
/* If no values were serialized for scope, resize |
|
* collection to 0 elements. */ |
|
ecs_assert(!scope->elem_cur, ECS_INTERNAL_ERROR, NULL); |
|
opaque->resize(scope->ptr, 0); |
|
} else { |
|
/* Otherwise resize collection to the index of the last |
|
* deserialized element + 1 */ |
|
opaque->resize(scope->ptr, |
|
flecs_ito(size_t, scope->elem_cur + 1)); |
|
} |
|
} |
|
} else { |
|
/* Opaque struct type, nothing to be done */ |
|
} |
|
} else { |
|
/* should not have been able to push if the previous scope was not |
|
* a complex or collection type */ |
|
ecs_assert(false, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
bool ecs_meta_is_collection( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
return scope->is_collection; |
|
} |
|
|
|
ecs_entity_t ecs_meta_get_type( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
return op->type; |
|
} |
|
|
|
ecs_entity_t ecs_meta_get_unit( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_entity_t type = scope->type; |
|
const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); |
|
if (!st) { |
|
return 0; |
|
} |
|
|
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
ecs_member_t *m = ecs_vec_get_t( |
|
&st->members, ecs_member_t, op->member_index); |
|
|
|
return m->unit; |
|
} |
|
|
|
const char* ecs_meta_get_member( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
return op->name; |
|
} |
|
|
|
ecs_entity_t ecs_meta_get_member_id( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_entity_t type = scope->type; |
|
const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); |
|
if (!st) { |
|
return 0; |
|
} |
|
|
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
ecs_member_t *m = ecs_vec_get_t( |
|
&st->members, ecs_member_t, op->member_index); |
|
|
|
return m->member; |
|
} |
|
|
|
/* Utilities for type conversions and bounds checking */ |
|
static struct { |
|
int64_t min, max; |
|
} ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { |
|
[EcsOpBool] = {false, true}, |
|
[EcsOpChar] = {INT8_MIN, INT8_MAX}, |
|
[EcsOpByte] = {0, UINT8_MAX}, |
|
[EcsOpU8] = {0, UINT8_MAX}, |
|
[EcsOpU16] = {0, UINT16_MAX}, |
|
[EcsOpU32] = {0, UINT32_MAX}, |
|
[EcsOpU64] = {0, INT64_MAX}, |
|
[EcsOpI8] = {INT8_MIN, INT8_MAX}, |
|
[EcsOpI16] = {INT16_MIN, INT16_MAX}, |
|
[EcsOpI32] = {INT32_MIN, INT32_MAX}, |
|
[EcsOpI64] = {INT64_MIN, INT64_MAX}, |
|
[EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, |
|
[EcsOpIPtr] = { |
|
((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), |
|
((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) |
|
}, |
|
[EcsOpEntity] = {0, INT64_MAX}, |
|
[EcsOpId] = {0, INT64_MAX}, |
|
[EcsOpEnum] = {INT32_MIN, INT32_MAX}, |
|
[EcsOpBitmask] = {0, INT32_MAX} |
|
}; |
|
|
|
static struct { |
|
uint64_t min, max; |
|
} ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { |
|
[EcsOpBool] = {false, true}, |
|
[EcsOpChar] = {0, INT8_MAX}, |
|
[EcsOpByte] = {0, UINT8_MAX}, |
|
[EcsOpU8] = {0, UINT8_MAX}, |
|
[EcsOpU16] = {0, UINT16_MAX}, |
|
[EcsOpU32] = {0, UINT32_MAX}, |
|
[EcsOpU64] = {0, UINT64_MAX}, |
|
[EcsOpI8] = {0, INT8_MAX}, |
|
[EcsOpI16] = {0, INT16_MAX}, |
|
[EcsOpI32] = {0, INT32_MAX}, |
|
[EcsOpI64] = {0, INT64_MAX}, |
|
[EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, |
|
[EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, |
|
[EcsOpEntity] = {0, UINT64_MAX}, |
|
[EcsOpId] = {0, UINT64_MAX}, |
|
[EcsOpEnum] = {0, INT32_MAX}, |
|
[EcsOpBitmask] = {0, UINT32_MAX} |
|
}; |
|
|
|
static struct { |
|
double min, max; |
|
} ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { |
|
[EcsOpBool] = {false, true}, |
|
[EcsOpChar] = {INT8_MIN, INT8_MAX}, |
|
[EcsOpByte] = {0, UINT8_MAX}, |
|
[EcsOpU8] = {0, UINT8_MAX}, |
|
[EcsOpU16] = {0, UINT16_MAX}, |
|
[EcsOpU32] = {0, UINT32_MAX}, |
|
[EcsOpU64] = {0, (double)UINT64_MAX}, |
|
[EcsOpI8] = {INT8_MIN, INT8_MAX}, |
|
[EcsOpI16] = {INT16_MIN, INT16_MAX}, |
|
[EcsOpI32] = {INT32_MIN, INT32_MAX}, |
|
[EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, |
|
[EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, |
|
[EcsOpIPtr] = { |
|
((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), |
|
((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) |
|
}, |
|
[EcsOpEntity] = {0, (double)UINT64_MAX}, |
|
[EcsOpId] = {0, (double)UINT64_MAX}, |
|
[EcsOpEnum] = {INT32_MIN, INT32_MAX}, |
|
[EcsOpBitmask] = {0, UINT32_MAX} |
|
}; |
|
|
|
#define set_T(T, ptr, value)\ |
|
((T*)ptr)[0] = ((T)value) |
|
|
|
#define case_T(kind, T, dst, src)\ |
|
case kind:\ |
|
set_T(T, dst, src);\ |
|
break |
|
|
|
#define case_T_checked(kind, T, dst, src, bounds)\ |
|
case kind:\ |
|
if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ |
|
ecs_err("value %.0f is out of bounds for type %s", (double)src,\ |
|
flecs_meta_op_kind_str(kind));\ |
|
return -1;\ |
|
}\ |
|
set_T(T, dst, src);\ |
|
break |
|
|
|
#define cases_T_float(dst, src)\ |
|
case_T(EcsOpF32, ecs_f32_t, dst, src);\ |
|
case_T(EcsOpF64, ecs_f64_t, dst, src) |
|
|
|
#define cases_T_signed(dst, src, bounds)\ |
|
case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpEnum, ecs_i32_t, dst, src, bounds) |
|
|
|
#define cases_T_unsigned(dst, src, bounds)\ |
|
case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpId, ecs_u64_t, dst, src, bounds);\ |
|
case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) |
|
|
|
#define cases_T_bool(dst, src)\ |
|
case EcsOpBool:\ |
|
set_T(ecs_bool_t, dst, value != 0);\ |
|
break |
|
|
|
static |
|
void flecs_meta_conversion_error( |
|
ecs_meta_cursor_t *cursor, |
|
ecs_meta_type_op_t *op, |
|
const char *from) |
|
{ |
|
if (op->kind == EcsOpPop) { |
|
ecs_err("cursor: out of bounds"); |
|
} else { |
|
char *path = ecs_get_fullpath(cursor->world, op->type); |
|
ecs_err("unsupported conversion from %s to '%s'", from, path); |
|
ecs_os_free(path); |
|
} |
|
} |
|
|
|
int ecs_meta_set_bool( |
|
ecs_meta_cursor_t *cursor, |
|
bool value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
switch(op->kind) { |
|
cases_T_bool(ptr, value); |
|
cases_T_signed(ptr, value, ecs_meta_bounds_signed); |
|
cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); |
|
case EcsOpOpaque: { |
|
const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); |
|
if (ot && ot->assign_bool) { |
|
ot->assign_bool(ptr, value); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpString: |
|
flecs_meta_conversion_error(cursor, op, "bool"); |
|
return -1; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_char( |
|
ecs_meta_cursor_t *cursor, |
|
char value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
switch(op->kind) { |
|
cases_T_bool(ptr, value); |
|
cases_T_signed(ptr, value, ecs_meta_bounds_signed); |
|
case EcsOpOpaque: { |
|
const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); |
|
ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); |
|
if (opaque->assign_char) { /* preferred operation */ |
|
opaque->assign_char(ptr, value); |
|
break; |
|
} else if (opaque->assign_uint) { |
|
opaque->assign_uint(ptr, (uint64_t)value); |
|
break; |
|
} else if (opaque->assign_int) { |
|
opaque->assign_int(ptr, value); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpString: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
flecs_meta_conversion_error(cursor, op, "char"); |
|
return -1; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_int( |
|
ecs_meta_cursor_t *cursor, |
|
int64_t value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
switch(op->kind) { |
|
cases_T_bool(ptr, value); |
|
cases_T_signed(ptr, value, ecs_meta_bounds_signed); |
|
cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); |
|
cases_T_float(ptr, value); |
|
case EcsOpOpaque: { |
|
const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); |
|
ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); |
|
if (opaque->assign_int) { /* preferred operation */ |
|
opaque->assign_int(ptr, value); |
|
break; |
|
} else if (opaque->assign_float) { /* most expressive */ |
|
opaque->assign_float(ptr, (double)value); |
|
break; |
|
} else if (opaque->assign_uint && (value > 0)) { |
|
opaque->assign_uint(ptr, flecs_ito(uint64_t, value)); |
|
break; |
|
} else if (opaque->assign_char && (value > 0) && (value < 256)) { |
|
opaque->assign_char(ptr, flecs_ito(char, value)); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
case EcsOpString: { |
|
if(!value) return ecs_meta_set_null(cursor); |
|
flecs_meta_conversion_error(cursor, op, "int"); |
|
return -1; |
|
} |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_uint( |
|
ecs_meta_cursor_t *cursor, |
|
uint64_t value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
switch(op->kind) { |
|
cases_T_bool(ptr, value); |
|
cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); |
|
cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); |
|
cases_T_float(ptr, value); |
|
case EcsOpOpaque: { |
|
const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); |
|
ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); |
|
if (opaque->assign_uint) { /* preferred operation */ |
|
opaque->assign_uint(ptr, value); |
|
break; |
|
} else if (opaque->assign_float) { /* most expressive */ |
|
opaque->assign_float(ptr, (double)value); |
|
break; |
|
} else if (opaque->assign_int && (value < INT64_MAX)) { |
|
opaque->assign_int(ptr, flecs_uto(int64_t, value)); |
|
break; |
|
} else if (opaque->assign_char && (value < 256)) { |
|
opaque->assign_char(ptr, flecs_uto(char, value)); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
case EcsOpString: |
|
if(!value) return ecs_meta_set_null(cursor); |
|
flecs_meta_conversion_error(cursor, op, "uint"); |
|
return -1; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_float( |
|
ecs_meta_cursor_t *cursor, |
|
double value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
switch(op->kind) { |
|
case EcsOpBool: |
|
if (ECS_EQZERO(value)) { |
|
set_T(bool, ptr, false); |
|
} else { |
|
set_T(bool, ptr, true); |
|
} |
|
break; |
|
cases_T_signed(ptr, value, ecs_meta_bounds_float); |
|
cases_T_unsigned(ptr, value, ecs_meta_bounds_float); |
|
cases_T_float(ptr, value); |
|
case EcsOpOpaque: { |
|
const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); |
|
ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); |
|
if (opaque->assign_float) { /* preferred operation */ |
|
opaque->assign_float(ptr, value); |
|
break; |
|
} else if (opaque->assign_int && /* most expressive */ |
|
(value <= (double)INT64_MAX) && (value >= (double)INT64_MIN)) |
|
{ |
|
opaque->assign_int(ptr, (int64_t)value); |
|
break; |
|
} else if (opaque->assign_uint && (value >= 0)) { |
|
opaque->assign_uint(ptr, (uint64_t)value); |
|
break; |
|
} else if (opaque->assign_entity && (value >= 0)) { |
|
opaque->assign_entity( |
|
ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), |
|
(ecs_entity_t)value); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
case EcsOpString: |
|
flecs_meta_conversion_error(cursor, op, "float"); |
|
return -1; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_value( |
|
ecs_meta_cursor_t *cursor, |
|
const ecs_value_t *value) |
|
{ |
|
ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_entity_t type = value->type; |
|
ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); |
|
const EcsMetaType *mt = ecs_get(cursor->world, type, EcsMetaType); |
|
if (!mt) { |
|
ecs_err("type of value does not have reflection data"); |
|
return -1; |
|
} |
|
|
|
if (mt->kind == EcsPrimitiveType) { |
|
const EcsPrimitive *prim = ecs_get(cursor->world, type, EcsPrimitive); |
|
ecs_check(prim != NULL, ECS_INTERNAL_ERROR, NULL); |
|
switch(prim->kind) { |
|
case EcsBool: return ecs_meta_set_bool(cursor, *(bool*)value->ptr); |
|
case EcsChar: return ecs_meta_set_char(cursor, *(char*)value->ptr); |
|
case EcsByte: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); |
|
case EcsU8: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); |
|
case EcsU16: return ecs_meta_set_uint(cursor, *(uint16_t*)value->ptr); |
|
case EcsU32: return ecs_meta_set_uint(cursor, *(uint32_t*)value->ptr); |
|
case EcsU64: return ecs_meta_set_uint(cursor, *(uint64_t*)value->ptr); |
|
case EcsI8: return ecs_meta_set_int(cursor, *(int8_t*)value->ptr); |
|
case EcsI16: return ecs_meta_set_int(cursor, *(int16_t*)value->ptr); |
|
case EcsI32: return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); |
|
case EcsI64: return ecs_meta_set_int(cursor, *(int64_t*)value->ptr); |
|
case EcsF32: return ecs_meta_set_float(cursor, (double)*(float*)value->ptr); |
|
case EcsF64: return ecs_meta_set_float(cursor, *(double*)value->ptr); |
|
case EcsUPtr: return ecs_meta_set_uint(cursor, *(uintptr_t*)value->ptr); |
|
case EcsIPtr: return ecs_meta_set_int(cursor, *(intptr_t*)value->ptr); |
|
case EcsString: return ecs_meta_set_string(cursor, *(char**)value->ptr); |
|
case EcsEntity: return ecs_meta_set_entity(cursor, *(ecs_entity_t*)value->ptr); |
|
case EcsId: return ecs_meta_set_id(cursor, *(ecs_id_t*)value->ptr); |
|
default: |
|
ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); |
|
goto error; |
|
} |
|
} else if (mt->kind == EcsEnumType) { |
|
return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); |
|
} else if (mt->kind == EcsBitmaskType) { |
|
return ecs_meta_set_int(cursor, *(uint32_t*)value->ptr); |
|
} else { |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
if (op->type != value->type) { |
|
char *type_str = ecs_get_fullpath(cursor->world, value->type); |
|
flecs_meta_conversion_error(cursor, op, type_str); |
|
ecs_os_free(type_str); |
|
goto error; |
|
} |
|
return ecs_value_copy(cursor->world, value->type, ptr, value->ptr); |
|
} |
|
|
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int flecs_meta_add_bitmask_constant( |
|
ecs_meta_cursor_t *cursor, |
|
ecs_meta_type_op_t *op, |
|
void *out, |
|
const char *value) |
|
{ |
|
ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!ecs_os_strcmp(value, "0")) { |
|
return 0; |
|
} |
|
|
|
ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); |
|
if (!c) { |
|
char *path = ecs_get_fullpath(cursor->world, op->type); |
|
ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
const ecs_u32_t *v = ecs_get_pair_object( |
|
cursor->world, c, EcsConstant, ecs_u32_t); |
|
if (v == NULL) { |
|
char *path = ecs_get_fullpath(cursor->world, op->type); |
|
ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
*(ecs_u32_t*)out |= v[0]; |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_parse_bitmask( |
|
ecs_meta_cursor_t *cursor, |
|
ecs_meta_type_op_t *op, |
|
void *out, |
|
const char *value) |
|
{ |
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
|
|
const char *prev = value, *ptr = value; |
|
|
|
*(ecs_u32_t*)out = 0; |
|
|
|
while ((ptr = strchr(ptr, '|'))) { |
|
ecs_os_memcpy(token, prev, ptr - prev); |
|
token[ptr - prev] = '\0'; |
|
if (flecs_meta_add_bitmask_constant(cursor, op, out, token) != 0) { |
|
return -1; |
|
} |
|
|
|
ptr ++; |
|
prev = ptr; |
|
} |
|
|
|
if (flecs_meta_add_bitmask_constant(cursor, op, out, prev) != 0) { |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_cursor_lookup( |
|
ecs_meta_cursor_t *cursor, |
|
const char *value, |
|
ecs_entity_t *out) |
|
{ |
|
if (ecs_os_strcmp(value, "0")) { |
|
if (cursor->lookup_action) { |
|
*out = cursor->lookup_action( |
|
cursor->world, value, |
|
cursor->lookup_ctx); |
|
} else { |
|
*out = ecs_lookup_path(cursor->world, 0, value); |
|
} |
|
if (!*out) { |
|
ecs_err("unresolved entity identifier '%s'", value); |
|
return -1; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
int ecs_meta_set_string( |
|
ecs_meta_cursor_t *cursor, |
|
const char *value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
switch(op->kind) { |
|
case EcsOpBool: |
|
if (!ecs_os_strcmp(value, "true")) { |
|
set_T(ecs_bool_t, ptr, true); |
|
} else if (!ecs_os_strcmp(value, "false")) { |
|
set_T(ecs_bool_t, ptr, false); |
|
} else if (isdigit(value[0])) { |
|
if (!ecs_os_strcmp(value, "0")) { |
|
set_T(ecs_bool_t, ptr, false); |
|
} else { |
|
set_T(ecs_bool_t, ptr, true); |
|
} |
|
} else { |
|
ecs_err("invalid value for boolean '%s'", value); |
|
goto error; |
|
} |
|
break; |
|
case EcsOpI8: |
|
case EcsOpU8: |
|
case EcsOpByte: |
|
set_T(ecs_i8_t, ptr, atol(value)); |
|
break; |
|
case EcsOpChar: |
|
set_T(char, ptr, value[0]); |
|
break; |
|
case EcsOpI16: |
|
case EcsOpU16: |
|
set_T(ecs_i16_t, ptr, atol(value)); |
|
break; |
|
case EcsOpI32: |
|
case EcsOpU32: |
|
set_T(ecs_i32_t, ptr, atol(value)); |
|
break; |
|
case EcsOpI64: |
|
case EcsOpU64: |
|
set_T(ecs_i64_t, ptr, atoll(value)); |
|
break; |
|
case EcsOpIPtr: |
|
case EcsOpUPtr: |
|
set_T(ecs_iptr_t, ptr, atoll(value)); |
|
break; |
|
case EcsOpF32: |
|
set_T(ecs_f32_t, ptr, atof(value)); |
|
break; |
|
case EcsOpF64: |
|
set_T(ecs_f64_t, ptr, atof(value)); |
|
break; |
|
case EcsOpString: { |
|
ecs_assert(*(ecs_string_t*)ptr != value, ECS_INVALID_PARAMETER, NULL); |
|
ecs_os_free(*(ecs_string_t*)ptr); |
|
char *result = ecs_os_strdup(value); |
|
set_T(ecs_string_t, ptr, result); |
|
break; |
|
} |
|
case EcsOpEnum: { |
|
ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); |
|
if (!c) { |
|
char *path = ecs_get_fullpath(cursor->world, op->type); |
|
ecs_err("unresolved enum constant '%s' for type '%s'", value, path); |
|
ecs_os_free(path); |
|
goto error; |
|
} |
|
|
|
const ecs_i32_t *v = ecs_get_pair_object( |
|
cursor->world, c, EcsConstant, ecs_i32_t); |
|
if (v == NULL) { |
|
char *path = ecs_get_fullpath(cursor->world, op->type); |
|
ecs_err("'%s' is not an enum constant for type '%s'", value, path); |
|
ecs_os_free(path); |
|
goto error; |
|
} |
|
|
|
set_T(ecs_i32_t, ptr, v[0]); |
|
break; |
|
} |
|
case EcsOpBitmask: |
|
if (flecs_meta_parse_bitmask(cursor, op, ptr, value) != 0) { |
|
goto error; |
|
} |
|
break; |
|
case EcsOpEntity: { |
|
ecs_entity_t e = 0; |
|
if (flecs_meta_cursor_lookup(cursor, value, &e)) { |
|
goto error; |
|
} |
|
set_T(ecs_entity_t, ptr, e); |
|
break; |
|
} |
|
case EcsOpId: { |
|
ecs_id_t id = 0; |
|
#ifdef FLECS_PARSER |
|
ecs_term_t term = {0}; |
|
if (ecs_parse_term( |
|
cursor->world, NULL, value, value, &term, NULL, NULL, false)) |
|
{ |
|
if (ecs_term_finalize(cursor->world, &term)) { |
|
ecs_term_fini(&term); |
|
goto error; |
|
} |
|
id = term.id; |
|
ecs_term_fini(&term); |
|
} else { |
|
ecs_term_fini(&term); |
|
goto error; |
|
} |
|
#endif |
|
set_T(ecs_id_t, ptr, id); |
|
|
|
break; |
|
} |
|
case EcsOpPop: |
|
ecs_err("excess element '%s' in scope", value); |
|
goto error; |
|
case EcsOpOpaque: { |
|
const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); |
|
ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); |
|
if (opaque->assign_string) { /* preferred */ |
|
opaque->assign_string(ptr, value); |
|
break; |
|
} else if (opaque->assign_char && value[0] && !value[1]) { |
|
opaque->assign_char(ptr, value[0]); |
|
break; |
|
} else if (opaque->assign_entity) { |
|
ecs_entity_t e = 0; |
|
if (flecs_meta_cursor_lookup(cursor, value, &e)) { |
|
goto error; |
|
} |
|
opaque->assign_entity(ptr, |
|
ECS_CONST_CAST(ecs_world_t*, cursor->world), e); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
ecs_err("unsupported conversion from string '%s' to '%s'", |
|
value, flecs_meta_op_kind_str(op->kind)); |
|
goto error; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_string_literal( |
|
ecs_meta_cursor_t *cursor, |
|
const char *value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
ecs_size_t len = ecs_os_strlen(value); |
|
if (value[0] != '\"' || value[len - 1] != '\"') { |
|
ecs_err("invalid string literal '%s'", value); |
|
goto error; |
|
} |
|
|
|
switch(op->kind) { |
|
case EcsOpChar: |
|
set_T(ecs_char_t, ptr, value[1]); |
|
break; |
|
|
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpString: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
len -= 2; |
|
|
|
char *result = ecs_os_malloc(len + 1); |
|
ecs_os_memcpy(result, value + 1, len); |
|
result[len] = '\0'; |
|
|
|
if (ecs_meta_set_string(cursor, result)) { |
|
ecs_os_free(result); |
|
return -1; |
|
} |
|
|
|
ecs_os_free(result); |
|
break; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_entity( |
|
ecs_meta_cursor_t *cursor, |
|
ecs_entity_t value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
switch(op->kind) { |
|
case EcsOpEntity: |
|
set_T(ecs_entity_t, ptr, value); |
|
break; |
|
case EcsOpId: |
|
set_T(ecs_id_t, ptr, value); /* entities are valid ids */ |
|
break; |
|
case EcsOpOpaque: { |
|
const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); |
|
if (opaque && opaque->assign_entity) { |
|
opaque->assign_entity(ptr, |
|
ECS_CONST_CAST(ecs_world_t*, cursor->world), value); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpString: |
|
flecs_meta_conversion_error(cursor, op, "entity"); |
|
goto error; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_id( |
|
ecs_meta_cursor_t *cursor, |
|
ecs_entity_t value) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
|
|
switch(op->kind) { |
|
case EcsOpId: |
|
set_T(ecs_id_t, ptr, value); |
|
break; |
|
case EcsOpOpaque: { |
|
const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); |
|
if (opaque && opaque->assign_id) { |
|
opaque->assign_id(ptr, |
|
ECS_CONST_CAST(ecs_world_t*, cursor->world), value); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpString: |
|
case EcsOpEntity: |
|
flecs_meta_conversion_error(cursor, op, "id"); |
|
goto error; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
int ecs_meta_set_null( |
|
ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
switch (op->kind) { |
|
case EcsOpString: |
|
ecs_os_free(*(char**)ptr); |
|
set_T(ecs_string_t, ptr, NULL); |
|
break; |
|
case EcsOpOpaque: { |
|
const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); |
|
if (ot && ot->assign_null) { |
|
ot->assign_null(ptr); |
|
break; |
|
} |
|
} |
|
/* fall through */ |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpChar: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
flecs_meta_conversion_error(cursor, op, "null"); |
|
goto error; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
bool ecs_meta_get_bool( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
switch(op->kind) { |
|
case EcsOpBool: return *(ecs_bool_t*)ptr; |
|
case EcsOpI8: return *(ecs_i8_t*)ptr != 0; |
|
case EcsOpU8: return *(ecs_u8_t*)ptr != 0; |
|
case EcsOpChar: return *(ecs_char_t*)ptr != 0; |
|
case EcsOpByte: return *(ecs_u8_t*)ptr != 0; |
|
case EcsOpI16: return *(ecs_i16_t*)ptr != 0; |
|
case EcsOpU16: return *(ecs_u16_t*)ptr != 0; |
|
case EcsOpI32: return *(ecs_i32_t*)ptr != 0; |
|
case EcsOpU32: return *(ecs_u32_t*)ptr != 0; |
|
case EcsOpI64: return *(ecs_i64_t*)ptr != 0; |
|
case EcsOpU64: return *(ecs_u64_t*)ptr != 0; |
|
case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; |
|
case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; |
|
case EcsOpF32: return ECS_NEQZERO(*(ecs_f32_t*)ptr); |
|
case EcsOpF64: return ECS_NEQZERO(*(ecs_f64_t*)ptr); |
|
case EcsOpString: return *(const char**)ptr != NULL; |
|
case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; |
|
case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; |
|
case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; |
|
case EcsOpId: return *(ecs_id_t*)ptr != 0; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid element for bool"); |
|
break; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
|
|
error: |
|
return 0; |
|
} |
|
|
|
char ecs_meta_get_char( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
switch(op->kind) { |
|
case EcsOpChar: |
|
return *(ecs_char_t*)ptr != 0; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpBool: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpString: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid element for char"); |
|
break; |
|
} |
|
|
|
error: |
|
return 0; |
|
} |
|
|
|
int64_t ecs_meta_get_int( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
switch(op->kind) { |
|
case EcsOpBool: return *(const ecs_bool_t*)ptr; |
|
case EcsOpI8: return *(const ecs_i8_t*)ptr; |
|
case EcsOpU8: return *(const ecs_u8_t*)ptr; |
|
case EcsOpChar: return *(const ecs_char_t*)ptr; |
|
case EcsOpByte: return *(const ecs_u8_t*)ptr; |
|
case EcsOpI16: return *(const ecs_i16_t*)ptr; |
|
case EcsOpU16: return *(const ecs_u16_t*)ptr; |
|
case EcsOpI32: return *(const ecs_i32_t*)ptr; |
|
case EcsOpU32: return *(const ecs_u32_t*)ptr; |
|
case EcsOpI64: return *(const ecs_i64_t*)ptr; |
|
case EcsOpU64: return flecs_uto(int64_t, *(const ecs_u64_t*)ptr); |
|
case EcsOpIPtr: return *(const ecs_iptr_t*)ptr; |
|
case EcsOpUPtr: return flecs_uto(int64_t, *(const ecs_uptr_t*)ptr); |
|
case EcsOpF32: return (int64_t)*(const ecs_f32_t*)ptr; |
|
case EcsOpF64: return (int64_t)*(const ecs_f64_t*)ptr; |
|
case EcsOpString: return atoi(*(const char**)ptr); |
|
case EcsOpEnum: return *(const ecs_i32_t*)ptr; |
|
case EcsOpBitmask: return *(const ecs_u32_t*)ptr; |
|
case EcsOpEntity: |
|
ecs_throw(ECS_INVALID_PARAMETER, |
|
"invalid conversion from entity to int"); |
|
break; |
|
case EcsOpId: |
|
ecs_throw(ECS_INVALID_PARAMETER, |
|
"invalid conversion from id to int"); |
|
break; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); |
|
break; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
uint64_t ecs_meta_get_uint( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
switch(op->kind) { |
|
case EcsOpBool: return *(ecs_bool_t*)ptr; |
|
case EcsOpI8: return flecs_ito(uint64_t, *(const ecs_i8_t*)ptr); |
|
case EcsOpU8: return *(ecs_u8_t*)ptr; |
|
case EcsOpChar: return flecs_ito(uint64_t, *(const ecs_char_t*)ptr); |
|
case EcsOpByte: return flecs_ito(uint64_t, *(const ecs_u8_t*)ptr); |
|
case EcsOpI16: return flecs_ito(uint64_t, *(const ecs_i16_t*)ptr); |
|
case EcsOpU16: return *(ecs_u16_t*)ptr; |
|
case EcsOpI32: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); |
|
case EcsOpU32: return *(ecs_u32_t*)ptr; |
|
case EcsOpI64: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); |
|
case EcsOpU64: return *(ecs_u64_t*)ptr; |
|
case EcsOpIPtr: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); |
|
case EcsOpUPtr: return *(ecs_uptr_t*)ptr; |
|
case EcsOpF32: return flecs_ito(uint64_t, *(const ecs_f32_t*)ptr); |
|
case EcsOpF64: return flecs_ito(uint64_t, *(const ecs_f64_t*)ptr); |
|
case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); |
|
case EcsOpEnum: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); |
|
case EcsOpBitmask: return *(const ecs_u32_t*)ptr; |
|
case EcsOpEntity: return *(const ecs_entity_t*)ptr; |
|
case EcsOpId: return *(const ecs_id_t*)ptr; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); |
|
break; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
static |
|
double flecs_meta_to_float( |
|
ecs_meta_type_op_kind_t kind, |
|
const void *ptr) |
|
{ |
|
switch(kind) { |
|
case EcsOpBool: return *(const ecs_bool_t*)ptr; |
|
case EcsOpI8: return *(const ecs_i8_t*)ptr; |
|
case EcsOpU8: return *(const ecs_u8_t*)ptr; |
|
case EcsOpChar: return *(const ecs_char_t*)ptr; |
|
case EcsOpByte: return *(const ecs_u8_t*)ptr; |
|
case EcsOpI16: return *(const ecs_i16_t*)ptr; |
|
case EcsOpU16: return *(const ecs_u16_t*)ptr; |
|
case EcsOpI32: return *(const ecs_i32_t*)ptr; |
|
case EcsOpU32: return *(const ecs_u32_t*)ptr; |
|
case EcsOpI64: return (double)*(const ecs_i64_t*)ptr; |
|
case EcsOpU64: return (double)*(const ecs_u64_t*)ptr; |
|
case EcsOpIPtr: return (double)*(const ecs_iptr_t*)ptr; |
|
case EcsOpUPtr: return (double)*(const ecs_uptr_t*)ptr; |
|
case EcsOpF32: return (double)*(const ecs_f32_t*)ptr; |
|
case EcsOpF64: return *(const ecs_f64_t*)ptr; |
|
case EcsOpString: return atof(*ECS_CONST_CAST(const char**, ptr)); |
|
case EcsOpEnum: return *(const ecs_i32_t*)ptr; |
|
case EcsOpBitmask: return *(const ecs_u32_t*)ptr; |
|
case EcsOpEntity: |
|
ecs_throw(ECS_INVALID_PARAMETER, |
|
"invalid conversion from entity to float"); |
|
break; |
|
case EcsOpId: |
|
ecs_throw(ECS_INVALID_PARAMETER, |
|
"invalid conversion from id to float"); |
|
break; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpPrimitive: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); |
|
break; |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); |
|
break; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
double ecs_meta_get_float( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
return flecs_meta_to_float(op->kind, ptr); |
|
} |
|
|
|
const char* ecs_meta_get_string( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
switch(op->kind) { |
|
case EcsOpString: return *(const char**)ptr; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpChar: |
|
case EcsOpBool: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpEntity: |
|
case EcsOpId: |
|
default: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); |
|
break; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_meta_get_entity( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
switch(op->kind) { |
|
case EcsOpEntity: return *(ecs_entity_t*)ptr; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpChar: |
|
case EcsOpBool: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpString: |
|
case EcsOpId: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); |
|
break; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_meta_get_id( |
|
const ecs_meta_cursor_t *cursor) |
|
{ |
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); |
|
ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); |
|
void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); |
|
switch(op->kind) { |
|
case EcsOpEntity: return *(ecs_id_t*)ptr; /* Entities are valid ids */ |
|
case EcsOpId: return *(ecs_id_t*)ptr; |
|
case EcsOpArray: |
|
case EcsOpVector: |
|
case EcsOpOpaque: |
|
case EcsOpPush: |
|
case EcsOpPop: |
|
case EcsOpScope: |
|
case EcsOpEnum: |
|
case EcsOpBitmask: |
|
case EcsOpPrimitive: |
|
case EcsOpChar: |
|
case EcsOpBool: |
|
case EcsOpByte: |
|
case EcsOpU8: |
|
case EcsOpU16: |
|
case EcsOpU32: |
|
case EcsOpU64: |
|
case EcsOpI8: |
|
case EcsOpI16: |
|
case EcsOpI32: |
|
case EcsOpI64: |
|
case EcsOpF32: |
|
case EcsOpF64: |
|
case EcsOpUPtr: |
|
case EcsOpIPtr: |
|
case EcsOpString: |
|
ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); |
|
break; |
|
} |
|
error: |
|
return 0; |
|
} |
|
|
|
double ecs_meta_ptr_to_float( |
|
ecs_primitive_kind_t type_kind, |
|
const void *ptr) |
|
{ |
|
ecs_meta_type_op_kind_t kind = flecs_meta_primitive_to_op_kind(type_kind); |
|
return flecs_meta_to_float(kind, ptr); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file meta/meta.c |
|
* @brief Meta addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_META |
|
|
|
/* ecs_string_t lifecycle */ |
|
|
|
static ECS_COPY(ecs_string_t, dst, src, { |
|
ecs_os_free(*(ecs_string_t*)dst); |
|
*(ecs_string_t*)dst = ecs_os_strdup(*(const ecs_string_t*)src); |
|
}) |
|
|
|
static ECS_MOVE(ecs_string_t, dst, src, { |
|
ecs_os_free(*(ecs_string_t*)dst); |
|
*(ecs_string_t*)dst = *(ecs_string_t*)src; |
|
*(ecs_string_t*)src = NULL; |
|
}) |
|
|
|
static ECS_DTOR(ecs_string_t, ptr, { |
|
ecs_os_free(*(ecs_string_t*)ptr); |
|
*(ecs_string_t*)ptr = NULL; |
|
}) |
|
|
|
|
|
/* EcsMetaTypeSerialized lifecycle */ |
|
|
|
void ecs_meta_dtor_serialized( |
|
EcsMetaTypeSerialized *ptr) |
|
{ |
|
int32_t i, count = ecs_vec_count(&ptr->ops); |
|
ecs_meta_type_op_t *ops = ecs_vec_first(&ptr->ops); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_meta_type_op_t *op = &ops[i]; |
|
if (op->members) { |
|
flecs_name_index_free(op->members); |
|
} |
|
} |
|
|
|
ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_type_op_t); |
|
} |
|
|
|
static ECS_COPY(EcsMetaTypeSerialized, dst, src, { |
|
ecs_meta_dtor_serialized(dst); |
|
|
|
dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_type_op_t); |
|
|
|
int32_t o, count = ecs_vec_count(&dst->ops); |
|
ecs_meta_type_op_t *ops = ecs_vec_first_t(&dst->ops, ecs_meta_type_op_t); |
|
|
|
for (o = 0; o < count; o ++) { |
|
ecs_meta_type_op_t *op = &ops[o]; |
|
if (op->members) { |
|
op->members = flecs_name_index_copy(op->members); |
|
} |
|
} |
|
}) |
|
|
|
static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { |
|
ecs_meta_dtor_serialized(dst); |
|
dst->ops = src->ops; |
|
src->ops = (ecs_vec_t){0}; |
|
}) |
|
|
|
static ECS_DTOR(EcsMetaTypeSerialized, ptr, { |
|
ecs_meta_dtor_serialized(ptr); |
|
}) |
|
|
|
|
|
/* EcsStruct lifecycle */ |
|
|
|
static void flecs_struct_dtor( |
|
EcsStruct *ptr) |
|
{ |
|
ecs_member_t *members = ecs_vec_first_t(&ptr->members, ecs_member_t); |
|
int32_t i, count = ecs_vec_count(&ptr->members); |
|
for (i = 0; i < count; i ++) { |
|
ecs_os_free(ECS_CONST_CAST(char*, members[i].name)); |
|
} |
|
ecs_vec_fini_t(NULL, &ptr->members, ecs_member_t); |
|
} |
|
|
|
static ECS_COPY(EcsStruct, dst, src, { |
|
flecs_struct_dtor(dst); |
|
|
|
dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); |
|
|
|
ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); |
|
int32_t m, count = ecs_vec_count(&dst->members); |
|
|
|
for (m = 0; m < count; m ++) { |
|
members[m].name = ecs_os_strdup(members[m].name); |
|
} |
|
}) |
|
|
|
static ECS_MOVE(EcsStruct, dst, src, { |
|
flecs_struct_dtor(dst); |
|
dst->members = src->members; |
|
src->members = (ecs_vec_t){0}; |
|
}) |
|
|
|
static ECS_DTOR(EcsStruct, ptr, { flecs_struct_dtor(ptr); }) |
|
|
|
|
|
/* EcsEnum lifecycle */ |
|
|
|
static void flecs_constants_dtor( |
|
ecs_map_t *constants) |
|
{ |
|
ecs_map_iter_t it = ecs_map_iter(constants); |
|
while (ecs_map_next(&it)) { |
|
ecs_enum_constant_t *c = ecs_map_ptr(&it); |
|
ecs_os_free(ECS_CONST_CAST(char*, c->name)); |
|
ecs_os_free(c); |
|
} |
|
ecs_map_fini(constants); |
|
} |
|
|
|
static void flecs_constants_copy( |
|
ecs_map_t *dst, |
|
const ecs_map_t *src) |
|
{ |
|
ecs_map_copy(dst, src); |
|
|
|
ecs_map_iter_t it = ecs_map_iter(dst); |
|
while (ecs_map_next(&it)) { |
|
ecs_enum_constant_t **r = ecs_map_ref(&it, ecs_enum_constant_t); |
|
ecs_enum_constant_t *src_c = r[0]; |
|
ecs_enum_constant_t *dst_c = ecs_os_calloc_t(ecs_enum_constant_t); |
|
*dst_c = *src_c; |
|
dst_c->name = ecs_os_strdup(dst_c->name); |
|
r[0] = dst_c; |
|
} |
|
} |
|
|
|
static ECS_COPY(EcsEnum, dst, src, { |
|
flecs_constants_dtor(&dst->constants); |
|
flecs_constants_copy(&dst->constants, &src->constants); |
|
}) |
|
|
|
static ECS_MOVE(EcsEnum, dst, src, { |
|
flecs_constants_dtor(&dst->constants); |
|
dst->constants = src->constants; |
|
ecs_os_zeromem(&src->constants); |
|
}) |
|
|
|
static ECS_DTOR(EcsEnum, ptr, { flecs_constants_dtor(&ptr->constants); }) |
|
|
|
|
|
/* EcsBitmask lifecycle */ |
|
|
|
static ECS_COPY(EcsBitmask, dst, src, { |
|
/* bitmask constant & enum constant have the same layout */ |
|
flecs_constants_dtor(&dst->constants); |
|
flecs_constants_copy(&dst->constants, &src->constants); |
|
}) |
|
|
|
static ECS_MOVE(EcsBitmask, dst, src, { |
|
flecs_constants_dtor(&dst->constants); |
|
dst->constants = src->constants; |
|
ecs_os_zeromem(&src->constants); |
|
}) |
|
|
|
static ECS_DTOR(EcsBitmask, ptr, { flecs_constants_dtor(&ptr->constants); }) |
|
|
|
|
|
/* EcsUnit lifecycle */ |
|
|
|
static void dtor_unit( |
|
EcsUnit *ptr) |
|
{ |
|
ecs_os_free(ptr->symbol); |
|
} |
|
|
|
static ECS_COPY(EcsUnit, dst, src, { |
|
dtor_unit(dst); |
|
dst->symbol = ecs_os_strdup(src->symbol); |
|
dst->base = src->base; |
|
dst->over = src->over; |
|
dst->prefix = src->prefix; |
|
dst->translation = src->translation; |
|
}) |
|
|
|
static ECS_MOVE(EcsUnit, dst, src, { |
|
dtor_unit(dst); |
|
dst->symbol = src->symbol; |
|
dst->base = src->base; |
|
dst->over = src->over; |
|
dst->prefix = src->prefix; |
|
dst->translation = src->translation; |
|
|
|
src->symbol = NULL; |
|
src->base = 0; |
|
src->over = 0; |
|
src->prefix = 0; |
|
src->translation = (ecs_unit_translation_t){0}; |
|
}) |
|
|
|
static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) |
|
|
|
|
|
/* EcsUnitPrefix lifecycle */ |
|
|
|
static void dtor_unit_prefix( |
|
EcsUnitPrefix *ptr) |
|
{ |
|
ecs_os_free(ptr->symbol); |
|
} |
|
|
|
static ECS_COPY(EcsUnitPrefix, dst, src, { |
|
dtor_unit_prefix(dst); |
|
dst->symbol = ecs_os_strdup(src->symbol); |
|
dst->translation = src->translation; |
|
}) |
|
|
|
static ECS_MOVE(EcsUnitPrefix, dst, src, { |
|
dtor_unit_prefix(dst); |
|
dst->symbol = src->symbol; |
|
dst->translation = src->translation; |
|
|
|
src->symbol = NULL; |
|
src->translation = (ecs_unit_translation_t){0}; |
|
}) |
|
|
|
static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) |
|
|
|
/* Type initialization */ |
|
|
|
static |
|
const char* flecs_type_kind_str( |
|
ecs_type_kind_t kind) |
|
{ |
|
switch(kind) { |
|
case EcsPrimitiveType: return "Primitive"; |
|
case EcsBitmaskType: return "Bitmask"; |
|
case EcsEnumType: return "Enum"; |
|
case EcsStructType: return "Struct"; |
|
case EcsArrayType: return "Array"; |
|
case EcsVectorType: return "Vector"; |
|
case EcsOpaqueType: return "Opaque"; |
|
default: return "unknown"; |
|
} |
|
} |
|
|
|
static |
|
int flecs_init_type( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_type_kind_t kind, |
|
ecs_size_t size, |
|
ecs_size_t alignment) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType); |
|
if (meta_type->kind == 0) { |
|
meta_type->existing = ecs_has(world, type, EcsComponent); |
|
|
|
/* Ensure that component has a default constructor, to prevent crashing |
|
* serializers on uninitialized values. */ |
|
ecs_type_info_t *ti = flecs_type_info_ensure(world, type); |
|
if (!ti->hooks.ctor) { |
|
ti->hooks.ctor = ecs_default_ctor; |
|
} |
|
} else { |
|
if (meta_type->kind != kind) { |
|
ecs_err("type '%s' reregistered as '%s' (was '%s')", |
|
ecs_get_name(world, type), |
|
flecs_type_kind_str(kind), |
|
flecs_type_kind_str(meta_type->kind)); |
|
return -1; |
|
} |
|
} |
|
|
|
if (!meta_type->existing) { |
|
EcsComponent *comp = ecs_get_mut(world, type, EcsComponent); |
|
comp->size = size; |
|
comp->alignment = alignment; |
|
ecs_modified(world, type, EcsComponent); |
|
} else { |
|
const EcsComponent *comp = ecs_get(world, type, EcsComponent); |
|
if (comp->size < size) { |
|
ecs_err("computed size (%d) for '%s' is larger than actual type (%d)", |
|
size, ecs_get_name(world, type), comp->size); |
|
return -1; |
|
} |
|
if (comp->alignment < alignment) { |
|
ecs_err("computed alignment (%d) for '%s' is larger than actual type (%d)", |
|
alignment, ecs_get_name(world, type), comp->alignment); |
|
return -1; |
|
} |
|
if (comp->size == size && comp->alignment != alignment) { |
|
if (comp->alignment < alignment) { |
|
ecs_err("computed size for '%s' matches with actual type but " |
|
"alignment is different (%d vs. %d)", ecs_get_name(world, type), |
|
alignment, comp->alignment); |
|
} |
|
} |
|
|
|
meta_type->partial = comp->size != size; |
|
} |
|
|
|
meta_type->kind = kind; |
|
ecs_modified(world, type, EcsMetaType); |
|
|
|
return 0; |
|
} |
|
|
|
#define init_type_t(world, type, kind, T) \ |
|
flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) |
|
|
|
static |
|
void flecs_set_struct_member( |
|
ecs_member_t *member, |
|
ecs_entity_t entity, |
|
const char *name, |
|
ecs_entity_t type, |
|
int32_t count, |
|
int32_t offset, |
|
ecs_entity_t unit, |
|
EcsMemberRanges *ranges) |
|
{ |
|
member->member = entity; |
|
member->type = type; |
|
member->count = count; |
|
member->unit = unit; |
|
member->offset = offset; |
|
|
|
if (!count) { |
|
member->count = 1; |
|
} |
|
|
|
ecs_os_strset(ECS_CONST_CAST(char**, &member->name), name); |
|
|
|
if (ranges) { |
|
member->range = ranges->value; |
|
member->error_range = ranges->error; |
|
member->warning_range = ranges->warning; |
|
} else { |
|
ecs_os_zeromem(&member->range); |
|
ecs_os_zeromem(&member->error_range); |
|
ecs_os_zeromem(&member->warning_range); |
|
} |
|
} |
|
|
|
static |
|
int flecs_add_member_to_struct( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_entity_t member, |
|
EcsMember *m, |
|
EcsMemberRanges *ranges) |
|
{ |
|
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(member != 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
const char *name = ecs_get_name(world, member); |
|
if (!name) { |
|
char *path = ecs_get_fullpath(world, type); |
|
ecs_err("member for struct '%s' does not have a name", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
if (!m->type) { |
|
char *path = ecs_get_fullpath(world, member); |
|
ecs_err("member '%s' does not have a type", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
if (ecs_get_typeid(world, m->type) == 0) { |
|
char *path = ecs_get_fullpath(world, member); |
|
char *ent_path = ecs_get_fullpath(world, m->type); |
|
ecs_err("member '%s.type' is '%s' which is not a type", path, ent_path); |
|
ecs_os_free(path); |
|
ecs_os_free(ent_path); |
|
return -1; |
|
} |
|
|
|
ecs_entity_t unit = m->unit; |
|
if (unit) { |
|
if (!ecs_has(world, unit, EcsUnit)) { |
|
ecs_err("entity '%s' for member '%s' is not a unit", |
|
ecs_get_name(world, unit), name); |
|
return -1; |
|
} |
|
|
|
if (ecs_has(world, m->type, EcsUnit) && m->type != unit) { |
|
ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", |
|
ecs_get_name(world, m->type), ecs_get_name(world, unit), name); |
|
return -1; |
|
} |
|
} else { |
|
if (ecs_has(world, m->type, EcsUnit)) { |
|
unit = m->type; |
|
m->unit = unit; |
|
} |
|
} |
|
|
|
EcsStruct *s = ecs_get_mut(world, type, EcsStruct); |
|
ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* First check if member is already added to struct */ |
|
ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); |
|
int32_t i, count = ecs_vec_count(&s->members); |
|
for (i = 0; i < count; i ++) { |
|
if (members[i].member == member) { |
|
flecs_set_struct_member(&members[i], member, name, m->type, |
|
m->count, m->offset, unit, ranges); |
|
break; |
|
} |
|
} |
|
|
|
/* If member wasn't added yet, add a new element to vector */ |
|
if (i == count) { |
|
ecs_vec_init_if_t(&s->members, ecs_member_t); |
|
ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); |
|
elem->name = NULL; |
|
flecs_set_struct_member(elem, member, name, m->type, |
|
m->count, m->offset, unit, ranges); |
|
|
|
/* Reobtain members array in case it was reallocated */ |
|
members = ecs_vec_first_t(&s->members, ecs_member_t); |
|
count ++; |
|
} |
|
|
|
bool explicit_offset = false; |
|
if (m->offset) { |
|
explicit_offset = true; |
|
} |
|
|
|
/* Compute member offsets and size & alignment of struct */ |
|
ecs_size_t size = 0; |
|
ecs_size_t alignment = 0; |
|
|
|
if (!explicit_offset) { |
|
for (i = 0; i < count; i ++) { |
|
ecs_member_t *elem = &members[i]; |
|
|
|
ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Get component of member type to get its size & alignment */ |
|
const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); |
|
if (!mbr_comp) { |
|
char *path = ecs_get_fullpath(world, elem->type); |
|
ecs_err("member '%s' is not a type", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
ecs_size_t member_size = mbr_comp->size; |
|
ecs_size_t member_alignment = mbr_comp->alignment; |
|
|
|
if (!member_size || !member_alignment) { |
|
char *path = ecs_get_fullpath(world, elem->type); |
|
ecs_err("member '%s' has 0 size/alignment", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
member_size *= elem->count; |
|
size = ECS_ALIGN(size, member_alignment); |
|
elem->size = member_size; |
|
elem->offset = size; |
|
|
|
/* Synchronize offset with Member component */ |
|
if (elem->member == member) { |
|
m->offset = elem->offset; |
|
} else { |
|
EcsMember *other = ecs_get_mut(world, elem->member, EcsMember); |
|
other->offset = elem->offset; |
|
} |
|
|
|
size += member_size; |
|
|
|
if (member_alignment > alignment) { |
|
alignment = member_alignment; |
|
} |
|
} |
|
} else { |
|
/* If members have explicit offsets, we can't rely on computed |
|
* size/alignment values. Calculate size as if this is the last member |
|
* instead, since this will validate if the member fits in the struct. |
|
* It doesn't matter if the size is smaller than the actual struct size |
|
* because flecs_init_type function compares computed size with actual |
|
* (component) size to determine if the type is partial. */ |
|
ecs_member_t *elem = &members[i]; |
|
|
|
ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Get component of member type to get its size & alignment */ |
|
const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); |
|
if (!mbr_comp) { |
|
char *path = ecs_get_fullpath(world, elem->type); |
|
ecs_err("member '%s' is not a type", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
ecs_size_t member_size = mbr_comp->size; |
|
ecs_size_t member_alignment = mbr_comp->alignment; |
|
|
|
if (!member_size || !member_alignment) { |
|
char *path = ecs_get_fullpath(world, elem->type); |
|
ecs_err("member '%s' has 0 size/alignment", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
member_size *= elem->count; |
|
elem->size = member_size; |
|
|
|
size = elem->offset + member_size; |
|
|
|
const EcsComponent* comp = ecs_get(world, type, EcsComponent); |
|
alignment = comp->alignment; |
|
} |
|
|
|
if (size == 0) { |
|
ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); |
|
return -1; |
|
} |
|
|
|
if (alignment == 0) { |
|
ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); |
|
return -1; |
|
} |
|
|
|
/* Align struct size to struct alignment */ |
|
size = ECS_ALIGN(size, alignment); |
|
|
|
ecs_modified(world, type, EcsStruct); |
|
|
|
/* Do this last as it triggers the update of EcsMetaTypeSerialized */ |
|
if (flecs_init_type(world, type, EcsStructType, size, alignment)) { |
|
return -1; |
|
} |
|
|
|
/* If current struct is also a member, assign to itself */ |
|
if (ecs_has(world, type, EcsMember)) { |
|
EcsMember *type_mbr = ecs_get_mut(world, type, EcsMember); |
|
ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
type_mbr->type = type; |
|
type_mbr->count = 1; |
|
|
|
ecs_modified(world, type, EcsMember); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_add_constant_to_enum( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_entity_t e, |
|
ecs_id_t constant_id) |
|
{ |
|
EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum); |
|
|
|
/* Remove constant from map if it was already added */ |
|
ecs_map_iter_t it = ecs_map_iter(&ptr->constants); |
|
while (ecs_map_next(&it)) { |
|
ecs_enum_constant_t *c = ecs_map_ptr(&it); |
|
if (c->constant == e) { |
|
ecs_os_free(ECS_CONST_CAST(char*, c->name)); |
|
ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); |
|
} |
|
} |
|
|
|
/* Check if constant sets explicit value */ |
|
int32_t value = 0; |
|
bool value_set = false; |
|
if (ecs_id_is_pair(constant_id)) { |
|
if (ecs_pair_second(world, constant_id) != ecs_id(ecs_i32_t)) { |
|
char *path = ecs_get_fullpath(world, e); |
|
ecs_err("expected i32 type for enum constant '%s'", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
const int32_t *value_ptr = ecs_get_pair_object( |
|
world, e, EcsConstant, ecs_i32_t); |
|
ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
value = *value_ptr; |
|
value_set = true; |
|
} |
|
|
|
/* Make sure constant value doesn't conflict if set / find the next value */ |
|
it = ecs_map_iter(&ptr->constants); |
|
while (ecs_map_next(&it)) { |
|
ecs_enum_constant_t *c = ecs_map_ptr(&it); |
|
if (value_set) { |
|
if (c->value == value) { |
|
char *path = ecs_get_fullpath(world, e); |
|
ecs_err("conflicting constant value %d for '%s' (other is '%s')", |
|
value, path, c->name); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
} else { |
|
if (c->value >= value) { |
|
value = c->value + 1; |
|
} |
|
} |
|
} |
|
|
|
ecs_map_init_if(&ptr->constants, &world->allocator); |
|
ecs_enum_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, |
|
ecs_enum_constant_t, (ecs_map_key_t)value); |
|
c->name = ecs_os_strdup(ecs_get_name(world, e)); |
|
c->value = value; |
|
c->constant = e; |
|
|
|
ecs_i32_t *cptr = ecs_get_mut_pair_object( |
|
world, e, EcsConstant, ecs_i32_t); |
|
ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
cptr[0] = value; |
|
|
|
cptr = ecs_get_mut_id(world, e, type); |
|
cptr[0] = value; |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_add_constant_to_bitmask( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_entity_t e, |
|
ecs_id_t constant_id) |
|
{ |
|
EcsBitmask *ptr = ecs_get_mut(world, type, EcsBitmask); |
|
|
|
/* Remove constant from map if it was already added */ |
|
ecs_map_iter_t it = ecs_map_iter(&ptr->constants); |
|
while (ecs_map_next(&it)) { |
|
ecs_bitmask_constant_t *c = ecs_map_ptr(&it); |
|
if (c->constant == e) { |
|
ecs_os_free(ECS_CONST_CAST(char*, c->name)); |
|
ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); |
|
} |
|
} |
|
|
|
/* Check if constant sets explicit value */ |
|
uint32_t value = 1; |
|
if (ecs_id_is_pair(constant_id)) { |
|
if (ecs_pair_second(world, constant_id) != ecs_id(ecs_u32_t)) { |
|
char *path = ecs_get_fullpath(world, e); |
|
ecs_err("expected u32 type for bitmask constant '%s'", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
const uint32_t *value_ptr = ecs_get_pair_object( |
|
world, e, EcsConstant, ecs_u32_t); |
|
ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
value = *value_ptr; |
|
} else { |
|
value = 1u << (ecs_u32_t)ecs_map_count(&ptr->constants); |
|
} |
|
|
|
/* Make sure constant value doesn't conflict */ |
|
it = ecs_map_iter(&ptr->constants); |
|
while (ecs_map_next(&it)) { |
|
ecs_bitmask_constant_t *c = ecs_map_ptr(&it); |
|
if (c->value == value) { |
|
char *path = ecs_get_fullpath(world, e); |
|
ecs_err("conflicting constant value for '%s' (other is '%s')", |
|
path, c->name); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
} |
|
|
|
ecs_map_init_if(&ptr->constants, &world->allocator); |
|
|
|
ecs_bitmask_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, |
|
ecs_bitmask_constant_t, value); |
|
c->name = ecs_os_strdup(ecs_get_name(world, e)); |
|
c->value = value; |
|
c->constant = e; |
|
|
|
ecs_u32_t *cptr = ecs_get_mut_pair_object( |
|
world, e, EcsConstant, ecs_u32_t); |
|
ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
cptr[0] = value; |
|
|
|
cptr = ecs_get_mut_id(world, e, type); |
|
cptr[0] = value; |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
void flecs_set_primitive(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsPrimitive *type = ecs_field(it, EcsPrimitive, 1); |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
switch(type->kind) { |
|
case EcsBool: |
|
init_type_t(world, e, EcsPrimitiveType, bool); |
|
break; |
|
case EcsChar: |
|
init_type_t(world, e, EcsPrimitiveType, char); |
|
break; |
|
case EcsByte: |
|
init_type_t(world, e, EcsPrimitiveType, bool); |
|
break; |
|
case EcsU8: |
|
init_type_t(world, e, EcsPrimitiveType, uint8_t); |
|
break; |
|
case EcsU16: |
|
init_type_t(world, e, EcsPrimitiveType, uint16_t); |
|
break; |
|
case EcsU32: |
|
init_type_t(world, e, EcsPrimitiveType, uint32_t); |
|
break; |
|
case EcsU64: |
|
init_type_t(world, e, EcsPrimitiveType, uint64_t); |
|
break; |
|
case EcsI8: |
|
init_type_t(world, e, EcsPrimitiveType, int8_t); |
|
break; |
|
case EcsI16: |
|
init_type_t(world, e, EcsPrimitiveType, int16_t); |
|
break; |
|
case EcsI32: |
|
init_type_t(world, e, EcsPrimitiveType, int32_t); |
|
break; |
|
case EcsI64: |
|
init_type_t(world, e, EcsPrimitiveType, int64_t); |
|
break; |
|
case EcsF32: |
|
init_type_t(world, e, EcsPrimitiveType, float); |
|
break; |
|
case EcsF64: |
|
init_type_t(world, e, EcsPrimitiveType, double); |
|
break; |
|
case EcsUPtr: |
|
init_type_t(world, e, EcsPrimitiveType, uintptr_t); |
|
break; |
|
case EcsIPtr: |
|
init_type_t(world, e, EcsPrimitiveType, intptr_t); |
|
break; |
|
case EcsString: |
|
init_type_t(world, e, EcsPrimitiveType, char*); |
|
break; |
|
case EcsEntity: |
|
init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); |
|
break; |
|
case EcsId: |
|
init_type_t(world, e, EcsPrimitiveType, ecs_id_t); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_set_member(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsMember *member = ecs_field(it, EcsMember, 1); |
|
EcsMemberRanges *ranges = ecs_table_get_id(world, it->table, |
|
ecs_id(EcsMemberRanges), it->offset); |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); |
|
if (!parent) { |
|
ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); |
|
continue; |
|
} |
|
|
|
flecs_add_member_to_struct(world, parent, e, &member[i], |
|
ranges ? &ranges[i] : NULL); |
|
} |
|
} |
|
|
|
static |
|
void flecs_set_member_ranges(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsMemberRanges *ranges = ecs_field(it, EcsMemberRanges, 1); |
|
EcsMember *member = ecs_table_get_id(world, it->table, |
|
ecs_id(EcsMember), it->offset); |
|
if (!member) { |
|
return; |
|
} |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); |
|
if (!parent) { |
|
ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); |
|
continue; |
|
} |
|
|
|
flecs_add_member_to_struct(world, parent, e, &member[i], |
|
&ranges[i]); |
|
} |
|
} |
|
|
|
static |
|
void flecs_add_enum(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]; |
|
|
|
if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { |
|
continue; |
|
} |
|
|
|
ecs_add_id(world, e, EcsExclusive); |
|
ecs_add_id(world, e, EcsOneOf); |
|
ecs_add_id(world, e, EcsTag); |
|
} |
|
} |
|
|
|
static |
|
void flecs_add_bitmask(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]; |
|
|
|
if (init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_add_constant(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]; |
|
ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); |
|
if (!parent) { |
|
ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); |
|
continue; |
|
} |
|
|
|
if (ecs_has(world, parent, EcsEnum)) { |
|
flecs_add_constant_to_enum(world, parent, e, it->event_id); |
|
} else if (ecs_has(world, parent, EcsBitmask)) { |
|
flecs_add_constant_to_bitmask(world, parent, e, it->event_id); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_set_array(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsArray *array = ecs_field(it, EcsArray, 1); |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_entity_t elem_type = array[i].type; |
|
int32_t elem_count = array[i].count; |
|
|
|
if (!elem_type) { |
|
ecs_err("array '%s' has no element type", ecs_get_name(world, e)); |
|
continue; |
|
} |
|
|
|
if (!elem_count) { |
|
ecs_err("array '%s' has size 0", ecs_get_name(world, e)); |
|
continue; |
|
} |
|
|
|
const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); |
|
if (flecs_init_type(world, e, EcsArrayType, |
|
elem_ptr->size * elem_count, elem_ptr->alignment)) |
|
{ |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_set_vector(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsVector *array = ecs_field(it, EcsVector, 1); |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_entity_t elem_type = array[i].type; |
|
|
|
if (!elem_type) { |
|
ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); |
|
continue; |
|
} |
|
|
|
if (init_type_t(world, e, EcsVectorType, ecs_vec_t)) { |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_set_custom_type(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsOpaque *serialize = ecs_field(it, EcsOpaque, 1); |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_entity_t elem_type = serialize[i].as_type; |
|
|
|
if (!elem_type) { |
|
ecs_err("custom type '%s' has no mapping type", ecs_get_name(world, e)); |
|
continue; |
|
} |
|
|
|
const EcsComponent *comp = ecs_get(world, e, EcsComponent); |
|
if (!comp || !comp->size || !comp->alignment) { |
|
ecs_err("custom type '%s' has no size/alignment, register as component first", |
|
ecs_get_name(world, e)); |
|
continue; |
|
} |
|
|
|
if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
bool flecs_unit_validate( |
|
ecs_world_t *world, |
|
ecs_entity_t t, |
|
EcsUnit *data) |
|
{ |
|
char *derived_symbol = NULL; |
|
const char *symbol = data->symbol; |
|
|
|
ecs_entity_t base = data->base; |
|
ecs_entity_t over = data->over; |
|
ecs_entity_t prefix = data->prefix; |
|
ecs_unit_translation_t translation = data->translation; |
|
|
|
if (base) { |
|
if (!ecs_has(world, base, EcsUnit)) { |
|
ecs_err("entity '%s' for unit '%s' used as base is not a unit", |
|
ecs_get_name(world, base), ecs_get_name(world, t)); |
|
goto error; |
|
} |
|
} |
|
|
|
if (over) { |
|
if (!base) { |
|
ecs_err("invalid unit '%s': cannot specify over without base", |
|
ecs_get_name(world, t)); |
|
goto error; |
|
} |
|
if (!ecs_has(world, over, EcsUnit)) { |
|
ecs_err("entity '%s' for unit '%s' used as over is not a unit", |
|
ecs_get_name(world, over), ecs_get_name(world, t)); |
|
goto error; |
|
} |
|
} |
|
|
|
if (prefix) { |
|
if (!base) { |
|
ecs_err("invalid unit '%s': cannot specify prefix without base", |
|
ecs_get_name(world, t)); |
|
goto error; |
|
} |
|
const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); |
|
if (!prefix_ptr) { |
|
ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", |
|
ecs_get_name(world, over), ecs_get_name(world, t)); |
|
goto error; |
|
} |
|
|
|
if (translation.factor || translation.power) { |
|
if (prefix_ptr->translation.factor != translation.factor || |
|
prefix_ptr->translation.power != translation.power) |
|
{ |
|
ecs_err( |
|
"factor for unit '%s' is inconsistent with prefix '%s'", |
|
ecs_get_name(world, t), ecs_get_name(world, prefix)); |
|
goto error; |
|
} |
|
} else { |
|
translation = prefix_ptr->translation; |
|
} |
|
} |
|
|
|
if (base) { |
|
bool must_match = false; /* Must base symbol match symbol? */ |
|
ecs_strbuf_t sbuf = ECS_STRBUF_INIT; |
|
if (prefix) { |
|
const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (ptr->symbol) { |
|
ecs_strbuf_appendstr(&sbuf, ptr->symbol); |
|
must_match = true; |
|
} |
|
} |
|
|
|
const EcsUnit *uptr = ecs_get(world, base, EcsUnit); |
|
ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (uptr->symbol) { |
|
ecs_strbuf_appendstr(&sbuf, uptr->symbol); |
|
} |
|
|
|
if (over) { |
|
uptr = ecs_get(world, over, EcsUnit); |
|
ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (uptr->symbol) { |
|
ecs_strbuf_appendch(&sbuf, '/'); |
|
ecs_strbuf_appendstr(&sbuf, uptr->symbol); |
|
must_match = true; |
|
} |
|
} |
|
|
|
derived_symbol = ecs_strbuf_get(&sbuf); |
|
if (derived_symbol && !ecs_os_strlen(derived_symbol)) { |
|
ecs_os_free(derived_symbol); |
|
derived_symbol = NULL; |
|
} |
|
|
|
if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { |
|
if (must_match) { |
|
ecs_err("symbol '%s' for unit '%s' does not match base" |
|
" symbol '%s'", symbol, |
|
ecs_get_name(world, t), derived_symbol); |
|
goto error; |
|
} |
|
} |
|
if (!symbol && derived_symbol && (prefix || over)) { |
|
ecs_os_free(data->symbol); |
|
data->symbol = derived_symbol; |
|
} else { |
|
ecs_os_free(derived_symbol); |
|
} |
|
} |
|
|
|
data->base = base; |
|
data->over = over; |
|
data->prefix = prefix; |
|
data->translation = translation; |
|
|
|
return true; |
|
error: |
|
ecs_os_free(derived_symbol); |
|
return false; |
|
} |
|
|
|
static |
|
void flecs_set_unit(ecs_iter_t *it) { |
|
EcsUnit *u = ecs_field(it, EcsUnit, 1); |
|
|
|
ecs_world_t *world = it->world; |
|
|
|
int i, count = it->count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
flecs_unit_validate(world, e, &u[i]); |
|
} |
|
} |
|
|
|
static |
|
void flecs_unit_quantity_monitor(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
|
|
int i, count = it->count; |
|
if (it->event == EcsOnAdd) { |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_add_pair(world, e, EcsQuantity, e); |
|
} |
|
} else { |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = it->entities[i]; |
|
ecs_remove_pair(world, e, EcsQuantity, e); |
|
} |
|
} |
|
} |
|
|
|
static |
|
void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { |
|
ecs_world_t *world = it->world; |
|
EcsMetaType *type = ecs_field(it, EcsMetaType, 1); |
|
|
|
int i; |
|
for (i = 0; i < it->count; i ++) { |
|
/* If a component is defined from reflection data, configure it with the |
|
* default constructor. This ensures that a new component value does not |
|
* contain uninitialized memory, which could cause serializers to crash |
|
* when for example inspecting string fields. */ |
|
if (!type->existing) { |
|
ecs_entity_t e = it->entities[i]; |
|
const ecs_type_info_t *ti = ecs_get_type_info(world, e); |
|
if (!ti || !ti->hooks.ctor) { |
|
ecs_set_hooks_id(world, e, |
|
&(ecs_type_hooks_t){ |
|
.ctor = ecs_default_ctor |
|
}); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_member_on_set(ecs_iter_t *it) { |
|
EcsMember *mbr = it->ptrs[0]; |
|
if (!mbr->count) { |
|
mbr->count = 1; |
|
} |
|
} |
|
|
|
void FlecsMetaImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsMeta); |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
flecs_bootstrap_component(world, EcsMetaType); |
|
flecs_bootstrap_component(world, EcsMetaTypeSerialized); |
|
flecs_bootstrap_component(world, EcsPrimitive); |
|
flecs_bootstrap_component(world, EcsEnum); |
|
flecs_bootstrap_component(world, EcsBitmask); |
|
flecs_bootstrap_component(world, EcsMember); |
|
flecs_bootstrap_component(world, EcsMemberRanges); |
|
flecs_bootstrap_component(world, EcsStruct); |
|
flecs_bootstrap_component(world, EcsArray); |
|
flecs_bootstrap_component(world, EcsVector); |
|
flecs_bootstrap_component(world, EcsOpaque); |
|
flecs_bootstrap_component(world, EcsUnit); |
|
flecs_bootstrap_component(world, EcsUnitPrefix); |
|
|
|
flecs_bootstrap_tag(world, EcsConstant); |
|
flecs_bootstrap_tag(world, EcsQuantity); |
|
|
|
ecs_set_hooks(world, EcsMetaType, { .ctor = ecs_default_ctor }); |
|
|
|
ecs_set_hooks(world, EcsMetaTypeSerialized, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsMetaTypeSerialized), |
|
.copy = ecs_copy(EcsMetaTypeSerialized), |
|
.dtor = ecs_dtor(EcsMetaTypeSerialized) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsStruct, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsStruct), |
|
.copy = ecs_copy(EcsStruct), |
|
.dtor = ecs_dtor(EcsStruct) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsMember, { |
|
.ctor = ecs_default_ctor, |
|
.on_set = flecs_member_on_set |
|
}); |
|
|
|
ecs_set_hooks(world, EcsMemberRanges, { |
|
.ctor = ecs_default_ctor |
|
}); |
|
|
|
ecs_set_hooks(world, EcsEnum, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsEnum), |
|
.copy = ecs_copy(EcsEnum), |
|
.dtor = ecs_dtor(EcsEnum) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsBitmask, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsBitmask), |
|
.copy = ecs_copy(EcsBitmask), |
|
.dtor = ecs_dtor(EcsBitmask) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsUnit, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsUnit), |
|
.copy = ecs_copy(EcsUnit), |
|
.dtor = ecs_dtor(EcsUnit) |
|
}); |
|
|
|
ecs_set_hooks(world, EcsUnitPrefix, { |
|
.ctor = ecs_default_ctor, |
|
.move = ecs_move(EcsUnitPrefix), |
|
.copy = ecs_copy(EcsUnitPrefix), |
|
.dtor = ecs_dtor(EcsUnitPrefix) |
|
}); |
|
|
|
/* Register triggers to finalize type information from component data */ |
|
ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ |
|
world, EcsFlecsInternals); |
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = flecs_set_primitive |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = flecs_set_member |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsMemberRanges), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = flecs_set_member_ranges |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_add_enum |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_add_bitmask |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, |
|
.events = {EcsOnAdd}, |
|
.callback = flecs_add_constant |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = flecs_add_constant |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = flecs_set_array |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = flecs_set_vector |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsOpaque), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = flecs_set_custom_type |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = flecs_set_unit |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = ecs_meta_type_serialized_init |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, |
|
.events = {EcsOnSet}, |
|
.callback = ecs_meta_type_init_default_ctor |
|
}); |
|
|
|
ecs_observer(world, { |
|
.filter.terms = { |
|
{ .id = ecs_id(EcsUnit) }, |
|
{ .id = EcsQuantity } |
|
}, |
|
.events = { EcsMonitor }, |
|
.callback = flecs_unit_quantity_monitor |
|
}); |
|
ecs_set_scope(world, old_scope); |
|
|
|
/* Initialize primitive types */ |
|
#define ECS_PRIMITIVE(world, type, primitive_kind)\ |
|
ecs_entity_init(world, &(ecs_entity_desc_t){\ |
|
.id = ecs_id(ecs_##type##_t),\ |
|
.name = #type,\ |
|
.symbol = #type });\ |
|
ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ |
|
.kind = primitive_kind\ |
|
}); |
|
|
|
ECS_PRIMITIVE(world, bool, EcsBool); |
|
ECS_PRIMITIVE(world, char, EcsChar); |
|
ECS_PRIMITIVE(world, byte, EcsByte); |
|
ECS_PRIMITIVE(world, u8, EcsU8); |
|
ECS_PRIMITIVE(world, u16, EcsU16); |
|
ECS_PRIMITIVE(world, u32, EcsU32); |
|
ECS_PRIMITIVE(world, u64, EcsU64); |
|
ECS_PRIMITIVE(world, uptr, EcsUPtr); |
|
ECS_PRIMITIVE(world, i8, EcsI8); |
|
ECS_PRIMITIVE(world, i16, EcsI16); |
|
ECS_PRIMITIVE(world, i32, EcsI32); |
|
ECS_PRIMITIVE(world, i64, EcsI64); |
|
ECS_PRIMITIVE(world, iptr, EcsIPtr); |
|
ECS_PRIMITIVE(world, f32, EcsF32); |
|
ECS_PRIMITIVE(world, f64, EcsF64); |
|
ECS_PRIMITIVE(world, string, EcsString); |
|
ECS_PRIMITIVE(world, entity, EcsEntity); |
|
ECS_PRIMITIVE(world, id, EcsId); |
|
|
|
#undef ECS_PRIMITIVE |
|
|
|
ecs_set_hooks(world, ecs_string_t, { |
|
.ctor = ecs_default_ctor, |
|
.copy = ecs_copy(ecs_string_t), |
|
.move = ecs_move(ecs_string_t), |
|
.dtor = ecs_dtor(ecs_string_t) |
|
}); |
|
|
|
/* Set default child components */ |
|
ecs_add_pair(world, ecs_id(EcsStruct), |
|
EcsDefaultChildComponent, ecs_id(EcsMember)); |
|
|
|
ecs_add_pair(world, ecs_id(EcsMember), |
|
EcsDefaultChildComponent, ecs_id(EcsMember)); |
|
|
|
ecs_add_pair(world, ecs_id(EcsEnum), |
|
EcsDefaultChildComponent, EcsConstant); |
|
|
|
ecs_add_pair(world, ecs_id(EcsBitmask), |
|
EcsDefaultChildComponent, EcsConstant); |
|
|
|
/* Relationship properties */ |
|
ecs_add_id(world, EcsQuantity, EcsExclusive); |
|
ecs_add_id(world, EcsQuantity, EcsTag); |
|
|
|
/* Initialize reflection data for meta components */ |
|
ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ |
|
.entity = ecs_entity(world, { .name = "TypeKind" }), |
|
.constants = { |
|
{ .name = "PrimitiveType" }, |
|
{ .name = "BitmaskType" }, |
|
{ .name = "EnumType" }, |
|
{ .name = "StructType" }, |
|
{ .name = "ArrayType" }, |
|
{ .name = "VectorType" }, |
|
{ .name = "OpaqueType" } |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsMetaType), |
|
.members = { |
|
{ .name = "kind", .type = type_kind } |
|
} |
|
}); |
|
|
|
ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ |
|
.entity = ecs_entity(world, { .name = "PrimitiveKind" }), |
|
.constants = { |
|
{ .name = "Bool", 1 }, |
|
{ .name = "Char" }, |
|
{ .name = "Byte" }, |
|
{ .name = "U8" }, |
|
{ .name = "U16" }, |
|
{ .name = "U32" }, |
|
{ .name = "U64 "}, |
|
{ .name = "I8" }, |
|
{ .name = "I16" }, |
|
{ .name = "I32" }, |
|
{ .name = "I64" }, |
|
{ .name = "F32" }, |
|
{ .name = "F64" }, |
|
{ .name = "UPtr "}, |
|
{ .name = "IPtr" }, |
|
{ .name = "String" }, |
|
{ .name = "Entity" }, |
|
{ .name = "Id" } |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsPrimitive), |
|
.members = { |
|
{ .name = "kind", .type = primitive_kind } |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsMember), |
|
.members = { |
|
{ .name = "type", .type = ecs_id(ecs_entity_t) }, |
|
{ .name = "count", .type = ecs_id(ecs_i32_t) }, |
|
{ .name = "unit", .type = ecs_id(ecs_entity_t) }, |
|
{ .name = "offset", .type = ecs_id(ecs_i32_t) } |
|
} |
|
}); |
|
|
|
ecs_entity_t vr = ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_entity(world, { .name = "value_range" }), |
|
.members = { |
|
{ .name = "min", .type = ecs_id(ecs_f64_t) }, |
|
{ .name = "max", .type = ecs_id(ecs_f64_t) } |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsMemberRanges), |
|
.members = { |
|
{ .name = "value", .type = vr }, |
|
{ .name = "warning", .type = vr }, |
|
{ .name = "error", .type = vr } |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsArray), |
|
.members = { |
|
{ .name = "type", .type = ecs_id(ecs_entity_t) }, |
|
{ .name = "count", .type = ecs_id(ecs_i32_t) }, |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsVector), |
|
.members = { |
|
{ .name = "type", .type = ecs_id(ecs_entity_t) } |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsOpaque), |
|
.members = { |
|
{ .name = "as_type", .type = ecs_id(ecs_entity_t) } |
|
} |
|
}); |
|
|
|
ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_entity(world, { .name = "unit_translation" }), |
|
.members = { |
|
{ .name = "factor", .type = ecs_id(ecs_i32_t) }, |
|
{ .name = "power", .type = ecs_id(ecs_i32_t) } |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsUnit), |
|
.members = { |
|
{ .name = "symbol", .type = ecs_id(ecs_string_t) }, |
|
{ .name = "prefix", .type = ecs_id(ecs_entity_t) }, |
|
{ .name = "base", .type = ecs_id(ecs_entity_t) }, |
|
{ .name = "over", .type = ecs_id(ecs_entity_t) }, |
|
{ .name = "translation", .type = ut } |
|
} |
|
}); |
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){ |
|
.entity = ecs_id(EcsUnitPrefix), |
|
.members = { |
|
{ .name = "symbol", .type = ecs_id(ecs_string_t) }, |
|
{ .name = "translation", .type = ut } |
|
} |
|
}); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file meta/serialized.c |
|
* @brief Serialize type into flat operations array to speed up deserialization. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_META |
|
|
|
static |
|
int flecs_meta_serialize_type( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops); |
|
|
|
ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) { |
|
return EcsOpPrimitive + kind; |
|
} |
|
|
|
static |
|
ecs_size_t flecs_meta_type_size(ecs_world_t *world, ecs_entity_t type) { |
|
const EcsComponent *comp = ecs_get(world, type, EcsComponent); |
|
ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return comp->size; |
|
} |
|
|
|
static |
|
ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t kind) { |
|
ecs_meta_type_op_t *op = ecs_vec_append_t(NULL, ops, ecs_meta_type_op_t); |
|
op->kind = kind; |
|
op->offset = 0; |
|
op->count = 1; |
|
op->op_count = 1; |
|
op->size = 0; |
|
op->name = NULL; |
|
op->members = NULL; |
|
op->type = 0; |
|
op->member_index = 0; |
|
return op; |
|
} |
|
|
|
static |
|
ecs_meta_type_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) { |
|
ecs_meta_type_op_t* op = ecs_vec_get_t(ops, ecs_meta_type_op_t, index); |
|
ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return op; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_primitive( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops) |
|
{ |
|
const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); |
|
if (!ptr) { |
|
char *name = ecs_get_fullpath(world, type); |
|
ecs_err("entity '%s' is not a primitive type", name); |
|
ecs_os_free(name); |
|
return -1; |
|
} |
|
|
|
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, flecs_meta_primitive_to_op_kind(ptr->kind)); |
|
op->offset = offset; |
|
op->type = type; |
|
op->size = flecs_meta_type_size(world, type); |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_enum( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops) |
|
{ |
|
(void)world; |
|
|
|
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum); |
|
op->offset = offset; |
|
op->type = type; |
|
op->size = ECS_SIZEOF(ecs_i32_t); |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_bitmask( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops) |
|
{ |
|
(void)world; |
|
|
|
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask); |
|
op->offset = offset; |
|
op->type = type; |
|
op->size = ECS_SIZEOF(ecs_u32_t); |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_array( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops) |
|
{ |
|
(void)world; |
|
|
|
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpArray); |
|
op->offset = offset; |
|
op->type = type; |
|
op->size = flecs_meta_type_size(world, type); |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_array_component( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_vec_t *ops) |
|
{ |
|
const EcsArray *ptr = ecs_get(world, type, EcsArray); |
|
if (!ptr) { |
|
return -1; /* Should never happen, will trigger internal error */ |
|
} |
|
|
|
if (flecs_meta_serialize_type(world, ptr->type, 0, ops) != 0) { |
|
return -1; |
|
} |
|
|
|
ecs_meta_type_op_t *first = ecs_vec_first(ops); |
|
first->count = ptr->count; |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_vector( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops) |
|
{ |
|
(void)world; |
|
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpVector); |
|
op->offset = offset; |
|
op->type = type; |
|
op->size = flecs_meta_type_size(world, type); |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_custom_type( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops) |
|
{ |
|
(void)world; |
|
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpOpaque); |
|
op->offset = offset; |
|
op->type = type; |
|
op->size = flecs_meta_type_size(world, type); |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_struct( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops) |
|
{ |
|
const EcsStruct *ptr = ecs_get(world, type, EcsStruct); |
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t cur, first = ecs_vec_count(ops); |
|
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpPush); |
|
op->offset = offset; |
|
op->type = type; |
|
op->size = flecs_meta_type_size(world, type); |
|
|
|
ecs_member_t *members = ecs_vec_first(&ptr->members); |
|
int32_t i, count = ecs_vec_count(&ptr->members); |
|
|
|
ecs_hashmap_t *member_index = NULL; |
|
if (count) { |
|
op->members = member_index = flecs_name_index_new( |
|
world, &world->allocator); |
|
} |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_member_t *member = &members[i]; |
|
|
|
cur = ecs_vec_count(ops); |
|
if (flecs_meta_serialize_type(world, member->type, offset + member->offset, ops) != 0) { |
|
continue; |
|
} |
|
|
|
op = flecs_meta_ops_get(ops, cur); |
|
if (!op->type) { |
|
op->type = member->type; |
|
} |
|
|
|
if (op->count <= 1) { |
|
op->count = member->count; |
|
} |
|
|
|
const char *member_name = member->name; |
|
op->name = member_name; |
|
op->op_count = ecs_vec_count(ops) - cur; |
|
op->member_index = i; |
|
|
|
flecs_name_index_ensure( |
|
member_index, flecs_ito(uint64_t, cur - first - 1), |
|
member_name, 0, 0); |
|
} |
|
|
|
flecs_meta_ops_add(ops, EcsOpPop); |
|
flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first; |
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_type( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_size_t offset, |
|
ecs_vec_t *ops) |
|
{ |
|
const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); |
|
if (!ptr) { |
|
char *path = ecs_get_fullpath(world, type); |
|
ecs_err("missing EcsMetaType for type %s'", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
switch(ptr->kind) { |
|
case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops); |
|
case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops); |
|
case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops); |
|
case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops); |
|
case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops); |
|
case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops); |
|
case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_meta_serialize_component( |
|
ecs_world_t *world, |
|
ecs_entity_t type, |
|
ecs_vec_t *ops) |
|
{ |
|
const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); |
|
if (!ptr) { |
|
char *path = ecs_get_fullpath(world, type); |
|
ecs_err("missing EcsMetaType for type %s'", path); |
|
ecs_os_free(path); |
|
return -1; |
|
} |
|
|
|
if (ptr->kind == EcsArrayType) { |
|
return flecs_meta_serialize_array_component(world, type, ops); |
|
} else { |
|
return flecs_meta_serialize_type(world, type, 0, ops); |
|
} |
|
} |
|
|
|
void ecs_meta_type_serialized_init( |
|
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]; |
|
ecs_vec_t ops; |
|
ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0); |
|
flecs_meta_serialize_component(world, e, &ops); |
|
|
|
EcsMetaTypeSerialized *ptr = ecs_get_mut( |
|
world, e, EcsMetaTypeSerialized); |
|
if (ptr->ops.array) { |
|
ecs_meta_dtor_serialized(ptr); |
|
} |
|
|
|
ptr->ops = ops; |
|
} |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/os_api_impl/os_api_impl.c |
|
* @brief Builtin implementation for OS API. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_OS_API_IMPL |
|
#ifdef ECS_TARGET_WINDOWS |
|
/** |
|
* @file addons/os_api_impl/posix_impl.inl |
|
* @brief Builtin Windows implementation for OS API. |
|
*/ |
|
|
|
#ifndef WIN32_LEAN_AND_MEAN |
|
#define WIN32_LEAN_AND_MEAN |
|
#endif |
|
#include <winsock2.h> |
|
#include <windows.h> |
|
|
|
typedef struct ecs_win_thread_t { |
|
HANDLE thread; |
|
ecs_os_thread_callback_t callback; |
|
void *arg; |
|
} ecs_win_thread_t; |
|
|
|
static |
|
DWORD flecs_win_thread(void *ptr) { |
|
ecs_win_thread_t *thread = ptr; |
|
thread->callback(thread->arg); |
|
return 0; |
|
} |
|
|
|
static |
|
ecs_os_thread_t win_thread_new( |
|
ecs_os_thread_callback_t callback, |
|
void *arg) |
|
{ |
|
ecs_win_thread_t *thread = ecs_os_malloc_t(ecs_win_thread_t); |
|
thread->arg= arg; |
|
thread->callback = callback; |
|
thread->thread = CreateThread( |
|
NULL, 0, (LPTHREAD_START_ROUTINE)flecs_win_thread, thread, 0, NULL); |
|
return (ecs_os_thread_t)(uintptr_t)thread; |
|
} |
|
|
|
static |
|
void* win_thread_join( |
|
ecs_os_thread_t thr) |
|
{ |
|
ecs_win_thread_t *thread = (ecs_win_thread_t*)(uintptr_t)thr; |
|
DWORD r = WaitForSingleObject(thread->thread, INFINITE); |
|
if (r == WAIT_FAILED) { |
|
ecs_err("win_thread_join: WaitForSingleObject failed"); |
|
} |
|
ecs_os_free(thread); |
|
return NULL; |
|
} |
|
|
|
static |
|
ecs_os_thread_id_t win_thread_self(void) |
|
{ |
|
return (ecs_os_thread_id_t)GetCurrentThreadId(); |
|
} |
|
|
|
static |
|
int32_t win_ainc( |
|
int32_t *count) |
|
{ |
|
return InterlockedIncrement((volatile long*)count); |
|
} |
|
|
|
static |
|
int32_t win_adec( |
|
int32_t *count) |
|
{ |
|
return InterlockedDecrement((volatile long*)count); |
|
} |
|
|
|
static |
|
int64_t win_lainc( |
|
int64_t *count) |
|
{ |
|
return InterlockedIncrement64(count); |
|
} |
|
|
|
static |
|
int64_t win_ladec( |
|
int64_t *count) |
|
{ |
|
return InterlockedDecrement64(count); |
|
} |
|
|
|
static |
|
ecs_os_mutex_t win_mutex_new(void) { |
|
CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); |
|
InitializeCriticalSection(mutex); |
|
return (ecs_os_mutex_t)(uintptr_t)mutex; |
|
} |
|
|
|
static |
|
void win_mutex_free( |
|
ecs_os_mutex_t m) |
|
{ |
|
CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; |
|
DeleteCriticalSection(mutex); |
|
ecs_os_free(mutex); |
|
} |
|
|
|
static |
|
void win_mutex_lock( |
|
ecs_os_mutex_t m) |
|
{ |
|
CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; |
|
EnterCriticalSection(mutex); |
|
} |
|
|
|
static |
|
void win_mutex_unlock( |
|
ecs_os_mutex_t m) |
|
{ |
|
CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; |
|
LeaveCriticalSection(mutex); |
|
} |
|
|
|
static |
|
ecs_os_cond_t win_cond_new(void) { |
|
CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); |
|
InitializeConditionVariable(cond); |
|
return (ecs_os_cond_t)(uintptr_t)cond; |
|
} |
|
|
|
static |
|
void win_cond_free( |
|
ecs_os_cond_t c) |
|
{ |
|
(void)c; |
|
} |
|
|
|
static |
|
void win_cond_signal( |
|
ecs_os_cond_t c) |
|
{ |
|
CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; |
|
WakeConditionVariable(cond); |
|
} |
|
|
|
static |
|
void win_cond_broadcast( |
|
ecs_os_cond_t c) |
|
{ |
|
CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; |
|
WakeAllConditionVariable(cond); |
|
} |
|
|
|
static |
|
void win_cond_wait( |
|
ecs_os_cond_t c, |
|
ecs_os_mutex_t m) |
|
{ |
|
CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; |
|
CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; |
|
SleepConditionVariableCS(cond, mutex, INFINITE); |
|
} |
|
|
|
static bool win_time_initialized; |
|
static double win_time_freq; |
|
static LARGE_INTEGER win_time_start; |
|
static ULONG win_current_resolution; |
|
|
|
static |
|
void win_time_setup(void) { |
|
if ( win_time_initialized) { |
|
return; |
|
} |
|
|
|
win_time_initialized = true; |
|
|
|
LARGE_INTEGER freq; |
|
QueryPerformanceFrequency(&freq); |
|
QueryPerformanceCounter(&win_time_start); |
|
win_time_freq = (double)freq.QuadPart / 1000000000.0; |
|
} |
|
|
|
static |
|
void win_sleep( |
|
int32_t sec, |
|
int32_t nanosec) |
|
{ |
|
HANDLE timer; |
|
LARGE_INTEGER ft; |
|
|
|
ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); |
|
|
|
timer = CreateWaitableTimer(NULL, TRUE, NULL); |
|
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); |
|
WaitForSingleObject(timer, INFINITE); |
|
CloseHandle(timer); |
|
} |
|
|
|
static |
|
void win_enable_high_timer_resolution(bool enable) |
|
{ |
|
HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); |
|
if (!hntdll) { |
|
return; |
|
} |
|
|
|
union { |
|
LONG (__stdcall *f)( |
|
ULONG desired, BOOLEAN set, ULONG * current); |
|
FARPROC p; |
|
} func; |
|
|
|
func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); |
|
if(!func.p) { |
|
return; |
|
} |
|
|
|
ULONG current, resolution = 10000; /* 1 ms */ |
|
|
|
if (!enable && win_current_resolution) { |
|
func.f(win_current_resolution, 0, ¤t); |
|
win_current_resolution = 0; |
|
return; |
|
} else if (!enable) { |
|
return; |
|
} |
|
|
|
if (resolution == win_current_resolution) { |
|
return; |
|
} |
|
|
|
if (win_current_resolution) { |
|
func.f(win_current_resolution, 0, ¤t); |
|
} |
|
|
|
if (func.f(resolution, 1, ¤t)) { |
|
/* Try setting a lower resolution */ |
|
resolution *= 2; |
|
if(func.f(resolution, 1, ¤t)) return; |
|
} |
|
|
|
win_current_resolution = resolution; |
|
} |
|
|
|
static |
|
uint64_t win_time_now(void) { |
|
uint64_t now; |
|
|
|
LARGE_INTEGER qpc_t; |
|
QueryPerformanceCounter(&qpc_t); |
|
now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); |
|
|
|
return now; |
|
} |
|
|
|
static |
|
void win_fini(void) { |
|
if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { |
|
win_enable_high_timer_resolution(false); |
|
} |
|
} |
|
|
|
void ecs_set_os_api_impl(void) { |
|
ecs_os_set_api_defaults(); |
|
|
|
ecs_os_api_t api = ecs_os_api; |
|
|
|
api.thread_new_ = win_thread_new; |
|
api.thread_join_ = win_thread_join; |
|
api.thread_self_ = win_thread_self; |
|
api.task_new_ = win_thread_new; |
|
api.task_join_ = win_thread_join; |
|
api.ainc_ = win_ainc; |
|
api.adec_ = win_adec; |
|
api.lainc_ = win_lainc; |
|
api.ladec_ = win_ladec; |
|
api.mutex_new_ = win_mutex_new; |
|
api.mutex_free_ = win_mutex_free; |
|
api.mutex_lock_ = win_mutex_lock; |
|
api.mutex_unlock_ = win_mutex_unlock; |
|
api.cond_new_ = win_cond_new; |
|
api.cond_free_ = win_cond_free; |
|
api.cond_signal_ = win_cond_signal; |
|
api.cond_broadcast_ = win_cond_broadcast; |
|
api.cond_wait_ = win_cond_wait; |
|
api.sleep_ = win_sleep; |
|
api.now_ = win_time_now; |
|
api.fini_ = win_fini; |
|
|
|
win_time_setup(); |
|
|
|
if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { |
|
win_enable_high_timer_resolution(true); |
|
} |
|
|
|
ecs_os_set_api(&api); |
|
} |
|
|
|
#else |
|
/** |
|
* @file addons/os_api_impl/posix_impl.inl |
|
* @brief Builtin POSIX implementation for OS API. |
|
*/ |
|
|
|
#include "pthread.h" |
|
|
|
#if defined(__APPLE__) && defined(__MACH__) |
|
#include <mach/mach_time.h> |
|
#elif defined(__EMSCRIPTEN__) |
|
#include <emscripten.h> |
|
#else |
|
#include <time.h> |
|
#endif |
|
|
|
/* This mutex is used to emulate atomic operations when the gnu builtins are |
|
* not supported. This is probably not very fast but if the compiler doesn't |
|
* support the gnu built-ins, then speed is probably not a priority. */ |
|
#ifndef __GNUC__ |
|
static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; |
|
#endif |
|
|
|
static |
|
ecs_os_thread_t posix_thread_new( |
|
ecs_os_thread_callback_t callback, |
|
void *arg) |
|
{ |
|
pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); |
|
|
|
if (pthread_create (thread, NULL, callback, arg) != 0) { |
|
ecs_os_abort(); |
|
} |
|
|
|
return (ecs_os_thread_t)(uintptr_t)thread; |
|
} |
|
|
|
static |
|
void* posix_thread_join( |
|
ecs_os_thread_t thread) |
|
{ |
|
void *arg; |
|
pthread_t *thr = (pthread_t*)(uintptr_t)thread; |
|
pthread_join(*thr, &arg); |
|
ecs_os_free(thr); |
|
return arg; |
|
} |
|
|
|
static |
|
ecs_os_thread_id_t posix_thread_self(void) |
|
{ |
|
return (ecs_os_thread_id_t)pthread_self(); |
|
} |
|
|
|
static |
|
int32_t posix_ainc( |
|
int32_t *count) |
|
{ |
|
int value; |
|
#ifdef __GNUC__ |
|
value = __sync_add_and_fetch (count, 1); |
|
return value; |
|
#else |
|
if (pthread_mutex_lock(&atomic_mutex)) { |
|
abort(); |
|
} |
|
value = (*count) += 1; |
|
if (pthread_mutex_unlock(&atomic_mutex)) { |
|
abort(); |
|
} |
|
return value; |
|
#endif |
|
} |
|
|
|
static |
|
int32_t posix_adec( |
|
int32_t *count) |
|
{ |
|
int32_t value; |
|
#ifdef __GNUC__ |
|
value = __sync_sub_and_fetch (count, 1); |
|
return value; |
|
#else |
|
if (pthread_mutex_lock(&atomic_mutex)) { |
|
abort(); |
|
} |
|
value = (*count) -= 1; |
|
if (pthread_mutex_unlock(&atomic_mutex)) { |
|
abort(); |
|
} |
|
return value; |
|
#endif |
|
} |
|
|
|
static |
|
int64_t posix_lainc( |
|
int64_t *count) |
|
{ |
|
int64_t value; |
|
#ifdef __GNUC__ |
|
value = __sync_add_and_fetch (count, 1); |
|
return value; |
|
#else |
|
if (pthread_mutex_lock(&atomic_mutex)) { |
|
abort(); |
|
} |
|
value = (*count) += 1; |
|
if (pthread_mutex_unlock(&atomic_mutex)) { |
|
abort(); |
|
} |
|
return value; |
|
#endif |
|
} |
|
|
|
static |
|
int64_t posix_ladec( |
|
int64_t *count) |
|
{ |
|
int64_t value; |
|
#ifdef __GNUC__ |
|
value = __sync_sub_and_fetch (count, 1); |
|
return value; |
|
#else |
|
if (pthread_mutex_lock(&atomic_mutex)) { |
|
abort(); |
|
} |
|
value = (*count) -= 1; |
|
if (pthread_mutex_unlock(&atomic_mutex)) { |
|
abort(); |
|
} |
|
return value; |
|
#endif |
|
} |
|
|
|
static |
|
ecs_os_mutex_t posix_mutex_new(void) { |
|
pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); |
|
if (pthread_mutex_init(mutex, NULL)) { |
|
abort(); |
|
} |
|
return (ecs_os_mutex_t)(uintptr_t)mutex; |
|
} |
|
|
|
static |
|
void posix_mutex_free( |
|
ecs_os_mutex_t m) |
|
{ |
|
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; |
|
pthread_mutex_destroy(mutex); |
|
ecs_os_free(mutex); |
|
} |
|
|
|
static |
|
void posix_mutex_lock( |
|
ecs_os_mutex_t m) |
|
{ |
|
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; |
|
if (pthread_mutex_lock(mutex)) { |
|
abort(); |
|
} |
|
} |
|
|
|
static |
|
void posix_mutex_unlock( |
|
ecs_os_mutex_t m) |
|
{ |
|
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; |
|
if (pthread_mutex_unlock(mutex)) { |
|
abort(); |
|
} |
|
} |
|
|
|
static |
|
ecs_os_cond_t posix_cond_new(void) { |
|
pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); |
|
if (pthread_cond_init(cond, NULL)) { |
|
abort(); |
|
} |
|
return (ecs_os_cond_t)(uintptr_t)cond; |
|
} |
|
|
|
static |
|
void posix_cond_free( |
|
ecs_os_cond_t c) |
|
{ |
|
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; |
|
if (pthread_cond_destroy(cond)) { |
|
abort(); |
|
} |
|
ecs_os_free(cond); |
|
} |
|
|
|
static |
|
void posix_cond_signal( |
|
ecs_os_cond_t c) |
|
{ |
|
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; |
|
if (pthread_cond_signal(cond)) { |
|
abort(); |
|
} |
|
} |
|
|
|
static |
|
void posix_cond_broadcast( |
|
ecs_os_cond_t c) |
|
{ |
|
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; |
|
if (pthread_cond_broadcast(cond)) { |
|
abort(); |
|
} |
|
} |
|
|
|
static |
|
void posix_cond_wait( |
|
ecs_os_cond_t c, |
|
ecs_os_mutex_t m) |
|
{ |
|
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; |
|
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; |
|
if (pthread_cond_wait(cond, mutex)) { |
|
abort(); |
|
} |
|
} |
|
|
|
static bool posix_time_initialized; |
|
|
|
#if defined(__APPLE__) && defined(__MACH__) |
|
static mach_timebase_info_data_t posix_osx_timebase; |
|
static uint64_t posix_time_start; |
|
#else |
|
static uint64_t posix_time_start; |
|
#endif |
|
|
|
static |
|
void posix_time_setup(void) { |
|
if (posix_time_initialized) { |
|
return; |
|
} |
|
|
|
posix_time_initialized = true; |
|
|
|
#if defined(__APPLE__) && defined(__MACH__) |
|
mach_timebase_info(&posix_osx_timebase); |
|
posix_time_start = mach_absolute_time(); |
|
#else |
|
struct timespec ts; |
|
clock_gettime(CLOCK_MONOTONIC, &ts); |
|
posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; |
|
#endif |
|
} |
|
|
|
static |
|
void posix_sleep( |
|
int32_t sec, |
|
int32_t nanosec) |
|
{ |
|
struct timespec sleepTime; |
|
ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
sleepTime.tv_sec = sec; |
|
sleepTime.tv_nsec = nanosec; |
|
if (nanosleep(&sleepTime, NULL)) { |
|
ecs_err("nanosleep failed"); |
|
} |
|
} |
|
|
|
/* prevent 64-bit overflow when computing relative timestamp |
|
see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 |
|
*/ |
|
#if defined(ECS_TARGET_DARWIN) |
|
static |
|
int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { |
|
int64_t q = value / denom; |
|
int64_t r = value % denom; |
|
return q * numer + r * numer / denom; |
|
} |
|
#endif |
|
|
|
static |
|
uint64_t posix_time_now(void) { |
|
ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
uint64_t now; |
|
|
|
#if defined(ECS_TARGET_DARWIN) |
|
now = (uint64_t) posix_int64_muldiv( |
|
(int64_t)mach_absolute_time(), |
|
(int64_t)posix_osx_timebase.numer, |
|
(int64_t)posix_osx_timebase.denom); |
|
#elif defined(__EMSCRIPTEN__) |
|
now = (long long)(emscripten_get_now() * 1000.0 * 1000); |
|
#else |
|
struct timespec ts; |
|
clock_gettime(CLOCK_MONOTONIC, &ts); |
|
now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); |
|
#endif |
|
|
|
return now; |
|
} |
|
|
|
void ecs_set_os_api_impl(void) { |
|
ecs_os_set_api_defaults(); |
|
|
|
ecs_os_api_t api = ecs_os_api; |
|
|
|
api.thread_new_ = posix_thread_new; |
|
api.thread_join_ = posix_thread_join; |
|
api.thread_self_ = posix_thread_self; |
|
api.task_new_ = posix_thread_new; |
|
api.task_join_ = posix_thread_join; |
|
api.ainc_ = posix_ainc; |
|
api.adec_ = posix_adec; |
|
api.lainc_ = posix_lainc; |
|
api.ladec_ = posix_ladec; |
|
api.mutex_new_ = posix_mutex_new; |
|
api.mutex_free_ = posix_mutex_free; |
|
api.mutex_lock_ = posix_mutex_lock; |
|
api.mutex_unlock_ = posix_mutex_unlock; |
|
api.cond_new_ = posix_cond_new; |
|
api.cond_free_ = posix_cond_free; |
|
api.cond_signal_ = posix_cond_signal; |
|
api.cond_broadcast_ = posix_cond_broadcast; |
|
api.cond_wait_ = posix_cond_wait; |
|
api.sleep_ = posix_sleep; |
|
api.now_ = posix_time_now; |
|
|
|
posix_time_setup(); |
|
|
|
ecs_os_set_api(&api); |
|
} |
|
|
|
#endif |
|
#endif |
|
|
|
/** |
|
* @file addons/ipeline/pipeline.c |
|
* @brief Functions for building and running pipelines. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_PIPELINE |
|
|
|
static void flecs_pipeline_free( |
|
ecs_pipeline_state_t *p) |
|
{ |
|
if (p) { |
|
ecs_world_t *world = p->query->filter.world; |
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_vec_fini_t(a, &p->ops, ecs_pipeline_op_t); |
|
ecs_vec_fini_t(a, &p->systems, ecs_entity_t); |
|
ecs_os_free(p->iters); |
|
ecs_query_fini(p->query); |
|
ecs_os_free(p); |
|
} |
|
} |
|
|
|
static ECS_MOVE(EcsPipeline, dst, src, { |
|
flecs_pipeline_free(dst->state); |
|
dst->state = src->state; |
|
src->state = NULL; |
|
}) |
|
|
|
static ECS_DTOR(EcsPipeline, ptr, { |
|
flecs_pipeline_free(ptr->state); |
|
}) |
|
|
|
typedef enum ecs_write_kind_t { |
|
WriteStateNone = 0, |
|
WriteStateToStage, |
|
} ecs_write_kind_t; |
|
|
|
typedef struct ecs_write_state_t { |
|
bool write_barrier; |
|
ecs_map_t ids; |
|
ecs_map_t wildcard_ids; |
|
} ecs_write_state_t; |
|
|
|
static |
|
ecs_write_kind_t flecs_pipeline_get_write_state( |
|
ecs_write_state_t *write_state, |
|
ecs_id_t id) |
|
{ |
|
ecs_write_kind_t result = WriteStateNone; |
|
|
|
if (write_state->write_barrier) { |
|
/* Any component could have been written */ |
|
return WriteStateToStage; |
|
} |
|
|
|
if (id == EcsWildcard) { |
|
/* Using a wildcard for id indicates read barrier. Return true if any |
|
* components could have been staged */ |
|
if (ecs_map_count(&write_state->ids) || |
|
ecs_map_count(&write_state->wildcard_ids)) |
|
{ |
|
return WriteStateToStage; |
|
} |
|
} |
|
|
|
if (!ecs_id_is_wildcard(id)) { |
|
if (ecs_map_get(&write_state->ids, id)) { |
|
result = WriteStateToStage; |
|
} |
|
} else { |
|
ecs_map_iter_t it = ecs_map_iter(&write_state->ids); |
|
while (ecs_map_next(&it)) { |
|
if (ecs_id_match(ecs_map_key(&it), id)) { |
|
return WriteStateToStage; |
|
} |
|
} |
|
} |
|
|
|
if (ecs_map_count(&write_state->wildcard_ids)) { |
|
ecs_map_iter_t it = ecs_map_iter(&write_state->wildcard_ids); |
|
while (ecs_map_next(&it)) { |
|
if (ecs_id_match(id, ecs_map_key(&it))) { |
|
return WriteStateToStage; |
|
} |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static |
|
void flecs_pipeline_set_write_state( |
|
ecs_write_state_t *write_state, |
|
ecs_id_t id) |
|
{ |
|
if (id == EcsWildcard) { |
|
/* If writing to wildcard, flag all components as written */ |
|
write_state->write_barrier = true; |
|
return; |
|
} |
|
|
|
ecs_map_t *ids; |
|
if (ecs_id_is_wildcard(id)) { |
|
ids = &write_state->wildcard_ids; |
|
} else { |
|
ids = &write_state->ids; |
|
} |
|
|
|
ecs_map_ensure(ids, id)[0] = true; |
|
} |
|
|
|
static |
|
void flecs_pipeline_reset_write_state( |
|
ecs_write_state_t *write_state) |
|
{ |
|
ecs_map_clear(&write_state->ids); |
|
ecs_map_clear(&write_state->wildcard_ids); |
|
write_state->write_barrier = false; |
|
} |
|
|
|
static |
|
bool flecs_pipeline_check_term( |
|
ecs_world_t *world, |
|
ecs_term_t *term, |
|
bool is_active, |
|
ecs_write_state_t *write_state) |
|
{ |
|
(void)world; |
|
|
|
ecs_term_id_t *src = &term->src; |
|
if (src->flags & EcsInOutNone) { |
|
return false; |
|
} |
|
|
|
ecs_id_t id = term->id; |
|
ecs_oper_kind_t oper = term->oper; |
|
ecs_inout_kind_t inout = term->inout; |
|
bool from_any = ecs_term_match_0(term); |
|
bool from_this = ecs_term_match_this(term); |
|
bool is_shared = !from_any && (!from_this || !(src->flags & EcsSelf)); |
|
|
|
ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id); |
|
|
|
if (from_this && ws >= WriteStateToStage) { |
|
/* A staged write could have happened for an id that's matched on the |
|
* main storage. Even if the id isn't read, still insert a merge so that |
|
* a write to the main storage after the staged write doesn't get |
|
* overwritten. */ |
|
return true; |
|
} |
|
|
|
if (inout == EcsInOutDefault) { |
|
if (from_any) { |
|
/* If no inout kind is specified for terms without a source, this is |
|
* not interpreted as a read/write annotation but just a (component) |
|
* id that's passed to a system. */ |
|
return false; |
|
} else if (is_shared) { |
|
inout = EcsIn; |
|
} else { |
|
/* Default for owned terms is InOut */ |
|
inout = EcsInOut; |
|
} |
|
} |
|
|
|
if (oper == EcsNot && inout == EcsOut) { |
|
/* If a Not term is combined with Out, it signals that the system |
|
* intends to add a component that the entity doesn't yet have */ |
|
from_any = true; |
|
} |
|
|
|
if (from_any) { |
|
switch(inout) { |
|
case EcsOut: |
|
case EcsInOut: |
|
if (is_active) { |
|
/* Only flag component as written if system is active */ |
|
flecs_pipeline_set_write_state(write_state, id); |
|
} |
|
break; |
|
case EcsInOutDefault: |
|
case EcsInOutNone: |
|
case EcsIn: |
|
break; |
|
} |
|
|
|
switch(inout) { |
|
case EcsIn: |
|
case EcsInOut: |
|
if (ws == WriteStateToStage) { |
|
/* If a system does a get/get_mut, the component is fetched from |
|
* the main store so it must be merged first */ |
|
return true; |
|
} |
|
/* fall through */ |
|
case EcsInOutDefault: |
|
case EcsInOutNone: |
|
case EcsOut: |
|
break; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_pipeline_check_terms( |
|
ecs_world_t *world, |
|
ecs_filter_t *filter, |
|
bool is_active, |
|
ecs_write_state_t *ws) |
|
{ |
|
bool needs_merge = false; |
|
ecs_term_t *terms = filter->terms; |
|
int32_t t, term_count = filter->term_count; |
|
|
|
/* Check This terms first. This way if a term indicating writing to a stage |
|
* was added before the term, it won't cause merging. */ |
|
for (t = 0; t < term_count; t ++) { |
|
ecs_term_t *term = &terms[t]; |
|
if (ecs_term_match_this(term)) { |
|
needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); |
|
} |
|
} |
|
|
|
/* Now check staged terms */ |
|
for (t = 0; t < term_count; t ++) { |
|
ecs_term_t *term = &terms[t]; |
|
if (!ecs_term_match_this(term)) { |
|
needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); |
|
} |
|
} |
|
|
|
return needs_merge; |
|
} |
|
|
|
static |
|
EcsPoly* flecs_pipeline_term_system( |
|
ecs_iter_t *it) |
|
{ |
|
int32_t index = ecs_table_get_column_index( |
|
it->real_world, it->table, ecs_poly_id(EcsSystem)); |
|
ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); |
|
EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); |
|
ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); |
|
return poly; |
|
} |
|
|
|
static |
|
bool flecs_pipeline_build( |
|
ecs_world_t *world, |
|
ecs_pipeline_state_t *pq) |
|
{ |
|
ecs_iter_t it = ecs_query_iter(world, pq->query); |
|
|
|
if (pq->match_count == pq->query->match_count) { |
|
/* No need to rebuild the pipeline */ |
|
ecs_iter_fini(&it); |
|
return false; |
|
} |
|
|
|
world->info.pipeline_build_count_total ++; |
|
pq->rebuild_count ++; |
|
|
|
ecs_allocator_t *a = &world->allocator; |
|
ecs_pipeline_op_t *op = NULL; |
|
ecs_write_state_t ws = {0}; |
|
ecs_map_init(&ws.ids, a); |
|
ecs_map_init(&ws.wildcard_ids, a); |
|
|
|
ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t); |
|
ecs_vec_reset_t(a, &pq->systems, ecs_entity_t); |
|
|
|
bool multi_threaded = false; |
|
bool no_readonly = false; |
|
bool first = true; |
|
|
|
/* Iterate systems in pipeline, add ops for running / merging */ |
|
while (ecs_query_next(&it)) { |
|
EcsPoly *poly = flecs_pipeline_term_system(&it); |
|
bool is_active = ecs_table_get_type_index(world, it.table, EcsEmpty) == -1; |
|
|
|
int32_t i; |
|
for (i = 0; i < it.count; i ++) { |
|
ecs_poly_assert(poly[i].poly, ecs_system_t); |
|
ecs_system_t *sys = (ecs_system_t*)poly[i].poly; |
|
ecs_query_t *q = sys->query; |
|
|
|
bool needs_merge = false; |
|
needs_merge = flecs_pipeline_check_terms( |
|
world, &q->filter, is_active, &ws); |
|
|
|
if (is_active) { |
|
if (first) { |
|
multi_threaded = sys->multi_threaded; |
|
no_readonly = sys->no_readonly; |
|
first = false; |
|
} |
|
|
|
if (sys->multi_threaded != multi_threaded) { |
|
needs_merge = true; |
|
multi_threaded = sys->multi_threaded; |
|
} |
|
if (sys->no_readonly != no_readonly) { |
|
needs_merge = true; |
|
no_readonly = sys->no_readonly; |
|
} |
|
} |
|
|
|
if (no_readonly) { |
|
needs_merge = true; |
|
} |
|
|
|
if (needs_merge) { |
|
/* After merge all components will be merged, so reset state */ |
|
flecs_pipeline_reset_write_state(&ws); |
|
|
|
/* An inactive system can insert a merge if one of its |
|
* components got written, which could make the system |
|
* active. If this is the only system in the pipeline operation, |
|
* it results in an empty operation when we get here. If that's |
|
* the case, reuse the empty operation for the next op. */ |
|
if (op && op->count) { |
|
op = NULL; |
|
} |
|
|
|
/* Re-evaluate columns to set write flags if system is active. |
|
* If system is inactive, it can't write anything and so it |
|
* should not insert unnecessary merges. */ |
|
needs_merge = false; |
|
if (is_active) { |
|
needs_merge = flecs_pipeline_check_terms( |
|
world, &q->filter, true, &ws); |
|
} |
|
|
|
/* The component states were just reset, so if we conclude that |
|
* another merge is needed something is wrong. */ |
|
ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
if (!op) { |
|
op = ecs_vec_append_t(a, &pq->ops, ecs_pipeline_op_t); |
|
op->offset = ecs_vec_count(&pq->systems); |
|
op->count = 0; |
|
op->multi_threaded = false; |
|
op->no_readonly = false; |
|
op->time_spent = 0; |
|
op->commands_enqueued = 0; |
|
} |
|
|
|
/* Don't increase count for inactive systems, as they are ignored by |
|
* the query used to run the pipeline. */ |
|
if (is_active) { |
|
ecs_vec_append_t(a, &pq->systems, ecs_entity_t)[0] = |
|
it.entities[i]; |
|
if (!op->count) { |
|
op->multi_threaded = multi_threaded; |
|
op->no_readonly = no_readonly; |
|
} |
|
op->count ++; |
|
} |
|
} |
|
} |
|
|
|
if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { |
|
ecs_vec_remove_last(&pq->ops); |
|
} |
|
|
|
ecs_map_fini(&ws.ids); |
|
ecs_map_fini(&ws.wildcard_ids); |
|
|
|
op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); |
|
|
|
if (!op) { |
|
ecs_dbg("#[green]pipeline#[reset] is empty"); |
|
return true; |
|
} else { |
|
/* Add schedule to debug tracing */ |
|
ecs_dbg("#[bold]pipeline rebuild"); |
|
ecs_log_push_1(); |
|
|
|
ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", |
|
op->multi_threaded, !op->no_readonly); |
|
ecs_log_push_1(); |
|
|
|
int32_t i, count = ecs_vec_count(&pq->systems); |
|
int32_t op_index = 0, ran_since_merge = 0; |
|
ecs_entity_t *systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t system = systems[i]; |
|
const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); |
|
ecs_poly_assert(poly->poly, ecs_system_t); |
|
ecs_system_t *sys = (ecs_system_t*)poly->poly; |
|
|
|
#ifdef FLECS_LOG_1 |
|
char *path = ecs_get_fullpath(world, system); |
|
const char *doc_name = NULL; |
|
#ifdef FLECS_DOC |
|
const EcsDocDescription *doc_name_id = ecs_get_pair(world, system, |
|
EcsDocDescription, EcsName); |
|
if (doc_name_id) { |
|
doc_name = doc_name_id->value; |
|
} |
|
#endif |
|
if (doc_name) { |
|
ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); |
|
} else { |
|
ecs_dbg("#[green]system#[reset] %s", path); |
|
} |
|
ecs_os_free(path); |
|
#endif |
|
|
|
ecs_assert(op[op_index].offset + ran_since_merge == i, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
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_vec_count(&pq->ops)) { |
|
ecs_dbg( |
|
"#[green]schedule#[reset]: " |
|
"threading: %d, staging: %d:", |
|
op[op_index].multi_threaded, |
|
!op[op_index].no_readonly); |
|
} |
|
ecs_log_push_1(); |
|
} |
|
|
|
if (sys->last_frame == (world->info.frame_count_total + 1)) { |
|
if (op_index < ecs_vec_count(&pq->ops)) { |
|
pq->cur_op = &op[op_index]; |
|
pq->cur_i = i; |
|
} else { |
|
pq->cur_op = NULL; |
|
pq->cur_i = 0; |
|
} |
|
} |
|
} |
|
|
|
ecs_log_pop_1(); |
|
ecs_log_pop_1(); |
|
} |
|
|
|
pq->match_count = pq->query->match_count; |
|
|
|
ecs_assert(pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
void flecs_pipeline_next_system( |
|
ecs_pipeline_state_t *pq) |
|
{ |
|
if (!pq->cur_op) { |
|
return; |
|
} |
|
|
|
pq->cur_i ++; |
|
if (pq->cur_i >= (pq->cur_op->offset + pq->cur_op->count)) { |
|
pq->cur_op ++; |
|
if (pq->cur_op > ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)) { |
|
pq->cur_op = NULL; |
|
} |
|
} |
|
} |
|
|
|
bool flecs_pipeline_update( |
|
ecs_world_t *world, |
|
ecs_pipeline_state_t *pq, |
|
bool start_of_frame) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); |
|
|
|
/* If any entity mutations happened that could have affected query matching |
|
* notify appropriate queries so caches are up to date. This includes the |
|
* pipeline query. */ |
|
if (start_of_frame) { |
|
ecs_run_aperiodic(world, 0); |
|
} |
|
|
|
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
bool rebuilt = flecs_pipeline_build(world, pq); |
|
if (start_of_frame) { |
|
/* Initialize iterators */ |
|
int32_t i, count = pq->iter_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_world_t *stage = ecs_get_stage(world, i); |
|
pq->iters[i] = ecs_query_iter(stage, pq->query); |
|
} |
|
pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); |
|
pq->cur_i = 0; |
|
} else { |
|
flecs_pipeline_next_system(pq); |
|
} |
|
|
|
return rebuilt; |
|
} |
|
|
|
void ecs_run_pipeline( |
|
ecs_world_t *world, |
|
ecs_entity_t pipeline, |
|
ecs_ftime_t delta_time) |
|
{ |
|
if (!pipeline) { |
|
pipeline = world->pipeline; |
|
} |
|
|
|
/* create any worker task threads request */ |
|
if (ecs_using_task_threads(world)) { |
|
flecs_create_worker_threads(world); |
|
} |
|
|
|
EcsPipeline *p = |
|
ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); |
|
flecs_workers_progress(world, p->state, delta_time); |
|
|
|
if (ecs_using_task_threads(world)) { |
|
/* task threads were temporary and may now be joined */ |
|
flecs_join_worker_threads(world); |
|
} |
|
} |
|
|
|
int32_t flecs_run_pipeline_ops( |
|
ecs_world_t* world, |
|
ecs_stage_t* stage, |
|
int32_t stage_index, |
|
int32_t stage_count, |
|
ecs_ftime_t delta_time) |
|
{ |
|
ecs_pipeline_state_t* pq = world->pq; |
|
ecs_pipeline_op_t* op = pq->cur_op; |
|
int32_t i = pq->cur_i; |
|
|
|
ecs_assert(!stage_index || op->multi_threaded, ECS_INTERNAL_ERROR, NULL); |
|
|
|
int32_t count = ecs_vec_count(&pq->systems); |
|
ecs_entity_t* systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); |
|
int32_t ran_since_merge = i - op->offset; |
|
|
|
for (; i < count; i++) { |
|
ecs_entity_t system = systems[i]; |
|
const EcsPoly* poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); |
|
ecs_poly_assert(poly->poly, ecs_system_t); |
|
ecs_system_t* sys = (ecs_system_t*)poly->poly; |
|
|
|
/* Keep track of the last frame for which the system has ran, so we |
|
* know from where to resume the schedule in case the schedule |
|
* changes during a merge. */ |
|
sys->last_frame = world->info.frame_count_total + 1; |
|
|
|
ecs_stage_t* s = NULL; |
|
if (!op->no_readonly) { |
|
/* If system is no_readonly it operates on the actual world, not |
|
* the stage. Only pass stage to system if it's readonly. */ |
|
s = stage; |
|
} |
|
|
|
ecs_run_intern(world, s, system, sys, stage_index, |
|
stage_count, delta_time, 0, 0, NULL); |
|
|
|
world->info.systems_ran_frame++; |
|
ran_since_merge++; |
|
|
|
if (ran_since_merge == op->count) { |
|
/* Merge */ |
|
break; |
|
} |
|
} |
|
|
|
return i; |
|
} |
|
|
|
void flecs_run_pipeline( |
|
ecs_world_t *world, |
|
ecs_pipeline_state_t *pq, |
|
ecs_ftime_t delta_time) |
|
{ |
|
ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); |
|
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_poly_assert(world, ecs_stage_t); |
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); |
|
int32_t stage_count = ecs_get_stage_count(world); |
|
|
|
ecs_assert(!stage_index, ECS_INVALID_OPERATION, NULL); |
|
|
|
bool multi_threaded = ecs_get_stage_count(world) > 1; |
|
|
|
// Update the pipeline the workers will execute |
|
world->pq = pq; |
|
|
|
// Update the pipeline before waking the workers. |
|
flecs_pipeline_update(world, pq, true); |
|
|
|
// If there are no operations to execute in the pipeline bail early, |
|
// no need to wake the workers since they have nothing to do. |
|
while (pq->cur_op != NULL) { |
|
if (pq->cur_i == ecs_vec_count(&pq->systems)) { |
|
flecs_pipeline_update(world, pq, false); |
|
continue; |
|
} |
|
|
|
bool no_readonly = pq->cur_op->no_readonly; |
|
bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded; |
|
|
|
pq->no_readonly = no_readonly; |
|
|
|
if (!no_readonly) { |
|
ecs_readonly_begin(world); |
|
} |
|
|
|
ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded); |
|
ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (op_multi_threaded) { |
|
flecs_signal_workers(world); |
|
} |
|
|
|
ecs_time_t st = { 0 }; |
|
bool measure_time = world->flags & EcsWorldMeasureSystemTime; |
|
if (measure_time) { |
|
ecs_time_measure(&st); |
|
} |
|
|
|
const int32_t i = flecs_run_pipeline_ops( |
|
world, stage, stage_index, stage_count, delta_time); |
|
|
|
if (measure_time) { |
|
/* Don't include merge time in system time */ |
|
world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); |
|
} |
|
|
|
if (op_multi_threaded) { |
|
flecs_wait_for_sync(world); |
|
} |
|
|
|
if (!no_readonly) { |
|
ecs_time_t mt = { 0 }; |
|
if (measure_time) { |
|
ecs_time_measure(&mt); |
|
} |
|
|
|
int32_t si; |
|
for (si = 0; si < stage_count; si ++) { |
|
ecs_stage_t *s = &world->stages[si]; |
|
pq->cur_op->commands_enqueued += ecs_vec_count(&s->cmd->queue); |
|
} |
|
|
|
ecs_readonly_end(world); |
|
if (measure_time) { |
|
pq->cur_op->time_spent += ecs_time_measure(&mt); |
|
} |
|
} |
|
|
|
/* Store the current state of the schedule after we synchronized the |
|
* threads, to avoid race conditions. */ |
|
pq->cur_i = i; |
|
|
|
flecs_pipeline_update(world, pq, false); |
|
} |
|
} |
|
|
|
static |
|
void flecs_run_startup_systems( |
|
ecs_world_t *world) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, |
|
ecs_dependson(EcsOnStart)); |
|
if (!idr || !flecs_table_cache_count(&idr->cache)) { |
|
/* Don't bother creating startup pipeline if no systems exist */ |
|
return; |
|
} |
|
|
|
ecs_dbg_2("#[bold]startup#[reset]"); |
|
ecs_log_push_2(); |
|
int32_t stage_count = world->stage_count; |
|
world->stage_count = 1; /* Prevents running startup systems on workers */ |
|
|
|
/* Creating a pipeline is relatively expensive, but this only happens |
|
* for the first frame. The startup pipeline is deleted afterwards, which |
|
* eliminates the overhead of keeping its query cache in sync. */ |
|
ecs_dbg_2("#[bold]create startup pipeline#[reset]"); |
|
ecs_log_push_2(); |
|
ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ |
|
.query = { |
|
.filter.terms = { |
|
{ .id = EcsSystem }, |
|
{ .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, |
|
{ .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn }, |
|
{ .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, |
|
{ .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } |
|
}, |
|
.order_by = flecs_entity_compare |
|
} |
|
}); |
|
ecs_log_pop_2(); |
|
|
|
/* Run & delete pipeline */ |
|
ecs_dbg_2("#[bold]run startup systems#[reset]"); |
|
ecs_log_push_2(); |
|
ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL); |
|
const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline); |
|
ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); |
|
flecs_workers_progress(world, p->state, 0); |
|
ecs_log_pop_2(); |
|
|
|
ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); |
|
ecs_log_push_2(); |
|
ecs_delete(world, start_pip); |
|
ecs_log_pop_2(); |
|
|
|
world->stage_count = stage_count; |
|
ecs_log_pop_2(); |
|
|
|
error: |
|
return; |
|
} |
|
|
|
bool ecs_progress( |
|
ecs_world_t *world, |
|
ecs_ftime_t user_delta_time) |
|
{ |
|
ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); |
|
|
|
/* If this is the first frame, run startup systems */ |
|
if (world->info.frame_count_total == 0) { |
|
flecs_run_startup_systems(world); |
|
} |
|
|
|
/* create any worker task threads request */ |
|
if (ecs_using_task_threads(world)) { |
|
flecs_create_worker_threads(world); |
|
} |
|
|
|
ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); |
|
ecs_log_push_3(); |
|
const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); |
|
ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); |
|
flecs_workers_progress(world, p->state, delta_time); |
|
ecs_log_pop_3(); |
|
|
|
ecs_frame_end(world); |
|
|
|
if (ecs_using_task_threads(world)) { |
|
/* task threads were temporary and may now be joined */ |
|
flecs_join_worker_threads(world); |
|
} |
|
|
|
return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); |
|
error: |
|
return false; |
|
} |
|
|
|
void ecs_set_time_scale( |
|
ecs_world_t *world, |
|
ecs_ftime_t scale) |
|
{ |
|
world->info.time_scale = scale; |
|
} |
|
|
|
void ecs_reset_clock( |
|
ecs_world_t *world) |
|
{ |
|
world->info.world_time_total = 0; |
|
world->info.world_time_total_raw = 0; |
|
} |
|
|
|
void ecs_set_pipeline( |
|
ecs_world_t *world, |
|
ecs_entity_t pipeline) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, |
|
ECS_INVALID_PARAMETER, "not a pipeline"); |
|
|
|
world->pipeline = pipeline; |
|
error: |
|
return; |
|
} |
|
|
|
ecs_entity_t ecs_get_pipeline( |
|
const ecs_world_t *world) |
|
{ |
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); |
|
world = ecs_get_world(world); |
|
return world->pipeline; |
|
error: |
|
return 0; |
|
} |
|
|
|
ecs_entity_t ecs_pipeline_init( |
|
ecs_world_t *world, |
|
const ecs_pipeline_desc_t *desc) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_entity_t result = desc->entity; |
|
if (!result) { |
|
result = ecs_new(world, 0); |
|
} |
|
|
|
ecs_query_desc_t qd = desc->query; |
|
if (!qd.order_by) { |
|
qd.order_by = flecs_entity_compare; |
|
} |
|
qd.filter.entity = result; |
|
|
|
ecs_query_t *query = ecs_query_init(world, &qd); |
|
if (!query) { |
|
ecs_delete(world, result); |
|
return 0; |
|
} |
|
|
|
ecs_check(query->filter.terms != NULL, ECS_INVALID_PARAMETER, |
|
"pipeline query cannot be empty"); |
|
ecs_check(query->filter.terms[0].id == EcsSystem, |
|
ECS_INVALID_PARAMETER, "pipeline must start with System term"); |
|
|
|
ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t); |
|
pq->query = query; |
|
pq->match_count = -1; |
|
pq->idr_inactive = flecs_id_record_ensure(world, EcsEmpty); |
|
ecs_set(world, result, EcsPipeline, { pq }); |
|
|
|
return result; |
|
error: |
|
return 0; |
|
} |
|
|
|
/* -- Module implementation -- */ |
|
|
|
static |
|
void FlecsPipelineFini( |
|
ecs_world_t *world, |
|
void *ctx) |
|
{ |
|
(void)ctx; |
|
if (ecs_get_stage_count(world)) { |
|
ecs_set_threads(world, 0); |
|
} |
|
|
|
ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
#define flecs_bootstrap_phase(world, phase, depends_on)\ |
|
flecs_bootstrap_tag(world, phase);\ |
|
flecs_bootstrap_phase_(world, phase, depends_on) |
|
static |
|
void flecs_bootstrap_phase_( |
|
ecs_world_t *world, |
|
ecs_entity_t phase, |
|
ecs_entity_t depends_on) |
|
{ |
|
ecs_add_id(world, phase, EcsPhase); |
|
if (depends_on) { |
|
ecs_add_pair(world, phase, EcsDependsOn, depends_on); |
|
} |
|
} |
|
|
|
void FlecsPipelineImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsPipeline); |
|
ECS_IMPORT(world, FlecsSystem); |
|
#ifdef FLECS_COREDOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
ecs_doc_set_brief(world, ecs_id(FlecsPipeline), |
|
"Module that schedules and runs systems"); |
|
#endif |
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
flecs_bootstrap_component(world, EcsPipeline); |
|
flecs_bootstrap_tag(world, EcsPhase); |
|
|
|
/* Create anonymous phases to which the builtin phases will have DependsOn |
|
* relationships. This ensures that, for example, EcsOnUpdate doesn't have a |
|
* direct DependsOn relationship on EcsPreUpdate, which ensures that when |
|
* the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ |
|
ecs_entity_t phase_0 = ecs_new(world, 0); |
|
ecs_entity_t phase_1 = ecs_new_w_pair(world, EcsDependsOn, phase_0); |
|
ecs_entity_t phase_2 = ecs_new_w_pair(world, EcsDependsOn, phase_1); |
|
ecs_entity_t phase_3 = ecs_new_w_pair(world, EcsDependsOn, phase_2); |
|
ecs_entity_t phase_4 = ecs_new_w_pair(world, EcsDependsOn, phase_3); |
|
ecs_entity_t phase_5 = ecs_new_w_pair(world, EcsDependsOn, phase_4); |
|
ecs_entity_t phase_6 = ecs_new_w_pair(world, EcsDependsOn, phase_5); |
|
ecs_entity_t phase_7 = ecs_new_w_pair(world, EcsDependsOn, phase_6); |
|
ecs_entity_t phase_8 = ecs_new_w_pair(world, EcsDependsOn, phase_7); |
|
|
|
flecs_bootstrap_phase(world, EcsOnStart, 0); |
|
flecs_bootstrap_phase(world, EcsPreFrame, 0); |
|
flecs_bootstrap_phase(world, EcsOnLoad, phase_0); |
|
flecs_bootstrap_phase(world, EcsPostLoad, phase_1); |
|
flecs_bootstrap_phase(world, EcsPreUpdate, phase_2); |
|
flecs_bootstrap_phase(world, EcsOnUpdate, phase_3); |
|
flecs_bootstrap_phase(world, EcsOnValidate, phase_4); |
|
flecs_bootstrap_phase(world, EcsPostUpdate, phase_5); |
|
flecs_bootstrap_phase(world, EcsPreStore, phase_6); |
|
flecs_bootstrap_phase(world, EcsOnStore, phase_7); |
|
flecs_bootstrap_phase(world, EcsPostFrame, phase_8); |
|
|
|
ecs_set_hooks(world, EcsPipeline, { |
|
.ctor = ecs_default_ctor, |
|
.dtor = ecs_dtor(EcsPipeline), |
|
.move = ecs_move(EcsPipeline) |
|
}); |
|
|
|
world->pipeline = ecs_pipeline(world, { |
|
.entity = ecs_entity(world, { .name = "BuiltinPipeline" }), |
|
.query = { |
|
.filter.terms = { |
|
{ .id = EcsSystem }, |
|
{ .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, |
|
{ .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn, .oper = EcsNot }, |
|
{ .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, |
|
{ .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } |
|
}, |
|
.order_by = flecs_entity_compare |
|
} |
|
}); |
|
|
|
/* Cleanup thread administration when world is destroyed */ |
|
ecs_atfini(world, FlecsPipelineFini, NULL); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/pipeline/worker.c |
|
* @brief Functions for running pipelines on one or more threads. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_PIPELINE |
|
|
|
/* Synchronize workers */ |
|
static |
|
void flecs_sync_worker( |
|
ecs_world_t* world) |
|
{ |
|
int32_t stage_count = ecs_get_stage_count(world); |
|
if (stage_count <= 1) { |
|
return; |
|
} |
|
|
|
/* Signal that thread is waiting */ |
|
ecs_os_mutex_lock(world->sync_mutex); |
|
if (++world->workers_waiting == (stage_count - 1)) { |
|
/* Only signal main thread when all threads are waiting */ |
|
ecs_os_cond_signal(world->sync_cond); |
|
} |
|
|
|
/* Wait until main thread signals that thread can continue */ |
|
ecs_os_cond_wait(world->worker_cond, world->sync_mutex); |
|
ecs_os_mutex_unlock(world->sync_mutex); |
|
} |
|
|
|
/* Worker thread */ |
|
static |
|
void* flecs_worker(void *arg) { |
|
ecs_stage_t *stage = arg; |
|
ecs_world_t *world = stage->world; |
|
|
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_poly_assert(stage, ecs_stage_t); |
|
|
|
ecs_dbg_2("worker %d: start", stage->id); |
|
|
|
/* Start worker, increase counter so main thread knows how many |
|
* workers are ready */ |
|
ecs_os_mutex_lock(world->sync_mutex); |
|
world->workers_running ++; |
|
|
|
if (!(world->flags & EcsWorldQuitWorkers)) { |
|
ecs_os_cond_wait(world->worker_cond, world->sync_mutex); |
|
} |
|
|
|
ecs_os_mutex_unlock(world->sync_mutex); |
|
|
|
while (!(world->flags & EcsWorldQuitWorkers)) { |
|
ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); |
|
|
|
ecs_dbg_3("worker %d: run", stage->id); |
|
flecs_run_pipeline_ops(world, stage, stage->id, world->stage_count, |
|
world->info.delta_time); |
|
|
|
ecs_set_scope((ecs_world_t*)stage, old_scope); |
|
|
|
flecs_sync_worker(world); |
|
} |
|
|
|
ecs_dbg_2("worker %d: finalizing", stage->id); |
|
|
|
ecs_os_mutex_lock(world->sync_mutex); |
|
world->workers_running --; |
|
ecs_os_mutex_unlock(world->sync_mutex); |
|
|
|
ecs_dbg_2("worker %d: stop", stage->id); |
|
|
|
return NULL; |
|
} |
|
|
|
/* Start threads */ |
|
void flecs_create_worker_threads( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
int32_t stages = ecs_get_stage_count(world); |
|
|
|
for (int32_t i = 1; i < stages; i ++) { |
|
ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); |
|
ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_poly_assert(stage, ecs_stage_t); |
|
|
|
ecs_assert(stage->thread == 0, ECS_INTERNAL_ERROR, NULL); |
|
if (ecs_using_task_threads(world)) { |
|
/* workers are using tasks in an external task manager provided to |
|
* the OS API */ |
|
stage->thread = ecs_os_task_new(flecs_worker, stage); |
|
} else { |
|
/* workers are using long-running os threads */ |
|
stage->thread = ecs_os_thread_new(flecs_worker, stage); |
|
} |
|
ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); |
|
} |
|
} |
|
|
|
static |
|
void flecs_start_workers( |
|
ecs_world_t *world, |
|
int32_t threads) |
|
{ |
|
ecs_set_stage_count(world, threads); |
|
|
|
ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!ecs_using_task_threads(world)) { |
|
flecs_create_worker_threads(world); |
|
} |
|
} |
|
|
|
/* Wait until all workers are running */ |
|
static |
|
void flecs_wait_for_workers( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
|
|
int32_t stage_count = ecs_get_stage_count(world); |
|
if (stage_count <= 1) { |
|
return; |
|
} |
|
|
|
bool wait = true; |
|
do { |
|
ecs_os_mutex_lock(world->sync_mutex); |
|
if (world->workers_running == (stage_count - 1)) { |
|
wait = false; |
|
} |
|
ecs_os_mutex_unlock(world->sync_mutex); |
|
} while (wait); |
|
} |
|
|
|
/* Wait until all threads are waiting on sync point */ |
|
void flecs_wait_for_sync( |
|
ecs_world_t *world) |
|
{ |
|
int32_t stage_count = ecs_get_stage_count(world); |
|
if (stage_count <= 1) { |
|
return; |
|
} |
|
|
|
ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); |
|
|
|
ecs_os_mutex_lock(world->sync_mutex); |
|
if (world->workers_waiting != (stage_count - 1)) { |
|
ecs_os_cond_wait(world->sync_cond, world->sync_mutex); |
|
} |
|
|
|
/* We shouldn't have been signalled unless all workers are waiting on sync */ |
|
ecs_assert(world->workers_waiting == (stage_count - 1), |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
world->workers_waiting = 0; |
|
ecs_os_mutex_unlock(world->sync_mutex); |
|
|
|
ecs_dbg_3("#[bold]pipeline: workers synced"); |
|
} |
|
|
|
/* Signal workers that they can start/resume work */ |
|
void flecs_signal_workers( |
|
ecs_world_t *world) |
|
{ |
|
int32_t stage_count = ecs_get_stage_count(world); |
|
if (stage_count <= 1) { |
|
return; |
|
} |
|
|
|
ecs_dbg_3("#[bold]pipeline: signal workers"); |
|
ecs_os_mutex_lock(world->sync_mutex); |
|
ecs_os_cond_broadcast(world->worker_cond); |
|
ecs_os_mutex_unlock(world->sync_mutex); |
|
} |
|
|
|
void flecs_join_worker_threads( |
|
ecs_world_t *world) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
bool threads_active = false; |
|
|
|
/* Test if threads are created. Cannot use workers_running, since this is |
|
* a potential race if threads haven't spun up yet. */ |
|
ecs_stage_t *stages = world->stages; |
|
int i, count = world->stage_count; |
|
for (i = 1; i < count; i ++) { |
|
ecs_stage_t *stage = &stages[i]; |
|
if (stage->thread) { |
|
threads_active = true; |
|
break; |
|
} |
|
}; |
|
|
|
/* If no threads are active, just return */ |
|
if (!threads_active) { |
|
return; |
|
} |
|
|
|
/* Make sure all threads are running, to ensure they catch the signal */ |
|
flecs_wait_for_workers(world); |
|
|
|
/* Signal threads should quit */ |
|
world->flags |= EcsWorldQuitWorkers; |
|
flecs_signal_workers(world); |
|
|
|
/* Join all threads with main */ |
|
for (i = 1; i < count; i ++) { |
|
if (ecs_using_task_threads(world)) { |
|
ecs_os_task_join(stages[i].thread); |
|
} else { |
|
ecs_os_thread_join(stages[i].thread); |
|
} |
|
stages[i].thread = 0; |
|
} |
|
|
|
world->flags &= ~EcsWorldQuitWorkers; |
|
ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
/* -- Private functions -- */ |
|
void flecs_workers_progress( |
|
ecs_world_t *world, |
|
ecs_pipeline_state_t *pq, |
|
ecs_ftime_t delta_time) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); |
|
|
|
/* Make sure workers are running and ready */ |
|
flecs_wait_for_workers(world); |
|
|
|
/* Run pipeline on main thread */ |
|
ecs_world_t *stage = ecs_get_stage(world, 0); |
|
ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); |
|
flecs_run_pipeline(stage, pq, delta_time); |
|
ecs_set_scope((ecs_world_t*)stage, old_scope); |
|
} |
|
|
|
static |
|
void flecs_set_threads_internal( |
|
ecs_world_t *world, |
|
int32_t threads, |
|
bool use_task_api) |
|
{ |
|
ecs_assert(threads <= 1 || (use_task_api |
|
? ecs_os_has_task_support() |
|
: ecs_os_has_threading()), |
|
ECS_MISSING_OS_API, NULL); |
|
|
|
int32_t stage_count = ecs_get_stage_count(world); |
|
bool worker_method_changed = (use_task_api != world->workers_use_task_api); |
|
|
|
if ((stage_count != threads) || worker_method_changed) { |
|
/* Stop existing threads */ |
|
if (stage_count > 1) { |
|
flecs_join_worker_threads(world); |
|
ecs_set_stage_count(world, 1); |
|
|
|
if (world->worker_cond) { |
|
ecs_os_cond_free(world->worker_cond); |
|
} |
|
if (world->sync_cond) { |
|
ecs_os_cond_free(world->sync_cond); |
|
} |
|
if (world->sync_mutex) { |
|
ecs_os_mutex_free(world->sync_mutex); |
|
} |
|
} |
|
|
|
world->workers_use_task_api = use_task_api; |
|
|
|
/* Start threads if number of threads > 1 */ |
|
if (threads > 1) { |
|
world->worker_cond = ecs_os_cond_new(); |
|
world->sync_cond = ecs_os_cond_new(); |
|
world->sync_mutex = ecs_os_mutex_new(); |
|
flecs_start_workers(world, threads); |
|
} |
|
} |
|
} |
|
|
|
/* -- Public functions -- */ |
|
|
|
void ecs_set_threads( |
|
ecs_world_t *world, |
|
int32_t threads) |
|
{ |
|
flecs_set_threads_internal(world, threads, false /* use thread API */); |
|
} |
|
|
|
void ecs_set_task_threads( |
|
ecs_world_t *world, |
|
int32_t task_threads) |
|
{ |
|
flecs_set_threads_internal(world, task_threads, true /* use task API */); |
|
} |
|
|
|
bool ecs_using_task_threads( |
|
ecs_world_t *world) |
|
{ |
|
return world->workers_use_task_api; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/rules/api.c |
|
* @brief User facing API for rules. |
|
*/ |
|
|
|
/** |
|
* @file addons/rules/rules.h |
|
* @brief Internal types and functions for rules addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_RULES |
|
|
|
typedef uint8_t ecs_var_id_t; |
|
typedef int16_t ecs_rule_lbl_t; |
|
typedef ecs_flags64_t ecs_write_flags_t; |
|
|
|
#define EcsRuleMaxVarCount (64) |
|
#define EcsVarNone ((ecs_var_id_t)-1) |
|
#define EcsThisName "this" |
|
|
|
/* -- Variable types -- */ |
|
typedef enum { |
|
EcsVarEntity, /* Variable that stores an entity id */ |
|
EcsVarTable, /* Variable that stores a table */ |
|
EcsVarAny /* Used when requesting either entity or table var */ |
|
} ecs_var_kind_t; |
|
|
|
typedef struct ecs_rule_var_t { |
|
int8_t kind; /* variable kind (EcsVarEntity or EcsVarTable) */ |
|
bool anonymous; /* variable is anonymous */ |
|
ecs_var_id_t id; /* variable id */ |
|
ecs_var_id_t table_id; /* id to table variable, if any */ |
|
ecs_var_id_t base_id; /* id to base entity variable, for lookups */ |
|
const char *name; /* variable name */ |
|
const char *lookup; /* Lookup string for variable */ |
|
#ifdef FLECS_DEBUG |
|
const char *label; /* for debugging */ |
|
#endif |
|
} ecs_rule_var_t; |
|
|
|
/* -- Instruction kinds -- */ |
|
typedef enum { |
|
EcsRuleAnd, /* And operator: find or match id against variable source */ |
|
EcsRuleAndId, /* And operator for fixed id (no wildcards/variables) */ |
|
EcsRuleAndAny, /* And operator with support for matching Any src/id */ |
|
EcsRuleTriv, /* Trivial search */ |
|
EcsRuleTrivData, /* Trivial search with setting data fields */ |
|
EcsRuleTrivWildcard, /* Trivial search with (exclusive) wildcard ids */ |
|
EcsRuleSelectAny, /* Dedicated instruction for _ queries where the src is unknown */ |
|
EcsRuleUp, /* Up traversal */ |
|
EcsRuleUpId, /* Up traversal for fixed id (like AndId) */ |
|
EcsRuleSelfUp, /* Self|up traversal */ |
|
EcsRuleSelfUpId, /* Self|up traversal for fixed id (like AndId) */ |
|
EcsRuleWith, /* Match id against fixed or variable source */ |
|
EcsRuleTrav, /* Support for transitive/reflexive queries */ |
|
EcsRuleIds, /* Test for existence of ids matching wildcard */ |
|
EcsRuleIdsRight, /* Find ids in use that match (R, *) wildcard */ |
|
EcsRuleIdsLeft, /* Find ids in use that match (*, T) wildcard */ |
|
EcsRuleEach, /* Iterate entities in table, populate entity variable */ |
|
EcsRuleStore, /* Store table or entity in variable */ |
|
EcsRuleReset, /* Reset value of variable to wildcard (*) */ |
|
EcsRuleOr, /* Or operator */ |
|
EcsRuleOptional, /* Optional operator */ |
|
EcsRuleIf, /* Conditional execution */ |
|
EcsRuleNot, /* Sets iterator state after term was not matched */ |
|
EcsRuleEnd, /* End of control flow block */ |
|
EcsRulePredEq, /* Test if variable is equal to, or assign to if not set */ |
|
EcsRulePredNeq, /* Test if variable is not equal to */ |
|
EcsRulePredEqName, /* Same as EcsRulePredEq but with matching by name */ |
|
EcsRulePredNeqName, /* Same as EcsRulePredNeq but with matching by name */ |
|
EcsRulePredEqMatch, /* Same as EcsRulePredEq but with fuzzy matching by name */ |
|
EcsRulePredNeqMatch, /* Same as EcsRulePredNeq but with fuzzy matching by name */ |
|
EcsRuleLookup, /* Lookup relative to variable */ |
|
EcsRuleSetVars, /* Populate it.sources from variables */ |
|
EcsRuleSetThis, /* Populate This entity variable */ |
|
EcsRuleSetFixed, /* Set fixed source entity ids */ |
|
EcsRuleSetIds, /* Set fixed (component) ids */ |
|
EcsRuleSetId, /* Set id if not set */ |
|
EcsRuleContain, /* Test if table contains entity */ |
|
EcsRulePairEq, /* Test if both elements of pair are the same */ |
|
EcsRulePopulate, /* Populate any data fields */ |
|
EcsRulePopulateSelf, /* Populate only self (owned) data fields */ |
|
EcsRuleYield, /* Yield result back to application */ |
|
EcsRuleNothing /* Must be last */ |
|
} ecs_rule_op_kind_t; |
|
|
|
/* Op flags to indicate if ecs_rule_ref_t is entity or variable */ |
|
#define EcsRuleIsEntity (1 << 0) |
|
#define EcsRuleIsVar (1 << 1) |
|
#define EcsRuleIsSelf (1 << 6) |
|
|
|
/* Op flags used to shift EcsRuleIsEntity and EcsRuleIsVar */ |
|
#define EcsRuleSrc 0 |
|
#define EcsRuleFirst 2 |
|
#define EcsRuleSecond 4 |
|
|
|
/* References to variable or entity */ |
|
typedef union { |
|
ecs_var_id_t var; |
|
ecs_entity_t entity; |
|
} ecs_rule_ref_t; |
|
|
|
/* Query instruction */ |
|
typedef struct ecs_rule_op_t { |
|
uint8_t kind; /* Instruction kind */ |
|
ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */ |
|
int8_t field_index; /* Query field corresponding with operation */ |
|
int8_t term_index; /* Query term corresponding with operation */ |
|
ecs_rule_lbl_t prev; /* Backtracking label (no data) */ |
|
ecs_rule_lbl_t next; /* Forwarding label. Must come after prev */ |
|
ecs_rule_lbl_t other; /* Misc register used for control flow */ |
|
ecs_flags16_t match_flags; /* Flags that modify matching behavior */ |
|
ecs_rule_ref_t src; |
|
ecs_rule_ref_t first; |
|
ecs_rule_ref_t second; |
|
ecs_flags64_t written; /* Bitset with variables written by op */ |
|
} ecs_rule_op_t; |
|
|
|
/* And context */ |
|
typedef struct { |
|
ecs_id_record_t *idr; |
|
ecs_table_cache_iter_t it; |
|
int16_t column; |
|
int16_t remaining; |
|
} ecs_rule_and_ctx_t; |
|
|
|
/* Down traversal cache (for resolving up queries w/unknown source) */ |
|
typedef struct { |
|
ecs_table_t *table; |
|
bool leaf; /* Table owns and inherits id (for Up queries without Self) */ |
|
} ecs_trav_down_elem_t; |
|
|
|
typedef struct { |
|
ecs_vec_t elems; /* vector<ecs_trav_down_elem_t> */ |
|
bool ready; |
|
} ecs_trav_down_t; |
|
|
|
typedef struct { |
|
ecs_entity_t src; |
|
ecs_id_t id; |
|
int32_t column; |
|
bool ready; |
|
} ecs_trav_up_t; |
|
|
|
typedef struct { |
|
ecs_map_t src; /* map<entity, trav_down_t> or map<table_id, trav_up_t> */ |
|
ecs_id_t with; |
|
ecs_flags32_t dir; |
|
} ecs_trav_up_cache_t; |
|
|
|
/* And up context */ |
|
typedef struct { |
|
ecs_rule_and_ctx_t and; |
|
ecs_table_t *table; |
|
int32_t row; |
|
int32_t end; |
|
ecs_entity_t trav; |
|
ecs_id_t with; |
|
ecs_id_t matched; |
|
ecs_id_record_t *idr_with; |
|
ecs_id_record_t *idr_trav; |
|
ecs_trav_down_t *down; |
|
int32_t cache_elem; |
|
ecs_trav_up_cache_t cache; |
|
} ecs_rule_up_ctx_t; |
|
|
|
/* Cache for storing results of upward/downward "all" traversal. This type of |
|
* traversal iterates and caches the entire tree. */ |
|
typedef struct { |
|
ecs_entity_t entity; |
|
ecs_id_record_t *idr; |
|
int32_t column; |
|
} ecs_trav_elem_t; |
|
|
|
typedef struct { |
|
ecs_id_t id; |
|
ecs_id_record_t *idr; |
|
ecs_vec_t entities; |
|
bool up; |
|
} ecs_trav_cache_t; |
|
|
|
/* Trav context */ |
|
typedef struct { |
|
ecs_rule_and_ctx_t and; |
|
int32_t index; |
|
int32_t offset; |
|
int32_t count; |
|
ecs_trav_cache_t cache; |
|
bool yield_reflexive; |
|
} ecs_rule_trav_ctx_t; |
|
|
|
/* Eq context */ |
|
typedef struct { |
|
ecs_table_range_t range; |
|
int32_t index; |
|
int16_t name_col; |
|
bool redo; |
|
} ecs_rule_eq_ctx_t; |
|
|
|
/* Each context */ |
|
typedef struct { |
|
int32_t row; |
|
} ecs_rule_each_ctx_t; |
|
|
|
/* Setthis context */ |
|
typedef struct { |
|
ecs_table_range_t range; |
|
} ecs_rule_setthis_ctx_t; |
|
|
|
/* Ids context */ |
|
typedef struct { |
|
ecs_id_record_t *cur; |
|
} ecs_rule_ids_ctx_t; |
|
|
|
/* Control flow context */ |
|
typedef struct { |
|
ecs_rule_lbl_t op_index; |
|
ecs_id_t field_id; |
|
} ecs_rule_ctrl_ctx_t; |
|
|
|
/* Trivial context */ |
|
typedef struct { |
|
ecs_table_cache_iter_t it; |
|
const ecs_table_record_t *tr; |
|
} ecs_rule_trivial_ctx_t; |
|
|
|
typedef struct ecs_rule_op_ctx_t { |
|
union { |
|
ecs_rule_and_ctx_t and; |
|
ecs_rule_up_ctx_t up; |
|
ecs_rule_trav_ctx_t trav; |
|
ecs_rule_ids_ctx_t ids; |
|
ecs_rule_eq_ctx_t eq; |
|
ecs_rule_each_ctx_t each; |
|
ecs_rule_setthis_ctx_t setthis; |
|
ecs_rule_ctrl_ctx_t ctrl; |
|
ecs_rule_trivial_ctx_t trivial; |
|
} is; |
|
} ecs_rule_op_ctx_t; |
|
|
|
typedef struct { |
|
/* Labels used for control flow */ |
|
ecs_rule_lbl_t lbl_query; /* Used to find the op that does the actual searching */ |
|
ecs_rule_lbl_t lbl_begin; |
|
ecs_rule_lbl_t lbl_cond_eval; |
|
ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */ |
|
bool in_or; /* Whether we're in an or chain */ |
|
} ecs_rule_compile_ctrlflow_t; |
|
|
|
/* Rule compiler state */ |
|
typedef struct { |
|
ecs_vec_t *ops; |
|
ecs_write_flags_t written; /* Bitmask to check which variables have been written */ |
|
ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */ |
|
|
|
/* Maintain control flow per scope */ |
|
ecs_rule_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX]; |
|
ecs_rule_compile_ctrlflow_t *cur; /* Current scope */ |
|
|
|
int32_t scope; /* Nesting level of query scopes */ |
|
ecs_flags32_t scope_is_not; /* Whether scope is prefixed with not */ |
|
} ecs_rule_compile_ctx_t; |
|
|
|
/* Rule run state */ |
|
typedef struct { |
|
uint64_t *written; /* Bitset to check which variables have been written */ |
|
ecs_rule_lbl_t op_index; /* Currently evaluated operation */ |
|
ecs_var_t *vars; /* Variable storage */ |
|
ecs_iter_t *it; /* Iterator */ |
|
ecs_rule_op_ctx_t *op_ctx; /* Operation context (stack) */ |
|
ecs_world_t *world; /* Reference to world */ |
|
const ecs_rule_t *rule; /* Reference to rule */ |
|
const ecs_rule_var_t *rule_vars; /* Reference to rule variable array */ |
|
ecs_flags32_t *source_set; /* Whether ecs_iter_t::sources is written by instruction */ |
|
ecs_rule_iter_t *rit; |
|
} ecs_rule_run_ctx_t; |
|
|
|
typedef struct { |
|
ecs_rule_var_t var; |
|
const char *name; |
|
} ecs_rule_var_cache_t; |
|
|
|
struct ecs_rule_t { |
|
ecs_header_t hdr; /* Poly header */ |
|
ecs_filter_t filter; /* Filter */ |
|
|
|
/* Variables */ |
|
ecs_rule_var_t *vars; /* Variables */ |
|
int32_t var_count; /* Number of variables */ |
|
int32_t var_pub_count; /* Number of public variables */ |
|
bool has_table_this; /* Does rule have [$this] */ |
|
ecs_hashmap_t tvar_index; /* Name index for table variables */ |
|
ecs_hashmap_t evar_index; /* Name index for entity variables */ |
|
ecs_rule_var_cache_t vars_cache; /* For trivial rules with only This variables */ |
|
char **var_names; /* Array with variable names for iterator */ |
|
|
|
ecs_var_id_t *src_vars; /* Array with ids to source variables for fields */ |
|
ecs_rule_op_t *ops; /* Operations */ |
|
int32_t op_count; /* Number of operations */ |
|
|
|
/* Mixins */ |
|
ecs_iterable_t iterable; |
|
ecs_poly_dtor_t dtor; |
|
|
|
#ifdef FLECS_DEBUG |
|
int32_t var_size; /* Used for out of bounds check during compilation */ |
|
#endif |
|
}; |
|
|
|
/* Convert integer to label */ |
|
ecs_rule_lbl_t flecs_itolbl( |
|
int64_t val); |
|
|
|
/* Get ref flags (IsEntity) or IsVar) for ref (Src, First, Second) */ |
|
ecs_flags16_t flecs_rule_ref_flags( |
|
ecs_flags16_t flags, |
|
ecs_flags16_t kind); |
|
|
|
/* Check if variable is written */ |
|
bool flecs_rule_is_written( |
|
ecs_var_id_t var_id, |
|
uint64_t written); |
|
|
|
/* Check if ref is written (calls flecs_rule_is_written)*/ |
|
bool flecs_ref_is_written( |
|
const ecs_rule_op_t *op, |
|
const ecs_rule_ref_t *ref, |
|
ecs_flags16_t kind, |
|
uint64_t written); |
|
|
|
/* Compile filter to list of operations */ |
|
int flecs_rule_compile( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_rule_t *rule); |
|
|
|
/* Get allocator from iterator */ |
|
ecs_allocator_t* flecs_rule_get_allocator( |
|
const ecs_iter_t *it); |
|
|
|
|
|
/* Traversal cache for transitive queries. Finds all reachable entities by |
|
* following a relationship */ |
|
|
|
/* Find all entities when traversing downwards */ |
|
void flecs_rule_get_trav_down_cache( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_cache_t *cache, |
|
ecs_entity_t trav, |
|
ecs_entity_t entity); |
|
|
|
/* Find all entities when traversing upwards */ |
|
void flecs_rule_get_trav_up_cache( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_cache_t *cache, |
|
ecs_entity_t trav, |
|
ecs_table_t *table); |
|
|
|
/* Free traversal cache */ |
|
void flecs_rule_trav_cache_fini( |
|
ecs_allocator_t *a, |
|
ecs_trav_cache_t *cache); |
|
|
|
/* Traversal caches for up traversal. Enables searching upwards until an entity |
|
* with the queried for id has been found. */ |
|
|
|
/* Traverse downwards from starting entity to find all tables for which the |
|
* specified entity is the source of the queried for id ('with'). */ |
|
ecs_trav_down_t* flecs_rule_get_down_cache( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_entity_t trav, |
|
ecs_entity_t entity, |
|
ecs_id_record_t *idr_with, |
|
bool self); |
|
|
|
/* Free down traversal cache */ |
|
void flecs_rule_down_cache_fini( |
|
ecs_allocator_t *a, |
|
ecs_trav_up_cache_t *cache); |
|
|
|
ecs_trav_up_t* flecs_rule_get_up_cache( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_table_t *table, |
|
ecs_id_t with, |
|
ecs_entity_t trav, |
|
ecs_id_record_t *idr_with, |
|
ecs_id_record_t *idr_trav); |
|
|
|
/* Free up traversal cache */ |
|
void flecs_rule_up_cache_fini( |
|
ecs_trav_up_cache_t *cache); |
|
|
|
/* Convert instruction kind to string */ |
|
const char* flecs_rule_op_str( |
|
uint16_t kind); |
|
|
|
/* Iterator for trivial queries. */ |
|
bool flecs_rule_trivial_search( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_trivial_ctx_t *op_ctx, |
|
bool first, |
|
int32_t until); |
|
|
|
/* Iterator for trivial queries. */ |
|
bool flecs_rule_trivial_search_nodata( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_trivial_ctx_t *op_ctx, |
|
bool first, |
|
int32_t until); |
|
|
|
/* Iterator for trivial queries with wildcard matching. */ |
|
bool flecs_rule_trivial_search_w_wildcards( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_trivial_ctx_t *op_ctx, |
|
bool first, |
|
int32_t until); |
|
|
|
/* Trivial test for constrained $this. */ |
|
bool flecs_rule_trivial_test( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
bool first, |
|
int32_t term_count); |
|
|
|
/* Trivial test for constrained $this with wildcard matching. */ |
|
bool flecs_rule_trivial_test_w_wildcards( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
bool first, |
|
int32_t term_count); |
|
|
|
#endif |
|
|
|
#include <ctype.h> |
|
|
|
#ifdef FLECS_RULES |
|
|
|
static ecs_mixins_t ecs_rule_t_mixins = { |
|
.type_name = "ecs_rule_t", |
|
.elems = { |
|
[EcsMixinWorld] = offsetof(ecs_rule_t, filter.world), |
|
[EcsMixinEntity] = offsetof(ecs_rule_t, filter.entity), |
|
[EcsMixinIterable] = offsetof(ecs_rule_t, iterable), |
|
[EcsMixinDtor] = offsetof(ecs_rule_t, dtor) |
|
} |
|
}; |
|
|
|
const char* flecs_rule_op_str( |
|
uint16_t kind) |
|
{ |
|
switch(kind) { |
|
case EcsRuleAnd: return "and "; |
|
case EcsRuleAndId: return "andid "; |
|
case EcsRuleAndAny: return "andany "; |
|
case EcsRuleTriv: return "triv "; |
|
case EcsRuleTrivData: return "trivpop "; |
|
case EcsRuleTrivWildcard: return "trivwc "; |
|
case EcsRuleSelectAny: return "any "; |
|
case EcsRuleUp: return "up "; |
|
case EcsRuleUpId: return "upid "; |
|
case EcsRuleSelfUp: return "selfup "; |
|
case EcsRuleSelfUpId: return "selfupid"; |
|
case EcsRuleWith: return "with "; |
|
case EcsRuleTrav: return "trav "; |
|
case EcsRuleIds: return "ids "; |
|
case EcsRuleIdsRight: return "idsr "; |
|
case EcsRuleIdsLeft: return "idsl "; |
|
case EcsRuleEach: return "each "; |
|
case EcsRuleStore: return "store "; |
|
case EcsRuleReset: return "reset "; |
|
case EcsRuleOr: return "or "; |
|
case EcsRuleOptional: return "option "; |
|
case EcsRuleIf: return "if "; |
|
case EcsRuleEnd: return "end "; |
|
case EcsRuleNot: return "not "; |
|
case EcsRulePredEq: return "eq "; |
|
case EcsRulePredNeq: return "neq "; |
|
case EcsRulePredEqName: return "eq_nm "; |
|
case EcsRulePredNeqName: return "neq_nm "; |
|
case EcsRulePredEqMatch: return "eq_m "; |
|
case EcsRulePredNeqMatch: return "neq_m "; |
|
case EcsRuleLookup: return "lookup "; |
|
case EcsRuleSetVars: return "setvars "; |
|
case EcsRuleSetThis: return "setthis "; |
|
case EcsRuleSetFixed: return "setfix "; |
|
case EcsRuleSetIds: return "setids "; |
|
case EcsRuleSetId: return "setid "; |
|
case EcsRuleContain: return "contain "; |
|
case EcsRulePairEq: return "pair_eq "; |
|
case EcsRulePopulate: return "pop "; |
|
case EcsRulePopulateSelf: return "popself "; |
|
case EcsRuleYield: return "yield "; |
|
case EcsRuleNothing: return "nothing "; |
|
default: return "!invalid"; |
|
} |
|
} |
|
|
|
/* Implementation for iterable mixin */ |
|
static |
|
void flecs_rule_iter_mixin_init( |
|
const ecs_world_t *world, |
|
const ecs_poly_t *poly, |
|
ecs_iter_t *iter, |
|
ecs_term_t *filter) |
|
{ |
|
ecs_poly_assert(poly, ecs_rule_t); |
|
|
|
if (filter) { |
|
iter[1] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly)); |
|
iter[0] = ecs_term_chain_iter(&iter[1], filter); |
|
} else { |
|
iter[0] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly)); |
|
} |
|
} |
|
|
|
static |
|
void flecs_rule_fini( |
|
ecs_rule_t *rule) |
|
{ |
|
if (rule->vars != &rule->vars_cache.var) { |
|
ecs_os_free(rule->vars); |
|
} |
|
|
|
ecs_os_free(rule->ops); |
|
ecs_os_free(rule->src_vars); |
|
flecs_name_index_fini(&rule->tvar_index); |
|
flecs_name_index_fini(&rule->evar_index); |
|
ecs_filter_fini(&rule->filter); |
|
|
|
ecs_poly_free(rule, ecs_rule_t); |
|
} |
|
|
|
void ecs_rule_fini( |
|
ecs_rule_t *rule) |
|
{ |
|
if (rule->filter.entity) { |
|
/* If filter is associated with entity, use poly dtor path */ |
|
ecs_delete(rule->filter.world, rule->filter.entity); |
|
} else { |
|
flecs_rule_fini(rule); |
|
} |
|
} |
|
|
|
ecs_rule_t* ecs_rule_init( |
|
ecs_world_t *world, |
|
const ecs_filter_desc_t *const_desc) |
|
{ |
|
ecs_rule_t *result = ecs_poly_new(ecs_rule_t); |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
|
|
/* Initialize the query */ |
|
ecs_filter_desc_t desc = *const_desc; |
|
desc.storage = &result->filter; /* Use storage of rule */ |
|
result->filter = ECS_FILTER_INIT; |
|
if (ecs_filter_init(world, &desc) == NULL) { |
|
goto error; |
|
} |
|
|
|
result->iterable.init = flecs_rule_iter_mixin_init; |
|
|
|
/* Compile filter to operations */ |
|
if (flecs_rule_compile(world, stage, result)) { |
|
goto error; |
|
} |
|
|
|
ecs_entity_t entity = const_desc->entity; |
|
result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; |
|
|
|
if (entity) { |
|
EcsPoly *poly = ecs_poly_bind(world, entity, ecs_rule_t); |
|
poly->poly = result; |
|
ecs_poly_modified(world, entity, ecs_rule_t); |
|
} |
|
|
|
return result; |
|
error: |
|
ecs_rule_fini(result); |
|
return NULL; |
|
} |
|
|
|
static |
|
int32_t flecs_rule_op_ref_str( |
|
const ecs_rule_t *rule, |
|
ecs_rule_ref_t *ref, |
|
ecs_flags16_t flags, |
|
ecs_strbuf_t *buf) |
|
{ |
|
int32_t color_chars = 0; |
|
if (flags & EcsRuleIsVar) { |
|
ecs_assert(ref->var < rule->var_count, ECS_INTERNAL_ERROR, NULL); |
|
ecs_rule_var_t *var = &rule->vars[ref->var]; |
|
ecs_strbuf_appendlit(buf, "#[green]$#[reset]"); |
|
if (var->kind == EcsVarTable) { |
|
ecs_strbuf_appendch(buf, '['); |
|
} |
|
ecs_strbuf_appendlit(buf, "#[green]"); |
|
if (var->name) { |
|
ecs_strbuf_appendstr(buf, var->name); |
|
} else { |
|
if (var->id) { |
|
#ifdef FLECS_DEBUG |
|
if (var->label) { |
|
ecs_strbuf_appendstr(buf, var->label); |
|
ecs_strbuf_appendch(buf, '\''); |
|
} |
|
#endif |
|
ecs_strbuf_append(buf, "%d", var->id); |
|
} else { |
|
ecs_strbuf_appendlit(buf, "this"); |
|
} |
|
} |
|
ecs_strbuf_appendlit(buf, "#[reset]"); |
|
if (var->kind == EcsVarTable) { |
|
ecs_strbuf_appendch(buf, ']'); |
|
} |
|
color_chars = ecs_os_strlen("#[green]#[reset]#[green]#[reset]"); |
|
} else if (flags & EcsRuleIsEntity) { |
|
char *path = ecs_get_fullpath(rule->filter.world, ref->entity); |
|
ecs_strbuf_appendlit(buf, "#[blue]"); |
|
ecs_strbuf_appendstr(buf, path); |
|
ecs_strbuf_appendlit(buf, "#[reset]"); |
|
ecs_os_free(path); |
|
color_chars = ecs_os_strlen("#[blue]#[reset]"); |
|
} |
|
return color_chars; |
|
} |
|
|
|
char* ecs_rule_str_w_profile( |
|
const ecs_rule_t *rule, |
|
const ecs_iter_t *it) |
|
{ |
|
ecs_poly_assert(rule, ecs_rule_t); |
|
|
|
ecs_strbuf_t buf = ECS_STRBUF_INIT; |
|
ecs_rule_op_t *ops = rule->ops; |
|
int32_t i, count = rule->op_count, indent = 0; |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_rule_op_t *op = &ops[i]; |
|
ecs_flags16_t flags = op->flags; |
|
ecs_flags16_t src_flags = flecs_rule_ref_flags(flags, EcsRuleSrc); |
|
ecs_flags16_t first_flags = flecs_rule_ref_flags(flags, EcsRuleFirst); |
|
ecs_flags16_t second_flags = flecs_rule_ref_flags(flags, EcsRuleSecond); |
|
|
|
if (it) { |
|
#ifdef FLECS_DEBUG |
|
const ecs_rule_iter_t *rit = &it->priv.iter.rule; |
|
ecs_strbuf_append(&buf, |
|
"#[green]%4d -> #[red]%4d <- #[grey] | ", |
|
rit->profile[i].count[0], |
|
rit->profile[i].count[1]); |
|
#endif |
|
} |
|
|
|
ecs_strbuf_append(&buf, |
|
"#[normal]%2d. [#[grey]%2d#[reset], #[green]%2d#[reset]] ", |
|
i, op->prev, op->next); |
|
int32_t hidden_chars, start = ecs_strbuf_written(&buf); |
|
if (op->kind == EcsRuleEnd) { |
|
indent --; |
|
} |
|
|
|
ecs_strbuf_append(&buf, "%*s", indent, ""); |
|
ecs_strbuf_appendstr(&buf, flecs_rule_op_str(op->kind)); |
|
ecs_strbuf_appendstr(&buf, " "); |
|
|
|
int32_t written = ecs_strbuf_written(&buf); |
|
for (int32_t j = 0; j < (12 - (written - start)); j ++) { |
|
ecs_strbuf_appendch(&buf, ' '); |
|
} |
|
|
|
hidden_chars = flecs_rule_op_ref_str(rule, &op->src, src_flags, &buf); |
|
|
|
if (op->kind == EcsRuleNot || |
|
op->kind == EcsRuleOr || |
|
op->kind == EcsRuleOptional || |
|
op->kind == EcsRuleIf) |
|
{ |
|
indent ++; |
|
} |
|
|
|
if (!first_flags && !second_flags) { |
|
ecs_strbuf_appendstr(&buf, "\n"); |
|
continue; |
|
} |
|
|
|
written = ecs_strbuf_written(&buf) - hidden_chars; |
|
for (int32_t j = 0; j < (30 - (written - start)); j ++) { |
|
ecs_strbuf_appendch(&buf, ' '); |
|
} |
|
|
|
if (!first_flags && !second_flags) { |
|
ecs_strbuf_appendstr(&buf, "\n"); |
|
continue; |
|
} |
|
|
|
ecs_strbuf_appendstr(&buf, "("); |
|
flecs_rule_op_ref_str(rule, &op->first, first_flags, &buf); |
|
|
|
if (second_flags) { |
|
ecs_strbuf_appendstr(&buf, ", "); |
|
flecs_rule_op_ref_str(rule, &op->second, second_flags, &buf); |
|
} else { |
|
switch (op->kind) { |
|
case EcsRulePredEqName: |
|
case EcsRulePredNeqName: |
|
case EcsRulePredEqMatch: |
|
case EcsRulePredNeqMatch: { |
|
int8_t term_index = op->term_index; |
|
ecs_strbuf_appendstr(&buf, ", #[yellow]\""); |
|
ecs_strbuf_appendstr(&buf, rule->filter.terms[term_index].second.name); |
|
ecs_strbuf_appendstr(&buf, "\"#[reset]"); |
|
break; |
|
} |
|
case EcsRuleLookup: { |
|
ecs_var_id_t src_id = op->src.var; |
|
ecs_strbuf_appendstr(&buf, ", #[yellow]\""); |
|
ecs_strbuf_appendstr(&buf, rule->vars[src_id].lookup); |
|
ecs_strbuf_appendstr(&buf, "\"#[reset]"); |
|
break; |
|
} |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
ecs_strbuf_appendch(&buf, ')'); |
|
|
|
ecs_strbuf_appendch(&buf, '\n'); |
|
} |
|
|
|
#ifdef FLECS_LOG |
|
char *str = ecs_strbuf_get(&buf); |
|
flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); |
|
ecs_os_free(str); |
|
#endif |
|
return ecs_strbuf_get(&buf); |
|
} |
|
|
|
char* ecs_rule_str( |
|
const ecs_rule_t *rule) |
|
{ |
|
return ecs_rule_str_w_profile(rule, NULL); |
|
} |
|
|
|
const ecs_filter_t* ecs_rule_get_filter( |
|
const ecs_rule_t *rule) |
|
{ |
|
return &rule->filter; |
|
} |
|
|
|
const char* ecs_rule_parse_vars( |
|
ecs_rule_t *rule, |
|
ecs_iter_t *it, |
|
const char *expr) |
|
{ |
|
ecs_poly_assert(rule, ecs_rule_t); |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL) |
|
char token[ECS_MAX_TOKEN_SIZE]; |
|
const char *ptr = expr; |
|
bool paren = false; |
|
|
|
const char *name = NULL; |
|
if (rule->filter.entity) { |
|
name = ecs_get_name(rule->filter.world, rule->filter.entity); |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
if (!ptr[0]) { |
|
return ptr; |
|
} |
|
|
|
if (ptr[0] == '(') { |
|
paren = true; |
|
ptr = ecs_parse_ws_eol(ptr + 1); |
|
if (ptr[0] == ')') { |
|
return ptr + 1; |
|
} |
|
} |
|
|
|
do { |
|
ptr = ecs_parse_ws_eol(ptr); |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
int var = ecs_rule_find_var(rule, token); |
|
if (var == -1) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unknown variable '%s'", token); |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
if (ptr[0] != ':') { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"missing ':'"); |
|
return NULL; |
|
} |
|
|
|
ptr = ecs_parse_ws_eol(ptr + 1); |
|
ptr = ecs_parse_identifier(name, expr, ptr, token); |
|
if (!ptr) { |
|
return NULL; |
|
} |
|
|
|
ecs_entity_t val = ecs_lookup_fullpath(rule->filter.world, token); |
|
if (!val) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unresolved entity '%s'", token); |
|
return NULL; |
|
} |
|
|
|
ecs_iter_set_var(it, var, val); |
|
|
|
ptr = ecs_parse_ws_eol(ptr); |
|
if (ptr[0] == ')') { |
|
if (!paren) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"unexpected closing parenthesis"); |
|
return NULL; |
|
} |
|
|
|
ptr ++; |
|
break; |
|
} else if (ptr[0] == ',') { |
|
ptr ++; |
|
} else if (!ptr[0]) { |
|
if (paren) { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"missing closing parenthesis"); |
|
return NULL; |
|
} |
|
break; |
|
} else { |
|
ecs_parser_error(name, expr, (ptr - expr), |
|
"expected , or end of string"); |
|
return NULL; |
|
} |
|
} while (true); |
|
|
|
return ptr; |
|
error: |
|
return NULL; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/rules/compile.c |
|
* @brief Compile rule program from filter. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_RULES |
|
|
|
#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ |
|
|
|
ecs_rule_lbl_t flecs_itolbl(int64_t val) { |
|
return flecs_ito(int16_t, val); |
|
} |
|
|
|
static |
|
ecs_var_id_t flecs_itovar(int64_t val) { |
|
return flecs_ito(uint8_t, val); |
|
} |
|
|
|
static |
|
ecs_var_id_t flecs_utovar(uint64_t val) { |
|
return flecs_uto(uint8_t, val); |
|
} |
|
|
|
#ifdef FLECS_DEBUG |
|
#define flecs_set_var_label(var, lbl) (var)->label = lbl |
|
#else |
|
#define flecs_set_var_label(var, lbl) |
|
#endif |
|
|
|
static |
|
bool flecs_rule_is_builtin_pred( |
|
ecs_term_t *term) |
|
{ |
|
if (term->first.flags & EcsIsEntity) { |
|
ecs_entity_t id = term->first.id; |
|
if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
bool flecs_rule_is_written( |
|
ecs_var_id_t var_id, |
|
uint64_t written) |
|
{ |
|
if (var_id == EcsVarNone) { |
|
return true; |
|
} |
|
|
|
ecs_assert(var_id < EcsRuleMaxVarCount, ECS_INTERNAL_ERROR, NULL); |
|
return (written & (1ull << var_id)) != 0; |
|
} |
|
|
|
static |
|
void flecs_rule_write( |
|
ecs_var_id_t var_id, |
|
uint64_t *written) |
|
{ |
|
ecs_assert(var_id < EcsRuleMaxVarCount, ECS_INTERNAL_ERROR, NULL); |
|
*written |= (1ull << var_id); |
|
} |
|
|
|
static |
|
void flecs_rule_write_ctx( |
|
ecs_var_id_t var_id, |
|
ecs_rule_compile_ctx_t *ctx, |
|
bool cond_write) |
|
{ |
|
bool is_written = flecs_rule_is_written(var_id, ctx->written); |
|
flecs_rule_write(var_id, &ctx->written); |
|
if (!is_written) { |
|
if (cond_write) { |
|
flecs_rule_write(var_id, &ctx->cond_written); |
|
} |
|
} |
|
} |
|
|
|
ecs_flags16_t flecs_rule_ref_flags( |
|
ecs_flags16_t flags, |
|
ecs_flags16_t kind) |
|
{ |
|
return (flags >> kind) & (EcsRuleIsVar | EcsRuleIsEntity); |
|
} |
|
|
|
bool flecs_ref_is_written( |
|
const ecs_rule_op_t *op, |
|
const ecs_rule_ref_t *ref, |
|
ecs_flags16_t kind, |
|
uint64_t written) |
|
{ |
|
ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, kind); |
|
if (flags & EcsRuleIsEntity) { |
|
ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); |
|
if (ref->entity) { |
|
return true; |
|
} |
|
} else if (flags & EcsRuleIsVar) { |
|
return flecs_rule_is_written(ref->var, written); |
|
} |
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_rule_var_is_anonymous( |
|
const ecs_rule_t *rule, |
|
ecs_var_id_t var_id) |
|
{ |
|
ecs_rule_var_t *var = &rule->vars[var_id]; |
|
return var->anonymous; |
|
} |
|
|
|
static |
|
ecs_var_id_t flecs_rule_find_var_id( |
|
const ecs_rule_t *rule, |
|
const char *name, |
|
ecs_var_kind_t kind) |
|
{ |
|
ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Backwards compatibility */ |
|
if (!ecs_os_strcmp(name, "This")) { |
|
name = "this"; |
|
} |
|
|
|
if (kind == EcsVarTable) { |
|
if (!ecs_os_strcmp(name, EcsThisName)) { |
|
if (rule->has_table_this) { |
|
return 0; |
|
} else { |
|
return EcsVarNone; |
|
} |
|
} |
|
|
|
if (!flecs_name_index_is_init(&rule->tvar_index)) { |
|
return EcsVarNone; |
|
} |
|
|
|
uint64_t index = flecs_name_index_find( |
|
&rule->tvar_index, name, 0, 0); |
|
if (index == 0) { |
|
return EcsVarNone; |
|
} |
|
return flecs_utovar(index); |
|
} |
|
|
|
if (kind == EcsVarEntity) { |
|
if (!flecs_name_index_is_init(&rule->evar_index)) { |
|
return EcsVarNone; |
|
} |
|
|
|
uint64_t index = flecs_name_index_find( |
|
&rule->evar_index, name, 0, 0); |
|
if (index == 0) { |
|
return EcsVarNone; |
|
} |
|
return flecs_utovar(index); |
|
} |
|
|
|
ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* If searching for any kind of variable, start with most specific */ |
|
ecs_var_id_t index = flecs_rule_find_var_id(rule, name, EcsVarEntity); |
|
if (index != EcsVarNone) { |
|
return index; |
|
} |
|
|
|
return flecs_rule_find_var_id(rule, name, EcsVarTable); |
|
} |
|
|
|
int32_t ecs_rule_var_count( |
|
const ecs_rule_t *rule) |
|
{ |
|
return rule->var_pub_count; |
|
} |
|
|
|
int32_t ecs_rule_find_var( |
|
const ecs_rule_t *rule, |
|
const char *name) |
|
{ |
|
ecs_var_id_t var_id = flecs_rule_find_var_id(rule, name, EcsVarEntity); |
|
if (var_id == EcsVarNone) { |
|
if (rule->filter.flags & EcsFilterMatchThis) { |
|
if (!ecs_os_strcmp(name, "This")) { |
|
name = "this"; |
|
} |
|
if (!ecs_os_strcmp(name, EcsThisName)) { |
|
var_id = 0; |
|
} |
|
} |
|
if (var_id == EcsVarNone) { |
|
return -1; |
|
} |
|
} |
|
return (int32_t)var_id; |
|
} |
|
|
|
const char* ecs_rule_var_name( |
|
const ecs_rule_t *rule, |
|
int32_t var_id) |
|
{ |
|
if (var_id) { |
|
return rule->vars[var_id].name; |
|
} else { |
|
return EcsThisName; |
|
} |
|
} |
|
|
|
bool ecs_rule_var_is_entity( |
|
const ecs_rule_t *rule, |
|
int32_t var_id) |
|
{ |
|
return rule->vars[var_id].kind == EcsVarEntity; |
|
} |
|
|
|
static |
|
const char* flecs_term_id_var_name( |
|
ecs_term_id_t *term_id) |
|
{ |
|
if (!(term_id->flags & EcsIsVariable)) { |
|
return NULL; |
|
} |
|
|
|
if (term_id->id == EcsThis) { |
|
return EcsThisName; |
|
} |
|
|
|
return term_id->name; |
|
} |
|
|
|
static |
|
bool flecs_term_id_is_wildcard( |
|
ecs_term_id_t *term_id) |
|
{ |
|
if ((term_id->flags & EcsIsVariable) && |
|
((term_id->id == EcsWildcard) || (term_id->id == EcsAny))) |
|
{ |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
static |
|
ecs_var_id_t flecs_rule_add_var( |
|
ecs_rule_t *rule, |
|
const char *name, |
|
ecs_vec_t *vars, |
|
ecs_var_kind_t kind) |
|
{ |
|
const char *dot = NULL; |
|
if (name) { |
|
dot = strchr(name, '.'); |
|
if (dot) { |
|
kind = EcsVarEntity; /* lookup variables are always entities */ |
|
} |
|
} |
|
|
|
ecs_hashmap_t *var_index = NULL; |
|
ecs_var_id_t var_id = EcsVarNone; |
|
if (name) { |
|
if (kind == EcsVarAny) { |
|
var_id = flecs_rule_find_var_id(rule, name, EcsVarEntity); |
|
if (var_id != EcsVarNone) { |
|
return var_id; |
|
} |
|
|
|
var_id = flecs_rule_find_var_id(rule, name, EcsVarTable); |
|
if (var_id != EcsVarNone) { |
|
return var_id; |
|
} |
|
|
|
kind = EcsVarTable; |
|
} else { |
|
var_id = flecs_rule_find_var_id(rule, name, kind); |
|
if (var_id != EcsVarNone) { |
|
return var_id; |
|
} |
|
} |
|
|
|
if (kind == EcsVarTable) { |
|
var_index = &rule->tvar_index; |
|
} else { |
|
var_index = &rule->evar_index; |
|
} |
|
|
|
/* If we're creating an entity var, check if it has a table variant */ |
|
if (kind == EcsVarEntity && var_id == EcsVarNone) { |
|
var_id = flecs_rule_find_var_id(rule, name, EcsVarTable); |
|
} |
|
} |
|
|
|
ecs_rule_var_t *var; |
|
ecs_var_id_t result; |
|
if (vars) { |
|
var = ecs_vec_append_t(NULL, vars, ecs_rule_var_t); |
|
result = var->id = flecs_itovar(ecs_vec_count(vars)); |
|
} else { |
|
ecs_dbg_assert(rule->var_count < rule->var_size, |
|
ECS_INTERNAL_ERROR, NULL); |
|
var = &rule->vars[rule->var_count]; |
|
result = var->id = flecs_itovar(rule->var_count); |
|
rule->var_count ++; |
|
} |
|
|
|
var->kind = flecs_ito(int8_t, kind); |
|
var->name = name; |
|
var->table_id = var_id; |
|
var->base_id = 0; |
|
var->lookup = NULL; |
|
flecs_set_var_label(var, NULL); |
|
|
|
if (name) { |
|
flecs_name_index_init_if(var_index, NULL); |
|
flecs_name_index_ensure(var_index, var->id, name, 0, 0); |
|
var->anonymous = name[0] == '_'; |
|
|
|
/* Handle variables that require a by-name lookup, e.g. $this.wheel */ |
|
if (dot != NULL) { |
|
ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); |
|
var->lookup = dot + 1; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static |
|
ecs_var_id_t flecs_rule_add_var_for_term_id( |
|
ecs_rule_t *rule, |
|
ecs_term_id_t *term_id, |
|
ecs_vec_t *vars, |
|
ecs_var_kind_t kind) |
|
{ |
|
const char *name = flecs_term_id_var_name(term_id); |
|
if (!name) { |
|
return EcsVarNone; |
|
} |
|
|
|
return flecs_rule_add_var(rule, name, vars, kind); |
|
} |
|
|
|
/* This function walks over terms to discover which variables are used in the |
|
* query. It needs to provide the following functionality: |
|
* - create table vars for all variables used as source |
|
* - create entity vars for all variables not used as source |
|
* - create entity vars for all non-$this vars |
|
* - create anonymous vars to store the content of wildcards |
|
* - create anonymous vars to store result of lookups (for $var.child_name) |
|
* - create anonymous vars for resolving component inheritance |
|
* - create array that stores the source variable for each field |
|
* - ensure table vars for non-$this variables are anonymous |
|
* - ensure variables created inside scopes are anonymous |
|
* - place anonymous variables after public variables in vars array |
|
*/ |
|
static |
|
int flecs_rule_discover_vars( |
|
ecs_stage_t *stage, |
|
ecs_rule_t *rule) |
|
{ |
|
ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ |
|
ecs_vec_reset_t(NULL, vars, ecs_rule_var_t); |
|
|
|
ecs_term_t *terms = rule->filter.terms; |
|
int32_t a, i, anonymous_count = 0, count = rule->filter.term_count; |
|
int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; |
|
bool table_this = false, entity_before_table_this = false; |
|
|
|
/* For This table lookups during discovery. This will be overwritten after |
|
* discovery with whether the rule actually has a This table variable. */ |
|
rule->has_table_this = true; |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
ecs_term_id_t *first = &term->first; |
|
ecs_term_id_t *second = &term->second; |
|
ecs_term_id_t *src = &term->src; |
|
|
|
if (first->id == EcsScopeOpen) { |
|
/* Keep track of which variables are first used in scope, so that we |
|
* can mark them as anonymous. Terms inside a scope are collapsed |
|
* into a single result, which means that outside of the scope the |
|
* value of those variables is undefined. */ |
|
if (!scope) { |
|
scoped_var_index = ecs_vec_count(vars); |
|
} |
|
scope ++; |
|
continue; |
|
} else if (first->id == EcsScopeClose) { |
|
if (!--scope) { |
|
/* Any new variables declared after entering a scope should be |
|
* marked as anonymous. */ |
|
int32_t v; |
|
for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { |
|
ecs_vec_get_t(vars, ecs_rule_var_t, v)->anonymous = true; |
|
} |
|
} |
|
continue; |
|
} |
|
|
|
ecs_var_id_t first_var_id = flecs_rule_add_var_for_term_id( |
|
rule, first, vars, EcsVarEntity); |
|
if (first_var_id == EcsVarNone) { |
|
/* If first is not a variable, check if we need to insert anonymous |
|
* variable for resolving component inheritance */ |
|
if (term->flags & EcsTermIdInherited) { |
|
anonymous_count += 2; /* table & entity variable */ |
|
} |
|
|
|
/* If first is a wildcard, insert anonymous variable */ |
|
if (flecs_term_id_is_wildcard(first)) { |
|
anonymous_count ++; |
|
} |
|
} |
|
|
|
if ((src->flags & EcsIsVariable) && (src->id != EcsThis)) { |
|
const char *var_name = flecs_term_id_var_name(src); |
|
if (var_name) { |
|
ecs_var_id_t var_id = flecs_rule_find_var_id( |
|
rule, var_name, EcsVarEntity); |
|
if (var_id == EcsVarNone || var_id == first_var_id) { |
|
var_id = flecs_rule_add_var( |
|
rule, var_name, vars, EcsVarEntity); |
|
} |
|
|
|
if (var_id != EcsVarNone) { |
|
/* Mark variable as one for which we need to create a table |
|
* variable. Don't create table variable now, so that we can |
|
* store it in the non-public part of the variable array. */ |
|
ecs_rule_var_t *var = ecs_vec_get_t( |
|
vars, ecs_rule_var_t, (int32_t)var_id - 1); |
|
ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (!var->lookup) { |
|
var->kind = EcsVarAny; |
|
anonymous_table_count ++; |
|
} |
|
|
|
if (!(term->flags & EcsTermNoData)) { |
|
/* Can't have an anonymous variable as source of a term |
|
* that returns a component. We need to return each |
|
* instance of the component, whereas anonymous |
|
* variables are not guaranteed to be resolved to |
|
* individual entities. */ |
|
if (var->anonymous) { |
|
ecs_err( |
|
"can't use anonymous variable '%s' as source of " |
|
"data term", var->name); |
|
goto error; |
|
} |
|
} |
|
|
|
/* Track which variable ids are used as field source */ |
|
if (!rule->src_vars) { |
|
rule->src_vars = ecs_os_calloc_n(ecs_var_id_t, |
|
rule->filter.field_count); |
|
} |
|
|
|
rule->src_vars[term->field_index] = var_id; |
|
} |
|
} else { |
|
if (flecs_term_id_is_wildcard(src)) { |
|
anonymous_count ++; |
|
} |
|
} |
|
} else if ((src->flags & EcsIsVariable) && (src->id == EcsThis)) { |
|
if (flecs_rule_is_builtin_pred(term) && term->oper == EcsOr) { |
|
flecs_rule_add_var(rule, EcsThisName, vars, EcsVarEntity); |
|
} |
|
} |
|
|
|
if (flecs_rule_add_var_for_term_id( |
|
rule, second, vars, EcsVarEntity) == EcsVarNone) |
|
{ |
|
/* If second is a wildcard, insert anonymous variable */ |
|
if (flecs_term_id_is_wildcard(second)) { |
|
anonymous_count ++; |
|
} |
|
} |
|
|
|
if (src->flags & EcsIsVariable && second->flags & EcsIsVariable) { |
|
if (term->flags & EcsTermTransitive) { |
|
/* Anonymous variable to store temporary id for finding |
|
* targets for transitive relationship, see compile_term. */ |
|
anonymous_count ++; |
|
} |
|
} |
|
|
|
/* Track if a This entity variable is used before a potential This table |
|
* variable. If this happens, the rule has no This table variable */ |
|
if (src->id == EcsThis) { |
|
table_this = true; |
|
} |
|
if (first->id == EcsThis || second->id == EcsThis) { |
|
if (!table_this) { |
|
entity_before_table_this = true; |
|
} |
|
} |
|
} |
|
|
|
int32_t var_count = ecs_vec_count(vars); |
|
ecs_var_id_t placeholder = EcsVarNone - 1; |
|
bool replace_placeholders = false; |
|
|
|
/* Ensure lookup variables have table and/or entity variables */ |
|
for (i = 0; i < var_count; i ++) { |
|
ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); |
|
if (var->lookup) { |
|
char *var_name = ecs_os_strdup(var->name); |
|
var_name[var->lookup - var->name - 1] = '\0'; |
|
|
|
ecs_var_id_t base_table_id = flecs_rule_find_var_id( |
|
rule, var_name, EcsVarTable); |
|
if (base_table_id != EcsVarNone) { |
|
var->table_id = base_table_id; |
|
} else if (anonymous_table_count) { |
|
/* Scan for implicit anonymous table variables that haven't been |
|
* inserted yet (happens after this step). Doing this here vs. |
|
* ensures that anonymous variables are appended at the end of |
|
* the variable array, while also ensuring that variable ids are |
|
* stable (no swapping of table var ids that are in use). */ |
|
for (a = 0; a < var_count; a ++) { |
|
ecs_rule_var_t *avar = ecs_vec_get_t( |
|
vars, ecs_rule_var_t, a); |
|
if (avar->kind == EcsVarAny) { |
|
if (!ecs_os_strcmp(avar->name, var_name)) { |
|
base_table_id = (ecs_var_id_t)(a + 1); |
|
break; |
|
} |
|
} |
|
} |
|
if (base_table_id != EcsVarNone) { |
|
/* Set marker so we can set the new table id afterwards */ |
|
var->table_id = placeholder; |
|
replace_placeholders = true; |
|
} |
|
} |
|
|
|
ecs_var_id_t base_entity_id = flecs_rule_find_var_id( |
|
rule, var_name, EcsVarEntity); |
|
if (base_entity_id == EcsVarNone) { |
|
/* Get name from table var (must exist). We can't use allocated |
|
* name since variables don't own names. */ |
|
const char *base_name = NULL; |
|
if (base_table_id) { |
|
ecs_rule_var_t *base_table_var = ecs_vec_get_t( |
|
vars, ecs_rule_var_t, (int32_t)base_table_id - 1); |
|
base_name = base_table_var->name; |
|
} else { |
|
base_name = EcsThisName; |
|
} |
|
|
|
base_entity_id = flecs_rule_add_var( |
|
rule, base_name, vars, EcsVarEntity); |
|
var = ecs_vec_get_t(vars, ecs_rule_var_t, i); |
|
} |
|
|
|
var->base_id = base_entity_id; |
|
|
|
ecs_os_free(var_name); |
|
} |
|
} |
|
var_count = ecs_vec_count(vars); |
|
|
|
/* Add non-This table variables */ |
|
if (anonymous_table_count) { |
|
anonymous_table_count = 0; |
|
for (i = 0; i < var_count; i ++) { |
|
ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); |
|
if (var->kind == EcsVarAny) { |
|
var->kind = EcsVarEntity; |
|
|
|
ecs_var_id_t var_id = flecs_rule_add_var( |
|
rule, var->name, vars, EcsVarTable); |
|
ecs_vec_get_t(vars, ecs_rule_var_t, i)->table_id = var_id; |
|
anonymous_table_count ++; |
|
} |
|
} |
|
|
|
var_count = ecs_vec_count(vars); |
|
} |
|
|
|
/* If any forward references to newly added anonymous tables exist, replace |
|
* them with the actual table variable ids. */ |
|
if (replace_placeholders) { |
|
for (i = 0; i < var_count; i ++) { |
|
ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); |
|
if (var->table_id == placeholder) { |
|
char *var_name = ecs_os_strdup(var->name); |
|
var_name[var->lookup - var->name - 1] = '\0'; |
|
|
|
var->table_id = flecs_rule_find_var_id( |
|
rule, var_name, EcsVarTable); |
|
ecs_assert(var->table_id != EcsVarNone, |
|
ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_os_free(var_name); |
|
} |
|
} |
|
} |
|
|
|
/* Always include spot for This variable, even if rule doesn't use it */ |
|
var_count ++; |
|
|
|
ecs_rule_var_t *rule_vars = &rule->vars_cache.var; |
|
if ((var_count + anonymous_count) > 1) { |
|
rule_vars = ecs_os_malloc( |
|
(ECS_SIZEOF(ecs_rule_var_t) + ECS_SIZEOF(char*)) * |
|
(var_count + anonymous_count)); |
|
} |
|
|
|
rule->vars = rule_vars; |
|
rule->var_count = var_count; |
|
rule->var_pub_count = var_count; |
|
rule->has_table_this = !entity_before_table_this; |
|
|
|
#ifdef FLECS_DEBUG |
|
rule->var_size = var_count + anonymous_count; |
|
#endif |
|
|
|
char **var_names = ECS_ELEM(rule_vars, ECS_SIZEOF(ecs_rule_var_t), |
|
var_count + anonymous_count); |
|
rule->var_names = (char**)var_names; |
|
|
|
rule_vars[0].kind = EcsVarTable; |
|
rule_vars[0].name = NULL; |
|
flecs_set_var_label(&rule_vars[0], NULL); |
|
rule_vars[0].id = 0; |
|
rule_vars[0].table_id = EcsVarNone; |
|
rule_vars[0].lookup = NULL; |
|
var_names[0] = ECS_CONST_CAST(char*, rule_vars[0].name); |
|
rule_vars ++; |
|
var_names ++; |
|
var_count --; |
|
|
|
if (var_count) { |
|
ecs_rule_var_t *user_vars = ecs_vec_first_t(vars, ecs_rule_var_t); |
|
ecs_os_memcpy_n(rule_vars, user_vars, ecs_rule_var_t, var_count); |
|
for (i = 0; i < var_count; i ++) { |
|
var_names[i] = ECS_CONST_CAST(char*, rule_vars[i].name); |
|
} |
|
} |
|
|
|
/* Hide anonymous table variables from application */ |
|
rule->var_pub_count -= anonymous_table_count; |
|
|
|
/* Sanity check to make sure that the public part of the variable array only |
|
* contains entity variables. */ |
|
#ifdef FLECS_DEBUG |
|
for (i = 1 /* first element = $this */; i < rule->var_pub_count; i ++) { |
|
ecs_assert(rule->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
#endif |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
ecs_var_id_t flecs_rule_most_specific_var( |
|
ecs_rule_t *rule, |
|
const char *name, |
|
ecs_var_kind_t kind, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
if (kind == EcsVarTable || kind == EcsVarEntity) { |
|
return flecs_rule_find_var_id(rule, name, kind); |
|
} |
|
|
|
ecs_var_id_t evar = flecs_rule_find_var_id(rule, name, EcsVarEntity); |
|
if ((evar != EcsVarNone) && flecs_rule_is_written(evar, ctx->written)) { |
|
/* If entity variable is available and written to, it contains the most |
|
* specific result and should be used. */ |
|
return evar; |
|
} |
|
|
|
ecs_var_id_t tvar = flecs_rule_find_var_id(rule, name, EcsVarTable); |
|
if ((tvar != EcsVarNone) && !flecs_rule_is_written(tvar, ctx->written)) { |
|
/* If variable of any kind is requested and variable hasn't been written |
|
* yet, write to table variable */ |
|
return tvar; |
|
} |
|
|
|
/* If table var is written, and entity var doesn't exist or is not written, |
|
* return table var */ |
|
if (tvar != EcsVarNone) { |
|
return tvar; |
|
} else { |
|
return evar; |
|
} |
|
} |
|
|
|
static |
|
ecs_rule_lbl_t flecs_rule_op_insert( |
|
ecs_rule_op_t *op, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_rule_op_t); |
|
int32_t count = ecs_vec_count(ctx->ops); |
|
*elem = *op; |
|
if (count > 1) { |
|
if (ctx->cur->lbl_begin == -1) { |
|
/* Variables written by previous instruction can't be written by |
|
* this instruction, except when this is a union. */ |
|
elem->written &= ~elem[-1].written; |
|
} |
|
} |
|
|
|
elem->next = flecs_itolbl(count); |
|
elem->prev = flecs_itolbl(count - 2); |
|
return flecs_itolbl(count - 1); |
|
} |
|
|
|
static |
|
ecs_rule_op_t* flecs_rule_begin_block( |
|
ecs_rule_op_kind_t kind, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_op_t op = {0}; |
|
op.kind = flecs_ito(uint8_t, kind); |
|
ctx->cur->lbl_begin = flecs_rule_op_insert(&op, ctx); |
|
return ecs_vec_get_t(ctx->ops, ecs_rule_op_t, ctx->cur->lbl_begin); |
|
} |
|
|
|
static |
|
void flecs_rule_end_block( |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_op_t new_op = {0}; |
|
new_op.kind = EcsRuleEnd; |
|
ecs_rule_lbl_t end = flecs_rule_op_insert(&new_op, ctx); |
|
|
|
ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); |
|
ops[ctx->cur->lbl_begin].next = end; |
|
|
|
ecs_rule_op_t *end_op = &ops[end]; |
|
if (ctx->cur->lbl_query != -1) { |
|
ecs_rule_op_t *query_op = &ops[ctx->cur->lbl_query]; |
|
end_op->prev = ctx->cur->lbl_begin; |
|
end_op->src = query_op->src; |
|
end_op->first = query_op->first; |
|
end_op->second = query_op->second; |
|
end_op->flags = query_op->flags; |
|
end_op->field_index = query_op->field_index; |
|
} else { |
|
end_op->prev = ctx->cur->lbl_begin; |
|
end_op->field_index = -1; |
|
} |
|
|
|
ctx->cur->lbl_begin = -1; |
|
} |
|
|
|
static |
|
void flecs_rule_begin_block_cond_eval( |
|
ecs_rule_op_t *op, |
|
ecs_rule_compile_ctx_t *ctx, |
|
ecs_write_flags_t cond_write_state) |
|
{ |
|
ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; |
|
ecs_write_flags_t cond_mask = 0; |
|
|
|
if (flecs_rule_ref_flags(op->flags, EcsRuleFirst) == EcsRuleIsVar) { |
|
first_var = op->first.var; |
|
ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); |
|
cond_mask |= (1ull << first_var); |
|
} |
|
if (flecs_rule_ref_flags(op->flags, EcsRuleSecond) == EcsRuleIsVar) { |
|
second_var = op->second.var; |
|
ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); |
|
cond_mask |= (1ull << second_var); |
|
} |
|
if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) == EcsRuleIsVar) { |
|
src_var = op->src.var; |
|
ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); |
|
cond_mask |= (1ull << src_var); |
|
} |
|
|
|
/* Variables set in an OR chain are marked as conditional writes. However, |
|
* writes from previous terms in the current OR chain shouldn't be treated |
|
* as variables that are conditionally set, so instead use the write mask |
|
* from before the chain started. */ |
|
if (ctx->ctrlflow->in_or) { |
|
cond_write_state = ctx->ctrlflow->cond_written_or; |
|
} |
|
|
|
/* If this term uses conditionally set variables, insert instruction that |
|
* jumps over the term if the variables weren't set yet. */ |
|
if (cond_mask & cond_write_state) { |
|
ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); |
|
|
|
ecs_rule_op_t jmp_op = {0}; |
|
jmp_op.kind = EcsRuleIf; |
|
|
|
if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { |
|
jmp_op.flags |= (EcsRuleIsVar << EcsRuleFirst); |
|
jmp_op.first.var = first_var; |
|
} |
|
if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { |
|
jmp_op.flags |= (EcsRuleIsVar << EcsRuleSecond); |
|
jmp_op.second.var = second_var; |
|
} |
|
if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { |
|
jmp_op.flags |= (EcsRuleIsVar << EcsRuleSrc); |
|
jmp_op.src.var = src_var; |
|
} |
|
|
|
flecs_rule_op_insert(&jmp_op, ctx); |
|
} else { |
|
ctx->cur->lbl_cond_eval = -1; |
|
} |
|
} |
|
|
|
static |
|
void flecs_rule_end_block_cond_eval( |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
if (ctx->cur->lbl_cond_eval == -1) { |
|
return; |
|
} |
|
|
|
ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_rule_op_t end_op = {0}; |
|
end_op.kind = EcsRuleEnd; |
|
ecs_rule_lbl_t end = flecs_rule_op_insert(&end_op, ctx); |
|
|
|
ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); |
|
ops[ctx->cur->lbl_cond_eval].next = end; |
|
|
|
ecs_rule_op_t *end_op_ptr = &ops[end]; |
|
ecs_rule_op_t *query_op = &ops[ctx->cur->lbl_query]; |
|
end_op_ptr->prev = ctx->cur->lbl_cond_eval; |
|
end_op_ptr->src = query_op->src; |
|
end_op_ptr->first = query_op->first; |
|
end_op_ptr->second = query_op->second; |
|
end_op_ptr->flags = query_op->flags; |
|
end_op_ptr->field_index = query_op->field_index; |
|
} |
|
|
|
static |
|
void flecs_rule_begin_block_or( |
|
ecs_rule_op_t *op, |
|
ecs_term_t *term, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_op_t *or_op = flecs_rule_begin_block(EcsRuleNot, ctx); |
|
or_op->kind = EcsRuleOr; |
|
|
|
/* Set the source of the evaluate terms as source of the Or instruction. |
|
* This lets the engine determine whether the variable has already been |
|
* written. When the source is not yet written, an OR operation needs to |
|
* take the union of all the terms in the OR chain. When the variable is |
|
* known, it will return after the first matching term. |
|
* |
|
* In case a term in the OR expression is an equality predicate which |
|
* compares the left hand side with a variable, the variable acts as an |
|
* alias, so we can always assume that it's written. */ |
|
bool add_src = true; |
|
if (term->first.id == EcsPredEq && term->second.flags & EcsIsVariable) { |
|
if (!(flecs_rule_is_written(op->src.var, ctx->written))) { |
|
add_src = false; |
|
} |
|
} |
|
|
|
if (add_src) { |
|
if (op->flags & (EcsRuleIsVar << EcsRuleSrc)) { |
|
or_op->flags = (EcsRuleIsVar << EcsRuleSrc); |
|
or_op->src = op->src; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_rule_end_block_or( |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_op_t op = {0}; |
|
op.kind = EcsRuleEnd; |
|
ecs_rule_lbl_t end = flecs_rule_op_insert(&op, ctx); |
|
|
|
ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); |
|
int32_t i, j, prev_or = -2; |
|
for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { |
|
if (ops[i].next == FlecsRuleOrMarker) { |
|
if (prev_or != -2) { |
|
ops[prev_or].prev = flecs_itolbl(i); |
|
} |
|
ops[i].next = flecs_itolbl(end); |
|
prev_or = i; |
|
} else { |
|
/* Combine operation with next OR marker. This supports OR chains |
|
* with terms that require multiple operations to test. */ |
|
for (j = i + 1; j < end; j ++) { |
|
if (ops[j].next == FlecsRuleOrMarker) { |
|
if (j == (end - 1)) { |
|
ops[i].prev = ctx->cur->lbl_begin; |
|
} else { |
|
ops[i].prev = flecs_itolbl(j + 1); |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
ops[ctx->cur->lbl_begin].next = flecs_itolbl(end); |
|
ops[end].prev = ctx->cur->lbl_begin; |
|
ops[end - 1].prev = ctx->cur->lbl_begin; |
|
|
|
/* Scan which variables were conditionally written in the OR chain and |
|
* reset instructions after the OR chain. If a variable is set in part one |
|
* of a chain but not part two, there would be nothing writing to the |
|
* variable in part two, leaving it to the previous value. To address this |
|
* a reset is inserted that resets the variable value on redo. */ |
|
for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { |
|
ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); |
|
ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); |
|
|
|
if (!prev && cur) { |
|
ecs_rule_op_t reset_op = {0}; |
|
reset_op.kind = EcsRuleReset; |
|
reset_op.flags |= (EcsRuleIsVar << EcsRuleSrc); |
|
reset_op.src.var = flecs_itovar(i); |
|
flecs_rule_op_insert(&reset_op, ctx); |
|
} |
|
} |
|
|
|
ctx->ctrlflow->in_or = false; |
|
ctx->cur->lbl_begin = -1; |
|
} |
|
|
|
static |
|
void flecs_rule_insert_each( |
|
ecs_var_id_t tvar, |
|
ecs_var_id_t evar, |
|
ecs_rule_compile_ctx_t *ctx, |
|
bool cond_write) |
|
{ |
|
ecs_rule_op_t each = {0}; |
|
each.kind = EcsRuleEach; |
|
each.src.var = evar; |
|
each.first.var = tvar; |
|
each.flags = (EcsRuleIsVar << EcsRuleSrc) | |
|
(EcsRuleIsVar << EcsRuleFirst); |
|
flecs_rule_write_ctx(evar, ctx, cond_write); |
|
flecs_rule_write(evar, &each.written); |
|
flecs_rule_op_insert(&each, ctx); |
|
} |
|
|
|
static |
|
void flecs_rule_insert_lookup( |
|
ecs_var_id_t base_var, |
|
ecs_var_id_t evar, |
|
ecs_rule_compile_ctx_t *ctx, |
|
bool cond_write) |
|
{ |
|
ecs_rule_op_t lookup = {0}; |
|
lookup.kind = EcsRuleLookup; |
|
lookup.src.var = evar; |
|
lookup.first.var = base_var; |
|
lookup.flags = (EcsRuleIsVar << EcsRuleSrc) | |
|
(EcsRuleIsVar << EcsRuleFirst); |
|
flecs_rule_write_ctx(evar, ctx, cond_write); |
|
flecs_rule_write(evar, &lookup.written); |
|
flecs_rule_op_insert(&lookup, ctx); |
|
} |
|
|
|
static |
|
void flecs_rule_insert_unconstrained_transitive( |
|
ecs_rule_t *rule, |
|
ecs_rule_op_t *op, |
|
ecs_rule_compile_ctx_t *ctx, |
|
bool cond_write) |
|
{ |
|
/* Create anonymous variable to store the target ids. This will return the |
|
* list of targets without constraining the variable of the term, which |
|
* needs to stay variable to find all transitive relationships for a src. */ |
|
ecs_var_id_t tgt = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); |
|
flecs_set_var_label(&rule->vars[tgt], rule->vars[op->second.var].name); |
|
|
|
/* First, find ids to start traversal from. This fixes op.second. */ |
|
ecs_rule_op_t find_ids = {0}; |
|
find_ids.kind = EcsRuleIdsRight; |
|
find_ids.field_index = -1; |
|
find_ids.first = op->first; |
|
find_ids.second = op->second; |
|
find_ids.flags = op->flags; |
|
find_ids.flags &= (ecs_flags8_t)~((EcsRuleIsVar|EcsRuleIsEntity) << EcsRuleSrc); |
|
find_ids.second.var = tgt; |
|
flecs_rule_write_ctx(tgt, ctx, cond_write); |
|
flecs_rule_write(tgt, &find_ids.written); |
|
flecs_rule_op_insert(&find_ids, ctx); |
|
|
|
/* Next, iterate all tables for the ids. This fixes op.src */ |
|
ecs_rule_op_t and_op = {0}; |
|
and_op.kind = EcsRuleAnd; |
|
and_op.field_index = op->field_index; |
|
and_op.first = op->first; |
|
and_op.second = op->second; |
|
and_op.src = op->src; |
|
and_op.flags = op->flags | EcsRuleIsSelf; |
|
and_op.second.var = tgt; |
|
flecs_rule_write_ctx(and_op.src.var, ctx, cond_write); |
|
flecs_rule_write(and_op.src.var, &and_op.written); |
|
flecs_rule_op_insert(&and_op, ctx); |
|
} |
|
|
|
static |
|
void flecs_rule_insert_inheritance( |
|
ecs_rule_t *rule, |
|
ecs_term_t *term, |
|
ecs_rule_op_t *op, |
|
ecs_rule_compile_ctx_t *ctx, |
|
bool cond_write) |
|
{ |
|
/* Anonymous variable to store the resolved component ids */ |
|
ecs_var_id_t tvar = flecs_rule_add_var(rule, NULL, NULL, EcsVarTable); |
|
ecs_var_id_t evar = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); |
|
flecs_set_var_label(&rule->vars[tvar], ecs_get_name(rule->filter.world, term->first.id)); |
|
flecs_set_var_label(&rule->vars[evar], ecs_get_name(rule->filter.world, term->first.id)); |
|
|
|
ecs_rule_op_t trav_op = {0}; |
|
trav_op.kind = EcsRuleTrav; |
|
trav_op.field_index = -1; |
|
trav_op.first.entity = term->first.trav; |
|
trav_op.second.entity = term->first.id; |
|
trav_op.src.var = tvar; |
|
trav_op.flags = EcsRuleIsSelf; |
|
trav_op.flags |= (EcsRuleIsEntity << EcsRuleFirst); |
|
trav_op.flags |= (EcsRuleIsEntity << EcsRuleSecond); |
|
trav_op.flags |= (EcsRuleIsVar << EcsRuleSrc); |
|
trav_op.written |= (1ull << tvar); |
|
if (term->first.flags & EcsSelf) { |
|
trav_op.match_flags |= EcsTermReflexive; |
|
} |
|
flecs_rule_op_insert(&trav_op, ctx); |
|
flecs_rule_insert_each(tvar, evar, ctx, cond_write); |
|
|
|
ecs_rule_ref_t r = { .var = evar }; |
|
op->first = r; |
|
op->flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleFirst); |
|
op->flags |= (EcsRuleIsVar << EcsRuleFirst); |
|
} |
|
|
|
static |
|
void flecs_rule_compile_term_id( |
|
ecs_world_t *world, |
|
ecs_rule_t *rule, |
|
ecs_rule_op_t *op, |
|
ecs_term_id_t *term_id, |
|
ecs_rule_ref_t *ref, |
|
ecs_flags8_t ref_kind, |
|
ecs_var_kind_t kind, |
|
ecs_rule_compile_ctx_t *ctx, |
|
bool create_wildcard_vars) |
|
{ |
|
(void)world; |
|
|
|
if (!ecs_term_id_is_set(term_id)) { |
|
return; |
|
} |
|
|
|
if (term_id->flags & EcsIsVariable) { |
|
op->flags |= (ecs_flags8_t)(EcsRuleIsVar << ref_kind); |
|
const char *name = flecs_term_id_var_name(term_id); |
|
if (name) { |
|
ref->var = flecs_rule_most_specific_var(rule, name, kind, ctx); |
|
ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); |
|
} else if (create_wildcard_vars) { |
|
bool is_wildcard = flecs_term_id_is_wildcard(term_id); |
|
if (is_wildcard && (kind == EcsVarAny)) { |
|
ref->var = flecs_rule_add_var(rule, NULL, NULL, EcsVarTable); |
|
} else { |
|
ref->var = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); |
|
} |
|
if (is_wildcard) { |
|
flecs_set_var_label(&rule->vars[ref->var], |
|
ecs_get_name(world, term_id->id)); |
|
} |
|
ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
} |
|
|
|
if (term_id->flags & EcsIsEntity) { |
|
op->flags |= (ecs_flags8_t)(EcsRuleIsEntity << ref_kind); |
|
ref->entity = term_id->id; |
|
} |
|
} |
|
|
|
static |
|
int flecs_rule_compile_ensure_vars( |
|
ecs_rule_t *rule, |
|
ecs_rule_op_t *op, |
|
ecs_rule_ref_t *ref, |
|
ecs_flags16_t ref_kind, |
|
ecs_rule_compile_ctx_t *ctx, |
|
bool cond_write, |
|
bool *written_out) |
|
{ |
|
ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); |
|
bool written = false; |
|
|
|
if (flags & EcsRuleIsVar) { |
|
ecs_var_id_t var_id = ref->var; |
|
ecs_rule_var_t *var = &rule->vars[var_id]; |
|
|
|
if (var->kind == EcsVarEntity && |
|
!flecs_rule_is_written(var_id, ctx->written)) |
|
{ |
|
/* If entity variable is not yet written but a table variant exists |
|
* that has been written, insert each operation to translate from |
|
* entity variable to table */ |
|
ecs_var_id_t tvar = var->table_id; |
|
if ((tvar != EcsVarNone) && |
|
flecs_rule_is_written(tvar, ctx->written)) |
|
{ |
|
if (var->lookup) { |
|
if (!flecs_rule_is_written(tvar, ctx->written)) { |
|
ecs_err("dependent variable of '$%s' is not written", |
|
var->name); |
|
return -1; |
|
} |
|
|
|
if (!flecs_rule_is_written(var->base_id, ctx->written)) { |
|
flecs_rule_insert_each( |
|
tvar, var->base_id, ctx, cond_write); |
|
} |
|
} else { |
|
flecs_rule_insert_each(tvar, var_id, ctx, cond_write); |
|
} |
|
|
|
/* Variable was written, just not as entity */ |
|
written = true; |
|
} else if (var->lookup) { |
|
if (!flecs_rule_is_written(var->base_id, ctx->written)) { |
|
ecs_err("dependent variable of '$%s' is not written", |
|
var->name); |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
written |= flecs_rule_is_written(var_id, ctx->written); |
|
} else { |
|
/* If it's not a variable, it's always written */ |
|
written = true; |
|
} |
|
|
|
if (written_out) { |
|
*written_out = written; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
bool flecs_rule_compile_lookup( |
|
ecs_rule_t *rule, |
|
ecs_var_id_t var_id, |
|
ecs_rule_compile_ctx_t *ctx, |
|
bool cond_write) |
|
{ |
|
ecs_rule_var_t *var = &rule->vars[var_id]; |
|
if (var->lookup) { |
|
flecs_rule_insert_lookup(var->base_id, var_id, ctx, cond_write); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
void flecs_rule_insert_contains( |
|
ecs_rule_t *rule, |
|
ecs_var_id_t src_var, |
|
ecs_var_id_t other_var, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_op_t contains = {0}; |
|
if ((src_var != other_var) && (src_var == rule->vars[other_var].table_id)) { |
|
contains.kind = EcsRuleContain; |
|
contains.src.var = src_var; |
|
contains.first.var = other_var; |
|
contains.flags |= (EcsRuleIsVar << EcsRuleSrc) | |
|
(EcsRuleIsVar << EcsRuleFirst); |
|
flecs_rule_op_insert(&contains, ctx); |
|
} |
|
} |
|
|
|
static |
|
void flecs_rule_insert_pair_eq( |
|
int32_t field_index, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_op_t contains = {0}; |
|
contains.kind = EcsRulePairEq; |
|
contains.field_index = flecs_ito(int8_t, field_index); |
|
flecs_rule_op_insert(&contains, ctx); |
|
} |
|
|
|
static |
|
bool flecs_rule_term_fixed_id( |
|
ecs_filter_t *filter, |
|
ecs_term_t *term) |
|
{ |
|
/* Transitive/inherited terms have variable ids */ |
|
if (term->flags & (EcsTermTransitive|EcsTermIdInherited)) { |
|
return false; |
|
} |
|
|
|
/* Or terms can match different ids */ |
|
if (term->oper == EcsOr) { |
|
return false; |
|
} |
|
if ((term != filter->terms) && term[-1].oper == EcsOr) { |
|
return false; |
|
} |
|
|
|
/* Wildcards can assume different ids */ |
|
if (ecs_id_is_wildcard(term->id)) { |
|
return false; |
|
} |
|
|
|
/* Any terms can have fixed ids, but they require special handling */ |
|
if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { |
|
return false; |
|
} |
|
|
|
/* First terms that are Not or Optional require special handling */ |
|
if (term->oper == EcsNot || term->oper == EcsOptional) { |
|
if (term == filter->terms) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
int flecs_rule_compile_builtin_pred( |
|
ecs_rule_t *rule, |
|
ecs_term_t *term, |
|
ecs_rule_op_t *op, |
|
ecs_write_flags_t write_state) |
|
{ |
|
ecs_entity_t id = term->first.id; |
|
|
|
ecs_rule_op_kind_t eq[] = {EcsRulePredEq, EcsRulePredNeq}; |
|
ecs_rule_op_kind_t eq_name[] = {EcsRulePredEqName, EcsRulePredNeqName}; |
|
ecs_rule_op_kind_t eq_match[] = {EcsRulePredEqMatch, EcsRulePredNeqMatch}; |
|
|
|
ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); |
|
|
|
if (id == EcsPredEq) { |
|
if (term->second.flags & EcsIsName) { |
|
op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); |
|
} else { |
|
op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); |
|
} |
|
} else if (id == EcsPredMatch) { |
|
op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); |
|
} |
|
|
|
op->first = op->src; |
|
op->src = (ecs_rule_ref_t){0}; |
|
op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleSrc); |
|
op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleFirst); |
|
op->flags |= EcsRuleIsVar << EcsRuleFirst; |
|
|
|
if (flags_2nd & EcsRuleIsVar) { |
|
if (!(write_state & (1ull << op->second.var))) { |
|
ecs_err("uninitialized variable '%s' on right-hand side of " |
|
"equality operator", |
|
ecs_rule_var_name(rule, op->second.var)); |
|
return -1; |
|
} |
|
} |
|
|
|
if (!(write_state & (1ull << op->first.var))) { |
|
/* If this is an == operator with a right-hand side that resolves to a |
|
* single entity, the left-hand side is allowed to be undefined, as the |
|
* instruction will be evaluated as an assignment. */ |
|
if (op->kind != EcsRulePredEq && op->kind != EcsRulePredEqName) { |
|
ecs_err("uninitialized variable '%s' on left-hand side of " |
|
"equality operator", |
|
ecs_rule_var_name(rule, op->first.var)); |
|
return -1; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static |
|
int flecs_rule_ensure_scope_var( |
|
ecs_rule_t *rule, |
|
ecs_rule_op_t *op, |
|
ecs_rule_ref_t *ref, |
|
ecs_flags16_t ref_kind, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_var_id_t var = ref->var; |
|
|
|
if (rule->vars[var].kind == EcsVarEntity && |
|
!flecs_rule_is_written(var, ctx->written)) |
|
{ |
|
ecs_var_id_t table_var = rule->vars[var].table_id; |
|
if (table_var != EcsVarNone && |
|
flecs_rule_is_written(table_var, ctx->written)) |
|
{ |
|
if (flecs_rule_compile_ensure_vars( |
|
rule, op, ref, ref_kind, ctx, false, NULL)) |
|
{ |
|
goto error; |
|
} |
|
|
|
} |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
int flecs_rule_ensure_scope_vars( |
|
ecs_world_t *world, |
|
ecs_rule_t *rule, |
|
ecs_rule_compile_ctx_t *ctx, |
|
ecs_term_t *term) |
|
{ |
|
/* If the scope uses variables as entity that have only been written as |
|
* table, resolve them as entities before entering the scope. */ |
|
ecs_term_t *cur = term; |
|
while(cur->first.id != EcsScopeClose) { |
|
/* Dummy operation to obtain variable information for term */ |
|
ecs_rule_op_t op = {0}; |
|
flecs_rule_compile_term_id(world, rule, &op, &cur->first, |
|
&op.first, EcsRuleFirst, EcsVarEntity, ctx, false); |
|
flecs_rule_compile_term_id(world, rule, &op, &cur->second, |
|
&op.second, EcsRuleSecond, EcsVarEntity, ctx, false); |
|
|
|
if (op.flags & (EcsRuleIsVar << EcsRuleFirst)) { |
|
if (flecs_rule_ensure_scope_var( |
|
rule, &op, &op.first, EcsRuleFirst, ctx)) |
|
{ |
|
goto error; |
|
} |
|
} |
|
if (op.flags & (EcsRuleIsVar << EcsRuleSecond)) { |
|
if (flecs_rule_ensure_scope_var( |
|
rule, &op, &op.second, EcsRuleSecond, ctx)) |
|
{ |
|
goto error; |
|
} |
|
} |
|
|
|
cur ++; |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
void flecs_rule_compile_push( |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ctx->cur = &ctx->ctrlflow[++ ctx->scope]; |
|
ctx->cur->lbl_begin = -1; |
|
ctx->cur->lbl_begin = -1; |
|
} |
|
|
|
static |
|
void flecs_rule_compile_pop( |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ctx->cur = &ctx->ctrlflow[-- ctx->scope]; |
|
} |
|
|
|
static |
|
bool flecs_rule_term_is_or( |
|
const ecs_filter_t *filter, |
|
const ecs_term_t *term) |
|
{ |
|
bool first_term = term == filter->terms; |
|
return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); |
|
} |
|
|
|
static |
|
int flecs_rule_compile_term( |
|
ecs_world_t *world, |
|
ecs_rule_t *rule, |
|
ecs_term_t *term, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_filter_t *filter = &rule->filter; |
|
bool first_term = term == filter->terms; |
|
bool first_is_var = term->first.flags & EcsIsVariable; |
|
bool second_is_var = term->second.flags & EcsIsVariable; |
|
bool src_is_var = term->src.flags & EcsIsVariable; |
|
bool builtin_pred = flecs_rule_is_builtin_pred(term); |
|
bool is_not = (term->oper == EcsNot) && !builtin_pred; |
|
bool is_or = flecs_rule_term_is_or(filter, term); |
|
bool first_or = false, last_or = false; |
|
bool cond_write = term->oper == EcsOptional || is_or; |
|
ecs_rule_op_t op = {0}; |
|
|
|
if (is_or) { |
|
first_or = first_term || (term[-1].oper != EcsOr); |
|
last_or = term->oper != EcsOr; |
|
} |
|
|
|
/* !_ (don't match anything) terms always return nothing. */ |
|
if (is_not && term->id == EcsAny) { |
|
op.kind = EcsRuleNothing; |
|
flecs_rule_op_insert(&op, ctx); |
|
return 0; |
|
} |
|
|
|
if (is_or && (first_term || term[-1].oper != EcsOr)) { |
|
ctx->ctrlflow->cond_written_or = ctx->cond_written; |
|
ctx->ctrlflow->in_or = true; |
|
} |
|
|
|
if (!term->src.id && term->src.flags & EcsIsEntity) { |
|
/* If the term has a 0 source, check if it's a scope open/close */ |
|
if (term->first.id == EcsScopeOpen) { |
|
if (flecs_rule_ensure_scope_vars(world, rule, ctx, term)) { |
|
goto error; |
|
} |
|
if (term->oper == EcsNot) { |
|
ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); |
|
flecs_rule_begin_block(EcsRuleNot, ctx); |
|
} else { |
|
ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); |
|
} |
|
flecs_rule_compile_push(ctx); |
|
} else if (term->first.id == EcsScopeClose) { |
|
flecs_rule_compile_pop(ctx); |
|
if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { |
|
ctx->cur->lbl_query = -1; |
|
flecs_rule_end_block(ctx); |
|
} |
|
} else { |
|
/* Noop */ |
|
} |
|
return 0; |
|
} |
|
|
|
if (builtin_pred) { |
|
if (term->second.id == EcsWildcard || term->second.id == EcsAny) { |
|
/* Noop */ |
|
return 0; |
|
} |
|
} |
|
|
|
/* Default instruction for And operators. If the source is fixed (like for |
|
* singletons or terms with an entity source), use With, which like And but |
|
* just matches against a source (vs. finding a source). */ |
|
op.kind = src_is_var ? EcsRuleAnd : EcsRuleWith; |
|
op.field_index = flecs_ito(int8_t, term->field_index); |
|
op.term_index = flecs_ito(int8_t, term - filter->terms); |
|
|
|
/* If rule is transitive, use Trav(ersal) instruction */ |
|
if (term->flags & EcsTermTransitive) { |
|
ecs_assert(ecs_term_id_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); |
|
op.kind = EcsRuleTrav; |
|
} else { |
|
/* Ignore cascade & parent flags */ |
|
ecs_flags32_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsParent); |
|
if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { |
|
op.kind = EcsRuleAndAny; |
|
} else if ((term->src.flags & trav_flags) == EcsUp) { |
|
op.kind = EcsRuleUp; |
|
} else if ((term->src.flags & trav_flags) == (EcsSelf|EcsUp)) { |
|
op.kind = EcsRuleSelfUp; |
|
} |
|
} |
|
|
|
/* If term has fixed id, insert simpler instruction that skips dealing with |
|
* wildcard terms and variables */ |
|
if (flecs_rule_term_fixed_id(filter, term)) { |
|
if (op.kind == EcsRuleAnd) { |
|
op.kind = EcsRuleAndId; |
|
} else if (op.kind == EcsRuleSelfUp) { |
|
op.kind = EcsRuleSelfUpId; |
|
} else if (op.kind == EcsRuleUp) { |
|
op.kind = EcsRuleUpId; |
|
} |
|
} |
|
|
|
/* Save write state at start of term so we can use it to reliably track |
|
* variables got written by this term. */ |
|
ecs_write_flags_t cond_write_state = ctx->cond_written; |
|
|
|
/* Resolve component inheritance if necessary */ |
|
ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; |
|
|
|
/* Resolve variables and entities for operation arguments */ |
|
flecs_rule_compile_term_id(world, rule, &op, &term->first, |
|
&op.first, EcsRuleFirst, EcsVarEntity, ctx, true); |
|
flecs_rule_compile_term_id(world, rule, &op, &term->second, |
|
&op.second, EcsRuleSecond, EcsVarEntity, ctx, true); |
|
|
|
if (first_is_var) first_var = op.first.var; |
|
if (second_is_var) second_var = op.second.var; |
|
|
|
flecs_rule_compile_term_id(world, rule, &op, &term->src, |
|
&op.src, EcsRuleSrc, EcsVarAny, ctx, true); |
|
if (src_is_var) src_var = op.src.var; |
|
bool src_written = flecs_rule_is_written(src_var, ctx->written); |
|
|
|
/* Insert each instructions for table -> entity variable if needed */ |
|
bool first_written, second_written; |
|
if (flecs_rule_compile_ensure_vars( |
|
rule, &op, &op.first, EcsRuleFirst, ctx, cond_write, &first_written)) |
|
{ |
|
goto error; |
|
} |
|
if (flecs_rule_compile_ensure_vars( |
|
rule, &op, &op.second, EcsRuleSecond, ctx, cond_write, &second_written)) |
|
{ |
|
goto error; |
|
} |
|
|
|
/* If the query starts with a Not or Optional term, insert an operation that |
|
* matches all entities. */ |
|
if (first_term && src_is_var && !src_written && term->src.id != EcsAny) { |
|
bool pred_match = builtin_pred && term->first.id == EcsPredMatch; |
|
if (term->oper == EcsNot || term->oper == EcsOptional || pred_match) { |
|
ecs_rule_op_t match_any = {0}; |
|
match_any.kind = EcsAnd; |
|
match_any.flags = EcsRuleIsSelf | (EcsRuleIsEntity << EcsRuleFirst); |
|
match_any.flags |= (EcsRuleIsVar << EcsRuleSrc); |
|
match_any.src = op.src; |
|
match_any.field_index = -1; |
|
if (!pred_match) { |
|
match_any.first.entity = EcsAny; |
|
} else { |
|
/* If matching by name, instead of finding all tables, just find |
|
* the ones with a name. */ |
|
match_any.first.entity = ecs_id(EcsIdentifier); |
|
match_any.second.entity = EcsName; |
|
match_any.flags |= (EcsRuleIsEntity << EcsRuleSecond); |
|
} |
|
match_any.written = (1ull << src_var); |
|
flecs_rule_op_insert(&match_any, ctx); |
|
flecs_rule_write_ctx(op.src.var, ctx, false); |
|
|
|
/* Update write administration */ |
|
src_written = true; |
|
} |
|
} |
|
|
|
/* A bit of special logic for OR expressions and equality predicates. If the |
|
* left-hand of an equality operator is a table, and there are multiple |
|
* operators in an Or expression, the Or chain should match all entities in |
|
* the table that match the right hand sides of the operator expressions. |
|
* For this to work, the src variable needs to be resolved as entity, as an |
|
* Or chain would otherwise only yield the first match from a table. */ |
|
if (src_is_var && src_written && builtin_pred && term->oper == EcsOr) { |
|
/* Or terms are required to have the same source, so we don't have to |
|
* worry about the last term in the chain. */ |
|
if (rule->vars[src_var].kind == EcsVarTable) { |
|
flecs_rule_compile_term_id(world, rule, &op, &term->src, |
|
&op.src, EcsRuleSrc, EcsVarEntity, ctx, true); |
|
src_var = op.src.var; |
|
} |
|
} |
|
|
|
if (flecs_rule_compile_ensure_vars( |
|
rule, &op, &op.src, EcsRuleSrc, ctx, cond_write, NULL)) |
|
{ |
|
goto error; |
|
} |
|
|
|
/* If source is Any (_) and first and/or second are unconstrained, insert an |
|
* ids instruction instead of an And */ |
|
if (term->flags & EcsTermMatchAnySrc) { |
|
op.kind = EcsRuleIds; |
|
/* Use up-to-date written values after potentially inserting each */ |
|
if (!first_written || !second_written) { |
|
if (!first_written) { |
|
/* If first is unknown, traverse left: <- (*, t) */ |
|
if (term->first.id != EcsAny) { |
|
op.kind = EcsRuleIdsLeft; |
|
} |
|
} else { |
|
/* If second is wildcard, traverse right: (r, *) -> */ |
|
if (term->second.id != EcsAny) { |
|
op.kind = EcsRuleIdsRight; |
|
} |
|
} |
|
op.src.entity = 0; |
|
op.flags &= (ecs_flags8_t)~(EcsRuleIsVar << EcsRuleSrc); /* ids has no src */ |
|
op.flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleSrc); |
|
} |
|
|
|
/* If source variable is not written and we're querying just for Any, insert |
|
* a dedicated instruction that uses the Any record in the id index. Any |
|
* queries that are evaluated against written sources can use Wildcard |
|
* records, which is what the AndAny instruction does. */ |
|
} else if (!src_written && term->id == EcsAny && op.kind == EcsRuleAndAny) { |
|
/* Lookup variables ($var.child_name) are always written */ |
|
if (!rule->vars[src_var].lookup) { |
|
op.kind = EcsRuleSelectAny; /* Uses Any (_) id record */ |
|
} |
|
} |
|
|
|
/* If this is a transitive term and both the target and source are unknown, |
|
* find the targets for the relationship first. This clusters together |
|
* tables for the same target, which allows for more efficient usage of the |
|
* traversal caches. */ |
|
if (term->flags & EcsTermTransitive && src_is_var && second_is_var) { |
|
if (!src_written && !second_written) { |
|
flecs_rule_insert_unconstrained_transitive( |
|
rule, &op, ctx, cond_write); |
|
} |
|
} |
|
|
|
/* Check if this term has variables that have been conditionally written, |
|
* like variables written by an optional term. */ |
|
if (ctx->cond_written) { |
|
if (!is_or || first_or) { |
|
flecs_rule_begin_block_cond_eval(&op, ctx, cond_write_state); |
|
} |
|
} |
|
|
|
/* Handle Not, Optional, Or operators */ |
|
if (is_not) { |
|
flecs_rule_begin_block(EcsRuleNot, ctx); |
|
} else if (term->oper == EcsOptional) { |
|
flecs_rule_begin_block(EcsRuleOptional, ctx); |
|
} else if (first_or) { |
|
flecs_rule_begin_block_or(&op, term, ctx); |
|
} |
|
|
|
/* If term has component inheritance enabled, insert instruction to walk |
|
* down the relationship tree of the id. */ |
|
if (term->flags & EcsTermIdInherited) { |
|
flecs_rule_insert_inheritance(rule, term, &op, ctx, cond_write); |
|
} |
|
|
|
op.match_flags = term->flags; |
|
|
|
if (first_is_var) { |
|
op.first.var = first_var; |
|
op.flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleFirst); |
|
op.flags |= (EcsRuleIsVar << EcsRuleFirst); |
|
} |
|
|
|
if (term->src.flags & EcsSelf) { |
|
op.flags |= EcsRuleIsSelf; |
|
} |
|
|
|
/* Insert instructions for lookup variables */ |
|
ecs_write_flags_t write_state = ctx->written; |
|
if (first_is_var) { |
|
if (flecs_rule_compile_lookup(rule, first_var, ctx, cond_write)) { |
|
write_state |= (1ull << first_var); // lookups are resolved inline |
|
} |
|
} |
|
if (src_is_var) { |
|
if (flecs_rule_compile_lookup(rule, src_var, ctx, cond_write)) { |
|
write_state |= (1ull << src_var); // lookups are resolved inline |
|
} |
|
} |
|
if (second_is_var) { |
|
if (flecs_rule_compile_lookup(rule, second_var, ctx, cond_write)) { |
|
write_state |= (1ull << second_var); // lookups are resolved inline |
|
} |
|
} |
|
|
|
if (builtin_pred) { |
|
if (flecs_rule_compile_builtin_pred(rule, term, &op, write_state)) { |
|
goto error; |
|
} |
|
} |
|
|
|
/* If we're writing the $this variable, filter out disabled/prefab entities |
|
* unless the filter explicitly matches them. |
|
* This could've been done with regular With instructions, but since |
|
* filtering out disabled/prefab entities is the default and this check is |
|
* cheap to perform on table flags, it's worth special casing. */ |
|
if (!src_written && src_var == 0) { |
|
ecs_flags32_t filter_flags = filter->flags; |
|
if (!(filter_flags & EcsFilterMatchDisabled) || |
|
!(filter_flags & EcsFilterMatchPrefab)) |
|
{ |
|
ecs_flags32_t table_flags = 0; |
|
if (!(filter_flags & EcsFilterMatchDisabled)) { |
|
table_flags |= EcsTableIsDisabled; |
|
} |
|
if (!(filter_flags & EcsFilterMatchPrefab)) { |
|
table_flags |= EcsTableIsPrefab; |
|
} |
|
|
|
op.other = flecs_itolbl(table_flags); |
|
} |
|
} |
|
|
|
/* After evaluating a term, a used variable is always written */ |
|
if (src_is_var) { |
|
flecs_rule_write(src_var, &op.written); |
|
flecs_rule_write_ctx(op.src.var, ctx, cond_write); |
|
} |
|
if (first_is_var) { |
|
flecs_rule_write(first_var, &op.written); |
|
flecs_rule_write_ctx(first_var, ctx, cond_write); |
|
} |
|
if (second_is_var) { |
|
flecs_rule_write(second_var, &op.written); |
|
flecs_rule_write_ctx(second_var, ctx, cond_write); |
|
} |
|
flecs_rule_op_insert(&op, ctx); |
|
|
|
ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); |
|
if (is_or) { |
|
ecs_rule_op_t *op_ptr = ecs_vec_get_t(ctx->ops, ecs_rule_op_t, |
|
ctx->cur->lbl_query); |
|
op_ptr->next = FlecsRuleOrMarker; |
|
} |
|
|
|
/* Handle self-references between src and first/second variables */ |
|
if (src_is_var) { |
|
if (first_is_var) { |
|
flecs_rule_insert_contains(rule, src_var, first_var, ctx); |
|
} |
|
if (second_is_var && first_var != second_var) { |
|
flecs_rule_insert_contains(rule, src_var, second_var, ctx); |
|
} |
|
} |
|
|
|
/* Handle self references between first and second variables */ |
|
if (first_is_var && !first_written && (first_var == second_var)) { |
|
flecs_rule_insert_pair_eq(term->field_index, ctx); |
|
} |
|
|
|
/* Handle closing of Not, Optional and Or operators */ |
|
if (is_not) { |
|
flecs_rule_end_block(ctx); |
|
} else if (term->oper == EcsOptional) { |
|
flecs_rule_end_block(ctx); |
|
} else if (last_or) { |
|
flecs_rule_end_block_or(ctx); |
|
} |
|
|
|
/* Handle closing of conditional evaluation */ |
|
if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { |
|
if (!is_or || last_or) { |
|
flecs_rule_end_block_cond_eval(ctx); |
|
} |
|
} |
|
|
|
/* Ensure that term id is set after evaluating Not */ |
|
if (term->flags & EcsTermIdInherited) { |
|
if (is_not) { |
|
ecs_rule_op_t set_id = {0}; |
|
set_id.kind = EcsRuleSetId; |
|
set_id.first.entity = term->id; |
|
set_id.flags = (EcsRuleIsEntity << EcsRuleFirst); |
|
set_id.field_index = flecs_ito(int8_t, term->field_index); |
|
flecs_rule_op_insert(&set_id, ctx); |
|
} |
|
} |
|
|
|
return 0; |
|
error: |
|
return -1; |
|
} |
|
|
|
static |
|
bool flecs_rule_var_is_unknown( |
|
ecs_rule_t *rule, |
|
ecs_var_id_t var_id, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_var_t *vars = rule->vars; |
|
if (ctx->written & (1ull << var_id)) { |
|
return false; |
|
} else { |
|
ecs_var_id_t table_var = vars[var_id].table_id; |
|
if (table_var != EcsVarNone) { |
|
return flecs_rule_var_is_unknown(rule, table_var, ctx); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
/* Returns whether term is unkown. A term is unknown when it has variable |
|
* elements (first, second, src) that are all unknown. */ |
|
static |
|
bool flecs_rule_term_is_unknown( |
|
ecs_rule_t *rule, |
|
ecs_term_t *term, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_rule_op_t dummy = {0}; |
|
flecs_rule_compile_term_id(NULL, rule, &dummy, &term->first, |
|
&dummy.first, EcsRuleFirst, EcsVarEntity, ctx, false); |
|
flecs_rule_compile_term_id(NULL, rule, &dummy, &term->second, |
|
&dummy.second, EcsRuleSecond, EcsVarEntity, ctx, false); |
|
flecs_rule_compile_term_id(NULL, rule, &dummy, &term->src, |
|
&dummy.src, EcsRuleSrc, EcsVarAny, ctx, false); |
|
|
|
bool has_vars = dummy.flags & |
|
((EcsRuleIsVar << EcsRuleFirst) | |
|
(EcsRuleIsVar << EcsRuleSecond) | |
|
(EcsRuleIsVar << EcsRuleSrc)); |
|
if (!has_vars) { |
|
/* If term has no variables (typically terms with a static src) there |
|
* can't be anything that's unknown. */ |
|
return false; |
|
} |
|
|
|
if (dummy.flags & (EcsRuleIsVar << EcsRuleFirst)) { |
|
if (!flecs_rule_var_is_unknown(rule, dummy.first.var, ctx)) { |
|
return false; |
|
} |
|
} |
|
if (dummy.flags & (EcsRuleIsVar << EcsRuleSecond)) { |
|
if (!flecs_rule_var_is_unknown(rule, dummy.second.var, ctx)) { |
|
return false; |
|
} |
|
} |
|
if (dummy.flags & (EcsRuleIsVar << EcsRuleSrc)) { |
|
if (!flecs_rule_var_is_unknown(rule, dummy.src.var, ctx)) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/* Find the next known term from specified offset. This function is used to find |
|
* a term that can be evaluated before a term that is unknown. Evaluating known |
|
* before unknown terms can significantly decrease the search space. */ |
|
static |
|
int32_t flecs_rule_term_next_known( |
|
ecs_rule_t *rule, |
|
ecs_rule_compile_ctx_t *ctx, |
|
int32_t offset, |
|
ecs_flags64_t compiled) |
|
{ |
|
ecs_filter_t *filter = &rule->filter; |
|
ecs_term_t *terms = filter->terms; |
|
int32_t i, count = filter->term_count; |
|
|
|
for (i = offset; i < count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
if (compiled & (1ull << i)) { |
|
continue; |
|
} |
|
|
|
/* Only evaluate And terms */ |
|
if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ |
|
continue; |
|
} |
|
|
|
/* Don't reorder terms before/after scopes */ |
|
if (term->first.id == EcsScopeOpen || term->first.id == EcsScopeClose) { |
|
return -1; |
|
} |
|
|
|
if (flecs_rule_term_is_unknown(rule, term, ctx)) { |
|
continue; |
|
} |
|
|
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
/* If the first part of a query contains more than one trivial term, insert a |
|
* special instruction which batch-evaluates multiple terms. */ |
|
static |
|
int32_t flecs_rule_insert_trivial_search( |
|
ecs_rule_t *rule, |
|
ecs_rule_compile_ctx_t *ctx) |
|
{ |
|
ecs_filter_t *filter = &rule->filter; |
|
ecs_term_t *terms = filter->terms; |
|
int32_t i, term_count = filter->term_count; |
|
|
|
/* Find trivial terms, which can be handled in single instruction */ |
|
int32_t trivial_wildcard_terms = 0; |
|
int32_t trivial_data_terms = 0; |
|
for (i = 0; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
if (!(term->flags & EcsTermIsTrivial)) { |
|
break; |
|
} |
|
|
|
/* We can only add trivial terms to plan if they no up traversal */ |
|
if ((term->src.flags & EcsTraverseFlags) != EcsSelf) { |
|
break; |
|
} |
|
|
|
if (ecs_id_is_wildcard(term->id)) { |
|
trivial_wildcard_terms ++; |
|
} |
|
|
|
if (!(term->flags & EcsTermNoData)) { |
|
trivial_data_terms ++; |
|
} |
|
} |
|
|
|
int32_t trivial_terms = i; |
|
if (trivial_terms >= 2) { |
|
/* If there's more than 1 trivial term, batch them in trivial search */ |
|
ecs_rule_op_t trivial = {0}; |
|
if (trivial_wildcard_terms) { |
|
trivial.kind = EcsRuleTrivWildcard; |
|
} else { |
|
if (trivial_data_terms) { |
|
/* Check to see if there are remaining data terms. If there are, |
|
* we'll have to insert an instruction later that populates all |
|
* fields, so don't do double work here. */ |
|
for (i = trivial_terms; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
if (!(term->flags & EcsTermIsTrivial)) { |
|
break; |
|
} |
|
} |
|
if (trivial_terms == term_count || i != term_count) { |
|
/* Nobody else is going to set the data fields, so we should |
|
* do it here. */ |
|
trivial.kind = EcsRuleTrivData; |
|
} |
|
} |
|
if (!trivial.kind) { |
|
trivial.kind = EcsRuleTriv; |
|
} |
|
} |
|
|
|
/* Store on the operation how many trivial terms should be evaluated */ |
|
trivial.other = (ecs_rule_lbl_t)trivial_terms; |
|
flecs_rule_op_insert(&trivial, ctx); |
|
} else { |
|
/* If fewer than 1 trivial term, there's no point in batching them */ |
|
trivial_terms = 0; |
|
} |
|
|
|
return trivial_terms; |
|
} |
|
|
|
/* Insert instruction to populate data fields. */ |
|
static |
|
void flecs_rule_insert_populate( |
|
ecs_rule_t *rule, |
|
ecs_rule_compile_ctx_t *ctx, |
|
int32_t trivial_terms) |
|
{ |
|
ecs_filter_t *filter = &rule->filter; |
|
int32_t i, term_count = filter->term_count; |
|
|
|
/* Insert instruction that populates data. This instruction does not |
|
* have to be inserted if the filter provides no data, or if all terms |
|
* of the filter are trivial, in which case the trivial search operation |
|
* also sets the data. */ |
|
if (!(filter->flags & EcsFilterNoData) && (trivial_terms != term_count)) { |
|
int32_t data_fields = 0; |
|
bool only_self = true; |
|
|
|
/* There are two instructions for setting data fields, a fast one |
|
* that only supports owned fields, and one that supports any kind |
|
* of field. Loop through (remaining) terms to check which one we |
|
* need to use. */ |
|
for (i = trivial_terms; i < term_count; i ++) { |
|
ecs_term_t *term = &filter->terms[i]; |
|
if (term->flags & EcsTermNoData) { |
|
/* Don't care about terms that have no data */ |
|
continue; |
|
} |
|
|
|
data_fields ++; |
|
|
|
if (!ecs_term_match_this(term)) { |
|
break; |
|
} |
|
|
|
if (term->src.flags & EcsUp) { |
|
break; |
|
} |
|
} |
|
|
|
if (i != filter->term_count) { |
|
only_self = false; /* Needs the more complex operation */ |
|
} |
|
|
|
if (data_fields) { |
|
if (only_self) { |
|
ecs_rule_op_t nothing = {0}; |
|
nothing.kind = EcsRulePopulateSelf; |
|
flecs_rule_op_insert(¬hing, ctx); |
|
} else { |
|
ecs_rule_op_t nothing = {0}; |
|
nothing.kind = EcsRulePopulate; |
|
flecs_rule_op_insert(¬hing, ctx); |
|
} |
|
} |
|
} |
|
} |
|
|
|
int flecs_rule_compile( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_rule_t *rule) |
|
{ |
|
ecs_filter_t *filter = &rule->filter; |
|
ecs_term_t *terms = filter->terms; |
|
ecs_rule_compile_ctx_t ctx = {0}; |
|
ecs_vec_reset_t(NULL, &stage->operations, ecs_rule_op_t); |
|
ctx.ops = &stage->operations; |
|
ctx.cur = ctx.ctrlflow; |
|
ctx.cur->lbl_begin = -1; |
|
ctx.cur->lbl_begin = -1; |
|
ecs_vec_clear(ctx.ops); |
|
|
|
/* Find all variables defined in query */ |
|
if (flecs_rule_discover_vars(stage, rule)) { |
|
return -1; |
|
} |
|
|
|
/* If rule contains fixed source terms, insert operation to set sources */ |
|
int32_t i, term_count = filter->term_count; |
|
for (i = 0; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
if (term->src.flags & EcsIsEntity) { |
|
ecs_rule_op_t set_fixed = {0}; |
|
set_fixed.kind = EcsRuleSetFixed; |
|
flecs_rule_op_insert(&set_fixed, &ctx); |
|
break; |
|
} |
|
} |
|
|
|
/* If the rule contains terms with fixed ids (no wildcards, variables), |
|
* insert instruction that initializes ecs_iter_t::ids. This allows for the |
|
* insertion of simpler instructions later on. */ |
|
for (i = 0; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
if (flecs_rule_term_fixed_id(filter, term) || |
|
(term->src.flags & EcsIsEntity && !term->src.id)) |
|
{ |
|
ecs_rule_op_t set_ids = {0}; |
|
set_ids.kind = EcsRuleSetIds; |
|
flecs_rule_op_insert(&set_ids, &ctx); |
|
break; |
|
} |
|
} |
|
|
|
/* Insert trivial term search if query allows for it */ |
|
int32_t trivial_terms = flecs_rule_insert_trivial_search(rule, &ctx); |
|
|
|
/* Compile remaining query terms to instructions */ |
|
ecs_flags64_t compiled = 0; |
|
for (i = trivial_terms; i < term_count; i ++) { |
|
ecs_term_t *term = &terms[i]; |
|
int32_t compile = i; |
|
|
|
if (compiled & (1ull << i)) { |
|
continue; /* Already compiled */ |
|
} |
|
|
|
bool can_reorder = true; |
|
if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ |
|
can_reorder = false; |
|
} |
|
|
|
/* If variables have been written, but this term has no known variables, |
|
* first try to resolve terms that have known variables. This can |
|
* significantly reduce the search space. |
|
* Only perform this optimization after at least one variable has been |
|
* written to, as all terms are unknown otherwise. */ |
|
if (can_reorder && ctx.written && |
|
flecs_rule_term_is_unknown(rule, term, &ctx)) |
|
{ |
|
int32_t term_index = flecs_rule_term_next_known( |
|
rule, &ctx, i + 1, compiled); |
|
if (term_index != -1) { |
|
term = &rule->filter.terms[term_index]; |
|
compile = term_index; |
|
i --; /* Repeat current term */ |
|
} |
|
} |
|
|
|
if (flecs_rule_compile_term(world, rule, term, &ctx)) { |
|
return -1; |
|
} |
|
|
|
compiled |= (1ull << compile); |
|
} |
|
|
|
ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); |
|
|
|
/* If This variable has been written as entity, insert an operation to |
|
* assign it to it.entities for consistency. */ |
|
if (this_id != EcsVarNone && (ctx.written & (1ull << this_id))) { |
|
ecs_rule_op_t set_this = {0}; |
|
set_this.kind = EcsRuleSetThis; |
|
set_this.flags |= (EcsRuleIsVar << EcsRuleFirst); |
|
set_this.first.var = this_id; |
|
flecs_rule_op_insert(&set_this, &ctx); |
|
} |
|
|
|
/* Make sure non-This variables are written as entities */ |
|
if (rule->vars) { |
|
for (i = 0; i < rule->var_count; i ++) { |
|
ecs_rule_var_t *var = &rule->vars[i]; |
|
if (var->id && var->kind == EcsVarTable && var->name) { |
|
ecs_var_id_t var_id = flecs_rule_find_var_id(rule, var->name, |
|
EcsVarEntity); |
|
if (!flecs_rule_is_written(var_id, ctx.written)) { |
|
/* Skip anonymous variables */ |
|
if (!flecs_rule_var_is_anonymous(rule, var_id)) { |
|
flecs_rule_insert_each(var->id, var_id, &ctx, false); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* If rule contains non-This variables as term source, build lookup array */ |
|
if (rule->src_vars) { |
|
ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); |
|
bool only_anonymous = true; |
|
|
|
for (i = 0; i < filter->field_count; i ++) { |
|
ecs_var_id_t var_id = rule->src_vars[i]; |
|
if (!var_id) { |
|
continue; |
|
} |
|
|
|
if (!flecs_rule_var_is_anonymous(rule, var_id)) { |
|
only_anonymous = false; |
|
break; |
|
} else { |
|
/* Don't fetch component data for anonymous variables. Because |
|
* not all metadata (such as it.sources) is initialized for |
|
* anonymous variables, and because they may only be available |
|
* as table variables (each is not guaranteed to be inserted for |
|
* anonymous variables) the iterator may not have sufficient |
|
* information to resolve component data. */ |
|
for (int32_t t = 0; t < filter->term_count; t ++) { |
|
ecs_term_t *term = &filter->terms[t]; |
|
if (term->field_index == i) { |
|
term->inout = EcsInOutNone; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Don't insert setvar instruction if all vars are anonymous */ |
|
if (!only_anonymous) { |
|
ecs_rule_op_t set_vars = {0}; |
|
set_vars.kind = EcsRuleSetVars; |
|
flecs_rule_op_insert(&set_vars, &ctx); |
|
} |
|
|
|
for (i = 0; i < filter->field_count; i ++) { |
|
ecs_var_id_t var_id = rule->src_vars[i]; |
|
if (!var_id) { |
|
continue; |
|
} |
|
|
|
if (rule->vars[var_id].kind == EcsVarTable) { |
|
var_id = flecs_rule_find_var_id(rule, rule->vars[var_id].name, |
|
EcsVarEntity); |
|
|
|
/* Variables used as source that aren't This must be entities */ |
|
ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
rule->src_vars[i] = var_id; |
|
} |
|
} |
|
|
|
/* If filter is empty, insert Nothing instruction */ |
|
if (!term_count) { |
|
ecs_rule_op_t nothing = {0}; |
|
nothing.kind = EcsRuleNothing; |
|
flecs_rule_op_insert(¬hing, &ctx); |
|
} else { |
|
/* Insert instruction to populate data fields */ |
|
flecs_rule_insert_populate(rule, &ctx, trivial_terms); |
|
|
|
/* Insert yield. If program reaches this operation, a result was found */ |
|
ecs_rule_op_t yield = {0}; |
|
yield.kind = EcsRuleYield; |
|
flecs_rule_op_insert(&yield, &ctx); |
|
} |
|
|
|
int32_t op_count = ecs_vec_count(ctx.ops); |
|
if (op_count) { |
|
rule->op_count = op_count; |
|
rule->ops = ecs_os_malloc_n(ecs_rule_op_t, op_count); |
|
ecs_rule_op_t *rule_ops = ecs_vec_first_t(ctx.ops, ecs_rule_op_t); |
|
ecs_os_memcpy_n(rule->ops, rule_ops, ecs_rule_op_t, op_count); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/rules/engine.c |
|
* @brief Rules engine implementation. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_RULES |
|
|
|
static |
|
bool flecs_rule_dispatch( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx); |
|
|
|
static |
|
bool flecs_rule_run_until( |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx, |
|
const ecs_rule_op_t *ops, |
|
ecs_rule_lbl_t first, |
|
ecs_rule_lbl_t cur, |
|
ecs_rule_op_kind_t until); |
|
|
|
ecs_allocator_t* flecs_rule_get_allocator( |
|
const ecs_iter_t *it) |
|
{ |
|
ecs_world_t *world = it->world; |
|
if (ecs_poly_is(world, ecs_world_t)) { |
|
return &world->allocator; |
|
} else { |
|
ecs_assert(ecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); |
|
return &((ecs_stage_t*)world)->allocator; |
|
} |
|
} |
|
|
|
static |
|
ecs_rule_op_ctx_t* flecs_op_ctx_( |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
return &ctx->op_ctx[ctx->op_index]; |
|
} |
|
|
|
#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) |
|
|
|
static |
|
void flecs_reset_source_set_flag( |
|
const ecs_rule_run_ctx_t *ctx, |
|
int32_t field_index) |
|
{ |
|
ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); |
|
(*ctx->source_set) &= ~(1u << field_index); |
|
} |
|
|
|
static |
|
void flecs_set_source_set_flag( |
|
const ecs_rule_run_ctx_t *ctx, |
|
int32_t field_index) |
|
{ |
|
ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); |
|
(*ctx->source_set) |= (1u << field_index); |
|
} |
|
|
|
static |
|
ecs_table_range_t flecs_range_from_entity( |
|
ecs_entity_t e, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_record_t *r = flecs_entities_get(ctx->world, e); |
|
if (!r) { |
|
return (ecs_table_range_t){ 0 }; |
|
} |
|
return (ecs_table_range_t){ |
|
.table = r->table, |
|
.offset = ECS_RECORD_TO_ROW(r->row), |
|
.count = 1 |
|
}; |
|
} |
|
|
|
static |
|
ecs_table_range_t flecs_rule_var_get_range( |
|
int32_t var_id, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_assert(var_id < ctx->rule->var_count, ECS_INTERNAL_ERROR, NULL); |
|
ecs_var_t *var = &ctx->vars[var_id]; |
|
ecs_table_t *table = var->range.table; |
|
if (table) { |
|
return var->range; |
|
} |
|
|
|
ecs_entity_t entity = var->entity; |
|
if (entity && entity != EcsWildcard) { |
|
var->range = flecs_range_from_entity(entity, ctx); |
|
return var->range; |
|
} |
|
|
|
return (ecs_table_range_t){ 0 }; |
|
} |
|
|
|
static |
|
ecs_table_t* flecs_rule_var_get_table( |
|
int32_t var_id, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_var_t *var = &ctx->vars[var_id]; |
|
ecs_table_t *table = var->range.table; |
|
if (table) { |
|
return table; |
|
} |
|
|
|
ecs_entity_t entity = var->entity; |
|
if (entity && entity != EcsWildcard) { |
|
var->range = flecs_range_from_entity(entity, ctx); |
|
return var->range.table; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static |
|
ecs_table_t* flecs_rule_get_table( |
|
const ecs_rule_op_t *op, |
|
const ecs_rule_ref_t *ref, |
|
ecs_flags16_t ref_kind, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); |
|
if (flags & EcsRuleIsEntity) { |
|
return ecs_get_table(ctx->world, ref->entity); |
|
} else { |
|
return flecs_rule_var_get_table(ref->var, ctx); |
|
} |
|
} |
|
|
|
static |
|
ecs_table_range_t flecs_rule_get_range( |
|
const ecs_rule_op_t *op, |
|
const ecs_rule_ref_t *ref, |
|
ecs_flags16_t ref_kind, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); |
|
if (flags & EcsRuleIsEntity) { |
|
ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); |
|
return flecs_range_from_entity(ref->entity, ctx); |
|
} else { |
|
ecs_var_t *var = &ctx->vars[ref->var]; |
|
if (var->range.table) { |
|
return ctx->vars[ref->var].range; |
|
} else if (var->entity) { |
|
return flecs_range_from_entity(var->entity, ctx); |
|
} |
|
} |
|
return (ecs_table_range_t){0}; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_rule_var_get_entity( |
|
ecs_var_id_t var_id, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_assert(var_id < (ecs_var_id_t)ctx->rule->var_count, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_var_t *var = &ctx->vars[var_id]; |
|
ecs_entity_t entity = var->entity; |
|
if (entity) { |
|
return entity; |
|
} |
|
|
|
ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_t *table = var->range.table; |
|
ecs_entity_t *entities = table->data.entities.array; |
|
var->entity = entities[var->range.offset]; |
|
return var->entity; |
|
} |
|
|
|
static |
|
void flecs_rule_var_reset( |
|
ecs_var_id_t var_id, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ctx->vars[var_id].entity = EcsWildcard; |
|
ctx->vars[var_id].range.table = NULL; |
|
} |
|
|
|
static |
|
void flecs_rule_var_set_table( |
|
const ecs_rule_op_t *op, |
|
ecs_var_id_t var_id, |
|
ecs_table_t *table, |
|
int32_t offset, |
|
int32_t count, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
(void)op; |
|
ecs_assert(ctx->rule_vars[var_id].kind == EcsVarTable, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(flecs_rule_is_written(var_id, op->written), |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_var_t *var = &ctx->vars[var_id]; |
|
var->entity = 0; |
|
var->range = (ecs_table_range_t){ |
|
.table = table, |
|
.offset = offset, |
|
.count = count |
|
}; |
|
} |
|
|
|
static |
|
void flecs_rule_var_set_entity( |
|
const ecs_rule_op_t *op, |
|
ecs_var_id_t var_id, |
|
ecs_entity_t entity, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
(void)op; |
|
ecs_assert(var_id < (ecs_var_id_t)ctx->rule->var_count, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(flecs_rule_is_written(var_id, op->written), |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_var_t *var = &ctx->vars[var_id]; |
|
var->range.table = NULL; |
|
var->entity = entity; |
|
} |
|
|
|
static |
|
void flecs_rule_set_vars( |
|
const ecs_rule_op_t *op, |
|
ecs_id_t id, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); |
|
ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); |
|
|
|
if (flags_1st & EcsRuleIsVar) { |
|
ecs_var_id_t var = op->first.var; |
|
if (op->written & (1ull << var)) { |
|
if (ECS_IS_PAIR(id)) { |
|
flecs_rule_var_set_entity( |
|
op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); |
|
} else { |
|
flecs_rule_var_set_entity(op, var, id, ctx); |
|
} |
|
} |
|
} |
|
if (flags_2nd & EcsRuleIsVar) { |
|
ecs_var_id_t var = op->second.var; |
|
if (op->written & (1ull << var)) { |
|
flecs_rule_var_set_entity( |
|
op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); |
|
} |
|
} |
|
} |
|
|
|
static |
|
ecs_table_range_t flecs_get_ref_range( |
|
const ecs_rule_ref_t *ref, |
|
ecs_flags16_t flag, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (flag & EcsRuleIsEntity) { |
|
return flecs_range_from_entity(ref->entity, ctx); |
|
} else if (flag & EcsRuleIsVar) { |
|
return flecs_rule_var_get_range(ref->var, ctx); |
|
} |
|
return (ecs_table_range_t){0}; |
|
} |
|
|
|
static |
|
ecs_entity_t flecs_get_ref_entity( |
|
const ecs_rule_ref_t *ref, |
|
ecs_flags16_t flag, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (flag & EcsRuleIsEntity) { |
|
return ref->entity; |
|
} else if (flag & EcsRuleIsVar) { |
|
return flecs_rule_var_get_entity(ref->var, ctx); |
|
} |
|
return 0; |
|
} |
|
|
|
static |
|
ecs_id_t flecs_rule_op_get_id_w_written( |
|
const ecs_rule_op_t *op, |
|
uint64_t written, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); |
|
ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); |
|
ecs_entity_t first = 0, second = 0; |
|
|
|
if (flags_1st) { |
|
if (flecs_ref_is_written(op, &op->first, EcsRuleFirst, written)) { |
|
first = flecs_get_ref_entity(&op->first, flags_1st, ctx); |
|
} else if (flags_1st & EcsRuleIsVar) { |
|
first = EcsWildcard; |
|
} |
|
} |
|
if (flags_2nd) { |
|
if (flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { |
|
second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); |
|
} else if (flags_2nd & EcsRuleIsVar) { |
|
second = EcsWildcard; |
|
} |
|
} |
|
|
|
if (flags_2nd & (EcsRuleIsVar | EcsRuleIsEntity)) { |
|
return ecs_pair(first, second); |
|
} else { |
|
return flecs_entities_get_generation(ctx->world, first); |
|
} |
|
} |
|
|
|
static |
|
ecs_id_t flecs_rule_op_get_id( |
|
const ecs_rule_op_t *op, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
return flecs_rule_op_get_id_w_written(op, written, ctx); |
|
} |
|
|
|
static |
|
int16_t flecs_rule_next_column( |
|
ecs_table_t *table, |
|
ecs_id_t id, |
|
int32_t column) |
|
{ |
|
if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { |
|
column = column + 1; |
|
} else { |
|
ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); |
|
column = ecs_search_offset(NULL, table, column + 1, id, NULL); |
|
ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
return flecs_ito(int16_t, column); |
|
} |
|
|
|
static |
|
void flecs_rule_it_set_column( |
|
ecs_iter_t *it, |
|
int32_t field_index, |
|
int32_t column) |
|
{ |
|
ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); |
|
it->columns[field_index] = column + 1; |
|
} |
|
|
|
static |
|
ecs_id_t flecs_rule_it_set_id( |
|
ecs_iter_t *it, |
|
ecs_table_t *table, |
|
int32_t field_index, |
|
int32_t column) |
|
{ |
|
ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); |
|
return it->ids[field_index] = table->type.array[column]; |
|
} |
|
|
|
static |
|
void flecs_rule_set_match( |
|
const ecs_rule_op_t *op, |
|
ecs_table_t *table, |
|
int32_t column, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); |
|
int32_t field_index = op->field_index; |
|
if (field_index == -1) { |
|
return; |
|
} |
|
|
|
ecs_iter_t *it = ctx->it; |
|
flecs_rule_it_set_column(it, field_index, column); |
|
ecs_id_t matched = flecs_rule_it_set_id(it, table, field_index, column); |
|
flecs_rule_set_vars(op, matched, ctx); |
|
} |
|
|
|
static |
|
void flecs_rule_set_trav_match( |
|
const ecs_rule_op_t *op, |
|
int32_t column, |
|
ecs_entity_t trav, |
|
ecs_entity_t second, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
int32_t field_index = op->field_index; |
|
if (field_index == -1) { |
|
return; |
|
} |
|
|
|
ecs_iter_t *it = ctx->it; |
|
ecs_id_t matched = ecs_pair(trav, second); |
|
it->ids[op->field_index] = matched; |
|
if (column != -1) { |
|
flecs_rule_it_set_column(it, op->field_index, column); |
|
} |
|
flecs_rule_set_vars(op, matched, ctx); |
|
} |
|
|
|
static |
|
bool flecs_rule_table_filter( |
|
ecs_table_t *table, |
|
ecs_rule_lbl_t other, |
|
ecs_flags32_t filter_mask) |
|
{ |
|
uint32_t filter = flecs_ito(uint32_t, other); |
|
return (table->flags & filter_mask & filter) != 0; |
|
} |
|
|
|
static |
|
bool flecs_rule_select_w_id( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_id_t id, |
|
ecs_flags32_t filter_mask) |
|
{ |
|
ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); |
|
ecs_id_record_t *idr = op_ctx->idr; |
|
ecs_table_record_t *tr; |
|
ecs_table_t *table; |
|
|
|
if (!redo) { |
|
if (!idr || idr->id != id) { |
|
idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); |
|
if (!idr) { |
|
return false; |
|
} |
|
} |
|
|
|
if (ctx->rule->filter.flags & EcsFilterMatchEmptyTables) { |
|
if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { |
|
return false; |
|
} |
|
} else { |
|
if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
repeat: |
|
if (!redo || !op_ctx->remaining) { |
|
tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); |
|
if (!tr) { |
|
return false; |
|
} |
|
|
|
op_ctx->column = flecs_ito(int16_t, tr->index); |
|
op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); |
|
table = tr->hdr.table; |
|
flecs_rule_var_set_table(op, op->src.var, table, 0, 0, ctx); |
|
} else { |
|
tr = (ecs_table_record_t*)op_ctx->it.cur; |
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); |
|
table = tr->hdr.table; |
|
op_ctx->column = flecs_rule_next_column(table, idr->id, op_ctx->column); |
|
op_ctx->remaining --; |
|
} |
|
|
|
if (flecs_rule_table_filter(table, op->other, filter_mask)) { |
|
goto repeat; |
|
} |
|
|
|
flecs_rule_set_match(op, table, op_ctx->column, ctx); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_select( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_id_t id = 0; |
|
if (!redo) { |
|
id = flecs_rule_op_get_id(op, ctx); |
|
} |
|
return flecs_rule_select_w_id(op, redo, ctx, id, |
|
(EcsTableIsPrefab|EcsTableIsDisabled)); |
|
} |
|
|
|
static |
|
bool flecs_rule_with( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); |
|
ecs_id_record_t *idr = op_ctx->idr; |
|
const ecs_table_record_t *tr; |
|
|
|
ecs_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); |
|
if (!table) { |
|
return false; |
|
} |
|
|
|
if (!redo) { |
|
ecs_id_t id = flecs_rule_op_get_id(op, ctx); |
|
if (!idr || idr->id != id) { |
|
idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); |
|
if (!idr) { |
|
return false; |
|
} |
|
} |
|
|
|
tr = flecs_id_record_get_table(idr, table); |
|
if (!tr) { |
|
return false; |
|
} |
|
|
|
op_ctx->column = flecs_ito(int16_t, tr->index); |
|
op_ctx->remaining = flecs_ito(int16_t, tr->count); |
|
} else { |
|
if (--op_ctx->remaining <= 0) { |
|
return false; |
|
} |
|
|
|
op_ctx->column = flecs_rule_next_column(table, idr->id, op_ctx->column); |
|
ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); |
|
} |
|
|
|
flecs_rule_set_match(op, table, op_ctx->column, ctx); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_and( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
if (written & (1ull << op->src.var)) { |
|
return flecs_rule_with(op, redo, ctx); |
|
} else { |
|
return flecs_rule_select(op, redo, ctx); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_select_id( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_flags32_t table_filter) |
|
{ |
|
ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); |
|
ecs_iter_t *it = ctx->it; |
|
int8_t field = op->field_index; |
|
ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!redo) { |
|
ecs_id_t id = it->ids[field]; |
|
ecs_id_record_t *idr = op_ctx->idr; |
|
if (!idr || idr->id != id) { |
|
idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); |
|
if (!idr) { |
|
return false; |
|
} |
|
} |
|
|
|
if (ctx->rule->filter.flags & EcsFilterMatchEmptyTables) { |
|
if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { |
|
return false; |
|
} |
|
} else { |
|
if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
repeat: {} |
|
const ecs_table_record_t *tr = flecs_table_cache_next( |
|
&op_ctx->it, ecs_table_record_t); |
|
if (!tr) { |
|
return false; |
|
} |
|
|
|
ecs_table_t *table = tr->hdr.table; |
|
if (flecs_rule_table_filter(table, op->other, table_filter)) { |
|
goto repeat; |
|
} |
|
|
|
flecs_rule_var_set_table(op, op->src.var, table, 0, 0, ctx); |
|
flecs_rule_it_set_column(it, field, tr->index); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_with_id( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (redo) { |
|
return false; |
|
} |
|
|
|
ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); |
|
ecs_iter_t *it = ctx->it; |
|
int8_t field = op->field_index; |
|
ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); |
|
if (!table) { |
|
return false; |
|
} |
|
|
|
ecs_id_t id = it->ids[field]; |
|
ecs_id_record_t *idr = op_ctx->idr; |
|
if (!idr || idr->id != id) { |
|
idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); |
|
if (!idr) { |
|
return false; |
|
} |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (!tr) { |
|
return false; |
|
} |
|
|
|
flecs_rule_it_set_column(it, field, tr->index); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_and_id( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
if (written & (1ull << op->src.var)) { |
|
return flecs_rule_with_id(op, redo, ctx); |
|
} else { |
|
return flecs_rule_select_id(op, redo, ctx, |
|
(EcsTableIsPrefab|EcsTableIsDisabled)); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_up_select( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx, |
|
bool self, |
|
bool id_only) |
|
{ |
|
ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); |
|
ecs_world_t *world = ctx->world; |
|
ecs_iter_t *it = ctx->it; |
|
bool redo_select = redo; |
|
const ecs_filter_t *filter = &ctx->rule->filter; |
|
|
|
/* Early out if traversal relationship doesn't exist */ |
|
op_ctx->trav = filter->terms[op->term_index].src.trav; |
|
if (!op_ctx->idr_trav) { |
|
op_ctx->idr_trav = flecs_id_record_get(ctx->world, |
|
ecs_pair(op_ctx->trav, EcsWildcard)); |
|
} |
|
if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)){ |
|
if (!self) { |
|
return false; |
|
} else if (id_only) { |
|
return flecs_rule_select_id(op, redo, ctx, |
|
(EcsTableIsPrefab|EcsTableIsDisabled)); |
|
} else { |
|
return flecs_rule_select(op, redo, ctx); |
|
} |
|
} |
|
|
|
if (!redo) { |
|
op_ctx->with = flecs_rule_op_get_id(op, ctx); |
|
op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); |
|
if (!op_ctx->idr_with) { |
|
return false; |
|
} |
|
|
|
op_ctx->down = NULL; |
|
op_ctx->cache_elem = 0; |
|
} |
|
|
|
ecs_trav_down_t *down = op_ctx->down; |
|
|
|
do { |
|
while (!down) { |
|
ecs_table_t *table = op_ctx->table; |
|
if (!table) { |
|
ecs_table_range_t range; |
|
it->sources[op->field_index] = 0; |
|
do { |
|
bool result; |
|
if (id_only) { |
|
result = flecs_rule_select_id(op, redo_select, ctx, 0); |
|
} else { |
|
result = flecs_rule_select_w_id(op, redo_select, ctx, |
|
op_ctx->with, 0); |
|
} |
|
if (!result) { |
|
return false; |
|
} |
|
|
|
redo_select = true; |
|
|
|
range = flecs_rule_get_range( |
|
op, &op->src, EcsRuleSrc, ctx); |
|
ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
} while (!self && range.table->_->traversable_count == 0); |
|
|
|
if (!range.count) { |
|
range.count = ecs_table_count(range.table); |
|
} |
|
|
|
table = op_ctx->table = range.table; |
|
op_ctx->row = range.offset; |
|
op_ctx->end = range.offset + range.count; |
|
op_ctx->matched = it->ids[op->field_index]; |
|
|
|
if (self) { |
|
if (!flecs_rule_table_filter(table, op->other, |
|
(EcsTableIsPrefab|EcsTableIsDisabled))) |
|
{ |
|
flecs_reset_source_set_flag(ctx, op->field_index); |
|
op_ctx->row --; |
|
return true; |
|
} |
|
} |
|
|
|
redo_select = true; |
|
} else { |
|
op_ctx->row ++; |
|
} |
|
|
|
if (table->_->traversable_count == 0) { |
|
op_ctx->table = NULL; |
|
continue; |
|
} else { |
|
int32_t row; |
|
ecs_entity_t entity = 0; |
|
ecs_entity_t *entities = flecs_table_entities_array(table); |
|
|
|
for (row = op_ctx->row; row < op_ctx->end; row ++) { |
|
entity = entities[row]; |
|
ecs_record_t *record = flecs_entities_get(world, entity); |
|
if (record->row & EcsEntityIsTraversable) { |
|
it->sources[op->field_index] = entity; |
|
break; |
|
} |
|
} |
|
|
|
if (row == op_ctx->end) { |
|
op_ctx->table = NULL; |
|
continue; |
|
} |
|
|
|
down = op_ctx->down = flecs_rule_get_down_cache(ctx, &op_ctx->cache, |
|
op_ctx->trav, entity, op_ctx->idr_with, self); |
|
op_ctx->cache_elem = -1; |
|
} |
|
} |
|
|
|
next_elem: |
|
if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { |
|
down = NULL; |
|
continue; |
|
} |
|
|
|
ecs_trav_down_elem_t *elem = ecs_vec_get_t( |
|
&down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); |
|
|
|
flecs_rule_var_set_table(op, op->src.var, elem->table, 0, 0, ctx); |
|
flecs_rule_set_vars(op, op_ctx->matched, ctx); |
|
|
|
if (flecs_rule_table_filter(elem->table, op->other, |
|
(EcsTableIsPrefab|EcsTableIsDisabled))) |
|
{ |
|
goto next_elem; |
|
} |
|
|
|
break; |
|
} while (true); |
|
|
|
flecs_set_source_set_flag(ctx, op->field_index); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_up_with( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
const ecs_filter_t *filter = &ctx->rule->filter; |
|
ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); |
|
ecs_iter_t *it = ctx->it; |
|
|
|
/* Early out if traversal relationship doesn't exist */ |
|
op_ctx->trav = filter->terms[op->term_index].src.trav; |
|
if (!op_ctx->idr_trav) { |
|
op_ctx->idr_trav = flecs_id_record_get(ctx->world, |
|
ecs_pair(op_ctx->trav, EcsWildcard)); |
|
} |
|
if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)){ |
|
return false; |
|
} |
|
|
|
if (!redo) { |
|
op_ctx->trav = filter->terms[op->term_index].src.trav; |
|
op_ctx->with = flecs_rule_op_get_id(op, ctx); |
|
op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); |
|
|
|
if (!op_ctx->idr_with) { |
|
return false; |
|
} |
|
|
|
ecs_table_range_t range = flecs_rule_get_range( |
|
op, &op->src, EcsRuleSrc, ctx); |
|
if (!range.table) { |
|
return false; |
|
} |
|
|
|
ecs_trav_up_t *up = flecs_rule_get_up_cache(ctx, &op_ctx->cache, |
|
range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, |
|
op_ctx->idr_trav); |
|
|
|
if (!up) { |
|
return false; |
|
} |
|
|
|
it->sources[op->field_index] = flecs_entities_get_generation( |
|
ctx->world, up->src); |
|
it->columns[op->field_index] = up->column + 1; |
|
it->ids[op->field_index] = up->id; |
|
flecs_rule_set_vars(op, up->id, ctx); |
|
flecs_set_source_set_flag(ctx, op->field_index); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_self_up_with( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx, |
|
bool id_only) |
|
{ |
|
if (!redo) { |
|
bool result; |
|
if (id_only) { |
|
result = flecs_rule_with_id(op, redo, ctx); |
|
} else { |
|
result = flecs_rule_with(op, redo, ctx); |
|
} |
|
if (result) { |
|
ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); |
|
op_ctx->trav = 0; |
|
if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar) { |
|
ecs_iter_t *it = ctx->it; |
|
it->sources[op->field_index] = 0; |
|
} |
|
return true; |
|
} |
|
|
|
flecs_reset_source_set_flag(ctx, op->field_index); |
|
|
|
return flecs_rule_up_with(op, redo, ctx); |
|
} else { |
|
ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); |
|
if (op_ctx->trav == 0) { |
|
return flecs_rule_with(op, redo, ctx); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_rule_up( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { |
|
return flecs_rule_up_with(op, redo, ctx); |
|
} else { |
|
return flecs_rule_up_select(op, redo, ctx, false, false); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_self_up( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { |
|
return flecs_rule_self_up_with(op, redo, ctx, false); |
|
} else { |
|
return flecs_rule_up_select(op, redo, ctx, true, false); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_up_id( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { |
|
return flecs_rule_up_with(op, redo, ctx); |
|
} else { |
|
return flecs_rule_up_select(op, redo, ctx, false, true); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_self_up_id( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { |
|
return flecs_rule_self_up_with(op, redo, ctx, true); |
|
} else { |
|
return flecs_rule_up_select(op, redo, ctx, true, true); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_select_any( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
return flecs_rule_select_w_id(op, redo, ctx, EcsAny, |
|
(EcsTableIsPrefab|EcsTableIsDisabled)); |
|
} |
|
|
|
static |
|
bool flecs_rule_and_any( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_flags16_t match_flags = op->match_flags; |
|
if (redo) { |
|
if (match_flags & EcsTermMatchAnySrc) { |
|
return false; |
|
} |
|
} |
|
|
|
uint64_t written = ctx->written[ctx->op_index]; |
|
int32_t remaining = 1; |
|
bool result; |
|
if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { |
|
result = flecs_rule_with(op, redo, ctx); |
|
} else { |
|
result = flecs_rule_select(op, redo, ctx); |
|
remaining = 0; |
|
} |
|
|
|
if (!redo) { |
|
ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); |
|
if (match_flags & EcsTermMatchAny && op_ctx->remaining) { |
|
op_ctx->remaining = flecs_ito(int16_t, remaining); |
|
} |
|
} |
|
|
|
int32_t field = op->field_index; |
|
if (field != -1) { |
|
ctx->it->ids[field] = flecs_rule_op_get_id(op, ctx); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static |
|
bool flecs_rule_triv( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); |
|
int32_t until = flecs_ito(int32_t, op->other); |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
ctx->written[ctx->op_index + 1] |= 1ull; |
|
if (written & 1ull) { |
|
return flecs_rule_trivial_test(ctx->rule, ctx, !redo, until); |
|
} else { |
|
return flecs_rule_trivial_search_nodata(ctx->rule, ctx, op_ctx, !redo, until); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_triv_data( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); |
|
int32_t until = flecs_ito(int32_t, op->other); |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
ctx->written[ctx->op_index + 1] |= 1ull; |
|
if (written & 1ull) { |
|
return flecs_rule_trivial_test(ctx->rule, ctx, !redo, until); |
|
} else { |
|
return flecs_rule_trivial_search(ctx->rule, ctx, op_ctx, !redo, until); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_triv_wildcard( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); |
|
int32_t until = flecs_ito(int32_t, op->other); |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
ctx->written[ctx->op_index + 1] |= 1ull; |
|
if (written & 1ull) { |
|
return flecs_rule_trivial_test_w_wildcards(ctx->rule, ctx, !redo, until); |
|
} else { |
|
return flecs_rule_trivial_search_w_wildcards(ctx->rule, ctx, op_ctx, !redo, until); |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_trav_fixed_src_reflexive( |
|
const ecs_rule_op_t *op, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_table_range_t *range, |
|
ecs_entity_t trav, |
|
ecs_entity_t second) |
|
{ |
|
ecs_table_t *table = range->table; |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_entity_t *entities = table->data.entities.array; |
|
int32_t count = range->count; |
|
if (!count) { |
|
count = ecs_table_count(table); |
|
} |
|
|
|
int32_t i = range->offset, end = i + count; |
|
for (; i < end; i ++) { |
|
if (entities[i] == second) { |
|
/* Even though table doesn't have the specific relationship |
|
* pair, the relationship is reflexive and the target entity |
|
* is stored in the table. */ |
|
break; |
|
} |
|
} |
|
if (i == end) { |
|
/* Table didn't contain target entity */ |
|
return false; |
|
} |
|
if (count > 1) { |
|
/* If the range contains more than one entity, set the range to |
|
* return only the entity matched by the reflexive property. */ |
|
ecs_assert(flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_var_t *var = &ctx->vars[op->src.var]; |
|
ecs_table_range_t *var_range = &var->range; |
|
var_range->offset = i; |
|
var_range->count = 1; |
|
var->entity = entities[i]; |
|
} |
|
|
|
flecs_rule_set_trav_match(op, -1, trav, second, ctx); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_trav_unknown_src_reflexive( |
|
const ecs_rule_op_t *op, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_entity_t trav, |
|
ecs_entity_t second) |
|
{ |
|
ecs_assert(flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_var_id_t src_var = op->src.var; |
|
flecs_rule_var_set_entity(op, src_var, second, ctx); |
|
flecs_rule_var_get_table(src_var, ctx); |
|
flecs_rule_set_trav_match(op, -1, trav, second, ctx); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_trav_fixed_src_up_fixed_second( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (redo) { |
|
return false; /* If everything's fixed, can only have a single result */ |
|
} |
|
|
|
ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); |
|
ecs_flags16_t f_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); |
|
ecs_flags16_t f_src = flecs_rule_ref_flags(op->flags, EcsRuleSrc); |
|
ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); |
|
ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); |
|
ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); |
|
ecs_table_t *table = range.table; |
|
|
|
/* Check if table has transitive relationship by traversing upwards */ |
|
int32_t column = ecs_search_relation(ctx->world, table, 0, |
|
ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, NULL); |
|
if (column == -1) { |
|
if (op->match_flags & EcsTermReflexive) { |
|
return flecs_rule_trav_fixed_src_reflexive(op, ctx, |
|
&range, trav, second); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
flecs_rule_set_trav_match(op, column, trav, second, ctx); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_trav_unknown_src_up_fixed_second( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); |
|
ecs_flags16_t f_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); |
|
ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); |
|
ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); |
|
ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); |
|
|
|
if (!redo) { |
|
ecs_record_t *r_second = flecs_entities_get(ctx->world, second); |
|
bool traversable = r_second && r_second->row & EcsEntityIsTraversable; |
|
bool reflexive = op->match_flags & EcsTermReflexive; |
|
if (!traversable && !reflexive) { |
|
trav_ctx->cache.id = 0; |
|
|
|
/* If there's no record for the entity, it can't have a subtree so |
|
* forward operation to a regular select. */ |
|
return flecs_rule_select(op, redo, ctx); |
|
} |
|
|
|
/* Entity is traversable, which means it could have a subtree */ |
|
flecs_rule_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); |
|
trav_ctx->index = 0; |
|
|
|
if (op->match_flags & EcsTermReflexive) { |
|
trav_ctx->index = -1; |
|
return flecs_rule_trav_unknown_src_reflexive( |
|
op, ctx, trav, second); |
|
} |
|
} else { |
|
if (!trav_ctx->cache.id) { |
|
/* No traversal cache, which means this is a regular select */ |
|
return flecs_rule_select(op, redo, ctx); |
|
} |
|
} |
|
|
|
if (trav_ctx->index == -1) { |
|
redo = false; /* First result after handling reflexive relationship */ |
|
trav_ctx->index = 0; |
|
} |
|
|
|
/* Forward to select */ |
|
int32_t count = ecs_vec_count(&trav_ctx->cache.entities); |
|
ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); |
|
for (; trav_ctx->index < count; trav_ctx->index ++) { |
|
ecs_trav_elem_t *el = &elems[trav_ctx->index]; |
|
trav_ctx->and.idr = el->idr; /* prevents lookup by select */ |
|
if (flecs_rule_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), |
|
(EcsTableIsPrefab|EcsTableIsDisabled))) |
|
{ |
|
return true; |
|
} |
|
|
|
redo = false; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_rule_trav_yield_reflexive_src( |
|
const ecs_rule_op_t *op, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_table_range_t *range, |
|
ecs_entity_t trav) |
|
{ |
|
ecs_var_t *vars = ctx->vars; |
|
ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); |
|
int32_t offset = trav_ctx->offset, count = trav_ctx->count; |
|
bool src_is_var = op->flags & (EcsRuleIsVar << EcsRuleSrc); |
|
|
|
if (trav_ctx->index >= (offset + count)) { |
|
/* Restore previous offset, count */ |
|
if (src_is_var) { |
|
ecs_var_id_t src_var = op->src.var; |
|
vars[src_var].range.offset = offset; |
|
vars[src_var].range.count = count; |
|
vars[src_var].entity = 0; |
|
} |
|
return false; |
|
} |
|
|
|
ecs_entity_t entity = ecs_vec_get_t( |
|
&range->table->data.entities, ecs_entity_t, trav_ctx->index)[0]; |
|
flecs_rule_set_trav_match(op, -1, trav, entity, ctx); |
|
|
|
/* Hijack existing variable to return one result at a time */ |
|
if (src_is_var) { |
|
ecs_var_id_t src_var = op->src.var; |
|
ecs_table_t *table = vars[src_var].range.table; |
|
ecs_assert(!table || table == ecs_get_table(ctx->world, entity), |
|
ECS_INTERNAL_ERROR, NULL); |
|
(void)table; |
|
vars[src_var].entity = entity; |
|
vars[src_var].range = flecs_range_from_entity(entity, ctx); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_trav_fixed_src_up_unknown_second( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); |
|
ecs_flags16_t f_src = flecs_rule_ref_flags(op->flags, EcsRuleSrc); |
|
ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); |
|
ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); |
|
ecs_table_t *table = range.table; |
|
ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); |
|
|
|
if (!redo) { |
|
flecs_rule_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); |
|
trav_ctx->index = 0; |
|
if (op->match_flags & EcsTermReflexive) { |
|
trav_ctx->yield_reflexive = true; |
|
trav_ctx->index = range.offset; |
|
trav_ctx->offset = range.offset; |
|
trav_ctx->count = range.count ? range.count : ecs_table_count(table); |
|
} |
|
} else { |
|
trav_ctx->index ++; |
|
} |
|
|
|
if (trav_ctx->yield_reflexive) { |
|
if (flecs_rule_trav_yield_reflexive_src(op, ctx, &range, trav)) { |
|
return true; |
|
} |
|
trav_ctx->yield_reflexive = false; |
|
trav_ctx->index = 0; |
|
} |
|
|
|
if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { |
|
return false; |
|
} |
|
|
|
ecs_trav_elem_t *el = ecs_vec_get_t( |
|
&trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); |
|
flecs_rule_set_trav_match(op, el->column, trav, el->entity, ctx); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_trav( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
|
|
if (!flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { |
|
if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { |
|
/* This can't happen, src or second should have been resolved */ |
|
ecs_abort(ECS_INTERNAL_ERROR, |
|
"invalid instruction sequence: unconstrained traversal"); |
|
} else { |
|
return flecs_rule_trav_unknown_src_up_fixed_second(op, redo, ctx); |
|
} |
|
} else { |
|
if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { |
|
return flecs_rule_trav_fixed_src_up_unknown_second(op, redo, ctx); |
|
} else { |
|
return flecs_rule_trav_fixed_src_up_fixed_second(op, redo, ctx); |
|
} |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_ids( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (redo) { |
|
return false; |
|
} |
|
|
|
ecs_id_record_t *cur; |
|
ecs_id_t id = flecs_rule_op_get_id(op, ctx); |
|
|
|
{ |
|
cur = flecs_id_record_get(ctx->world, id); |
|
if (!cur || !cur->cache.tables.count) { |
|
return false; |
|
} |
|
} |
|
|
|
flecs_rule_set_vars(op, cur->id, ctx); |
|
|
|
if (op->field_index != -1) { |
|
ecs_iter_t *it = ctx->it; |
|
it->ids[op->field_index] = id; |
|
it->sources[op->field_index] = EcsWildcard; |
|
it->columns[op->field_index] = -1; /* Mark field as set */ |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_idsright( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); |
|
ecs_id_record_t *cur; |
|
|
|
if (!redo) { |
|
ecs_id_t id = flecs_rule_op_get_id(op, ctx); |
|
if (!ecs_id_is_wildcard(id)) { |
|
/* If id is not a wildcard, we can directly return it. This can |
|
* happen if a variable was constrained by an iterator. */ |
|
op_ctx->cur = NULL; |
|
flecs_rule_set_vars(op, id, ctx); |
|
return true; |
|
} |
|
|
|
cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); |
|
if (!cur) { |
|
return false; |
|
} |
|
} else { |
|
if (!op_ctx->cur) { |
|
return false; |
|
} |
|
} |
|
|
|
do { |
|
cur = op_ctx->cur = op_ctx->cur->first.next; |
|
} while (cur && !cur->cache.tables.count); /* Skip empty ids */ |
|
|
|
if (!cur) { |
|
return false; |
|
} |
|
|
|
flecs_rule_set_vars(op, cur->id, ctx); |
|
|
|
if (op->field_index != -1) { |
|
ecs_iter_t *it = ctx->it; |
|
ecs_id_t id = flecs_rule_op_get_id_w_written(op, op->written, ctx); |
|
it->ids[op->field_index] = id; |
|
it->sources[op->field_index] = EcsWildcard; |
|
it->columns[op->field_index] = -1; /* Mark field as set */ |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_idsleft( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); |
|
ecs_id_record_t *cur; |
|
|
|
if (!redo) { |
|
ecs_id_t id = flecs_rule_op_get_id(op, ctx); |
|
if (!ecs_id_is_wildcard(id)) { |
|
/* If id is not a wildcard, we can directly return it. This can |
|
* happen if a variable was constrained by an iterator. */ |
|
op_ctx->cur = NULL; |
|
flecs_rule_set_vars(op, id, ctx); |
|
return true; |
|
} |
|
|
|
cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); |
|
if (!cur) { |
|
return false; |
|
} |
|
} else { |
|
if (!op_ctx->cur) { |
|
return false; |
|
} |
|
} |
|
|
|
do { |
|
cur = op_ctx->cur = op_ctx->cur->second.next; |
|
} while (cur && !cur->cache.tables.count); /* Skip empty ids */ |
|
|
|
if (!cur) { |
|
return false; |
|
} |
|
|
|
flecs_rule_set_vars(op, cur->id, ctx); |
|
|
|
if (op->field_index != -1) { |
|
ecs_iter_t *it = ctx->it; |
|
ecs_id_t id = flecs_rule_op_get_id_w_written(op, op->written, ctx); |
|
it->ids[op->field_index] = id; |
|
it->sources[op->field_index] = EcsWildcard; |
|
it->columns[op->field_index] = -1; /* Mark field as set */ |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_each( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); |
|
int32_t row; |
|
|
|
ecs_table_range_t range = flecs_rule_var_get_range(op->first.var, ctx); |
|
ecs_table_t *table = range.table; |
|
if (!table) { |
|
return false; |
|
} |
|
|
|
if (!redo) { |
|
row = op_ctx->row = range.offset; |
|
} else { |
|
int32_t end = range.count; |
|
if (end) { |
|
end += range.offset; |
|
} else { |
|
end = table->data.entities.count; |
|
} |
|
row = ++ op_ctx->row; |
|
if (op_ctx->row >= end) { |
|
return false; |
|
} |
|
} |
|
|
|
ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_entity_t *entities = table->data.entities.array; |
|
ecs_entity_t e; |
|
do { |
|
if (row == ecs_table_count(table)) { |
|
return false; |
|
} |
|
e = entities[row ++]; |
|
ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
/* Exclude entities that are used as markers by rule engine */ |
|
} while ((e == EcsWildcard) || (e == EcsAny) || |
|
(e == EcsThis) || (e == EcsVariable)); |
|
|
|
flecs_rule_var_set_entity(op, op->src.var, e, ctx); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_store( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (!redo) { |
|
flecs_rule_var_set_entity(op, op->src.var, op->first.entity, ctx); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_reset( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
const ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (!redo) { |
|
return true; |
|
} else { |
|
flecs_rule_var_reset(op->src.var, ctx); |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
void flecs_rule_reset_after_block( |
|
const ecs_rule_op_t *start_op, |
|
ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_ctrl_ctx_t *op_ctx) |
|
{ |
|
ecs_rule_lbl_t op_index = start_op->next; |
|
const ecs_rule_op_t *op = &ctx->rit->ops[op_index]; |
|
ctx->written[op_index] = ctx->written[ctx->op_index]; |
|
ctx->op_index = op_index; |
|
|
|
int32_t field = op->field_index; |
|
if (field == -1) { |
|
goto done; |
|
} |
|
|
|
ecs_iter_t *it = ctx->it; |
|
|
|
/* Not terms return no data */ |
|
it->columns[field] = 0; |
|
|
|
/* Ignore variables written by Not operation */ |
|
uint64_t *written = ctx->written; |
|
uint64_t written_cur = written[op->prev + 1]; |
|
ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); |
|
ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); |
|
|
|
/* Overwrite id with cleared out variables */ |
|
ecs_id_t id = flecs_rule_op_get_id(op, ctx); |
|
if (id) { |
|
it->ids[field] = id; |
|
} |
|
|
|
/* Reset variables */ |
|
if (flags_1st & EcsRuleIsVar) { |
|
if (!flecs_ref_is_written(op, &op->first, EcsRuleFirst, written_cur)){ |
|
flecs_rule_var_reset(op->first.var, ctx); |
|
} |
|
} |
|
if (flags_2nd & EcsRuleIsVar) { |
|
if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written_cur)){ |
|
flecs_rule_var_reset(op->second.var, ctx); |
|
} |
|
} |
|
|
|
/* If term has entity src, set it because no other instruction might */ |
|
if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { |
|
it->sources[field] = op->src.entity; |
|
} |
|
|
|
done: |
|
op_ctx->op_index = op_index; |
|
} |
|
|
|
static |
|
const char* flecs_rule_name_arg( |
|
const ecs_rule_op_t *op, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
int8_t term_index = op->term_index; |
|
ecs_term_t *term = &ctx->rule->filter.terms[term_index]; |
|
return term->second.name; |
|
} |
|
|
|
static |
|
bool flecs_rule_compare_range( |
|
const ecs_table_range_t *l, |
|
const ecs_table_range_t *r) |
|
{ |
|
if (l->table != r->table) { |
|
return false; |
|
} |
|
|
|
if (l->count) { |
|
int32_t l_end = l->offset + l->count; |
|
int32_t r_end = r->offset + r->count; |
|
if (r->offset < l->offset) { |
|
return false; |
|
} |
|
if (r_end > l_end) { |
|
return false; |
|
} |
|
} else { |
|
/* Entire table is matched */ |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_eq_w_range( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx, |
|
ecs_table_range_t r) |
|
{ |
|
if (redo) { |
|
return false; |
|
} |
|
|
|
uint64_t written = ctx->written[ctx->op_index]; |
|
ecs_var_id_t first_var = op->first.var; |
|
if (!(written & (1ull << first_var))) { |
|
/* left = unknown, right = known. Assign right-hand value to left */ |
|
ecs_var_id_t l = first_var; |
|
ctx->vars[l].range = r; |
|
if (r.count == 1) { |
|
ctx->vars[l].entity = ecs_vec_get_t(&r.table->data.entities, |
|
ecs_entity_t, r.offset)[0]; |
|
} |
|
return true; |
|
} else { |
|
ecs_table_range_t l = flecs_rule_get_range( |
|
op, &op->first, EcsRuleFirst, ctx); |
|
|
|
if (!flecs_rule_compare_range(&l, &r)) { |
|
return false; |
|
} |
|
|
|
ctx->vars[first_var].range.offset = r.offset; |
|
ctx->vars[first_var].range.count = r.count; |
|
return true; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_eq( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; (void)written; |
|
ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), |
|
ECS_INTERNAL_ERROR, |
|
"invalid instruction sequence: uninitialized eq operand"); |
|
|
|
ecs_table_range_t r = flecs_rule_get_range( |
|
op, &op->second, EcsRuleSecond, ctx); |
|
return flecs_rule_pred_eq_w_range(op, redo, ctx, r); |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_eq_name( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
const char *name = flecs_rule_name_arg(op, ctx); |
|
ecs_entity_t e = ecs_lookup_fullpath(ctx->world, name); |
|
if (!e) { |
|
/* Entity doesn't exist */ |
|
return false; |
|
} |
|
|
|
ecs_table_range_t r = flecs_range_from_entity(e, ctx); |
|
return flecs_rule_pred_eq_w_range(op, redo, ctx, r); |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_neq_w_range( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx, |
|
ecs_table_range_t r) |
|
{ |
|
ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); |
|
ecs_var_id_t first_var = op->first.var; |
|
ecs_table_range_t l = flecs_rule_get_range( |
|
op, &op->first, EcsRuleFirst, ctx); |
|
|
|
/* If tables don't match, neq always returns once */ |
|
if (l.table != r.table) { |
|
return true && !redo; |
|
} |
|
|
|
int32_t l_offset; |
|
int32_t l_count; |
|
if (!redo) { |
|
/* Make sure we're working with the correct table count */ |
|
if (!l.count && l.table) { |
|
l.count = ecs_table_count(l.table); |
|
} |
|
|
|
l_offset = l.offset; |
|
l_count = l.count; |
|
|
|
/* Cache old value */ |
|
op_ctx->range = l; |
|
} else { |
|
l_offset = op_ctx->range.offset; |
|
l_count = op_ctx->range.count; |
|
} |
|
|
|
/* If the table matches, a Neq returns twice: once for the slice before the |
|
* excluded slice, once for the slice after the excluded slice. If the right |
|
* hand range starts & overlaps with the left hand range, there is only |
|
* one slice. */ |
|
ecs_var_t *var = &ctx->vars[first_var]; |
|
if (!redo && r.offset > l_offset) { |
|
int32_t end = r.offset; |
|
if (end > l_count) { |
|
end = l_count; |
|
} |
|
|
|
/* Return first slice */ |
|
var->range.table = l.table; |
|
var->range.offset = l_offset; |
|
var->range.count = end - l_offset; |
|
op_ctx->redo = false; |
|
return true; |
|
} else if (!op_ctx->redo) { |
|
int32_t l_end = op_ctx->range.offset + l_count; |
|
int32_t r_end = r.offset + r.count; |
|
|
|
if (l_end <= r_end) { |
|
/* If end of existing range falls inside the excluded range, there's |
|
* nothing more to return */ |
|
var->range = l; |
|
return false; |
|
} |
|
|
|
/* Return second slice */ |
|
var->range.table = l.table; |
|
var->range.offset = r_end; |
|
var->range.count = l_end - r_end; |
|
|
|
/* Flag so we know we're done the next redo */ |
|
op_ctx->redo = true; |
|
return true; |
|
} else { |
|
/* Restore previous value */ |
|
var->range = l; |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_match( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx, |
|
bool is_neq) |
|
{ |
|
ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
ecs_assert(flecs_ref_is_written(op, &op->first, EcsRuleFirst, written), |
|
ECS_INTERNAL_ERROR, |
|
"invalid instruction sequence: uninitialized match operand"); |
|
(void)written; |
|
|
|
ecs_var_id_t first_var = op->first.var; |
|
const char *match = flecs_rule_name_arg(op, ctx); |
|
ecs_table_range_t l; |
|
if (!redo) { |
|
l = flecs_rule_get_range(op, &op->first, EcsRuleFirst, ctx); |
|
if (!l.table) { |
|
return false; |
|
} |
|
|
|
if (!l.count) { |
|
l.count = ecs_table_count(l.table); |
|
} |
|
|
|
op_ctx->range = l; |
|
op_ctx->index = l.offset; |
|
op_ctx->name_col = flecs_ito(int16_t, |
|
ecs_table_get_type_index(ctx->world, l.table, |
|
ecs_pair(ecs_id(EcsIdentifier), EcsName))); |
|
if (op_ctx->name_col == -1) { |
|
return is_neq; |
|
} |
|
op_ctx->name_col = flecs_ito(int16_t, |
|
l.table->column_map[op_ctx->name_col]); |
|
ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); |
|
} else { |
|
if (op_ctx->name_col == -1) { |
|
/* Table has no name */ |
|
return false; |
|
} |
|
|
|
l = op_ctx->range; |
|
} |
|
|
|
const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data.array; |
|
int32_t count = l.offset + l.count, offset = -1; |
|
for (; op_ctx->index < count; op_ctx->index ++) { |
|
const char *name = names[op_ctx->index].value; |
|
bool result = strstr(name, match); |
|
if (is_neq) { |
|
result = !result; |
|
} |
|
|
|
if (!result) { |
|
if (offset != -1) { |
|
break; |
|
} |
|
} else { |
|
if (offset == -1) { |
|
offset = op_ctx->index; |
|
} |
|
} |
|
} |
|
|
|
if (offset == -1) { |
|
ctx->vars[first_var].range = op_ctx->range; |
|
return false; |
|
} |
|
|
|
ctx->vars[first_var].range.offset = offset; |
|
ctx->vars[first_var].range.count = (op_ctx->index - offset); |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_eq_match( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
return flecs_rule_pred_match(op, redo, ctx, false); |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_neq_match( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
return flecs_rule_pred_match(op, redo, ctx, true); |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_neq( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
uint64_t written = ctx->written[ctx->op_index]; (void)written; |
|
ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), |
|
ECS_INTERNAL_ERROR, |
|
"invalid instruction sequence: uninitialized neq operand"); |
|
|
|
ecs_table_range_t r = flecs_rule_get_range( |
|
op, &op->second, EcsRuleSecond, ctx); |
|
return flecs_rule_pred_neq_w_range(op, redo, ctx, r); |
|
} |
|
|
|
static |
|
bool flecs_rule_pred_neq_name( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
const char *name = flecs_rule_name_arg(op, ctx); |
|
ecs_entity_t e = ecs_lookup_fullpath(ctx->world, name); |
|
if (!e) { |
|
/* Entity doesn't exist */ |
|
return true && !redo; |
|
} |
|
|
|
ecs_table_range_t r = flecs_range_from_entity(e, ctx); |
|
return flecs_rule_pred_neq_w_range(op, redo, ctx, r); |
|
} |
|
|
|
static |
|
bool flecs_rule_lookup( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (redo) { |
|
return false; |
|
} |
|
|
|
const ecs_rule_t *rule = ctx->rule; |
|
ecs_entity_t first = flecs_rule_var_get_entity(op->first.var, ctx); |
|
ecs_rule_var_t *var = &rule->vars[op->src.var]; |
|
|
|
ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, |
|
NULL, NULL, false); |
|
if (!result) { |
|
flecs_rule_var_set_entity(op, op->src.var, EcsWildcard, ctx); |
|
return false; |
|
} |
|
|
|
flecs_rule_var_set_entity(op, op->src.var, result, ctx); |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_setvars( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
(void)op; |
|
|
|
const ecs_rule_t *rule = ctx->rule; |
|
const ecs_filter_t *filter = &rule->filter; |
|
ecs_var_id_t *src_vars = rule->src_vars; |
|
ecs_iter_t *it = ctx->it; |
|
|
|
if (redo) { |
|
return false; |
|
} |
|
|
|
int32_t i; |
|
ecs_flags32_t source_set = *ctx->source_set; |
|
for (i = 0; i < filter->field_count; i ++) { |
|
ecs_var_id_t var_id = src_vars[i]; |
|
if (!var_id) { |
|
continue; |
|
} |
|
|
|
if (source_set & (1u << i)) { |
|
continue; |
|
} |
|
|
|
it->sources[i] = flecs_rule_var_get_entity(var_id, ctx); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_setthis( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); |
|
ecs_var_t *vars = ctx->vars; |
|
ecs_var_t *this_var = &vars[op->first.var]; |
|
|
|
if (!redo) { |
|
/* Save values so we can restore them later */ |
|
op_ctx->range = vars[0].range; |
|
|
|
/* Constrain This table variable to a single entity from the table */ |
|
vars[0].range = flecs_range_from_entity(this_var->entity, ctx); |
|
vars[0].entity = this_var->entity; |
|
return true; |
|
} else { |
|
/* Restore previous values, so that instructions that are operating on |
|
* the table variable use all the entities in the table. */ |
|
vars[0].range = op_ctx->range; |
|
vars[0].entity = 0; |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_setfixed( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
(void)op; |
|
const ecs_rule_t *rule = ctx->rule; |
|
const ecs_filter_t *filter = &rule->filter; |
|
ecs_iter_t *it = ctx->it; |
|
|
|
if (redo) { |
|
return false; |
|
} |
|
|
|
int32_t i; |
|
for (i = 0; i < filter->term_count; i ++) { |
|
ecs_term_t *term = &filter->terms[i]; |
|
ecs_term_id_t *src = &term->src; |
|
if (src->flags & EcsIsEntity) { |
|
it->sources[term->field_index] = src->id; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_setids( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
(void)op; |
|
const ecs_rule_t *rule = ctx->rule; |
|
const ecs_filter_t *filter = &rule->filter; |
|
ecs_iter_t *it = ctx->it; |
|
|
|
if (redo) { |
|
return false; |
|
} |
|
|
|
int32_t i; |
|
for (i = 0; i < filter->term_count; i ++) { |
|
ecs_term_t *term = &filter->terms[i]; |
|
it->ids[term->field_index] = term->id; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_setid( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (redo) { |
|
return false; |
|
} |
|
|
|
ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); |
|
ctx->it->ids[op->field_index] = op->first.entity; |
|
return true; |
|
} |
|
|
|
/* Check if entity is stored in table */ |
|
static |
|
bool flecs_rule_contain( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (redo) { |
|
return false; |
|
} |
|
|
|
ecs_var_id_t src_id = op->src.var; |
|
ecs_var_id_t first_id = op->first.var; |
|
|
|
ecs_table_t *table = flecs_rule_var_get_table(src_id, ctx); |
|
|
|
ecs_entity_t e = flecs_rule_var_get_entity(first_id, ctx); |
|
return table == ecs_get_table(ctx->world, e); |
|
} |
|
|
|
/* Check if first and second id of pair from last operation are the same */ |
|
static |
|
bool flecs_rule_pair_eq( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (redo) { |
|
return false; |
|
} |
|
|
|
ecs_iter_t *it = ctx->it; |
|
ecs_id_t id = it->ids[op->field_index]; |
|
return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); |
|
} |
|
|
|
static |
|
bool flecs_rule_select_or( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_iter_t *it = ctx->it; |
|
ecs_rule_iter_t *rit = &it->priv.iter.rule; |
|
ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); |
|
|
|
ecs_rule_lbl_t first = flecs_itolbl(ctx->op_index + 1); |
|
if (!redo) { |
|
op_ctx->op_index = first; |
|
} |
|
|
|
const ecs_rule_op_t *cur = &rit->ops[op_ctx->op_index]; |
|
bool result = false; |
|
|
|
/* Evaluate operations in OR chain one by one. */ |
|
|
|
do { |
|
ctx->op_index = op_ctx->op_index; |
|
ctx->written[op_ctx->op_index] = op->written; |
|
|
|
result = flecs_rule_dispatch(cur, redo, ctx); |
|
if (result) { |
|
ctx->written[op_ctx->op_index] |= cur->written; |
|
|
|
/* If a previous operation in the OR chain returned a result for the |
|
* same matched source, skip it so we don't yield for each matching |
|
* element in the chain. */ |
|
ecs_rule_lbl_t prev_index; |
|
for (prev_index = first; prev_index < op_ctx->op_index; |
|
prev_index ++) |
|
{ |
|
const ecs_rule_op_t *prev = &rit->ops[prev_index]; |
|
ctx->op_index = prev_index; |
|
ctx->written[prev_index] = ctx->written[op_ctx->op_index]; |
|
if (flecs_rule_dispatch(prev, false, ctx)) { |
|
break; |
|
} |
|
} |
|
|
|
if (prev_index != op_ctx->op_index) { |
|
/* Duplicate match was found, redo search */ |
|
redo = true; |
|
continue; |
|
} |
|
break; |
|
} |
|
|
|
/* No result was found, go to next operation in chain */ |
|
op_ctx->op_index ++; |
|
cur = &rit->ops[op_ctx->op_index]; |
|
redo = false; |
|
} while (cur->kind != EcsRuleEnd); |
|
|
|
return result; |
|
} |
|
|
|
static |
|
bool flecs_rule_run_block( |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_ctrl_ctx_t *op_ctx) |
|
{ |
|
ecs_iter_t *it = ctx->it; |
|
ecs_rule_iter_t *rit = &it->priv.iter.rule; |
|
|
|
if (!redo) { |
|
op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); |
|
} else if (ctx->rit->ops[op_ctx->op_index].kind == EcsRuleEnd) { |
|
return false; |
|
} |
|
|
|
ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; |
|
|
|
return flecs_rule_run_until( |
|
redo, ctx, rit->ops, ctx->op_index, op_ctx->op_index, EcsRuleEnd); |
|
} |
|
|
|
static |
|
bool flecs_rule_with_or( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); |
|
|
|
bool result = flecs_rule_run_block(redo, ctx, op_ctx); |
|
if (result) { |
|
/* If a match was found, no need to keep searching for this source */ |
|
op_ctx->op_index = op->next; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static |
|
bool flecs_rule_or( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (op->flags & (EcsRuleIsVar << EcsRuleSrc)) { |
|
uint64_t written = ctx->written[ctx->op_index]; |
|
if (!(written & (1ull << op->src.var))) { |
|
return flecs_rule_select_or(op, redo, ctx); |
|
} |
|
} |
|
|
|
return flecs_rule_with_or(op, redo, ctx); |
|
} |
|
|
|
static |
|
bool flecs_rule_run_block_w_reset( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); |
|
bool result = flecs_rule_run_block(redo, ctx, op_ctx); |
|
if (!result) { |
|
flecs_rule_reset_after_block(op, ctx, op_ctx); |
|
} |
|
return result; |
|
} |
|
|
|
static |
|
bool flecs_rule_not( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (redo) { |
|
return false; |
|
} |
|
|
|
return !flecs_rule_run_block_w_reset(op, redo, ctx); |
|
} |
|
|
|
static |
|
bool flecs_rule_optional( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
bool result = flecs_rule_run_block_w_reset(op, redo, ctx); |
|
if (!redo) { |
|
return true; /* Return at least once */ |
|
} else { |
|
return result; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_eval_if( |
|
const ecs_rule_op_t *op, |
|
ecs_rule_run_ctx_t *ctx, |
|
const ecs_rule_ref_t *ref, |
|
ecs_flags16_t ref_kind) |
|
{ |
|
if (flecs_rule_ref_flags(op->flags, ref_kind) == EcsRuleIsVar) { |
|
if (ctx->vars[ref->var].entity == EcsWildcard) { |
|
ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); |
|
flecs_rule_reset_after_block(op, ctx, op_ctx); |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
static |
|
bool flecs_rule_if( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
if (!redo) { |
|
if (!flecs_rule_eval_if(op, ctx, &op->src, EcsRuleSrc) || |
|
!flecs_rule_eval_if(op, ctx, &op->first, EcsRuleFirst) || |
|
!flecs_rule_eval_if(op, ctx, &op->second, EcsRuleSecond)) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return flecs_rule_run_block_w_reset(op, redo, ctx); |
|
} |
|
|
|
static |
|
bool flecs_rule_end( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
(void)op; (void)ctx; |
|
return !redo; |
|
} |
|
|
|
static |
|
bool flecs_rule_populate( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
(void)op; |
|
if (!redo) { |
|
ecs_iter_t *it = ctx->it; |
|
if (it->flags & EcsIterNoData) { |
|
return true; |
|
} |
|
|
|
ECS_BIT_CLEAR(it->flags, EcsIterHasShared); |
|
|
|
const ecs_rule_t *rule = ctx->rule; |
|
const ecs_filter_t *filter = &rule->filter; |
|
int32_t i, field_count = filter->field_count; |
|
ecs_flags64_t data_fields = filter->data_fields; |
|
ecs_table_range_t *range = &ctx->vars[0].range; |
|
ecs_table_t *table = range->table; |
|
if (table && !range->count) { |
|
range->count = ecs_table_count(table); |
|
} |
|
|
|
for (i = 0; i < field_count; i ++) { |
|
if (!(data_fields & (1llu << i))) { |
|
continue; |
|
} |
|
|
|
int32_t index = it->columns[i]; |
|
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); |
|
if (!index) { |
|
continue; |
|
} |
|
|
|
ecs_entity_t src = it->sources[i]; |
|
if (!src) { |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (range->count && table->column_map) { |
|
int32_t column = table->column_map[index - 1]; |
|
if (column != -1) { |
|
it->ptrs[i] = ECS_ELEM( |
|
table->data.columns[column].data.array, |
|
it->sizes[i], |
|
range->offset); |
|
continue; |
|
} |
|
} |
|
} else { |
|
ecs_record_t *r = flecs_entities_get(ctx->world, src); |
|
ecs_table_t *src_table = r->table; |
|
if (src_table->column_map) { |
|
int32_t column = src_table->column_map[index - 1]; |
|
if (column != -1) { |
|
it->ptrs[i] = ecs_vec_get( |
|
&src_table->data.columns[column].data, |
|
it->sizes[i], |
|
ECS_RECORD_TO_ROW(r->row)); |
|
ECS_BIT_SET(it->flags, EcsIterHasShared); |
|
continue; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_populate_self( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
(void)op; |
|
if (!redo) { |
|
const ecs_rule_t *rule = ctx->rule; |
|
const ecs_filter_t *filter = &rule->filter; |
|
int32_t i, field_count = filter->field_count; |
|
ecs_flags64_t data_fields = filter->data_fields; |
|
ecs_iter_t *it = ctx->it; |
|
|
|
ecs_table_range_t *range = &ctx->vars[0].range; |
|
ecs_table_t *table = range->table; |
|
if (!table->column_map) { |
|
return true; |
|
} |
|
|
|
if (!ecs_table_count(table)) { |
|
return true; |
|
} |
|
|
|
for (i = 0; i < field_count; i ++) { |
|
if (!(data_fields & (1llu << i))) { |
|
continue; |
|
} |
|
|
|
int32_t index = it->columns[i]; |
|
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Only owned */ |
|
if (!index) { |
|
continue; |
|
} |
|
|
|
int32_t column = table->column_map[index - 1]; |
|
if (column != -1) { |
|
it->ptrs[i] = ECS_ELEM( |
|
table->data.columns[column].data.array, |
|
it->sizes[i], |
|
range->offset); |
|
} |
|
} |
|
|
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_dispatch( |
|
const ecs_rule_op_t *op, |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
switch(op->kind) { |
|
case EcsRuleAnd: return flecs_rule_and(op, redo, ctx); |
|
case EcsRuleAndId: return flecs_rule_and_id(op, redo, ctx); |
|
case EcsRuleAndAny: return flecs_rule_and_any(op, redo, ctx); |
|
case EcsRuleTriv: return flecs_rule_triv(op, redo, ctx); |
|
case EcsRuleTrivData: return flecs_rule_triv_data(op, redo, ctx); |
|
case EcsRuleTrivWildcard: return flecs_rule_triv_wildcard(op, redo, ctx); |
|
case EcsRuleSelectAny: return flecs_rule_select_any(op, redo, ctx); |
|
case EcsRuleUp: return flecs_rule_up(op, redo, ctx); |
|
case EcsRuleUpId: return flecs_rule_up_id(op, redo, ctx); |
|
case EcsRuleSelfUp: return flecs_rule_self_up(op, redo, ctx); |
|
case EcsRuleSelfUpId: return flecs_rule_self_up_id(op, redo, ctx); |
|
case EcsRuleWith: return flecs_rule_with(op, redo, ctx); |
|
case EcsRuleTrav: return flecs_rule_trav(op, redo, ctx); |
|
case EcsRuleIds: return flecs_rule_ids(op, redo, ctx); |
|
case EcsRuleIdsRight: return flecs_rule_idsright(op, redo, ctx); |
|
case EcsRuleIdsLeft: return flecs_rule_idsleft(op, redo, ctx); |
|
case EcsRuleEach: return flecs_rule_each(op, redo, ctx); |
|
case EcsRuleStore: return flecs_rule_store(op, redo, ctx); |
|
case EcsRuleReset: return flecs_rule_reset(op, redo, ctx); |
|
case EcsRuleOr: return flecs_rule_or(op, redo, ctx); |
|
case EcsRuleOptional: return flecs_rule_optional(op, redo, ctx); |
|
case EcsRuleIf: return flecs_rule_if(op, redo, ctx); |
|
case EcsRuleEnd: return flecs_rule_end(op, redo, ctx); |
|
case EcsRuleNot: return flecs_rule_not(op, redo, ctx); |
|
case EcsRulePredEq: return flecs_rule_pred_eq(op, redo, ctx); |
|
case EcsRulePredNeq: return flecs_rule_pred_neq(op, redo, ctx); |
|
case EcsRulePredEqName: return flecs_rule_pred_eq_name(op, redo, ctx); |
|
case EcsRulePredNeqName: return flecs_rule_pred_neq_name(op, redo, ctx); |
|
case EcsRulePredEqMatch: return flecs_rule_pred_eq_match(op, redo, ctx); |
|
case EcsRulePredNeqMatch: return flecs_rule_pred_neq_match(op, redo, ctx); |
|
case EcsRuleLookup: return flecs_rule_lookup(op, redo, ctx); |
|
case EcsRuleSetVars: return flecs_rule_setvars(op, redo, ctx); |
|
case EcsRuleSetThis: return flecs_rule_setthis(op, redo, ctx); |
|
case EcsRuleSetFixed: return flecs_rule_setfixed(op, redo, ctx); |
|
case EcsRuleSetIds: return flecs_rule_setids(op, redo, ctx); |
|
case EcsRuleSetId: return flecs_rule_setid(op, redo, ctx); |
|
case EcsRuleContain: return flecs_rule_contain(op, redo, ctx); |
|
case EcsRulePairEq: return flecs_rule_pair_eq(op, redo, ctx); |
|
case EcsRulePopulate: return flecs_rule_populate(op, redo, ctx); |
|
case EcsRulePopulateSelf: return flecs_rule_populate_self(op, redo, ctx); |
|
case EcsRuleYield: return false; |
|
case EcsRuleNothing: return false; |
|
} |
|
return false; |
|
} |
|
|
|
static |
|
bool flecs_rule_run_until( |
|
bool redo, |
|
ecs_rule_run_ctx_t *ctx, |
|
const ecs_rule_op_t *ops, |
|
ecs_rule_lbl_t first, |
|
ecs_rule_lbl_t cur, |
|
ecs_rule_op_kind_t until) |
|
{ |
|
ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ctx->op_index = cur; |
|
const ecs_rule_op_t *op = &ops[ctx->op_index]; |
|
ecs_assert(op->kind != until, ECS_INTERNAL_ERROR, NULL); |
|
|
|
do { |
|
#ifdef FLECS_DEBUG |
|
ctx->rit->profile[ctx->op_index].count[redo] ++; |
|
#endif |
|
|
|
bool result = flecs_rule_dispatch(op, redo, ctx); |
|
cur = (&op->prev)[result]; |
|
redo = cur < ctx->op_index; |
|
|
|
if (!redo) { |
|
ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; |
|
} |
|
|
|
ctx->op_index = cur; |
|
op = &ops[ctx->op_index]; |
|
|
|
if (cur <= first) { |
|
return false; |
|
} |
|
if (op->kind == until) { |
|
return true; |
|
} |
|
} while (true); |
|
|
|
return false; |
|
} |
|
|
|
static |
|
void flecs_rule_iter_init( |
|
ecs_rule_run_ctx_t *ctx) |
|
{ |
|
ecs_assert(ctx->written != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_iter_t *it = ctx->it; |
|
|
|
const ecs_rule_t *rule = ctx->rule; |
|
ecs_flags64_t it_written = it->constrained_vars; |
|
ctx->written[0] = it_written; |
|
if (it_written && ctx->rule->src_vars) { |
|
/* If variables were constrained, check if there are any table |
|
* variables that have a constrained entity variable. */ |
|
ecs_var_t *vars = ctx->vars; |
|
int32_t i, count = rule->filter.field_count; |
|
for (i = 0; i < count; i ++) { |
|
ecs_var_id_t var_id = rule->src_vars[i]; |
|
ecs_rule_var_t *var = &rule->vars[var_id]; |
|
|
|
if (!(it_written & (1ull << var_id)) || |
|
(var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) |
|
{ |
|
continue; |
|
} |
|
|
|
/* Initialize table variable with constrained entity variable */ |
|
ecs_var_t *tvar = &vars[var->table_id]; |
|
tvar->range = flecs_range_from_entity(vars[var_id].entity, ctx); |
|
ctx->written[0] |= (1ull << var->table_id); /* Mark as written */ |
|
} |
|
} |
|
|
|
ecs_flags32_t flags = rule->filter.flags; |
|
if (flags & EcsFilterIsTrivial) { |
|
if ((flags & EcsFilterMatchOnlySelf) || |
|
!flecs_table_cache_count(&ctx->world->idr_isa_wildcard->cache)) |
|
{ |
|
if (it_written) { |
|
it->offset = ctx->vars[0].range.offset; |
|
it->count = ctx->vars[0].range.count; |
|
if (!it->count) { |
|
ecs_assert(!it->offset, ECS_INVALID_PARAMETER, NULL); |
|
it->count = ecs_table_count(ctx->vars[0].range.table); |
|
} |
|
|
|
it->flags |= EcsIterTrivialTest; |
|
flecs_rule_setids(&rule->ops[0], false, ctx); |
|
} else { |
|
if (flags & EcsFilterHasWildcards) { |
|
it->flags |= EcsIterTrivialSearchWildcard; |
|
flecs_rule_setids(&rule->ops[0], false, ctx); |
|
} else if (flags & EcsFilterNoData) { |
|
it->flags |= EcsIterTrivialSearchNoData; |
|
flecs_rule_setids(&rule->ops[0], false, ctx); |
|
} else { |
|
it->flags |= EcsIterTrivialSearch; |
|
flecs_rule_setids(&rule->ops[0], false, ctx); |
|
} |
|
} |
|
} |
|
} |
|
|
|
flecs_iter_validate(it); |
|
} |
|
|
|
bool ecs_rule_next_instanced( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_rule_iter_t *rit = &it->priv.iter.rule; |
|
ecs_rule_run_ctx_t ctx; |
|
ctx.world = it->real_world; |
|
ctx.rule = rit->rule; |
|
ctx.it = it; |
|
ctx.vars = rit->vars; |
|
ctx.rule_vars = rit->rule_vars; |
|
ctx.written = rit->written; |
|
ctx.op_ctx = rit->op_ctx; |
|
ctx.source_set = &rit->source_set; |
|
ctx.rit = rit; |
|
const ecs_rule_op_t *ops = rit->ops; |
|
|
|
bool redo = true; |
|
if (!(it->flags & EcsIterIsValid)) { |
|
ecs_assert(ctx.rule != NULL, ECS_INVALID_PARAMETER, NULL); |
|
flecs_rule_iter_init(&ctx); |
|
redo = false; |
|
} else { |
|
it->frame_offset += it->count; |
|
} |
|
|
|
/* Specialized iterator modes for trivial queries */ |
|
if (it->flags & EcsIterTrivialSearch) { |
|
ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; |
|
int32_t fields = ctx.rule->filter.term_count; |
|
if (!flecs_rule_trivial_search(ctx.rule, &ctx, op_ctx, !redo, fields)) { |
|
goto done; |
|
} |
|
it->table = ctx.vars[0].range.table; |
|
it->count = ecs_table_count(it->table); |
|
it->entities = flecs_table_entities_array(it->table); |
|
return true; |
|
} else if (it->flags & EcsIterTrivialSearchNoData) { |
|
ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; |
|
int32_t fields = ctx.rule->filter.term_count; |
|
if (!flecs_rule_trivial_search_nodata(ctx.rule, &ctx, op_ctx, !redo, fields)) { |
|
goto done; |
|
} |
|
it->table = ctx.vars[0].range.table; |
|
it->count = ecs_table_count(it->table); |
|
it->entities = flecs_table_entities_array(it->table); |
|
return true; |
|
} else if (it->flags & EcsIterTrivialTest) { |
|
int32_t fields = ctx.rule->filter.term_count; |
|
if (!flecs_rule_trivial_test(ctx.rule, &ctx, !redo, fields)) { |
|
goto done; |
|
} |
|
return true; |
|
} else if (it->flags & EcsIterTrivialSearchWildcard) { |
|
ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; |
|
int32_t fields = ctx.rule->filter.term_count; |
|
if (!flecs_rule_trivial_search_w_wildcards(ctx.rule, &ctx, op_ctx, !redo, fields)) { |
|
goto done; |
|
} |
|
it->table = ctx.vars[0].range.table; |
|
it->count = ecs_table_count(it->table); |
|
it->entities = flecs_table_entities_array(it->table); |
|
return true; |
|
} |
|
|
|
/* Default iterator mode */ |
|
if (flecs_rule_run_until(redo, &ctx, ops, -1, rit->op, EcsRuleYield)) { |
|
ecs_assert(ops[ctx.op_index].kind == EcsRuleYield, |
|
ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_range_t *range = &ctx.vars[0].range; |
|
ecs_table_t *table = range->table; |
|
int32_t count = range->count; |
|
if (table) { |
|
if (!count) { |
|
count = ecs_table_count(table); |
|
} |
|
it->table = table; |
|
it->offset = range->offset; |
|
it->count = count; |
|
it->entities = ECS_ELEM_T( |
|
table->data.entities.array, ecs_entity_t, it->offset); |
|
} else if (count == 1) { |
|
it->count = 1; |
|
it->entities = &ctx.vars[0].entity; |
|
} |
|
|
|
rit->op = flecs_itolbl(ctx.op_index - 1); |
|
return true; |
|
} |
|
|
|
done: |
|
ecs_iter_fini(it); |
|
return false; |
|
} |
|
|
|
bool ecs_rule_next( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); |
|
|
|
if (flecs_iter_next_row(it)) { |
|
return true; |
|
} |
|
|
|
return flecs_iter_next_instanced(it, ecs_rule_next_instanced(it)); |
|
error: |
|
return false; |
|
} |
|
|
|
static |
|
void flecs_rule_iter_fini_ctx( |
|
ecs_iter_t *it, |
|
ecs_rule_iter_t *rit) |
|
{ |
|
const ecs_rule_t *rule = rit->rule; |
|
int32_t i, count = rule->op_count; |
|
ecs_rule_op_t *ops = rule->ops; |
|
ecs_rule_op_ctx_t *ctx = rit->op_ctx; |
|
ecs_allocator_t *a = flecs_rule_get_allocator(it); |
|
|
|
for (i = 0; i < count; i ++) { |
|
ecs_rule_op_t *op = &ops[i]; |
|
switch(op->kind) { |
|
case EcsRuleTrav: |
|
flecs_rule_trav_cache_fini(a, &ctx[i].is.trav.cache); |
|
break; |
|
case EcsRuleUp: |
|
case EcsRuleSelfUp: |
|
case EcsRuleUpId: |
|
case EcsRuleSelfUpId: { |
|
ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; |
|
if (cache->dir == EcsDown) { |
|
flecs_rule_down_cache_fini(a, cache); |
|
} else { |
|
flecs_rule_up_cache_fini(cache); |
|
} |
|
break; |
|
} |
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_rule_iter_fini( |
|
ecs_iter_t *it) |
|
{ |
|
ecs_rule_iter_t *rit = &it->priv.iter.rule; |
|
ecs_assert(rit->rule != NULL, ECS_INVALID_OPERATION, NULL); |
|
ecs_poly_assert(rit->rule, ecs_rule_t); |
|
int32_t op_count = rit->rule->op_count; |
|
int32_t var_count = rit->rule->var_count; |
|
|
|
#ifdef FLECS_DEBUG |
|
if (it->flags & EcsIterProfile) { |
|
char *str = ecs_rule_str_w_profile(rit->rule, it); |
|
printf("%s\n", str); |
|
ecs_os_free(str); |
|
} |
|
|
|
flecs_iter_free_n(rit->profile, ecs_rule_op_profile_t, op_count); |
|
#endif |
|
|
|
flecs_rule_iter_fini_ctx(it, rit); |
|
flecs_iter_free_n(rit->vars, ecs_var_t, var_count); |
|
flecs_iter_free_n(rit->written, ecs_write_flags_t, op_count); |
|
flecs_iter_free_n(rit->op_ctx, ecs_rule_op_ctx_t, op_count); |
|
rit->vars = NULL; |
|
rit->written = NULL; |
|
rit->op_ctx = NULL; |
|
rit->rule = NULL; |
|
} |
|
|
|
ecs_iter_t ecs_rule_iter( |
|
const ecs_world_t *world, |
|
const ecs_rule_t *rule) |
|
{ |
|
ecs_iter_t it = {0}; |
|
ecs_rule_iter_t *rit = &it.priv.iter.rule; |
|
ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
ecs_run_aperiodic(rule->filter.world, EcsAperiodicEmptyTables); |
|
|
|
int32_t i, var_count = rule->var_count, op_count = rule->op_count; |
|
it.world = ECS_CONST_CAST(ecs_world_t*, world); |
|
it.real_world = rule->filter.world; |
|
it.query = &rule->filter; |
|
it.terms = rule->filter.terms; |
|
it.next = ecs_rule_next; |
|
it.fini = flecs_rule_iter_fini; |
|
it.field_count = rule->filter.field_count; |
|
it.sizes = rule->filter.sizes; |
|
it.system = rule->filter.entity; |
|
flecs_filter_apply_iter_flags(&it, &rule->filter); |
|
|
|
flecs_iter_init(world, &it, |
|
flecs_iter_cache_ids | |
|
flecs_iter_cache_columns | |
|
flecs_iter_cache_sources | |
|
flecs_iter_cache_ptrs); |
|
|
|
rit->rule = rule; |
|
rit->rule_vars = rule->vars; |
|
rit->ops = rule->ops; |
|
rit->source_set = 0; |
|
if (var_count) { |
|
rit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); |
|
} |
|
if (op_count) { |
|
rit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); |
|
rit->op_ctx = flecs_iter_calloc_n(&it, ecs_rule_op_ctx_t, op_count); |
|
} |
|
|
|
#ifdef FLECS_DEBUG |
|
rit->profile = flecs_iter_calloc_n(&it, ecs_rule_op_profile_t, op_count); |
|
#endif |
|
|
|
for (i = 1; i < var_count; i ++) { |
|
rit->vars[i].entity = EcsWildcard; |
|
} |
|
|
|
it.variables = rit->vars; |
|
it.variable_count = rule->var_pub_count; |
|
it.variable_names = rule->var_names; |
|
|
|
error: |
|
return it; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/rules/trav_cache.c |
|
* @brief Cache that stores the result of graph traversal. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_RULES |
|
|
|
static |
|
void flecs_rule_build_down_cache( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_cache_t *cache, |
|
ecs_entity_t trav, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); |
|
if (!idr) { |
|
return; |
|
} |
|
|
|
ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, |
|
ecs_trav_elem_t); |
|
elem->entity = entity; |
|
elem->idr = idr; |
|
|
|
ecs_table_cache_iter_t it; |
|
if (flecs_table_cache_iter(&idr->cache, &it)) { |
|
ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_t *table = tr->hdr.table; |
|
if (!table->_->traversable_count) { |
|
continue; |
|
} |
|
|
|
int32_t i, count = ecs_table_count(table); |
|
ecs_entity_t *entities = table->data.entities.array; |
|
for (i = 0; i < count; i ++) { |
|
ecs_record_t *r = flecs_entities_get(world, entities[i]); |
|
if (r->row & EcsEntityIsTraversable) { |
|
flecs_rule_build_down_cache( |
|
world, a, ctx, cache, trav, entities[i]); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
void flecs_rule_build_up_cache( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_cache_t *cache, |
|
ecs_entity_t trav, |
|
ecs_table_t *table, |
|
const ecs_table_record_t *tr, |
|
int32_t root_column) |
|
{ |
|
ecs_id_t *ids = table->type.array; |
|
int32_t i = tr->index, end = i + tr->count; |
|
bool is_root = root_column == -1; |
|
|
|
for (; i < end; i ++) { |
|
ecs_entity_t second = ecs_pair_second(world, ids[i]); |
|
if (is_root) { |
|
root_column = i; |
|
} |
|
|
|
ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, |
|
ecs_trav_elem_t); |
|
el->entity = second; |
|
el->column = root_column; |
|
el->idr = NULL; |
|
|
|
ecs_record_t *r = flecs_entities_get_any(world, second); |
|
if (r->table) { |
|
ecs_table_record_t *r_tr = flecs_id_record_get_table( |
|
cache->idr, r->table); |
|
if (!r_tr) { |
|
return; |
|
} |
|
flecs_rule_build_up_cache(world, a, ctx, cache, trav, r->table, |
|
r_tr, root_column); |
|
} |
|
} |
|
} |
|
|
|
void flecs_rule_trav_cache_fini( |
|
ecs_allocator_t *a, |
|
ecs_trav_cache_t *cache) |
|
{ |
|
ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); |
|
} |
|
|
|
void flecs_rule_get_trav_down_cache( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_cache_t *cache, |
|
ecs_entity_t trav, |
|
ecs_entity_t entity) |
|
{ |
|
if (cache->id != ecs_pair(trav, entity) || cache->up) { |
|
ecs_world_t *world = ctx->it->real_world; |
|
ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); |
|
ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); |
|
flecs_rule_build_down_cache(world, a, ctx, cache, trav, entity); |
|
cache->id = ecs_pair(trav, entity); |
|
cache->up = false; |
|
} |
|
} |
|
|
|
void flecs_rule_get_trav_up_cache( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_cache_t *cache, |
|
ecs_entity_t trav, |
|
ecs_table_t *table) |
|
{ |
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_world_t *world = ctx->it->real_world; |
|
ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); |
|
|
|
ecs_id_record_t *idr = cache->idr; |
|
if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { |
|
idr = cache->idr = flecs_id_record_get(world, |
|
ecs_pair(trav, EcsWildcard)); |
|
if (!idr) { |
|
ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); |
|
return; |
|
} |
|
} |
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); |
|
if (!tr) { |
|
ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); |
|
return; |
|
} |
|
|
|
ecs_id_t id = table->type.array[tr->index]; |
|
|
|
if (cache->id != id || !cache->up) { |
|
ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); |
|
flecs_rule_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); |
|
cache->id = id; |
|
cache->up = true; |
|
} |
|
} |
|
|
|
#endif |
|
|
|
|
|
#ifdef FLECS_RULES |
|
|
|
static |
|
void flecs_trav_entity_down_isa( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_trav_down_t *dst, |
|
ecs_entity_t trav, |
|
ecs_entity_t entity, |
|
ecs_id_record_t *idr_with, |
|
bool self); |
|
|
|
static |
|
ecs_trav_down_t* flecs_trav_entity_down( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_trav_down_t *dst, |
|
ecs_entity_t trav, |
|
ecs_entity_t entity, |
|
ecs_id_record_t *idr_trav, |
|
ecs_id_record_t *idr_with, |
|
bool self); |
|
|
|
static |
|
ecs_trav_down_t* flecs_trav_down_ensure( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_entity_t entity) |
|
{ |
|
ecs_trav_down_t **trav = ecs_map_ensure_ref( |
|
&cache->src, ecs_trav_down_t, entity); |
|
if (!trav[0]) { |
|
trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); |
|
ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); |
|
} |
|
|
|
return trav[0]; |
|
} |
|
|
|
static |
|
ecs_trav_down_t* flecs_trav_table_down( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_trav_down_t *dst, |
|
ecs_entity_t trav, |
|
const ecs_table_t *table, |
|
ecs_id_record_t *idr_with, |
|
bool self) |
|
{ |
|
ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (!table->_->traversable_count) { |
|
return dst; |
|
} |
|
|
|
ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities); |
|
int32_t i, count = ecs_table_count(table); |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t entity = entities[i]; |
|
ecs_record_t *record = flecs_entities_get(world, entity); |
|
if (!record) { |
|
continue; |
|
} |
|
|
|
uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); |
|
if (flags & EcsEntityIsTraversable) { |
|
ecs_id_record_t *idr_trav = flecs_id_record_get(world, |
|
ecs_pair(trav, entity)); |
|
if (!idr_trav) { |
|
continue; |
|
} |
|
|
|
flecs_trav_entity_down(world, a, cache, dst, |
|
trav, entity, idr_trav, idr_with, self); |
|
} |
|
} |
|
|
|
return dst; |
|
} |
|
|
|
static |
|
void flecs_trav_entity_down_isa( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_trav_down_t *dst, |
|
ecs_entity_t trav, |
|
ecs_entity_t entity, |
|
ecs_id_record_t *idr_with, |
|
bool self) |
|
{ |
|
if (trav == EcsIsA || !world->idr_isa_wildcard) { |
|
return; |
|
} |
|
|
|
ecs_id_record_t *idr_isa = flecs_id_record_get( |
|
world, ecs_pair(EcsIsA, entity)); |
|
if (!idr_isa) { |
|
return; |
|
} |
|
|
|
ecs_table_cache_iter_t it; |
|
if (flecs_table_cache_all_iter(&idr_isa->cache, &it)) { |
|
ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_table_t *table = tr->hdr.table; |
|
if (!table->_->traversable_count) { |
|
continue; |
|
} |
|
|
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities); |
|
int32_t i, count = ecs_table_count(table); |
|
for (i = 0; i < count; i ++) { |
|
ecs_entity_t e = entities[i]; |
|
ecs_record_t *record = flecs_entities_get(world, e); |
|
if (!record) { |
|
continue; |
|
} |
|
|
|
uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); |
|
if (flags & EcsEntityIsTraversable) { |
|
ecs_id_record_t *idr_trav = flecs_id_record_get(world, |
|
ecs_pair(trav, e)); |
|
flecs_trav_entity_down(world, a, cache, dst, trav, e, |
|
idr_trav, idr_with, self); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
static |
|
ecs_trav_down_t* flecs_trav_entity_down( |
|
ecs_world_t *world, |
|
ecs_allocator_t *a, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_trav_down_t *dst, |
|
ecs_entity_t trav, |
|
ecs_entity_t entity, |
|
ecs_id_record_t *idr_trav, |
|
ecs_id_record_t *idr_with, |
|
bool self) |
|
{ |
|
ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
flecs_trav_entity_down_isa( |
|
world, a, cache, dst, trav, entity, idr_with, self); |
|
|
|
int32_t first = ecs_vec_count(&dst->elems); |
|
|
|
ecs_table_cache_iter_t it; |
|
if (flecs_table_cache_iter(&idr_trav->cache, &it)) { |
|
ecs_table_record_t *tr; |
|
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { |
|
ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_t *table = tr->hdr.table; |
|
bool leaf = false; |
|
|
|
if (flecs_id_record_get_table(idr_with, table) != NULL) { |
|
if (self) { |
|
continue; |
|
} |
|
leaf = true; |
|
} |
|
|
|
/* If record is not the first instance of (trav, *), don't add it |
|
* to the cache. */ |
|
int32_t index = tr->index; |
|
if (index) { |
|
ecs_id_t id = table->type.array[index - 1]; |
|
if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { |
|
int32_t col = ecs_search_relation(world, table, 0, |
|
idr_with->id, trav, EcsUp, NULL, NULL, &tr); |
|
ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
if (col != index) { |
|
/* First relationship through which the id is |
|
* reachable is not the current one, so skip. */ |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
ecs_trav_down_elem_t *elem = ecs_vec_append_t( |
|
a, &dst->elems, ecs_trav_down_elem_t); |
|
elem->table = table; |
|
elem->leaf = leaf; |
|
} |
|
} |
|
|
|
/* Breadth first walk */ |
|
int32_t t, last = ecs_vec_count(&dst->elems); |
|
for (t = first; t < last; t ++) { |
|
ecs_trav_down_elem_t *elem = ecs_vec_get_t( |
|
&dst->elems, ecs_trav_down_elem_t, t); |
|
if (!elem->leaf) { |
|
flecs_trav_table_down(world, a, cache, dst, trav, |
|
elem->table, idr_with, self); |
|
} |
|
} |
|
|
|
return dst; |
|
} |
|
|
|
ecs_trav_down_t* flecs_rule_get_down_cache( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_entity_t trav, |
|
ecs_entity_t e, |
|
ecs_id_record_t *idr_with, |
|
bool self) |
|
{ |
|
ecs_world_t *world = ctx->it->real_world; |
|
ecs_assert(!(cache->dir & EcsUp), ECS_INTERNAL_ERROR, NULL); |
|
cache->dir = EcsDown; |
|
|
|
ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); |
|
ecs_map_init_if(&cache->src, a); |
|
|
|
ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); |
|
if (result->ready) { |
|
return result; |
|
} |
|
|
|
ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); |
|
if (!idr_trav) { |
|
if (trav != EcsIsA) { |
|
flecs_trav_entity_down_isa( |
|
world, a, cache, result, trav, e, idr_with, self); |
|
} |
|
result->ready = true; |
|
return result; |
|
} |
|
|
|
ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); |
|
flecs_trav_entity_down( |
|
world, a, cache, result, trav, e, idr_trav, idr_with, self); |
|
result->ready = true; |
|
|
|
return result; |
|
} |
|
|
|
void flecs_rule_down_cache_fini( |
|
ecs_allocator_t *a, |
|
ecs_trav_up_cache_t *cache) |
|
{ |
|
ecs_map_iter_t it = ecs_map_iter(&cache->src); |
|
while (ecs_map_next(&it)) { |
|
ecs_trav_down_t *t = ecs_map_ptr(&it); |
|
ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); |
|
} |
|
ecs_map_fini(&cache->src); |
|
} |
|
|
|
#endif |
|
|
|
|
|
#ifdef FLECS_RULES |
|
|
|
static |
|
ecs_trav_up_t* flecs_trav_up_ensure( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_up_cache_t *cache, |
|
uint64_t table_id) |
|
{ |
|
ecs_trav_up_t **trav = ecs_map_ensure_ref( |
|
&cache->src, ecs_trav_up_t, table_id); |
|
if (!trav[0]) { |
|
trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); |
|
} |
|
|
|
return trav[0]; |
|
} |
|
|
|
static |
|
int32_t flecs_trav_type_search( |
|
ecs_trav_up_t *up, |
|
const ecs_table_t *table, |
|
ecs_id_record_t *idr_with, |
|
ecs_type_t *type) |
|
{ |
|
ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); |
|
if (tr) { |
|
up->id = type->array[tr->index]; |
|
return up->column = tr->index; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
static |
|
int32_t flecs_trav_type_offset_search( |
|
ecs_trav_up_t *up, |
|
int32_t offset, |
|
ecs_id_t with, |
|
ecs_type_t *type) |
|
{ |
|
ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); |
|
|
|
while (offset < type->count) { |
|
ecs_id_t type_id = type->array[offset ++]; |
|
if (ecs_id_match(type_id, with)) { |
|
up->id = flecs_to_public_id(type_id); |
|
return up->column = offset - 1; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
static |
|
ecs_trav_up_t* flecs_trav_table_up( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_allocator_t *a, |
|
ecs_trav_up_cache_t *cache, |
|
const ecs_world_t *world, |
|
ecs_entity_t src, |
|
ecs_id_t with, |
|
ecs_id_t rel, |
|
ecs_id_record_t *idr_with, |
|
ecs_id_record_t *idr_trav) |
|
{ |
|
ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); |
|
if (up->ready) { |
|
return up; |
|
} |
|
|
|
ecs_record_t *src_record = flecs_entities_get_any(world, src); |
|
ecs_table_t *table = src_record->table; |
|
if (!table) { |
|
goto not_found; |
|
} |
|
|
|
ecs_type_t type = table->type; |
|
if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { |
|
up->src = src; |
|
goto found; |
|
} |
|
|
|
ecs_flags32_t flags = table->flags; |
|
if ((flags & EcsTableHasPairs) && rel) { |
|
bool is_a = idr_trav == world->idr_isa_wildcard; |
|
if (is_a) { |
|
if (!(flags & EcsTableHasIsA)) { |
|
goto not_found; |
|
} |
|
|
|
if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { |
|
goto not_found; |
|
} |
|
} |
|
|
|
ecs_trav_up_t up_pair = {0}; |
|
int32_t r_column = flecs_trav_type_search( |
|
&up_pair, table, idr_trav, &type); |
|
|
|
while (r_column != -1) { |
|
ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); |
|
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, |
|
world, tgt, with, rel, idr_with, idr_trav); |
|
if (up_parent->column != -1) { |
|
up->src = up_parent->src; |
|
up->column = up_parent->column; |
|
up->id = up_parent->id; |
|
goto found; |
|
} |
|
|
|
r_column = flecs_trav_type_offset_search( |
|
&up_pair, r_column + 1, rel, &type); |
|
} |
|
|
|
if (!is_a) { |
|
idr_trav = world->idr_isa_wildcard; |
|
r_column = flecs_trav_type_search( |
|
&up_pair, table, idr_trav, &type); |
|
|
|
while (r_column != -1) { |
|
ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); |
|
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); |
|
|
|
ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, |
|
world, tgt, with, rel, idr_with, idr_trav); |
|
if (up_parent->column != -1) { |
|
up->src = up_parent->src; |
|
up->column = up_parent->column; |
|
up->id = up_parent->id; |
|
goto found; |
|
} |
|
|
|
r_column = flecs_trav_type_offset_search( |
|
&up_pair, r_column + 1, rel, &type); |
|
} |
|
} |
|
} |
|
|
|
not_found: |
|
up->column = -1; |
|
found: |
|
up->ready = true; |
|
return up; |
|
} |
|
|
|
ecs_trav_up_t* flecs_rule_get_up_cache( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_trav_up_cache_t *cache, |
|
ecs_table_t *table, |
|
ecs_id_t with, |
|
ecs_entity_t trav, |
|
ecs_id_record_t *idr_with, |
|
ecs_id_record_t *idr_trav) |
|
{ |
|
if (cache->with && cache->with != with) { |
|
flecs_rule_up_cache_fini(cache); |
|
} |
|
|
|
ecs_world_t *world = ctx->it->real_world; |
|
ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); |
|
ecs_map_init_if(&cache->src, a); |
|
|
|
ecs_assert(!(cache->dir & EcsDown), ECS_INTERNAL_ERROR, NULL); |
|
cache->dir = EcsUp; |
|
cache->with = with; |
|
|
|
ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); |
|
ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); |
|
if (!tr) { |
|
return NULL; /* Table doesn't have the relationship */ |
|
} |
|
|
|
int32_t i = tr->index, end = i + tr->count; |
|
for (; i < end; i ++) { |
|
ecs_id_t id = table->type.array[i]; |
|
ecs_entity_t tgt = ECS_PAIR_SECOND(id); |
|
ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, |
|
with, ecs_pair(trav, EcsWildcard), idr_with, idr_trav); |
|
ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); |
|
if (result->src != 0) { |
|
return result; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void flecs_rule_up_cache_fini( |
|
ecs_trav_up_cache_t *cache) |
|
{ |
|
ecs_map_fini(&cache->src); |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/rules/engine.c |
|
* @brief Iterator for trivial queries. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_RULES |
|
|
|
static |
|
bool flecs_rule_trivial_init( |
|
ecs_world_t *world, |
|
const ecs_filter_t *filter) |
|
{ |
|
int32_t t, count = filter->term_count; |
|
ecs_term_t *terms = filter->terms; |
|
|
|
for (t = 0; t < count; t ++) { |
|
ecs_term_t *term = &terms[t]; |
|
if (!term->idr) { |
|
term->idr = flecs_id_record_get(world, term->id); |
|
if (!term->idr) { |
|
/* Id doesn't exist, so query can't match */ |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool flecs_rule_trivial_test( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
bool first, |
|
int32_t term_count) |
|
{ |
|
if (first) { |
|
const ecs_filter_t *filter = &rule->filter; |
|
int32_t t; |
|
ecs_term_t *terms = filter->terms; |
|
ecs_iter_t *it = ctx->it; |
|
|
|
if (!flecs_rule_trivial_init(ctx->world, filter)) { |
|
return false; |
|
} |
|
|
|
ecs_table_t *table = ctx->vars[0].range.table; |
|
ecs_assert(table != NULL, ECS_INVALID_OPERATION, NULL); |
|
|
|
for (t = 0; t < term_count; t ++) { |
|
ecs_term_t *term = &terms[t]; |
|
const ecs_table_record_t *tr = flecs_id_record_get_table( |
|
term->idr, table); |
|
if (!tr) { |
|
return false; |
|
} |
|
|
|
it->columns[t] = tr->index + 1; |
|
if (it->count && tr->column != -1) { |
|
it->ptrs[t] = ecs_vec_get( |
|
&table->data.columns[tr->column].data, |
|
it->sizes[t], |
|
it->offset); |
|
} |
|
} |
|
|
|
it->table = table; |
|
it->entities = &flecs_table_entities_array(table)[it->offset]; |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static |
|
bool flecs_rule_trivial_search_init( |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_trivial_ctx_t *op_ctx, |
|
const ecs_filter_t *filter, |
|
bool first) |
|
{ |
|
if (first) { |
|
ecs_term_t *terms = filter->terms; |
|
if (!flecs_rule_trivial_init(ctx->world, filter)) { |
|
return false; |
|
} |
|
|
|
if (filter->flags & EcsFilterMatchEmptyTables) { |
|
if (!flecs_table_cache_all_iter(&terms[0].idr->cache, &op_ctx->it)){ |
|
return false; |
|
} |
|
} else { |
|
if (!flecs_table_cache_iter(&terms[0].idr->cache, &op_ctx->it)) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool flecs_rule_trivial_search( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_trivial_ctx_t *op_ctx, |
|
bool first, |
|
int32_t term_count) |
|
{ |
|
const ecs_filter_t *filter = &rule->filter; |
|
int32_t t; |
|
ecs_term_t *terms = filter->terms; |
|
ecs_iter_t *it = ctx->it; |
|
|
|
if (!flecs_rule_trivial_search_init(ctx, op_ctx, filter, first)) { |
|
return false; |
|
} |
|
|
|
do { |
|
const ecs_table_record_t *tr = flecs_table_cache_next( |
|
&op_ctx->it, ecs_table_record_t); |
|
if (!tr) { |
|
return false; |
|
} |
|
|
|
ecs_table_t *table = tr->hdr.table; |
|
if (table->flags & (EcsTableIsPrefab|EcsTableIsDisabled)) { |
|
continue; |
|
} |
|
|
|
for (t = 1; t < term_count; t ++) { |
|
ecs_term_t *term = &terms[t]; |
|
const ecs_table_record_t *tr_with = flecs_id_record_get_table( |
|
term->idr, table); |
|
if (!tr_with) { |
|
break; |
|
} |
|
|
|
it->columns[t] = tr_with->index + 1; |
|
if (tr_with->column != -1) { |
|
it->ptrs[t] = ecs_vec_first( |
|
&table->data.columns[tr_with->column].data); |
|
} |
|
} |
|
|
|
if (t == term_count) { |
|
ctx->vars[0].range.table = table; |
|
ctx->vars[0].range.count = 0; |
|
ctx->vars[0].range.offset = 0; |
|
it->columns[0] = tr->index + 1; |
|
if (tr->column != -1) { |
|
it->ptrs[0] = ecs_vec_first( |
|
&table->data.columns[tr->column].data); |
|
} |
|
break; |
|
} |
|
} while (true); |
|
|
|
return true; |
|
} |
|
|
|
bool flecs_rule_trivial_search_w_wildcards( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_trivial_ctx_t *op_ctx, |
|
bool first, |
|
int32_t term_count) |
|
{ |
|
bool result = flecs_rule_trivial_search( |
|
rule, ctx, op_ctx, first, term_count); |
|
if (result) { |
|
ecs_iter_t *it = ctx->it; |
|
ecs_table_t *table = ctx->vars[0].range.table; |
|
int32_t t; |
|
for (t = 0; t < term_count; t ++) { |
|
it->ids[t] = table->type.array[it->columns[t] - 1]; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
bool flecs_rule_trivial_test_w_wildcards( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
bool first, |
|
int32_t term_count) |
|
{ |
|
bool result = flecs_rule_trivial_test( |
|
rule, ctx, first, term_count); |
|
if (result) { |
|
ecs_iter_t *it = ctx->it; |
|
ecs_table_t *table = ctx->vars[0].range.table; |
|
int32_t t; |
|
for (t = 0; t < term_count; t ++) { |
|
it->ids[t] = table->type.array[it->columns[t] - 1]; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
bool flecs_rule_trivial_search_nodata( |
|
const ecs_rule_t *rule, |
|
const ecs_rule_run_ctx_t *ctx, |
|
ecs_rule_trivial_ctx_t *op_ctx, |
|
bool first, |
|
int32_t term_count) |
|
{ |
|
const ecs_filter_t *filter = &rule->filter; |
|
int32_t t; |
|
ecs_term_t *terms = filter->terms; |
|
ecs_iter_t *it = ctx->it; |
|
|
|
if (!flecs_rule_trivial_search_init(ctx, op_ctx, filter, first)) { |
|
return false; |
|
} |
|
|
|
do { |
|
const ecs_table_record_t *tr = flecs_table_cache_next( |
|
&op_ctx->it, ecs_table_record_t); |
|
if (!tr) { |
|
return false; |
|
} |
|
|
|
ecs_table_t *table = tr->hdr.table; |
|
if (table->flags & (EcsTableIsPrefab|EcsTableIsDisabled)) { |
|
continue; |
|
} |
|
|
|
for (t = 1; t < term_count; t ++) { |
|
ecs_term_t *term = &terms[t]; |
|
const ecs_table_record_t *tr_with = flecs_id_record_get_table( |
|
term->idr, table); |
|
if (!tr_with) { |
|
break; |
|
} |
|
|
|
it->columns[t] = tr_with->index + 1; |
|
} |
|
|
|
if (t == term_count) { |
|
ctx->vars[0].range.table = table; |
|
ctx->vars[0].range.count = 0; |
|
ctx->vars[0].range.offset = 0; |
|
it->columns[0] = tr->index + 1; |
|
break; |
|
} |
|
} while (true); |
|
|
|
return true; |
|
} |
|
|
|
#endif |
|
|
|
/** |
|
* @file addons/system/system.c |
|
* @brief System addon. |
|
*/ |
|
|
|
|
|
#ifdef FLECS_SYSTEM |
|
|
|
|
|
ecs_mixins_t ecs_system_t_mixins = { |
|
.type_name = "ecs_system_t", |
|
.elems = { |
|
[EcsMixinWorld] = offsetof(ecs_system_t, world), |
|
[EcsMixinEntity] = offsetof(ecs_system_t, entity), |
|
[EcsMixinDtor] = offsetof(ecs_system_t, dtor) |
|
} |
|
}; |
|
|
|
/* -- Public API -- */ |
|
|
|
ecs_entity_t ecs_run_intern( |
|
ecs_world_t *world, |
|
ecs_stage_t *stage, |
|
ecs_entity_t system, |
|
ecs_system_t *system_data, |
|
int32_t stage_index, |
|
int32_t stage_count, |
|
ecs_ftime_t delta_time, |
|
int32_t offset, |
|
int32_t limit, |
|
void *param) |
|
{ |
|
ecs_ftime_t time_elapsed = delta_time; |
|
ecs_entity_t tick_source = system_data->tick_source; |
|
|
|
/* Support legacy behavior */ |
|
if (!param) { |
|
param = system_data->ctx; |
|
} |
|
|
|
if (tick_source) { |
|
const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); |
|
|
|
if (tick) { |
|
time_elapsed = tick->time_elapsed; |
|
|
|
/* If timer hasn't fired we shouldn't run the system */ |
|
if (!tick->tick) { |
|
return 0; |
|
} |
|
} else { |
|
/* If a timer has been set but the timer entity does not have the |
|
* EcsTimer component, don't run the system. This can be the result |
|
* of a single-shot timer that has fired already. Not resetting the |
|
* timer field of the system will ensure that the system won't be |
|
* ran after the timer has fired. */ |
|
return 0; |
|
} |
|
} |
|
|
|
if (ecs_should_log_3()) { |
|
char *path = ecs_get_fullpath(world, system); |
|
ecs_dbg_3("worker %d: %s", stage_index, path); |
|
ecs_os_free(path); |
|
} |
|
|
|
ecs_time_t time_start; |
|
bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); |
|
if (measure_time) { |
|
ecs_os_get_time(&time_start); |
|
} |
|
|
|
ecs_world_t *thread_ctx = world; |
|
if (stage) { |
|
thread_ctx = stage->thread_ctx; |
|
} else { |
|
stage = &world->stages[0]; |
|
} |
|
|
|
/* Prepare the query iterator */ |
|
ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query); |
|
ecs_iter_t *it = &qit; |
|
|
|
qit.system = system; |
|
qit.delta_time = delta_time; |
|
qit.delta_system_time = time_elapsed; |
|
qit.frame_offset = offset; |
|
qit.param = param; |
|
qit.ctx = system_data->ctx; |
|
qit.binding_ctx = system_data->binding_ctx; |
|
|
|
flecs_defer_begin(world, stage); |
|
|
|
if (offset || limit) { |
|
pit = ecs_page_iter(it, offset, limit); |
|
it = &pit; |
|
} |
|
|
|
if (stage_count > 1 && system_data->multi_threaded) { |
|
wit = ecs_worker_iter(it, stage_index, stage_count); |
|
it = &wit; |
|
} |
|
|
|
ecs_iter_action_t action = system_data->action; |
|
it->callback = action; |
|
|
|
ecs_run_action_t run = system_data->run; |
|
if (run) { |
|
run(it); |
|
} else if (system_data->query->filter.term_count) { |
|
if (it == &qit) { |
|
while (ecs_query_next(&qit)) { |
|
action(&qit); |
|
} |
|
} else { |
|
while (ecs_iter_next(it)) { |
|
action(it); |
|
} |
|
} |
|
} else { |
|
action(&qit); |
|
ecs_iter_fini(&qit); |
|
} |
|
|
|
if (measure_time) { |
|
system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); |
|
} |
|
|
|
system_data->invoke_count ++; |
|
|
|
flecs_defer_end(world, stage); |
|
|
|
return it->interrupted_by; |
|
} |
|
|
|
/* -- Public API -- */ |
|
|
|
ecs_entity_t ecs_run_w_filter( |
|
ecs_world_t *world, |
|
ecs_entity_t system, |
|
ecs_ftime_t delta_time, |
|
int32_t offset, |
|
int32_t limit, |
|
void *param) |
|
{ |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); |
|
ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); |
|
return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, |
|
offset, limit, param); |
|
} |
|
|
|
ecs_entity_t ecs_run_worker( |
|
ecs_world_t *world, |
|
ecs_entity_t system, |
|
int32_t stage_index, |
|
int32_t stage_count, |
|
ecs_ftime_t delta_time, |
|
void *param) |
|
{ |
|
ecs_stage_t *stage = flecs_stage_from_world(&world); |
|
ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); |
|
ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); |
|
|
|
return ecs_run_intern( |
|
world, stage, system, system_data, stage_index, stage_count, |
|
delta_time, 0, 0, param); |
|
} |
|
|
|
ecs_entity_t ecs_run( |
|
ecs_world_t *world, |
|
ecs_entity_t system, |
|
ecs_ftime_t delta_time, |
|
void *param) |
|
{ |
|
return ecs_run_w_filter(world, system, delta_time, 0, 0, param); |
|
} |
|
|
|
ecs_query_t* ecs_system_get_query( |
|
const ecs_world_t *world, |
|
ecs_entity_t system) |
|
{ |
|
const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); |
|
if (s) { |
|
return s->query; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
void* ecs_system_get_ctx( |
|
const ecs_world_t *world, |
|
ecs_entity_t system) |
|
{ |
|
const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); |
|
if (s) { |
|
return s->ctx; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
void* ecs_system_get_binding_ctx( |
|
const ecs_world_t *world, |
|
ecs_entity_t system) |
|
{ |
|
const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); |
|
if (s) { |
|
return s->binding_ctx; |
|
} else { |
|
return NULL; |
|
} |
|
} |
|
|
|
/* System deinitialization */ |
|
static |
|
void flecs_system_fini(ecs_system_t *sys) { |
|
if (sys->ctx_free) { |
|
sys->ctx_free(sys->ctx); |
|
} |
|
|
|
if (sys->binding_ctx_free) { |
|
sys->binding_ctx_free(sys->binding_ctx); |
|
} |
|
|
|
ecs_poly_free(sys, ecs_system_t); |
|
} |
|
|
|
static |
|
void flecs_system_init_timer( |
|
ecs_world_t *world, |
|
ecs_entity_t entity, |
|
const ecs_system_desc_t *desc) |
|
{ |
|
if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || |
|
ECS_NEQZERO(desc->tick_source)) |
|
{ |
|
#ifdef FLECS_TIMER |
|
if (ECS_NEQZERO(desc->interval)) { |
|
ecs_set_interval(world, entity, desc->interval); |
|
} |
|
|
|
if (desc->rate) { |
|
ecs_entity_t tick_source = desc->tick_source; |
|
if (!tick_source) { |
|
tick_source = entity; |
|
} |
|
ecs_set_rate(world, entity, desc->rate, tick_source); |
|
} else if (desc->tick_source) { |
|
ecs_set_tick_source(world, entity, desc->tick_source); |
|
} |
|
#else |
|
(void)world; |
|
(void)entity; |
|
ecs_abort(ECS_UNSUPPORTED, "timer module not available"); |
|
#endif |
|
} |
|
} |
|
|
|
ecs_entity_t ecs_system_init( |
|
ecs_world_t *world, |
|
const ecs_system_desc_t *desc) |
|
{ |
|
ecs_poly_assert(world, ecs_world_t); |
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); |
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); |
|
ecs_assert(!(world->flags & EcsWorldReadonly), |
|
ECS_INVALID_WHILE_READONLY, NULL); |
|
|
|
ecs_entity_t entity = desc->entity; |
|
if (!entity) { |
|
entity = ecs_new(world, 0); |
|
} |
|
EcsPoly *poly = ecs_poly_bind(world, entity, ecs_system_t); |
|
if (!poly->poly) { |
|
ecs_system_t *system = ecs_poly_new(ecs_system_t); |
|
ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); |
|
|
|
poly->poly = system; |
|
system->world = world; |
|
system->dtor = (ecs_poly_dtor_t)flecs_system_fini; |
|
system->entity = entity; |
|
|
|
ecs_query_desc_t query_desc = desc->query; |
|
query_desc.filter.entity = entity; |
|
|
|
ecs_query_t *query = ecs_query_init(world, &query_desc); |
|
if (!query) { |
|
ecs_delete(world, entity); |
|
return 0; |
|
} |
|
|
|
/* Prevent the system from moving while we're initializing */ |
|
flecs_defer_begin(world, &world->stages[0]); |
|
|
|
system->query = query; |
|
system->query_entity = query->filter.entity; |
|
|
|
system->run = desc->run; |
|
system->action = desc->callback; |
|
|
|
system->ctx = desc->ctx; |
|
system->binding_ctx = desc->binding_ctx; |
|
|
|
system->ctx_free = desc->ctx_free; |
|
system->binding_ctx_free = desc->binding_ctx_free; |
|
|
|
system->tick_source = desc->tick_source; |
|
|
|
system->multi_threaded = desc->multi_threaded; |
|
system->no_readonly = desc->no_readonly; |
|
|
|
flecs_system_init_timer(world, entity, desc); |
|
|
|
if (ecs_get_name(world, entity)) { |
|
ecs_trace("#[green]system#[reset] %s created", |
|
ecs_get_name(world, entity)); |
|
} |
|
|
|
ecs_defer_end(world); |
|
} else { |
|
ecs_poly_assert(poly->poly, ecs_system_t); |
|
ecs_system_t *system = (ecs_system_t*)poly->poly; |
|
|
|
if (desc->run) { |
|
system->run = desc->run; |
|
} |
|
if (desc->callback) { |
|
system->action = desc->callback; |
|
} |
|
|
|
if (system->ctx_free) { |
|
if (system->ctx && system->ctx != desc->ctx) { |
|
system->ctx_free(system->ctx); |
|
} |
|
} |
|
if (system->binding_ctx_free) { |
|
if (system->binding_ctx && system->binding_ctx != desc->binding_ctx) { |
|
system->binding_ctx_free(system->binding_ctx); |
|
} |
|
} |
|
|
|
if (desc->ctx) { |
|
system->ctx = desc->ctx; |
|
} |
|
if (desc->binding_ctx) { |
|
system->binding_ctx = desc->binding_ctx; |
|
} |
|
if (desc->ctx_free) { |
|
system->ctx_free = desc->ctx_free; |
|
} |
|
if (desc->binding_ctx_free) { |
|
system->binding_ctx_free = desc->binding_ctx_free; |
|
} |
|
if (desc->query.filter.instanced) { |
|
ECS_BIT_SET(system->query->filter.flags, EcsFilterIsInstanced); |
|
} |
|
if (desc->multi_threaded) { |
|
system->multi_threaded = desc->multi_threaded; |
|
} |
|
if (desc->no_readonly) { |
|
system->no_readonly = desc->no_readonly; |
|
} |
|
|
|
flecs_system_init_timer(world, entity, desc); |
|
} |
|
|
|
ecs_poly_modified(world, entity, ecs_system_t); |
|
|
|
return entity; |
|
error: |
|
return 0; |
|
} |
|
|
|
void FlecsSystemImport( |
|
ecs_world_t *world) |
|
{ |
|
ECS_MODULE(world, FlecsSystem); |
|
#ifdef FLECS_COREDOC |
|
ECS_IMPORT(world, FlecsDoc); |
|
ecs_doc_set_brief(world, ecs_id(FlecsSystem), |
|
"Module that implements Flecs systems"); |
|
#endif |
|
|
|
ecs_set_name_prefix(world, "Ecs"); |
|
|
|
flecs_bootstrap_tag(world, EcsSystem); |
|
flecs_bootstrap_component(world, EcsTickSource); |
|
|
|
/* Make sure to never inherit system component. This makes sure that any |
|
* term created for the System component will default to 'self' traversal, |
|
* which improves efficiency of the query. */ |
|
ecs_add_id(world, EcsSystem, EcsDontInherit); |
|
} |
|
|
|
#endif |
|
|