56004 lines
1.5 MiB
56004 lines
1.5 MiB
/**
|
|
* @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 storage_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.
|
|
*/
|
|
|
|
#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. It is implemented as
|
|
* a sparse set. This file contains convenience macros for working with the
|
|
* entity index.
|
|
*/
|
|
|
|
#ifndef FLECS_ENTITY_INDEX_H
|
|
#define FLECS_ENTITY_INDEX_H
|
|
|
|
#define ecs_eis(world) (&((world)->store.entity_index))
|
|
#define flecs_entities_get(world, entity) flecs_sparse_get(ecs_eis(world), ecs_record_t, entity)
|
|
#define flecs_entities_get_any(world, entity) flecs_sparse_get_any(ecs_eis(world), ecs_record_t, entity)
|
|
#define flecs_entities_set(world, entity, ...) (flecs_sparse_set(ecs_eis(world), ecs_record_t, entity, (__VA_ARGS__)))
|
|
#define flecs_entities_ensure(world, entity) flecs_sparse_ensure(ecs_eis(world), ecs_record_t, entity)
|
|
#define flecs_entities_remove(world, entity) flecs_sparse_remove(ecs_eis(world), entity)
|
|
#define flecs_entities_set_generation(world, entity) flecs_sparse_set_generation(ecs_eis(world), entity)
|
|
#define flecs_entities_is_alive(world, entity) flecs_sparse_is_alive(ecs_eis(world), entity)
|
|
#define flecs_entities_is_valid(world, entity) flecs_sparse_is_valid(ecs_eis(world), entity)
|
|
#define flecs_entities_get_current(world, entity) flecs_sparse_get_alive(ecs_eis(world), entity)
|
|
#define flecs_entities_exists(world, entity) flecs_sparse_exists(ecs_eis(world), entity)
|
|
#define flecs_entities_recycle(world) flecs_sparse_new_id(ecs_eis(world))
|
|
#define flecs_entities_clear_entity(world, entity, is_watched) flecs_entities_set(ecs_eis(world), entity, &(ecs_record_t){NULL, is_watched})
|
|
#define flecs_entities_set_size(world, size) flecs_sparse_set_size(ecs_eis(world), size)
|
|
#define flecs_entities_count(world) flecs_sparse_count(ecs_eis(world))
|
|
#define flecs_entities_clear(world) flecs_sparse_clear(ecs_eis(world))
|
|
#define flecs_entities_copy(world) flecs_sparse_copy(ecs_eis(world))
|
|
#define flecs_entities_free(world) flecs_sparse_free(ecs_eis(world))
|
|
#define flecs_entities_memory(world, allocd, used) flecs_sparse_memory(ecs_eis(world), allocd, used)
|
|
|
|
#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 *cur;
|
|
} ecs_stack_t;
|
|
|
|
void flecs_stack_init(
|
|
ecs_stack_t *stack);
|
|
|
|
void flecs_stack_fini(
|
|
ecs_stack_t *stack);
|
|
|
|
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))
|
|
|
|
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))
|
|
|
|
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);
|
|
|
|
ecs_stack_cursor_t flecs_stack_get_cursor(
|
|
ecs_stack_t *stack);
|
|
|
|
void flecs_stack_restore_cursor(
|
|
ecs_stack_t *stack,
|
|
const 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);
|
|
|
|
/** Deinialize 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
|
|
|
|
|
|
/* 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)
|
|
|
|
/* 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,
|
|
EcsMixinBase, /* If mixin can't be found in object, look in base */
|
|
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;
|
|
|
|
/* 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 nubmer 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;
|
|
|
|
/** Stage-specific component data */
|
|
struct ecs_data_t {
|
|
ecs_vec_t entities; /* Entity identifiers */
|
|
ecs_vec_t records; /* Ptrs to records in main entity index */
|
|
ecs_vec_t *columns; /* Component columns */
|
|
ecs_switch_t *sw_columns; /* Switch columns */
|
|
ecs_bitset_t *bs_columns; /* Bitset columns */
|
|
};
|
|
|
|
/** Cache of added/removed components for non-trivial edges between tables */
|
|
#define ECS_TABLE_DIFF_INIT { .added = {0}}
|
|
|
|
typedef struct ecs_table_diff_t {
|
|
ecs_type_t added; /* Components added between tables */
|
|
ecs_type_t removed; /* Components removed between tables */
|
|
} ecs_table_diff_t;
|
|
|
|
/** 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;
|
|
|
|
/** 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;
|
|
|
|
/** 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_type_t type; /* Identifies table type in type_index */
|
|
ecs_flags32_t flags; /* Flags for testing table properties */
|
|
uint16_t storage_count; /* Number of components (excluding tags) */
|
|
uint16_t generation; /* Used for table cleanup */
|
|
|
|
struct ecs_table_record_t *records; /* Array with table records */
|
|
ecs_table_t *storage_table; /* Table without tags */
|
|
ecs_id_t *storage_ids; /* Component ids (prevent indirection) */
|
|
int32_t *storage_map; /* Map type <-> data type
|
|
* - 0..count(T): type -> data_type
|
|
* - count(T)..count(S): data_type -> type
|
|
*/
|
|
|
|
ecs_graph_node_t node; /* Graph node */
|
|
ecs_data_t data; /* Component storage */
|
|
ecs_type_info_t **type_info; /* Cached type info */
|
|
|
|
int32_t *dirty_state; /* Keep track of changes in columns */
|
|
|
|
int16_t sw_count;
|
|
int16_t sw_offset;
|
|
int16_t bs_count;
|
|
int16_t bs_offset;
|
|
|
|
int32_t refcount; /* Increased when used as storage table */
|
|
int32_t lock; /* Prevents modifications */
|
|
int32_t observed_count; /* Number of observed entities in table */
|
|
uint16_t record_count; /* Table record count including wildcards */
|
|
};
|
|
|
|
/** 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 column */
|
|
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 column */
|
|
typedef struct flecs_bitset_term_t {
|
|
ecs_bitset_t *bs_column;
|
|
int32_t column_index;
|
|
} flecs_bitset_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 */
|
|
bool has_filter;
|
|
} ecs_entity_filter_t;
|
|
|
|
typedef struct ecs_entity_filter_iter_t {
|
|
ecs_entity_filter_t *entity_filter;
|
|
int32_t *columns;
|
|
ecs_table_range_t range;
|
|
int32_t bs_offset;
|
|
int32_t sw_offset;
|
|
int32_t sw_smallest;
|
|
} ecs_entity_filter_iter_t;
|
|
|
|
typedef struct ecs_query_table_match_t ecs_query_table_match_t;
|
|
|
|
/** List node used to iterate tables in a query.
|
|
* The list of nodes dictates the order in which tables should be iterated by a
|
|
* query. A single node may refer to the table multiple times with different
|
|
* offset/count parameters, which enables features such as sorting. */
|
|
struct ecs_query_table_node_t {
|
|
ecs_query_table_node_t *next, *prev;
|
|
ecs_table_t *table; /* The current table. */
|
|
uint64_t group_id; /* Value used to organize tables in groups */
|
|
int32_t offset; /* Starting point in table */
|
|
int32_t count; /* Number of entities to iterate in table */
|
|
ecs_query_table_match_t *match; /* Reference to the match */
|
|
};
|
|
|
|
/** Type containing data for a table matched with a query.
|
|
* A single table can have multiple matches, if the query contains wildcards. */
|
|
struct ecs_query_table_match_t {
|
|
ecs_query_table_node_t node; /* Embedded list node */
|
|
|
|
int32_t *columns; /* Mapping from query fields to table columns */
|
|
int32_t *storage_columns; /* Mapping from query fields to storage columns */
|
|
ecs_id_t *ids; /* Resolved (component) ids for current table */
|
|
ecs_entity_t *sources; /* Subjects (sources) of ids */
|
|
ecs_size_t *sizes; /* Sizes for ids for current table */
|
|
ecs_vec_t refs; /* Cached components for non-this terms */
|
|
ecs_entity_filter_t entity_filter; /* Entity specific filters */
|
|
|
|
/* Next match in cache for same table (includes empty tables) */
|
|
ecs_query_table_match_t *next_match;
|
|
|
|
int32_t *monitor; /* Used to monitor table for changes */
|
|
};
|
|
|
|
/** A single table can occur multiple times in the cache when a term matches
|
|
* multiple table columns. */
|
|
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 */
|
|
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_node_t *first;
|
|
ecs_query_table_node_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 sizes;
|
|
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_vector_t *table_slices;
|
|
|
|
/* 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_vector_t *subqueries;
|
|
|
|
/* Flags for query properties */
|
|
ecs_flags32_t flags;
|
|
|
|
/* Monitor generation */
|
|
int32_t monitor_generation;
|
|
|
|
int32_t cascade_by; /* Identify cascade column */
|
|
int32_t match_count; /* How often have tables been (un)matched */
|
|
int32_t prev_match_count; /* Track if sorting is needed */
|
|
int32_t rematch_count; /* Track which tables were added during rematch */
|
|
|
|
/* 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 {
|
|
EcsOpClone,
|
|
EcsOpBulkNew,
|
|
EcsOpAdd,
|
|
EcsOpRemove,
|
|
EcsOpSet,
|
|
EcsOpEmplace,
|
|
EcsOpMut,
|
|
EcsOpModified,
|
|
EcsOpDelete,
|
|
EcsOpClear,
|
|
EcsOpOnDeleteAction,
|
|
EcsOpEnable,
|
|
EcsOpDisable,
|
|
EcsOpSkip
|
|
} ecs_cmd_kind_t;
|
|
|
|
typedef struct ecs_cmd_1_t {
|
|
void *value; /* Component value (used by set / get_mut) */
|
|
ecs_size_t size; /* Size of value */
|
|
bool clone_value; /* Clone entity with value (used for clone) */
|
|
} 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_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;
|
|
|
|
/* Entity specific metadata for command in defer queue */
|
|
typedef struct ecs_cmd_entry_t {
|
|
int32_t first;
|
|
int32_t last; /* If -1, a delete command was inserted */
|
|
} ecs_cmd_entry_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;
|
|
|
|
/* Deferred command queue */
|
|
int32_t defer;
|
|
ecs_vec_t commands;
|
|
ecs_stack_t defer_stack; /* Temp memory used by deferred commands */
|
|
ecs_sparse_t cmd_entries; /* <entity, op_entry_t> - command combining */
|
|
bool defer_suspend; /* Suspend deferring without flushing */
|
|
|
|
/* 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_vector_t *post_frame_actions;
|
|
|
|
/* Namespacing */
|
|
ecs_entity_t scope; /* Entity of current scope */
|
|
ecs_entity_t with; /* Id to add by default to new entities */
|
|
ecs_entity_t base; /* Currently instantiated top-level base */
|
|
ecs_entity_t *lookup_path; /* Search path used by lookup operations */
|
|
|
|
/* 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;
|
|
};
|
|
|
|
/* Component monitor */
|
|
typedef struct ecs_monitor_t {
|
|
ecs_vector_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_sparse_t entity_index; /* sparse<entity, ecs_record_t> */
|
|
|
|
/* 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_vector_t *records;
|
|
|
|
/* Stack of ids being deleted. */
|
|
ecs_vector_t *marked_ids; /* vector<ecs_marked_ids_t> */
|
|
} ecs_store_t;
|
|
|
|
/* fini actions */
|
|
typedef struct ecs_action_elem_t {
|
|
ecs_fini_action_t action;
|
|
void *ctx;
|
|
} ecs_action_elem_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_sparse_t id_index_lo; /* sparse<id, ecs_id_record_t> */
|
|
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;
|
|
|
|
/* -- 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 */
|
|
|
|
/* -- 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 *context; /* Application context */
|
|
ecs_vector_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,
|
|
const ecs_table_t *table,
|
|
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_record.h
|
|
* @brief Index for looking up tables by (component) id.
|
|
*/
|
|
|
|
#ifndef FLECS_ID_RECORD_H
|
|
#define FLECS_ID_RECORD_H
|
|
|
|
/* Payload for id cache */
|
|
struct ecs_table_record_t {
|
|
ecs_table_cache_hdr_t hdr; /* Table cache header */
|
|
int32_t column; /* First column where id occurs in table */
|
|
int32_t count; /* Number of times id occurs in table */
|
|
};
|
|
|
|
/* 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 datastructures for an id. */
|
|
struct ecs_id_record_t {
|
|
/* Cache with all tables that contain the id. Must be first member. */
|
|
ecs_table_cache_t cache; /* table_cache<ecs_table_record_t> */
|
|
|
|
/* Flags for id */
|
|
ecs_flags32_t flags;
|
|
|
|
/* 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;
|
|
|
|
/* Name lookup index (currently only used for ChildOf pairs) */
|
|
ecs_hashmap_t *name_index;
|
|
|
|
/* Cached pointer to type info for id, if id contains data. */
|
|
const ecs_type_info_t *type_info;
|
|
|
|
/* Id of record */
|
|
ecs_id_t id;
|
|
|
|
/* Parent id record. For pair records the parent is the (R, *) record. */
|
|
ecs_id_record_t *parent;
|
|
|
|
/* Lists for all id records that match a pair wildcard. The wildcard id
|
|
* record is at the head of the list. */
|
|
ecs_id_record_elem_t first; /* (R, *) */
|
|
ecs_id_record_elem_t second; /* (*, O) */
|
|
ecs_id_record_elem_t acyclic; /* (*, O) with only acyclic relationships */
|
|
};
|
|
|
|
/* 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);
|
|
|
|
/* 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 */
|
|
const 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);
|
|
|
|
void flecs_observers_notify(
|
|
ecs_iter_t *it,
|
|
ecs_observable_t *observable,
|
|
const ecs_type_t *ids,
|
|
ecs_entity_t event);
|
|
|
|
void flecs_set_observers_notify(
|
|
ecs_iter_t *it,
|
|
ecs_observable_t *observable,
|
|
const ecs_type_t *ids,
|
|
ecs_entity_t event,
|
|
ecs_id_t set_id);
|
|
|
|
bool flecs_check_observers_for_event(
|
|
const ecs_poly_t *world,
|
|
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_default_uni_observer_run_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,
|
|
ecs_size_t *sizes);
|
|
|
|
bool flecs_iter_next_row(
|
|
ecs_iter_t *it);
|
|
|
|
bool flecs_iter_next_instanced(
|
|
ecs_iter_t *it,
|
|
bool result);
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file table.c
|
|
* @brief Table storage implementation.
|
|
*/
|
|
|
|
#ifndef FLECS_TABLE_H
|
|
#define FLECS_TABLE_H
|
|
|
|
/* 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,
|
|
ecs_record_t *record,
|
|
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);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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_clear_edges(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table);
|
|
|
|
void flecs_table_delete_entities(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table);
|
|
|
|
ecs_vec_t *ecs_table_column_for_id(
|
|
const ecs_world_t *world,
|
|
const ecs_table_t *table,
|
|
ecs_id_t id);
|
|
|
|
int32_t flecs_table_column_to_union_index(
|
|
const ecs_table_t *table,
|
|
int32_t column);
|
|
|
|
/* Increase refcount of table (prevents deletion) */
|
|
void flecs_table_claim(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table);
|
|
|
|
/* Decreases refcount of table (may delete) */
|
|
bool flecs_table_release(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table);
|
|
|
|
/* Increase observer count of table */
|
|
void flecs_table_observer_add(
|
|
ecs_table_t *table,
|
|
int32_t value);
|
|
|
|
/* 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
|
|
|
|
/**
|
|
* @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
|
|
void* _ecs_poly_assert(
|
|
const ecs_poly_t *object,
|
|
int32_t type,
|
|
const char *file,
|
|
int32_t line);
|
|
|
|
#define ecs_poly_assert(object, type)\
|
|
_ecs_poly_assert(object, type##_magic, __FILE__, __LINE__)
|
|
|
|
#define ecs_poly(object, T) ((T*)ecs_poly_assert(object, T))
|
|
#else
|
|
#define ecs_poly_assert(object, type)
|
|
#define ecs_poly(object, T) ((T*)object)
|
|
#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_world_t *world,
|
|
ecs_stage_t *stage);
|
|
|
|
bool flecs_defer_begin(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage);
|
|
|
|
bool flecs_defer_modified(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_entity_t component);
|
|
|
|
bool flecs_defer_clone(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_entity_t src,
|
|
bool clone_value);
|
|
|
|
bool flecs_defer_bulk_new(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
int32_t count,
|
|
ecs_id_t id,
|
|
const ecs_entity_t **ids_out);
|
|
|
|
bool flecs_defer_delete(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity);
|
|
|
|
bool flecs_defer_clear(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity);
|
|
|
|
bool flecs_defer_on_delete_action(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_id_t id,
|
|
ecs_entity_t action);
|
|
|
|
bool flecs_defer_enable(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_entity_t component,
|
|
bool enable);
|
|
|
|
bool flecs_defer_add(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_id_t id);
|
|
|
|
bool flecs_defer_remove(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_id_t id);
|
|
|
|
bool flecs_defer_set(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_cmd_kind_t op_kind,
|
|
ecs_entity_t entity,
|
|
ecs_entity_t component,
|
|
ecs_size_t size,
|
|
void *value,
|
|
void **value_out,
|
|
bool emplace);
|
|
|
|
bool flecs_defer_end(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage);
|
|
|
|
bool flecs_defer_purge(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage);
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file world.c
|
|
* @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_notify_queries(
|
|
ecs_world_t *world,
|
|
ecs_query_event_t *event);
|
|
|
|
void flecs_register_table(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table);
|
|
|
|
void flecs_unregister_table(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table);
|
|
|
|
void flecs_table_set_empty(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table);
|
|
|
|
void flecs_delete_table(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table);
|
|
|
|
void flecs_process_pending_tables(
|
|
const ecs_world_t *world);
|
|
|
|
/* Suspend/resume readonly state. To fully support implicit registration of
|
|
* components, it should be possible to register components while the world is
|
|
* in readonly mode. It is not uncommon that a component is used first from
|
|
* within a system, which are often ran while in readonly mode.
|
|
*
|
|
* Suspending readonly mode is only allowed when the world is not multithreaded.
|
|
* When a world is multithreaded, it is not safe to (even temporarily) leave
|
|
* readonly mode, so a multithreaded application should always explicitly
|
|
* register components in advance.
|
|
*
|
|
* These operations also suspend deferred mode.
|
|
*/
|
|
typedef struct 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/qsort.h
|
|
* @brief Quicksort implementation.
|
|
*/
|
|
|
|
/* From: https://github.com/svpv/qsort/blob/master/qsort.h
|
|
* Use custom qsort implementation rather than relying on the version in libc to
|
|
* ensure that results are consistent across platforms.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2013, 2017 Alexey Tourbin
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* This is a traditional Quicksort implementation which mostly follows
|
|
* [Sedgewick 1978]. Sorting is performed entirely on array indices,
|
|
* while actual access to the array elements is abstracted out with the
|
|
* user-defined `LESS` and `SWAP` primitives.
|
|
*
|
|
* Synopsis:
|
|
* QSORT(N, LESS, SWAP);
|
|
* where
|
|
* N - the number of elements in A[];
|
|
* LESS(i, j) - compares A[i] to A[j];
|
|
* SWAP(i, j) - exchanges A[i] with A[j].
|
|
*/
|
|
|
|
#ifndef QSORT_H
|
|
#define QSORT_H
|
|
|
|
/* Sort 3 elements. */
|
|
#define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \
|
|
do { \
|
|
if (Q_LESS(q_a2, q_a1)) { \
|
|
if (Q_LESS(q_a3, q_a2)) \
|
|
Q_SWAP(q_a1, q_a3); \
|
|
else { \
|
|
Q_SWAP(q_a1, q_a2); \
|
|
if (Q_LESS(q_a3, q_a2)) \
|
|
Q_SWAP(q_a2, q_a3); \
|
|
} \
|
|
} \
|
|
else if (Q_LESS(q_a3, q_a2)) { \
|
|
Q_SWAP(q_a2, q_a3); \
|
|
if (Q_LESS(q_a2, q_a1)) \
|
|
Q_SWAP(q_a1, q_a2); \
|
|
} \
|
|
} while (0)
|
|
|
|
/* Partition [q_l,q_r] around a pivot. After partitioning,
|
|
* [q_l,q_j] are the elements that are less than or equal to the pivot,
|
|
* while [q_i,q_r] are the elements greater than or equal to the pivot. */
|
|
#define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \
|
|
do { \
|
|
/* The middle element, not to be confused with the median. */ \
|
|
Q_UINT q_m = q_l + ((q_r - q_l) >> 1); \
|
|
/* Reorder the second, the middle, and the last items. \
|
|
* As [Edelkamp Weiss 2016] explain, using the second element \
|
|
* instead of the first one helps avoid bad behaviour for \
|
|
* decreasingly sorted arrays. This method is used in recent \
|
|
* versions of gcc's std::sort, see gcc bug 58437#c13, although \
|
|
* the details are somewhat different (cf. #c14). */ \
|
|
Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP); \
|
|
/* Place the median at the beginning. */ \
|
|
Q_SWAP(q_l, q_m); \
|
|
/* Partition [q_l+2, q_r-1] around the median which is in q_l. \
|
|
* q_i and q_j are initially off by one, they get decremented \
|
|
* in the do-while loops. */ \
|
|
q_i = q_l + 1; q_j = q_r; \
|
|
while (1) { \
|
|
do q_i++; while (Q_LESS(q_i, q_l)); \
|
|
do q_j--; while (Q_LESS(q_l, q_j)); \
|
|
if (q_i >= q_j) break; /* Sedgewick says "until j < i" */ \
|
|
Q_SWAP(q_i, q_j); \
|
|
} \
|
|
/* Compensate for the i==j case. */ \
|
|
q_i = q_j + 1; \
|
|
/* Put the median to its final place. */ \
|
|
Q_SWAP(q_l, q_j); \
|
|
/* The median is not part of the left subfile. */ \
|
|
q_j--; \
|
|
} while (0)
|
|
|
|
/* Insertion sort is applied to small subfiles - this is contrary to
|
|
* Sedgewick's suggestion to run a separate insertion sort pass after
|
|
* the partitioning is done. The reason I don't like a separate pass
|
|
* is that it triggers extra comparisons, because it can't see that the
|
|
* medians are already in their final positions and need not be rechecked.
|
|
* Since I do not assume that comparisons are cheap, I also do not try
|
|
* to eliminate the (q_j > q_l) boundary check. */
|
|
#define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \
|
|
do { \
|
|
Q_UINT q_i, q_j; \
|
|
/* For each item starting with the second... */ \
|
|
for (q_i = q_l + 1; q_i <= q_r; q_i++) \
|
|
/* move it down the array so that the first part is sorted. */ \
|
|
for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--) \
|
|
Q_SWAP(q_j, q_j - 1); \
|
|
} while (0)
|
|
|
|
/* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to
|
|
* Q_THRESH, the algorithm performs recursive partitioning. When the size
|
|
* drops below Q_THRESH, the algorithm switches to insertion sort.
|
|
* The minimum valid value is probably 5 (with 5 items, the second and
|
|
* the middle items, the middle itself being rounded down, are distinct). */
|
|
#define Q_THRESH 16
|
|
|
|
/* The main loop. */
|
|
#define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \
|
|
do { \
|
|
Q_UINT q_l = 0; \
|
|
Q_UINT q_r = (Q_N) - 1; \
|
|
Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \
|
|
struct { Q_UINT q_l, q_r; } \
|
|
/* On 32-bit platforms, to sort a "char[3GB+]" array, \
|
|
* it may take full 32 stack frames. On 64-bit CPUs, \
|
|
* though, the address space is limited to 48 bits. \
|
|
* The usage is further reduced if Q_N has a 32-bit type. */ \
|
|
q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \
|
|
while (1) { \
|
|
if (q_r - q_l + 1 >= Q_THRESH) { \
|
|
Q_UINT q_i, q_j; \
|
|
Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \
|
|
/* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \
|
|
* Dealing with them depends on which one is bigger. */ \
|
|
if (q_j - q_l >= q_r - q_i) \
|
|
Q_SUBFILES(q_l, q_j, q_i, q_r); \
|
|
else \
|
|
Q_SUBFILES(q_i, q_r, q_l, q_j); \
|
|
} \
|
|
else { \
|
|
Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \
|
|
/* Pop subfiles from the stack, until it gets empty. */ \
|
|
if (q_sp == 0) break; \
|
|
q_sp--; \
|
|
q_l = q_st[q_sp].q_l; \
|
|
q_r = q_st[q_sp].q_r; \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
/* The missing part: dealing with subfiles.
|
|
* Assumes that the first subfile is not smaller than the second. */
|
|
#define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \
|
|
do { \
|
|
/* If the second subfile is only a single element, it needs \
|
|
* no further processing. The first subfile will be processed \
|
|
* on the next iteration (both subfiles cannot be only a single \
|
|
* element, due to Q_THRESH). */ \
|
|
if (q_l2 == q_r2) { \
|
|
q_l = q_l1; \
|
|
q_r = q_r1; \
|
|
} \
|
|
else { \
|
|
/* Otherwise, both subfiles need processing. \
|
|
* Push the larger subfile onto the stack. */ \
|
|
q_st[q_sp].q_l = q_l1; \
|
|
q_st[q_sp].q_r = q_r1; \
|
|
q_sp++; \
|
|
/* Process the smaller subfile on the next iteration. */ \
|
|
q_l = q_l2; \
|
|
q_r = q_r2; \
|
|
} \
|
|
} while (0)
|
|
|
|
/* And now, ladies and gentlemen, may I proudly present to you... */
|
|
#define QSORT(Q_N, Q_LESS, Q_SWAP) \
|
|
do { \
|
|
if ((Q_N) > 1) \
|
|
/* We could check sizeof(Q_N) and use "unsigned", but at least \
|
|
* on x86_64, this has the performance penalty of up to 5%. */ \
|
|
Q_LOOP(ecs_size_t, Q_N, Q_LESS, Q_SWAP); \
|
|
} while (0)
|
|
|
|
void ecs_qsort(
|
|
void *base,
|
|
ecs_size_t nitems,
|
|
ecs_size_t size,
|
|
int (*compar)(const void *, const void*));
|
|
|
|
#define ecs_qsort_t(base, nitems, T, compar) \
|
|
ecs_qsort(base, nitems, ECS_SIZEOF(T), compar)
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file datastructures/name_index.h
|
|
* @brief Data structure for resolving 64bit keys by string (name).
|
|
*/
|
|
|
|
#ifndef FLECS_NAME_INDEX_H
|
|
#define FLECS_NAME_INDEX_H
|
|
|
|
void flecs_name_index_init(
|
|
ecs_hashmap_t *hm,
|
|
ecs_allocator_t *allocator);
|
|
|
|
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 *map);
|
|
|
|
ecs_hashed_string_t flecs_get_hashed_string(
|
|
const char *name,
|
|
ecs_size_t length,
|
|
uint64_t hash);
|
|
|
|
const uint64_t* flecs_name_index_find_ptr(
|
|
const ecs_hashmap_t *map,
|
|
const char *name,
|
|
ecs_size_t length,
|
|
uint64_t hash);
|
|
|
|
uint64_t flecs_name_index_find(
|
|
const ecs_hashmap_t *map,
|
|
const char *name,
|
|
ecs_size_t length,
|
|
uint64_t hash);
|
|
|
|
void flecs_name_index_ensure(
|
|
ecs_hashmap_t *map,
|
|
uint64_t id,
|
|
const char *name,
|
|
ecs_size_t length,
|
|
uint64_t hash);
|
|
|
|
void flecs_name_index_remove(
|
|
ecs_hashmap_t *map,
|
|
uint64_t id,
|
|
uint64_t hash);
|
|
|
|
void flecs_name_index_update_name(
|
|
ecs_hashmap_t *map,
|
|
uint64_t e,
|
|
uint64_t hash,
|
|
const char *name);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// 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_add_id(world, name, EcsFinal);\
|
|
ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\
|
|
ecs_set(world, name, EcsComponent, {.size = 0});\
|
|
ecs_set_name(world, name, (char*)&#name[ecs_os_strlen(world->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. */
|
|
ecs_record_t* flecs_add_flag(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
uint32_t flag);
|
|
|
|
ecs_entity_t flecs_get_oneof(
|
|
const ecs_world_t *world,
|
|
ecs_entity_t e);
|
|
|
|
void flecs_notify_on_remove(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_table_t *other_table,
|
|
int32_t row,
|
|
int32_t count,
|
|
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);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// 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);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// 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);
|
|
|
|
/* 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);
|
|
|
|
/* Compare function for entity ids which can be used with qsort */
|
|
int flecs_entity_compare_qsort(
|
|
const void *e1,
|
|
const void *e2);
|
|
|
|
/* 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);
|
|
|
|
#define assert_func(cond) _assert_func(cond, #cond, __FILE__, __LINE__, __func__)
|
|
void _assert_func(
|
|
bool cond,
|
|
const char *cond_str,
|
|
const char *file,
|
|
int32_t line,
|
|
const char *func);
|
|
|
|
void flecs_dump_backtrace(
|
|
FILE *stream);
|
|
|
|
void ecs_colorize_buf(
|
|
char *msg,
|
|
bool enable_colors,
|
|
ecs_strbuf_t *buf);
|
|
|
|
bool flecs_isident(
|
|
char ch);
|
|
|
|
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);
|
|
|
|
#endif
|
|
|
|
|
|
/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as
|
|
* this can severly 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);
|
|
|
|
ecs_assert(size == ecs_vec_size(&table->data.records),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(count == ecs_vec_count(&table->data.records),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t i;
|
|
int32_t sw_offset = table->sw_offset;
|
|
int32_t sw_count = table->sw_count;
|
|
int32_t bs_offset = table->bs_offset;
|
|
int32_t bs_count = table->bs_count;
|
|
int32_t type_count = table->type.count;
|
|
ecs_id_t *ids = table->type.array;
|
|
|
|
ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_table_t *storage_table = table->storage_table;
|
|
if (storage_table) {
|
|
ecs_assert(table->storage_count == storage_table->type.count,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table->storage_ids == storage_table->type.array,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t storage_count = table->storage_count;
|
|
ecs_assert(type_count >= storage_count, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t *storage_map = table->storage_map;
|
|
ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_id_t *storage_ids = table->storage_ids;
|
|
for (i = 0; i < type_count; i ++) {
|
|
if (storage_map[i] != -1) {
|
|
ecs_assert(ids[i] == storage_ids[storage_map[i]],
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
}
|
|
|
|
ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table->type_info != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
for (i = 0; i < storage_count; i ++) {
|
|
ecs_vec_t *column = &table->data.columns[i];
|
|
ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL);
|
|
int32_t storage_map_id = storage_map[i + type_count];
|
|
ecs_assert(storage_map_id >= 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(ids[storage_map_id] == storage_ids[i],
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
} else {
|
|
ecs_assert(table->storage_count == 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table->storage_ids == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
if (sw_count) {
|
|
ecs_assert(table->data.sw_columns != NULL,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
for (i = 0; i < sw_count; i ++) {
|
|
ecs_switch_t *sw = &table->data.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->data.bs_columns != NULL,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
for (i = 0; i < bs_count; i ++) {
|
|
ecs_bitset_t *bs = &table->data.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->observed_count == 0) ||
|
|
(table->flags & EcsTableHasObserved), ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
#else
|
|
#define flecs_table_check_sanity(table)
|
|
#endif
|
|
|
|
static
|
|
void flecs_table_init_storage_map(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
if (!table->storage_table) {
|
|
return;
|
|
}
|
|
|
|
ecs_type_t type = table->type;
|
|
ecs_id_t *ids = type.array;
|
|
int32_t t, ids_count = type.count;
|
|
ecs_id_t *storage_ids = table->storage_ids;
|
|
int32_t s, storage_ids_count = table->storage_count;
|
|
|
|
if (!ids_count) {
|
|
table->storage_map = NULL;
|
|
return;
|
|
}
|
|
|
|
table->storage_map = flecs_walloc_n(world, int32_t,
|
|
ids_count + storage_ids_count);
|
|
|
|
int32_t *t2s = table->storage_map;
|
|
int32_t *s2t = &table->storage_map[ids_count];
|
|
|
|
for (s = 0, t = 0; (t < ids_count) && (s < storage_ids_count); ) {
|
|
ecs_id_t id = ids[t];
|
|
ecs_id_t storage_id = storage_ids[s];
|
|
|
|
if (id == storage_id) {
|
|
t2s[t] = s;
|
|
s2t[s] = t;
|
|
} else {
|
|
t2s[t] = -1;
|
|
}
|
|
|
|
/* Ids can never get ahead of storage id, as ids are a superset of the
|
|
* storage ids */
|
|
ecs_assert(id <= storage_id, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
t += (id <= storage_id);
|
|
s += (id == storage_id);
|
|
}
|
|
|
|
/* Storage ids is always a subset of ids, so all should be iterated */
|
|
ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Initialize remainder of type -> storage_type map */
|
|
for (; (t < ids_count); t ++) {
|
|
t2s[t] = -1;
|
|
}
|
|
}
|
|
|
|
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_type_info(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table->storage_table == table, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table->type_info == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_table_record_t *records = table->records;
|
|
int32_t i, count = table->type.count;
|
|
table->type_info = flecs_walloc_n(world, ecs_type_info_t*, count);
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_table_record_t *tr = &records[i];
|
|
ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache;
|
|
|
|
/* All ids in the storage table must be components with type info */
|
|
const ecs_type_info_t *ti = idr->type_info;
|
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
table->flags |= flecs_type_info_flags(ti);
|
|
table->type_info[i] = (ecs_type_info_t*)ti;
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_table_init_storage_table(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
if (table->storage_table) {
|
|
return;
|
|
}
|
|
|
|
ecs_type_t type = table->type;
|
|
int32_t i, count = type.count;
|
|
ecs_id_t *ids = type.array;
|
|
ecs_table_record_t *records = table->records;
|
|
|
|
ecs_id_t array[ECS_ID_CACHE_SIZE];
|
|
ecs_type_t storage_ids = { .array = array };
|
|
if (count > ECS_ID_CACHE_SIZE) {
|
|
storage_ids.array = flecs_walloc_n(world, ecs_id_t, count);
|
|
}
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_table_record_t *tr = &records[i];
|
|
ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache;
|
|
ecs_id_t id = ids[i];
|
|
|
|
if (idr->type_info != NULL) {
|
|
storage_ids.array[storage_ids.count ++] = id;
|
|
}
|
|
}
|
|
|
|
if (storage_ids.count && storage_ids.count != count) {
|
|
ecs_table_t *storage_table = flecs_table_find_or_create(world,
|
|
&storage_ids);
|
|
table->storage_table = storage_table;
|
|
table->storage_count = flecs_ito(uint16_t, storage_ids.count);
|
|
table->storage_ids = storage_table->type.array;
|
|
table->type_info = storage_table->type_info;
|
|
table->flags |= storage_table->flags;
|
|
storage_table->refcount ++;
|
|
} else if (storage_ids.count) {
|
|
table->storage_table = table;
|
|
table->storage_count = flecs_ito(uint16_t, count);
|
|
table->storage_ids = type.array;
|
|
flecs_table_init_type_info(world, table);
|
|
}
|
|
|
|
if (storage_ids.array != array) {
|
|
flecs_wfree_n(world, ecs_id_t, count, storage_ids.array);
|
|
}
|
|
|
|
if (!table->storage_map) {
|
|
flecs_table_init_storage_map(world, table);
|
|
}
|
|
}
|
|
|
|
void flecs_table_init_data(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
int32_t sw_count = table->sw_count;
|
|
int32_t bs_count = table->bs_count;
|
|
|
|
ecs_data_t *storage = &table->data;
|
|
int32_t i, count = table->storage_count;
|
|
|
|
/* Root tables don't have columns */
|
|
if (!count && !sw_count && !bs_count) {
|
|
storage->columns = NULL;
|
|
}
|
|
|
|
ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0);
|
|
ecs_vec_init_t(NULL, &storage->records, ecs_record_t*, 0);
|
|
|
|
if (count) {
|
|
ecs_vec_t *columns = flecs_wcalloc_n(world, ecs_vec_t, count);
|
|
storage->columns = columns;
|
|
#ifdef FLECS_DEBUG
|
|
ecs_type_info_t **ti = table->type_info;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_vec_init(NULL, &columns[i], ti[i]->size, 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (sw_count) {
|
|
storage->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count);
|
|
for (i = 0; i < sw_count; i ++) {
|
|
flecs_switch_init(&storage->sw_columns[i],
|
|
&world->allocator, 0);
|
|
}
|
|
}
|
|
|
|
if (bs_count) {
|
|
storage->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count);
|
|
for (i = 0; i < bs_count; i ++) {
|
|
flecs_bitset_init(&storage->bs_columns[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
/* Iterate components to initialize table flags */
|
|
int32_t i;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_id_t id = ids[i];
|
|
|
|
/* As we're iterating over the table components, also set the table
|
|
* flags. These allow us to quickly determine if the table contains
|
|
* data that needs to be handled in a special way. */
|
|
|
|
if (id <= EcsLastInternalComponentId) {
|
|
table->flags |= EcsTableHasBuiltins;
|
|
}
|
|
|
|
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 (r == EcsUnion) {
|
|
table->flags |= EcsTableHasUnion;
|
|
|
|
if (!table->sw_count) {
|
|
table->sw_offset = flecs_ito(int16_t, i);
|
|
}
|
|
table->sw_count ++;
|
|
} else if (r == ecs_id(EcsPoly)) {
|
|
table->flags |= EcsTableHasBuiltins;
|
|
}
|
|
} else {
|
|
if (ECS_HAS_ID_FLAG(id, TOGGLE)) {
|
|
table->flags |= EcsTableHasToggle;
|
|
|
|
if (!table->bs_count) {
|
|
table->bs_offset = flecs_ito(int16_t, i);
|
|
}
|
|
table->bs_count ++;
|
|
}
|
|
if (ECS_HAS_ID_FLAG(id, OVERRIDE)) {
|
|
table->flags |= EcsTableHasOverrides;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_table_append_to_records(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_vector_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_vector_add(records, ecs_table_record_t);
|
|
tr->column = 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);
|
|
}
|
|
|
|
void flecs_table_init(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_table_t *from)
|
|
{
|
|
flecs_table_init_flags(world, table);
|
|
|
|
int32_t dst_i = 0, dst_count = table->type.count;
|
|
int32_t src_i = 0, src_count = 0;
|
|
ecs_id_t *dst_ids = table->type.array;
|
|
ecs_id_t *src_ids = NULL;
|
|
ecs_table_record_t *tr = NULL, *src_tr = NULL;
|
|
if (from) {
|
|
src_count = from->type.count;
|
|
src_ids = from->type.array;
|
|
src_tr = from->records;
|
|
}
|
|
|
|
/* We don't know in advance how large the records array will be, so use
|
|
* cached vector. This eliminates unnecessary allocations, and/or expensive
|
|
* iterations to determine how many records we need. */
|
|
ecs_vector_t *records = world->store.records;
|
|
ecs_vector_clear(records);
|
|
ecs_id_record_t *idr;
|
|
|
|
int32_t 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) {
|
|
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_vector_add(&records, ecs_table_record_t);
|
|
tr->hdr.cache = (ecs_table_cache_t*)idr;
|
|
tr->column = dst_i;
|
|
tr->count = 1;
|
|
}
|
|
|
|
dst_i += dst_id <= src_id;
|
|
src_i += dst_id >= src_id;
|
|
}
|
|
|
|
/* Add remaining ids that the "from" table didn't have */
|
|
for (; (dst_i < dst_count); dst_i ++) {
|
|
ecs_id_t dst_id = dst_ids[dst_i];
|
|
tr = ecs_vector_add(&records, ecs_table_record_t);
|
|
idr = flecs_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->column = 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_vector_set_min_size(&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_vector_get(records, ecs_table_record_t, dst_i);
|
|
idr = ((ecs_id_record_t*)tr->hdr.cache)->parent; /* (R, *) */
|
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
tr = ecs_vector_add(&records, ecs_table_record_t);
|
|
tr->hdr.cache = (ecs_table_cache_t*)idr;
|
|
tr->column = dst_i;
|
|
tr->count = 0;
|
|
r = ECS_PAIR_FIRST(dst_id);
|
|
}
|
|
|
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
tr->count ++;
|
|
}
|
|
|
|
last_pair = dst_i;
|
|
|
|
/* 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_vector_add(&records, ecs_table_record_t);
|
|
tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard;
|
|
tr->column = 0;
|
|
tr->count = last_id + 1;
|
|
}
|
|
if (last_pair - first_pair) {
|
|
tr = ecs_vector_add(&records, ecs_table_record_t);
|
|
tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard;
|
|
tr->column = first_pair;
|
|
tr->count = last_pair - first_pair;
|
|
}
|
|
if (dst_count) {
|
|
tr = ecs_vector_add(&records, ecs_table_record_t);
|
|
tr->hdr.cache = (ecs_table_cache_t*)world->idr_any;
|
|
tr->column = 0;
|
|
tr->count = 1;
|
|
}
|
|
if (dst_count && !has_childof) {
|
|
tr = ecs_vector_add(&records, ecs_table_record_t);
|
|
tr->hdr.cache = (ecs_table_cache_t*)world->idr_childof_0;
|
|
tr->column = 0;
|
|
tr->count = 1;
|
|
}
|
|
|
|
/* Now that all records have been added, copy them to array */
|
|
int32_t i, dst_record_count = ecs_vector_count(records);
|
|
ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t,
|
|
dst_record_count, ecs_vector_first(records, ecs_table_record_t));
|
|
table->record_count = flecs_ito(uint16_t, dst_record_count);
|
|
table->records = dst_tr;
|
|
|
|
/* Register & patch up records */
|
|
for (i = 0; i < dst_record_count; i ++) {
|
|
tr = &dst_tr[i];
|
|
idr = (ecs_id_record_t*)dst_tr[i].hdr.cache;
|
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (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;
|
|
}
|
|
|
|
world->store.records = records;
|
|
|
|
flecs_table_init_storage_table(world, table);
|
|
flecs_table_init_data(world, table);
|
|
}
|
|
|
|
static
|
|
void flecs_table_records_unregister(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
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, &tr->hdr);
|
|
flecs_id_record_release(world, (ecs_id_record_t*)cache);
|
|
}
|
|
|
|
flecs_wfree_n(world, ecs_table_record_t, count, table->records);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* -- Private functions -- */
|
|
|
|
static
|
|
void flecs_on_component_callback(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_iter_action_t callback,
|
|
ecs_entity_t event,
|
|
ecs_vec_t *column,
|
|
ecs_entity_t *entities,
|
|
ecs_id_t id,
|
|
int32_t row,
|
|
int32_t count,
|
|
ecs_type_info_t *ti)
|
|
{
|
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_iter_t it = { .field_count = 1 };
|
|
it.entities = entities;
|
|
|
|
ecs_size_t size = ti->size;
|
|
void *ptr = ecs_vec_get(column, size, row);
|
|
|
|
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[0] = 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;
|
|
flecs_iter_validate(&it);
|
|
callback(&it);
|
|
ecs_iter_fini(&it);
|
|
}
|
|
|
|
static
|
|
void flecs_ctor_component(
|
|
ecs_type_info_t *ti,
|
|
ecs_vec_t *column,
|
|
int32_t row,
|
|
int32_t count)
|
|
{
|
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_xtor_t ctor = ti->hooks.ctor;
|
|
if (ctor) {
|
|
void *ptr = ecs_vec_get(column, ti->size, row);
|
|
ctor(ptr, count, ti);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_run_add_hooks(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_type_info_t *ti,
|
|
ecs_vec_t *column,
|
|
ecs_entity_t *entities,
|
|
ecs_id_t id,
|
|
int32_t row,
|
|
int32_t count,
|
|
bool construct)
|
|
{
|
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (construct) {
|
|
flecs_ctor_component(ti, column, row, count);
|
|
}
|
|
|
|
ecs_iter_action_t on_add = ti->hooks.on_add;
|
|
if (on_add) {
|
|
flecs_on_component_callback(world, table, on_add, EcsOnAdd, column,
|
|
entities, id, row, count, ti);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_dtor_component(
|
|
ecs_type_info_t *ti,
|
|
ecs_vec_t *column,
|
|
int32_t row,
|
|
int32_t count)
|
|
{
|
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_xtor_t dtor = ti->hooks.dtor;
|
|
if (dtor) {
|
|
void *ptr = ecs_vec_get(column, ti->size, row);
|
|
dtor(ptr, count, ti);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_run_remove_hooks(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_type_info_t *ti,
|
|
ecs_vec_t *column,
|
|
ecs_entity_t *entities,
|
|
ecs_id_t id,
|
|
int32_t row,
|
|
int32_t count,
|
|
bool dtor)
|
|
{
|
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_iter_action_t on_remove = ti->hooks.on_remove;
|
|
if (on_remove) {
|
|
flecs_on_component_callback(world, table, on_remove, EcsOnRemove, column,
|
|
entities, id, row, count, ti);
|
|
}
|
|
|
|
if (dtor) {
|
|
flecs_dtor_component(ti, column, row, count);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_dtor_all_components(
|
|
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);
|
|
|
|
ecs_id_t *ids = table->storage_ids;
|
|
int32_t ids_count = table->storage_count;
|
|
ecs_record_t **records = data->records.array;
|
|
ecs_entity_t *entities = data->entities.array;
|
|
int32_t i, c, end = row + count;
|
|
|
|
(void)records;
|
|
|
|
if (is_delete && table->observed_count) {
|
|
/* If table contains monitored entities with acyclic 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_vec_t *column = &data->columns[c];
|
|
ecs_type_info_t *ti = table->type_info[c];
|
|
ecs_iter_action_t on_remove = ti->hooks.on_remove;
|
|
if (on_remove) {
|
|
flecs_on_component_callback(world, table, on_remove, EcsOnRemove,
|
|
column, &entities[row], ids[c], row, count, ti);
|
|
}
|
|
}
|
|
|
|
/* Destruct components */
|
|
for (c = 0; c < ids_count; c++) {
|
|
flecs_dtor_component(table->type_info[c], &data->columns[c],
|
|
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);
|
|
ecs_assert(!e || records[i] == flecs_entities_get(world, e),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(!e || records[i]->table == table,
|
|
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
|
|
records[i]->table = NULL;
|
|
records[i]->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);
|
|
ecs_assert(!e || records[i] == flecs_entities_get(world, e),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(!e || records[i]->table == table,
|
|
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_assert(!e || records[i] == flecs_entities_get(world, e),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(!e || records[i]->table == table,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
records[i]->table = NULL;
|
|
records[i]->row = records[i]->row & ECS_ROW_FLAGS_MASK;
|
|
(void)e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_dtor_all_components(world, table, data, 0, count,
|
|
update_entity_index, is_delete);
|
|
}
|
|
|
|
/* Sanity check */
|
|
ecs_assert(data->records.count ==
|
|
data->entities.count, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_vec_t *columns = data->columns;
|
|
if (columns) {
|
|
int32_t c, column_count = table->storage_count;
|
|
for (c = 0; c < column_count; c ++) {
|
|
/* Sanity check */
|
|
ecs_assert(columns[c].count == data->entities.count,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_vec_fini(&world->allocator,
|
|
&columns[c], table->type_info[c]->size);
|
|
}
|
|
flecs_wfree_n(world, ecs_vec_t, column_count, columns);
|
|
data->columns = NULL;
|
|
}
|
|
|
|
ecs_switch_t *sw_columns = data->sw_columns;
|
|
if (sw_columns) {
|
|
int32_t c, column_count = table->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);
|
|
data->sw_columns = NULL;
|
|
}
|
|
|
|
ecs_bitset_t *bs_columns = data->bs_columns;
|
|
if (bs_columns) {
|
|
int32_t c, column_count = table->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);
|
|
data->bs_columns = NULL;
|
|
}
|
|
|
|
ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t);
|
|
ecs_vec_fini_t(&world->allocator, &data->records, ecs_record_t*);
|
|
|
|
if (deactivate && count) {
|
|
flecs_table_set_empty(world, table);
|
|
}
|
|
|
|
table->observed_count = 0;
|
|
table->flags &= ~EcsTableHasObserved;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
ecs_assert(table->refcount == 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (!is_root) {
|
|
flecs_notify_queries(
|
|
world, &(ecs_query_event_t){
|
|
.kind = EcsQueryTableUnmatch,
|
|
.table = table
|
|
});
|
|
}
|
|
|
|
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(&world->store.table_map, &ids, ecs_table_t*);
|
|
}
|
|
|
|
flecs_wfree_n(world, int32_t, table->storage_count + 1, table->dirty_state);
|
|
flecs_wfree_n(world, int32_t, table->storage_count + table->type.count,
|
|
table->storage_map);
|
|
flecs_table_records_unregister(world, table);
|
|
|
|
ecs_table_t *storage_table = table->storage_table;
|
|
if (storage_table == table) {
|
|
if (table->type_info) {
|
|
flecs_wfree_n(world, ecs_type_info_t*, table->storage_count,
|
|
table->type_info);
|
|
}
|
|
} else if (storage_table) {
|
|
flecs_table_release(world, storage_table);
|
|
}
|
|
|
|
/* Update counters */
|
|
world->info.table_count --;
|
|
world->info.table_record_count -= table->record_count;
|
|
world->info.table_storage_count -= table->storage_count;
|
|
world->info.table_delete_total ++;
|
|
|
|
if (!table->storage_count) {
|
|
world->info.tag_table_count --;
|
|
} else {
|
|
world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex);
|
|
}
|
|
|
|
if (!(world->flags & EcsWorldFini)) {
|
|
ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL);
|
|
flecs_table_free_type(world, table);
|
|
flecs_sparse_remove(&world->store.tables, table->id);
|
|
}
|
|
|
|
ecs_log_pop_2();
|
|
}
|
|
|
|
void flecs_table_claim(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL);
|
|
table->refcount ++;
|
|
(void)world;
|
|
}
|
|
|
|
bool flecs_table_release(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (--table->refcount == 0) {
|
|
flecs_table_free(world, table);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
void flecs_table_observer_add(
|
|
ecs_table_t *table,
|
|
int32_t value)
|
|
{
|
|
int32_t result = table->observed_count += value;
|
|
ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL);
|
|
if (result == 0) {
|
|
table->flags &= ~EcsTableHasObserved;
|
|
} else if (result == value) {
|
|
table->flags |= EcsTableHasObserved;
|
|
}
|
|
}
|
|
|
|
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] ++;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
int32_t index = ecs_search(world, table->storage_table, component, 0);
|
|
ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL);
|
|
table->dirty_state[index + 1] ++;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
int32_t i_old = 0, src_column_count = src_table->sw_count;
|
|
int32_t i_new = 0, dst_column_count = dst_table->sw_count;
|
|
|
|
if (!src_column_count && !dst_column_count) {
|
|
return;
|
|
}
|
|
|
|
ecs_switch_t *src_columns = src_table->data.sw_columns;
|
|
ecs_switch_t *dst_columns = dst_table->data.sw_columns;
|
|
|
|
ecs_type_t dst_type = dst_table->type;
|
|
ecs_type_t src_type = src_table->type;
|
|
|
|
int32_t offset_new = dst_table->sw_offset;
|
|
int32_t offset_old = src_table->sw_offset;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
int32_t i_old = 0, src_column_count = src_table->bs_count;
|
|
int32_t i_new = 0, dst_column_count = dst_table->bs_count;
|
|
|
|
if (!src_column_count && !dst_column_count) {
|
|
return;
|
|
}
|
|
|
|
ecs_bitset_t *src_columns = src_table->data.bs_columns;
|
|
ecs_bitset_t *dst_columns = dst_table->data.bs_columns;
|
|
|
|
ecs_type_t dst_type = dst_table->type;
|
|
ecs_type_t src_type = src_table->type;
|
|
|
|
int32_t offset_new = dst_table->bs_offset;
|
|
int32_t offset_old = src_table->bs_offset;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void* flecs_table_grow_column(
|
|
ecs_world_t *world,
|
|
ecs_vec_t *column,
|
|
ecs_type_info_t *ti,
|
|
int32_t to_add,
|
|
int32_t dst_size,
|
|
bool construct)
|
|
{
|
|
ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t size = ti->size;
|
|
int32_t count = column->count;
|
|
int32_t src_size = column->size;
|
|
int32_t dst_count = count + to_add;
|
|
bool can_realloc = dst_size != src_size;
|
|
void *result = NULL;
|
|
|
|
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->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, ti->size);
|
|
|
|
*column = 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, size, dst_size);
|
|
}
|
|
|
|
result = ecs_vec_grow(&world->allocator, column, 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->size == dst_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
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->storage_count;
|
|
int32_t sw_count = table->sw_count;
|
|
int32_t bs_count = table->bs_count;
|
|
ecs_vec_t *columns = data->columns;
|
|
ecs_switch_t *sw_columns = data->sw_columns;
|
|
ecs_bitset_t *bs_columns = data->bs_columns;
|
|
|
|
/* Add record to record ptr array */
|
|
ecs_vec_set_size_t(&world->allocator, &data->records, ecs_record_t*, size);
|
|
ecs_record_t **r = ecs_vec_last_t(&data->records, ecs_record_t*) + 1;
|
|
data->records.count += to_add;
|
|
if (data->records.size > size) {
|
|
size = data->records.size;
|
|
}
|
|
|
|
/* Add entity to column with entity ids */
|
|
ecs_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;
|
|
ecs_assert(data->entities.size == size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* 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);
|
|
}
|
|
ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add);
|
|
|
|
/* Add elements to each column array */
|
|
ecs_type_info_t **type_info = table->type_info;
|
|
for (i = 0; i < column_count; i ++) {
|
|
ecs_vec_t *column = &columns[i];
|
|
ecs_type_info_t *ti = type_info[i];
|
|
flecs_table_grow_column(world, column, ti, to_add, size, true);
|
|
ecs_assert(columns[i].size == size, ECS_INTERNAL_ERROR, NULL);
|
|
flecs_run_add_hooks(world, table, ti, column, e, table->type.array[i],
|
|
cur_count, to_add, false);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static
|
|
void flecs_table_fast_append(
|
|
ecs_world_t *world,
|
|
ecs_type_info_t **type_info,
|
|
ecs_vec_t *columns,
|
|
int32_t count)
|
|
{
|
|
/* Add elements to each column array */
|
|
int32_t i;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_type_info_t *ti = type_info[i];
|
|
ecs_vec_t *column = &columns[i];
|
|
ecs_vec_append(&world->allocator, column, ti->size);
|
|
}
|
|
}
|
|
|
|
int32_t flecs_table_append(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_entity_t entity,
|
|
ecs_record_t *record,
|
|
bool construct,
|
|
bool on_add)
|
|
{
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(!table->lock, ECS_LOCKED_STORAGE, 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->storage_count;
|
|
ecs_vec_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;
|
|
|
|
/* Add record ptr to array with record ptrs */
|
|
ecs_record_t **r = ecs_vec_append_t(&world->allocator,
|
|
&data->records, ecs_record_t*);
|
|
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
*r = record;
|
|
|
|
/* 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);
|
|
|
|
ecs_type_info_t **type_info = table->type_info;
|
|
|
|
/* Fast path: no switch columns, no lifecycle actions */
|
|
if (!(table->flags & EcsTableIsComplex)) {
|
|
flecs_table_fast_append(world, type_info, columns, column_count);
|
|
if (!count) {
|
|
flecs_table_set_empty(world, table); /* See below */
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int32_t sw_count = table->sw_count;
|
|
int32_t bs_count = table->bs_count;
|
|
ecs_switch_t *sw_columns = data->sw_columns;
|
|
ecs_bitset_t *bs_columns = data->bs_columns;
|
|
ecs_entity_t *entities = data->entities.array;
|
|
|
|
/* Reobtain size to ensure that the columns have the same size as the
|
|
* entities and record vectors. This keeps reasoning about when allocations
|
|
* occur easier. */
|
|
int32_t size = data->entities.size;
|
|
|
|
/* Grow component arrays with 1 element */
|
|
int32_t i;
|
|
for (i = 0; i < column_count; i ++) {
|
|
ecs_vec_t *column = &columns[i];
|
|
ecs_type_info_t *ti = type_info[i];
|
|
flecs_table_grow_column(world, column, ti, 1, size, construct);
|
|
|
|
ecs_iter_action_t on_add_hook;
|
|
if (on_add && (on_add_hook = ti->hooks.on_add)) {
|
|
flecs_on_component_callback(world, table, on_add_hook, EcsOnAdd, column,
|
|
&entities[count], table->storage_ids[i], count, 1, ti);
|
|
}
|
|
|
|
ecs_assert(columns[i].size ==
|
|
data->entities.size, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(columns[i].count ==
|
|
data->entities.count, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
/* Add element to each switch column */
|
|
for (i = 0; i < sw_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;
|
|
}
|
|
|
|
static
|
|
void flecs_table_fast_delete_last(
|
|
ecs_vec_t *columns,
|
|
int32_t column_count)
|
|
{
|
|
int i;
|
|
for (i = 0; i < column_count; i ++) {
|
|
ecs_vec_remove_last(&columns[i]);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_table_fast_delete(
|
|
ecs_type_info_t **type_info,
|
|
ecs_vec_t *columns,
|
|
int32_t column_count,
|
|
int32_t index)
|
|
{
|
|
int i;
|
|
for (i = 0; i < column_count; i ++) {
|
|
ecs_type_info_t *ti = type_info[i];
|
|
ecs_vec_t *column = &columns[i];
|
|
ecs_vec_remove(column, ti->size, index);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
/* Move last record ptr to index */
|
|
ecs_assert(count < data->records.count, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_record_t **records = data->records.array;
|
|
ecs_record_t *record_to_move = records[count];
|
|
records[index] = record_to_move;
|
|
ecs_vec_remove_last(&data->records);
|
|
|
|
/* Update record of moved entity in entity index */
|
|
if (index != count) {
|
|
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_type_info_t **type_info = table->type_info;
|
|
ecs_vec_t *columns = data->columns;
|
|
int32_t column_count = table->storage_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(type_info, columns, column_count, index);
|
|
}
|
|
|
|
flecs_table_check_sanity(table);
|
|
return;
|
|
}
|
|
|
|
ecs_id_t *ids = table->storage_ids;
|
|
|
|
/* 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_run_remove_hooks(world, table, type_info[i], &columns[i],
|
|
&entity_to_delete, ids[i], 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_vec_t *column = &columns[i];
|
|
ecs_type_info_t *ti = type_info[i];
|
|
ecs_size_t size = ti->size;
|
|
void *dst = ecs_vec_get(column, size, index);
|
|
void *src = ecs_vec_last(column, size);
|
|
|
|
ecs_iter_action_t on_remove = ti->hooks.on_remove;
|
|
if (destruct && on_remove) {
|
|
flecs_on_component_callback(world, table, on_remove, EcsOnRemove,
|
|
column, &entity_to_delete, ids[i], index, 1, ti);
|
|
}
|
|
|
|
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);
|
|
}
|
|
} else {
|
|
flecs_table_fast_delete(type_info, columns, column_count, index);
|
|
}
|
|
}
|
|
|
|
/* Remove elements from switch columns */
|
|
ecs_switch_t *sw_columns = data->sw_columns;
|
|
int32_t sw_count = table->sw_count;
|
|
for (i = 0; i < sw_count; i ++) {
|
|
flecs_switch_remove(&sw_columns[i], index);
|
|
}
|
|
|
|
/* Remove elements from bitset columns */
|
|
ecs_bitset_t *bs_columns = data->bs_columns;
|
|
int32_t bs_count = table->bs_count;
|
|
for (i = 0; i < bs_count; i ++) {
|
|
flecs_bitset_remove(&bs_columns[i], index);
|
|
}
|
|
|
|
flecs_table_check_sanity(table);
|
|
}
|
|
|
|
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->storage_count;
|
|
int32_t i_old = 0, src_column_count = src_table->storage_count;
|
|
ecs_id_t *dst_ids = dst_table->storage_ids;
|
|
ecs_id_t *src_ids = src_table->storage_ids;
|
|
|
|
ecs_vec_t *src_columns = src_table->data.columns;
|
|
ecs_vec_t *dst_columns = dst_table->data.columns;
|
|
|
|
ecs_type_info_t **dst_type_info = dst_table->type_info;
|
|
|
|
for (; (i_new < dst_column_count) && (i_old < src_column_count);) {
|
|
ecs_id_t dst_id = dst_ids[i_new];
|
|
ecs_id_t src_id = src_ids[i_old];
|
|
|
|
if (dst_id == src_id) {
|
|
ecs_vec_t *dst_column = &dst_columns[i_new];
|
|
ecs_vec_t *src_column = &src_columns[i_old];
|
|
ecs_type_info_t *ti = dst_type_info[i_new];
|
|
int32_t size = ti->size;
|
|
void *dst = ecs_vec_get(dst_column, size, dst_index);
|
|
void *src = ecs_vec_get(src_column, size, src_index);
|
|
ecs_os_memcpy(dst, src, size);
|
|
}
|
|
|
|
i_new += dst_id <= src_id;
|
|
i_old += dst_id >= src_id;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
ecs_type_info_t **dst_type_info = dst_table->type_info;
|
|
ecs_type_info_t **src_type_info = src_table->type_info;
|
|
|
|
int32_t i_new = 0, dst_column_count = dst_table->storage_count;
|
|
int32_t i_old = 0, src_column_count = src_table->storage_count;
|
|
ecs_id_t *dst_ids = dst_table->storage_ids;
|
|
ecs_id_t *src_ids = src_table->storage_ids;
|
|
|
|
ecs_vec_t *src_columns = src_table->data.columns;
|
|
ecs_vec_t *dst_columns = dst_table->data.columns;
|
|
|
|
for (; (i_new < dst_column_count) && (i_old < src_column_count);) {
|
|
ecs_id_t dst_id = dst_ids[i_new];
|
|
ecs_id_t src_id = src_ids[i_old];
|
|
|
|
if (dst_id == src_id) {
|
|
ecs_vec_t *dst_column = &dst_columns[i_new];
|
|
ecs_vec_t *src_column = &src_columns[i_old];
|
|
ecs_type_info_t *ti = dst_type_info[i_new];
|
|
int32_t size = ti->size;
|
|
|
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
|
|
void *dst = ecs_vec_get(dst_column, size, dst_index);
|
|
void *src = ecs_vec_get(src_column, size, src_index);
|
|
|
|
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_run_add_hooks(world, dst_table, dst_type_info[i_new],
|
|
&dst_columns[i_new], &dst_entity, dst_id,
|
|
dst_index, 1, construct);
|
|
} else {
|
|
flecs_run_remove_hooks(world, src_table, src_type_info[i_old],
|
|
&src_columns[i_old], &src_entity, src_id,
|
|
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_run_add_hooks(world, dst_table, dst_type_info[i_new],
|
|
&dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1,
|
|
construct);
|
|
}
|
|
|
|
for (; (i_old < src_column_count); i_old ++) {
|
|
flecs_run_remove_hooks(world, src_table, src_type_info[i_old],
|
|
&src_columns[i_old], &src_entity, src_ids[i_old],
|
|
src_index, 1, use_move_dtor);
|
|
}
|
|
|
|
flecs_table_check_sanity(dst_table);
|
|
flecs_table_check_sanity(src_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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
ecs_vec_reclaim_t(&world->allocator, &data->records, ecs_record_t*);
|
|
|
|
int32_t i, count = table->storage_count;
|
|
ecs_type_info_t **type_info = table->type_info;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_vec_t *column = &data->columns[i];
|
|
ecs_type_info_t *ti = type_info[i];
|
|
ecs_vec_reclaim(&world->allocator, column, ti->size);
|
|
}
|
|
|
|
return has_payload;
|
|
}
|
|
|
|
int32_t flecs_table_data_count(
|
|
const ecs_data_t *data)
|
|
{
|
|
return data ? data->entities.count : 0;
|
|
}
|
|
|
|
static
|
|
void flecs_table_swap_switch_columns(
|
|
ecs_table_t *table,
|
|
ecs_data_t *data,
|
|
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 = data->sw_columns;
|
|
|
|
for (i = 0; i < column_count; i ++) {
|
|
ecs_switch_t *sw = &columns[i];
|
|
flecs_switch_swap(sw, row_1, row_2);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_table_swap_bitset_columns(
|
|
ecs_table_t *table,
|
|
ecs_data_t *data,
|
|
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 = data->bs_columns;
|
|
|
|
for (i = 0; i < column_count; i ++) {
|
|
ecs_bitset_t *bs = &columns[i];
|
|
flecs_bitset_swap(bs, row_1, row_2);
|
|
}
|
|
}
|
|
|
|
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 **records = table->data.records.array;
|
|
ecs_record_t *record_ptr_1 = records[row_1];
|
|
ecs_record_t *record_ptr_2 = records[row_2];
|
|
|
|
ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* 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);
|
|
records[row_1] = record_ptr_2;
|
|
records[row_2] = record_ptr_1;
|
|
|
|
flecs_table_swap_switch_columns(table, &table->data, row_1, row_2);
|
|
flecs_table_swap_bitset_columns(table, &table->data, row_1, row_2);
|
|
|
|
ecs_vec_t *columns = table->data.columns;
|
|
if (!columns) {
|
|
flecs_table_check_sanity(table);
|
|
return;
|
|
}
|
|
|
|
ecs_type_info_t **type_info = table->type_info;
|
|
|
|
/* 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->storage_count;
|
|
for (i = 0; i < column_count; i++) {
|
|
ecs_type_info_t* ti = type_info[i];
|
|
temp_buffer_size = ECS_MAX(temp_buffer_size, ti->size);
|
|
}
|
|
|
|
void* tmp = ecs_os_alloca(temp_buffer_size);
|
|
|
|
/* Swap columns */
|
|
for (i = 0; i < column_count; i ++) {
|
|
ecs_type_info_t *ti = type_info[i];
|
|
int32_t size = ti->size;
|
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
void *ptr = columns[i].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_merge_column(
|
|
ecs_world_t *world,
|
|
ecs_vec_t *dst,
|
|
ecs_vec_t *src,
|
|
int32_t size,
|
|
int32_t column_size,
|
|
ecs_type_info_t *ti)
|
|
{
|
|
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;
|
|
|
|
/* If the new table is not empty, copy the contents from the
|
|
* src into the dst. */
|
|
} else {
|
|
int32_t src_count = src->count;
|
|
|
|
if (ti) {
|
|
flecs_table_grow_column(world, dst, ti, src_count,
|
|
column_size, true);
|
|
} else {
|
|
if (column_size) {
|
|
ecs_vec_set_size(&world->allocator,
|
|
dst, size, column_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;
|
|
|
|
/* Move values into column */
|
|
ecs_move_t move = NULL;
|
|
if (ti) {
|
|
move = ti->hooks.move;
|
|
}
|
|
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, size);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_merge_table_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->storage_count;
|
|
int32_t i_old = 0, src_column_count = src_table->storage_count;
|
|
ecs_id_t *dst_ids = dst_table->storage_ids;
|
|
ecs_id_t *src_ids = src_table->storage_ids;
|
|
|
|
ecs_type_info_t **dst_type_info = dst_table->type_info;
|
|
ecs_type_info_t **src_type_info = src_table->type_info;
|
|
|
|
ecs_vec_t *src = src_data->columns;
|
|
ecs_vec_t *dst = dst_data->columns;
|
|
|
|
ecs_assert(!dst_column_count || dst, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (!src_count) {
|
|
return;
|
|
}
|
|
|
|
/* Merge entities */
|
|
flecs_merge_column(world, &dst_data->entities, &src_data->entities,
|
|
ECS_SIZEOF(ecs_entity_t), 0, NULL);
|
|
ecs_assert(dst_data->entities.count == src_count + dst_count,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
int32_t column_size = dst_data->entities.size;
|
|
|
|
/* Merge record pointers */
|
|
flecs_merge_column(world, &dst_data->records, &src_data->records,
|
|
ECS_SIZEOF(ecs_record_t*), 0, NULL);
|
|
|
|
for (; (i_new < dst_column_count) && (i_old < src_column_count); ) {
|
|
ecs_id_t dst_id = dst_ids[i_new];
|
|
ecs_id_t src_id = src_ids[i_old];
|
|
ecs_type_info_t *dst_ti = dst_type_info[i_new];
|
|
int32_t size = dst_ti->size;
|
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (dst_id == src_id) {
|
|
flecs_merge_column(world, &dst[i_new], &src[i_old], size, column_size, dst_ti);
|
|
flecs_table_mark_table_dirty(world, dst_table, i_new + 1);
|
|
ecs_assert(dst[i_new].size == dst_data->entities.size,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
i_new ++;
|
|
i_old ++;
|
|
} else if (dst_id < src_id) {
|
|
/* New column, make sure vector is large enough. */
|
|
ecs_vec_t *column = &dst[i_new];
|
|
ecs_vec_set_count(&world->allocator, column, size, src_count + dst_count);
|
|
flecs_ctor_component(dst_ti, column, 0, src_count + dst_count);
|
|
i_new ++;
|
|
} else if (dst_id > src_id) {
|
|
/* Old column does not occur in new table, destruct */
|
|
ecs_vec_t *column = &src[i_old];
|
|
ecs_type_info_t *ti = src_type_info[i_old];
|
|
flecs_dtor_component(ti, column, 0, src_count);
|
|
ecs_vec_fini(&world->allocator, column, ti->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_vec_t *column = &dst[i_new];
|
|
ecs_type_info_t *ti = dst_type_info[i_new];
|
|
int32_t size = ti->size;
|
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_vec_set_count(&world->allocator, column, size, src_count + dst_count);
|
|
flecs_ctor_component(ti, column, 0, src_count + dst_count);
|
|
}
|
|
|
|
/* Destruct remaining columns */
|
|
for (; i_old < src_column_count; i_old ++) {
|
|
ecs_vec_t *column = &src[i_old];
|
|
ecs_type_info_t *ti = src_type_info[i_old];
|
|
flecs_dtor_component(ti, column, 0, src_count);
|
|
ecs_vec_fini(&world->allocator, column, ti->size);
|
|
}
|
|
|
|
/* Mark entity column as dirty */
|
|
flecs_table_mark_table_dirty(world, dst_table, 0);
|
|
}
|
|
|
|
int32_t ecs_table_count(
|
|
const ecs_table_t *table)
|
|
{
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
return flecs_table_data_count(&table->data);
|
|
}
|
|
|
|
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(dst_table);
|
|
flecs_table_check_sanity(src_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;
|
|
ecs_record_t **src_records = src_data->records.array;
|
|
|
|
/* 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;
|
|
if (dst_table != src_table) {
|
|
record = src_records[i];
|
|
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
} else {
|
|
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_merge_table_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_observer_add(dst_table, src_table->observed_count);
|
|
flecs_table_observer_add(src_table, -src_table->observed_count);
|
|
ecs_assert(src_table->observed_count == 0, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
flecs_table_check_sanity(src_table);
|
|
flecs_table_check_sanity(dst_table);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int32_t* flecs_table_get_dirty_state(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
if (!table->dirty_state) {
|
|
int32_t column_count = table->storage_count;
|
|
table->dirty_state = flecs_alloc_n(&world->allocator,
|
|
int32_t, column_count + 1);
|
|
ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
for (int i = 0; i < column_count + 1; i ++) {
|
|
table->dirty_state[i] = 1;
|
|
}
|
|
}
|
|
return table->dirty_state;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ecs_table_has_module(
|
|
ecs_table_t *table)
|
|
{
|
|
return table->flags & EcsTableHasModule;
|
|
}
|
|
|
|
ecs_vec_t* ecs_table_column_for_id(
|
|
const ecs_world_t *world,
|
|
const ecs_table_t *table,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_table_t *storage_table = table->storage_table;
|
|
if (!storage_table) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_table_record_t *tr = flecs_table_record_get(world, storage_table, id);
|
|
if (tr) {
|
|
return &table->data.columns[tr->column];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const ecs_type_t* ecs_table_get_type(
|
|
const ecs_table_t *table)
|
|
{
|
|
if (table) {
|
|
return &table->type;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
ecs_table_t* ecs_table_get_storage_table(
|
|
const ecs_table_t *table)
|
|
{
|
|
return table->storage_table;
|
|
}
|
|
|
|
int32_t ecs_table_type_to_storage_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 *storage_map = table->storage_map;
|
|
if (storage_map) {
|
|
return storage_map[index];
|
|
}
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
int32_t ecs_table_storage_to_type_index(
|
|
const ecs_table_t *table,
|
|
int32_t index)
|
|
{
|
|
ecs_check(index < table->storage_count, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
int32_t offset = table->type.count;
|
|
return table->storage_map[offset + index];
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
int32_t flecs_table_column_to_union_index(
|
|
const ecs_table_t *table,
|
|
int32_t column)
|
|
{
|
|
int32_t sw_count = table->sw_count;
|
|
if (sw_count) {
|
|
int32_t sw_offset = table->sw_offset;
|
|
if (column >= sw_offset && column < (sw_offset + sw_count)){
|
|
return column - sw_offset;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
ecs_record_t* ecs_record_find(
|
|
const ecs_world_t *world,
|
|
ecs_entity_t entity)
|
|
{
|
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
world = ecs_get_world(world);
|
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity);
|
|
if (r) {
|
|
return r;
|
|
}
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
void* ecs_table_get_column(
|
|
const ecs_table_t *table,
|
|
int32_t index,
|
|
int32_t offset)
|
|
{
|
|
ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
int32_t storage_index = table->storage_map[index];
|
|
if (storage_index == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
void *result = table->data.columns[storage_index].array;
|
|
if (offset) {
|
|
ecs_size_t size = table->type_info[storage_index]->size;
|
|
result = ECS_ELEM(result, size, offset);
|
|
}
|
|
|
|
return result;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
int32_t ecs_table_get_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;
|
|
}
|
|
|
|
const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
|
|
if (!tr) {
|
|
return -1;
|
|
}
|
|
|
|
return tr->column;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
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_index(world, table, id);
|
|
if (index == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
return ecs_table_get_column(table, index, offset);
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void* ecs_record_get_column(
|
|
const ecs_record_t *r,
|
|
int32_t column,
|
|
size_t c_size)
|
|
{
|
|
(void)c_size;
|
|
ecs_table_t *table = r->table;
|
|
|
|
ecs_check(column < table->storage_count, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_type_info_t *ti = table->type_info[column];
|
|
ecs_vec_t *c = &table->data.columns[column];
|
|
ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == ti->size,
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
|
|
return ecs_vec_get(c, ti->size, ECS_RECORD_TO_ROW(r->row));
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
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->observed_count;
|
|
}
|
|
|
|
/**
|
|
* @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[] = {
|
|
[EcsMixinBase] = "base (should never be requested by application)",
|
|
[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 = {
|
|
[EcsMixinBase] = offsetof(ecs_stage_t, world),
|
|
[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* get_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;
|
|
if (!mixins) {
|
|
/* Object has no mixins */
|
|
goto not_found;
|
|
}
|
|
|
|
ecs_size_t offset = mixins->elems[kind];
|
|
if (offset == 0) {
|
|
/* Object has mixins but not the requested one. Try to find the mixin
|
|
* in the poly's base */
|
|
goto find_in_base;
|
|
}
|
|
|
|
/* Object has mixin, return its address */
|
|
return ECS_OFFSET(hdr, offset);
|
|
|
|
find_in_base:
|
|
if (offset) {
|
|
/* If the poly has a base, try to find the mixin in the base */
|
|
ecs_poly_t *base = *(ecs_poly_t**)ECS_OFFSET(hdr, offset);
|
|
if (base) {
|
|
return get_mixin(base, kind);
|
|
}
|
|
}
|
|
|
|
not_found:
|
|
/* Mixin wasn't found for poly */
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
void* assert_mixin(
|
|
const ecs_poly_t *poly,
|
|
ecs_mixin_kind_t kind)
|
|
{
|
|
void *ptr = get_mixin(poly, kind);
|
|
if (!ptr) {
|
|
const ecs_header_t *header = poly;
|
|
const ecs_mixins_t *mixins = header->mixins;
|
|
ecs_err("%s not available for type %s",
|
|
mixin_kind_str[kind],
|
|
mixins ? mixins->type_name : "unknown");
|
|
ecs_os_abort();
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#define assert_object(cond, file, line, type_name)\
|
|
_ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, type_name);\
|
|
assert(cond)
|
|
|
|
#ifndef FLECS_NDEBUG
|
|
void* _ecs_poly_assert(
|
|
const ecs_poly_t *poly,
|
|
int32_t type,
|
|
const char *file,
|
|
int32_t line)
|
|
{
|
|
assert_object(poly != NULL, file, line, 0);
|
|
|
|
const ecs_header_t *hdr = poly;
|
|
const char *type_name = hdr->mixins->type_name;
|
|
assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line, type_name);
|
|
assert_object(hdr->type == type, file, line, type_name);
|
|
return (ecs_poly_t*)poly;
|
|
}
|
|
#endif
|
|
|
|
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)
|
|
{
|
|
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 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 {
|
|
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->storage_count, ECS_NOT_A_COMPONENT, NULL);
|
|
ecs_type_info_t *ti = table->type_info[column_index];
|
|
ecs_vec_t *column = &table->data.columns[column_index];
|
|
return (flecs_component_ptr_t){
|
|
.ti = ti,
|
|
.ptr = ecs_vec_get(column, ti->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);
|
|
|
|
if (!table->storage_table) {
|
|
ecs_check(ecs_search(world, table, id, 0) == -1,
|
|
ECS_NOT_A_COMPONENT, NULL);
|
|
return (flecs_component_ptr_t){0};
|
|
}
|
|
|
|
ecs_table_record_t *tr = flecs_table_record_get(
|
|
world, table->storage_table, id);
|
|
if (!tr) {
|
|
ecs_check(ecs_search(world, table, id, 0) == -1,
|
|
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;
|
|
}
|
|
|
|
static
|
|
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 */
|
|
const 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->column, end = tr_isa->count + tr_isa->column;
|
|
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);
|
|
if (!r) {
|
|
continue;
|
|
}
|
|
|
|
table = r->table;
|
|
if (!table) {
|
|
continue;
|
|
}
|
|
|
|
const ecs_table_record_t *tr = NULL;
|
|
|
|
ecs_table_t *storage_table = table->storage_table;
|
|
if (storage_table) {
|
|
tr = flecs_id_record_get_table(table_index, storage_table);
|
|
} else {
|
|
ecs_check(!ecs_owns_id(world, base, id),
|
|
ECS_NOT_A_COMPONENT, NULL);
|
|
}
|
|
|
|
if (!tr) {
|
|
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) == base)) {
|
|
childof_base_index = pos;
|
|
}
|
|
|
|
int32_t storage_index = ecs_table_type_to_storage_index(child_table, i);
|
|
if (storage_index != -1) {
|
|
ecs_vec_t *column = &child_data->columns[storage_index];
|
|
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) {
|
|
int32_t u, u_count = child_table->sw_count;
|
|
for (u = 0; u < u_count; u ++) {
|
|
ecs_switch_t *src_sw = &child_table->data.sw_columns[i];
|
|
ecs_switch_t *dst_sw = &i_table->data.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_table_t *base_table = ecs_get_table(world, ecs_get_alive(world, base));
|
|
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;
|
|
}
|
|
|
|
const ecs_table_record_t *tr = flecs_id_record_get_table(
|
|
idr, table);
|
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
int32_t column = tr->column - table->sw_offset;
|
|
ecs_switch_t *sw = &table->data.sw_columns[column];
|
|
ecs_entity_t union_case = 0;
|
|
union_case = ECS_PAIR_SECOND(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|EcsTableHasObserved)) {
|
|
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|EcsTableHasObserved)))
|
|
{
|
|
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
|
|
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)
|
|
{
|
|
if (!record) {
|
|
record = flecs_entities_ensure(world, entity);
|
|
}
|
|
|
|
int32_t row = flecs_table_append(world, table, entity, record, 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,
|
|
record, 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);
|
|
|
|
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;
|
|
uint32_t row_flags = 0;
|
|
int observed = 0;
|
|
if (record) {
|
|
src_table = record->table;
|
|
row_flags = record->row & ECS_ROW_FLAGS_MASK;
|
|
observed = (row_flags & EcsEntityObservedAcyclic) != 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_observer_add(dst_table, observed);
|
|
|
|
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_observer_add(src_table, -observed);
|
|
} else {
|
|
flecs_table_observer_add(dst_table, observed);
|
|
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 (row_flags) {
|
|
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_sparse_new_ids(ecs_eis(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;
|
|
ecs_record_t **records = ecs_vec_first(&data->records);
|
|
for (i = 0; i < count; i ++) {
|
|
records[row + i] = flecs_entities_set(world, entities[i],
|
|
&(ecs_record_t){
|
|
.table = table,
|
|
.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) {
|
|
/* Set components that we're setting in the component mask so the init
|
|
* actions won't call OnSet triggers for them. This ensures we won't
|
|
* call OnSet triggers multiple times for the same component */
|
|
int32_t c_i;
|
|
ecs_table_t *storage_table = table->storage_table;
|
|
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, storage_table, id);
|
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t index = tr->column;
|
|
ecs_type_info_t *ti = table->type_info[index];
|
|
ecs_vec_t *column = &table->data.columns[index];
|
|
int32_t size = ti->size;
|
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
|
|
void *ptr = ecs_vec_get(column, 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);
|
|
}
|
|
};
|
|
|
|
flecs_notify_on_set(world, table, row, count, NULL, true);
|
|
}
|
|
|
|
flecs_defer_end(world, &world->stages[0]);
|
|
|
|
if (row_out) {
|
|
*row_out = row;
|
|
}
|
|
|
|
if (sparse_count) {
|
|
entities = flecs_sparse_ids(ecs_eis(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_table_diff_t diff = ECS_TABLE_DIFF_INIT;
|
|
|
|
ecs_table_t *src_table = NULL;
|
|
if (record) {
|
|
src_table = record->table;
|
|
}
|
|
|
|
ecs_table_t *dst_table = flecs_table_traverse_add(
|
|
world, src_table, &id, &diff);
|
|
|
|
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(world, stage, entity, id)) {
|
|
return;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_ensure(world, entity);
|
|
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(world, stage, entity, id)) {
|
|
return;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity);
|
|
ecs_table_t *src_table = NULL;
|
|
if (!r || !(src_table = r->table)) {
|
|
goto done; /* Nothing to remove */
|
|
}
|
|
|
|
ecs_table_diff_t diff = ECS_TABLE_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);
|
|
|
|
done:
|
|
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) {
|
|
/* 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->storage_table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
dst = flecs_get_component_ptr(
|
|
world, r->table, ECS_RECORD_TO_ROW(r->row), id);
|
|
|
|
return dst;
|
|
}
|
|
|
|
error:
|
|
return dst;
|
|
}
|
|
|
|
/* -- Private functions -- */
|
|
|
|
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_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);
|
|
|
|
ecs_type_t local_ids;
|
|
if (!ids) {
|
|
local_ids.array = table->storage_ids;
|
|
local_ids.count = table->storage_count;
|
|
ids = &local_ids;
|
|
}
|
|
|
|
if (owned) {
|
|
ecs_table_t *storage_table = table->storage_table;
|
|
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,
|
|
storage_table, id);
|
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL);
|
|
int32_t column = tr->column;
|
|
const ecs_type_info_t *ti = table->type_info[column];
|
|
ecs_iter_action_t on_set = ti->hooks.on_set;
|
|
if (on_set) {
|
|
ecs_vec_t *c = &table->data.columns[column];
|
|
ecs_size_t size = ti->size;
|
|
void *ptr = ecs_vec_get(c, size, row);
|
|
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
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[0] = size;
|
|
it.ids[0] = id;
|
|
it.event = EcsOnSet;
|
|
it.event_id = id;
|
|
it.ctx = ti->hooks.ctx;
|
|
it.binding_ctx = ti->hooks.binding_ctx;
|
|
it.count = count;
|
|
flecs_iter_validate(&it);
|
|
on_set(&it);
|
|
ecs_iter_fini(&it);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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
|
|
});
|
|
}
|
|
}
|
|
|
|
ecs_record_t* flecs_add_flag(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
uint32_t flag)
|
|
{
|
|
ecs_record_t *record = flecs_entities_get(world, entity);
|
|
if (!record) {
|
|
ecs_record_t new_record = {.row = flag, .table = NULL};
|
|
flecs_entities_set(world, entity, &new_record);
|
|
} else {
|
|
if (flag == EcsEntityObservedAcyclic) {
|
|
if (!(record->row & flag)) {
|
|
ecs_table_t *table = record->table;
|
|
if (table) {
|
|
flecs_table_observer_add(table, 1);
|
|
}
|
|
}
|
|
}
|
|
record->row |= flag;
|
|
}
|
|
return record;
|
|
}
|
|
|
|
|
|
/* -- 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_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.added = *removed;
|
|
}
|
|
|
|
flecs_commit(world, entity, record, table, &diff, true, 0);
|
|
|
|
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_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(unsafe_world->info.last_id < UINT_MAX,
|
|
ECS_INVALID_OPERATION, NULL);
|
|
entity = (ecs_entity_t)ecs_os_ainc(
|
|
(int32_t*)&unsafe_world->info.last_id);
|
|
} else {
|
|
entity = flecs_entities_recycle(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_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 < ECS_HI_COMPONENT_ID) {
|
|
do {
|
|
id = unsafe_world->info.last_component_id ++;
|
|
} while (ecs_exists(unsafe_world, id) && id <= ECS_HI_COMPONENT_ID);
|
|
}
|
|
|
|
if (!id || id >= ECS_HI_COMPONENT_ID) {
|
|
/* If the low component ids are depleted, return a regular entity id */
|
|
id = ecs_new_id(unsafe_world);
|
|
}
|
|
|
|
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(world, stage, entity, to_add.array[0])) {
|
|
int i;
|
|
for (i = 1; i < to_add.count; i ++) {
|
|
flecs_defer_add(world, 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);
|
|
flecs_new_entity(world, entity, NULL, table, &table_diff, true, true);
|
|
flecs_table_diff_builder_fini(world, &diff);
|
|
} else {
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
return entity;
|
|
}
|
|
|
|
flecs_entities_set(world, entity, &(ecs_record_t){ 0 });
|
|
}
|
|
flecs_defer_end(world, stage);
|
|
|
|
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))){
|
|
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))){
|
|
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 = NULL;
|
|
if (!flecs_new_entity) {
|
|
r = flecs_entities_get(world, result);
|
|
if (r) {
|
|
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 < ECS_ID_CACHE_SIZE) && (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 < ECS_ID_CACHE_SIZE) && (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) {
|
|
/* To prevent creating two entities with the same name, temporarily go
|
|
* out of readonly mode if it's safe to do so. */
|
|
ecs_suspend_readonly_state_t state;
|
|
if (thread_count <= 1) {
|
|
/* When not running on multiple threads we can temporarily leave
|
|
* readonly mode which ensures that we don't accidentally create
|
|
* two entities with the same name. */
|
|
ecs_world_t *real_world = flecs_suspend_readonly(world, &state);
|
|
ecs_add_path_w_sep(real_world, entity, scope, name, sep, root_sep);
|
|
flecs_resume_readonly(real_world, &state);
|
|
} else {
|
|
/* In multithreaded mode we can't leave readonly mode, which means
|
|
* there is a risk of creating two entities with the same name.
|
|
* Future improvements will be able to detect this. */
|
|
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);
|
|
|
|
const char *name = desc->name;
|
|
const char *sep = desc->sep;
|
|
if (!sep) {
|
|
sep = ".";
|
|
}
|
|
|
|
if (name && !name[0]) {
|
|
name = NULL;
|
|
}
|
|
|
|
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 */
|
|
ecs_entity_t result = desc->id;
|
|
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 < ECS_ID_CACHE_SIZE) && (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 */
|
|
path = ecs_get_path_w_sep(world, scope, result, sep, "");
|
|
}
|
|
if (path) {
|
|
if (ecs_os_strcmp(path, name)) {
|
|
/* Mismatching name */
|
|
ecs_err("existing entity '%s' is initialized with "
|
|
"conflicting name '%s'", path, name);
|
|
ecs_os_free(path);
|
|
return 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_sparse_new_ids(ecs_eis(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;
|
|
ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT;
|
|
flecs_table_diff_builder_init(world, &diff);
|
|
if (!table) {
|
|
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_id_t*)desc->ids;
|
|
ids.count = i;
|
|
} else {
|
|
diff.added.array = table->type.array;
|
|
diff.added.count = table->type.count;
|
|
ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count};
|
|
}
|
|
|
|
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);
|
|
|
|
if (!sparse_count) {
|
|
return entities;
|
|
} else {
|
|
/* Refetch entity ids, in case the underlying array was reallocated */
|
|
entities = flecs_sparse_ids(ecs_eis(world));
|
|
return &entities[sparse_count];
|
|
}
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
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);
|
|
|
|
ecs_suspend_readonly_state_t readonly_state;
|
|
world = flecs_suspend_readonly(world, &readonly_state);
|
|
|
|
bool new_component = true;
|
|
ecs_entity_t result = desc->entity;
|
|
if (!result) {
|
|
result = ecs_new_low_id(world);
|
|
} else {
|
|
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 {
|
|
if (ptr->size != desc->type.size) {
|
|
char *path = ecs_get_fullpath(world, result);
|
|
ecs_abort(ECS_INVALID_COMPONENT_SIZE, path);
|
|
ecs_os_free(path);
|
|
}
|
|
if (ptr->alignment != desc->type.alignment) {
|
|
char *path = ecs_get_fullpath(world, result);
|
|
ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path);
|
|
ecs_os_free(path);
|
|
}
|
|
}
|
|
|
|
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 < ECS_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(world, stage, entity)) {
|
|
return;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_get(world, entity);
|
|
if (!r) {
|
|
return; /* Nothing to clear */
|
|
}
|
|
|
|
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 & EcsEntityObservedAcyclic) {
|
|
flecs_table_observer_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_vector_add(&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);
|
|
ecs_record_t **records = ecs_vec_first(&table->data.records);
|
|
int32_t i, count = ecs_vec_count(&table->data.entities);
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_record_t *r = records[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 & (EcsEntityObservedId|EcsEntityObservedTarget))) {
|
|
continue;
|
|
}
|
|
|
|
ecs_entity_t e = entities[i];
|
|
if (flags & EcsEntityObservedId) {
|
|
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 & EcsEntityObservedTarget) {
|
|
if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) {
|
|
flecs_id_mark_for_delete(world, idr,
|
|
ECS_ID_ON_DELETE_OBJECT(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_OBJECT(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) {
|
|
/* If action is not specified and we're deleting a relationship target,
|
|
* derive the action from the current record */
|
|
ecs_table_record_t *trr = &table->records[tr->column];
|
|
ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache;
|
|
result = ECS_ID_ON_DELETE_OBJECT(idrr->flags);
|
|
}
|
|
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_vector_count(world->store.marked_ids);
|
|
ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids,
|
|
ecs_marked_id_t);
|
|
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->column;
|
|
|
|
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)
|
|
{
|
|
int32_t i, last = ecs_vector_count(world->store.marked_ids), first = 0;
|
|
ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids,
|
|
ecs_marked_id_t);
|
|
|
|
/* Iterate in reverse order so that DAGs get deleted bottom to top */
|
|
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 */
|
|
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_vector_first(world->store.marked_ids, ecs_marked_id_t);
|
|
}
|
|
|
|
/* Check if new ids were marked since we started */
|
|
int32_t new_last = ecs_vector_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_vector_count(world->store.marked_ids);
|
|
ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids,
|
|
ecs_marked_id_t);
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
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 (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;
|
|
}
|
|
}
|
|
|
|
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_vector_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_vector_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_vector_first(world->store.marked_ids,
|
|
ecs_marked_id_t);
|
|
int32_t i;
|
|
count = ecs_vector_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_vector_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(world, 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(world, 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(world, stage, entity)) {
|
|
return;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_get(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 & EcsEntityObservedAcyclic) {
|
|
table = r->table;
|
|
if (table) {
|
|
flecs_table_observer_add(table, -1);
|
|
}
|
|
}
|
|
if (row_flags & EcsEntityObservedId) {
|
|
flecs_on_delete(world, entity, 0, true);
|
|
flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true);
|
|
}
|
|
if (row_flags & EcsEntityObservedTarget) {
|
|
flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true);
|
|
flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true);
|
|
r->idr = NULL;
|
|
}
|
|
|
|
/* 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_valid(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_valid(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_valid(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(world, stage, dst, src, copy_value)) {
|
|
return dst;
|
|
}
|
|
|
|
ecs_record_t *src_r = flecs_entities_get(world, src);
|
|
ecs_table_t *src_table;
|
|
if (!src_r || !(src_table = src_r->table)) {
|
|
goto done;
|
|
}
|
|
|
|
ecs_type_t src_type = src_table->type;
|
|
ecs_table_diff_t diff = { .added = src_type };
|
|
ecs_record_t *dst_r = flecs_new_entity(world, dst, NULL, src_table, &diff, true, true);
|
|
int32_t row = ECS_RECORD_TO_ROW(dst_r->row);
|
|
|
|
if (copy_value) {
|
|
flecs_table_move(world, dst, src, src_table,
|
|
row, src_table, ECS_RECORD_TO_ROW(src_r->row), true);
|
|
|
|
flecs_notify_on_set(world, src_table, row, 1, NULL, 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_valid(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);
|
|
if (!r) {
|
|
return 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 = NULL;
|
|
ecs_table_t *storage_table = table->storage_table;
|
|
if (storage_table) {
|
|
tr = flecs_id_record_get_table(idr, storage_table);
|
|
} else {
|
|
/* If the entity does not have a storage table (has no data) but it does
|
|
* have the id, the id must be a tag, and getting a tag is illegal. */
|
|
ecs_check(!ecs_owns_id(world, entity, id), ECS_NOT_A_COMPONENT, NULL);
|
|
}
|
|
|
|
if (!tr) {
|
|
return flecs_get_base_component(world, table, id, idr, 0);
|
|
}
|
|
|
|
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_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);
|
|
void *result;
|
|
|
|
if (flecs_defer_set(
|
|
world, stage, EcsOpMut, entity, id, 0, NULL, &result, false))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_ensure(world, entity);
|
|
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;
|
|
}
|
|
|
|
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);
|
|
if (!r) {
|
|
return 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);
|
|
}
|
|
|
|
const void* ecs_record_get_id(
|
|
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);
|
|
}
|
|
|
|
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_valid(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);
|
|
}
|
|
|
|
return result;
|
|
error:
|
|
return (ecs_ref_t){0};
|
|
}
|
|
|
|
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 (!tr || tr->hdr.table != 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);
|
|
}
|
|
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 (!tr || tr->hdr.table != table) {
|
|
tr = ref->tr = flecs_table_record_get(world, table, id);
|
|
if (!tr) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
int32_t column = ecs_table_type_to_storage_index(table, 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_valid(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);
|
|
void *result;
|
|
|
|
if (flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL,
|
|
&result, true))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_ensure(world, entity);
|
|
flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */);
|
|
|
|
void *ptr = flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id);
|
|
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
flecs_defer_end(world, stage);
|
|
|
|
return ptr;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
void flecs_modified_id_if(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
|
|
|
if (flecs_defer_modified(world, 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, true);
|
|
|
|
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_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_modified(world, 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_set(world, stage, EcsOpSet, entity, id,
|
|
flecs_utosize(size), ptr, NULL, false))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_ensure(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_set(world, stage, cmd_kind, entity, id,
|
|
flecs_utosize(size), ptr, NULL, (cmd_kind == EcsOpEmplace)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_ensure(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 == EcsOpEmplace) {
|
|
move = ti->hooks.move_ctor;
|
|
} else {
|
|
move = ti->hooks.move;
|
|
}
|
|
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 == EcsOpSet) {
|
|
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_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 (!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, (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(
|
|
world, stage, entity, id, enable))
|
|
{
|
|
return;
|
|
} else {
|
|
/* Operations invoked by enable/disable should not be deferred */
|
|
stage->defer --;
|
|
}
|
|
|
|
ecs_record_t *r = flecs_entities_ensure(world, entity);
|
|
ecs_entity_t bs_id = id | ECS_TOGGLE;
|
|
|
|
ecs_table_t *table = r->table;
|
|
int32_t index = -1;
|
|
if (table) {
|
|
index = ecs_search(world, table, bs_id, 0);
|
|
}
|
|
|
|
if (index == -1) {
|
|
ecs_add_id(world, entity, bs_id);
|
|
ecs_enable_id(world, entity, id, enable);
|
|
return;
|
|
}
|
|
|
|
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->data.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_valid(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_table_t *table;
|
|
if (!r || !(table = r->table)) {
|
|
return false;
|
|
}
|
|
|
|
ecs_entity_t bs_id = id | ECS_TOGGLE;
|
|
int32_t index = ecs_search(world, table, bs_id, 0);
|
|
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);
|
|
}
|
|
|
|
index -= table->bs_offset;
|
|
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_bitset_t *bs = &table->data.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_valid(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(world, entity);
|
|
ecs_table_t *table;
|
|
if (!r || !(table = r->table)) {
|
|
return false;
|
|
}
|
|
|
|
ecs_table_record_t *tr = NULL;
|
|
int32_t 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_switch_t *sw = &table->data.sw_columns[
|
|
column - table->sw_offset];
|
|
int32_t row = ECS_RECORD_TO_ROW(r->row);
|
|
uint64_t value = flecs_switch_get(sw, row);
|
|
return value == ECS_PAIR_SECOND(id);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
error:
|
|
return false;
|
|
}
|
|
|
|
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(entity != 0, 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_table_t *table;
|
|
if (!r || !(table = r->table)) {
|
|
return 0;
|
|
}
|
|
|
|
ecs_id_t wc = ecs_pair(rel, EcsWildcard);
|
|
ecs_table_record_t *tr = flecs_table_record_get(world, table, wc);
|
|
if (!tr) {
|
|
if (table->flags & EcsTableHasUnion) {
|
|
wc = ecs_pair(EcsUnion, rel);
|
|
tr = flecs_table_record_get(world, table, wc);
|
|
if (tr) {
|
|
ecs_switch_t *sw = &table->data.sw_columns[
|
|
tr->column - table->sw_offset];
|
|
int32_t row = ECS_RECORD_TO_ROW(r->row);
|
|
return flecs_switch_get(sw, row);
|
|
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (index >= tr->count) {
|
|
return 0;
|
|
}
|
|
|
|
ecs_id_t *ids = table->type.array;
|
|
return ecs_pair_second(world, ids[tr->column + index]);
|
|
error:
|
|
return 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_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 {
|
|
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;
|
|
}
|
|
}
|
|
|
|
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
|
|
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_valid(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_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);
|
|
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_init(world, &(ecs_entity_desc_t){
|
|
.name = name
|
|
});
|
|
}
|
|
return flecs_set_identifier(world, entity, EcsName, name);
|
|
}
|
|
|
|
ecs_entity_t ecs_set_symbol(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
const char *name)
|
|
{
|
|
return flecs_set_identifier(world, entity, EcsSymbol, name);
|
|
}
|
|
|
|
void ecs_set_alias(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
const char *name)
|
|
{
|
|
flecs_set_identifier(world, entity, EcsAlias, name);
|
|
}
|
|
|
|
ecs_id_t ecs_make_pair(
|
|
ecs_entity_t relationship,
|
|
ecs_entity_t target)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/* Make sure we're not working with a stage */
|
|
world = ecs_get_world(world);
|
|
|
|
/* 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 & ECS_ID_FLAG_BIT) {
|
|
return ecs_entity_t_lo(entity) != 0;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
if (ecs_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;
|
|
}
|
|
|
|
/* Make sure we're not working with a stage */
|
|
world = ecs_get_world(world);
|
|
|
|
ecs_entity_t current = flecs_entities_get_current(world, entity);
|
|
if (!current || !ecs_is_alive(world, current)) {
|
|
return 0;
|
|
}
|
|
|
|
return current;
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
void ecs_ensure(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t); /* Cannot be a stage */
|
|
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
/* Check if a version of the provided id is alive */
|
|
ecs_entity_t any = ecs_get_alive(world, ecs_strip_generation(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 datastructure 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)
|
|
{
|
|
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 (ecs_get_alive(world, r) == 0) {
|
|
ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER,
|
|
"first element of pair is not alive");
|
|
ecs_ensure(world, r);
|
|
}
|
|
if (ecs_get_alive(world, o) == 0) {
|
|
ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER,
|
|
"second element of pair is not alive");
|
|
ecs_ensure(world, o);
|
|
}
|
|
} else {
|
|
ecs_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);
|
|
if (record) {
|
|
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);
|
|
|
|
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) {
|
|
return ti->component;
|
|
}
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
ecs_entity_t 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;
|
|
}
|
|
|
|
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
|
|
});
|
|
|
|
it.flags |= EcsIterIsFilter;
|
|
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_suspend == false, ECS_INVALID_OPERATION, NULL);
|
|
stage->defer_suspend = true;
|
|
error:
|
|
return;
|
|
}
|
|
|
|
void ecs_defer_resume(
|
|
ecs_world_t *world)
|
|
{
|
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL);
|
|
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
|
ecs_check(stage->defer_suspend == true, ECS_INVALID_OPERATION, NULL);
|
|
stage->defer_suspend = false;
|
|
error:
|
|
return;
|
|
}
|
|
|
|
const char* ecs_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_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);
|
|
}
|
|
|
|
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_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_discard_cmd(
|
|
ecs_world_t *world,
|
|
ecs_cmd_t *cmd)
|
|
{
|
|
if (cmd->kind != EcsOpBulkNew) {
|
|
void *value = cmd->is._1.value;
|
|
if (value) {
|
|
flecs_dtor_value(world, cmd->id, value, 1);
|
|
flecs_stack_free(value, cmd->is._1.size);
|
|
}
|
|
} else {
|
|
ecs_os_free(cmd->is._n.entities);
|
|
}
|
|
}
|
|
|
|
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_OBJECT(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
|
|
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_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 = EcsOpSkip;
|
|
continue;
|
|
}
|
|
/* Entity should remain alive and id is still valid */
|
|
} else {
|
|
/* Id was no longer valid and had a Delete policy */
|
|
cmd->kind = EcsOpSkip;
|
|
ecs_delete(world, entity);
|
|
flecs_table_diff_builder_clear(diff);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ecs_cmd_kind_t kind = cmd->kind;
|
|
switch(kind) {
|
|
case EcsOpAdd:
|
|
table = flecs_find_table_add(world, table, id, diff);
|
|
world->info.cmd.batched_command_count ++;
|
|
break;
|
|
case EcsOpSet:
|
|
case EcsOpMut:
|
|
table = flecs_find_table_add(world, table, id, diff);
|
|
world->info.cmd.batched_command_count ++;
|
|
has_set = true;
|
|
break;
|
|
case EcsOpEmplace:
|
|
/* Don't add for emplace, as this requires a special call to ensure
|
|
* the constructor is not invoked for the component */
|
|
break;
|
|
case EcsOpRemove:
|
|
table = flecs_find_table_remove(world, table, id, diff);
|
|
world->info.cmd.batched_command_count ++;
|
|
break;
|
|
case EcsOpClear:
|
|
table = &world->store.root;
|
|
world->info.cmd.batched_command_count ++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Add, remove and clear operations can be skipped since they have no
|
|
* side effects besides adding/removing components */
|
|
if (kind == EcsOpAdd || kind == EcsOpRemove || kind == EcsOpClear) {
|
|
cmd->kind = EcsOpSkip;
|
|
}
|
|
} 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 ids were both removed and set, check if there are ids that were both
|
|
* set and removed. If so, skip the set command so that the id won't get
|
|
* re-added */
|
|
if (has_set && table_diff.removed.count) {
|
|
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 EcsOpSet:
|
|
case EcsOpMut: {
|
|
ecs_id_record_t *idr = cmd->idr;
|
|
if (!idr) {
|
|
idr = flecs_id_record_get(world, cmd->id);
|
|
}
|
|
|
|
if (!flecs_id_record_get_table(idr, table)) {
|
|
/* Component was deleted */
|
|
cmd->kind = EcsOpSkip;
|
|
world->info.cmd.batched_command_count --;
|
|
world->info.cmd.discard_count ++;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
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_suspend) {
|
|
/* Defer suspending makes it possible to do operations on the storage
|
|
* without flushing the commands in the queue */
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
if (ecs_vec_count(&stage->commands)) {
|
|
ecs_vec_t commands = stage->commands;
|
|
stage->commands.array = NULL;
|
|
stage->commands.count = 0;
|
|
stage->commands.size = 0;
|
|
|
|
ecs_cmd_t *cmds = ecs_vec_first(&commands);
|
|
int32_t i, count = ecs_vec_count(&commands);
|
|
ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0);
|
|
|
|
ecs_stack_t stack = stage->defer_stack;
|
|
flecs_stack_init(&stage->defer_stack);
|
|
|
|
ecs_table_diff_builder_t diff;
|
|
flecs_table_diff_builder_init(world, &diff);
|
|
flecs_sparse_clear(&stage->cmd_entries);
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_cmd_t *cmd = &cmds[i];
|
|
ecs_entity_t e = cmd->entity;
|
|
bool is_alive = flecs_entities_is_valid(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 ++;
|
|
}
|
|
}
|
|
|
|
/* 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 == EcsOpSkip) || (e && !is_alive)) {
|
|
world->info.cmd.discard_count ++;
|
|
flecs_discard_cmd(world, cmd);
|
|
continue;
|
|
}
|
|
|
|
ecs_id_t id = cmd->id;
|
|
|
|
switch(kind) {
|
|
case EcsOpAdd:
|
|
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 EcsOpRemove:
|
|
flecs_remove_id(world, e, id);
|
|
world->info.cmd.remove_count ++;
|
|
break;
|
|
case EcsOpClone:
|
|
ecs_clone(world, e, id, cmd->is._1.clone_value);
|
|
break;
|
|
case EcsOpSet:
|
|
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 EcsOpEmplace:
|
|
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 EcsOpMut:
|
|
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 EcsOpModified:
|
|
flecs_modified_id_if(world, e, id);
|
|
world->info.cmd.modified_count ++;
|
|
break;
|
|
case EcsOpDelete: {
|
|
ecs_delete(world, e);
|
|
world->info.cmd.delete_count ++;
|
|
break;
|
|
}
|
|
case EcsOpClear:
|
|
ecs_clear(world, e);
|
|
world->info.cmd.clear_count ++;
|
|
break;
|
|
case EcsOpOnDeleteAction:
|
|
flecs_on_delete(world, id, e, false);
|
|
world->info.cmd.other_count ++;
|
|
break;
|
|
case EcsOpEnable:
|
|
ecs_enable_id(world, e, id, true);
|
|
world->info.cmd.other_count ++;
|
|
break;
|
|
case EcsOpDisable:
|
|
ecs_enable_id(world, e, id, false);
|
|
world->info.cmd.other_count ++;
|
|
break;
|
|
case EcsOpBulkNew:
|
|
flecs_flush_bulk_new(world, cmd);
|
|
world->info.cmd.other_count ++;
|
|
continue;
|
|
case EcsOpSkip:
|
|
break;
|
|
}
|
|
|
|
if (cmd->is._1.value) {
|
|
flecs_stack_free(cmd->is._1.value, cmd->is._1.size);
|
|
}
|
|
}
|
|
|
|
ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t);
|
|
|
|
/* Restore defer queue */
|
|
ecs_vec_clear(&commands);
|
|
stage->commands = commands;
|
|
|
|
/* Restore stack */
|
|
flecs_stack_fini(&stage->defer_stack);
|
|
stage->defer_stack = stack;
|
|
flecs_stack_reset(&stage->defer_stack);
|
|
|
|
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->commands;
|
|
|
|
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->commands, ecs_cmd_t);
|
|
|
|
ecs_vec_clear(&commands);
|
|
flecs_stack_reset(&stage->defer_stack);
|
|
flecs_sparse_clear(&stage->cmd_entries);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
error:
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @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_alloc(
|
|
ecs_stage_t *stage)
|
|
{
|
|
ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->commands,
|
|
ecs_cmd_t);
|
|
ecs_os_zeromem(cmd);
|
|
return cmd;
|
|
}
|
|
|
|
static
|
|
ecs_cmd_t* flecs_cmd_new(
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t e,
|
|
bool is_delete,
|
|
bool can_batch)
|
|
{
|
|
if (e) {
|
|
ecs_vec_t *cmds = &stage->commands;
|
|
ecs_cmd_entry_t *entry = flecs_sparse_get(
|
|
&stage->cmd_entries, ecs_cmd_entry_t, e);
|
|
int32_t cur = ecs_vec_count(cmds);
|
|
if (entry) {
|
|
int32_t last = entry->last;
|
|
if (entry->last == -1) {
|
|
/* Entity was deleted, don't insert command */
|
|
return NULL;
|
|
}
|
|
|
|
if (can_batch) {
|
|
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 if (can_batch || is_delete) {
|
|
entry = flecs_sparse_ensure(&stage->cmd_entries,
|
|
ecs_cmd_entry_t, e);
|
|
entry->first = cur;
|
|
}
|
|
if (can_batch) {
|
|
entry->last = cur;
|
|
}
|
|
if (is_delete) {
|
|
/* Prevent insertion of more commands for entity */
|
|
entry->last = -1;
|
|
}
|
|
}
|
|
|
|
return flecs_cmd_alloc(stage);
|
|
}
|
|
|
|
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_suspend) return false;
|
|
return (++ stage->defer) == 1;
|
|
}
|
|
|
|
bool flecs_defer_cmd(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage)
|
|
{
|
|
(void)world;
|
|
|
|
/* If deferring is suspended, do operation as usual */
|
|
if (stage->defer_suspend) return false;
|
|
|
|
if (stage->defer) {
|
|
/* If deferring is enabled, defer operation */
|
|
return true;
|
|
}
|
|
|
|
/* Deferring is disabled, defer while operation is executed */
|
|
stage->defer ++;
|
|
return false;
|
|
}
|
|
|
|
bool flecs_defer_modified(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_id_t id)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false);
|
|
if (cmd) {
|
|
cmd->kind = EcsOpModified;
|
|
cmd->id = id;
|
|
cmd->entity = entity;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool flecs_defer_clone(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_entity_t src,
|
|
bool clone_value)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false);
|
|
if (cmd) {
|
|
cmd->kind = EcsOpClone;
|
|
cmd->id = src;
|
|
cmd->entity = entity;
|
|
cmd->is._1.clone_value = clone_value;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool flecs_defer_delete(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, true, false);
|
|
if (cmd) {
|
|
cmd->kind = EcsOpDelete;
|
|
cmd->entity = entity;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool flecs_defer_clear(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
|
if (cmd) {
|
|
cmd->kind = EcsOpClear;
|
|
cmd->entity = entity;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool flecs_defer_on_delete_action(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_id_t id,
|
|
ecs_entity_t action)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_cmd_t *cmd = flecs_cmd_alloc(stage);
|
|
cmd->kind = EcsOpOnDeleteAction;
|
|
cmd->id = id;
|
|
cmd->entity = action;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool flecs_defer_enable(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_id_t id,
|
|
bool enable)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false);
|
|
if (cmd) {
|
|
cmd->kind = enable ? EcsOpEnable : EcsOpDisable;
|
|
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(world, 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_alloc(stage);
|
|
if (cmd) {
|
|
cmd->kind = EcsOpBulkNew;
|
|
cmd->id = id;
|
|
cmd->is._n.entities = ids;
|
|
cmd->is._n.count = count;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool flecs_defer_add(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_id_t id)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
|
if (cmd) {
|
|
cmd->kind = EcsOpAdd;
|
|
cmd->id = id;
|
|
cmd->entity = entity;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool flecs_defer_remove(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_entity_t entity,
|
|
ecs_id_t id)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
|
if (cmd) {
|
|
cmd->kind = EcsOpRemove;
|
|
cmd->id = id;
|
|
cmd->entity = entity;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool 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,
|
|
void **value_out,
|
|
bool emplace)
|
|
{
|
|
if (flecs_defer_cmd(world, stage)) {
|
|
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
|
if (!cmd) {
|
|
if (value_out) {
|
|
/* Entity is deleted by a previous command, but we still need to
|
|
* return a temporary storage to the application. */
|
|
cmd_kind = EcsOpSkip;
|
|
} else {
|
|
/* No value needs to be returned, we can drop the command */
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
ecs_stack_t *stack = &stage->defer_stack;
|
|
void *op_value = flecs_stack_alloc(stack, size, ti->alignment);
|
|
|
|
if (!value && !emplace) {
|
|
/* Const cast is safe, value will only be moved when this is an
|
|
* emplace op */
|
|
value = (void*)ecs_get_id(world, entity, id);
|
|
}
|
|
|
|
if (value) {
|
|
if (emplace) {
|
|
ecs_move_t move;
|
|
if ((move = ti->hooks.move_ctor)) {
|
|
move(op_value, value, 1, ti);
|
|
} else {
|
|
ecs_os_memcpy(op_value, value, size);
|
|
}
|
|
} else {
|
|
ecs_copy_t copy;
|
|
if ((copy = ti->hooks.copy_ctor)) {
|
|
copy(op_value, value, 1, ti);
|
|
} else {
|
|
ecs_os_memcpy(op_value, value, size);
|
|
}
|
|
}
|
|
} else if (!emplace) {
|
|
ecs_xtor_t ctor;
|
|
if ((ctor = ti->hooks.ctor)) {
|
|
ctor(op_value, 1, ti);
|
|
}
|
|
}
|
|
|
|
if (value_out) {
|
|
*value_out = op_value;
|
|
}
|
|
|
|
if (!cmd) {
|
|
/* If op is NULL, entity was already deleted. Check if we need to
|
|
* insert an operation 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 an operation when the entity was
|
|
* already deleted. */
|
|
return true;
|
|
}
|
|
cmd = flecs_cmd_alloc(stage);
|
|
}
|
|
|
|
cmd->kind = cmd_kind;
|
|
cmd->id = id;
|
|
cmd->idr = idr;
|
|
cmd->entity = entity;
|
|
cmd->is._1.size = size;
|
|
cmd->is._1.value = op_value;
|
|
|
|
return true;
|
|
}
|
|
error:
|
|
return false;
|
|
}
|
|
|
|
void flecs_stage_merge_post_frame(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage)
|
|
{
|
|
/* Execute post frame actions */
|
|
ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, {
|
|
action->action(world, action->ctx);
|
|
});
|
|
|
|
ecs_vector_free(stage->post_frame_actions);
|
|
stage->post_frame_actions = NULL;
|
|
}
|
|
|
|
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->defer_stack);
|
|
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_CHUNK_SIZE);
|
|
|
|
ecs_vec_init_t(&stage->allocator, &stage->commands, ecs_cmd_t, 0);
|
|
flecs_sparse_init(&stage->cmd_entries, &stage->allocator,
|
|
&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t);
|
|
}
|
|
|
|
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);
|
|
|
|
/* Make sure stage has no unmerged data */
|
|
ecs_assert(ecs_vec_count(&stage->commands) == 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_poly_fini(stage, ecs_stage_t);
|
|
|
|
flecs_sparse_fini(&stage->cmd_entries);
|
|
|
|
ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t);
|
|
flecs_stack_fini(&stage->defer_stack);
|
|
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;
|
|
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;
|
|
}
|
|
} 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_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 (count > 1) {
|
|
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 (((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 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_CHUNK_SIZE);
|
|
flecs_sparse_init(&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(
|
|
&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(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(&a->sizes,
|
|
ecs_block_allocator_t, (uint32_t)hash);
|
|
|
|
if (!result) {
|
|
result = flecs_sparse_ensure_fast(&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/vector.c
|
|
* @brief Vector datastructure.
|
|
*
|
|
* This is an implementation of a simple vector type. The vector is allocated in
|
|
* a single block of memory, with the element count, and allocated number of
|
|
* elements encoded in the block. As this vector is used for user-types it has
|
|
* been designed to support alignments higher than 8 bytes. This makes the size
|
|
* of the vector header variable in size. To reduce the overhead associated with
|
|
* retrieving or computing this size, the functions are wrapped in macro calls
|
|
* that compute the header size at compile time.
|
|
*
|
|
* The API provides a number of _t macros, which accept a size and alignment.
|
|
* These macros are used when no compile-time type is available.
|
|
*
|
|
* The vector guarantees contiguous access to its elements. When an element is
|
|
* removed from the vector, the last element is copied to the removed element.
|
|
*
|
|
* The API requires passing in the type of the vector. This type is used to test
|
|
* whether the size of the provided type equals the size of the type with which
|
|
* the vector was created. In release mode this check is not performed.
|
|
*
|
|
* When elements are added to the vector, it will automatically resize to the
|
|
* next power of two. This can change the pointer of the vector, which is why
|
|
* operations that can increase the vector size, accept a double pointer to the
|
|
* vector.
|
|
*/
|
|
|
|
|
|
struct ecs_vector_t {
|
|
int32_t count;
|
|
int32_t size;
|
|
|
|
#ifndef FLECS_NDEBUG
|
|
int64_t elem_size; /* Used in debug mode to validate size */
|
|
#endif
|
|
};
|
|
|
|
/** Resize the vector buffer */
|
|
static
|
|
ecs_vector_t* flecs_vector_resize(
|
|
ecs_vector_t *vector,
|
|
int16_t offset,
|
|
int32_t size)
|
|
{
|
|
ecs_vector_t *result = ecs_os_realloc(vector, offset + size);
|
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0);
|
|
return result;
|
|
}
|
|
|
|
/* -- Public functions -- */
|
|
|
|
ecs_vector_t* _ecs_vector_new(
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t elem_count)
|
|
{
|
|
ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_vector_t *result =
|
|
ecs_os_malloc(offset + elem_size * elem_count);
|
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
|
|
|
|
result->count = 0;
|
|
result->size = elem_count;
|
|
#ifndef FLECS_NDEBUG
|
|
result->elem_size = elem_size;
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
ecs_vector_t* _ecs_vector_from_array(
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t elem_count,
|
|
void *array)
|
|
{
|
|
ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_vector_t *result =
|
|
ecs_os_malloc(offset + elem_size * elem_count);
|
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
|
|
|
|
ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count);
|
|
|
|
result->count = elem_count;
|
|
result->size = elem_count;
|
|
#ifndef FLECS_NDEBUG
|
|
result->elem_size = elem_size;
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
void ecs_vector_free(
|
|
ecs_vector_t *vector)
|
|
{
|
|
ecs_os_free(vector);
|
|
}
|
|
|
|
void ecs_vector_clear(
|
|
ecs_vector_t *vector)
|
|
{
|
|
if (vector) {
|
|
vector->count = 0;
|
|
}
|
|
}
|
|
|
|
void _ecs_vector_zero(
|
|
ecs_vector_t *vector,
|
|
ecs_size_t elem_size,
|
|
int16_t offset)
|
|
{
|
|
void *array = ECS_OFFSET(vector, offset);
|
|
ecs_os_memset(array, 0, elem_size * vector->count);
|
|
}
|
|
|
|
void ecs_vector_assert_size(
|
|
ecs_vector_t *vector,
|
|
ecs_size_t elem_size)
|
|
{
|
|
(void)elem_size;
|
|
|
|
if (vector) {
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
}
|
|
|
|
void* _ecs_vector_addn(
|
|
ecs_vector_t **array_inout,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t elem_count)
|
|
{
|
|
ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (elem_count == 1) {
|
|
return _ecs_vector_add(array_inout, elem_size, offset);
|
|
}
|
|
|
|
ecs_vector_t *vector = *array_inout;
|
|
if (!vector) {
|
|
vector = _ecs_vector_new(elem_size, offset, 1);
|
|
*array_inout = vector;
|
|
}
|
|
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t max_count = vector->size;
|
|
int32_t old_count = vector->count;
|
|
int32_t new_count = old_count + elem_count;
|
|
|
|
if ((new_count - 1) >= max_count) {
|
|
if (!max_count) {
|
|
max_count = elem_count;
|
|
} else {
|
|
while (max_count < new_count) {
|
|
max_count *= 2;
|
|
}
|
|
}
|
|
|
|
max_count = flecs_next_pow_of_2(max_count);
|
|
vector = flecs_vector_resize(vector, offset, max_count * elem_size);
|
|
vector->size = max_count;
|
|
*array_inout = vector;
|
|
}
|
|
|
|
vector->count = new_count;
|
|
|
|
return ECS_OFFSET(vector, offset + elem_size * old_count);
|
|
}
|
|
|
|
void* _ecs_vector_add(
|
|
ecs_vector_t **array_inout,
|
|
ecs_size_t elem_size,
|
|
int16_t offset)
|
|
{
|
|
ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_vector_t *vector = *array_inout;
|
|
int32_t count, size;
|
|
|
|
if (vector) {
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
count = vector->count;
|
|
size = vector->size;
|
|
|
|
if (count >= size) {
|
|
size *= 2;
|
|
if (!size) {
|
|
size = 2;
|
|
}
|
|
|
|
size = flecs_next_pow_of_2(size);
|
|
vector = flecs_vector_resize(vector, offset, size * elem_size);
|
|
*array_inout = vector;
|
|
vector->size = size;
|
|
}
|
|
|
|
vector->count = count + 1;
|
|
return ECS_OFFSET(vector, offset + elem_size * count);
|
|
}
|
|
|
|
vector = _ecs_vector_new(elem_size, offset, 2);
|
|
*array_inout = vector;
|
|
vector->count = 1;
|
|
vector->size = 2;
|
|
return ECS_OFFSET(vector, offset);
|
|
}
|
|
|
|
void* _ecs_vector_insert_at(
|
|
ecs_vector_t **vec,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t index)
|
|
{
|
|
ecs_assert(vec != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
int32_t count = vec[0]->count;
|
|
if (index == count) {
|
|
return _ecs_vector_add(vec, elem_size, offset);
|
|
}
|
|
ecs_assert(index < count, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
_ecs_vector_add(vec, elem_size, offset);
|
|
void *start = _ecs_vector_get(*vec, elem_size, offset, index);
|
|
if (index < count) {
|
|
ecs_os_memmove(ECS_OFFSET(start, elem_size), start,
|
|
(count - index) * elem_size);
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
int32_t _ecs_vector_move_index(
|
|
ecs_vector_t **dst,
|
|
ecs_vector_t *src,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t index)
|
|
{
|
|
if (dst && *dst) {
|
|
ecs_dbg_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
ecs_dbg_assert(src->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
void *dst_elem = _ecs_vector_add(dst, elem_size, offset);
|
|
void *src_elem = _ecs_vector_get(src, elem_size, offset, index);
|
|
|
|
ecs_os_memcpy(dst_elem, src_elem, elem_size);
|
|
return _ecs_vector_remove(src, elem_size, offset, index);
|
|
}
|
|
|
|
void ecs_vector_remove_last(
|
|
ecs_vector_t *vector)
|
|
{
|
|
if (vector && vector->count) vector->count --;
|
|
}
|
|
|
|
bool _ecs_vector_pop(
|
|
ecs_vector_t *vector,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
void *value)
|
|
{
|
|
if (!vector) {
|
|
return false;
|
|
}
|
|
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t count = vector->count;
|
|
if (!count) {
|
|
return false;
|
|
}
|
|
|
|
void *elem = ECS_OFFSET(vector, offset + (count - 1) * elem_size);
|
|
|
|
if (value) {
|
|
ecs_os_memcpy(value, elem, elem_size);
|
|
}
|
|
|
|
ecs_vector_remove_last(vector);
|
|
|
|
return true;
|
|
}
|
|
|
|
int32_t _ecs_vector_remove(
|
|
ecs_vector_t *vector,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t index)
|
|
{
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t count = vector->count;
|
|
void *buffer = ECS_OFFSET(vector, offset);
|
|
void *elem = ECS_OFFSET(buffer, index * elem_size);
|
|
|
|
ecs_assert(index < count, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
count --;
|
|
if (index != count) {
|
|
void *last_elem = ECS_OFFSET(buffer, elem_size * count);
|
|
ecs_os_memcpy(elem, last_elem, elem_size);
|
|
}
|
|
|
|
vector->count = count;
|
|
|
|
return count;
|
|
}
|
|
|
|
void _ecs_vector_reclaim(
|
|
ecs_vector_t **array_inout,
|
|
ecs_size_t elem_size,
|
|
int16_t offset)
|
|
{
|
|
ecs_vector_t *vector = *array_inout;
|
|
if (!vector) {
|
|
return;
|
|
}
|
|
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t size = vector->size;
|
|
int32_t count = vector->count;
|
|
|
|
if (count < size) {
|
|
if (count) {
|
|
size = count;
|
|
vector = flecs_vector_resize(vector, offset, size * elem_size);
|
|
vector->size = size;
|
|
*array_inout = vector;
|
|
} else {
|
|
ecs_vector_free(vector);
|
|
*array_inout = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t ecs_vector_count(
|
|
const ecs_vector_t *vector)
|
|
{
|
|
if (!vector) {
|
|
return 0;
|
|
}
|
|
return vector->count;
|
|
}
|
|
|
|
int32_t ecs_vector_size(
|
|
const ecs_vector_t *vector)
|
|
{
|
|
if (!vector) {
|
|
return 0;
|
|
}
|
|
return vector->size;
|
|
}
|
|
|
|
int32_t _ecs_vector_set_size(
|
|
ecs_vector_t **array_inout,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t elem_count)
|
|
{
|
|
ecs_vector_t *vector = *array_inout;
|
|
|
|
if (!vector) {
|
|
*array_inout = _ecs_vector_new(elem_size, offset, elem_count);
|
|
return elem_count;
|
|
} else {
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t result = vector->size;
|
|
|
|
if (elem_count < vector->count) {
|
|
elem_count = vector->count;
|
|
}
|
|
|
|
if (result < elem_count) {
|
|
elem_count = flecs_next_pow_of_2(elem_count);
|
|
vector = flecs_vector_resize(vector, offset, elem_count * elem_size);
|
|
vector->size = elem_count;
|
|
*array_inout = vector;
|
|
result = elem_count;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
int32_t _ecs_vector_grow(
|
|
ecs_vector_t **array_inout,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t elem_count)
|
|
{
|
|
int32_t current = ecs_vector_count(*array_inout);
|
|
return _ecs_vector_set_size(array_inout, elem_size, offset, current + elem_count);
|
|
}
|
|
|
|
int32_t _ecs_vector_set_count(
|
|
ecs_vector_t **array_inout,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t elem_count)
|
|
{
|
|
if (!*array_inout) {
|
|
*array_inout = _ecs_vector_new(elem_size, offset, elem_count);
|
|
}
|
|
|
|
ecs_dbg_assert((*array_inout)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
(*array_inout)->count = elem_count;
|
|
ecs_size_t size = _ecs_vector_set_size(array_inout, elem_size, offset, elem_count);
|
|
return size;
|
|
}
|
|
|
|
void* _ecs_vector_first(
|
|
const ecs_vector_t *vector,
|
|
ecs_size_t elem_size,
|
|
int16_t offset)
|
|
{
|
|
(void)elem_size;
|
|
|
|
ecs_dbg_assert(!vector || vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
if (vector && vector->size) {
|
|
return ECS_OFFSET(vector, offset);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void* _ecs_vector_get(
|
|
const ecs_vector_t *vector,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t index)
|
|
{
|
|
ecs_assert(vector != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(index < vector->count, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
return ECS_OFFSET(vector, offset + elem_size * index);
|
|
}
|
|
|
|
void* _ecs_vector_last(
|
|
const ecs_vector_t *vector,
|
|
ecs_size_t elem_size,
|
|
int16_t offset)
|
|
{
|
|
if (vector) {
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
int32_t count = vector->count;
|
|
if (!count) {
|
|
return NULL;
|
|
} else {
|
|
return ECS_OFFSET(vector, offset + elem_size * (count - 1));
|
|
}
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int32_t _ecs_vector_set_min_size(
|
|
ecs_vector_t **vector_inout,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t elem_count)
|
|
{
|
|
if (!*vector_inout || (*vector_inout)->size < elem_count) {
|
|
return _ecs_vector_set_size(vector_inout, elem_size, offset, elem_count);
|
|
} else {
|
|
return (*vector_inout)->size;
|
|
}
|
|
}
|
|
|
|
int32_t _ecs_vector_set_min_count(
|
|
ecs_vector_t **vector_inout,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
int32_t elem_count)
|
|
{
|
|
_ecs_vector_set_min_size(vector_inout, elem_size, offset, elem_count);
|
|
|
|
ecs_vector_t *v = *vector_inout;
|
|
if (v && v->count < elem_count) {
|
|
v->count = elem_count;
|
|
}
|
|
|
|
return v->count;
|
|
}
|
|
|
|
void _ecs_vector_sort(
|
|
ecs_vector_t *vector,
|
|
ecs_size_t elem_size,
|
|
int16_t offset,
|
|
ecs_comparator_t compare_action)
|
|
{
|
|
if (!vector) {
|
|
return;
|
|
}
|
|
|
|
ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t count = vector->count;
|
|
void *buffer = ECS_OFFSET(vector, offset);
|
|
|
|
if (count > 1) {
|
|
qsort(buffer, (size_t)count, (size_t)elem_size, compare_action);
|
|
}
|
|
}
|
|
|
|
ecs_vector_t* _ecs_vector_copy(
|
|
const ecs_vector_t *src,
|
|
ecs_size_t elem_size,
|
|
int16_t offset)
|
|
{
|
|
if (!src) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_vector_t *dst = _ecs_vector_new(elem_size, offset, src->size);
|
|
ecs_os_memcpy(dst, src, offset + elem_size * src->count);
|
|
return dst;
|
|
}
|
|
|
|
/**
|
|
* @file datastructures/sparse.c
|
|
* @brief Sparse set data structure.
|
|
*
|
|
* The sparse set data structure allows for fast lookups by 64bit key with
|
|
* variable payload size. Lookup operations are guaranteed to be O(1), in
|
|
* contrast to ecs_map_t which has O(1) lookup time on average. At a high level
|
|
* the sparse set works with two arrays:
|
|
*
|
|
* dense [ - ][ 3 ][ 1 ][ 4 ]
|
|
* sparse [ ][ 2 ][ ][ 1 ][ 3 ]
|
|
*
|
|
* Indices in the dense array point to the sparse array, and vice versa. The
|
|
* dense array is guaranteed to contain no holes. By iterating the dense array,
|
|
* all populated elements in the sparse set can be found without having to skip
|
|
* empty elements.
|
|
*
|
|
* The sparse array is paged, which means that it is split up in memory blocks
|
|
* (pages, not to be confused with OS pages) of equal size. The page size is
|
|
* set to 4096 elements. Paging prevents the sparse array from having to grow
|
|
* to N elements, where N is the largest key used in the set. It also ensures
|
|
* that payload pointers are stable.
|
|
*
|
|
* The sparse set recycles deleted keys. It does this by moving not alive keys
|
|
* to the end of the dense array, and using a count member that indicates the
|
|
* last alive member in the dense array. This approach makes it possible to
|
|
* recycle keys in bulk, by increasing the alive count.
|
|
*
|
|
* When a key is deleted, the sparse set increases its generation count. This
|
|
* generation count is used to test whether a key passed to the sparse set is
|
|
* still valid, or whether it has been deleted.
|
|
*
|
|
* The sparse set is used in a number of places, like for retrieving entity
|
|
* administration, tables and allocators.
|
|
*/
|
|
|
|
|
|
/** Compute the chunk index from an id by stripping the first 12 bits */
|
|
#define CHUNK(index) ((int32_t)((uint32_t)index >> 12))
|
|
|
|
/** This computes the offset of an index inside a chunk */
|
|
#define OFFSET(index) ((int32_t)index & 0xFFF)
|
|
|
|
/* Utility to get a pointer to the payload */
|
|
#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset))
|
|
|
|
typedef struct chunk_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. */
|
|
} chunk_t;
|
|
|
|
static
|
|
chunk_t* flecs_sparse_chunk_new(
|
|
ecs_sparse_t *sparse,
|
|
int32_t chunk_index)
|
|
{
|
|
int32_t count = ecs_vector_count(sparse->chunks);
|
|
chunk_t *chunks;
|
|
|
|
if (count <= chunk_index) {
|
|
ecs_vector_set_count(&sparse->chunks, chunk_t, chunk_index + 1);
|
|
chunks = ecs_vector_first(sparse->chunks, chunk_t);
|
|
ecs_os_memset(&chunks[count], 0, (1 + chunk_index - count) * ECS_SIZEOF(chunk_t));
|
|
} else {
|
|
chunks = ecs_vector_first(sparse->chunks, chunk_t);
|
|
}
|
|
|
|
ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
chunk_t *result = &chunks[chunk_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. */
|
|
if (sparse->chunk_allocator) {
|
|
result->sparse = flecs_bcalloc(sparse->chunk_allocator);
|
|
} else {
|
|
result->sparse = ecs_os_calloc_n(int32_t, FLECS_SPARSE_CHUNK_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. */
|
|
if (sparse->allocator) {
|
|
result->data = flecs_calloc(sparse->allocator,
|
|
sparse->size * FLECS_SPARSE_CHUNK_SIZE);
|
|
} else {
|
|
result->data = ecs_os_calloc(sparse->size * FLECS_SPARSE_CHUNK_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_chunk_free(
|
|
ecs_sparse_t *sparse,
|
|
chunk_t *chunk)
|
|
{
|
|
if (sparse->chunk_allocator) {
|
|
flecs_bfree(sparse->chunk_allocator, chunk->sparse);
|
|
} else {
|
|
ecs_os_free(chunk->sparse);
|
|
}
|
|
if (sparse->allocator) {
|
|
flecs_free(sparse->allocator, sparse->size * FLECS_SPARSE_CHUNK_SIZE,
|
|
chunk->data);
|
|
} else {
|
|
ecs_os_free(chunk->data);
|
|
}
|
|
}
|
|
|
|
static
|
|
chunk_t* flecs_sparse_get_chunk(
|
|
const ecs_sparse_t *sparse,
|
|
int32_t chunk_index)
|
|
{
|
|
if (!sparse->chunks) {
|
|
return NULL;
|
|
}
|
|
if (chunk_index >= ecs_vector_count(sparse->chunks)) {
|
|
return NULL;
|
|
}
|
|
|
|
/* If chunk_index is below zero, application used an invalid entity id */
|
|
ecs_assert(chunk_index >= 0, ECS_INVALID_PARAMETER, NULL);
|
|
chunk_t *result = ecs_vector_get(sparse->chunks, chunk_t, chunk_index);
|
|
if (result && !result->sparse) {
|
|
return NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static
|
|
chunk_t* flecs_sparse_get_or_create_chunk(
|
|
ecs_sparse_t *sparse,
|
|
int32_t chunk_index)
|
|
{
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, chunk_index);
|
|
if (chunk) {
|
|
return chunk;
|
|
}
|
|
|
|
return flecs_sparse_chunk_new(sparse, chunk_index);
|
|
}
|
|
|
|
static
|
|
void flecs_sparse_grow_dense(
|
|
ecs_sparse_t *sparse)
|
|
{
|
|
ecs_vector_add(&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(
|
|
chunk_t * chunk,
|
|
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 .*/
|
|
chunk->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[0]);
|
|
}
|
|
|
|
static
|
|
uint64_t flecs_sparse_get_id(
|
|
const ecs_sparse_t *sparse)
|
|
{
|
|
return sparse->max_id[0];
|
|
}
|
|
|
|
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[0] = 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);
|
|
|
|
chunk_t *chunk = flecs_sparse_get_or_create_chunk(sparse, CHUNK(index));
|
|
ecs_assert(chunk->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t);
|
|
flecs_sparse_assign_index(chunk, dense_array, index, dense);
|
|
|
|
return index;
|
|
}
|
|
|
|
/* Create new id */
|
|
static
|
|
uint64_t flecs_sparse_new_index(
|
|
ecs_sparse_t *sparse)
|
|
{
|
|
ecs_vector_t *dense = sparse->dense;
|
|
int32_t dense_count = ecs_vector_count(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_vector_first(dense, uint64_t);
|
|
return dense_array[count];
|
|
} else {
|
|
return flecs_sparse_create_id(sparse, count);
|
|
}
|
|
}
|
|
|
|
/* Try obtaining a value from the sparse set, don't care about whether the
|
|
* provided index matches the current generation count. */
|
|
static
|
|
void* flecs_sparse_try_sparse_any(
|
|
const ecs_sparse_t *sparse,
|
|
uint64_t index)
|
|
{
|
|
flecs_sparse_strip_generation(&index);
|
|
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index));
|
|
if (!chunk) {
|
|
return NULL;
|
|
}
|
|
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->sparse[offset];
|
|
bool in_use = dense && (dense < sparse->count);
|
|
if (!in_use) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL);
|
|
return DATA(chunk->data, sparse->size, offset);
|
|
}
|
|
|
|
/* Try obtaining a value from the sparse set, make sure it's alive. */
|
|
static
|
|
void* flecs_sparse_try_sparse(
|
|
const ecs_sparse_t *sparse,
|
|
uint64_t index)
|
|
{
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index));
|
|
if (!chunk) {
|
|
return NULL;
|
|
}
|
|
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->sparse[offset];
|
|
bool in_use = dense && (dense < sparse->count);
|
|
if (!in_use) {
|
|
return NULL;
|
|
}
|
|
|
|
uint64_t gen = flecs_sparse_strip_generation(&index);
|
|
uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t);
|
|
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK;
|
|
|
|
if (cur_gen != gen) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL);
|
|
return DATA(chunk->data, sparse->size, offset);
|
|
}
|
|
|
|
/* 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);
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index));
|
|
if (!chunk) {
|
|
return NULL;
|
|
}
|
|
int32_t offset = OFFSET(index);
|
|
|
|
ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL);
|
|
(void)dense;
|
|
|
|
return DATA(chunk->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,
|
|
chunk_t * chunk_a,
|
|
int32_t a,
|
|
int32_t b)
|
|
{
|
|
ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL);
|
|
uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t);
|
|
uint64_t index_a = dense_array[a];
|
|
uint64_t index_b = dense_array[b];
|
|
|
|
chunk_t *chunk_b = flecs_sparse_get_or_create_chunk(sparse, CHUNK(index_b));
|
|
flecs_sparse_assign_index(chunk_a, dense_array, index_a, b);
|
|
flecs_sparse_assign_index(chunk_b, dense_array, index_b, a);
|
|
}
|
|
|
|
void _flecs_sparse_init(
|
|
ecs_sparse_t *result,
|
|
struct ecs_allocator_t *allocator,
|
|
ecs_block_allocator_t *chunk_allocator,
|
|
ecs_size_t size)
|
|
{
|
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
|
|
result->size = size;
|
|
result->max_id_local = UINT64_MAX;
|
|
result->max_id = &result->max_id_local;
|
|
result->allocator = allocator;
|
|
result->chunk_allocator = chunk_allocator;
|
|
|
|
/* 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. */
|
|
uint64_t *first = ecs_vector_add(&result->dense, uint64_t);
|
|
*first = 0;
|
|
|
|
result->count = 1;
|
|
}
|
|
|
|
ecs_sparse_t* _flecs_sparse_new(
|
|
struct ecs_allocator_t *allocator,
|
|
ecs_block_allocator_t *chunk_allocator,
|
|
ecs_size_t size)
|
|
{
|
|
ecs_sparse_t *result = ecs_os_calloc_t(ecs_sparse_t);
|
|
|
|
_flecs_sparse_init(result, allocator, chunk_allocator, size);
|
|
|
|
return result;
|
|
}
|
|
|
|
void flecs_sparse_set_id_source(
|
|
ecs_sparse_t * sparse,
|
|
uint64_t * id_source)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
sparse->max_id = id_source;
|
|
}
|
|
|
|
void flecs_sparse_clear(
|
|
ecs_sparse_t *sparse)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
int32_t i, count = ecs_vector_count(sparse->chunks);
|
|
chunk_t *chunks = ecs_vector_first(sparse->chunks, chunk_t);
|
|
for (i = 0; i < count; i ++) {
|
|
int32_t *indices = chunks[i].sparse;
|
|
if (indices) {
|
|
ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_CHUNK_SIZE);
|
|
}
|
|
}
|
|
|
|
ecs_vector_set_count(&sparse->dense, uint64_t, 1);
|
|
|
|
sparse->count = 1;
|
|
sparse->max_id_local = 0;
|
|
}
|
|
|
|
void _flecs_sparse_fini(
|
|
ecs_sparse_t *sparse)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t i, count = ecs_vector_count(sparse->chunks);
|
|
chunk_t *chunks = ecs_vector_first(sparse->chunks, chunk_t);
|
|
for (i = 0; i < count; i ++) {
|
|
flecs_sparse_chunk_free(sparse, &chunks[i]);
|
|
}
|
|
|
|
ecs_vector_free(sparse->chunks);
|
|
ecs_vector_free(sparse->dense);
|
|
|
|
sparse->chunks = NULL;
|
|
sparse->dense = NULL;
|
|
}
|
|
|
|
void flecs_sparse_free(
|
|
ecs_sparse_t *sparse)
|
|
{
|
|
if (sparse) {
|
|
_flecs_sparse_fini(sparse);
|
|
ecs_os_free(sparse);
|
|
}
|
|
}
|
|
|
|
uint64_t flecs_sparse_new_id(
|
|
ecs_sparse_t *sparse)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
return flecs_sparse_new_index(sparse);
|
|
}
|
|
|
|
const uint64_t* flecs_sparse_new_ids(
|
|
ecs_sparse_t *sparse,
|
|
int32_t new_count)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
int32_t dense_count = ecs_vector_count(sparse->dense);
|
|
int32_t count = sparse->count;
|
|
int32_t recyclable = dense_count - count;
|
|
int32_t i, to_create = new_count - recyclable;
|
|
|
|
if (to_create > 0) {
|
|
flecs_sparse_set_size(sparse, dense_count + to_create);
|
|
for (i = 0; i < to_create; i ++) {
|
|
flecs_sparse_create_id(sparse, count + recyclable + i);
|
|
}
|
|
}
|
|
|
|
sparse->count += new_count;
|
|
|
|
return ecs_vector_get(sparse->dense, uint64_t, count);
|
|
}
|
|
|
|
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);
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index));
|
|
ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
return DATA(chunk->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_vector_first(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_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL);
|
|
(void)size;
|
|
|
|
uint64_t gen = flecs_sparse_strip_generation(&index);
|
|
chunk_t *chunk = flecs_sparse_get_or_create_chunk(sparse, CHUNK(index));
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->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 the next unused element in the array, simply increase
|
|
* the count to make it part of the alive set. */
|
|
sparse->count ++;
|
|
} else if (dense > count) {
|
|
/* If dense is not alive, swap it with the first unused element. */
|
|
flecs_sparse_swap_dense(sparse, chunk, 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. */
|
|
ecs_vector_t *dense_vector = sparse->dense;
|
|
uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t);
|
|
ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL);
|
|
(void)dense_vector;
|
|
(void)dense_array;
|
|
} else {
|
|
/* Element is not paired yet. Must add a new element to dense array */
|
|
flecs_sparse_grow_dense(sparse);
|
|
|
|
ecs_vector_t *dense_vector = sparse->dense;
|
|
uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t);
|
|
int32_t dense_count = ecs_vector_count(dense_vector) - 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];
|
|
chunk_t *unused_chunk = flecs_sparse_get_or_create_chunk(sparse, CHUNK(unused));
|
|
flecs_sparse_assign_index(unused_chunk, dense_array, unused, dense_count);
|
|
}
|
|
|
|
flecs_sparse_assign_index(chunk, dense_array, index, count);
|
|
dense_array[count] |= gen;
|
|
}
|
|
|
|
return DATA(chunk->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_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL);
|
|
(void)size;
|
|
|
|
uint32_t index = (uint32_t)index_long;
|
|
chunk_t *chunk = flecs_sparse_get_or_create_chunk(sparse, CHUNK(index));
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->sparse[offset];
|
|
int32_t count = sparse->count;
|
|
|
|
if (!dense) {
|
|
/* Element is not paired yet. Must add a new element to dense array */
|
|
ecs_vector_t *dense_vector = sparse->dense;
|
|
sparse->count = count + 1;
|
|
if (count == ecs_vector_count(dense_vector)) {
|
|
flecs_sparse_grow_dense(sparse);
|
|
dense_vector = sparse->dense;
|
|
}
|
|
|
|
uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t);
|
|
flecs_sparse_assign_index(chunk, dense_array, index, count);
|
|
}
|
|
|
|
return DATA(chunk->data, sparse->size, offset);
|
|
}
|
|
|
|
void* _flecs_sparse_set(
|
|
ecs_sparse_t * sparse,
|
|
ecs_size_t elem_size,
|
|
uint64_t index,
|
|
void* value)
|
|
{
|
|
void *ptr = _flecs_sparse_ensure(sparse, elem_size, index);
|
|
ecs_os_memcpy(ptr, value, elem_size);
|
|
return ptr;
|
|
}
|
|
|
|
void* _flecs_sparse_remove_get(
|
|
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;
|
|
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index));
|
|
if (!chunk) {
|
|
return NULL;
|
|
}
|
|
|
|
uint64_t gen = flecs_sparse_strip_generation(&index);
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->sparse[offset];
|
|
|
|
if (dense) {
|
|
uint64_t *dense_array = ecs_vector_first(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 NULL;
|
|
}
|
|
|
|
/* 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, chunk, dense, count - 1);
|
|
sparse->count --;
|
|
} else {
|
|
/* Element is not alive, nothing to be done */
|
|
return NULL;
|
|
}
|
|
|
|
/* Reset memory to zero on remove */
|
|
return DATA(chunk->data, sparse->size, offset);
|
|
} else {
|
|
/* Element is not paired and thus not alive, nothing to be done */
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void flecs_sparse_remove(
|
|
ecs_sparse_t *sparse,
|
|
uint64_t index)
|
|
{
|
|
void *ptr = _flecs_sparse_remove_get(sparse, 0, index);
|
|
if (ptr) {
|
|
ecs_os_memset(ptr, 0, sparse->size);
|
|
}
|
|
}
|
|
|
|
void flecs_sparse_set_generation(
|
|
ecs_sparse_t *sparse,
|
|
uint64_t index)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
chunk_t *chunk = flecs_sparse_get_or_create_chunk(sparse, CHUNK(index));
|
|
|
|
uint64_t index_w_gen = index;
|
|
flecs_sparse_strip_generation(&index);
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->sparse[offset];
|
|
|
|
if (dense) {
|
|
/* Increase generation */
|
|
uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t);
|
|
dense_array[dense] = index_w_gen;
|
|
} else {
|
|
/* Element is not paired and thus not alive, nothing to be done */
|
|
}
|
|
}
|
|
|
|
bool flecs_sparse_exists(
|
|
const ecs_sparse_t *sparse,
|
|
uint64_t index)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index));
|
|
if (!chunk) {
|
|
return false;
|
|
}
|
|
|
|
flecs_sparse_strip_generation(&index);
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->sparse[offset];
|
|
|
|
return dense != 0;
|
|
}
|
|
|
|
bool flecs_sparse_is_valid(
|
|
const ecs_sparse_t *sparse,
|
|
uint64_t index)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index));
|
|
if (!chunk) {
|
|
return true; /* Doesn't exist yet, so is valid */
|
|
}
|
|
|
|
flecs_sparse_strip_generation(&index);
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->sparse[offset];
|
|
if (!dense) {
|
|
return true; /* Doesn't exist yet, so is valid */
|
|
}
|
|
|
|
/* If the id exists, it must be alive */
|
|
return dense < sparse->count;
|
|
}
|
|
|
|
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_vector_first(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)
|
|
{
|
|
return flecs_sparse_try_sparse(sparse, index) != NULL;
|
|
}
|
|
|
|
uint64_t flecs_sparse_get_alive(
|
|
const ecs_sparse_t *sparse,
|
|
uint64_t index)
|
|
{
|
|
chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index));
|
|
if (!chunk) {
|
|
return 0;
|
|
}
|
|
|
|
int32_t offset = OFFSET(index);
|
|
int32_t dense = chunk->sparse[offset];
|
|
uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t);
|
|
|
|
/* If dense is 0 (tombstone) this will return 0 */
|
|
return dense_array[dense];
|
|
}
|
|
|
|
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(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
|
(void)size;
|
|
return flecs_sparse_try_sparse(sparse, index);
|
|
}
|
|
|
|
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(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
|
(void)size;
|
|
return flecs_sparse_try_sparse_any(sparse, index);
|
|
}
|
|
|
|
int32_t flecs_sparse_count(
|
|
const ecs_sparse_t *sparse)
|
|
{
|
|
if (!sparse) {
|
|
return 0;
|
|
}
|
|
|
|
return sparse->count - 1;
|
|
}
|
|
|
|
int32_t flecs_sparse_not_alive_count(
|
|
const ecs_sparse_t *sparse)
|
|
{
|
|
if (!sparse) {
|
|
return 0;
|
|
}
|
|
|
|
return ecs_vector_count(sparse->dense) - sparse->count;
|
|
}
|
|
|
|
int32_t flecs_sparse_size(
|
|
const ecs_sparse_t *sparse)
|
|
{
|
|
if (!sparse) {
|
|
return 0;
|
|
}
|
|
|
|
return ecs_vector_count(sparse->dense) - 1;
|
|
}
|
|
|
|
const uint64_t* flecs_sparse_ids(
|
|
const ecs_sparse_t *sparse)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
return &(ecs_vector_first(sparse->dense, uint64_t)[1]);
|
|
}
|
|
|
|
void flecs_sparse_set_size(
|
|
ecs_sparse_t *sparse,
|
|
int32_t elem_count)
|
|
{
|
|
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_vector_set_size(&sparse->dense, uint64_t, elem_count);
|
|
}
|
|
|
|
static
|
|
void sparse_copy(
|
|
ecs_sparse_t * dst,
|
|
const ecs_sparse_t * src)
|
|
{
|
|
flecs_sparse_set_size(dst, flecs_sparse_size(src));
|
|
const uint64_t *indices = flecs_sparse_ids(src);
|
|
|
|
ecs_size_t size = src->size;
|
|
int32_t i, count = src->count;
|
|
|
|
for (i = 0; i < count - 1; i ++) {
|
|
uint64_t index = indices[i];
|
|
void *src_ptr = _flecs_sparse_get(src, size, index);
|
|
void *dst_ptr = _flecs_sparse_ensure(dst, size, index);
|
|
flecs_sparse_set_generation(dst, index);
|
|
ecs_os_memcpy(dst_ptr, src_ptr, size);
|
|
}
|
|
|
|
flecs_sparse_set_id(dst, flecs_sparse_get_id(src));
|
|
|
|
ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
ecs_sparse_t* flecs_sparse_copy(
|
|
const ecs_sparse_t *src)
|
|
{
|
|
if (!src) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_sparse_t *dst = _flecs_sparse_new(
|
|
src->allocator, src->chunk_allocator, src->size);
|
|
sparse_copy(dst, src);
|
|
|
|
return dst;
|
|
}
|
|
|
|
void flecs_sparse_restore(
|
|
ecs_sparse_t * dst,
|
|
const ecs_sparse_t * src)
|
|
{
|
|
ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
dst->count = 1;
|
|
if (src) {
|
|
sparse_copy(dst, src);
|
|
}
|
|
}
|
|
|
|
ecs_sparse_t* _ecs_sparse_new(
|
|
ecs_size_t elem_size)
|
|
{
|
|
return _flecs_sparse_new(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/switch_list.c
|
|
* @brief Interleaved linked list for storing mutually exclusive values.
|
|
*
|
|
* Datastructure 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 datastructure 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_map_get(&sw->hdrs, ecs_switch_header_t, value);
|
|
}
|
|
|
|
static
|
|
ecs_switch_header_t *flecs_switch_ensure_header(
|
|
ecs_switch_t *sw,
|
|
uint64_t value)
|
|
{
|
|
if (value == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_switch_header_t *node = flecs_switch_get_header(sw, value);
|
|
if (!node) {
|
|
node = ecs_map_ensure(&sw->hdrs, ecs_switch_header_t, value);
|
|
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, ecs_switch_header_t, allocator, 1);
|
|
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_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/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);
|
|
}
|
|
|
|
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_bdup(map->hashmap_allocator, map);
|
|
flecs_hashmap_copy(result, result);
|
|
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 = (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 = (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/hash.c
|
|
* @brief Functions for hasing byte arrays to 64bit value.
|
|
*/
|
|
|
|
|
|
#ifdef ECS_TARGET_GNU
|
|
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
|
#endif
|
|
|
|
/* See explanation below. The hashing function may read beyond the memory passed
|
|
* into the hashing function, but only at word boundaries. This should be safe,
|
|
* but trips up address sanitizers and valgrind.
|
|
* This ensures clean valgrind logs in debug mode & the best perf in release */
|
|
#if !defined(FLECS_NDEBUG) || defined(ADDRESS_SANITIZER)
|
|
#ifndef VALGRIND
|
|
#define VALGRIND
|
|
#endif
|
|
#endif
|
|
|
|
/*
|
|
-------------------------------------------------------------------------------
|
|
lookup3.c, by Bob Jenkins, May 2006, Public Domain.
|
|
http://burtleburtle.net/bob/c/lookup3.c
|
|
-------------------------------------------------------------------------------
|
|
*/
|
|
|
|
#ifdef ECS_TARGET_POSIX
|
|
#include <sys/param.h> /* attempt to define endianness */
|
|
#endif
|
|
#ifdef ECS_TARGET_LINUX
|
|
#include <endian.h> /* attempt to define endianness */
|
|
#endif
|
|
|
|
/*
|
|
* My best guess at if you are big-endian or little-endian. This may
|
|
* need adjustment.
|
|
*/
|
|
#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \
|
|
__BYTE_ORDER == __LITTLE_ENDIAN) || \
|
|
(defined(i386) || defined(__i386__) || defined(__i486__) || \
|
|
defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL))
|
|
# define HASH_LITTLE_ENDIAN 1
|
|
#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \
|
|
__BYTE_ORDER == __BIG_ENDIAN) || \
|
|
(defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel))
|
|
# define HASH_LITTLE_ENDIAN 0
|
|
#else
|
|
# define HASH_LITTLE_ENDIAN 0
|
|
#endif
|
|
|
|
#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
|
|
|
|
/*
|
|
-------------------------------------------------------------------------------
|
|
mix -- mix 3 32-bit values reversibly.
|
|
This is reversible, so any information in (a,b,c) before mix() is
|
|
still in (a,b,c) after mix().
|
|
If four pairs of (a,b,c) inputs are run through mix(), or through
|
|
mix() in reverse, there are at least 32 bits of the output that
|
|
are sometimes the same for one pair and different for another pair.
|
|
This was tested for:
|
|
* pairs that differed by one bit, by two bits, in any combination
|
|
of top bits of (a,b,c), or in any combination of bottom bits of
|
|
(a,b,c).
|
|
* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
|
|
the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
|
|
is commonly produced by subtraction) look like a single 1-bit
|
|
difference.
|
|
* the base values were pseudorandom, all zero but one bit set, or
|
|
all zero plus a counter that starts at zero.
|
|
Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
|
|
satisfy this are
|
|
4 6 8 16 19 4
|
|
9 15 3 18 27 15
|
|
14 9 3 7 17 3
|
|
Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
|
|
for "differ" defined as + with a one-bit base and a two-bit delta. I
|
|
used http://burtleburtle.net/bob/hash/avalanche.html to choose
|
|
the operations, constants, and arrangements of the variables.
|
|
This does not achieve avalanche. There are input bits of (a,b,c)
|
|
that fail to affect some output bits of (a,b,c), especially of a. The
|
|
most thoroughly mixed value is c, but it doesn't really even achieve
|
|
avalanche in c.
|
|
This allows some parallelism. Read-after-writes are good at doubling
|
|
the number of bits affected, so the goal of mixing pulls in the opposite
|
|
direction as the goal of parallelism. I did what I could. Rotates
|
|
seem to cost as much as shifts on every machine I could lay my hands
|
|
on, and rotates are much kinder to the top and bottom bits, so I used
|
|
rotates.
|
|
-------------------------------------------------------------------------------
|
|
*/
|
|
#define mix(a,b,c) \
|
|
{ \
|
|
a -= c; a ^= rot(c, 4); c += b; \
|
|
b -= a; b ^= rot(a, 6); a += c; \
|
|
c -= b; c ^= rot(b, 8); b += a; \
|
|
a -= c; a ^= rot(c,16); c += b; \
|
|
b -= a; b ^= rot(a,19); a += c; \
|
|
c -= b; c ^= rot(b, 4); b += a; \
|
|
}
|
|
|
|
/*
|
|
-------------------------------------------------------------------------------
|
|
final -- final mixing of 3 32-bit values (a,b,c) into c
|
|
Pairs of (a,b,c) values differing in only a few bits will usually
|
|
produce values of c that look totally different. This was tested for
|
|
* pairs that differed by one bit, by two bits, in any combination
|
|
of top bits of (a,b,c), or in any combination of bottom bits of
|
|
(a,b,c).
|
|
* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
|
|
the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
|
|
is commonly produced by subtraction) look like a single 1-bit
|
|
difference.
|
|
* the base values were pseudorandom, all zero but one bit set, or
|
|
all zero plus a counter that starts at zero.
|
|
These constants passed:
|
|
14 11 25 16 4 14 24
|
|
12 14 25 16 4 14 24
|
|
and these came close:
|
|
4 8 15 26 3 22 24
|
|
10 8 15 26 3 22 24
|
|
11 8 15 26 3 22 24
|
|
-------------------------------------------------------------------------------
|
|
*/
|
|
#define final(a,b,c) \
|
|
{ \
|
|
c ^= b; c -= rot(b,14); \
|
|
a ^= c; a -= rot(c,11); \
|
|
b ^= a; b -= rot(a,25); \
|
|
c ^= b; c -= rot(b,16); \
|
|
a ^= c; a -= rot(c,4); \
|
|
b ^= a; b -= rot(a,14); \
|
|
c ^= b; c -= rot(b,24); \
|
|
}
|
|
|
|
|
|
/*
|
|
* hashlittle2: return 2 32-bit hash values
|
|
*
|
|
* This is identical to hashlittle(), except it returns two 32-bit hash
|
|
* values instead of just one. This is good enough for hash table
|
|
* lookup with 2^^64 buckets, or if you want a second hash if you're not
|
|
* happy with the first, or if you want a probably-unique 64-bit ID for
|
|
* the key. *pc is better mixed than *pb, so use *pc first. If you want
|
|
* a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
|
|
*/
|
|
static
|
|
void hashlittle2(
|
|
const void *key, /* the key to hash */
|
|
size_t length, /* length of the key */
|
|
uint32_t *pc, /* IN: primary initval, OUT: primary hash */
|
|
uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */
|
|
{
|
|
uint32_t a,b,c; /* internal state */
|
|
union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
|
|
|
|
/* Set up the internal state */
|
|
a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc;
|
|
c += *pb;
|
|
|
|
u.ptr = key;
|
|
if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
|
|
const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
|
|
const uint8_t *k8;
|
|
(void)k8;
|
|
|
|
/*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
|
|
while (length > 12)
|
|
{
|
|
a += k[0];
|
|
b += k[1];
|
|
c += k[2];
|
|
mix(a,b,c);
|
|
length -= 12;
|
|
k += 3;
|
|
}
|
|
|
|
/*----------------------------- handle the last (probably partial) block */
|
|
/*
|
|
* "k[2]&0xffffff" actually reads beyond the end of the string, but
|
|
* then masks off the part it's not allowed to read. Because the
|
|
* string is aligned, the masked-off tail is in the same word as the
|
|
* rest of the string. Every machine with memory protection I've seen
|
|
* does it on word boundaries, so is OK with this. But VALGRIND will
|
|
* still catch it and complain. The masking trick does make the hash
|
|
* noticably faster for short strings (like English words).
|
|
*/
|
|
#ifndef VALGRIND
|
|
|
|
switch(length)
|
|
{
|
|
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
|
|
case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
|
|
case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
|
|
case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
|
|
case 8 : b+=k[1]; a+=k[0]; break;
|
|
case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
|
|
case 6 : b+=k[1]&0xffff; a+=k[0]; break;
|
|
case 5 : b+=k[1]&0xff; a+=k[0]; break;
|
|
case 4 : a+=k[0]; break;
|
|
case 3 : a+=k[0]&0xffffff; break;
|
|
case 2 : a+=k[0]&0xffff; break;
|
|
case 1 : a+=k[0]&0xff; break;
|
|
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
|
|
}
|
|
|
|
#else /* make valgrind happy */
|
|
|
|
k8 = (const uint8_t *)k;
|
|
switch(length)
|
|
{
|
|
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
|
|
case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
|
|
case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
|
|
case 9 : c+=k8[8]; /* fall through */
|
|
case 8 : b+=k[1]; a+=k[0]; break;
|
|
case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
|
|
case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
|
|
case 5 : b+=k8[4]; /* fall through */
|
|
case 4 : a+=k[0]; break;
|
|
case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
|
|
case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
|
|
case 1 : a+=k8[0]; break;
|
|
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
|
|
}
|
|
|
|
#endif /* !valgrind */
|
|
|
|
} else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
|
|
const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
|
|
const uint8_t *k8;
|
|
|
|
/*--------------- all but last block: aligned reads and different mixing */
|
|
while (length > 12)
|
|
{
|
|
a += k[0] + (((uint32_t)k[1])<<16);
|
|
b += k[2] + (((uint32_t)k[3])<<16);
|
|
c += k[4] + (((uint32_t)k[5])<<16);
|
|
mix(a,b,c);
|
|
length -= 12;
|
|
k += 6;
|
|
}
|
|
|
|
/*----------------------------- handle the last (probably partial) block */
|
|
k8 = (const uint8_t *)k;
|
|
switch(length)
|
|
{
|
|
case 12: c+=k[4]+(((uint32_t)k[5])<<16);
|
|
b+=k[2]+(((uint32_t)k[3])<<16);
|
|
a+=k[0]+(((uint32_t)k[1])<<16);
|
|
break;
|
|
case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
|
|
case 10: c+=k[4];
|
|
b+=k[2]+(((uint32_t)k[3])<<16);
|
|
a+=k[0]+(((uint32_t)k[1])<<16);
|
|
break;
|
|
case 9 : c+=k8[8]; /* fall through */
|
|
case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
|
|
a+=k[0]+(((uint32_t)k[1])<<16);
|
|
break;
|
|
case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
|
|
case 6 : b+=k[2];
|
|
a+=k[0]+(((uint32_t)k[1])<<16);
|
|
break;
|
|
case 5 : b+=k8[4]; /* fall through */
|
|
case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
|
|
break;
|
|
case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
|
|
case 2 : a+=k[0];
|
|
break;
|
|
case 1 : a+=k8[0];
|
|
break;
|
|
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
|
|
}
|
|
|
|
} else { /* need to read the key one byte at a time */
|
|
const uint8_t *k = (const uint8_t *)key;
|
|
|
|
/*--------------- all but the last block: affect some 32 bits of (a,b,c) */
|
|
while (length > 12)
|
|
{
|
|
a += k[0];
|
|
a += ((uint32_t)k[1])<<8;
|
|
a += ((uint32_t)k[2])<<16;
|
|
a += ((uint32_t)k[3])<<24;
|
|
b += k[4];
|
|
b += ((uint32_t)k[5])<<8;
|
|
b += ((uint32_t)k[6])<<16;
|
|
b += ((uint32_t)k[7])<<24;
|
|
c += k[8];
|
|
c += ((uint32_t)k[9])<<8;
|
|
c += ((uint32_t)k[10])<<16;
|
|
c += ((uint32_t)k[11])<<24;
|
|
mix(a,b,c);
|
|
length -= 12;
|
|
k += 12;
|
|
}
|
|
|
|
/*-------------------------------- last block: affect all 32 bits of (c) */
|
|
switch(length) /* all the case statements fall through */
|
|
{
|
|
case 12: c+=((uint32_t)k[11])<<24;
|
|
case 11: c+=((uint32_t)k[10])<<16;
|
|
case 10: c+=((uint32_t)k[9])<<8;
|
|
case 9 : c+=k[8];
|
|
case 8 : b+=((uint32_t)k[7])<<24;
|
|
case 7 : b+=((uint32_t)k[6])<<16;
|
|
case 6 : b+=((uint32_t)k[5])<<8;
|
|
case 5 : b+=k[4];
|
|
case 4 : a+=((uint32_t)k[3])<<24;
|
|
case 3 : a+=((uint32_t)k[2])<<16;
|
|
case 2 : a+=((uint32_t)k[1])<<8;
|
|
case 1 : a+=k[0];
|
|
break;
|
|
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
|
|
}
|
|
}
|
|
|
|
final(a,b,c);
|
|
*pc=c; *pb=b;
|
|
}
|
|
|
|
uint64_t flecs_hash(
|
|
const void *data,
|
|
ecs_size_t length)
|
|
{
|
|
uint32_t h_1 = 0;
|
|
uint32_t h_2 = 0;
|
|
|
|
hashlittle2(
|
|
data,
|
|
flecs_ito(size_t, length),
|
|
&h_1,
|
|
&h_2);
|
|
|
|
#ifndef __clang_analyzer__
|
|
uint64_t h_2_shift = (uint64_t)h_2 << 32;
|
|
#else
|
|
uint64_t h_2_shift = 0;
|
|
#endif
|
|
return h_1 | h_2_shift;
|
|
}
|
|
|
|
/**
|
|
* @file datastructures/qsort.c
|
|
* @brief Quicksort implementation.
|
|
*/
|
|
|
|
|
|
void ecs_qsort(
|
|
void *base,
|
|
ecs_size_t nitems,
|
|
ecs_size_t size,
|
|
int (*compar)(const void *, const void*))
|
|
{
|
|
void *tmp = ecs_os_alloca(size); /* For swap */
|
|
|
|
#define LESS(i, j) \
|
|
compar(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j)) < 0
|
|
|
|
#define SWAP(i, j) \
|
|
ecs_os_memcpy(tmp, ECS_ELEM(base, size, i), size),\
|
|
ecs_os_memcpy(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j), size),\
|
|
ecs_os_memcpy(ECS_ELEM(base, size, j), tmp, size)
|
|
|
|
QSORT(nitems, LESS, SWAP);
|
|
}
|
|
|
|
/**
|
|
* @file datastructures/bitset.c
|
|
* @brief Bitset data structure.
|
|
*
|
|
* 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);
|
|
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/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
|
|
int 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 (isnan(f)) {
|
|
if (nan_delim) {
|
|
ecs_strbuf_appendch(out, nan_delim);
|
|
ecs_strbuf_appendlit(out, "NaN");
|
|
return ecs_strbuf_appendch(out, nan_delim);
|
|
} else {
|
|
return ecs_strbuf_appendlit(out, "NaN");
|
|
}
|
|
}
|
|
if (isinf(f)) {
|
|
if (nan_delim) {
|
|
ecs_strbuf_appendch(out, nan_delim);
|
|
ecs_strbuf_appendlit(out, "Inf");
|
|
return ecs_strbuf_appendch(out, nan_delim);
|
|
} else {
|
|
return ecs_strbuf_appendlit(out, "Inf");
|
|
}
|
|
}
|
|
|
|
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];
|
|
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';
|
|
}
|
|
|
|
return 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)
|
|
{
|
|
/* Allocate new element */
|
|
ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded);
|
|
b->size += b->current->pos;
|
|
b->current->next = (ecs_strbuf_element*)e;
|
|
b->current = (ecs_strbuf_element*)e;
|
|
b->elementCount ++;
|
|
e->super.buffer_embedded = true;
|
|
e->super.buf = e->buf;
|
|
e->super.pos = 0;
|
|
e->super.next = NULL;
|
|
}
|
|
|
|
/* Add an extra dynamic element */
|
|
static
|
|
void flecs_strbuf_grow_str(
|
|
ecs_strbuf_t *b,
|
|
char *str,
|
|
char *alloc_str,
|
|
int32_t size)
|
|
{
|
|
/* Allocate new element */
|
|
ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str);
|
|
b->size += b->current->pos;
|
|
b->current->next = (ecs_strbuf_element*)e;
|
|
b->current = (ecs_strbuf_element*)e;
|
|
b->elementCount ++;
|
|
e->super.buffer_embedded = false;
|
|
e->super.pos = size ? size : (int32_t)ecs_os_strlen(str);
|
|
e->super.next = NULL;
|
|
e->super.buf = str;
|
|
e->alloc_str = alloc_str;
|
|
}
|
|
|
|
static
|
|
char* flecs_strbuf_ptr(
|
|
ecs_strbuf_t *b)
|
|
{
|
|
if (b->buf) {
|
|
return &b->buf[b->current->pos];
|
|
} else {
|
|
return &b->current->buf[b->current->pos];
|
|
}
|
|
}
|
|
|
|
/* Compute the amount of space left in the current element */
|
|
static
|
|
int32_t flecs_strbuf_memLeftInCurrentElement(
|
|
ecs_strbuf_t *b)
|
|
{
|
|
if (b->current->buffer_embedded) {
|
|
return ECS_STRBUF_ELEMENT_SIZE - b->current->pos;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Compute the amount of space left */
|
|
static
|
|
int32_t flecs_strbuf_memLeft(
|
|
ecs_strbuf_t *b)
|
|
{
|
|
if (b->max) {
|
|
return b->max - b->size - b->current->pos;
|
|
} else {
|
|
return INT_MAX;
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_strbuf_init(
|
|
ecs_strbuf_t *b)
|
|
{
|
|
/* Initialize buffer structure only once */
|
|
if (!b->elementCount) {
|
|
b->size = 0;
|
|
b->firstElement.super.next = NULL;
|
|
b->firstElement.super.pos = 0;
|
|
b->firstElement.super.buffer_embedded = true;
|
|
b->firstElement.super.buf = b->firstElement.buf;
|
|
b->elementCount ++;
|
|
b->current = (ecs_strbuf_element*)&b->firstElement;
|
|
}
|
|
}
|
|
|
|
/* Append a format string to a buffer */
|
|
static
|
|
bool flecs_strbuf_vappend(
|
|
ecs_strbuf_t *b,
|
|
const char* str,
|
|
va_list args)
|
|
{
|
|
bool result = true;
|
|
va_list arg_cpy;
|
|
|
|
if (!str) {
|
|
return result;
|
|
}
|
|
|
|
flecs_strbuf_init(b);
|
|
|
|
int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b);
|
|
int32_t memLeft = flecs_strbuf_memLeft(b);
|
|
|
|
if (!memLeft) {
|
|
return false;
|
|
}
|
|
|
|
/* 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 max_copy = b->buf ? memLeft : memLeftInElement;
|
|
int32_t memRequired;
|
|
|
|
va_copy(arg_cpy, args);
|
|
memRequired = vsnprintf(
|
|
flecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args);
|
|
|
|
ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (memRequired <= memLeftInElement) {
|
|
/* Element was large enough to fit string */
|
|
b->current->pos += memRequired;
|
|
} else if ((memRequired - memLeftInElement) < memLeft) {
|
|
/* If string is a format string, a new buffer of size memRequired is
|
|
* needed to re-evaluate the format string and only use the part that
|
|
* wasn't already copied to the previous element */
|
|
if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) {
|
|
/* Resulting string fits in standard-size buffer. Note that the
|
|
* entire string needs to fit, not just the remainder, as the
|
|
* format string cannot be partially evaluated */
|
|
flecs_strbuf_grow(b);
|
|
|
|
/* Copy entire string to new buffer */
|
|
ecs_os_vsprintf(flecs_strbuf_ptr(b), str, arg_cpy);
|
|
|
|
/* Ignore the part of the string that was copied into the
|
|
* previous buffer. The string copied into the new buffer could
|
|
* be memmoved so that only the remainder is left, but that is
|
|
* most likely more expensive than just keeping the entire
|
|
* string. */
|
|
|
|
/* Update position in buffer */
|
|
b->current->pos += memRequired;
|
|
} else {
|
|
/* Resulting string does not fit in standard-size buffer.
|
|
* Allocate a new buffer that can hold the entire string. */
|
|
char *dst = ecs_os_malloc(memRequired + 1);
|
|
ecs_os_vsprintf(dst, str, arg_cpy);
|
|
flecs_strbuf_grow_str(b, dst, dst, memRequired);
|
|
}
|
|
}
|
|
|
|
va_end(arg_cpy);
|
|
|
|
return flecs_strbuf_memLeft(b) > 0;
|
|
}
|
|
|
|
static
|
|
bool flecs_strbuf_appendstr(
|
|
ecs_strbuf_t *b,
|
|
const char* str,
|
|
int n)
|
|
{
|
|
flecs_strbuf_init(b);
|
|
|
|
int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b);
|
|
int32_t memLeft = flecs_strbuf_memLeft(b);
|
|
if (memLeft <= 0) {
|
|
return false;
|
|
}
|
|
|
|
/* Never write more than what the buffer can store */
|
|
if (n > memLeft) {
|
|
n = memLeft;
|
|
}
|
|
|
|
if (n <= memLeftInElement) {
|
|
/* Element was large enough to fit string */
|
|
ecs_os_strncpy(flecs_strbuf_ptr(b), str, n);
|
|
b->current->pos += n;
|
|
} else if ((n - memLeftInElement) < memLeft) {
|
|
ecs_os_strncpy(flecs_strbuf_ptr(b), str, memLeftInElement);
|
|
|
|
/* Element was not large enough, but buffer still has space */
|
|
b->current->pos += memLeftInElement;
|
|
n -= memLeftInElement;
|
|
|
|
/* Current element was too small, copy remainder into new element */
|
|
if (n < ECS_STRBUF_ELEMENT_SIZE) {
|
|
/* A standard-size buffer is large enough for the new string */
|
|
flecs_strbuf_grow(b);
|
|
|
|
/* Copy the remainder to the new buffer */
|
|
if (n) {
|
|
/* If a max number of characters to write is set, only a
|
|
* subset of the string should be copied to the buffer */
|
|
ecs_os_strncpy(
|
|
flecs_strbuf_ptr(b),
|
|
str + memLeftInElement,
|
|
(size_t)n);
|
|
} else {
|
|
ecs_os_strcpy(flecs_strbuf_ptr(b), str + memLeftInElement);
|
|
}
|
|
|
|
/* Update to number of characters copied to new buffer */
|
|
b->current->pos += n;
|
|
} else {
|
|
/* String doesn't fit in a single element, strdup */
|
|
char *remainder = ecs_os_strdup(str + memLeftInElement);
|
|
flecs_strbuf_grow_str(b, remainder, remainder, n);
|
|
}
|
|
} else {
|
|
/* Buffer max has been reached */
|
|
return false;
|
|
}
|
|
|
|
return flecs_strbuf_memLeft(b) > 0;
|
|
}
|
|
|
|
static
|
|
bool flecs_strbuf_appendch(
|
|
ecs_strbuf_t *b,
|
|
char ch)
|
|
{
|
|
flecs_strbuf_init(b);
|
|
|
|
int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b);
|
|
int32_t memLeft = flecs_strbuf_memLeft(b);
|
|
if (memLeft <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if (memLeftInElement) {
|
|
/* Element was large enough to fit string */
|
|
flecs_strbuf_ptr(b)[0] = ch;
|
|
b->current->pos ++;
|
|
} else {
|
|
flecs_strbuf_grow(b);
|
|
flecs_strbuf_ptr(b)[0] = ch;
|
|
b->current->pos ++;
|
|
}
|
|
|
|
return flecs_strbuf_memLeft(b) > 0;
|
|
}
|
|
|
|
bool 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);
|
|
return flecs_strbuf_vappend(b, fmt, args);
|
|
}
|
|
|
|
bool 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);
|
|
bool result = flecs_strbuf_vappend(b, fmt, args);
|
|
va_end(args);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool 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);
|
|
return flecs_strbuf_appendstr(b, str, len);
|
|
}
|
|
|
|
bool ecs_strbuf_appendch(
|
|
ecs_strbuf_t *b,
|
|
char ch)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
return flecs_strbuf_appendch(b, ch);
|
|
}
|
|
|
|
bool 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);
|
|
return ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf));
|
|
}
|
|
|
|
bool ecs_strbuf_appendflt(
|
|
ecs_strbuf_t *b,
|
|
double flt,
|
|
char nan_delim)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
return flecs_strbuf_ftoa(b, flt, 10, nan_delim);
|
|
}
|
|
|
|
bool ecs_strbuf_appendstr_zerocpy(
|
|
ecs_strbuf_t *b,
|
|
char* str)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
flecs_strbuf_init(b);
|
|
flecs_strbuf_grow_str(b, str, str, 0);
|
|
return true;
|
|
}
|
|
|
|
bool ecs_strbuf_appendstr_zerocpyn(
|
|
ecs_strbuf_t *b,
|
|
char *str,
|
|
int32_t n)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
flecs_strbuf_init(b);
|
|
flecs_strbuf_grow_str(b, str, str, n);
|
|
return true;
|
|
}
|
|
|
|
bool ecs_strbuf_appendstr_zerocpy_const(
|
|
ecs_strbuf_t *b,
|
|
const char* str)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
/* Removes const modifier, but logic prevents changing / delete string */
|
|
flecs_strbuf_init(b);
|
|
flecs_strbuf_grow_str(b, (char*)str, NULL, 0);
|
|
return true;
|
|
}
|
|
|
|
bool ecs_strbuf_appendstr_zerocpyn_const(
|
|
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);
|
|
/* Removes const modifier, but logic prevents changing / delete string */
|
|
flecs_strbuf_init(b);
|
|
flecs_strbuf_grow_str(b, (char*)str, NULL, n);
|
|
return true;
|
|
}
|
|
|
|
bool 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);
|
|
return flecs_strbuf_appendstr(b, str, ecs_os_strlen(str));
|
|
}
|
|
|
|
bool ecs_strbuf_mergebuff(
|
|
ecs_strbuf_t *dst_buffer,
|
|
ecs_strbuf_t *src_buffer)
|
|
{
|
|
if (src_buffer->elementCount) {
|
|
if (src_buffer->buf) {
|
|
return ecs_strbuf_appendstrn(
|
|
dst_buffer, src_buffer->buf, src_buffer->length);
|
|
} else {
|
|
ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement;
|
|
|
|
/* Copy first element as it is inlined in the src buffer */
|
|
ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos);
|
|
|
|
while ((e = e->next)) {
|
|
dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element));
|
|
*dst_buffer->current->next = *e;
|
|
}
|
|
}
|
|
|
|
*src_buffer = ECS_STRBUF_INIT;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
char* ecs_strbuf_get(
|
|
ecs_strbuf_t *b)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
char* result = NULL;
|
|
if (b->elementCount) {
|
|
if (b->buf) {
|
|
b->buf[b->current->pos] = '\0';
|
|
result = ecs_os_strdup(b->buf);
|
|
} else {
|
|
void *next = NULL;
|
|
int32_t len = b->size + b->current->pos + 1;
|
|
ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement;
|
|
|
|
result = ecs_os_malloc(len);
|
|
char* ptr = result;
|
|
|
|
do {
|
|
ecs_os_memcpy(ptr, e->buf, e->pos);
|
|
ptr += e->pos;
|
|
next = e->next;
|
|
if (e != &b->firstElement.super) {
|
|
if (!e->buffer_embedded) {
|
|
ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str);
|
|
}
|
|
ecs_os_free(e);
|
|
}
|
|
} while ((e = next));
|
|
|
|
result[len - 1] = '\0';
|
|
b->length = len;
|
|
}
|
|
} else {
|
|
result = NULL;
|
|
}
|
|
|
|
b->elementCount = 0;
|
|
|
|
b->content = result;
|
|
|
|
return result;
|
|
}
|
|
|
|
char *ecs_strbuf_get_small(
|
|
ecs_strbuf_t *b)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
int32_t written = ecs_strbuf_written(b);
|
|
ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL);
|
|
char *buf = b->firstElement.buf;
|
|
buf[written] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
void ecs_strbuf_reset(
|
|
ecs_strbuf_t *b)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
if (b->elementCount && !b->buf) {
|
|
void *next = NULL;
|
|
ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement;
|
|
do {
|
|
next = e->next;
|
|
if (e != (ecs_strbuf_element*)&b->firstElement) {
|
|
ecs_os_free(e);
|
|
}
|
|
} while ((e = next));
|
|
}
|
|
|
|
*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);
|
|
|
|
b->list_sp ++;
|
|
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);
|
|
|
|
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 ++;
|
|
}
|
|
|
|
bool ecs_strbuf_list_appendch(
|
|
ecs_strbuf_t *b,
|
|
char ch)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_strbuf_list_next(b);
|
|
return flecs_strbuf_appendch(b, ch);
|
|
}
|
|
|
|
bool 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);
|
|
bool result = flecs_strbuf_vappend(b, fmt, args);
|
|
va_end(args);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool 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);
|
|
return ecs_strbuf_appendstr(b, str);
|
|
}
|
|
|
|
bool 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);
|
|
return ecs_strbuf_appendstrn(b, str, n);
|
|
}
|
|
|
|
int32_t ecs_strbuf_written(
|
|
const ecs_strbuf_t *b)
|
|
{
|
|
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
if (b->current) {
|
|
return b->size + b->current->pos;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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) {
|
|
v->array = flecs_alloc(allocator, size * elem_count);
|
|
}
|
|
v->size = elem_count;
|
|
#ifdef FLECS_DEBUG
|
|
v->elem_size = size;
|
|
#endif
|
|
return v;
|
|
}
|
|
|
|
void ecs_vec_fini(
|
|
ecs_allocator_t *allocator,
|
|
ecs_vec_t *v,
|
|
ecs_size_t size)
|
|
{
|
|
if (v->array) {
|
|
ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
|
flecs_free(allocator, size * v->size, 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_dbg_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,
|
|
ecs_vec_t *v,
|
|
ecs_size_t size)
|
|
{
|
|
ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
|
return (ecs_vec_t) {
|
|
.count = v->count,
|
|
.size = v->size,
|
|
.array = flecs_dup(allocator, size * v->size, v->array)
|
|
#ifdef FLECS_DEBUG
|
|
, .elem_size = size
|
|
#endif
|
|
};
|
|
}
|
|
|
|
void ecs_vec_reclaim(
|
|
ecs_allocator_t *allocator,
|
|
ecs_vec_t *v,
|
|
ecs_size_t size)
|
|
{
|
|
ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
|
int32_t count = v->count;
|
|
if (count < v->size) {
|
|
if (count) {
|
|
v->array = flecs_realloc(
|
|
allocator, size * count, size * v->size, v->array);
|
|
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_dbg_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) {
|
|
v->array = flecs_realloc(
|
|
allocator, size * elem_count, size * v->size, v->array);
|
|
v->size = elem_count;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ecs_vec_set_count(
|
|
ecs_allocator_t *allocator,
|
|
ecs_vec_t *v,
|
|
ecs_size_t size,
|
|
int32_t elem_count)
|
|
{
|
|
ecs_dbg_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_dbg_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_dbg_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_dbg_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_dbg_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_dbg_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;
|
|
}
|
|
|
|
/**
|
|
* @file datastructures/map.c
|
|
* @brief Map data structure.
|
|
*
|
|
* Map data structure for 64bit keys and dynamic payload size.
|
|
*/
|
|
|
|
#include <math.h>
|
|
|
|
/* The ratio used to determine whether the map should flecs_map_rehash. If
|
|
* (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */
|
|
#define LOAD_FACTOR (1.2f)
|
|
#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t))
|
|
#define GET_ELEM(array, elem_size, index) \
|
|
ECS_OFFSET(array, (elem_size) * (index))
|
|
|
|
static
|
|
uint8_t ecs_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 get_bucket_count(
|
|
int32_t element_count)
|
|
{
|
|
return flecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR));
|
|
}
|
|
|
|
/* Get bucket shift amount for a given bucket count */
|
|
static
|
|
uint8_t get_bucket_shift (
|
|
int32_t bucket_count)
|
|
{
|
|
return (uint8_t)(64u - ecs_log2((uint32_t)bucket_count));
|
|
}
|
|
|
|
/* Get bucket index for provided map key */
|
|
static
|
|
int32_t 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->bucket_shift == get_bucket_shift(map->bucket_count),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
int32_t bucket_id = get_bucket_index(map->bucket_shift, key);
|
|
ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL);
|
|
return &map->buckets[bucket_id];
|
|
}
|
|
|
|
/* Ensure that map has at least new_count buckets */
|
|
static
|
|
void ensure_buckets(
|
|
ecs_map_t *map,
|
|
int32_t new_count)
|
|
{
|
|
int32_t bucket_count = map->bucket_count;
|
|
new_count = flecs_next_pow_of_2(new_count);
|
|
if (new_count < 2) {
|
|
new_count = 2;
|
|
}
|
|
|
|
if (new_count && new_count > bucket_count) {
|
|
if (map->allocator) {
|
|
map->buckets = flecs_realloc_n(map->allocator, ecs_bucket_t,
|
|
new_count, bucket_count, map->buckets);
|
|
} else {
|
|
map->buckets = ecs_os_realloc_n(
|
|
map->buckets, ecs_bucket_t, new_count);
|
|
}
|
|
|
|
map->buckets_end = ECS_ELEM_T(map->buckets, ecs_bucket_t, new_count);
|
|
map->bucket_count = new_count;
|
|
map->bucket_shift = get_bucket_shift(new_count);
|
|
ecs_os_memset_n(ECS_ELEM_T(map->buckets, ecs_bucket_t, bucket_count),
|
|
0, ecs_bucket_t, (new_count - bucket_count));
|
|
}
|
|
}
|
|
|
|
/* Free contents of bucket */
|
|
static
|
|
void clear_bucket(
|
|
ecs_block_allocator_t *allocator,
|
|
ecs_bucket_entry_t *bucket)
|
|
{
|
|
while(bucket) {
|
|
ecs_bucket_entry_t *next = bucket->next;
|
|
flecs_bfree(allocator, bucket);
|
|
bucket = next;
|
|
}
|
|
}
|
|
|
|
/* Clear all buckets */
|
|
static
|
|
void clear_buckets(
|
|
ecs_map_t *map)
|
|
{
|
|
int32_t i, count = map->bucket_count;
|
|
for (i = 0; i < count; i ++) {
|
|
clear_bucket(map->entry_allocator, map->buckets[i].first);
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* Add element to bucket */
|
|
static
|
|
void* flecs_map_add_to_bucket(
|
|
ecs_block_allocator_t *allocator,
|
|
ecs_bucket_t *bucket,
|
|
ecs_size_t elem_size,
|
|
ecs_map_key_t key,
|
|
const void *payload)
|
|
{
|
|
ecs_bucket_entry_t *new_entry = flecs_balloc(allocator);
|
|
new_entry->key = key;
|
|
new_entry->next = bucket->first;
|
|
bucket->first = new_entry;
|
|
void *new_payload = ECS_OFFSET(&new_entry->key, ECS_SIZEOF(ecs_map_key_t));
|
|
if (elem_size && payload) {
|
|
ecs_os_memcpy(new_payload, payload, elem_size);
|
|
}
|
|
return new_payload;
|
|
}
|
|
|
|
/* Remove element from bucket */
|
|
static
|
|
bool remove_from_bucket(
|
|
ecs_block_allocator_t *allocator,
|
|
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_bucket_entry_t **next_holder = &bucket->first;
|
|
while(*next_holder != entry) {
|
|
next_holder = &(*next_holder)->next;
|
|
}
|
|
*next_holder = entry->next;
|
|
flecs_bfree(allocator, entry);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Get payload pointer for key from bucket */
|
|
static
|
|
void* flecs_map_get_from_bucket(
|
|
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 ECS_OFFSET(&entry->key, ECS_SIZEOF(ecs_map_key_t));
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Grow number of buckets */
|
|
static
|
|
void flecs_map_rehash(
|
|
ecs_map_t *map,
|
|
int32_t bucket_count)
|
|
{
|
|
ecs_assert(bucket_count != 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t old_count = map->bucket_count;
|
|
ecs_bucket_t *old_buckets = map->buckets;
|
|
|
|
int32_t new_count = flecs_next_pow_of_2(bucket_count);
|
|
map->bucket_count = new_count;
|
|
map->bucket_shift = get_bucket_shift(new_count);
|
|
if (map->allocator) {
|
|
map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, new_count);
|
|
} else {
|
|
map->buckets = ecs_os_calloc_n(ecs_bucket_t, new_count);
|
|
}
|
|
map->buckets_end = ECS_ELEM_T(map->buckets, ecs_bucket_t, new_count);
|
|
|
|
/* Remap old bucket entries to new buckets */
|
|
int32_t index;
|
|
for (index = 0; index < old_count; ++index) {
|
|
ecs_bucket_entry_t* entry;
|
|
for (entry = old_buckets[index].first; entry;) {
|
|
ecs_bucket_entry_t* next = entry->next;
|
|
int32_t bucket_index = get_bucket_index(
|
|
map->bucket_shift, entry->key);
|
|
ecs_bucket_t *bucket = &map->buckets[bucket_index];
|
|
entry->next = bucket->first;
|
|
bucket->first = entry;
|
|
entry = next;
|
|
}
|
|
}
|
|
|
|
if (map->allocator) {
|
|
flecs_free_n(map->allocator, ecs_bucket_t, old_count, old_buckets);
|
|
} else {
|
|
ecs_os_free(old_buckets);
|
|
}
|
|
}
|
|
|
|
bool ecs_map_is_initialized(
|
|
const ecs_map_t *result)
|
|
{
|
|
return result != NULL && result->bucket_count != 0;
|
|
}
|
|
|
|
static
|
|
ecs_size_t flecs_map_chunk_size(
|
|
ecs_size_t size)
|
|
{
|
|
int32_t entry_size = size + ECS_SIZEOF(ecs_bucket_entry_t);
|
|
return ECS_MAX(entry_size, ECS_SIZEOF(ecs_block_allocator_chunk_header_t));
|
|
}
|
|
|
|
void _ecs_map_params_init(
|
|
ecs_map_params_t *params,
|
|
ecs_allocator_t *allocator,
|
|
ecs_size_t size)
|
|
{
|
|
params->size = size;
|
|
params->allocator = allocator;
|
|
flecs_ballocator_init(¶ms->entry_allocator,
|
|
flecs_map_chunk_size(size));
|
|
}
|
|
|
|
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_assert(params->size < INT16_MAX, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
result->count = 0;
|
|
result->elem_size = flecs_ito(int16_t, params->size);
|
|
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(
|
|
flecs_map_chunk_size(params->size));
|
|
}
|
|
|
|
ensure_buckets(result, get_bucket_count(params->initial_count));
|
|
}
|
|
|
|
void _ecs_map_init_w_params_if(
|
|
ecs_map_t *result,
|
|
ecs_map_params_t *params)
|
|
{
|
|
if (ecs_map_is_initialized(result)) {
|
|
ecs_assert(params->size == result->elem_size,
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
return;
|
|
}
|
|
_ecs_map_init_w_params(result, params);
|
|
}
|
|
|
|
void _ecs_map_init(
|
|
ecs_map_t *result,
|
|
ecs_size_t elem_size,
|
|
ecs_allocator_t *allocator,
|
|
int32_t element_count)
|
|
{
|
|
_ecs_map_init_w_params(result, &(ecs_map_params_t) {
|
|
.size = elem_size,
|
|
.initial_count = element_count,
|
|
.allocator = allocator
|
|
});
|
|
}
|
|
|
|
void _ecs_map_init_if(
|
|
ecs_map_t *result,
|
|
ecs_size_t elem_size,
|
|
ecs_allocator_t *allocator,
|
|
int32_t element_count)
|
|
{
|
|
if (ecs_map_is_initialized(result)) {
|
|
ecs_assert(elem_size == result->elem_size, ECS_INVALID_PARAMETER, NULL);
|
|
return;
|
|
}
|
|
_ecs_map_init(result, elem_size, allocator, element_count);
|
|
}
|
|
|
|
ecs_map_t* _ecs_map_new(
|
|
ecs_size_t elem_size,
|
|
ecs_allocator_t *allocator,
|
|
int32_t element_count)
|
|
{
|
|
ecs_map_t *result = ecs_os_calloc_t(ecs_map_t);
|
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
|
|
|
|
_ecs_map_init(result, elem_size, allocator, element_count);
|
|
|
|
return result;
|
|
}
|
|
|
|
void ecs_map_fini(
|
|
ecs_map_t *map)
|
|
{
|
|
ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
#ifdef FLECS_SANITIZE
|
|
bool sanitize = true;
|
|
#else
|
|
bool sanitize = false;
|
|
#endif
|
|
|
|
/* Free buckets in sanirized mode, so we can replace the allocator with
|
|
* regular malloc/free and use asan/valgrind to find memory errors. */
|
|
if (map->shared_allocator || sanitize) {
|
|
ecs_bucket_t *bucket = map->buckets;
|
|
while ((bucket != map->buckets_end)) {
|
|
ecs_bucket_entry_t *entry = bucket->first;
|
|
while (entry) {
|
|
ecs_bucket_entry_t *next = entry->next;
|
|
flecs_bfree(map->entry_allocator, entry);
|
|
entry = next;
|
|
}
|
|
bucket ++;
|
|
}
|
|
}
|
|
|
|
if (map->entry_allocator && !map->shared_allocator) {
|
|
flecs_ballocator_free(map->entry_allocator);
|
|
map->entry_allocator = NULL;
|
|
}
|
|
if (map->allocator) {
|
|
flecs_free_n(map->allocator, ecs_bucket_t, map->bucket_count,
|
|
map->buckets);
|
|
} else {
|
|
ecs_os_free(map->buckets);
|
|
}
|
|
map->buckets = NULL;
|
|
map->buckets_end = NULL;
|
|
map->bucket_count = 0;
|
|
ecs_assert(!ecs_map_is_initialized(map), ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
void ecs_map_free(
|
|
ecs_map_t *map)
|
|
{
|
|
if (map) {
|
|
ecs_map_fini(map);
|
|
ecs_os_free(map);
|
|
}
|
|
}
|
|
|
|
void* _ecs_map_get(
|
|
const ecs_map_t *map,
|
|
ecs_size_t elem_size,
|
|
ecs_map_key_t key)
|
|
{
|
|
(void)elem_size;
|
|
|
|
if (!ecs_map_is_initialized(map)) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_bucket_t *bucket = flecs_map_get_bucket(map, key);
|
|
|
|
return flecs_map_get_from_bucket(bucket, key);
|
|
}
|
|
|
|
void* _ecs_map_get_ptr(
|
|
const ecs_map_t *map,
|
|
ecs_map_key_t key)
|
|
{
|
|
void* ptr_ptr = _ecs_map_get(map, ECS_SIZEOF(void*), key);
|
|
|
|
if (ptr_ptr) {
|
|
return *(void**)ptr_ptr;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
bool ecs_map_has(
|
|
const ecs_map_t *map,
|
|
ecs_map_key_t key)
|
|
{
|
|
if (!ecs_map_is_initialized(map)) {
|
|
return false;
|
|
}
|
|
|
|
ecs_bucket_t *bucket = flecs_map_get_bucket(map, key);
|
|
|
|
return flecs_map_get_from_bucket(bucket, key) != NULL;
|
|
}
|
|
|
|
void* _ecs_map_ensure(
|
|
ecs_map_t *map,
|
|
ecs_size_t elem_size,
|
|
ecs_map_key_t key)
|
|
{
|
|
void *result = _ecs_map_get(map, elem_size, key);
|
|
if (!result) {
|
|
result = _ecs_map_set(map, elem_size, key, NULL);
|
|
if (elem_size) {
|
|
ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_os_memset(result, 0, elem_size);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void* _ecs_map_set(
|
|
ecs_map_t *map,
|
|
ecs_size_t elem_size,
|
|
ecs_map_key_t key,
|
|
const void *payload)
|
|
{
|
|
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_bucket_t *bucket = flecs_map_get_bucket(map, key);
|
|
|
|
void *elem = flecs_map_get_from_bucket(bucket, key);
|
|
if (!elem) {
|
|
void *added_data = flecs_map_add_to_bucket(
|
|
map->entry_allocator, bucket, elem_size, key, payload);
|
|
int32_t map_count = ++map->count;
|
|
int32_t target_bucket_count = get_bucket_count(map_count);
|
|
int32_t map_bucket_count = map->bucket_count;
|
|
|
|
if (target_bucket_count > map_bucket_count) {
|
|
flecs_map_rehash(map, target_bucket_count);
|
|
bucket = flecs_map_get_bucket(map, key);
|
|
added_data = flecs_map_get_from_bucket(bucket, key);
|
|
ecs_assert(added_data != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
}
|
|
return added_data;
|
|
} else {
|
|
if (payload) {
|
|
ecs_os_memcpy(elem, payload, elem_size);
|
|
}
|
|
return elem;
|
|
}
|
|
}
|
|
|
|
int32_t ecs_map_remove(
|
|
ecs_map_t *map,
|
|
ecs_map_key_t key)
|
|
{
|
|
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_bucket_t *bucket = flecs_map_get_bucket(map, key);
|
|
if (remove_from_bucket(map->entry_allocator, bucket, key)) {
|
|
return --map->count;
|
|
}
|
|
|
|
return map->count;
|
|
}
|
|
|
|
int32_t ecs_map_count(
|
|
const ecs_map_t *map)
|
|
{
|
|
return map ? map->count : 0;
|
|
}
|
|
|
|
int32_t ecs_map_bucket_count(
|
|
const ecs_map_t *map)
|
|
{
|
|
return map ? map->bucket_count : 0;
|
|
}
|
|
|
|
void ecs_map_clear(
|
|
ecs_map_t *map)
|
|
{
|
|
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
clear_buckets(map);
|
|
map->count = 0;
|
|
ensure_buckets(map, 2);
|
|
}
|
|
|
|
ecs_map_iter_t ecs_map_iter(
|
|
const ecs_map_t *map)
|
|
{
|
|
return (ecs_map_iter_t){
|
|
.map = map,
|
|
.bucket = NULL,
|
|
.entry = NULL
|
|
};
|
|
}
|
|
|
|
void* _ecs_map_next(
|
|
ecs_map_iter_t *iter,
|
|
ecs_size_t elem_size,
|
|
ecs_map_key_t *key_out)
|
|
{
|
|
(void)elem_size;
|
|
const ecs_map_t *map = iter->map;
|
|
if (!ecs_map_is_initialized(map)) {
|
|
return NULL;
|
|
}
|
|
if (iter->bucket == map->buckets_end) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_assert(!elem_size || elem_size == map->elem_size,
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_bucket_entry_t *entry = NULL;
|
|
|
|
if (!iter->bucket) {
|
|
for (iter->bucket = map->buckets;
|
|
iter->bucket != map->buckets_end;
|
|
++iter->bucket)
|
|
{
|
|
if (iter->bucket->first) {
|
|
entry = iter->bucket->first;
|
|
break;
|
|
}
|
|
}
|
|
if (iter->bucket == map->buckets_end) {
|
|
return NULL;
|
|
}
|
|
} else if ((entry = iter->entry) == NULL) {
|
|
do {
|
|
++iter->bucket;
|
|
if (iter->bucket == map->buckets_end) {
|
|
return NULL;
|
|
}
|
|
} while(!iter->bucket->first);
|
|
entry = iter->bucket->first;
|
|
}
|
|
|
|
if (key_out) {
|
|
ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
*key_out = entry->key;
|
|
}
|
|
|
|
iter->entry = entry->next;
|
|
|
|
return ECS_OFFSET(&entry->key, ECS_SIZEOF(ecs_map_key_t));
|
|
}
|
|
|
|
void* _ecs_map_next_ptr(
|
|
ecs_map_iter_t *iter,
|
|
ecs_map_key_t *key_out)
|
|
{
|
|
void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out);
|
|
if (result) {
|
|
return *(void**)result;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void ecs_map_grow(
|
|
ecs_map_t *map,
|
|
int32_t element_count)
|
|
{
|
|
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
int32_t target_count = map->count + element_count;
|
|
int32_t bucket_count = get_bucket_count(target_count);
|
|
|
|
if (bucket_count > map->bucket_count) {
|
|
flecs_map_rehash(map, bucket_count);
|
|
}
|
|
}
|
|
|
|
void ecs_map_set_size(
|
|
ecs_map_t *map,
|
|
int32_t element_count)
|
|
{
|
|
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
int32_t bucket_count = get_bucket_count(element_count);
|
|
|
|
if (bucket_count) {
|
|
flecs_map_rehash(map, bucket_count);
|
|
}
|
|
}
|
|
|
|
ecs_map_t* ecs_map_copy(
|
|
ecs_map_t *map)
|
|
{
|
|
if (!ecs_map_is_initialized(map)) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_size_t elem_size = map->elem_size;
|
|
ecs_map_t *result = _ecs_map_new(map->elem_size,
|
|
map->allocator,
|
|
ecs_map_count(map));
|
|
|
|
ecs_map_iter_t it = ecs_map_iter(map);
|
|
ecs_map_key_t key;
|
|
void *ptr;
|
|
while ((ptr = _ecs_map_next(&it, elem_size, &key))) {
|
|
_ecs_map_set(result, elem_size, key, ptr);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @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_USE_OS_ALLOC
|
|
// #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, NULL);
|
|
#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;
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
ecs_map_init(&map->impl, ecs_hm_bucket_t, allocator, 0);
|
|
}
|
|
|
|
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);
|
|
ecs_hm_bucket_t *bucket;
|
|
uint64_t key;
|
|
while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, &key))) {
|
|
ecs_vec_fini(a, &bucket->keys, map->key_size);
|
|
ecs_vec_fini(a, &bucket->values, map->value_size);
|
|
}
|
|
|
|
ecs_map_fini(&map->impl);
|
|
}
|
|
|
|
void flecs_hashmap_copy(
|
|
const ecs_hashmap_t *src,
|
|
ecs_hashmap_t *dst)
|
|
{
|
|
if (dst != src) {
|
|
*dst = *src;
|
|
}
|
|
|
|
ecs_map_t *impl = ecs_map_copy(&dst->impl);
|
|
ecs_allocator_t *a = impl->allocator;
|
|
dst->impl = *impl;
|
|
ecs_os_free(impl);
|
|
|
|
ecs_map_iter_t it = ecs_map_iter(&dst->impl);
|
|
ecs_hm_bucket_t *bucket;
|
|
while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) {
|
|
bucket->keys = ecs_vec_copy(a, &bucket->keys, dst->key_size);
|
|
bucket->values = ecs_vec_copy(a, &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(&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 *bucket = ecs_map_ensure(&map->impl, ecs_hm_bucket_t, hash);
|
|
ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
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(&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_map_remove(&map->impl, hash);
|
|
}
|
|
}
|
|
|
|
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(&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)) {
|
|
bucket = it->bucket = ecs_map_next(&it->it, ecs_hm_bucket_t, NULL);
|
|
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/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->cur;
|
|
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->cur = 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)
|
|
{
|
|
return (ecs_stack_cursor_t){
|
|
.cur = stack->cur, .sp = stack->cur->sp
|
|
};
|
|
}
|
|
|
|
void flecs_stack_restore_cursor(
|
|
ecs_stack_t *stack,
|
|
const ecs_stack_cursor_t *cursor)
|
|
{
|
|
ecs_stack_page_t *cur = cursor->cur;
|
|
if (!cur) {
|
|
return;
|
|
}
|
|
|
|
if (cur == stack->cur) {
|
|
if (cursor->sp > stack->cur->sp) {
|
|
return;
|
|
}
|
|
} else if (cur->id > stack->cur->id) {
|
|
return;
|
|
}
|
|
|
|
stack->cur = cursor->cur;
|
|
stack->cur->sp = cursor->sp;
|
|
}
|
|
|
|
void flecs_stack_reset(
|
|
ecs_stack_t *stack)
|
|
{
|
|
stack->cur = &stack->first;
|
|
stack->first.sp = 0;
|
|
}
|
|
|
|
void flecs_stack_init(
|
|
ecs_stack_t *stack)
|
|
{
|
|
ecs_os_zeromem(stack);
|
|
stack->cur = &stack->first;
|
|
stack->first.data = NULL;
|
|
}
|
|
|
|
void flecs_stack_fini(
|
|
ecs_stack_t *stack)
|
|
{
|
|
ecs_stack_page_t *next, *cur = &stack->first;
|
|
ecs_assert(stack->cur == &stack->first, ECS_LEAK_DETECTED, NULL);
|
|
ecs_assert(stack->cur->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 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)
|
|
{
|
|
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 */
|
|
ecs_data_t *data = &table->data;
|
|
sw = sparse_column->sw_column =
|
|
&data->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->data.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
|
|
|
|
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_vec_t *sw_terms = &entity_filter->sw_terms;
|
|
ecs_vec_t *bs_terms = &entity_filter->bs_terms;
|
|
ecs_vec_reset_t(a, sw_terms, flecs_switch_term_t);
|
|
ecs_vec_reset_t(a, bs_terms, flecs_bitset_term_t);
|
|
ecs_term_t *terms = filter->terms;
|
|
int32_t i, term_count = filter->term_count;
|
|
entity_filter->has_filter = false;
|
|
|
|
/* 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(id);
|
|
el->sw_column = NULL;
|
|
ids[field] = id;
|
|
entity_filter->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_search(world, table, bs_id, 0);
|
|
|
|
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;
|
|
entity_filter->has_filter = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void flecs_entity_filter_fini(
|
|
ecs_world_t *world,
|
|
ecs_entity_filter_t *entity_filter)
|
|
{
|
|
ecs_allocator_t *a = &world->allocator;
|
|
ecs_vec_fini_t(a, &entity_filter->sw_terms, flecs_switch_term_t);
|
|
ecs_vec_fini_t(a, &entity_filter->bs_terms, flecs_bitset_term_t);
|
|
}
|
|
|
|
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_table_range_t *range = &it->range;
|
|
bool found = false, next_table = true;
|
|
|
|
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 {
|
|
found = true;
|
|
next_table = false;
|
|
}
|
|
}
|
|
|
|
if (sw_terms) {
|
|
if (flecs_entity_filter_switch_next(table, it, found) == -1) {
|
|
/* No more elements in sparse column */
|
|
if (found) {
|
|
/* Try again */
|
|
next_table = true;
|
|
found = false;
|
|
} else {
|
|
/* Nothing found */
|
|
it->bs_offset = 0;
|
|
break;
|
|
}
|
|
} else {
|
|
found = true;
|
|
next_table = false;
|
|
it->bs_offset = range->offset + range->count;
|
|
}
|
|
}
|
|
} while (!found);
|
|
|
|
if (!found) {
|
|
return 1;
|
|
} else if (next_table) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @file addons/log.c
|
|
* @brief Log addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_LOG
|
|
|
|
#include <ctype.h>
|
|
|
|
void ecs_colorize_buf(
|
|
char *msg,
|
|
bool enable_colors,
|
|
ecs_strbuf_t *buf)
|
|
{
|
|
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);
|
|
ecs_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;
|
|
}
|
|
|
|
bool _ecs_assert(
|
|
bool condition,
|
|
int32_t err,
|
|
const char *cond_str,
|
|
const char *file,
|
|
int32_t line,
|
|
const char *fmt,
|
|
...)
|
|
{
|
|
if (!condition) {
|
|
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;
|
|
}
|
|
|
|
return condition;
|
|
}
|
|
|
|
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_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;
|
|
}
|
|
|
|
bool _ecs_assert(
|
|
bool condition,
|
|
int32_t error_code,
|
|
const char *condition_str,
|
|
const char *file,
|
|
int32_t line,
|
|
const char *fmt,
|
|
...)
|
|
{
|
|
(void)condition;
|
|
(void)error_code;
|
|
(void)condition_str;
|
|
(void)file;
|
|
(void)line;
|
|
(void)fmt;
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
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/pipeline/worker.c
|
|
* @brief Functions for running pipelines on one or more threads.
|
|
*/
|
|
|
|
/**
|
|
* @file addons/system/system.c
|
|
* @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
|
|
|
|
|
|
#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 */
|
|
bool multi_threaded; /* Whether systems can be ran multi threaded */
|
|
bool no_readonly; /* Whether systems are staged or not */
|
|
} ecs_pipeline_op_t;
|
|
|
|
typedef 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 */
|
|
} ecs_pipeline_state_t;
|
|
|
|
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);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// Worker API
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool flecs_worker_begin(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_pipeline_state_t *pq,
|
|
bool start_of_frame);
|
|
|
|
void flecs_worker_end(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage);
|
|
|
|
bool flecs_worker_sync(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_pipeline_state_t *pq,
|
|
ecs_pipeline_op_t **cur_op,
|
|
int32_t *cur_i);
|
|
|
|
void flecs_workers_progress(
|
|
ecs_world_t *world,
|
|
ecs_pipeline_state_t *pq,
|
|
ecs_ftime_t delta_time);
|
|
|
|
#endif
|
|
|
|
|
|
typedef struct ecs_worker_state_t {
|
|
ecs_stage_t *stage;
|
|
ecs_pipeline_state_t *pq;
|
|
} ecs_worker_state_t;
|
|
|
|
/* Worker thread */
|
|
static
|
|
void* flecs_worker(void *arg) {
|
|
ecs_worker_state_t *state = arg;
|
|
ecs_stage_t *stage = state->stage;
|
|
ecs_pipeline_state_t *pq = state->pq;
|
|
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((ecs_world_t*)stage, pq, world->info.delta_time);
|
|
|
|
ecs_set_scope((ecs_world_t*)stage, old_scope);
|
|
}
|
|
|
|
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);
|
|
|
|
ecs_os_free(state);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
bool flecs_is_multithreaded(
|
|
ecs_world_t *world)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
return ecs_get_stage_count(world) > 1;
|
|
}
|
|
|
|
static
|
|
bool flecs_is_main_thread(
|
|
ecs_stage_t *stage)
|
|
{
|
|
return !stage->id;
|
|
}
|
|
|
|
/* Start threads */
|
|
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);
|
|
|
|
int32_t i;
|
|
for (i = 0; i < threads - 1; i ++) {
|
|
ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i + 1);
|
|
ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_poly_assert(stage, ecs_stage_t);
|
|
|
|
ecs_entity_t pipeline = world->pipeline;
|
|
ecs_assert(pipeline != 0, ECS_INVALID_OPERATION, NULL);
|
|
const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline);
|
|
ecs_assert(pqc != NULL, ECS_INVALID_OPERATION, NULL);
|
|
ecs_pipeline_state_t *pq = pqc->state;
|
|
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_worker_state_t *state = ecs_os_calloc_t(ecs_worker_state_t);
|
|
state->stage = stage;
|
|
state->pq = pq;
|
|
stage->thread = ecs_os_thread_new(flecs_worker, state);
|
|
ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL);
|
|
}
|
|
}
|
|
|
|
/* 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 */
|
|
static
|
|
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");
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* Signal workers that they can start/resume work */
|
|
static
|
|
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);
|
|
}
|
|
|
|
/** Stop workers */
|
|
static
|
|
bool ecs_stop_threads(
|
|
ecs_world_t *world)
|
|
{
|
|
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;
|
|
}
|
|
stage->thread = 0;
|
|
};
|
|
|
|
/* If no threads are active, just return */
|
|
if (!threads_active) {
|
|
return false;
|
|
}
|
|
|
|
/* 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 ++) {
|
|
ecs_os_thread_join(stages[i].thread);
|
|
stages[i].thread = 0;
|
|
}
|
|
|
|
world->flags &= ~EcsWorldQuitWorkers;
|
|
ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Deinitialize stages */
|
|
ecs_set_stage_count(world, 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* -- Private functions -- */
|
|
bool flecs_worker_begin(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_pipeline_state_t *pq,
|
|
bool start_of_frame)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
ecs_poly_assert(stage, ecs_stage_t);
|
|
bool main_thread = flecs_is_main_thread(stage);
|
|
bool multi_threaded = flecs_is_multithreaded(world);
|
|
|
|
if (main_thread) {
|
|
if (ecs_stage_is_readonly(world)) {
|
|
ecs_assert(!pq->no_readonly, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_readonly_end(world);
|
|
pq->no_readonly = false;
|
|
}
|
|
|
|
flecs_pipeline_update(world, pq, start_of_frame);
|
|
}
|
|
|
|
ecs_pipeline_op_t *cur_op = pq->cur_op;
|
|
if (main_thread && (cur_op != NULL)) {
|
|
pq->no_readonly = cur_op->no_readonly;
|
|
if (!cur_op->no_readonly) {
|
|
ecs_readonly_begin(world);
|
|
}
|
|
|
|
ECS_BIT_COND(world->flags, EcsWorldMultiThreaded,
|
|
cur_op->multi_threaded);
|
|
ecs_assert(world->workers_waiting == 0,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
if (main_thread && multi_threaded) {
|
|
flecs_signal_workers(world);
|
|
}
|
|
|
|
return pq->cur_op != NULL;
|
|
}
|
|
|
|
void flecs_worker_end(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
ecs_poly_assert(stage, ecs_stage_t);
|
|
|
|
if (flecs_is_multithreaded(world)) {
|
|
if (flecs_is_main_thread(stage)) {
|
|
flecs_wait_for_sync(world);
|
|
} else {
|
|
flecs_sync_worker(world);
|
|
}
|
|
}
|
|
|
|
if (flecs_is_main_thread(stage)) {
|
|
if (ecs_stage_is_readonly(world)) {
|
|
ecs_readonly_end(world);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool flecs_worker_sync(
|
|
ecs_world_t *world,
|
|
ecs_stage_t *stage,
|
|
ecs_pipeline_state_t *pq,
|
|
ecs_pipeline_op_t **cur_op,
|
|
int32_t *cur_i)
|
|
{
|
|
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(pq->cur_op != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
bool main_thread = flecs_is_main_thread(stage);
|
|
|
|
/* Synchronize workers */
|
|
flecs_worker_end(world, stage);
|
|
|
|
/* Store the current state of the schedule after we synchronized the
|
|
* threads, to avoid race conditions. */
|
|
if (main_thread) {
|
|
pq->cur_op = *cur_op;
|
|
pq->cur_i = *cur_i;
|
|
}
|
|
|
|
/* Prepare state for running the next part of the schedule */
|
|
bool result = flecs_worker_begin(world, stage, pq, false);
|
|
*cur_op = pq->cur_op;
|
|
*cur_i = pq->cur_i;
|
|
return result;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* -- Public functions -- */
|
|
|
|
void ecs_set_threads(
|
|
ecs_world_t *world,
|
|
int32_t threads)
|
|
{
|
|
ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL);
|
|
|
|
int32_t stage_count = ecs_get_stage_count(world);
|
|
|
|
if (stage_count != threads) {
|
|
/* Stop existing threads */
|
|
if (stage_count > 1) {
|
|
if (ecs_stop_threads(world)) {
|
|
ecs_os_cond_free(world->worker_cond);
|
|
ecs_os_cond_free(world->sync_cond);
|
|
ecs_os_mutex_free(world->sync_mutex);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#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 {
|
|
ecs_map_t *ids;
|
|
ecs_map_t *wildcard_ids;
|
|
bool write_barrier;
|
|
} 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, bool, id)) {
|
|
result = WriteStateToStage;
|
|
}
|
|
} else {
|
|
ecs_map_iter_t it = ecs_map_iter(write_state->ids);
|
|
ecs_id_t write_id;
|
|
while (ecs_map_next(&it, bool, &write_id)) {
|
|
if (ecs_id_match(write_id, id)) {
|
|
return WriteStateToStage;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (write_state->wildcard_ids) {
|
|
ecs_map_iter_t it = ecs_map_iter(write_state->wildcard_ids);
|
|
ecs_id_t write_id;
|
|
while (ecs_map_next(&it, bool, &write_id)) {
|
|
if (ecs_id_match(id, write_id)) {
|
|
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;
|
|
}
|
|
|
|
bool value = true;
|
|
ecs_map_set(ids, id, &value);
|
|
}
|
|
|
|
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;
|
|
default:
|
|
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;
|
|
}
|
|
default:
|
|
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_search(it->real_world, it->table,
|
|
ecs_poly_id(EcsSystem), 0);
|
|
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_write_state_t ws = {
|
|
.ids = ecs_map_new(bool, &world->allocator, ECS_HI_COMPONENT_ID),
|
|
.wildcard_ids = ecs_map_new(bool, &world->allocator, ECS_HI_COMPONENT_ID)
|
|
};
|
|
|
|
ecs_pipeline_op_t *op = NULL;
|
|
ecs_allocator_t *a = &world->allocator;
|
|
|
|
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_index(world, it.table, EcsEmpty) == -1;
|
|
|
|
int32_t i;
|
|
for (i = 0; i < it.count; i ++) {
|
|
ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t);
|
|
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;
|
|
}
|
|
|
|
/* 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_free(ws.ids);
|
|
ecs_map_free(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_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_t);
|
|
|
|
#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;
|
|
}
|
|
|
|
EcsPipeline *pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline);
|
|
flecs_pipeline_update(world, pq->state, true);
|
|
flecs_run_pipeline((ecs_world_t*)flecs_stage_from_world(&world),
|
|
pq->state, delta_time);
|
|
}
|
|
|
|
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);
|
|
|
|
if (!flecs_worker_begin(world, stage, pq, true)) {
|
|
return;
|
|
}
|
|
|
|
ecs_time_t st = {0};
|
|
bool main_thread = !stage_index;
|
|
bool measure_time = main_thread && (world->flags & EcsWorldMeasureSystemTime);
|
|
ecs_pipeline_op_t *op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t);
|
|
int32_t i = 0;
|
|
|
|
do {
|
|
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;
|
|
|
|
if (i == count) {
|
|
break;
|
|
}
|
|
|
|
if (measure_time) {
|
|
ecs_time_measure(&st);
|
|
}
|
|
|
|
for (; i < count; i ++) {
|
|
/* Run system if:
|
|
* - this is the main thread, or if
|
|
* - the system is multithreaded
|
|
*/
|
|
if (main_thread || op->multi_threaded) {
|
|
ecs_entity_t system = systems[i];
|
|
const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem);
|
|
ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_t);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
if (measure_time) {
|
|
/* Don't include merge time in system time */
|
|
world->info.system_time_total +=
|
|
(ecs_ftime_t)ecs_time_measure(&st);
|
|
}
|
|
|
|
/* Synchronize workers, rebuild pipeline if necessary. Pass current op
|
|
* and system index to function, so we know where to resume from. */
|
|
} while (flecs_worker_sync(world, stage, pq, &op, &i));
|
|
|
|
if (measure_time) {
|
|
world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st);
|
|
}
|
|
|
|
flecs_worker_end(world, stage);
|
|
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
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");
|
|
|
|
int32_t thread_count = ecs_get_stage_count(world);
|
|
if (thread_count > 1) {
|
|
ecs_set_threads(world, 1);
|
|
}
|
|
world->pipeline = pipeline;
|
|
if (thread_count > 1) {
|
|
ecs_set_threads(world, thread_count);
|
|
}
|
|
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_assert(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_assert(query->filter.terms[0].id == EcsSystem,
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
|
|
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;
|
|
}
|
|
|
|
/* -- 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);
|
|
|
|
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_init(world, &(ecs_pipeline_desc_t){
|
|
.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/monitor.c
|
|
* @brief Monitor addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_MONITOR
|
|
|
|
ECS_COMPONENT_DECLARE(FlecsMonitor);
|
|
ECS_COMPONENT_DECLARE(EcsWorldStats);
|
|
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 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_init(world, &(ecs_system_desc_t){
|
|
.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_init(world, &(ecs_system_desc_t){
|
|
.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_init(world, &(ecs_system_desc_t){
|
|
.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_init(world, &(ecs_system_desc_t){
|
|
.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_init(world, &(ecs_system_desc_t){
|
|
.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_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");
|
|
|
|
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/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;
|
|
|
|
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);
|
|
|
|
timer = ecs_set(world, timer, EcsTimer, {
|
|
.timeout = interval,
|
|
.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_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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void FlecsTimerImport(
|
|
ecs_world_t *world)
|
|
{
|
|
ECS_MODULE(world, FlecsTimer);
|
|
|
|
ECS_IMPORT(world, FlecsPipeline);
|
|
|
|
ecs_set_name_prefix(world, "Ecs");
|
|
|
|
flecs_bootstrap_component(world, EcsTimer);
|
|
flecs_bootstrap_component(world, EcsRateFilter);
|
|
|
|
/* Add EcsTickSource to timers and rate filters */
|
|
ecs_system_init(world, &(ecs_system_desc_t){
|
|
.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 = EcsOr, .inout = EcsIn },
|
|
{ .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut}
|
|
},
|
|
.callback = AddTickSource
|
|
});
|
|
|
|
/* Timer handling */
|
|
ecs_system_init(world, &(ecs_system_desc_t){
|
|
.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_init(world, &(ecs_system_desc_t){
|
|
.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_init(world, &(ecs_system_desc_t){
|
|
.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/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)
|
|
{
|
|
memcpy(type_name, func_name + ECS_FUNC_NAME_FRONT(const char*, type_name), 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* cpp_func_rchr(
|
|
const char *func_name,
|
|
ecs_size_t func_name_len,
|
|
char ch)
|
|
{
|
|
const char *r = strrchr(func_name, ch);
|
|
if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))) {
|
|
return NULL;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static
|
|
const char* 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)
|
|
{
|
|
ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len);
|
|
const char *start = cpp_func_rchr(func_name, f_len, ' ');
|
|
start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ')'));
|
|
start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ':'));
|
|
start = cpp_func_max(start, cpp_func_rchr(func_name, f_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) - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK)));
|
|
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) {
|
|
const char *ptr = strrchr(type_name, ':');
|
|
ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL);
|
|
if (ptr) {
|
|
ptr --;
|
|
ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL);
|
|
ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name);
|
|
if (name_path_len <= ecs_os_strlen(path)) {
|
|
if (!ecs_os_strncmp(type_name, path, name_path_len)) {
|
|
type_name = &type_name[name_path_len + 2];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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_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);
|
|
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);
|
|
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)
|
|
{
|
|
ecs_suspend_readonly_state_t readonly_state;
|
|
world = flecs_suspend_readonly(world, &readonly_state);
|
|
ecs_add_id(world, id, EcsExclusive);
|
|
ecs_add_id(world, id, EcsOneOf);
|
|
ecs_add_id(world, id, EcsTag);
|
|
flecs_resume_readonly(world, &readonly_state);
|
|
}
|
|
|
|
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_init(world, &(ecs_entity_desc_t){
|
|
.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
|
|
|
|
ecs_set_id(world, id, parent, sizeof(int), &value);
|
|
|
|
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;
|
|
}
|
|
|
|
#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>
|
|
|
|
static
|
|
ecs_os_thread_t win_thread_new(
|
|
ecs_os_thread_callback_t callback,
|
|
void *arg)
|
|
{
|
|
HANDLE *thread = ecs_os_malloc_t(HANDLE);
|
|
*thread = CreateThread(
|
|
NULL, 0, (LPTHREAD_START_ROUTINE)callback, arg, 0, NULL);
|
|
return (ecs_os_thread_t)(uintptr_t)thread;
|
|
}
|
|
|
|
static
|
|
void* win_thread_join(
|
|
ecs_os_thread_t thr)
|
|
{
|
|
HANDLE *thread = (HANDLE*)(uintptr_t)thr;
|
|
DWORD r = WaitForSingleObject(*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(count);
|
|
}
|
|
|
|
static
|
|
int32_t win_adec(
|
|
int32_t *count)
|
|
{
|
|
return InterlockedDecrement(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
|
|
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 double win_time_freq;
|
|
static ULONG win_current_resolution;
|
|
|
|
static
|
|
void win_enable_high_timer_resolution(bool enable)
|
|
{
|
|
HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll");
|
|
if (!hntdll) {
|
|
return;
|
|
}
|
|
|
|
LONG (__stdcall *pNtSetTimerResolution)(
|
|
ULONG desired, BOOLEAN set, ULONG * current);
|
|
|
|
pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*))
|
|
GetProcAddress(hntdll, "NtSetTimerResolution");
|
|
|
|
if(!pNtSetTimerResolution) {
|
|
return;
|
|
}
|
|
|
|
ULONG current, resolution = 10000; /* 1 ms */
|
|
|
|
if (!enable && win_current_resolution) {
|
|
pNtSetTimerResolution(win_current_resolution, 0, ¤t);
|
|
win_current_resolution = 0;
|
|
return;
|
|
} else if (!enable) {
|
|
return;
|
|
}
|
|
|
|
if (resolution == win_current_resolution) {
|
|
return;
|
|
}
|
|
|
|
if (win_current_resolution) {
|
|
pNtSetTimerResolution(win_current_resolution, 0, ¤t);
|
|
}
|
|
|
|
if (pNtSetTimerResolution(resolution, 1, ¤t)) {
|
|
/* Try setting a lower resolution */
|
|
resolution *= 2;
|
|
if(pNtSetTimerResolution(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)(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.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
|
|
|
|
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
|
|
/* Unsupported */
|
|
abort();
|
|
#endif
|
|
}
|
|
|
|
static
|
|
int32_t posix_adec(
|
|
int32_t *count)
|
|
{
|
|
int32_t value;
|
|
#ifdef __GNUC__
|
|
value = __sync_sub_and_fetch (count, 1);
|
|
return value;
|
|
#else
|
|
/* Unsupported */
|
|
abort();
|
|
#endif
|
|
}
|
|
|
|
static
|
|
int64_t posix_lainc(
|
|
int64_t *count)
|
|
{
|
|
int64_t value;
|
|
#ifdef __GNUC__
|
|
value = __sync_add_and_fetch (count, 1);
|
|
return value;
|
|
#else
|
|
/* Unsupported */
|
|
abort();
|
|
#endif
|
|
}
|
|
|
|
static
|
|
int64_t posix_ladec(
|
|
int64_t *count)
|
|
{
|
|
int64_t value;
|
|
#ifdef __GNUC__
|
|
value = __sync_sub_and_fetch (count, 1);
|
|
return value;
|
|
#else
|
|
/* Unsupported */
|
|
abort();
|
|
#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.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/plecs.c
|
|
* @brief Plecs addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_PLECS
|
|
|
|
#include <ctype.h>
|
|
|
|
#define TOK_NEWLINE '\n'
|
|
#define TOK_WITH "with"
|
|
#define TOK_USING "using"
|
|
#define TOK_CONST "const"
|
|
|
|
#define STACK_MAX_SIZE (64)
|
|
|
|
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];
|
|
int32_t using_frames[STACK_MAX_SIZE];
|
|
int32_t sp;
|
|
int32_t with_frame;
|
|
int32_t using_frame;
|
|
|
|
char *annot[STACK_MAX_SIZE];
|
|
int32_t annot_count;
|
|
|
|
#ifdef FLECS_EXPR
|
|
ecs_vars_t vars;
|
|
char var_name[256];
|
|
#endif
|
|
|
|
bool with_stmt;
|
|
bool scope_assign_stmt;
|
|
bool using_stmt;
|
|
bool assign_stmt;
|
|
bool isa_stmt;
|
|
bool decl_stmt;
|
|
bool decl_type;
|
|
bool const_stmt;
|
|
|
|
int32_t errors;
|
|
} plecs_state_t;
|
|
|
|
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 */
|
|
#ifdef FLECS_EXPR
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
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, "_");
|
|
if (is_anonymous) {
|
|
path = NULL;
|
|
e = ecs_new_id(world);
|
|
}
|
|
|
|
if (!e) {
|
|
e = plecs_lookup(world, path, state, rel, is_subject);
|
|
}
|
|
|
|
if (!e) {
|
|
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;
|
|
}
|
|
|
|
if (!is_subject) {
|
|
/* If this is not a subject create an existing empty id, which
|
|
* ensures that scope & with are not applied */
|
|
e = ecs_new_id(world);
|
|
}
|
|
|
|
e = ecs_add_path(world, e, 0, path);
|
|
ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL);
|
|
} 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
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->using_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 = (char*)ecs_parse_whitespace(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 *pred_name = term->first.name;
|
|
const char *subj_name = term->src.name;
|
|
const char *obj_name = term->second.name;
|
|
|
|
if (!subj_name) {
|
|
subj_name = plecs_set_mask_to_name(term->src.flags);
|
|
}
|
|
if (!obj_name) {
|
|
obj_name = plecs_set_mask_to_name(term->second.flags);
|
|
}
|
|
|
|
if (!ecs_term_id_is_set(&term->first)) {
|
|
ecs_parser_error(name, expr, column, "missing predicate 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 pred = plecs_ensure_entity(world, state, pred_name, 0, pred_as_subj);
|
|
ecs_entity_t subj = plecs_ensure_entity(world, state, subj_name, pred, true);
|
|
ecs_entity_t obj = 0;
|
|
|
|
if (ecs_term_id_is_set(&term->second)) {
|
|
obj = plecs_ensure_entity(world, state, obj_name, pred,
|
|
state->assign_stmt == false);
|
|
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->using_stmt && (obj || subj)) {
|
|
ecs_parser_error(name, expr, column,
|
|
"invalid predicate/object in using statement");
|
|
return -1;
|
|
}
|
|
|
|
if (state->isa_stmt) {
|
|
pred = ecs_pair(EcsIsA, pred);
|
|
}
|
|
|
|
if (subj) {
|
|
if (!obj) {
|
|
ecs_add_id(world, subj, pred);
|
|
state->last_assign_id = pred;
|
|
} else {
|
|
ecs_add_pair(world, subj, pred, obj);
|
|
state->last_object = obj;
|
|
state->last_assign_id = ecs_pair(pred, obj);
|
|
}
|
|
state->last_predicate = pred;
|
|
state->last_subject = subj;
|
|
|
|
pred_as_subj = false;
|
|
} else {
|
|
if (!obj) {
|
|
/* If no subject or object were provided, use predicate as subj
|
|
* unless the expression explictly 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 (state->using_stmt) {
|
|
ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(obj == 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
state->using[state->using_frame ++] = pred;
|
|
state->using_frames[state->sp] = state->using_frame;
|
|
|
|
/* If this is not a with/using clause, add with frames to subject */
|
|
} else {
|
|
if (subj) {
|
|
int32_t i, frame_count = state->with_frames[state->sp];
|
|
for (i = 0; i < frame_count; i ++) {
|
|
ecs_add_id(world, subj, state->with[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
#ifdef FLECS_EXPR
|
|
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_value_t value = {0};
|
|
ecs_expr_var_t *var = NULL;
|
|
if (state->last_assign_id) {
|
|
value.type = state->last_assign_id;
|
|
value.ptr = ecs_value_new(world, state->last_assign_id);
|
|
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) {
|
|
return NULL;
|
|
}
|
|
|
|
if (var) {
|
|
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 {
|
|
var = ecs_vars_declare_w_value(
|
|
&state->vars, state->var_name, &value);
|
|
if (!var) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
#endif
|
|
|
|
static
|
|
const char* plecs_parse_assign_expr(
|
|
ecs_world_t *world,
|
|
const char *name,
|
|
const char *expr,
|
|
const char *ptr,
|
|
plecs_state_t *state)
|
|
{
|
|
(void)world;
|
|
|
|
if (state->const_stmt) {
|
|
#ifdef FLECS_EXPR
|
|
return plecs_parse_assign_var_expr(world, name, expr, ptr, state);
|
|
#else
|
|
ecs_parser_error(name, expr, ptr - expr,
|
|
"variables not supported, missing FLECS_EXPR addon");
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifndef FLECS_EXPR
|
|
ecs_parser_error(name, expr, ptr - expr,
|
|
"cannot parse value, missing FLECS_EXPR addon");
|
|
return NULL;
|
|
#else
|
|
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;
|
|
}
|
|
|
|
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);
|
|
#endif
|
|
|
|
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_using_stmt(
|
|
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;
|
|
}
|
|
|
|
/* Add following expressions to using list */
|
|
state->using_stmt = true;
|
|
|
|
return ptr + 5;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef FLECS_EXPR
|
|
static
|
|
const char* plecs_parse_const_stmt(
|
|
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 || ptr[0] != '=') {
|
|
return NULL;
|
|
}
|
|
state->const_stmt = true;
|
|
return ptr + 1;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
|
|
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;
|
|
} else {
|
|
state->scope[state->sp] = state->scope[state->sp - 1];
|
|
state->default_scope_type[state->sp] =
|
|
state->default_scope_type[state->sp - 1];
|
|
}
|
|
|
|
state->using_frames[state->sp] = state->using_frame;
|
|
state->with_frames[state->sp] = state->with_frame;
|
|
state->with_stmt = false;
|
|
|
|
#ifdef FLECS_EXPR
|
|
ecs_vars_push(&state->vars);
|
|
#endif
|
|
|
|
return ptr;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
state->with_frame = state->with_frames[state->sp];
|
|
state->using_frame = state->using_frames[state->sp];
|
|
state->last_subject = 0;
|
|
state->assign_stmt = false;
|
|
|
|
#ifdef FLECS_EXPR
|
|
ecs_vars_pop(&state->vars);
|
|
#endif
|
|
|
|
return ptr;
|
|
}
|
|
|
|
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);
|
|
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 = ecs_parse_fluff(ptr, NULL);
|
|
} 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->using_stmt = false;
|
|
state->decl_stmt = false;
|
|
state->const_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 = ecs_parse_fluff(ptr, NULL);
|
|
|
|
char ch = ptr[0];
|
|
|
|
if (!ch) {
|
|
goto done;
|
|
} else if (ch == '{') {
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
goto scope_open;
|
|
} else if (ch == '}') {
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
goto scope_close;
|
|
} else if (ch == '-') {
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
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(name, expr, ptr, state);
|
|
if (!ptr) goto error;
|
|
goto term_expr;
|
|
} 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;
|
|
#ifdef FLECS_EXPR
|
|
} else if (!ecs_os_strncmp(ptr, TOK_CONST " ", 6)) {
|
|
ptr = plecs_parse_const_stmt(name, expr, ptr, state);
|
|
if (!ptr) goto error;
|
|
|
|
goto assign_expr;
|
|
#endif
|
|
} else {
|
|
goto term_expr;
|
|
}
|
|
|
|
term_expr:
|
|
if (!ptr[0]) {
|
|
goto done;
|
|
}
|
|
|
|
if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) {
|
|
goto error;
|
|
}
|
|
|
|
const char *tptr = ecs_parse_whitespace(ptr);
|
|
if (flecs_isident(tptr[0])) {
|
|
if (state->decl_stmt) {
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"unexpected ' ' in declaration statement");
|
|
}
|
|
ptr = tptr;
|
|
goto decl_stmt;
|
|
}
|
|
|
|
ptr = ecs_parse_fluff(ptr, NULL);
|
|
|
|
if (!state->using_stmt) {
|
|
if (ptr[0] == ':' && ptr[1] == '-') {
|
|
ptr = ecs_parse_fluff(ptr + 2, NULL);
|
|
goto assign_stmt;
|
|
} else if (ptr[0] == ':') {
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
goto inherit_stmt;
|
|
} else if (ptr[0] == ',') {
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
goto term_expr;
|
|
} else if (ptr[0] == '{') {
|
|
if (state->assign_stmt) {
|
|
goto assign_expr;
|
|
} else {
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
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 = ecs_parse_fluff(ptr, NULL);
|
|
|
|
/* Assignment without a preceding component */
|
|
if (ptr[0] == '{') {
|
|
goto assign_expr;
|
|
}
|
|
|
|
/* Expect component identifiers */
|
|
goto term_expr;
|
|
|
|
assign_expr:
|
|
ptr = plecs_parse_assign_expr(world, name, expr, ptr, state);
|
|
if (!ptr) goto error;
|
|
|
|
ptr = ecs_parse_fluff(ptr, NULL);
|
|
if (ptr[0] == ',') {
|
|
ptr ++;
|
|
goto term_expr;
|
|
} else if (ptr[0] == '{') {
|
|
if (state->const_stmt) {
|
|
#ifdef FLECS_EXPR
|
|
const 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);
|
|
goto done;
|
|
}
|
|
#else
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"variables not supported, missing FLECS_EXPR addon");
|
|
#endif
|
|
}
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"unexpected '{' after assignment");
|
|
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;
|
|
}
|
|
|
|
int ecs_plecs_from_str(
|
|
ecs_world_t *world,
|
|
const char *name,
|
|
const char *expr)
|
|
{
|
|
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);
|
|
|
|
#ifdef FLECS_EXPR
|
|
ecs_vars_init(world, &state.vars);
|
|
#endif
|
|
|
|
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.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;
|
|
}
|
|
|
|
#ifdef FLECS_EXPR
|
|
ecs_vars_fini(&state.vars);
|
|
#endif
|
|
return 0;
|
|
error:
|
|
#ifdef FLECS_EXPR
|
|
ecs_vars_fini(&state.vars);
|
|
#endif
|
|
ecs_set_scope(world, state.scope[0]);
|
|
ecs_set_with(world, prev_with);
|
|
ecs_term_fini(&term);
|
|
return -1;
|
|
}
|
|
|
|
int ecs_plecs_from_file(
|
|
ecs_world_t *world,
|
|
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);
|
|
|
|
int result = ecs_plecs_from_str(world, filename, content);
|
|
ecs_os_free(content);
|
|
return result;
|
|
error:
|
|
ecs_os_free(content);
|
|
return -1;
|
|
}
|
|
|
|
#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) {
|
|
path = ecs_get_fullpath(world, entity);
|
|
var_id = flecs_journal_entitystr(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/rules.c
|
|
* @brief Rules addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_RULES
|
|
|
|
#define ECS_RULE_MAX_VAR_COUNT (32)
|
|
|
|
#define RULE_PAIR_PREDICATE (1)
|
|
#define RULE_PAIR_OBJECT (2)
|
|
|
|
/* A rule pair contains a predicate and object that can be stored in a register. */
|
|
typedef struct ecs_rule_pair_t {
|
|
union {
|
|
int32_t reg;
|
|
ecs_entity_t id;
|
|
} first;
|
|
union {
|
|
int32_t reg;
|
|
ecs_entity_t id;
|
|
} second;
|
|
int32_t reg_mask; /* bit 1 = predicate, bit 2 = object */
|
|
|
|
bool transitive; /* Is predicate transitive */
|
|
bool final; /* Is predicate final */
|
|
bool reflexive; /* Is predicate reflexive */
|
|
bool acyclic; /* Is predicate acyclic */
|
|
bool second_0;
|
|
} ecs_rule_pair_t;
|
|
|
|
/* Filter for evaluating & reifing types and variables. Filters are created ad-
|
|
* hoc from pairs, and take into account all variables that had been resolved
|
|
* up to that point. */
|
|
typedef struct ecs_rule_filter_t {
|
|
ecs_id_t mask; /* Mask with wildcard in place of variables */
|
|
|
|
bool wildcard; /* Does the filter contain wildcards */
|
|
bool first_wildcard; /* Is predicate a wildcard */
|
|
bool second_wildcard; /* Is object a wildcard */
|
|
bool same_var; /* True if first & second are both the same variable */
|
|
|
|
int32_t hi_var; /* If hi part should be stored in var, this is the var id */
|
|
int32_t lo_var; /* If lo part should be stored in var, this is the var id */
|
|
} ecs_rule_filter_t;
|
|
|
|
/* A rule register stores temporary values for rule variables */
|
|
typedef enum ecs_rule_var_kind_t {
|
|
EcsRuleVarKindTable, /* Used for sorting, must be smallest */
|
|
EcsRuleVarKindEntity,
|
|
EcsRuleVarKindUnknown
|
|
} ecs_rule_var_kind_t;
|
|
|
|
/* Operations describe how the rule should be evaluated */
|
|
typedef enum ecs_rule_op_kind_t {
|
|
EcsRuleInput, /* Input placeholder, first instruction in every rule */
|
|
EcsRuleSelect, /* Selects all ables for a given predicate */
|
|
EcsRuleWith, /* Applies a filter to a table or entity */
|
|
EcsRuleSubSet, /* Finds all subsets for transitive relationship */
|
|
EcsRuleSuperSet, /* Finds all supersets for a transitive relationship */
|
|
EcsRuleStore, /* Store entity in table or entity variable */
|
|
EcsRuleEach, /* Forwards each entity in a table */
|
|
EcsRuleSetJmp, /* Set label for jump operation to one of two values */
|
|
EcsRuleJump, /* Jump to an operation label */
|
|
EcsRuleNot, /* Invert result of an operation */
|
|
EcsRuleInTable, /* Test if entity (subject) is in table (r_in) */
|
|
EcsRuleEq, /* Test if entity in (subject) and (r_in) are equal */
|
|
EcsRuleYield /* Yield result */
|
|
} ecs_rule_op_kind_t;
|
|
|
|
/* Single operation */
|
|
typedef struct ecs_rule_op_t {
|
|
ecs_rule_op_kind_t kind; /* What kind of operation is it */
|
|
ecs_rule_pair_t filter; /* Parameter that contains optional filter */
|
|
ecs_entity_t subject; /* If set, operation has a constant subject */
|
|
|
|
int32_t on_pass; /* Jump location when match succeeds */
|
|
int32_t on_fail; /* Jump location when match fails */
|
|
int32_t frame; /* Register frame */
|
|
|
|
int32_t term; /* Corresponding term index in signature */
|
|
int32_t r_in; /* Optional In/Out registers */
|
|
int32_t r_out;
|
|
|
|
bool has_in, has_out; /* Keep track of whether operation uses input
|
|
* and/or output registers. This helps with
|
|
* debugging rule programs. */
|
|
} ecs_rule_op_t;
|
|
|
|
/* With context. Shared with select. */
|
|
typedef struct ecs_rule_with_ctx_t {
|
|
ecs_id_record_t *idr; /* Currently evaluated table set */
|
|
ecs_table_cache_iter_t it;
|
|
int32_t column;
|
|
} ecs_rule_with_ctx_t;
|
|
|
|
/* Subset context */
|
|
typedef struct ecs_rule_subset_frame_t {
|
|
ecs_rule_with_ctx_t with_ctx;
|
|
ecs_table_t *table;
|
|
int32_t row;
|
|
int32_t column;
|
|
} ecs_rule_subset_frame_t;
|
|
|
|
typedef struct ecs_rule_subset_ctx_t {
|
|
ecs_rule_subset_frame_t storage[16]; /* Alloc-free array for small trees */
|
|
ecs_rule_subset_frame_t *stack;
|
|
int32_t sp;
|
|
} ecs_rule_subset_ctx_t;
|
|
|
|
/* Superset context */
|
|
typedef struct ecs_rule_superset_frame_t {
|
|
ecs_table_t *table;
|
|
int32_t column;
|
|
} ecs_rule_superset_frame_t;
|
|
|
|
typedef struct ecs_rule_superset_ctx_t {
|
|
ecs_rule_superset_frame_t storage[16]; /* Alloc-free array for small trees */
|
|
ecs_rule_superset_frame_t *stack;
|
|
ecs_id_record_t *idr;
|
|
int32_t sp;
|
|
} ecs_rule_superset_ctx_t;
|
|
|
|
/* Each context */
|
|
typedef struct ecs_rule_each_ctx_t {
|
|
int32_t row; /* Currently evaluated row in evaluated table */
|
|
} ecs_rule_each_ctx_t;
|
|
|
|
/* Jump context */
|
|
typedef struct ecs_rule_setjmp_ctx_t {
|
|
int32_t label; /* Operation label to jump to */
|
|
} ecs_rule_setjmp_ctx_t;
|
|
|
|
/* Operation context. This is a per-operation, per-iterator structure that
|
|
* stores information for stateful operations. */
|
|
typedef struct ecs_rule_op_ctx_t {
|
|
union {
|
|
ecs_rule_subset_ctx_t subset;
|
|
ecs_rule_superset_ctx_t superset;
|
|
ecs_rule_with_ctx_t with;
|
|
ecs_rule_each_ctx_t each;
|
|
ecs_rule_setjmp_ctx_t setjmp;
|
|
} is;
|
|
} ecs_rule_op_ctx_t;
|
|
|
|
/* Rule variables allow for the rule to be parameterized */
|
|
typedef struct ecs_rule_var_t {
|
|
ecs_rule_var_kind_t kind;
|
|
char *name; /* Variable name */
|
|
int32_t id; /* Unique variable id */
|
|
int32_t other; /* Id to table variable (-1 if none exists) */
|
|
int32_t occurs; /* Number of occurrences (used for operation ordering) */
|
|
int32_t depth; /* Depth in dependency tree (used for operation ordering) */
|
|
bool marked; /* Used for cycle detection */
|
|
} ecs_rule_var_t;
|
|
|
|
/* Variable ids per term */
|
|
typedef struct ecs_rule_term_vars_t {
|
|
int32_t first;
|
|
int32_t src;
|
|
int32_t second;
|
|
} ecs_rule_term_vars_t;
|
|
|
|
/* Top-level rule datastructure */
|
|
struct ecs_rule_t {
|
|
ecs_header_t hdr;
|
|
|
|
ecs_rule_op_t *operations; /* Operations array */
|
|
ecs_filter_t filter; /* Filter of rule */
|
|
|
|
/* Variable array */
|
|
ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT];
|
|
|
|
/* Passed to iterator */
|
|
char *var_names[ECS_RULE_MAX_VAR_COUNT];
|
|
|
|
/* Variable ids used in terms */
|
|
ecs_rule_term_vars_t term_vars[ECS_RULE_MAX_VAR_COUNT];
|
|
|
|
/* Variable evaluation order */
|
|
int32_t var_eval_order[ECS_RULE_MAX_VAR_COUNT];
|
|
|
|
int32_t var_count; /* Number of variables in signature */
|
|
int32_t subj_var_count;
|
|
int32_t frame_count; /* Number of register frames */
|
|
int32_t operation_count; /* Number of operations in rule */
|
|
|
|
ecs_iterable_t iterable; /* Iterable mixin */
|
|
ecs_poly_dtor_t dtor;
|
|
};
|
|
|
|
/* ecs_rule_t mixins */
|
|
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)
|
|
}
|
|
};
|
|
|
|
static
|
|
void rule_error(
|
|
const ecs_rule_t *rule,
|
|
const char *fmt,
|
|
...)
|
|
{
|
|
char *fstr = ecs_filter_str(rule->filter.world, &rule->filter);
|
|
va_list valist;
|
|
va_start(valist, fmt);
|
|
const char *name = NULL;
|
|
if (rule->filter.entity) {
|
|
name = ecs_get_name(rule->filter.world, rule->filter.entity);
|
|
}
|
|
ecs_parser_errorv(name, fstr, -1, fmt, valist);
|
|
va_end(valist);
|
|
ecs_os_free(fstr);
|
|
}
|
|
|
|
static
|
|
bool subj_is_set(
|
|
ecs_term_t *term)
|
|
{
|
|
return ecs_term_id_is_set(&term->src);
|
|
}
|
|
|
|
static
|
|
bool obj_is_set(
|
|
ecs_term_t *term)
|
|
{
|
|
return ecs_term_id_is_set(&term->second) || ECS_HAS_ID_FLAG(term->id_flags, PAIR);
|
|
}
|
|
|
|
static
|
|
ecs_rule_op_t* create_operation(
|
|
ecs_rule_t *rule)
|
|
{
|
|
int32_t cur = rule->operation_count ++;
|
|
rule->operations = ecs_os_realloc(
|
|
rule->operations, (cur + 1) * ECS_SIZEOF(ecs_rule_op_t));
|
|
|
|
ecs_rule_op_t *result = &rule->operations[cur];
|
|
ecs_os_memset_t(result, 0, ecs_rule_op_t);
|
|
|
|
return result;
|
|
}
|
|
|
|
static
|
|
const char* get_var_name(const char *name) {
|
|
if (name && !ecs_os_strcmp(name, "This")) {
|
|
/* Make sure that both This and . resolve to the same variable */
|
|
name = ".";
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
static
|
|
ecs_rule_var_t* create_variable(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_kind_t kind,
|
|
const char *name)
|
|
{
|
|
int32_t cur = ++ rule->var_count;
|
|
|
|
name = get_var_name(name);
|
|
if (name && !ecs_os_strcmp(name, "*")) {
|
|
/* Wildcards are treated as anonymous variables */
|
|
name = NULL;
|
|
}
|
|
|
|
ecs_rule_var_t *var = &rule->vars[cur - 1];
|
|
if (name) {
|
|
var->name = ecs_os_strdup(name);
|
|
} else {
|
|
/* Anonymous register */
|
|
char name_buff[32];
|
|
ecs_os_sprintf(name_buff, "_%u", cur - 1);
|
|
var->name = ecs_os_strdup(name_buff);
|
|
}
|
|
|
|
var->kind = kind;
|
|
|
|
/* The variable id is the location in the variable array and also points to
|
|
* the register element that corresponds with the variable. */
|
|
var->id = cur - 1;
|
|
|
|
/* Depth is used to calculate how far the variable is from the root, where
|
|
* the root is the variable with 0 dependencies. */
|
|
var->depth = UINT8_MAX;
|
|
var->marked = false;
|
|
var->occurs = 0;
|
|
|
|
return var;
|
|
}
|
|
|
|
static
|
|
ecs_rule_var_t* create_anonymous_variable(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_kind_t kind)
|
|
{
|
|
return create_variable(rule, kind, NULL);
|
|
}
|
|
|
|
/* Find variable with specified name and type. If Unknown is provided as type,
|
|
* the function will return any variable with the provided name. The root
|
|
* variable can occur both as a table and entity variable, as some rules
|
|
* require that each entity in a table is iterated. In this case, there are two
|
|
* variables, one for the table and one for the entities in the table, that both
|
|
* have the same name. */
|
|
static
|
|
ecs_rule_var_t* find_variable(
|
|
const ecs_rule_t *rule,
|
|
ecs_rule_var_kind_t kind,
|
|
const char *name)
|
|
{
|
|
ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
name = get_var_name(name);
|
|
|
|
const ecs_rule_var_t *variables = rule->vars;
|
|
int32_t i, count = rule->var_count;
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
const ecs_rule_var_t *variable = &variables[i];
|
|
if (!ecs_os_strcmp(name, variable->name)) {
|
|
if (kind == EcsRuleVarKindUnknown || kind == variable->kind) {
|
|
return (ecs_rule_var_t*)variable;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Ensure variable with specified name and type exists. If an existing variable
|
|
* is found with an unknown type, its type will be overwritten with the
|
|
* specified type. During the variable ordering phase it is not yet clear which
|
|
* variable is the root. Which variable is the root determines its type, which
|
|
* is why during this phase variables are still untyped. */
|
|
static
|
|
ecs_rule_var_t* ensure_variable(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_kind_t kind,
|
|
const char *name)
|
|
{
|
|
ecs_rule_var_t *var = find_variable(rule, kind, name);
|
|
if (!var) {
|
|
var = create_variable(rule, kind, name);
|
|
} else {
|
|
if (var->kind == EcsRuleVarKindUnknown) {
|
|
var->kind = kind;
|
|
}
|
|
}
|
|
|
|
return var;
|
|
}
|
|
|
|
static
|
|
const char *term_id_var_name(
|
|
ecs_term_id_t *term_id)
|
|
{
|
|
if (term_id->flags & EcsIsVariable) {
|
|
if (term_id->name) {
|
|
return term_id->name;
|
|
} else if (term_id->id == EcsThis) {
|
|
return ".";
|
|
} else if (term_id->id == EcsWildcard) {
|
|
return "*";
|
|
} else if (term_id->id == EcsAny) {
|
|
return "_";
|
|
} else if (term_id->id == EcsVariable) {
|
|
return "$";
|
|
} else {
|
|
ecs_check(term_id->name != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
}
|
|
}
|
|
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
int ensure_term_id_variable(
|
|
ecs_rule_t *rule,
|
|
ecs_term_t *term,
|
|
ecs_term_id_t *term_id)
|
|
{
|
|
if (term_id->flags & EcsIsVariable) {
|
|
if (term_id->id == EcsAny) {
|
|
/* Any variables aren't translated to rule variables since their
|
|
* result isn't stored. */
|
|
return 0;
|
|
}
|
|
|
|
const char *name = term_id_var_name(term_id);
|
|
|
|
/* If this is a Not term, it should not introduce new variables. It may
|
|
* however create entity variables if there already was an existing
|
|
* table variable */
|
|
if (term->oper == EcsNot) {
|
|
if (!find_variable(rule, EcsRuleVarKindUnknown, name)) {
|
|
rule_error(rule, "variable in '%s' only appears in Not term",
|
|
name);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ecs_rule_var_t *var = ensure_variable(rule, EcsRuleVarKindEntity, name);
|
|
ecs_os_strset(&term_id->name, var->name);
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
bool term_id_is_variable(
|
|
ecs_term_id_t *term_id)
|
|
{
|
|
return term_id->flags & EcsIsVariable;
|
|
}
|
|
|
|
/* Get variable from a term identifier */
|
|
static
|
|
ecs_rule_var_t* term_id_to_var(
|
|
ecs_rule_t *rule,
|
|
ecs_term_id_t *id)
|
|
{
|
|
if (id->flags & EcsIsVariable) {
|
|
return find_variable(rule, EcsRuleVarKindUnknown, term_id_var_name(id));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Get variable from a term predicate */
|
|
static
|
|
ecs_rule_var_t* term_pred(
|
|
ecs_rule_t *rule,
|
|
ecs_term_t *term)
|
|
{
|
|
return term_id_to_var(rule, &term->first);
|
|
}
|
|
|
|
/* Get variable from a term subject */
|
|
static
|
|
ecs_rule_var_t* term_subj(
|
|
ecs_rule_t *rule,
|
|
ecs_term_t *term)
|
|
{
|
|
return term_id_to_var(rule, &term->src);
|
|
}
|
|
|
|
/* Get variable from a term object */
|
|
static
|
|
ecs_rule_var_t* term_obj(
|
|
ecs_rule_t *rule,
|
|
ecs_term_t *term)
|
|
{
|
|
if (obj_is_set(term)) {
|
|
return term_id_to_var(rule, &term->second);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Return predicate variable from pair */
|
|
static
|
|
ecs_rule_var_t* pair_pred(
|
|
ecs_rule_t *rule,
|
|
const ecs_rule_pair_t *pair)
|
|
{
|
|
if (pair->reg_mask & RULE_PAIR_PREDICATE) {
|
|
return &rule->vars[pair->first.reg];
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Return object variable from pair */
|
|
static
|
|
ecs_rule_var_t* pair_obj(
|
|
ecs_rule_t *rule,
|
|
const ecs_rule_pair_t *pair)
|
|
{
|
|
if (pair->reg_mask & RULE_PAIR_OBJECT) {
|
|
return &rule->vars[pair->second.reg];
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Create new frame for storing register values. Each operation that yields data
|
|
* gets its own register frame, which contains all variables reified up to that
|
|
* point. The preceding frame always contains the reified variables from the
|
|
* previous operation. Operations that do not yield data (such as control flow)
|
|
* do not have their own frames. */
|
|
static
|
|
int32_t push_frame(
|
|
ecs_rule_t *rule)
|
|
{
|
|
return rule->frame_count ++;
|
|
}
|
|
|
|
/* Get register array for current stack frame. The stack frame is determined by
|
|
* the current operation that is evaluated. The register array contains the
|
|
* values for the reified variables. If a variable hasn't been reified yet, its
|
|
* register will store a wildcard. */
|
|
static
|
|
ecs_var_t* get_register_frame(
|
|
const ecs_rule_iter_t *it,
|
|
int32_t frame)
|
|
{
|
|
if (it->registers) {
|
|
return &it->registers[frame * it->rule->var_count];
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Get register array for current stack frame. The stack frame is determined by
|
|
* the current operation that is evaluated. The register array contains the
|
|
* values for the reified variables. If a variable hasn't been reified yet, its
|
|
* register will store a wildcard. */
|
|
static
|
|
ecs_var_t* get_registers(
|
|
const ecs_rule_iter_t *it,
|
|
ecs_rule_op_t *op)
|
|
{
|
|
return get_register_frame(it, op->frame);
|
|
}
|
|
|
|
/* Get columns array. Columns store, for each matched column in a table, the
|
|
* index at which it occurs. This reduces the amount of searching that
|
|
* operations need to do in a type, since select/with already provide it. */
|
|
static
|
|
int32_t* rule_get_columns_frame(
|
|
ecs_rule_iter_t *it,
|
|
int32_t frame)
|
|
{
|
|
return &it->columns[frame * it->rule->filter.term_count];
|
|
}
|
|
|
|
static
|
|
int32_t* rule_get_columns(
|
|
ecs_rule_iter_t *it,
|
|
ecs_rule_op_t *op)
|
|
{
|
|
return rule_get_columns_frame(it, op->frame);
|
|
}
|
|
|
|
static
|
|
void entity_reg_set(
|
|
const ecs_rule_t *rule,
|
|
ecs_var_t *regs,
|
|
int32_t r,
|
|
ecs_entity_t entity)
|
|
{
|
|
(void)rule;
|
|
ecs_assert(rule->vars[r].kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_check(ecs_is_valid(rule->filter.world, entity), ECS_INVALID_PARAMETER, NULL);
|
|
regs[r].entity = entity;
|
|
error:
|
|
return;
|
|
}
|
|
|
|
static
|
|
ecs_entity_t entity_reg_get(
|
|
const ecs_rule_t *rule,
|
|
ecs_var_t *regs,
|
|
int32_t r)
|
|
{
|
|
(void)rule;
|
|
ecs_entity_t e = regs[r].entity;
|
|
if (!e) {
|
|
return EcsWildcard;
|
|
}
|
|
|
|
ecs_check(ecs_is_valid(rule->filter.world, e), ECS_INVALID_PARAMETER, NULL);
|
|
return e;
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
void table_reg_set(
|
|
const ecs_rule_t *rule,
|
|
ecs_var_t *regs,
|
|
int32_t r,
|
|
ecs_table_t *table)
|
|
{
|
|
(void)rule;
|
|
ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
regs[r].range.table = table;
|
|
regs[r].range.offset = 0;
|
|
regs[r].range.count = 0;
|
|
regs[r].entity = 0;
|
|
}
|
|
|
|
static
|
|
ecs_table_range_t table_reg_get(
|
|
const ecs_rule_t *rule,
|
|
ecs_var_t *regs,
|
|
int32_t r)
|
|
{
|
|
(void)rule;
|
|
ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
return regs[r].range;
|
|
}
|
|
|
|
static
|
|
ecs_entity_t reg_get_entity(
|
|
const ecs_rule_t *rule,
|
|
ecs_rule_op_t *op,
|
|
ecs_var_t *regs,
|
|
int32_t r)
|
|
{
|
|
if (r == UINT8_MAX) {
|
|
ecs_assert(op->subject != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* The subject is referenced from the query string by string identifier.
|
|
* If subject entity is not valid, it could have been deletd by the
|
|
* application after the rule was created */
|
|
ecs_check(ecs_is_valid(rule->filter.world, op->subject),
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
|
|
return op->subject;
|
|
}
|
|
if (rule->vars[r].kind == EcsRuleVarKindTable) {
|
|
int32_t offset = regs[r].range.offset;
|
|
|
|
ecs_assert(regs[r].range.count == 1, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_data_t *data = &table_reg_get(rule, regs, r).table->data;
|
|
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_entity_t *entities = ecs_vec_first(&data->entities);
|
|
ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(offset < ecs_vec_count(&data->entities),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_check(ecs_is_valid(rule->filter.world, entities[offset]),
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
|
|
return entities[offset];
|
|
}
|
|
if (rule->vars[r].kind == EcsRuleVarKindEntity) {
|
|
return entity_reg_get(rule, regs, r);
|
|
}
|
|
|
|
/* Must return an entity */
|
|
ecs_assert(false, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
ecs_table_range_t table_from_entity(
|
|
const ecs_world_t *world,
|
|
ecs_entity_t entity)
|
|
{
|
|
ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
entity = ecs_get_alive(world, entity);
|
|
|
|
ecs_table_range_t slice = {0};
|
|
ecs_record_t *record = flecs_entities_get(world, entity);
|
|
if (record) {
|
|
slice.table = record->table;
|
|
slice.offset = ECS_RECORD_TO_ROW(record->row);
|
|
slice.count = 1;
|
|
}
|
|
|
|
return slice;
|
|
}
|
|
|
|
static
|
|
ecs_table_range_t reg_get_range(
|
|
const ecs_rule_t *rule,
|
|
ecs_rule_op_t *op,
|
|
ecs_var_t *regs,
|
|
int32_t r)
|
|
{
|
|
if (r == UINT8_MAX) {
|
|
ecs_check(ecs_is_valid(rule->filter.world, op->subject),
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
return table_from_entity(rule->filter.world, op->subject);
|
|
}
|
|
if (rule->vars[r].kind == EcsRuleVarKindTable) {
|
|
return table_reg_get(rule, regs, r);
|
|
}
|
|
if (rule->vars[r].kind == EcsRuleVarKindEntity) {
|
|
return table_from_entity(rule->filter.world, entity_reg_get(rule, regs, r));
|
|
}
|
|
error:
|
|
return (ecs_table_range_t){0};
|
|
}
|
|
|
|
static
|
|
void reg_set_entity(
|
|
const ecs_rule_t *rule,
|
|
ecs_var_t *regs,
|
|
int32_t r,
|
|
ecs_entity_t entity)
|
|
{
|
|
if (rule->vars[r].kind == EcsRuleVarKindTable) {
|
|
ecs_world_t *world = rule->filter.world;
|
|
ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL);
|
|
regs[r].range = table_from_entity(world, entity);
|
|
regs[r].entity = entity;
|
|
} else {
|
|
entity_reg_set(rule, regs, r, entity);
|
|
}
|
|
error:
|
|
return;
|
|
}
|
|
|
|
static
|
|
void reg_set_range(
|
|
const ecs_rule_t *rule,
|
|
ecs_var_t *regs,
|
|
int32_t r,
|
|
const ecs_table_range_t *range)
|
|
{
|
|
if (rule->vars[r].kind == EcsRuleVarKindEntity) {
|
|
ecs_check(range->count == 1, ECS_INTERNAL_ERROR, NULL);
|
|
regs[r].range = *range;
|
|
regs[r].entity = ecs_vec_get_t(&range->table->data.entities,
|
|
ecs_entity_t, range->offset)[0];
|
|
} else {
|
|
regs[r].range = *range;
|
|
regs[r].entity = 0;
|
|
}
|
|
error:
|
|
return;
|
|
}
|
|
|
|
/* This encodes a column expression into a pair. A pair stores information about
|
|
* the variable(s) associated with the column. Pairs are used by operations to
|
|
* apply filters, and when there is a match, to reify variables. */
|
|
static
|
|
ecs_rule_pair_t term_to_pair(
|
|
ecs_rule_t *rule,
|
|
ecs_term_t *term)
|
|
{
|
|
ecs_rule_pair_t result = {0};
|
|
|
|
/* Terms must always have at least one argument (the subject) */
|
|
ecs_assert(subj_is_set(term), ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* If the predicate id is a variable, find the variable and encode its id
|
|
* in the pair so the operation can find it later. */
|
|
if (term->first.flags & EcsIsVariable) {
|
|
if (term->first.id != EcsAny) {
|
|
/* Always lookup var as an entity, as pairs never refer to tables */
|
|
const ecs_rule_var_t *var = find_variable(
|
|
rule, EcsRuleVarKindEntity, term_id_var_name(&term->first));
|
|
|
|
/* Variables should have been declared */
|
|
ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(var->kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
result.first.reg = var->id;
|
|
|
|
/* Set flag so the operation can see the predicate is a variable */
|
|
result.reg_mask |= RULE_PAIR_PREDICATE;
|
|
result.final = true;
|
|
} else {
|
|
result.first.id = EcsWildcard;
|
|
result.final = true;
|
|
}
|
|
} else {
|
|
/* If the predicate is not a variable, simply store its id. */
|
|
ecs_entity_t pred_id = term->first.id;
|
|
result.first.id = pred_id;
|
|
|
|
/* Test if predicate is transitive. When evaluating the predicate, this
|
|
* will also take into account transitive relationships */
|
|
if (ecs_has_id(rule->filter.world, pred_id, EcsTransitive)) {
|
|
/* Transitive queries must have an object */
|
|
if (obj_is_set(term)) {
|
|
if (term->second.flags & (EcsUp|EcsTraverseAll)) {
|
|
result.transitive = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ecs_has_id(rule->filter.world, pred_id, EcsFinal)) {
|
|
result.final = true;
|
|
}
|
|
|
|
if (ecs_has_id(rule->filter.world, pred_id, EcsReflexive)) {
|
|
result.reflexive = true;
|
|
}
|
|
|
|
if (ecs_has_id(rule->filter.world, pred_id, EcsAcyclic)) {
|
|
result.acyclic = true;
|
|
}
|
|
}
|
|
|
|
/* The pair doesn't do anything with the subject (sources are the things that
|
|
* are matched against pairs) so if the column does not have a object,
|
|
* there is nothing left to do. */
|
|
if (!obj_is_set(term)) {
|
|
return result;
|
|
}
|
|
|
|
/* If arguments is higher than 2 this is not a pair but a nested rule */
|
|
ecs_assert(obj_is_set(term), ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Same as above, if the object is a variable, store it and flag it */
|
|
if (term->second.flags & EcsIsVariable) {
|
|
if (term->second.id != EcsAny) {
|
|
const ecs_rule_var_t *var = find_variable(
|
|
rule, EcsRuleVarKindEntity, term_id_var_name(&term->second));
|
|
|
|
/* Variables should have been declared */
|
|
ecs_assert(var != NULL, ECS_INTERNAL_ERROR,
|
|
term_id_var_name(&term->second));
|
|
ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR,
|
|
term_id_var_name(&term->second));
|
|
|
|
result.second.reg = var->id;
|
|
result.reg_mask |= RULE_PAIR_OBJECT;
|
|
} else {
|
|
result.second.id = EcsWildcard;
|
|
}
|
|
} else {
|
|
/* If the object is not a variable, simply store its id */
|
|
result.second.id = term->second.id;
|
|
if (!result.second.id) {
|
|
result.second_0 = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* When an operation has a pair, it is used to filter its input. This function
|
|
* translates a pair back into an entity id, and in the process substitutes the
|
|
* variables that have already been filled out. It's one of the most important
|
|
* functions, as a lot of the filtering logic depends on having an entity that
|
|
* has all of the reified variables correctly filled out. */
|
|
static
|
|
ecs_rule_filter_t pair_to_filter(
|
|
ecs_rule_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
ecs_rule_pair_t pair)
|
|
{
|
|
ecs_entity_t first = pair.first.id;
|
|
ecs_entity_t second = pair.second.id;
|
|
ecs_rule_filter_t result = {
|
|
.lo_var = -1,
|
|
.hi_var = -1
|
|
};
|
|
|
|
/* Get registers in case we need to resolve ids from registers. Get them
|
|
* from the previous, not the current stack frame as the current operation
|
|
* hasn't reified its variables yet. */
|
|
ecs_var_t *regs = get_register_frame(it, op->frame - 1);
|
|
|
|
if (pair.reg_mask & RULE_PAIR_OBJECT) {
|
|
second = entity_reg_get(it->rule, regs, pair.second.reg);
|
|
second = ecs_entity_t_lo(second); /* Filters don't have generations */
|
|
|
|
if (second == EcsWildcard) {
|
|
result.wildcard = true;
|
|
result.second_wildcard = true;
|
|
result.lo_var = pair.second.reg;
|
|
}
|
|
}
|
|
|
|
if (pair.reg_mask & RULE_PAIR_PREDICATE) {
|
|
first = entity_reg_get(it->rule, regs, pair.first.reg);
|
|
first = ecs_entity_t_lo(first); /* Filters don't have generations */
|
|
|
|
if (first == EcsWildcard) {
|
|
if (result.wildcard) {
|
|
result.same_var = pair.first.reg == pair.second.reg;
|
|
}
|
|
|
|
result.wildcard = true;
|
|
result.first_wildcard = true;
|
|
|
|
if (second) {
|
|
result.hi_var = pair.first.reg;
|
|
} else {
|
|
result.lo_var = pair.first.reg;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!second && !pair.second_0) {
|
|
result.mask = first;
|
|
} else {
|
|
result.mask = ecs_pair(first, second);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* This function is responsible for reifying the variables (filling them out
|
|
* with their actual values as soon as they are known). It uses the pair
|
|
* expression returned by pair_get_most_specific_var, and attempts to fill out each of the
|
|
* wildcards in the pair. If a variable isn't reified yet, the pair expression
|
|
* will still contain one or more wildcards, which is harmless as the respective
|
|
* registers will also point to a wildcard. */
|
|
static
|
|
void reify_variables(
|
|
ecs_rule_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
ecs_rule_filter_t *filter,
|
|
ecs_type_t type,
|
|
int32_t column)
|
|
{
|
|
const ecs_rule_t *rule = it->rule;
|
|
const ecs_rule_var_t *vars = rule->vars;
|
|
(void)vars;
|
|
|
|
ecs_var_t *regs = get_registers(it, op);
|
|
ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_entity_t *elem = &type.array[column];
|
|
|
|
int32_t obj_var = filter->lo_var;
|
|
int32_t pred_var = filter->hi_var;
|
|
|
|
if (obj_var != -1) {
|
|
ecs_assert(vars[obj_var].kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
entity_reg_set(rule, regs, obj_var,
|
|
ecs_get_alive(rule->filter.world, ECS_PAIR_SECOND(*elem)));
|
|
}
|
|
|
|
if (pred_var != -1) {
|
|
ecs_assert(vars[pred_var].kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
entity_reg_set(rule, regs, pred_var,
|
|
ecs_get_alive(rule->filter.world,
|
|
ECS_PAIR_FIRST(*elem)));
|
|
}
|
|
}
|
|
|
|
/* Returns whether variable is a subject */
|
|
static
|
|
bool is_subject(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var)
|
|
{
|
|
ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (!var) {
|
|
return false;
|
|
}
|
|
|
|
if (var->id < rule->subj_var_count) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static
|
|
bool skip_term(ecs_term_t *term) {
|
|
if (ecs_term_match_0(term)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static
|
|
int32_t get_variable_depth(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var,
|
|
ecs_rule_var_t *root,
|
|
int recur);
|
|
|
|
static
|
|
int32_t crawl_variable(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var,
|
|
ecs_rule_var_t *root,
|
|
int recur)
|
|
{
|
|
ecs_term_t *terms = rule->filter.terms;
|
|
int32_t i, count = rule->filter.term_count;
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
if (skip_term(term)) {
|
|
continue;
|
|
}
|
|
|
|
ecs_rule_var_t
|
|
*first = term_pred(rule, term),
|
|
*src = term_subj(rule, term),
|
|
*second = term_obj(rule, term);
|
|
|
|
/* Variable must at least appear once in term */
|
|
if (var != first && var != src && var != second) {
|
|
continue;
|
|
}
|
|
|
|
if (first && first != var && !first->marked) {
|
|
get_variable_depth(rule, first, root, recur + 1);
|
|
}
|
|
|
|
if (src && src != var && !src->marked) {
|
|
get_variable_depth(rule, src, root, recur + 1);
|
|
}
|
|
|
|
if (second && second != var && !second->marked) {
|
|
get_variable_depth(rule, second, root, recur + 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
int32_t get_depth_from_var(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var,
|
|
ecs_rule_var_t *root,
|
|
int recur)
|
|
{
|
|
/* If variable is the root or if depth has been set, return depth + 1 */
|
|
if (var == root || var->depth != UINT8_MAX) {
|
|
return var->depth + 1;
|
|
}
|
|
|
|
/* Variable is already being evaluated, so this indicates a cycle. Stop */
|
|
if (var->marked) {
|
|
return 0;
|
|
}
|
|
|
|
/* Variable is not yet being evaluated and depth has not yet been set.
|
|
* Calculate depth. */
|
|
int32_t depth = get_variable_depth(rule, var, root, recur + 1);
|
|
if (depth == UINT8_MAX) {
|
|
return depth;
|
|
} else {
|
|
return depth + 1;
|
|
}
|
|
}
|
|
|
|
static
|
|
int32_t get_depth_from_term(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *cur,
|
|
ecs_rule_var_t *first,
|
|
ecs_rule_var_t *second,
|
|
ecs_rule_var_t *root,
|
|
int recur)
|
|
{
|
|
int32_t result = UINT8_MAX;
|
|
|
|
/* If neither of the other parts of the terms are variables, this
|
|
* variable is guaranteed to have no dependencies. */
|
|
if (!first && !second) {
|
|
result = 0;
|
|
} else {
|
|
/* If this is a variable that is not the same as the current,
|
|
* we can use it to determine dependency depth. */
|
|
if (first && cur != first) {
|
|
int32_t depth = get_depth_from_var(rule, first, root, recur);
|
|
if (depth == UINT8_MAX) {
|
|
return UINT8_MAX;
|
|
}
|
|
|
|
/* If the found depth is lower than the depth found, overwrite it */
|
|
if (depth < result) {
|
|
result = depth;
|
|
}
|
|
}
|
|
|
|
/* Same for second */
|
|
if (second && cur != second) {
|
|
int32_t depth = get_depth_from_var(rule, second, root, recur);
|
|
if (depth == UINT8_MAX) {
|
|
return UINT8_MAX;
|
|
}
|
|
|
|
if (depth < result) {
|
|
result = depth;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Find the depth of the dependency tree from the variable to the root */
|
|
static
|
|
int32_t get_variable_depth(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var,
|
|
ecs_rule_var_t *root,
|
|
int recur)
|
|
{
|
|
var->marked = true;
|
|
|
|
/* Iterate columns, find all instances where 'var' is not used as subject.
|
|
* If the subject of that column is either the root or a variable for which
|
|
* the depth is known, the depth for this variable can be determined. */
|
|
ecs_term_t *terms = rule->filter.terms;
|
|
|
|
int32_t i, count = rule->filter.term_count;
|
|
int32_t result = UINT8_MAX;
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
if (skip_term(term)) {
|
|
continue;
|
|
}
|
|
|
|
ecs_rule_var_t
|
|
*first = term_pred(rule, term),
|
|
*src = term_subj(rule, term),
|
|
*second = term_obj(rule, term);
|
|
|
|
if (src != var) {
|
|
continue;
|
|
}
|
|
|
|
if (!is_subject(rule, first)) {
|
|
first = NULL;
|
|
}
|
|
|
|
if (!is_subject(rule, second)) {
|
|
second = NULL;
|
|
}
|
|
|
|
int32_t depth = get_depth_from_term(rule, var, first, second, root, recur);
|
|
if (depth < result) {
|
|
result = depth;
|
|
}
|
|
}
|
|
|
|
if (result == UINT8_MAX) {
|
|
result = 0;
|
|
}
|
|
|
|
var->depth = result;
|
|
|
|
/* Dependencies are calculated from subject to (first, second). If there were
|
|
* sources that are only related by object (like (X, Y), (Z, Y)) it is
|
|
* possible that those have not yet been found yet. To make sure those
|
|
* variables are found, loop again & follow predicate & object links */
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
if (skip_term(term)) {
|
|
continue;
|
|
}
|
|
|
|
ecs_rule_var_t
|
|
*src = term_subj(rule, term),
|
|
*first = term_pred(rule, term),
|
|
*second = term_obj(rule, term);
|
|
|
|
/* Only evaluate first & second for current subject. This ensures that we
|
|
* won't evaluate variables that are unreachable from the root. This
|
|
* must be detected as unconstrained variables are not allowed. */
|
|
if (src != var) {
|
|
continue;
|
|
}
|
|
|
|
crawl_variable(rule, src, root, recur);
|
|
|
|
if (first && first != var) {
|
|
crawl_variable(rule, first, root, recur);
|
|
}
|
|
|
|
if (second && second != var) {
|
|
crawl_variable(rule, second, root, recur);
|
|
}
|
|
}
|
|
|
|
return var->depth;
|
|
}
|
|
|
|
/* Compare function used for qsort. It ensures that variables are first ordered
|
|
* by depth, followed by how often they occur. */
|
|
static
|
|
int compare_variable(
|
|
const void* ptr1,
|
|
const void *ptr2)
|
|
{
|
|
const ecs_rule_var_t *v1 = ptr1;
|
|
const ecs_rule_var_t *v2 = ptr2;
|
|
|
|
if (v1->kind < v2->kind) {
|
|
return -1;
|
|
} else if (v1->kind > v2->kind) {
|
|
return 1;
|
|
}
|
|
|
|
if (v1->depth < v2->depth) {
|
|
return -1;
|
|
} else if (v1->depth > v2->depth) {
|
|
return 1;
|
|
}
|
|
|
|
if (v1->occurs < v2->occurs) {
|
|
return 1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
return (v1->id < v2->id) - (v1->id > v2->id);
|
|
}
|
|
|
|
/* After all subject variables have been found, inserted and sorted, the
|
|
* remaining variables (predicate & object) still need to be inserted. This
|
|
* function serves two purposes. The first purpose is to ensure that all
|
|
* variables are known before operations are emitted. This ensures that the
|
|
* variables array won't be reallocated while emitting, which simplifies code.
|
|
* The second purpose of the function is to ensure that if the root variable
|
|
* (which, if it exists has now been created with a table type) is also inserted
|
|
* with an entity type if required. This is used later to decide whether the
|
|
* rule needs to insert an each instruction. */
|
|
static
|
|
int ensure_all_variables(
|
|
ecs_rule_t *rule)
|
|
{
|
|
ecs_term_t *terms = rule->filter.terms;
|
|
int32_t i, count = rule->filter.term_count;
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
if (skip_term(term)) {
|
|
continue;
|
|
}
|
|
|
|
/* If predicate is a variable, make sure it has been registered */
|
|
if (term->first.flags & EcsIsVariable) {
|
|
if (ensure_term_id_variable(rule, term, &term->first) != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* If subject is a variable and it is not This, make sure it is
|
|
* registered as an entity variable. This ensures that the program will
|
|
* correctly return all permutations */
|
|
if (!ecs_term_match_this(term)) {
|
|
if (ensure_term_id_variable(rule, term, &term->src) != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* If object is a variable, make sure it has been registered */
|
|
if (obj_is_set(term) && (term->second.flags & EcsIsVariable)) {
|
|
if (ensure_term_id_variable(rule, term, &term->second) != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Scan for variables, put them in optimal dependency order. */
|
|
static
|
|
int scan_variables(
|
|
ecs_rule_t *rule)
|
|
{
|
|
/* Objects found in rule. One will be elected root */
|
|
int32_t subject_count = 0;
|
|
|
|
/* If this (.) is found, it always takes precedence in root election */
|
|
int32_t this_var = UINT8_MAX;
|
|
|
|
/* Keep track of the subject variable that occurs the most. In the absence of
|
|
* this (.) the variable with the most occurrences will be elected root. */
|
|
int32_t max_occur = 0;
|
|
int32_t max_occur_var = UINT8_MAX;
|
|
|
|
/* Step 1: find all possible roots */
|
|
ecs_term_t *terms = rule->filter.terms;
|
|
int32_t i, term_count = rule->filter.term_count;
|
|
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
|
|
/* Evaluate the subject. The predicate and object are not evaluated,
|
|
* since they never can be elected as root. */
|
|
if (term_id_is_variable(&term->src)) {
|
|
const char *subj_name = term_id_var_name(&term->src);
|
|
|
|
ecs_rule_var_t *src = find_variable(
|
|
rule, EcsRuleVarKindTable, subj_name);
|
|
if (!src) {
|
|
src = create_variable(rule, EcsRuleVarKindTable, subj_name);
|
|
if (subject_count >= ECS_RULE_MAX_VAR_COUNT) {
|
|
rule_error(rule, "too many variables in rule");
|
|
goto error;
|
|
}
|
|
|
|
/* Make sure that variable name in term array matches with the
|
|
* rule name. */
|
|
if (term->src.id != EcsThis && term->src.id != EcsAny) {
|
|
ecs_os_strset(&term->src.name, src->name);
|
|
term->src.id = 0;
|
|
}
|
|
}
|
|
|
|
if (++ src->occurs > max_occur) {
|
|
max_occur = src->occurs;
|
|
max_occur_var = src->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
rule->subj_var_count = rule->var_count;
|
|
|
|
if (ensure_all_variables(rule) != 0) {
|
|
goto error;
|
|
}
|
|
|
|
/* Variables in a term with a literal subject have depth 0 */
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
|
|
if (!(term->src.flags & EcsIsVariable)) {
|
|
ecs_rule_var_t
|
|
*first = term_pred(rule, term),
|
|
*second = term_obj(rule, term);
|
|
|
|
if (first) {
|
|
first->depth = 0;
|
|
}
|
|
if (second) {
|
|
second->depth = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Elect a root. This is either this (.) or the variable with the most
|
|
* occurrences. */
|
|
int32_t root_var = this_var;
|
|
if (root_var == UINT8_MAX) {
|
|
root_var = max_occur_var;
|
|
if (root_var == UINT8_MAX) {
|
|
/* If no subject variables have been found, the rule expression only
|
|
* operates on a fixed set of entities, in which case no root
|
|
* election is required. */
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
ecs_rule_var_t *root = &rule->vars[root_var];
|
|
root->depth = get_variable_depth(rule, root, root, 0);
|
|
|
|
/* Verify that there are no unconstrained variables. Unconstrained variables
|
|
* are variables that are unreachable from the root. */
|
|
for (i = 0; i < rule->subj_var_count; i ++) {
|
|
if (rule->vars[i].depth == UINT8_MAX) {
|
|
rule_error(rule, "unconstrained variable '%s'",
|
|
rule->vars[i].name);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* For each Not term, verify that variables are known */
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
if (term->oper != EcsNot) {
|
|
continue;
|
|
}
|
|
|
|
ecs_rule_var_t
|
|
*first = term_pred(rule, term),
|
|
*second = term_obj(rule, term);
|
|
|
|
if (!first && term_id_is_variable(&term->first) &&
|
|
term->first.id != EcsAny)
|
|
{
|
|
rule_error(rule, "missing predicate variable '%s'",
|
|
term_id_var_name(&term->first));
|
|
goto error;
|
|
}
|
|
if (!second && term_id_is_variable(&term->second) &&
|
|
term->second.id != EcsAny)
|
|
{
|
|
rule_error(rule, "missing object variable '%s'",
|
|
term_id_var_name(&term->second));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Order variables by depth, followed by occurrence. The variable
|
|
* array will later be used to lead the iteration over the terms, and
|
|
* determine which operations get inserted first. */
|
|
int32_t var_count = rule->var_count;
|
|
ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT];
|
|
ecs_os_memcpy_n(vars, rule->vars, ecs_rule_var_t, var_count);
|
|
ecs_qsort_t(&vars, var_count, ecs_rule_var_t, compare_variable);
|
|
for (i = 0; i < var_count; i ++) {
|
|
rule->var_eval_order[i] = vars[i].id;
|
|
}
|
|
|
|
done:
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
/* Get entity variable from table variable */
|
|
static
|
|
ecs_rule_var_t* to_entity(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var)
|
|
{
|
|
if (!var) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_rule_var_t *evar = NULL;
|
|
if (var->kind == EcsRuleVarKindTable) {
|
|
evar = find_variable(rule, EcsRuleVarKindEntity, var->name);
|
|
} else {
|
|
evar = var;
|
|
}
|
|
|
|
return evar;
|
|
}
|
|
|
|
/* Ensure that if a table variable has been written, the corresponding entity
|
|
* variable is populated. The function will return the most specific, populated
|
|
* variable. */
|
|
static
|
|
ecs_rule_var_t* most_specific_var(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var,
|
|
bool *written,
|
|
bool create)
|
|
{
|
|
if (!var) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_rule_var_t *tvar, *evar = to_entity(rule, var);
|
|
if (!evar) {
|
|
return var;
|
|
}
|
|
|
|
if (var->kind == EcsRuleVarKindTable) {
|
|
tvar = var;
|
|
} else {
|
|
tvar = find_variable(rule, EcsRuleVarKindTable, var->name);
|
|
}
|
|
|
|
/* If variable is used as predicate or object, it should have been
|
|
* registered as an entity. */
|
|
ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Usually table variables are resolved before they are used as a predicate
|
|
* or object, but in the case of cyclic dependencies this is not guaranteed.
|
|
* Only insert an each instruction of the table variable has been written */
|
|
if (tvar && written[tvar->id]) {
|
|
/* If the variable has been written as a table but not yet
|
|
* as an entity, insert an each operation that yields each
|
|
* entity in the table. */
|
|
if (evar) {
|
|
if (written[evar->id]) {
|
|
return evar;
|
|
} else if (create) {
|
|
ecs_rule_op_t *op = create_operation(rule);
|
|
op->kind = EcsRuleEach;
|
|
op->on_pass = rule->operation_count;
|
|
op->on_fail = rule->operation_count - 2;
|
|
op->frame = rule->frame_count;
|
|
op->has_in = true;
|
|
op->has_out = true;
|
|
op->r_in = tvar->id;
|
|
op->r_out = evar->id;
|
|
|
|
/* Entity will either be written or has been written */
|
|
written[evar->id] = true;
|
|
|
|
push_frame(rule);
|
|
|
|
return evar;
|
|
} else {
|
|
return tvar;
|
|
}
|
|
}
|
|
} else if (evar && written[evar->id]) {
|
|
return evar;
|
|
}
|
|
|
|
return var;
|
|
}
|
|
|
|
/* Get most specific known variable */
|
|
static
|
|
ecs_rule_var_t *get_most_specific_var(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var,
|
|
bool *written)
|
|
{
|
|
return most_specific_var(rule, var, written, false);
|
|
}
|
|
|
|
/* Get or create most specific known variable. This will populate an entity
|
|
* variable if a table variable is known but the entity variable isn't. */
|
|
static
|
|
ecs_rule_var_t *ensure_most_specific_var(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var,
|
|
bool *written)
|
|
{
|
|
return most_specific_var(rule, var, written, true);
|
|
}
|
|
|
|
|
|
/* Ensure that an entity variable is written before using it */
|
|
static
|
|
ecs_rule_var_t* ensure_entity_written(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_var_t *var,
|
|
bool *written)
|
|
{
|
|
if (!var) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Ensure we're working with the most specific version of src we can get */
|
|
ecs_rule_var_t *evar = ensure_most_specific_var(rule, var, written);
|
|
|
|
/* The post condition of this function is that there is an entity variable,
|
|
* and that it is written. Make sure that the result is an entity */
|
|
ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(evar->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Make sure the variable has been written */
|
|
ecs_assert(written[evar->id] == true, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
return evar;
|
|
}
|
|
|
|
static
|
|
ecs_rule_op_t* insert_operation(
|
|
ecs_rule_t *rule,
|
|
int32_t term_index,
|
|
bool *written)
|
|
{
|
|
ecs_rule_pair_t pair = {0};
|
|
|
|
/* Parse the term's type into a pair. A pair extracts the ids from
|
|
* the term, and replaces variables with wildcards which can then
|
|
* be matched against actual relationships. A pair retains the
|
|
* information about the variables, so that when a match happens,
|
|
* the pair can be used to reify the variable. */
|
|
if (term_index != -1) {
|
|
ecs_term_t *term = &rule->filter.terms[term_index];
|
|
|
|
pair = term_to_pair(rule, term);
|
|
|
|
/* If the pair contains entity variables that have not yet been written,
|
|
* insert each instructions in case their tables are known. Variables in
|
|
* a pair that are truly unknown will be populated by the operation,
|
|
* but an operation should never overwrite an entity variable if the
|
|
* corresponding table variable has already been resolved. */
|
|
if (pair.reg_mask & RULE_PAIR_PREDICATE) {
|
|
ecs_rule_var_t *first = &rule->vars[pair.first.reg];
|
|
first = get_most_specific_var(rule, first, written);
|
|
pair.first.reg = first->id;
|
|
}
|
|
|
|
if (pair.reg_mask & RULE_PAIR_OBJECT) {
|
|
ecs_rule_var_t *second = &rule->vars[pair.second.reg];
|
|
second = get_most_specific_var(rule, second, written);
|
|
pair.second.reg = second->id;
|
|
}
|
|
} else {
|
|
/* Not all operations have a filter (like Each) */
|
|
}
|
|
|
|
ecs_rule_op_t *op = create_operation(rule);
|
|
op->on_pass = rule->operation_count;
|
|
op->on_fail = rule->operation_count - 2;
|
|
op->frame = rule->frame_count;
|
|
op->filter = pair;
|
|
|
|
/* Store corresponding signature term so we can correlate and
|
|
* store the table columns with signature columns. */
|
|
op->term = term_index;
|
|
|
|
return op;
|
|
}
|
|
|
|
/* Insert first operation, which is always Input. This creates an entry in
|
|
* the register stack for the initial state. */
|
|
static
|
|
void insert_input(
|
|
ecs_rule_t *rule)
|
|
{
|
|
ecs_rule_op_t *op = create_operation(rule);
|
|
op->kind = EcsRuleInput;
|
|
|
|
/* The first time Input is evaluated it goes to the next/first operation */
|
|
op->on_pass = 1;
|
|
|
|
/* When Input is evaluated with redo = true it will return false, which will
|
|
* finish the program as op becomes -1. */
|
|
op->on_fail = -1;
|
|
|
|
push_frame(rule);
|
|
}
|
|
|
|
/* Insert last operation, which is always Yield. When the program hits Yield,
|
|
* data is returned to the application. */
|
|
static
|
|
void insert_yield(
|
|
ecs_rule_t *rule)
|
|
{
|
|
ecs_rule_op_t *op = create_operation(rule);
|
|
op->kind = EcsRuleYield;
|
|
op->has_in = true;
|
|
op->on_fail = rule->operation_count - 2;
|
|
/* Yield can only "fail" since it is the end of the program */
|
|
|
|
/* Find variable associated with this. It is possible that the variable
|
|
* exists both as a table and as an entity. This can happen when a rule
|
|
* first selects a table for this, but then subsequently needs to evaluate
|
|
* each entity in that table. In that case the yield instruction should
|
|
* return the entity, so look for that first. */
|
|
ecs_rule_var_t *var = find_variable(rule, EcsRuleVarKindEntity, ".");
|
|
if (!var) {
|
|
var = find_variable(rule, EcsRuleVarKindTable, ".");
|
|
}
|
|
|
|
/* If there is no this, there is nothing to yield. In that case the rule
|
|
* simply returns true or false. */
|
|
if (!var) {
|
|
op->r_in = UINT8_MAX;
|
|
} else {
|
|
op->r_in = var->id;
|
|
}
|
|
|
|
op->frame = push_frame(rule);
|
|
}
|
|
|
|
/* Return superset/subset including the root */
|
|
static
|
|
void insert_reflexive_set(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_op_kind_t op_kind,
|
|
ecs_rule_var_t *out,
|
|
const ecs_rule_pair_t pair,
|
|
int32_t c,
|
|
bool *written,
|
|
bool reflexive)
|
|
{
|
|
ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_rule_var_t *first = pair_pred(rule, &pair);
|
|
ecs_rule_var_t *second = pair_obj(rule, &pair);
|
|
|
|
int32_t setjmp_lbl = rule->operation_count;
|
|
int32_t store_lbl = setjmp_lbl + 1;
|
|
int32_t set_lbl = setjmp_lbl + 2;
|
|
int32_t next_op = setjmp_lbl + 4;
|
|
int32_t prev_op = setjmp_lbl - 1;
|
|
|
|
/* Insert 4 operations at once, so we don't have to worry about how
|
|
* the instruction array reallocs. If operation is not reflexive, we only
|
|
* need to insert the set operation. */
|
|
if (reflexive) {
|
|
insert_operation(rule, -1, written);
|
|
insert_operation(rule, -1, written);
|
|
insert_operation(rule, -1, written);
|
|
}
|
|
|
|
ecs_rule_op_t *op = insert_operation(rule, -1, written);
|
|
ecs_rule_op_t *setjmp = &rule->operations[setjmp_lbl];
|
|
ecs_rule_op_t *store = &rule->operations[store_lbl];
|
|
ecs_rule_op_t *set = &rule->operations[set_lbl];
|
|
ecs_rule_op_t *jump = op;
|
|
|
|
if (!reflexive) {
|
|
set_lbl = setjmp_lbl;
|
|
set = op;
|
|
setjmp = NULL;
|
|
store = NULL;
|
|
jump = NULL;
|
|
next_op = set_lbl + 1;
|
|
prev_op = set_lbl - 1;
|
|
}
|
|
|
|
/* The SetJmp operation stores a conditional jump label that either
|
|
* points to the Store or *Set operation */
|
|
if (reflexive) {
|
|
setjmp->kind = EcsRuleSetJmp;
|
|
setjmp->on_pass = store_lbl;
|
|
setjmp->on_fail = set_lbl;
|
|
}
|
|
|
|
/* The Store operation yields the root of the subtree. After yielding,
|
|
* this operation will fail and return to SetJmp, which will cause it
|
|
* to switch to the *Set operation. */
|
|
if (reflexive) {
|
|
store->kind = EcsRuleStore;
|
|
store->on_pass = next_op;
|
|
store->on_fail = setjmp_lbl;
|
|
store->has_in = true;
|
|
store->has_out = true;
|
|
store->r_out = out->id;
|
|
store->term = c;
|
|
|
|
if (!first) {
|
|
store->filter.first = pair.first;
|
|
} else {
|
|
store->filter.first.reg = first->id;
|
|
store->filter.reg_mask |= RULE_PAIR_PREDICATE;
|
|
}
|
|
|
|
/* If the object of the filter is not a variable, store literal */
|
|
if (!second) {
|
|
store->r_in = UINT8_MAX;
|
|
store->subject = ecs_get_alive(rule->filter.world, pair.second.id);
|
|
store->filter.second = pair.second;
|
|
} else {
|
|
store->r_in = second->id;
|
|
store->filter.second.reg = second->id;
|
|
store->filter.reg_mask |= RULE_PAIR_OBJECT;
|
|
}
|
|
}
|
|
|
|
/* This is either a SubSet or SuperSet operation */
|
|
set->kind = op_kind;
|
|
set->on_pass = next_op;
|
|
set->on_fail = prev_op;
|
|
set->has_out = true;
|
|
set->r_out = out->id;
|
|
set->term = c;
|
|
|
|
/* Predicate can be a variable if it's non-final */
|
|
if (!first) {
|
|
set->filter.first = pair.first;
|
|
} else {
|
|
set->filter.first.reg = first->id;
|
|
set->filter.reg_mask |= RULE_PAIR_PREDICATE;
|
|
}
|
|
|
|
if (!second) {
|
|
set->filter.second = pair.second;
|
|
} else {
|
|
set->filter.second.reg = second->id;
|
|
set->filter.reg_mask |= RULE_PAIR_OBJECT;
|
|
}
|
|
|
|
if (reflexive) {
|
|
/* The jump operation jumps to either the store or subset operation,
|
|
* depending on whether the store operation already yielded. The
|
|
* operation is inserted last, so that the on_fail label of the next
|
|
* operation will point to it */
|
|
jump->kind = EcsRuleJump;
|
|
|
|
/* The pass/fail labels of the Jump operation are not used, since it
|
|
* jumps to a variable location. Instead, the pass label is (ab)used to
|
|
* store the label of the SetJmp operation, so that the jump can access
|
|
* the label it needs to jump to from the setjmp op_ctx. */
|
|
jump->on_pass = setjmp_lbl;
|
|
jump->on_fail = -1;
|
|
}
|
|
|
|
written[out->id] = true;
|
|
}
|
|
|
|
static
|
|
ecs_rule_var_t* store_reflexive_set(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_op_kind_t op_kind,
|
|
ecs_rule_pair_t *pair,
|
|
bool *written,
|
|
bool reflexive,
|
|
bool as_entity)
|
|
{
|
|
/* Ensure we're using the most specific version of second */
|
|
ecs_rule_var_t *second = pair_obj(rule, pair);
|
|
if (second) {
|
|
pair->second.reg = second->id;
|
|
}
|
|
|
|
ecs_rule_var_kind_t var_kind = EcsRuleVarKindTable;
|
|
|
|
/* Create anonymous variable for storing the set */
|
|
ecs_rule_var_t *av = create_anonymous_variable(rule, var_kind);
|
|
int32_t ave_id = 0, av_id = av->id;
|
|
|
|
/* If the variable kind is a table, also create an entity variable as the
|
|
* result of the set operation should be returned as an entity */
|
|
if (var_kind == EcsRuleVarKindTable && as_entity) {
|
|
create_variable(rule, EcsRuleVarKindEntity, av->name);
|
|
av = &rule->vars[av_id];
|
|
ave_id = av_id + 1;
|
|
}
|
|
|
|
/* Generate the operations */
|
|
insert_reflexive_set(rule, op_kind, av, *pair, -1, written, reflexive);
|
|
|
|
/* Make sure to return entity variable, and that it is populated */
|
|
if (as_entity) {
|
|
return ensure_entity_written(rule, &rule->vars[ave_id], written);
|
|
} else {
|
|
return &rule->vars[av_id];
|
|
}
|
|
}
|
|
|
|
static
|
|
bool is_known(
|
|
ecs_rule_var_t *var,
|
|
bool *written)
|
|
{
|
|
if (!var) {
|
|
return true;
|
|
} else {
|
|
return written[var->id];
|
|
}
|
|
}
|
|
|
|
static
|
|
bool is_pair_known(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_pair_t *pair,
|
|
bool *written)
|
|
{
|
|
ecs_rule_var_t *pred_var = pair_pred(rule, pair);
|
|
if (!is_known(pred_var, written) || pair->first.id == EcsWildcard) {
|
|
return false;
|
|
}
|
|
|
|
ecs_rule_var_t *obj_var = pair_obj(rule, pair);
|
|
if (!is_known(obj_var, written) || pair->second.id == EcsWildcard) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static
|
|
void set_input_to_subj(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_op_t *op,
|
|
ecs_term_t *term,
|
|
ecs_rule_var_t *var)
|
|
{
|
|
(void)rule;
|
|
|
|
op->has_in = true;
|
|
if (!var) {
|
|
op->r_in = UINT8_MAX;
|
|
op->subject = term->src.id;
|
|
|
|
/* Invalid entities should have been caught during parsing */
|
|
ecs_assert(ecs_is_valid(rule->filter.world, op->subject),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
} else {
|
|
op->r_in = var->id;
|
|
}
|
|
}
|
|
|
|
static
|
|
void set_output_to_subj(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_op_t *op,
|
|
ecs_term_t *term,
|
|
ecs_rule_var_t *var)
|
|
{
|
|
(void)rule;
|
|
|
|
op->has_out = true;
|
|
if (!var) {
|
|
op->r_out = UINT8_MAX;
|
|
op->subject = term->src.id;
|
|
|
|
/* Invalid entities should have been caught during parsing */
|
|
ecs_assert(ecs_is_valid(rule->filter.world, op->subject),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
} else {
|
|
op->r_out = var->id;
|
|
}
|
|
}
|
|
|
|
static
|
|
void insert_select_or_with(
|
|
ecs_rule_t *rule,
|
|
int32_t c,
|
|
ecs_term_t *term,
|
|
ecs_rule_var_t *src,
|
|
ecs_rule_pair_t *pair,
|
|
bool *written)
|
|
{
|
|
ecs_rule_op_t *op;
|
|
bool eval_subject_supersets = false;
|
|
|
|
/* Find any entity and/or table variables for subject */
|
|
ecs_rule_var_t *tvar = NULL, *evar = to_entity(rule, src), *var = evar;
|
|
if (src && src->kind == EcsRuleVarKindTable) {
|
|
tvar = src;
|
|
if (!evar) {
|
|
var = tvar;
|
|
}
|
|
}
|
|
|
|
int32_t lbl_start = rule->operation_count;
|
|
ecs_rule_pair_t filter;
|
|
if (pair) {
|
|
filter = *pair;
|
|
} else {
|
|
filter = term_to_pair(rule, term);
|
|
}
|
|
|
|
/* Only insert implicit IsA if filter isn't already an IsA */
|
|
if ((!filter.transitive || filter.first.id != EcsIsA) && term->oper != EcsNot) {
|
|
if (!var) {
|
|
ecs_rule_pair_t isa_pair = {
|
|
.first.id = EcsIsA,
|
|
.second.id = term->src.id
|
|
};
|
|
|
|
evar = src = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair,
|
|
written, true, true);
|
|
tvar = NULL;
|
|
eval_subject_supersets = true;
|
|
|
|
} else if (ecs_id_is_wildcard(term->id) &&
|
|
ECS_PAIR_FIRST(term->id) != EcsThis &&
|
|
ECS_PAIR_SECOND(term->id) != EcsThis)
|
|
{
|
|
ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
op = insert_operation(rule, -1, written);
|
|
|
|
if (!is_known(src, written)) {
|
|
op->kind = EcsRuleSelect;
|
|
set_output_to_subj(rule, op, term, src);
|
|
written[src->id] = true;
|
|
} else {
|
|
op->kind = EcsRuleWith;
|
|
set_input_to_subj(rule, op, term, src);
|
|
}
|
|
|
|
ecs_rule_pair_t isa_pair = {
|
|
.first.id = EcsIsA,
|
|
.second.reg = src->id,
|
|
.reg_mask = RULE_PAIR_OBJECT
|
|
};
|
|
|
|
op->filter = filter;
|
|
if (op->filter.reg_mask & RULE_PAIR_PREDICATE) {
|
|
op->filter.first.id = EcsWildcard;
|
|
}
|
|
if (op->filter.reg_mask & RULE_PAIR_OBJECT) {
|
|
op->filter.second.id = EcsWildcard;
|
|
}
|
|
op->filter.reg_mask = 0;
|
|
|
|
push_frame(rule);
|
|
|
|
tvar = src = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair,
|
|
written, true, false);
|
|
|
|
evar = NULL;
|
|
}
|
|
}
|
|
|
|
/* If no pair is provided, create operation from specified term */
|
|
if (!pair) {
|
|
op = insert_operation(rule, c, written);
|
|
|
|
/* If an explicit pair is provided, override the default one from the
|
|
* term. This allows for using a predicate or object variable different
|
|
* from what is in the term. One application of this is to substitute a
|
|
* predicate with its subsets, if it is non final */
|
|
} else {
|
|
op = insert_operation(rule, -1, written);
|
|
op->filter = *pair;
|
|
|
|
/* Assign the term id, so that the operation will still be correctly
|
|
* associated with the correct expression term. */
|
|
op->term = c;
|
|
}
|
|
|
|
/* If entity variable is known and resolved, create with for it */
|
|
if (evar && is_known(evar, written)) {
|
|
op->kind = EcsRuleWith;
|
|
op->r_in = evar->id;
|
|
set_input_to_subj(rule, op, term, src);
|
|
|
|
/* If table variable is known and resolved, create with for it */
|
|
} else if (tvar && is_known(tvar, written)) {
|
|
op->kind = EcsRuleWith;
|
|
op->r_in = tvar->id;
|
|
set_input_to_subj(rule, op, term, src);
|
|
|
|
/* If subject is neither table nor entitiy, with operates on literal */
|
|
} else if (!tvar && !evar) {
|
|
op->kind = EcsRuleWith;
|
|
set_input_to_subj(rule, op, term, src);
|
|
|
|
/* If subject is table or entity but not known, use select */
|
|
} else {
|
|
ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
op->kind = EcsRuleSelect;
|
|
set_output_to_subj(rule, op, term, src);
|
|
written[src->id] = true;
|
|
}
|
|
|
|
/* If supersets of subject are being evaluated, and we're looking for a
|
|
* specific filter, stop as soon as the filter has been matched. */
|
|
if (eval_subject_supersets && is_pair_known(rule, &op->filter, written)) {
|
|
op = insert_operation(rule, -1, written);
|
|
|
|
/* When the next operation returns, it will first hit SetJmp with a redo
|
|
* which will switch the jump label to the previous operation */
|
|
op->kind = EcsRuleSetJmp;
|
|
op->on_pass = rule->operation_count;
|
|
op->on_fail = lbl_start - 1;
|
|
}
|
|
|
|
if (op->filter.reg_mask & RULE_PAIR_PREDICATE) {
|
|
written[op->filter.first.reg] = true;
|
|
}
|
|
|
|
if (op->filter.reg_mask & RULE_PAIR_OBJECT) {
|
|
written[op->filter.second.reg] = true;
|
|
}
|
|
}
|
|
|
|
static
|
|
void prepare_predicate(
|
|
ecs_rule_t *rule,
|
|
ecs_rule_pair_t *pair,
|
|
int32_t term,
|
|
bool *written)
|
|
{
|
|
/* If pair is not final, resolve term for all IsA relationships of the
|
|
* predicate. Note that if the pair has final set to true, it is guaranteed
|
|
* that the predicate can be used in an IsA query */
|
|
if (!pair->final) {
|
|
ecs_rule_pair_t isa_pair = {
|
|
.first.id = EcsIsA,
|
|
.second.id = pair->first.id
|
|
};
|
|
|
|
ecs_rule_var_t *first = store_reflexive_set(rule, EcsRuleSubSet,
|
|
&isa_pair, written, true, true);
|
|
|
|
pair->first.reg = first->id;
|
|
pair->reg_mask |= RULE_PAIR_PREDICATE;
|
|
|
|
if (term != -1) {
|
|
rule->term_vars[term].first = first->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void insert_term_2(
|
|
ecs_rule_t *rule,
|
|
ecs_term_t *term,
|
|
ecs_rule_pair_t *filter,
|
|
int32_t c,
|
|
bool *written)
|
|
{
|
|
int32_t subj_id = -1, obj_id = -1;
|
|
ecs_rule_var_t *src = term_subj(rule, term);
|
|
if ((src = get_most_specific_var(rule, src, written))) {
|
|
subj_id = src->id;
|
|
}
|
|
|
|
ecs_rule_var_t *second = term_obj(rule, term);
|
|
if ((second = get_most_specific_var(rule, second, written))) {
|
|
obj_id = second->id;
|
|
}
|
|
|
|
bool subj_known = is_known(src, written);
|
|
bool same_obj_subj = false;
|
|
if (src && second) {
|
|
same_obj_subj = !ecs_os_strcmp(src->name, second->name);
|
|
}
|
|
|
|
if (!filter->transitive) {
|
|
insert_select_or_with(rule, c, term, src, filter, written);
|
|
if (src) src = &rule->vars[subj_id];
|
|
if (second) second = &rule->vars[obj_id];
|
|
|
|
} else if (filter->transitive) {
|
|
if (subj_known) {
|
|
if (is_known(second, written)) {
|
|
if (filter->second.id != EcsWildcard) {
|
|
ecs_rule_var_t *obj_subsets = store_reflexive_set(
|
|
rule, EcsRuleSubSet, filter, written, true, true);
|
|
|
|
if (src) {
|
|
src = &rule->vars[subj_id];
|
|
}
|
|
|
|
rule->term_vars[c].second = obj_subsets->id;
|
|
|
|
ecs_rule_pair_t pair = *filter;
|
|
pair.second.reg = obj_subsets->id;
|
|
pair.reg_mask |= RULE_PAIR_OBJECT;
|
|
|
|
insert_select_or_with(rule, c, term, src, &pair, written);
|
|
} else {
|
|
insert_select_or_with(rule, c, term, src, filter, written);
|
|
}
|
|
} else {
|
|
ecs_assert(second != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* If subject is literal, find supersets for subject */
|
|
if (src == NULL || src->kind == EcsRuleVarKindEntity) {
|
|
second = to_entity(rule, second);
|
|
|
|
ecs_rule_pair_t set_pair = *filter;
|
|
set_pair.reg_mask &= RULE_PAIR_PREDICATE;
|
|
|
|
if (src) {
|
|
set_pair.second.reg = src->id;
|
|
set_pair.reg_mask |= RULE_PAIR_OBJECT;
|
|
} else {
|
|
set_pair.second.id = term->src.id;
|
|
}
|
|
|
|
insert_reflexive_set(rule, EcsRuleSuperSet, second, set_pair,
|
|
c, written, filter->reflexive);
|
|
|
|
/* If subject is variable, first find matching pair for the
|
|
* evaluated entity(s) and return supersets */
|
|
} else {
|
|
ecs_rule_var_t *av = create_anonymous_variable(
|
|
rule, EcsRuleVarKindEntity);
|
|
|
|
src = &rule->vars[subj_id];
|
|
second = &rule->vars[obj_id];
|
|
second = to_entity(rule, second);
|
|
|
|
ecs_rule_pair_t set_pair = *filter;
|
|
set_pair.second.reg = av->id;
|
|
set_pair.reg_mask |= RULE_PAIR_OBJECT;
|
|
|
|
/* Insert with to find initial object for relationship */
|
|
insert_select_or_with(
|
|
rule, c, term, src, &set_pair, written);
|
|
|
|
push_frame(rule);
|
|
|
|
/* Find supersets for returned initial object. Make sure
|
|
* this is always reflexive since it needs to return the
|
|
* object from the pair that the entity has itself. */
|
|
insert_reflexive_set(rule, EcsRuleSuperSet, second, set_pair,
|
|
c, written, true);
|
|
}
|
|
}
|
|
|
|
/* src is not known */
|
|
} else {
|
|
ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (is_known(second, written)) {
|
|
ecs_rule_pair_t set_pair = *filter;
|
|
set_pair.reg_mask &= RULE_PAIR_PREDICATE; /* clear object mask */
|
|
|
|
if (second) {
|
|
set_pair.second.reg = second->id;
|
|
set_pair.reg_mask |= RULE_PAIR_OBJECT;
|
|
} else {
|
|
set_pair.second.id = term->second.id;
|
|
}
|
|
|
|
if (second) {
|
|
rule->term_vars[c].second = second->id;
|
|
} else {
|
|
ecs_rule_var_t *av = create_anonymous_variable(rule,
|
|
EcsRuleVarKindEntity);
|
|
rule->term_vars[c].second = av->id;
|
|
written[av->id] = true;
|
|
}
|
|
|
|
insert_reflexive_set(rule, EcsRuleSubSet, src, set_pair, c,
|
|
written, filter->reflexive);
|
|
} else if (src == second) {
|
|
insert_select_or_with(rule, c, term, src, filter, written);
|
|
} else {
|
|
ecs_assert(second != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_rule_var_t *av = NULL;
|
|
if (!filter->reflexive) {
|
|
av = create_anonymous_variable(rule, EcsRuleVarKindEntity);
|
|
}
|
|
|
|
src = &rule->vars[subj_id];
|
|
second = &rule->vars[obj_id];
|
|
second = to_entity(rule, second);
|
|
|
|
/* Insert instruction to find all sources and objects */
|
|
ecs_rule_op_t *op = insert_operation(rule, -1, written);
|
|
op->kind = EcsRuleSelect;
|
|
set_output_to_subj(rule, op, term, src);
|
|
op->filter.first = filter->first;
|
|
|
|
if (filter->reflexive) {
|
|
op->filter.second.id = EcsWildcard;
|
|
op->filter.reg_mask = filter->reg_mask & RULE_PAIR_PREDICATE;
|
|
} else {
|
|
op->filter.second.reg = av->id;
|
|
op->filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT;
|
|
written[av->id] = true;
|
|
}
|
|
|
|
written[src->id] = true;
|
|
|
|
/* Create new frame for operations that create reflexive set */
|
|
push_frame(rule);
|
|
|
|
/* Insert superset instruction to find all supersets */
|
|
if (filter->reflexive) {
|
|
src = ensure_most_specific_var(rule, src, written);
|
|
ecs_assert(src->kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(written[src->id] == true,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_rule_pair_t super_filter = {0};
|
|
super_filter.first = filter->first;
|
|
super_filter.second.reg = src->id;
|
|
super_filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT;
|
|
|
|
insert_reflexive_set(rule, EcsRuleSuperSet, second,
|
|
super_filter, c, written, true);
|
|
} else {
|
|
insert_reflexive_set(rule, EcsRuleSuperSet, second,
|
|
op->filter, c, written, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (same_obj_subj) {
|
|
/* Can't have relationship with same variables that is acyclic and not
|
|
* reflexive, this should've been caught earlier. */
|
|
ecs_assert(!filter->acyclic || filter->reflexive,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* If relationship is reflexive and entity has an instance of R, no checks
|
|
* are needed because R(X, X) is always true. */
|
|
if (!filter->reflexive) {
|
|
push_frame(rule);
|
|
|
|
/* Insert check if the (R, X) pair that was found matches with one
|
|
* of the entities in the table with the pair. */
|
|
ecs_rule_op_t *op = insert_operation(rule, -1, written);
|
|
second = get_most_specific_var(rule, second, written);
|
|
ecs_assert(second->kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(written[src->id] == true, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(written[second->id] == true, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
set_input_to_subj(rule, op, term, src);
|
|
op->filter.second.reg = second->id;
|
|
op->filter.reg_mask = RULE_PAIR_OBJECT;
|
|
|
|
if (src->kind == EcsRuleVarKindTable) {
|
|
op->kind = EcsRuleInTable;
|
|
} else {
|
|
op->kind = EcsRuleEq;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void insert_term_1(
|
|
ecs_rule_t *rule,
|
|
ecs_term_t *term,
|
|
ecs_rule_pair_t *filter,
|
|
int32_t c,
|
|
bool *written)
|
|
{
|
|
ecs_rule_var_t *src = term_subj(rule, term);
|
|
src = get_most_specific_var(rule, src, written);
|
|
insert_select_or_with(rule, c, term, src, filter, written);
|
|
}
|
|
|
|
static
|
|
void insert_term(
|
|
ecs_rule_t *rule,
|
|
ecs_term_t *term,
|
|
int32_t c,
|
|
bool *written)
|
|
{
|
|
bool obj_set = obj_is_set(term);
|
|
|
|
ensure_most_specific_var(rule, term_pred(rule, term), written);
|
|
if (obj_set) {
|
|
ensure_most_specific_var(rule, term_obj(rule, term), written);
|
|
}
|
|
|
|
/* If term has Not operator, prepend Not which turns a fail into a pass */
|
|
int32_t prev = rule->operation_count;
|
|
ecs_rule_op_t *not_pre;
|
|
if (term->oper == EcsNot) {
|
|
not_pre = insert_operation(rule, -1, written);
|
|
not_pre->kind = EcsRuleNot;
|
|
not_pre->has_in = false;
|
|
not_pre->has_out = false;
|
|
}
|
|
|
|
ecs_rule_pair_t filter = term_to_pair(rule, term);
|
|
prepare_predicate(rule, &filter, c, written);
|
|
|
|
if (subj_is_set(term) && !obj_set) {
|
|
insert_term_1(rule, term, &filter, c, written);
|
|
} else if (obj_set) {
|
|
insert_term_2(rule, term, &filter, c, written);
|
|
}
|
|
|
|
/* If term has Not operator, append Not which turns a pass into a fail */
|
|
if (term->oper == EcsNot) {
|
|
ecs_rule_op_t *not_post = insert_operation(rule, -1, written);
|
|
not_post->kind = EcsRuleNot;
|
|
not_post->has_in = false;
|
|
not_post->has_out = false;
|
|
|
|
not_post->on_pass = prev - 1;
|
|
not_post->on_fail = prev - 1;
|
|
not_pre = &rule->operations[prev];
|
|
not_pre->on_fail = rule->operation_count;
|
|
}
|
|
|
|
if (term->oper == EcsOptional) {
|
|
/* Insert Not instruction that ensures that the optional term is only
|
|
* executed once */
|
|
ecs_rule_op_t *jump = insert_operation(rule, -1, written);
|
|
jump->kind = EcsRuleNot;
|
|
jump->has_in = false;
|
|
jump->has_out = false;
|
|
jump->on_pass = rule->operation_count;
|
|
jump->on_fail = prev - 1;
|
|
|
|
/* Find exit instruction for optional term, and make the fail label
|
|
* point to the Not operation, so that even when the operation fails,
|
|
* it won't discard the result */
|
|
int i, min_fail = -1, exit_op = -1;
|
|
for (i = prev; i < rule->operation_count; i ++) {
|
|
ecs_rule_op_t *op = &rule->operations[i];
|
|
if (min_fail == -1 || (op->on_fail >= 0 && op->on_fail < min_fail)){
|
|
min_fail = op->on_fail;
|
|
exit_op = i;
|
|
}
|
|
}
|
|
|
|
ecs_assert(exit_op != -1, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_rule_op_t *op = &rule->operations[exit_op];
|
|
op->on_fail = rule->operation_count - 1;
|
|
}
|
|
|
|
push_frame(rule);
|
|
}
|
|
|
|
/* Create program from operations that will execute the query */
|
|
static
|
|
void compile_program(
|
|
ecs_rule_t *rule)
|
|
{
|
|
/* Trace which variables have been written while inserting instructions.
|
|
* This determines which instruction needs to be inserted */
|
|
bool written[ECS_RULE_MAX_VAR_COUNT] = { false };
|
|
|
|
ecs_term_t *terms = rule->filter.terms;
|
|
int32_t v, c, term_count = rule->filter.term_count;
|
|
ecs_rule_op_t *op;
|
|
|
|
/* Insert input, which is always the first instruction */
|
|
insert_input(rule);
|
|
|
|
/* First insert all instructions that do not have a variable subject. Such
|
|
* instructions iterate the type of an entity literal and are usually good
|
|
* candidates for quickly narrowing down the set of potential results. */
|
|
for (c = 0; c < term_count; c ++) {
|
|
ecs_term_t *term = &terms[c];
|
|
if (skip_term(term)) {
|
|
continue;
|
|
}
|
|
|
|
if (term->oper == EcsOptional || term->oper == EcsNot) {
|
|
continue;
|
|
}
|
|
|
|
ecs_rule_var_t* src = term_subj(rule, term);
|
|
if (src) {
|
|
continue;
|
|
}
|
|
|
|
insert_term(rule, term, c, written);
|
|
}
|
|
|
|
/* Insert variables based on dependency order */
|
|
for (v = 0; v < rule->subj_var_count; v ++) {
|
|
int32_t var_id = rule->var_eval_order[v];
|
|
ecs_rule_var_t *var = &rule->vars[var_id];
|
|
|
|
ecs_assert(var->kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
for (c = 0; c < term_count; c ++) {
|
|
ecs_term_t *term = &terms[c];
|
|
if (skip_term(term)) {
|
|
continue;
|
|
}
|
|
|
|
if (term->oper == EcsOptional || term->oper == EcsNot) {
|
|
continue;
|
|
}
|
|
|
|
/* Only process columns for which variable is subject */
|
|
ecs_rule_var_t* src = term_subj(rule, term);
|
|
if (src != var) {
|
|
continue;
|
|
}
|
|
|
|
insert_term(rule, term, c, written);
|
|
|
|
var = &rule->vars[var_id];
|
|
}
|
|
}
|
|
|
|
/* Insert terms with Not operators */
|
|
for (c = 0; c < term_count; c ++) {
|
|
ecs_term_t *term = &terms[c];
|
|
if (term->oper != EcsNot) {
|
|
continue;
|
|
}
|
|
|
|
insert_term(rule, term, c, written);
|
|
}
|
|
|
|
/* Insert terms with Optional operators last, as optional terms cannot
|
|
* eliminate results, and would just add overhead to evaluation of
|
|
* non-matching entities. */
|
|
for (c = 0; c < term_count; c ++) {
|
|
ecs_term_t *term = &terms[c];
|
|
if (term->oper != EcsOptional) {
|
|
continue;
|
|
}
|
|
|
|
insert_term(rule, term, c, written);
|
|
}
|
|
|
|
/* Verify all subject variables have been written. Source variables are of
|
|
* the table type, and a select/subset should have been inserted for each */
|
|
for (v = 0; v < rule->subj_var_count; v ++) {
|
|
if (!written[v]) {
|
|
/* If the table variable hasn't been written, this can only happen
|
|
* if an instruction wrote the variable before a select/subset could
|
|
* have been inserted for it. Make sure that this is the case by
|
|
* testing if an entity variable exists and whether it has been
|
|
* written. */
|
|
ecs_rule_var_t *var = find_variable(
|
|
rule, EcsRuleVarKindEntity, rule->vars[v].name);
|
|
ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(written[var->id], ECS_INTERNAL_ERROR, var->name);
|
|
(void)var;
|
|
}
|
|
}
|
|
|
|
/* Make sure that all entity variables are written. With the exception of
|
|
* the this variable, which can be returned as a table, other variables need
|
|
* to be available as entities. This ensures that all permutations for all
|
|
* variables are correctly returned by the iterator. When an entity variable
|
|
* hasn't been written yet at this point, it is because it only constrained
|
|
* through a common predicate or object. */
|
|
for (; v < rule->var_count; v ++) {
|
|
if (!written[v]) {
|
|
ecs_rule_var_t *var = &rule->vars[v];
|
|
ecs_assert(var->kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_rule_var_t *table_var = find_variable(
|
|
rule, EcsRuleVarKindTable, var->name);
|
|
|
|
/* A table variable must exist if the variable hasn't been resolved
|
|
* yet. If there doesn't exist one, this could indicate an
|
|
* unconstrained variable which should have been caught earlier */
|
|
ecs_assert(table_var != NULL, ECS_INTERNAL_ERROR, var->name);
|
|
|
|
/* Insert each operation that takes the table variable as input, and
|
|
* yields each entity in the table */
|
|
op = insert_operation(rule, -1, written);
|
|
op->kind = EcsRuleEach;
|
|
op->r_in = table_var->id;
|
|
op->r_out = var->id;
|
|
op->frame = rule->frame_count;
|
|
op->has_in = true;
|
|
op->has_out = true;
|
|
written[var->id] = true;
|
|
|
|
push_frame(rule);
|
|
}
|
|
}
|
|
|
|
/* Insert yield, which is always the last operation */
|
|
insert_yield(rule);
|
|
}
|
|
|
|
static
|
|
void create_variable_name_array(
|
|
ecs_rule_t *rule)
|
|
{
|
|
if (rule->var_count) {
|
|
int i;
|
|
for (i = 0; i < rule->var_count; i ++) {
|
|
ecs_rule_var_t *var = &rule->vars[i];
|
|
|
|
if (var->kind != EcsRuleVarKindEntity) {
|
|
/* Table variables are hidden for applications. */
|
|
rule->var_names[var->id] = NULL;
|
|
} else {
|
|
rule->var_names[var->id] = var->name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void create_variable_cross_references(
|
|
ecs_rule_t *rule)
|
|
{
|
|
if (rule->var_count) {
|
|
int i;
|
|
for (i = 0; i < rule->var_count; i ++) {
|
|
ecs_rule_var_t *var = &rule->vars[i];
|
|
if (var->kind == EcsRuleVarKindEntity) {
|
|
ecs_rule_var_t *tvar = find_variable(
|
|
rule, EcsRuleVarKindTable, var->name);
|
|
if (tvar) {
|
|
var->other = tvar->id;
|
|
} else {
|
|
var->other = -1;
|
|
}
|
|
} else {
|
|
ecs_rule_var_t *evar = find_variable(
|
|
rule, EcsRuleVarKindEntity, var->name);
|
|
if (evar) {
|
|
var->other = evar->id;
|
|
} else {
|
|
var->other = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Implementation for iterable mixin */
|
|
static
|
|
void rule_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_rule_t);
|
|
|
|
if (filter) {
|
|
iter[1] = ecs_rule_iter(world, (ecs_rule_t*)poly);
|
|
iter[0] = ecs_term_chain_iter(&iter[1], filter);
|
|
} else {
|
|
iter[0] = ecs_rule_iter(world, (ecs_rule_t*)poly);
|
|
}
|
|
}
|
|
|
|
static
|
|
int32_t find_term_var_id(
|
|
ecs_rule_t *rule,
|
|
ecs_term_id_t *term_id)
|
|
{
|
|
if (term_id_is_variable(term_id)) {
|
|
const char *var_name = term_id_var_name(term_id);
|
|
ecs_rule_var_t *var = find_variable(
|
|
rule, EcsRuleVarKindEntity, var_name);
|
|
if (var) {
|
|
return var->id;
|
|
} else {
|
|
/* If this is Any look for table variable. Since Any is only
|
|
* required to return a single result, there is no need to
|
|
* insert an each instruction for a matching table. */
|
|
if (term_id->id == EcsAny) {
|
|
var = find_variable(
|
|
rule, EcsRuleVarKindTable, var_name);
|
|
if (var) {
|
|
return var->id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static
|
|
void flecs_rule_fini(
|
|
ecs_rule_t *rule)
|
|
{
|
|
int32_t i;
|
|
for (i = 0; i < rule->var_count; i ++) {
|
|
ecs_os_free(rule->vars[i].name);
|
|
}
|
|
|
|
ecs_filter_fini(&rule->filter);
|
|
|
|
ecs_os_free(rule->operations);
|
|
ecs_os_free(rule);
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Rule has no terms */
|
|
if (!result->filter.term_count) {
|
|
rule_error(result, "rule has no terms");
|
|
goto error;
|
|
}
|
|
|
|
ecs_term_t *terms = result->filter.terms;
|
|
int32_t i, term_count = result->filter.term_count;
|
|
|
|
/* Make sure rule doesn't just have Not terms */
|
|
for (i = 0; i < term_count; i++) {
|
|
ecs_term_t *term = &terms[i];
|
|
if (term->oper != EcsNot) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == term_count) {
|
|
rule_error(result, "rule cannot only have terms with Not operator");
|
|
goto error;
|
|
}
|
|
|
|
/* Translate terms with a Not operator and Wildcard to Any, as we don't need
|
|
* implicit variables for those */
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
if (term->oper == EcsNot) {
|
|
if (term->first.id == EcsWildcard) {
|
|
term->first.id = EcsAny;
|
|
}
|
|
if (term->src.id == EcsWildcard) {
|
|
term->src.id = EcsAny;
|
|
}
|
|
if (term->second.id == EcsWildcard) {
|
|
term->second.id = EcsAny;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Find all variables & resolve dependencies */
|
|
if (scan_variables(result) != 0) {
|
|
goto error;
|
|
}
|
|
|
|
/* Create lookup array for subject variables */
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
ecs_rule_term_vars_t *vars = &result->term_vars[i];
|
|
vars->first = find_term_var_id(result, &term->first);
|
|
vars->src = find_term_var_id(result, &term->src);
|
|
vars->second = find_term_var_id(result, &term->second);
|
|
}
|
|
|
|
/* Generate the opcode array */
|
|
compile_program(result);
|
|
|
|
/* Create array with variable names so this can be easily accessed by
|
|
* iterators without requiring access to the ecs_rule_t */
|
|
create_variable_name_array(result);
|
|
|
|
/* Create cross-references between variables so it's easy to go from entity
|
|
* to table variable and vice versa */
|
|
create_variable_cross_references(result);
|
|
|
|
result->iterable.init = rule_iter_init;
|
|
|
|
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;
|
|
}
|
|
|
|
const ecs_filter_t* ecs_rule_get_filter(
|
|
const ecs_rule_t *rule)
|
|
{
|
|
return &rule->filter;
|
|
}
|
|
|
|
/* Quick convenience function to get a variable from an id */
|
|
static
|
|
ecs_rule_var_t* get_variable(
|
|
const ecs_rule_t *rule,
|
|
int32_t var_id)
|
|
{
|
|
if (var_id == UINT8_MAX) {
|
|
return NULL;
|
|
}
|
|
|
|
return (ecs_rule_var_t*)&rule->vars[var_id];
|
|
}
|
|
|
|
/* Convert the program to a string. This can be useful to analyze how a rule is
|
|
* being evaluated. */
|
|
char* ecs_rule_str(
|
|
ecs_rule_t *rule)
|
|
{
|
|
ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_world_t *world = rule->filter.world;
|
|
ecs_strbuf_t buf = ECS_STRBUF_INIT;
|
|
char filter_expr[256];
|
|
|
|
int32_t i, count = rule->operation_count;
|
|
for (i = 1; i < count; i ++) {
|
|
ecs_rule_op_t *op = &rule->operations[i];
|
|
ecs_rule_pair_t pair = op->filter;
|
|
ecs_entity_t first = pair.first.id;
|
|
ecs_entity_t second = pair.second.id;
|
|
const char *pred_name = NULL, *obj_name = NULL;
|
|
char *pred_name_alloc = NULL, *obj_name_alloc = NULL;
|
|
|
|
if (pair.reg_mask & RULE_PAIR_PREDICATE) {
|
|
ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_rule_var_t *type_var = &rule->vars[pair.first.reg];
|
|
pred_name = type_var->name;
|
|
} else if (first) {
|
|
pred_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, first));
|
|
pred_name = pred_name_alloc;
|
|
}
|
|
|
|
if (pair.reg_mask & RULE_PAIR_OBJECT) {
|
|
ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_rule_var_t *obj_var = &rule->vars[pair.second.reg];
|
|
obj_name = obj_var->name;
|
|
} else if (second) {
|
|
obj_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, second));
|
|
obj_name = obj_name_alloc;
|
|
} else if (pair.second_0) {
|
|
obj_name = "0";
|
|
}
|
|
|
|
ecs_strbuf_append(&buf, "%2d: [S:%2d, P:%2d, F:%2d, T:%2d] ", i,
|
|
op->frame, op->on_pass, op->on_fail, op->term);
|
|
|
|
bool has_filter = false;
|
|
|
|
switch(op->kind) {
|
|
case EcsRuleSelect:
|
|
ecs_strbuf_append(&buf, "select ");
|
|
has_filter = true;
|
|
break;
|
|
case EcsRuleWith:
|
|
ecs_strbuf_append(&buf, "with ");
|
|
has_filter = true;
|
|
break;
|
|
case EcsRuleStore:
|
|
ecs_strbuf_append(&buf, "store ");
|
|
break;
|
|
case EcsRuleSuperSet:
|
|
ecs_strbuf_append(&buf, "superset ");
|
|
has_filter = true;
|
|
break;
|
|
case EcsRuleSubSet:
|
|
ecs_strbuf_append(&buf, "subset ");
|
|
has_filter = true;
|
|
break;
|
|
case EcsRuleEach:
|
|
ecs_strbuf_append(&buf, "each ");
|
|
break;
|
|
case EcsRuleSetJmp:
|
|
ecs_strbuf_append(&buf, "setjmp ");
|
|
break;
|
|
case EcsRuleJump:
|
|
ecs_strbuf_append(&buf, "jump ");
|
|
break;
|
|
case EcsRuleNot:
|
|
ecs_strbuf_append(&buf, "not ");
|
|
break;
|
|
case EcsRuleInTable:
|
|
ecs_strbuf_append(&buf, "intable ");
|
|
has_filter = true;
|
|
break;
|
|
case EcsRuleEq:
|
|
ecs_strbuf_append(&buf, "eq ");
|
|
has_filter = true;
|
|
break;
|
|
case EcsRuleYield:
|
|
ecs_strbuf_append(&buf, "yield ");
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
if (op->has_out) {
|
|
ecs_rule_var_t *r_out = get_variable(rule, op->r_out);
|
|
if (r_out) {
|
|
ecs_strbuf_append(&buf, "O:%s%s ",
|
|
r_out->kind == EcsRuleVarKindTable ? "t" : "",
|
|
r_out->name);
|
|
} else if (op->subject) {
|
|
char *subj_path = ecs_get_fullpath(world, op->subject);
|
|
ecs_strbuf_append(&buf, "O:%s ", subj_path);
|
|
ecs_os_free(subj_path);
|
|
}
|
|
}
|
|
|
|
if (op->has_in) {
|
|
ecs_rule_var_t *r_in = get_variable(rule, op->r_in);
|
|
if (r_in) {
|
|
ecs_strbuf_append(&buf, "I:%s%s ",
|
|
r_in->kind == EcsRuleVarKindTable ? "t" : "",
|
|
r_in->name);
|
|
}
|
|
if (op->subject) {
|
|
char *subj_path = ecs_get_fullpath(world, op->subject);
|
|
ecs_strbuf_append(&buf, "I:%s ", subj_path);
|
|
ecs_os_free(subj_path);
|
|
}
|
|
}
|
|
|
|
if (has_filter) {
|
|
if (!pred_name) {
|
|
pred_name = "-";
|
|
}
|
|
if (!obj_name && !pair.second_0) {
|
|
ecs_os_sprintf(filter_expr, "(%s)", pred_name);
|
|
} else {
|
|
ecs_os_sprintf(filter_expr, "(%s, %s)", pred_name, obj_name);
|
|
}
|
|
ecs_strbuf_append(&buf, "F:%s", filter_expr);
|
|
}
|
|
|
|
ecs_strbuf_appendch(&buf, '\n');
|
|
|
|
ecs_os_free(pred_name_alloc);
|
|
ecs_os_free(obj_name_alloc);
|
|
}
|
|
|
|
return ecs_strbuf_get(&buf);
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
/* Public function that returns number of variables. This enables an application
|
|
* to iterate the variables and obtain their values. */
|
|
int32_t ecs_rule_var_count(
|
|
const ecs_rule_t *rule)
|
|
{
|
|
ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
return rule->var_count;
|
|
}
|
|
|
|
/* Public function to find a variable by name */
|
|
int32_t ecs_rule_find_var(
|
|
const ecs_rule_t *rule,
|
|
const char *name)
|
|
{
|
|
ecs_rule_var_t *v = find_variable(rule, EcsRuleVarKindEntity, name);
|
|
if (v) {
|
|
return v->id;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Public function to get the name of a variable. */
|
|
const char* ecs_rule_var_name(
|
|
const ecs_rule_t *rule,
|
|
int32_t var_id)
|
|
{
|
|
return rule->vars[var_id].name;
|
|
}
|
|
|
|
/* Public function to get the type of a variable. */
|
|
bool ecs_rule_var_is_entity(
|
|
const ecs_rule_t *rule,
|
|
int32_t var_id)
|
|
{
|
|
return rule->vars[var_id].kind == EcsRuleVarKindEntity;
|
|
}
|
|
|
|
static
|
|
void ecs_rule_iter_free(
|
|
ecs_iter_t *iter)
|
|
{
|
|
ecs_rule_iter_t *it = &iter->priv.iter.rule;
|
|
ecs_os_free(it->registers);
|
|
ecs_os_free(it->columns);
|
|
ecs_os_free(it->op_ctx);
|
|
iter->columns = NULL;
|
|
it->registers = NULL;
|
|
it->columns = NULL;
|
|
it->op_ctx = NULL;
|
|
}
|
|
|
|
/* Create rule iterator */
|
|
ecs_iter_t ecs_rule_iter(
|
|
const ecs_world_t *world,
|
|
const ecs_rule_t *rule)
|
|
{
|
|
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(rule != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_iter_t result = {0};
|
|
int i;
|
|
|
|
result.world = (ecs_world_t*)world;
|
|
result.real_world = (ecs_world_t*)ecs_get_world(rule->filter.world);
|
|
|
|
flecs_process_pending_tables(result.real_world);
|
|
|
|
ecs_rule_iter_t *it = &result.priv.iter.rule;
|
|
it->rule = rule;
|
|
|
|
if (rule->operation_count) {
|
|
if (rule->var_count) {
|
|
it->registers = ecs_os_malloc_n(ecs_var_t,
|
|
rule->operation_count * rule->var_count);
|
|
}
|
|
|
|
it->op_ctx = ecs_os_calloc_n(ecs_rule_op_ctx_t, rule->operation_count);
|
|
|
|
if (rule->filter.term_count) {
|
|
it->columns = ecs_os_malloc_n(int32_t,
|
|
rule->operation_count * rule->filter.term_count);
|
|
}
|
|
|
|
for (i = 0; i < rule->filter.term_count; i ++) {
|
|
it->columns[i] = -1;
|
|
}
|
|
}
|
|
|
|
it->op = 0;
|
|
|
|
for (i = 0; i < rule->var_count; i ++) {
|
|
if (rule->vars[i].kind == EcsRuleVarKindEntity) {
|
|
entity_reg_set(rule, it->registers, i, EcsWildcard);
|
|
} else {
|
|
table_reg_set(rule, it->registers, i, NULL);
|
|
}
|
|
}
|
|
|
|
result.variable_names = (char**)rule->var_names;
|
|
result.variable_count = rule->var_count;
|
|
result.field_count = rule->filter.term_count;
|
|
result.terms = rule->filter.terms;
|
|
result.next = ecs_rule_next;
|
|
result.fini = ecs_rule_iter_free;
|
|
ECS_BIT_COND(result.flags, EcsIterIsFilter,
|
|
ECS_BIT_IS_SET(rule->filter.flags, EcsFilterNoData));
|
|
|
|
flecs_iter_init(world, &result,
|
|
flecs_iter_cache_ids |
|
|
/* flecs_iter_cache_columns | provided by rule iterator */
|
|
flecs_iter_cache_sources |
|
|
flecs_iter_cache_sizes |
|
|
flecs_iter_cache_ptrs |
|
|
/* flecs_iter_cache_match_indices | not necessary for iteration */
|
|
flecs_iter_cache_variables);
|
|
|
|
result.columns = it->columns; /* prevent alloc */
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Edge case: if the filter has the same variable for both predicate and
|
|
* object, they are both resolved at the same time but at the time of
|
|
* evaluating the filter they're still wildcards which would match columns
|
|
* that have different predicates/objects. Do an additional scan to make
|
|
* sure the column we're returning actually matches. */
|
|
static
|
|
int32_t find_next_same_var(
|
|
ecs_type_t type,
|
|
int32_t column,
|
|
ecs_id_t pattern)
|
|
{
|
|
/* If same_var is true, this has to be a wildcard pair. We cannot have
|
|
* the same variable in a pair, and one part of a pair resolved with
|
|
* another part unresolved. */
|
|
ecs_assert(pattern == ecs_pair(EcsWildcard, EcsWildcard),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
(void)pattern;
|
|
|
|
/* Keep scanning for an id where rel and second are the same */
|
|
ecs_id_t *ids = type.array;
|
|
int32_t i, count = type.count;
|
|
for (i = column + 1; i < count; i ++) {
|
|
ecs_id_t id = ids[i];
|
|
if (!ECS_HAS_ID_FLAG(id, PAIR)) {
|
|
/* If id is not a pair, this will definitely not match, and we
|
|
* will find no further matches. */
|
|
return -1;
|
|
}
|
|
|
|
if (ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id)) {
|
|
/* Found a match! */
|
|
return i;
|
|
}
|
|
}
|
|
|
|
/* No pairs found with same rel/second */
|
|
return -1;
|
|
}
|
|
|
|
static
|
|
int32_t find_next_column(
|
|
const ecs_world_t *world,
|
|
const ecs_table_t *table,
|
|
int32_t column,
|
|
ecs_rule_filter_t *filter)
|
|
{
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_entity_t pattern = filter->mask;
|
|
ecs_type_t type = table->type;
|
|
|
|
if (column == -1) {
|
|
ecs_table_record_t *tr = flecs_table_record_get(world, table, pattern);
|
|
if (!tr) {
|
|
return -1;
|
|
}
|
|
column = tr->column;
|
|
} else {
|
|
column = ecs_search_offset(world, table, column + 1, filter->mask, 0);
|
|
if (column == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (filter->same_var) {
|
|
column = find_next_same_var(type, column - 1, filter->mask);
|
|
}
|
|
|
|
return column;
|
|
}
|
|
|
|
/* This function finds the next table in a table set, and is used by the select
|
|
* operation. The function automatically skips empty tables, so that subsequent
|
|
* operations don't waste a lot of processing for nothing. */
|
|
static
|
|
ecs_table_record_t find_next_table(
|
|
ecs_rule_filter_t *filter,
|
|
ecs_rule_with_ctx_t *op_ctx)
|
|
{
|
|
ecs_table_cache_iter_t *it = &op_ctx->it;
|
|
ecs_table_t *table = NULL;
|
|
int32_t column = -1;
|
|
|
|
const ecs_table_record_t *tr;
|
|
while ((column == -1) && (tr = flecs_table_cache_next(it, ecs_table_record_t))) {
|
|
table = tr->hdr.table;
|
|
|
|
/* Should only iterate non-empty tables */
|
|
ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
column = tr->column;
|
|
if (filter->same_var) {
|
|
column = find_next_same_var(table->type, column - 1, filter->mask);
|
|
}
|
|
}
|
|
|
|
if (column == -1) {
|
|
table = NULL;
|
|
}
|
|
|
|
return (ecs_table_record_t){.hdr.table = table, .column = column};
|
|
}
|
|
|
|
|
|
static
|
|
ecs_id_record_t* find_tables(
|
|
ecs_world_t *world,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
|
if (!idr || !flecs_table_cache_count(&idr->cache)) {
|
|
/* Skip ids that don't have (non-empty) tables */
|
|
return NULL;
|
|
}
|
|
return idr;
|
|
}
|
|
|
|
static
|
|
ecs_id_t rule_get_column(
|
|
ecs_type_t type,
|
|
int32_t column)
|
|
{
|
|
ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_id_t *comp = &type.array[column];
|
|
return *comp;
|
|
}
|
|
|
|
static
|
|
void set_source(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
ecs_var_t *regs,
|
|
int32_t r)
|
|
{
|
|
if (op->term == -1) {
|
|
/* If operation is not associated with a term, don't set anything */
|
|
return;
|
|
}
|
|
|
|
ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
const ecs_rule_t *rule = it->priv.iter.rule.rule;
|
|
if ((r != UINT8_MAX) && rule->vars[r].kind == EcsRuleVarKindEntity) {
|
|
it->sources[op->term] = reg_get_entity(rule, op, regs, r);
|
|
} else {
|
|
it->sources[op->term] = 0;
|
|
}
|
|
}
|
|
|
|
static
|
|
void set_term_vars(
|
|
const ecs_rule_t *rule,
|
|
ecs_var_t *regs,
|
|
int32_t term,
|
|
ecs_id_t id)
|
|
{
|
|
if (term != -1) {
|
|
ecs_world_t *world = rule->filter.world;
|
|
const ecs_rule_term_vars_t *vars = &rule->term_vars[term];
|
|
if (vars->first != -1) {
|
|
regs[vars->first].entity = ecs_pair_first(world, id);
|
|
ecs_assert(ecs_is_valid(world, regs[vars->first].entity),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
if (vars->second != -1) {
|
|
regs[vars->second].entity = ecs_pair_second(world, id);
|
|
ecs_assert(ecs_is_valid(world, regs[vars->second].entity),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Input operation. The input operation acts as a placeholder for the start of
|
|
* the program, and creates an entry in the register array that can serve to
|
|
* store variables passed to an iterator. */
|
|
static
|
|
bool eval_input(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
(void)it;
|
|
(void)op;
|
|
(void)op_index;
|
|
|
|
if (!redo) {
|
|
/* First operation executed by the iterator. Always return true. */
|
|
return true;
|
|
} else {
|
|
/* When Input is asked to redo, it means that all other operations have
|
|
* exhausted their results. Input itself does not yield anything, so
|
|
* return false. This will terminate rule execution. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static
|
|
bool eval_superset(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
const ecs_rule_t *rule = iter->rule;
|
|
ecs_world_t *world = rule->filter.world;
|
|
ecs_rule_superset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.superset;
|
|
ecs_rule_superset_frame_t *frame = NULL;
|
|
ecs_var_t *regs = get_registers(iter, op);
|
|
|
|
/* Get register indices for output */
|
|
int32_t sp;
|
|
int32_t r = op->r_out;
|
|
|
|
/* Register cannot be a literal, since we need to store things in it */
|
|
ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Get queried for id, fill out potential variables */
|
|
ecs_rule_pair_t pair = op->filter;
|
|
|
|
ecs_rule_filter_t filter = pair_to_filter(iter, op, pair);
|
|
ecs_entity_t rel = ECS_PAIR_FIRST(filter.mask);
|
|
ecs_rule_filter_t super_filter = {
|
|
.mask = ecs_pair(rel, EcsWildcard)
|
|
};
|
|
ecs_table_t *table = NULL;
|
|
|
|
/* Check if input register is constrained */
|
|
ecs_entity_t result = iter->registers[r].entity;
|
|
bool output_is_input = ecs_iter_var_is_constrained(it, r);
|
|
if (output_is_input && !redo) {
|
|
ecs_assert(regs[r].entity == iter->registers[r].entity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
if (!redo) {
|
|
op_ctx->stack = op_ctx->storage;
|
|
sp = op_ctx->sp = 0;
|
|
frame = &op_ctx->stack[sp];
|
|
|
|
/* Get table of object for which to get supersets */
|
|
ecs_entity_t second = ECS_PAIR_SECOND(filter.mask);
|
|
if (second == EcsWildcard) {
|
|
ecs_assert(pair.reg_mask & RULE_PAIR_OBJECT,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
table = regs[pair.second.reg].range.table;
|
|
} else {
|
|
table = table_from_entity(world, second).table;
|
|
}
|
|
|
|
int32_t column;
|
|
|
|
/* If output variable is already set, check if it matches */
|
|
if (output_is_input) {
|
|
ecs_id_t id = ecs_pair(rel, result);
|
|
ecs_entity_t src = 0;
|
|
column = ecs_search_relation(world, table, 0, id, rel,
|
|
0, &src, 0, 0);
|
|
if (column != -1) {
|
|
if (src != 0) {
|
|
table = ecs_get_table(world, src);
|
|
}
|
|
}
|
|
} else {
|
|
column = find_next_column(world, table, -1, &super_filter);
|
|
}
|
|
|
|
/* If no matching column was found, there are no supersets */
|
|
if (column == -1) {
|
|
return false;
|
|
}
|
|
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_id_t col_id = rule_get_column(table->type, column);
|
|
ecs_assert(ECS_HAS_ID_FLAG(col_id, PAIR), ECS_INTERNAL_ERROR, NULL);
|
|
ecs_entity_t col_obj = ecs_pair_second(world, col_id);
|
|
|
|
reg_set_entity(rule, regs, r, col_obj);
|
|
|
|
frame->table = table;
|
|
frame->column = column;
|
|
|
|
return true;
|
|
} else if (output_is_input) {
|
|
return false;
|
|
}
|
|
|
|
sp = op_ctx->sp;
|
|
frame = &op_ctx->stack[sp];
|
|
table = frame->table;
|
|
int32_t column = frame->column;
|
|
|
|
ecs_id_t col_id = rule_get_column(table->type, column);
|
|
ecs_entity_t col_obj = ecs_pair_second(world, col_id);
|
|
ecs_table_t *next_table = table_from_entity(world, col_obj).table;
|
|
|
|
if (next_table) {
|
|
sp ++;
|
|
frame = &op_ctx->stack[sp];
|
|
frame->table = next_table;
|
|
frame->column = -1;
|
|
}
|
|
|
|
do {
|
|
frame = &op_ctx->stack[sp];
|
|
table = frame->table;
|
|
column = frame->column;
|
|
|
|
column = find_next_column(world, table, column, &super_filter);
|
|
if (column != -1) {
|
|
op_ctx->sp = sp;
|
|
frame->column = column;
|
|
col_id = rule_get_column(table->type, column);
|
|
col_obj = ecs_pair_second(world, col_id);
|
|
reg_set_entity(rule, regs, r, col_obj);
|
|
return true;
|
|
}
|
|
|
|
sp --;
|
|
} while (sp >= 0);
|
|
|
|
return false;
|
|
}
|
|
|
|
static
|
|
bool eval_subset(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
const ecs_rule_t *rule = iter->rule;
|
|
ecs_world_t *world = rule->filter.world;
|
|
ecs_rule_subset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.subset;
|
|
ecs_rule_subset_frame_t *frame = NULL;
|
|
ecs_table_record_t table_record;
|
|
ecs_var_t *regs = get_registers(iter, op);
|
|
|
|
/* Get register indices for output */
|
|
int32_t sp, row;
|
|
int32_t r = op->r_out;
|
|
ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Get queried for id, fill out potential variables */
|
|
ecs_rule_pair_t pair = op->filter;
|
|
ecs_rule_filter_t filter = pair_to_filter(iter, op, pair);
|
|
ecs_id_record_t *idr;
|
|
ecs_table_t *table = NULL;
|
|
|
|
if (!redo) {
|
|
op_ctx->stack = op_ctx->storage;
|
|
sp = op_ctx->sp = 0;
|
|
frame = &op_ctx->stack[sp];
|
|
idr = frame->with_ctx.idr = find_tables(world, filter.mask);
|
|
if (!idr) {
|
|
return false;
|
|
}
|
|
|
|
flecs_table_cache_iter(&idr->cache, &frame->with_ctx.it);
|
|
table_record = find_next_table(&filter, &frame->with_ctx);
|
|
|
|
/* If first table set has no non-empty table, yield nothing */
|
|
if (!table_record.hdr.table) {
|
|
return false;
|
|
}
|
|
|
|
frame->row = 0;
|
|
frame->column = table_record.column;
|
|
table_reg_set(rule, regs, r, (frame->table = table_record.hdr.table));
|
|
goto yield;
|
|
}
|
|
|
|
do {
|
|
sp = op_ctx->sp;
|
|
frame = &op_ctx->stack[sp];
|
|
table = frame->table;
|
|
row = frame->row;
|
|
|
|
/* If row exceeds number of elements in table, find next table in frame that
|
|
* still has entities */
|
|
while ((sp >= 0) && (row >= ecs_table_count(table))) {
|
|
table_record = find_next_table(&filter, &frame->with_ctx);
|
|
|
|
if (table_record.hdr.table) {
|
|
table = frame->table = table_record.hdr.table;
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
frame->row = 0;
|
|
frame->column = table_record.column;
|
|
table_reg_set(rule, regs, r, table);
|
|
goto yield;
|
|
} else {
|
|
sp = -- op_ctx->sp;
|
|
if (sp < 0) {
|
|
/* If none of the frames yielded anything, no more data */
|
|
return false;
|
|
}
|
|
frame = &op_ctx->stack[sp];
|
|
table = frame->table;
|
|
idr = frame->with_ctx.idr;
|
|
row = ++ frame->row;
|
|
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
}
|
|
|
|
int32_t row_count = ecs_table_count(table);
|
|
|
|
/* Table must have at least row elements */
|
|
ecs_assert(row_count > row, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities);
|
|
ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* The entity used to find the next table set */
|
|
do {
|
|
ecs_entity_t e = entities[row];
|
|
|
|
/* Create look_for expression with the resolved entity as object */
|
|
pair.reg_mask &= ~RULE_PAIR_OBJECT; /* turn of bit because it's not a reg */
|
|
pair.second.id = e;
|
|
filter = pair_to_filter(iter, op, pair);
|
|
|
|
/* Find table set for expression */
|
|
table = NULL;
|
|
idr = find_tables(world, filter.mask);
|
|
|
|
/* If table set is found, find first non-empty table */
|
|
if (idr) {
|
|
ecs_rule_subset_frame_t *new_frame = &op_ctx->stack[sp + 1];
|
|
new_frame->with_ctx.idr = idr;
|
|
flecs_table_cache_iter(&idr->cache, &new_frame->with_ctx.it);
|
|
table_record = find_next_table(&filter, &new_frame->with_ctx);
|
|
|
|
/* If set contains non-empty table, push it to stack */
|
|
if (table_record.hdr.table) {
|
|
table = table_record.hdr.table;
|
|
op_ctx->sp ++;
|
|
new_frame->table = table;
|
|
new_frame->row = 0;
|
|
new_frame->column = table_record.column;
|
|
frame = new_frame;
|
|
}
|
|
}
|
|
|
|
/* If no table was found for the current entity, advance row */
|
|
if (!table) {
|
|
row = ++ frame->row;
|
|
}
|
|
} while (!table && row < row_count);
|
|
} while (!table);
|
|
|
|
table_reg_set(rule, regs, r, table);
|
|
|
|
yield:
|
|
set_term_vars(rule, regs, op->term, frame->table->type.array[frame->column]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Select operation. The select operation finds and iterates a table set that
|
|
* corresponds to its pair expression. */
|
|
static
|
|
bool eval_select(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
const ecs_rule_t *rule = iter->rule;
|
|
ecs_world_t *world = rule->filter.world;
|
|
ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with;
|
|
ecs_table_record_t table_record;
|
|
ecs_var_t *regs = get_registers(iter, op);
|
|
|
|
/* Get register indices for output */
|
|
int32_t r = op->r_out;
|
|
ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Get queried for id, fill out potential variables */
|
|
ecs_rule_pair_t pair = op->filter;
|
|
ecs_rule_filter_t filter = pair_to_filter(iter, op, pair);
|
|
ecs_entity_t pattern = filter.mask;
|
|
int32_t *columns = rule_get_columns(iter, op);
|
|
|
|
int32_t column = -1;
|
|
ecs_table_t *table = NULL;
|
|
ecs_id_record_t *idr;
|
|
|
|
if (!redo && op->term != -1) {
|
|
columns[op->term] = -1;
|
|
}
|
|
|
|
/* If this is a redo, we already looked up the table set */
|
|
if (redo) {
|
|
idr = op_ctx->idr;
|
|
|
|
/* If this is not a redo lookup the table set. Even though this may not be
|
|
* the first time the operation is evaluated, variables may have changed
|
|
* since last time, which could change the table set to lookup. */
|
|
} else {
|
|
/* A table set is a set of tables that all contain at least the
|
|
* requested look_for expression. What is returned is a table record,
|
|
* which in addition to the table also stores the first occurrance at
|
|
* which the requested expression occurs in the table. This reduces (and
|
|
* in most cases eliminates) any searching that needs to occur in a
|
|
* table type. Tables are also registered under wildcards, which is why
|
|
* this operation can simply use the look_for variable directly */
|
|
|
|
idr = op_ctx->idr = find_tables(world, pattern);
|
|
}
|
|
|
|
/* If no table set was found for queried for entity, there are no results */
|
|
if (!idr) {
|
|
return false;
|
|
}
|
|
|
|
/* If the input register is not NULL, this is a variable that's been set by
|
|
* the application. */
|
|
table = iter->registers[r].range.table;
|
|
bool output_is_input = table != NULL;
|
|
|
|
if (output_is_input && !redo) {
|
|
ecs_assert(regs[r].range.table == iter->registers[r].range.table,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
table = iter->registers[r].range.table;
|
|
|
|
/* Check if table can be found in the id record. If not, the provided
|
|
* table does not match with the query. */
|
|
ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table);
|
|
if (!tr) {
|
|
return false;
|
|
}
|
|
|
|
column = op_ctx->column = tr->column;
|
|
}
|
|
|
|
/* If this is not a redo, start at the beginning */
|
|
if (!redo) {
|
|
if (!table) {
|
|
flecs_table_cache_iter(&idr->cache, &op_ctx->it);
|
|
|
|
/* Return the first table_record in the table set. */
|
|
table_record = find_next_table(&filter, op_ctx);
|
|
|
|
/* If no table record was found, there are no results. */
|
|
if (!table_record.hdr.table) {
|
|
return false;
|
|
}
|
|
|
|
table = table_record.hdr.table;
|
|
|
|
/* Set current column to first occurrence of queried for entity */
|
|
column = op_ctx->column = table_record.column;
|
|
|
|
/* Store table in register */
|
|
table_reg_set(rule, regs, r, table);
|
|
}
|
|
|
|
/* If this is a redo, progress to the next match */
|
|
} else {
|
|
/* First test if there are any more matches for the current table, in
|
|
* case we're looking for a wildcard. */
|
|
if (filter.wildcard) {
|
|
table = table_reg_get(rule, regs, r).table;
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
column = op_ctx->column;
|
|
column = find_next_column(world, table, column, &filter);
|
|
op_ctx->column = column;
|
|
}
|
|
|
|
/* If no next match was found for this table, move to next table */
|
|
if (column == -1) {
|
|
if (output_is_input) {
|
|
return false;
|
|
}
|
|
|
|
table_record = find_next_table(&filter, op_ctx);
|
|
if (!table_record.hdr.table) {
|
|
return false;
|
|
}
|
|
|
|
/* Assign new table to table register */
|
|
table_reg_set(rule, regs, r, (table = table_record.hdr.table));
|
|
|
|
/* Assign first matching column */
|
|
column = op_ctx->column = table_record.column;
|
|
}
|
|
}
|
|
|
|
/* If we got here, we found a match. Table and column must be set */
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (op->term != -1) {
|
|
columns[op->term] = column;
|
|
}
|
|
|
|
/* If this is a wildcard query, fill out the variable registers */
|
|
if (filter.wildcard) {
|
|
reify_variables(iter, op, &filter, table->type, column);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* With operation. The With operation always comes after either the Select or
|
|
* another With operation, and applies additional filters to the table. */
|
|
static
|
|
bool eval_with(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
const ecs_rule_t *rule = iter->rule;
|
|
ecs_world_t *world = rule->filter.world;
|
|
ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with;
|
|
ecs_var_t *regs = get_registers(iter, op);
|
|
|
|
/* Get register indices for input */
|
|
int32_t r = op->r_in;
|
|
|
|
/* Get queried for id, fill out potential variables */
|
|
ecs_rule_pair_t pair = op->filter;
|
|
ecs_rule_filter_t filter = pair_to_filter(iter, op, pair);
|
|
int32_t *columns = rule_get_columns(iter, op);
|
|
|
|
/* If looked for entity is not a wildcard (meaning there are no unknown/
|
|
* unconstrained variables) and this is a redo, nothing more to yield. */
|
|
if (redo && !filter.wildcard) {
|
|
return false;
|
|
}
|
|
|
|
int32_t column = -1;
|
|
ecs_table_t *table = NULL;
|
|
ecs_id_record_t *idr;
|
|
|
|
if (op->term != -1) {
|
|
columns[op->term] = -1;
|
|
}
|
|
|
|
/* If this is a redo, we already looked up the table set */
|
|
if (redo) {
|
|
idr = op_ctx->idr;
|
|
|
|
/* If this is not a redo lookup the table set. Even though this may not be
|
|
* the first time the operation is evaluated, variables may have changed
|
|
* since last time, which could change the table set to lookup. */
|
|
} else {
|
|
/* Predicates can be reflexive, which means that if we have a
|
|
* transitive predicate which is provided with the same subject and
|
|
* object, it should return true. By default with will not return true
|
|
* as the subject likely does not have itself as a relationship, which
|
|
* is why this is a special case.
|
|
*
|
|
* TODO: might want to move this code to a separate with_reflexive
|
|
* instruction to limit branches for non-transitive queries (and to keep
|
|
* code more readable).
|
|
*/
|
|
if (pair.transitive && pair.reflexive) {
|
|
ecs_entity_t src = 0, second = 0;
|
|
|
|
if (r == UINT8_MAX) {
|
|
src = op->subject;
|
|
} else {
|
|
const ecs_rule_var_t *v_subj = &rule->vars[r];
|
|
|
|
if (v_subj->kind == EcsRuleVarKindEntity) {
|
|
src = entity_reg_get(rule, regs, r);
|
|
|
|
/* This is the input for the op, so should always be set */
|
|
ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
}
|
|
|
|
/* If src is set, it means that it is an entity. Try to also
|
|
* resolve the object. */
|
|
if (src) {
|
|
/* If the object is not a wildcard, it has been reified. Get the
|
|
* value from either the register or as a literal */
|
|
if (!filter.second_wildcard) {
|
|
second = ECS_PAIR_SECOND(filter.mask);
|
|
if (ecs_strip_generation(src) == second) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The With operation finds the table set that belongs to its pair
|
|
* filter. The table set is a sparse set that provides an O(1) operation
|
|
* to check whether the current table has the required expression. */
|
|
idr = op_ctx->idr = find_tables(world, filter.mask);
|
|
}
|
|
|
|
/* If no table set was found for queried for entity, there are no results.
|
|
* If this result is a transitive query, the table we're evaluating may not
|
|
* be in the returned table set. Regardless, if the filter that contains a
|
|
* transitive predicate does not have any tables associated with it, there
|
|
* can be no transitive matches for the filter. */
|
|
if (!idr) {
|
|
return false;
|
|
}
|
|
|
|
table = reg_get_range(rule, op, regs, r).table;
|
|
if (!table) {
|
|
return false;
|
|
}
|
|
|
|
/* If this is not a redo, start at the beginning */
|
|
if (!redo) {
|
|
column = op_ctx->column = find_next_column(world, table, -1, &filter);
|
|
|
|
/* If this is a redo, progress to the next match */
|
|
} else {
|
|
if (!filter.wildcard) {
|
|
return false;
|
|
}
|
|
|
|
/* Find the next match for the expression in the column. The columns
|
|
* array keeps track of the state for each With operation, so that
|
|
* even after redoing a With, the search doesn't have to start from
|
|
* the beginning. */
|
|
column = find_next_column(world, table, op_ctx->column, &filter);
|
|
op_ctx->column = column;
|
|
}
|
|
|
|
/* If no next match was found for this table, no more data */
|
|
if (column == -1) {
|
|
return false;
|
|
}
|
|
|
|
if (op->term != -1) {
|
|
columns[op->term] = column;
|
|
}
|
|
|
|
/* If we got here, we found a match. Table and column must be set */
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* If this is a wildcard query, fill out the variable registers */
|
|
if (filter.wildcard) {
|
|
reify_variables(iter, op, &filter, table->type, column);
|
|
}
|
|
|
|
set_source(it, op, regs, r);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Each operation. The each operation is a simple operation that takes a table
|
|
* as input, and outputs each of the entities in a table. This operation is
|
|
* useful for rules that match a table, and where the entities of the table are
|
|
* used as predicate or object. If a rule contains an each operation, an
|
|
* iterator is guaranteed to yield an entity instead of a table. The input for
|
|
* an each operation can only be the root variable. */
|
|
static
|
|
bool eval_each(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
ecs_rule_each_ctx_t *op_ctx = &iter->op_ctx[op_index].is.each;
|
|
ecs_var_t *regs = get_registers(iter, op);
|
|
int32_t r_in = op->r_in;
|
|
int32_t r_out = op->r_out;
|
|
ecs_entity_t e;
|
|
|
|
/* Make sure in/out registers are of the correct kind */
|
|
ecs_assert(iter->rule->vars[r_in].kind == EcsRuleVarKindTable,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(iter->rule->vars[r_out].kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Get table, make sure that it contains data. The select operation should
|
|
* ensure that empty tables are never forwarded. */
|
|
ecs_table_range_t slice = table_reg_get(iter->rule, regs, r_in);
|
|
ecs_table_t *table = slice.table;
|
|
if (table) {
|
|
int32_t row, count = slice.count;
|
|
int32_t offset = slice.offset;
|
|
|
|
if (!count) {
|
|
count = ecs_table_count(table);
|
|
ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL);
|
|
} else {
|
|
count += offset;
|
|
}
|
|
|
|
ecs_entity_t *entities = ecs_vec_first(&table->data.entities);
|
|
ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* If this is is not a redo, start from row 0, otherwise go to the
|
|
* next entity. */
|
|
if (!redo) {
|
|
row = op_ctx->row = offset;
|
|
} else {
|
|
row = ++ op_ctx->row;
|
|
}
|
|
|
|
/* If row exceeds number of entities in table, return false */
|
|
if (row >= count) {
|
|
return false;
|
|
}
|
|
|
|
/* Skip builtin entities that could confuse operations */
|
|
e = entities[row];
|
|
while (e == EcsWildcard || e == EcsThis || e == EcsAny) {
|
|
row ++;
|
|
if (row == count) {
|
|
return false;
|
|
}
|
|
e = entities[row];
|
|
}
|
|
} else {
|
|
if (!redo) {
|
|
e = entity_reg_get(iter->rule, regs, r_in);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Assign entity */
|
|
entity_reg_set(iter->rule, regs, r_out, e);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Store operation. Stores entity in register. This can either be an entity
|
|
* literal or an entity variable that will be stored in a table register. The
|
|
* latter facilitates scenarios where an iterator only need to return a single
|
|
* entity but where the Yield returns tables. */
|
|
static
|
|
bool eval_store(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
(void)op_index;
|
|
|
|
if (redo) {
|
|
/* Only ever return result once */
|
|
return false;
|
|
}
|
|
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
const ecs_rule_t *rule = iter->rule;
|
|
ecs_world_t *world = rule->filter.world;
|
|
ecs_var_t *regs = get_registers(iter, op);
|
|
int32_t r_in = op->r_in;
|
|
int32_t r_out = op->r_out;
|
|
|
|
(void)world;
|
|
|
|
const ecs_rule_var_t *var_out = &rule->vars[r_out];
|
|
if (var_out->kind == EcsRuleVarKindEntity) {
|
|
ecs_entity_t out, in = reg_get_entity(rule, op, regs, r_in);
|
|
ecs_assert(in != 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(ecs_is_valid(world, in), ECS_INTERNAL_ERROR, NULL);
|
|
|
|
out = iter->registers[r_out].entity;
|
|
bool output_is_input = ecs_iter_var_is_constrained(it, r_out);
|
|
if (output_is_input && !redo) {
|
|
ecs_assert(regs[r_out].entity == iter->registers[r_out].entity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (out != in) {
|
|
/* If output variable is set it must match the input */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
reg_set_entity(rule, regs, r_out, in);
|
|
} else {
|
|
ecs_table_range_t out, in = reg_get_range(rule, op, regs, r_in);
|
|
|
|
out = iter->registers[r_out].range;
|
|
bool output_is_input = out.table != NULL;
|
|
|
|
if (output_is_input && !redo) {
|
|
ecs_assert(regs[r_out].entity == iter->registers[r_out].entity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (ecs_os_memcmp_t(&out, &in, ecs_table_range_t)) {
|
|
/* If output variable is set it must match the input */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
reg_set_range(rule, regs, r_out, &in);
|
|
|
|
/* Ensure that if the input was an empty entity, information is not
|
|
* lost */
|
|
if (!regs[r_out].range.table) {
|
|
regs[r_out].entity = reg_get_entity(rule, op, regs, r_in);
|
|
ecs_assert(ecs_is_valid(world, regs[r_out].entity),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
}
|
|
|
|
ecs_rule_filter_t filter = pair_to_filter(iter, op, op->filter);
|
|
set_term_vars(rule, regs, op->term, filter.mask);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* A setjmp operation sets the jump label for a subsequent jump label. When the
|
|
* operation is first evaluated (redo=false) it sets the label to the on_pass
|
|
* label, and returns true. When the operation is evaluated again (redo=true)
|
|
* the label is set to on_fail and the operation returns false. */
|
|
static
|
|
bool eval_setjmp(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
ecs_rule_setjmp_ctx_t *ctx = &iter->op_ctx[op_index].is.setjmp;
|
|
|
|
if (!redo) {
|
|
ctx->label = op->on_pass;
|
|
return true;
|
|
} else {
|
|
ctx->label = op->on_fail;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* The jump operation jumps to an operation label. The operation always returns
|
|
* true. Since the operation modifies the control flow of the program directly,
|
|
* the dispatcher does not look at the on_pass or on_fail labels of the jump
|
|
* instruction. Instead, the on_pass label is used to store the label of the
|
|
* operation that contains the label to jump to. */
|
|
static
|
|
bool eval_jump(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
(void)it;
|
|
(void)op;
|
|
(void)op_index;
|
|
|
|
/* Passthrough, result is not used for control flow */
|
|
return !redo;
|
|
}
|
|
|
|
/* The not operation reverts the result of the operation it embeds */
|
|
static
|
|
bool eval_not(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
(void)it;
|
|
(void)op;
|
|
(void)op_index;
|
|
|
|
return !redo;
|
|
}
|
|
|
|
/* Check if entity is stored in table */
|
|
static
|
|
bool eval_intable(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
(void)op_index;
|
|
|
|
if (redo) {
|
|
return false;
|
|
}
|
|
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
const ecs_rule_t *rule = iter->rule;
|
|
ecs_world_t *world = rule->filter.world;
|
|
ecs_var_t *regs = get_registers(iter, op);
|
|
ecs_table_t *table = table_reg_get(rule, regs, op->r_in).table;
|
|
|
|
ecs_rule_pair_t pair = op->filter;
|
|
ecs_rule_filter_t filter = pair_to_filter(iter, op, pair);
|
|
ecs_entity_t second = ECS_PAIR_SECOND(filter.mask);
|
|
ecs_assert(second != 0 && second != EcsWildcard, ECS_INTERNAL_ERROR, NULL);
|
|
second = ecs_get_alive(world, second);
|
|
ecs_assert(second != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_table_t *obj_table = ecs_get_table(world, second);
|
|
return obj_table == table;
|
|
}
|
|
|
|
/* Yield operation. This is the simplest operation, as all it does is return
|
|
* false. This will move the solver back to the previous instruction which
|
|
* forces redo's on previous operations, for as long as there are matching
|
|
* results. */
|
|
static
|
|
bool eval_yield(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
(void)it;
|
|
(void)op;
|
|
(void)op_index;
|
|
(void)redo;
|
|
|
|
/* Yield always returns false, because there are never any operations after
|
|
* a yield. */
|
|
return false;
|
|
}
|
|
|
|
/* Dispatcher for operations */
|
|
static
|
|
bool eval_op(
|
|
ecs_iter_t *it,
|
|
ecs_rule_op_t *op,
|
|
int32_t op_index,
|
|
bool redo)
|
|
{
|
|
switch(op->kind) {
|
|
case EcsRuleInput:
|
|
return eval_input(it, op, op_index, redo);
|
|
case EcsRuleSelect:
|
|
return eval_select(it, op, op_index, redo);
|
|
case EcsRuleWith:
|
|
return eval_with(it, op, op_index, redo);
|
|
case EcsRuleSubSet:
|
|
return eval_subset(it, op, op_index, redo);
|
|
case EcsRuleSuperSet:
|
|
return eval_superset(it, op, op_index, redo);
|
|
case EcsRuleEach:
|
|
return eval_each(it, op, op_index, redo);
|
|
case EcsRuleStore:
|
|
return eval_store(it, op, op_index, redo);
|
|
case EcsRuleSetJmp:
|
|
return eval_setjmp(it, op, op_index, redo);
|
|
case EcsRuleJump:
|
|
return eval_jump(it, op, op_index, redo);
|
|
case EcsRuleNot:
|
|
return eval_not(it, op, op_index, redo);
|
|
case EcsRuleInTable:
|
|
return eval_intable(it, op, op_index, redo);
|
|
case EcsRuleYield:
|
|
return eval_yield(it, op, op_index, redo);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Utility to copy all registers to the next frame. Keeping track of register
|
|
* values for each operation is necessary, because if an operation is asked to
|
|
* redo matching, it must to be able to pick up from where it left of */
|
|
static
|
|
void push_registers(
|
|
ecs_rule_iter_t *it,
|
|
int32_t cur,
|
|
int32_t next)
|
|
{
|
|
if (!it->rule->var_count) {
|
|
return;
|
|
}
|
|
|
|
ecs_var_t *src_regs = get_register_frame(it, cur);
|
|
ecs_var_t *dst_regs = get_register_frame(it, next);
|
|
|
|
ecs_os_memcpy_n(dst_regs, src_regs,
|
|
ecs_var_t, it->rule->var_count);
|
|
}
|
|
|
|
/* Utility to copy all columns to the next frame. Columns keep track of which
|
|
* columns are currently being evaluated for a table, and are populated by the
|
|
* Select and With operations. The columns array is important, as it is used
|
|
* to tell the application where to find component data. */
|
|
static
|
|
void push_columns(
|
|
ecs_rule_iter_t *it,
|
|
int32_t cur,
|
|
int32_t next)
|
|
{
|
|
if (!it->rule->filter.term_count) {
|
|
return;
|
|
}
|
|
|
|
int32_t *src_cols = rule_get_columns_frame(it, cur);
|
|
int32_t *dst_cols = rule_get_columns_frame(it, next);
|
|
|
|
ecs_os_memcpy_n(dst_cols, src_cols, int32_t, it->rule->filter.term_count);
|
|
}
|
|
|
|
/* Populate iterator with data before yielding to application */
|
|
static
|
|
void populate_iterator(
|
|
const ecs_rule_t *rule,
|
|
ecs_iter_t *iter,
|
|
ecs_rule_iter_t *it,
|
|
ecs_rule_op_t *op)
|
|
{
|
|
ecs_world_t *world = rule->filter.world;
|
|
int32_t r = op->r_in;
|
|
ecs_var_t *regs = get_register_frame(it, op->frame);
|
|
ecs_table_t *table = NULL;
|
|
int32_t count = 0;
|
|
int32_t offset = 0;
|
|
|
|
/* If the input register for the yield does not point to a variable,
|
|
* the rule doesn't contain a this (.) variable. In that case, the
|
|
* iterator doesn't contain any data, and this function will simply
|
|
* return true or false. An application will still be able to obtain
|
|
* the variables that were resolved. */
|
|
if (r != UINT8_MAX) {
|
|
const ecs_rule_var_t *var = &rule->vars[r];
|
|
ecs_var_t *reg = ®s[r];
|
|
|
|
if (var->kind == EcsRuleVarKindTable) {
|
|
ecs_table_range_t slice = table_reg_get(rule, regs, r);
|
|
table = slice.table;
|
|
count = slice.count;
|
|
offset = slice.offset;
|
|
if (!count && table) {
|
|
count = ecs_table_count(table);
|
|
}
|
|
} else {
|
|
/* If a single entity is returned, simply return the
|
|
* iterator with count 1 and a pointer to the entity id */
|
|
ecs_assert(var->kind == EcsRuleVarKindEntity,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_entity_t e = reg->entity;
|
|
ecs_assert(ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL);
|
|
ecs_record_t *record = flecs_entities_get(world, e);
|
|
offset = ECS_RECORD_TO_ROW(record->row);
|
|
|
|
/* If an entity is not stored in a table, it could not have
|
|
* been matched by anything */
|
|
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
table = record->table;
|
|
count = 1;
|
|
}
|
|
}
|
|
|
|
int32_t i, var_count = rule->var_count;
|
|
int32_t term_count = rule->filter.term_count;
|
|
|
|
for (i = 0; i < var_count; i ++) {
|
|
iter->variables[i] = regs[i];
|
|
}
|
|
|
|
for (i = 0; i < term_count; i ++) {
|
|
int32_t v = rule->term_vars[i].src;
|
|
if (v != -1) {
|
|
const ecs_rule_var_t *var = &rule->vars[v];
|
|
if (var->name[0] != '.') {
|
|
if (var->kind == EcsRuleVarKindEntity) {
|
|
iter->sources[i] = regs[var->id].entity;
|
|
} else {
|
|
/* This can happen for Any variables, where the actual
|
|
* content of the variable is not of interest to the query.
|
|
* Just pick the first entity from the table, so that the
|
|
* column can be correctly resolved */
|
|
ecs_table_t *t = regs[var->id].range.table;
|
|
if (t) {
|
|
iter->sources[i] = ecs_vec_first_t(
|
|
&t->data.entities, ecs_entity_t)[0];
|
|
} else {
|
|
/* Can happen if term is optional */
|
|
iter->sources[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Iterator expects column indices to start at 1 */
|
|
iter->columns = rule_get_columns_frame(it, op->frame);
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_assert(iter->sources != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_entity_t src = iter->sources[i];
|
|
int32_t c = ++ iter->columns[i];
|
|
if (!src) {
|
|
src = iter->terms[i].src.id;
|
|
if (src != EcsThis && src != EcsAny) {
|
|
iter->columns[i] = 0;
|
|
}
|
|
} else if (c) {
|
|
iter->columns[i] = -1;
|
|
}
|
|
}
|
|
|
|
/* Set iterator ids */
|
|
for (i = 0; i < term_count; i ++) {
|
|
const ecs_rule_term_vars_t *vars = &rule->term_vars[i];
|
|
ecs_term_t *term = &rule->filter.terms[i];
|
|
if (term->oper == EcsOptional || term->oper == EcsNot) {
|
|
if (iter->columns[i] == 0) {
|
|
iter->ids[i] = term->id;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ecs_id_t id = term->id;
|
|
ecs_entity_t first = 0;
|
|
ecs_entity_t second = 0;
|
|
bool is_pair = ECS_HAS_ID_FLAG(id, PAIR);
|
|
|
|
if (!is_pair) {
|
|
first = id;
|
|
} else {
|
|
first = ECS_PAIR_FIRST(id);
|
|
second = ECS_PAIR_SECOND(id);
|
|
}
|
|
|
|
if (vars->first != -1) {
|
|
first = regs[vars->first].entity;
|
|
}
|
|
if (vars->second != -1) {
|
|
ecs_assert(is_pair, ECS_INTERNAL_ERROR, NULL);
|
|
second = regs[vars->second].entity;
|
|
}
|
|
|
|
if (!is_pair) {
|
|
id = first;
|
|
} else {
|
|
id = ecs_pair(first, second);
|
|
}
|
|
|
|
iter->ids[i] = id;
|
|
}
|
|
|
|
flecs_iter_populate_data(world, iter, table, offset, count,
|
|
iter->ptrs, iter->sizes);
|
|
}
|
|
|
|
static
|
|
bool is_control_flow(
|
|
ecs_rule_op_t *op)
|
|
{
|
|
switch(op->kind) {
|
|
case EcsRuleSetJmp:
|
|
case EcsRuleJump:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static
|
|
void rule_iter_set_initial_state(
|
|
ecs_iter_t *it,
|
|
ecs_rule_iter_t *iter,
|
|
const ecs_rule_t *rule)
|
|
{
|
|
int32_t i;
|
|
|
|
/* Make sure that if there are any terms with literal sources, they're
|
|
* initialized in the sources array */
|
|
const ecs_filter_t *filter = &rule->filter;
|
|
int32_t term_count = filter->term_count;
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_term_t *t = &filter->terms[i];
|
|
ecs_term_id_t *src = &t->src;
|
|
ecs_assert(src->flags & EcsIsVariable || src->id != EcsThis,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (!(src->flags & EcsIsVariable)) {
|
|
it->sources[i] = src->id;
|
|
}
|
|
}
|
|
|
|
/* Initialize registers of constrained variables */
|
|
if (it->constrained_vars) {
|
|
ecs_var_t *regs = get_register_frame(iter, 0);
|
|
|
|
for (i = 0; i < it->variable_count; i ++) {
|
|
if (ecs_iter_var_is_constrained(it, i)) {
|
|
const ecs_rule_var_t *var = &rule->vars[i];
|
|
ecs_assert(var->id == i, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t other_var = var->other;
|
|
ecs_var_t *it_var = &it->variables[i];
|
|
ecs_entity_t e = it_var->entity;
|
|
|
|
if (e) {
|
|
ecs_assert(ecs_is_valid(it->world, e),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
reg_set_entity(rule, regs, i, e);
|
|
if (other_var != -1) {
|
|
reg_set_entity(rule, regs, other_var, e);
|
|
}
|
|
} else {
|
|
ecs_assert(it_var->range.table != NULL,
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
reg_set_range(rule, regs, i, &it_var->range);
|
|
if (other_var != -1) {
|
|
reg_set_range(rule, regs, other_var, &it_var->range);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Iterator next function. This evaluates the program until it reaches a Yield
|
|
* operation, and returns the intermediate result(s) to the application. An
|
|
* iterator can, depending on the program, either return a table, entity, or
|
|
* just true/false, in case a rule doesn't contain the this variable. */
|
|
bool ecs_rule_next_instanced(
|
|
ecs_iter_t *it)
|
|
{
|
|
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_rule_iter_t *iter = &it->priv.iter.rule;
|
|
const ecs_rule_t *rule = iter->rule;
|
|
bool redo = iter->redo;
|
|
int32_t last_frame = -1;
|
|
bool first_time = !ECS_BIT_IS_SET(it->flags, EcsIterIsValid);
|
|
|
|
ecs_poly_assert(rule, ecs_rule_t);
|
|
|
|
/* Mark iterator as valid & ensure iterator resources are up to date */
|
|
flecs_iter_validate(it);
|
|
|
|
/* Can't iterate an iterator that's already depleted */
|
|
ecs_check(iter->op != -1, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
/* If this is the first time the iterator is iterated, set initial state */
|
|
if (first_time) {
|
|
ecs_assert(redo == false, ECS_INTERNAL_ERROR, NULL);
|
|
rule_iter_set_initial_state(it, iter, rule);
|
|
}
|
|
|
|
do {
|
|
/* Evaluate an operation. The result of an operation determines the
|
|
* flow of the program. If an operation returns true, the program
|
|
* continues to the operation pointed to by 'on_pass'. If the operation
|
|
* returns false, the program continues to the operation pointed to by
|
|
* 'on_fail'.
|
|
*
|
|
* In most scenarios, on_pass points to the next operation, and on_fail
|
|
* points to the previous operation.
|
|
*
|
|
* When an operation fails, the previous operation will be invoked with
|
|
* redo=true. This will cause the operation to continue its search from
|
|
* where it left off. When the operation succeeds, the next operation
|
|
* will be invoked with redo=false. This causes the operation to start
|
|
* from the beginning, which is necessary since it just received a new
|
|
* input. */
|
|
int32_t op_index = iter->op;
|
|
ecs_rule_op_t *op = &rule->operations[op_index];
|
|
int32_t cur = op->frame;
|
|
|
|
/* If this is not the first operation and is also not a control flow
|
|
* operation, push a new frame on the stack for the next operation */
|
|
if (!redo && !is_control_flow(op) && cur && cur != last_frame) {
|
|
int32_t prev = cur - 1;
|
|
push_registers(iter, prev, cur);
|
|
push_columns(iter, prev, cur);
|
|
}
|
|
|
|
/* Dispatch the operation */
|
|
bool result = eval_op(it, op, op_index, redo);
|
|
iter->op = result ? op->on_pass : op->on_fail;
|
|
|
|
/* If the current operation is yield, return results */
|
|
if (op->kind == EcsRuleYield) {
|
|
populate_iterator(rule, it, iter, op);
|
|
iter->redo = true;
|
|
return true;
|
|
}
|
|
|
|
/* If the current operation is a jump, goto stored label */
|
|
if (op->kind == EcsRuleJump) {
|
|
/* Label is stored in setjmp context */
|
|
iter->op = iter->op_ctx[op->on_pass].is.setjmp.label;
|
|
}
|
|
|
|
/* If jumping backwards, it's a redo */
|
|
redo = iter->op <= op_index;
|
|
|
|
if (!is_control_flow(op)) {
|
|
last_frame = op->frame;
|
|
}
|
|
} while (iter->op != -1);
|
|
|
|
ecs_iter_fini(it);
|
|
|
|
error:
|
|
return false;
|
|
}
|
|
|
|
#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 = (char*)module_name; /* safe */
|
|
char *module = (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);
|
|
}
|
|
|
|
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 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);
|
|
|
|
|
|
bool flecs_unit_validate(
|
|
ecs_world_t *world,
|
|
ecs_entity_t t,
|
|
EcsUnit *data);
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef FLECS_META
|
|
|
|
ecs_entity_t ecs_primitive_init(
|
|
ecs_world_t *world,
|
|
const ecs_primitive_desc_t *desc)
|
|
{
|
|
ecs_entity_t t = desc->entity;
|
|
if (!t) {
|
|
t = ecs_new_low_id(world);
|
|
}
|
|
|
|
ecs_set(world, t, EcsPrimitive, { desc->kind });
|
|
|
|
return t;
|
|
}
|
|
|
|
ecs_entity_t ecs_enum_init(
|
|
ecs_world_t *world,
|
|
const ecs_enum_desc_t *desc)
|
|
{
|
|
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_init(world, &(ecs_entity_desc_t){
|
|
.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);
|
|
|
|
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_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_init(world, &(ecs_entity_desc_t){
|
|
.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);
|
|
|
|
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_entity_t t = desc->entity;
|
|
if (!t) {
|
|
t = ecs_new_low_id(world);
|
|
}
|
|
|
|
ecs_set(world, t, EcsArray, {
|
|
.type = desc->type,
|
|
.count = desc->count
|
|
});
|
|
|
|
return t;
|
|
}
|
|
|
|
ecs_entity_t ecs_vector_init(
|
|
ecs_world_t *world,
|
|
const ecs_vector_desc_t *desc)
|
|
{
|
|
ecs_entity_t t = desc->entity;
|
|
if (!t) {
|
|
t = ecs_new_low_id(world);
|
|
}
|
|
|
|
ecs_set(world, t, EcsVector, {
|
|
.type = desc->type
|
|
});
|
|
|
|
return t;
|
|
}
|
|
|
|
ecs_entity_t ecs_struct_init(
|
|
ecs_world_t *world,
|
|
const ecs_struct_desc_t *desc)
|
|
{
|
|
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));
|
|
ecs_delete(world, t);
|
|
return 0;
|
|
}
|
|
|
|
ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t){
|
|
.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
|
|
});
|
|
}
|
|
|
|
ecs_set_scope(world, old_scope);
|
|
|
|
if (i == 0) {
|
|
ecs_err("struct '%s' has no members", ecs_get_name(world, t));
|
|
ecs_delete(world, t);
|
|
return 0;
|
|
}
|
|
|
|
if (!ecs_has(world, t, EcsStruct)) {
|
|
/* Invalid members */
|
|
ecs_delete(world, t);
|
|
return 0;
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
ecs_entity_t ecs_unit_init(
|
|
ecs_world_t *world,
|
|
const ecs_unit_desc_t *desc)
|
|
{
|
|
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);
|
|
|
|
return t;
|
|
error:
|
|
if (t) {
|
|
ecs_delete(world, t);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ecs_entity_t ecs_unit_prefix_init(
|
|
ecs_world_t *world,
|
|
const ecs_unit_prefix_desc_t *desc)
|
|
{
|
|
ecs_entity_t t = desc->entity;
|
|
if (!t) {
|
|
t = ecs_new_low_id(world);
|
|
}
|
|
|
|
ecs_set(world, t, EcsUnitPrefix, {
|
|
.symbol = (char*)desc->symbol,
|
|
.translation = desc->translation
|
|
});
|
|
|
|
return t;
|
|
}
|
|
|
|
ecs_entity_t ecs_quantity_init(
|
|
ecs_world_t *world,
|
|
const ecs_entity_desc_t *desc)
|
|
{
|
|
ecs_entity_t t = ecs_entity_init(world, desc);
|
|
if (!t) {
|
|
return 0;
|
|
}
|
|
|
|
ecs_add_id(world, t, EcsQuantity);
|
|
|
|
return t;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file meta/serialized.c
|
|
* @brief Serialize type into flat operations array to speed up deserialization.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_META
|
|
|
|
static
|
|
ecs_vector_t* serialize_type(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_size_t offset,
|
|
ecs_vector_t *ops);
|
|
|
|
static
|
|
ecs_meta_type_op_kind_t primitive_to_op_kind(ecs_primitive_kind_t kind) {
|
|
return EcsOpPrimitive + kind;
|
|
}
|
|
|
|
static
|
|
ecs_size_t 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* ops_add(ecs_vector_t **ops, ecs_meta_type_op_kind_t kind) {
|
|
ecs_meta_type_op_t *op = ecs_vector_add(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->unit = 0;
|
|
return op;
|
|
}
|
|
|
|
static
|
|
ecs_meta_type_op_t* ops_get(ecs_vector_t *ops, int32_t index) {
|
|
ecs_meta_type_op_t* op = ecs_vector_get(ops, ecs_meta_type_op_t, index);
|
|
ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
return op;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_primitive(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_size_t offset,
|
|
ecs_vector_t *ops)
|
|
{
|
|
const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive);
|
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_meta_type_op_t *op = ops_add(&ops, primitive_to_op_kind(ptr->kind));
|
|
op->offset = offset,
|
|
op->type = type;
|
|
op->size = type_size(world, type);
|
|
|
|
return ops;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_enum(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_size_t offset,
|
|
ecs_vector_t *ops)
|
|
{
|
|
(void)world;
|
|
|
|
ecs_meta_type_op_t *op = ops_add(&ops, EcsOpEnum);
|
|
op->offset = offset,
|
|
op->type = type;
|
|
op->size = ECS_SIZEOF(ecs_i32_t);
|
|
|
|
return ops;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_bitmask(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_size_t offset,
|
|
ecs_vector_t *ops)
|
|
{
|
|
(void)world;
|
|
|
|
ecs_meta_type_op_t *op = ops_add(&ops, EcsOpBitmask);
|
|
op->offset = offset,
|
|
op->type = type;
|
|
op->size = ECS_SIZEOF(ecs_u32_t);
|
|
|
|
return ops;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_array(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_size_t offset,
|
|
ecs_vector_t *ops)
|
|
{
|
|
(void)world;
|
|
|
|
ecs_meta_type_op_t *op = ops_add(&ops, EcsOpArray);
|
|
op->offset = offset;
|
|
op->type = type;
|
|
op->size = type_size(world, type);
|
|
|
|
return ops;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_array_component(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type)
|
|
{
|
|
const EcsArray *ptr = ecs_get(world, type, EcsArray);
|
|
if (!ptr) {
|
|
return NULL; /* Should never happen, will trigger internal error */
|
|
}
|
|
|
|
ecs_vector_t *ops = serialize_type(world, ptr->type, 0, NULL);
|
|
ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_meta_type_op_t *first = ecs_vector_first(ops, ecs_meta_type_op_t);
|
|
first->count = ptr->count;
|
|
|
|
return ops;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_vector(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_size_t offset,
|
|
ecs_vector_t *ops)
|
|
{
|
|
(void)world;
|
|
|
|
ecs_meta_type_op_t *op = ops_add(&ops, EcsOpVector);
|
|
op->offset = offset;
|
|
op->type = type;
|
|
op->size = type_size(world, type);
|
|
|
|
return ops;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_struct(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_size_t offset,
|
|
ecs_vector_t *ops)
|
|
{
|
|
const EcsStruct *ptr = ecs_get(world, type, EcsStruct);
|
|
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t cur, first = ecs_vector_count(ops);
|
|
ecs_meta_type_op_t *op = ops_add(&ops, EcsOpPush);
|
|
op->offset = offset;
|
|
op->type = type;
|
|
op->size = type_size(world, type);
|
|
|
|
ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t);
|
|
int32_t i, count = ecs_vector_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_vector_count(ops);
|
|
ops = serialize_type(world, member->type, offset + member->offset, ops);
|
|
|
|
op = 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->unit = member->unit;
|
|
op->op_count = ecs_vector_count(ops) - cur;
|
|
|
|
flecs_name_index_ensure(
|
|
member_index, flecs_ito(uint64_t, cur - first - 1),
|
|
member_name, 0, 0);
|
|
}
|
|
|
|
ops_add(&ops, EcsOpPop);
|
|
ops_get(ops, first)->op_count = ecs_vector_count(ops) - first;
|
|
|
|
return ops;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_type(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_size_t offset,
|
|
ecs_vector_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 NULL;
|
|
}
|
|
|
|
switch(ptr->kind) {
|
|
case EcsPrimitiveType:
|
|
ops = serialize_primitive(world, type, offset, ops);
|
|
break;
|
|
|
|
case EcsEnumType:
|
|
ops = serialize_enum(world, type, offset, ops);
|
|
break;
|
|
|
|
case EcsBitmaskType:
|
|
ops = serialize_bitmask(world, type, offset, ops);
|
|
break;
|
|
|
|
case EcsStructType:
|
|
ops = serialize_struct(world, type, offset, ops);
|
|
break;
|
|
|
|
case EcsArrayType:
|
|
ops = serialize_array(world, type, offset, ops);
|
|
break;
|
|
|
|
case EcsVectorType:
|
|
ops = serialize_vector(world, type, offset, ops);
|
|
break;
|
|
}
|
|
|
|
return ops;
|
|
}
|
|
|
|
static
|
|
ecs_vector_t* serialize_component(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type)
|
|
{
|
|
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 NULL;
|
|
}
|
|
|
|
ecs_vector_t *ops = NULL;
|
|
|
|
switch(ptr->kind) {
|
|
case EcsArrayType:
|
|
ops = serialize_array_component(world, type);
|
|
break;
|
|
default:
|
|
ops = serialize_type(world, type, 0, NULL);
|
|
break;
|
|
}
|
|
|
|
return 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_vector_t *ops = serialize_component(world, e);
|
|
ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
EcsMetaTypeSerialized *ptr = ecs_get_mut(
|
|
world, e, EcsMetaTypeSerialized);
|
|
if (ptr->ops) {
|
|
ecs_meta_dtor_serialized(ptr);
|
|
}
|
|
|
|
ptr->ops = ops;
|
|
}
|
|
}
|
|
|
|
#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(*(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_vector_count(ptr->ops);
|
|
ecs_meta_type_op_t *ops = ecs_vector_first(ptr->ops, ecs_meta_type_op_t);
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_meta_type_op_t *op = &ops[i];
|
|
if (op->members) {
|
|
flecs_name_index_free(op->members);
|
|
}
|
|
}
|
|
|
|
ecs_vector_free(ptr->ops);
|
|
}
|
|
|
|
static ECS_COPY(EcsMetaTypeSerialized, dst, src, {
|
|
ecs_meta_dtor_serialized(dst);
|
|
|
|
dst->ops = ecs_vector_copy(src->ops, ecs_meta_type_op_t);
|
|
|
|
int32_t o, count = ecs_vector_count(src->ops);
|
|
ecs_meta_type_op_t *ops = ecs_vector_first(src->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 = NULL;
|
|
})
|
|
|
|
static ECS_DTOR(EcsMetaTypeSerialized, ptr, {
|
|
ecs_meta_dtor_serialized(ptr);
|
|
})
|
|
|
|
|
|
/* EcsStruct lifecycle */
|
|
|
|
static void dtor_struct(
|
|
EcsStruct *ptr)
|
|
{
|
|
ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t);
|
|
int32_t i, count = ecs_vector_count(ptr->members);
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_os_free((char*)members[i].name);
|
|
}
|
|
ecs_vector_free(ptr->members);
|
|
}
|
|
|
|
static ECS_COPY(EcsStruct, dst, src, {
|
|
dtor_struct(dst);
|
|
|
|
dst->members = ecs_vector_copy(src->members, ecs_member_t);
|
|
|
|
ecs_member_t *members = ecs_vector_first(dst->members, ecs_member_t);
|
|
int32_t m, count = ecs_vector_count(dst->members);
|
|
|
|
for (m = 0; m < count; m ++) {
|
|
members[m].name = ecs_os_strdup(members[m].name);
|
|
}
|
|
})
|
|
|
|
static ECS_MOVE(EcsStruct, dst, src, {
|
|
dtor_struct(dst);
|
|
dst->members = src->members;
|
|
src->members = NULL;
|
|
})
|
|
|
|
static ECS_DTOR(EcsStruct, ptr, { dtor_struct(ptr); })
|
|
|
|
|
|
/* EcsEnum lifecycle */
|
|
|
|
static void dtor_enum(
|
|
EcsEnum *ptr)
|
|
{
|
|
ecs_map_iter_t it = ecs_map_iter(ptr->constants);
|
|
ecs_enum_constant_t *c;
|
|
while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) {
|
|
ecs_os_free((char*)c->name);
|
|
}
|
|
ecs_map_free(ptr->constants);
|
|
}
|
|
|
|
static ECS_COPY(EcsEnum, dst, src, {
|
|
dtor_enum(dst);
|
|
|
|
dst->constants = ecs_map_copy(src->constants);
|
|
ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_map_iter_t it = ecs_map_iter(dst->constants);
|
|
ecs_enum_constant_t *c;
|
|
while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) {
|
|
c->name = ecs_os_strdup(c->name);
|
|
}
|
|
})
|
|
|
|
static ECS_MOVE(EcsEnum, dst, src, {
|
|
dtor_enum(dst);
|
|
dst->constants = src->constants;
|
|
src->constants = NULL;
|
|
})
|
|
|
|
static ECS_DTOR(EcsEnum, ptr, { dtor_enum(ptr); })
|
|
|
|
|
|
/* EcsBitmask lifecycle */
|
|
|
|
static void dtor_bitmask(
|
|
EcsBitmask *ptr)
|
|
{
|
|
ecs_map_iter_t it = ecs_map_iter(ptr->constants);
|
|
ecs_bitmask_constant_t *c;
|
|
while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, NULL))) {
|
|
ecs_os_free((char*)c->name);
|
|
}
|
|
ecs_map_free(ptr->constants);
|
|
}
|
|
|
|
static ECS_COPY(EcsBitmask, dst, src, {
|
|
dtor_bitmask(dst);
|
|
|
|
dst->constants = ecs_map_copy(src->constants);
|
|
ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_map_iter_t it = ecs_map_iter(dst->constants);
|
|
ecs_bitmask_constant_t *c;
|
|
while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, NULL))) {
|
|
c->name = ecs_os_strdup(c->name);
|
|
}
|
|
})
|
|
|
|
static ECS_MOVE(EcsBitmask, dst, src, {
|
|
dtor_bitmask(dst);
|
|
dst->constants = src->constants;
|
|
src->constants = NULL;
|
|
})
|
|
|
|
static ECS_DTOR(EcsBitmask, ptr, { dtor_bitmask(ptr); })
|
|
|
|
|
|
/* 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
|
|
int 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 with different kind",
|
|
ecs_get_name(world, type));
|
|
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) {
|
|
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);
|
|
return -1;
|
|
}
|
|
|
|
meta_type->partial = comp->size != size;
|
|
}
|
|
|
|
meta_type->kind = kind;
|
|
meta_type->size = size;
|
|
meta_type->alignment = alignment;
|
|
ecs_modified(world, type, EcsMetaType);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define init_type_t(world, type, kind, T) \
|
|
init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T))
|
|
|
|
static
|
|
void 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)
|
|
{
|
|
member->member = entity;
|
|
member->type = type;
|
|
member->count = count;
|
|
member->unit = unit;
|
|
member->offset = offset;
|
|
|
|
if (!count) {
|
|
member->count = 1;
|
|
}
|
|
|
|
ecs_os_strset((char**)&member->name, name);
|
|
}
|
|
|
|
static
|
|
int add_member_to_struct(
|
|
ecs_world_t *world,
|
|
ecs_entity_t type,
|
|
ecs_entity_t member,
|
|
EcsMember *m)
|
|
{
|
|
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_vector_first(s->members, ecs_member_t);
|
|
int32_t i, count = ecs_vector_count(s->members);
|
|
for (i = 0; i < count; i ++) {
|
|
if (members[i].member == member) {
|
|
set_struct_member(
|
|
&members[i], member, name, m->type, m->count, m->offset, unit);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If member wasn't added yet, add a new element to vector */
|
|
if (i == count) {
|
|
ecs_member_t *elem = ecs_vector_add(&s->members, ecs_member_t);
|
|
elem->name = NULL;
|
|
set_struct_member(elem, member, name, m->type,
|
|
m->count, m->offset, unit);
|
|
|
|
/* Reobtain members array in case it was reallocated */
|
|
members = ecs_vector_first(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, member);
|
|
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, member);
|
|
ecs_err("member '%s' has 0 size/alignment");
|
|
ecs_os_free(path);
|
|
return -1;
|
|
}
|
|
|
|
member_size *= elem->count;
|
|
size = ECS_ALIGN(size, member_alignment);
|
|
elem->size = member_size;
|
|
elem->offset = size;
|
|
|
|
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. Grab size of just added member instead. It
|
|
* doesn't matter if the size doesn't correspond to the actual struct
|
|
* size. The init_type function compares computed size with actual
|
|
* (component) size to determine if the type is partial. */
|
|
const EcsComponent *cptr = ecs_get(world, m->type, EcsComponent);
|
|
ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
size = cptr->size;
|
|
alignment = cptr->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 (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 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);
|
|
ecs_enum_constant_t *c;
|
|
ecs_map_key_t key;
|
|
|
|
while ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) {
|
|
if (c->constant == e) {
|
|
ecs_os_free((char*)c->name);
|
|
ecs_map_remove(ptr->constants, key);
|
|
}
|
|
}
|
|
|
|
/* 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 ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) {
|
|
if (value_set) {
|
|
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;
|
|
}
|
|
} else {
|
|
if (c->value >= value) {
|
|
value = c->value + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ptr->constants) {
|
|
ptr->constants = ecs_map_new(ecs_enum_constant_t,
|
|
&world->allocator, 1);
|
|
}
|
|
|
|
c = ecs_map_ensure(ptr->constants, ecs_enum_constant_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;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
int 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);
|
|
ecs_bitmask_constant_t *c;
|
|
ecs_map_key_t key;
|
|
while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) {
|
|
if (c->constant == e) {
|
|
ecs_os_free((char*)c->name);
|
|
ecs_map_remove(ptr->constants, key);
|
|
}
|
|
}
|
|
|
|
/* 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 ((c = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!ptr->constants) {
|
|
ptr->constants = ecs_map_new(ecs_bitmask_constant_t,
|
|
&world->allocator, 1);
|
|
}
|
|
|
|
c = ecs_map_ensure(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;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
void 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void set_member(ecs_iter_t *it) {
|
|
ecs_world_t *world = it->world;
|
|
EcsMember *member = ecs_field(it, EcsMember, 1);
|
|
|
|
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;
|
|
}
|
|
|
|
add_member_to_struct(world, parent, e, &member[i]);
|
|
}
|
|
}
|
|
|
|
static
|
|
void 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 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 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)) {
|
|
add_constant_to_enum(world, parent, e, it->event_id);
|
|
} else if (ecs_has(world, parent, EcsBitmask)) {
|
|
add_constant_to_bitmask(world, parent, e, it->event_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void 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 (init_type(world, e, EcsArrayType,
|
|
elem_ptr->size * elem_count, elem_ptr->alignment))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void 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_vector_t*)) {
|
|
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 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 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_set_hooks_id(world, it->entities[i],
|
|
&(ecs_type_hooks_t){
|
|
.ctor = ecs_default_ctor
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void 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, EcsStruct);
|
|
flecs_bootstrap_component(world, EcsArray);
|
|
flecs_bootstrap_component(world, EcsVector);
|
|
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 = member_on_set
|
|
});
|
|
|
|
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_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf },
|
|
.events = {EcsOnSet},
|
|
.callback = set_primitive
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf },
|
|
.events = {EcsOnSet},
|
|
.callback = set_member
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf },
|
|
.events = {EcsOnAdd},
|
|
.callback = add_enum
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf },
|
|
.events = {EcsOnAdd},
|
|
.callback = add_bitmask
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf },
|
|
.events = {EcsOnAdd},
|
|
.callback = add_constant
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf },
|
|
.events = {EcsOnSet},
|
|
.callback = add_constant
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf },
|
|
.events = {EcsOnSet},
|
|
.callback = set_array
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf },
|
|
.events = {EcsOnSet},
|
|
.callback = set_vector
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf },
|
|
.events = {EcsOnSet},
|
|
.callback = set_unit
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf },
|
|
.events = {EcsOnSet},
|
|
.callback = ecs_meta_type_serialized_init
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf },
|
|
.events = {EcsOnSet},
|
|
.callback = ecs_meta_type_init_default_ctor
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {
|
|
{ .id = ecs_id(EcsUnit) },
|
|
{ .id = EcsQuantity }
|
|
},
|
|
.events = { EcsMonitor },
|
|
.callback = 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);
|
|
|
|
#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"}
|
|
}
|
|
});
|
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){
|
|
.entity = ecs_id(EcsMetaType),
|
|
.members = {
|
|
{.name = (char*)"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"}
|
|
}
|
|
});
|
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){
|
|
.entity = ecs_id(EcsPrimitive),
|
|
.members = {
|
|
{.name = (char*)"kind", .type = primitive_kind}
|
|
}
|
|
});
|
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){
|
|
.entity = ecs_id(EcsMember),
|
|
.members = {
|
|
{.name = (char*)"type", .type = ecs_id(ecs_entity_t)},
|
|
{.name = (char*)"count", .type = ecs_id(ecs_i32_t)},
|
|
{.name = (char*)"unit", .type = ecs_id(ecs_entity_t)},
|
|
{.name = (char*)"offset", .type = ecs_id(ecs_i32_t)}
|
|
}
|
|
});
|
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){
|
|
.entity = ecs_id(EcsArray),
|
|
.members = {
|
|
{.name = (char*)"type", .type = ecs_id(ecs_entity_t)},
|
|
{.name = (char*)"count", .type = ecs_id(ecs_i32_t)},
|
|
}
|
|
});
|
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){
|
|
.entity = ecs_id(EcsVector),
|
|
.members = {
|
|
{.name = (char*)"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 = (char*)"factor", .type = ecs_id(ecs_i32_t)},
|
|
{.name = (char*)"power", .type = ecs_id(ecs_i32_t)}
|
|
}
|
|
});
|
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){
|
|
.entity = ecs_id(EcsUnit),
|
|
.members = {
|
|
{.name = (char*)"symbol", .type = ecs_id(ecs_string_t)},
|
|
{.name = (char*)"prefix", .type = ecs_id(ecs_entity_t)},
|
|
{.name = (char*)"base", .type = ecs_id(ecs_entity_t)},
|
|
{.name = (char*)"over", .type = ecs_id(ecs_entity_t)},
|
|
{.name = (char*)"translation", .type = ut}
|
|
}
|
|
});
|
|
|
|
ecs_struct_init(world, &(ecs_struct_desc_t){
|
|
.entity = ecs_id(EcsUnitPrefix),
|
|
.members = {
|
|
{.name = (char*)"symbol", .type = ecs_id(ecs_string_t)},
|
|
{.name = (char*)"translation", .type = ut}
|
|
}
|
|
});
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file meta/api.c
|
|
* @brief API for assigning values of runtime types with reflection.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
|
|
#ifdef FLECS_META
|
|
|
|
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 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";
|
|
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_meta_scope_t*)&cursor->scope[cursor->depth];
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
/* Restore scope, if dotmember was used */
|
|
static
|
|
void 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;
|
|
}
|
|
|
|
/* Get previous scope */
|
|
static
|
|
ecs_meta_scope_t* get_prev_scope(
|
|
ecs_meta_cursor_t *cursor)
|
|
{
|
|
ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(cursor->depth > 0, ECS_INVALID_PARAMETER, NULL);
|
|
return &cursor->scope[cursor->depth - 1];
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Get alignment for type in current scope */
|
|
static
|
|
ecs_size_t get_alignment(
|
|
const ecs_world_t *world,
|
|
ecs_meta_scope_t *scope)
|
|
{
|
|
return get_ecs_component(world, scope)->alignment;
|
|
}
|
|
|
|
static
|
|
int32_t get_elem_count(
|
|
ecs_meta_scope_t *scope)
|
|
{
|
|
if (scope->vector) {
|
|
return ecs_vector_count(*(scope->vector));
|
|
}
|
|
|
|
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);
|
|
|
|
if (scope->vector) {
|
|
ecs_size_t align = get_alignment(world, scope);
|
|
ecs_vector_set_min_count_t(
|
|
scope->vector, size, align, scope->elem_cur + 1);
|
|
scope->ptr = ecs_vector_first_t(*(scope->vector), size, align);
|
|
}
|
|
|
|
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_vector_first(ser->ops, ecs_meta_type_op_t),
|
|
.op_count = ecs_vector_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_check(ptr != NULL, 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);
|
|
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->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 *prev_scope = get_prev_scope(cursor);
|
|
ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor);
|
|
ecs_meta_type_op_t *push_op = flecs_meta_cursor_get_op(prev_scope);
|
|
const ecs_world_t *world = cursor->world;
|
|
|
|
ecs_assert(push_op->kind == EcsOpPush, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (!push_op->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(push_op->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]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ecs_meta_dotmember(
|
|
ecs_meta_cursor_t *cursor,
|
|
const char *name)
|
|
{
|
|
#ifdef FLECS_PARSER
|
|
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 ++;
|
|
}
|
|
|
|
ecs_meta_scope_t *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) {
|
|
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;
|
|
}
|
|
|
|
switch(op->kind) {
|
|
case EcsOpPush:
|
|
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
|
|
};
|
|
break;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
default: {
|
|
char *path = ecs_get_fullpath(world, scope->type);
|
|
ecs_err("invalid push for type '%s'", path);
|
|
ecs_os_free(path);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (scope->is_collection) {
|
|
next_scope[0].ptr = ECS_OFFSET(next_scope[0].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);
|
|
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 {
|
|
/* 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);
|
|
}
|
|
} else {
|
|
/* Make sure that this was an inline array */
|
|
ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, next_scope);
|
|
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_meta_type_op_t *op = flecs_meta_cursor_get_op(scope);
|
|
return op->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;
|
|
}
|
|
|
|
/* Utilities for type conversions and bounds checking */
|
|
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},
|
|
[EcsOpEnum] = {INT32_MIN, INT32_MAX},
|
|
[EcsOpBitmask] = {0, INT32_MAX}
|
|
};
|
|
|
|
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},
|
|
[EcsOpEnum] = {0, INT32_MAX},
|
|
[EcsOpBitmask] = {0, UINT32_MAX}
|
|
};
|
|
|
|
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},
|
|
[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(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 conversion_error(
|
|
ecs_meta_cursor_t *cursor,
|
|
ecs_meta_type_op_t *op,
|
|
const char *from)
|
|
{
|
|
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_unsigned(ptr, value, ecs_meta_bounds_unsigned);
|
|
default:
|
|
conversion_error(cursor, op, "bool");
|
|
return -1;
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
default:
|
|
conversion_error(cursor, op, "char");
|
|
return -1;
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
default: {
|
|
if(!value) return ecs_meta_set_null(cursor);
|
|
conversion_error(cursor, op, "int");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
default:
|
|
if(!value) return ecs_meta_set_null(cursor);
|
|
conversion_error(cursor, op, "uint");
|
|
return -1;
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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) {
|
|
cases_T_bool(ptr, value);
|
|
cases_T_signed(ptr, value, ecs_meta_bounds_float);
|
|
cases_T_unsigned(ptr, value, ecs_meta_bounds_float);
|
|
cases_T_float(ptr, value);
|
|
default:
|
|
conversion_error(cursor, op, "float");
|
|
return -1;
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
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);
|
|
conversion_error(cursor, op, type_str);
|
|
ecs_os_free(type_str);
|
|
goto error;
|
|
}
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return ecs_value_copy(cursor->world, value->type, ptr, value->ptr);
|
|
}
|
|
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
static
|
|
int 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 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 (add_bitmask_constant(cursor, op, out, token) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
ptr ++;
|
|
prev = ptr;
|
|
}
|
|
|
|
if (add_bitmask_constant(cursor, op, out, prev) != 0) {
|
|
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);
|
|
return -1;
|
|
}
|
|
break;
|
|
case EcsOpI8:
|
|
case EcsOpU8:
|
|
case EcsOpChar:
|
|
case EcsOpByte:
|
|
set_T(ecs_i8_t, ptr, atol(value));
|
|
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, atol(value));
|
|
break;
|
|
case EcsOpIPtr:
|
|
case EcsOpUPtr:
|
|
set_T(ecs_iptr_t, ptr, atol(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);
|
|
return -1;
|
|
}
|
|
|
|
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);
|
|
return -1;
|
|
}
|
|
|
|
set_T(ecs_i32_t, ptr, v[0]);
|
|
break;
|
|
}
|
|
case EcsOpBitmask:
|
|
if (parse_bitmask(cursor, op, ptr, value) != 0) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case EcsOpEntity: {
|
|
ecs_entity_t e = 0;
|
|
|
|
if (ecs_os_strcmp(value, "0")) {
|
|
if (cursor->lookup_action) {
|
|
e = cursor->lookup_action(
|
|
cursor->world, value,
|
|
cursor->lookup_ctx);
|
|
} else {
|
|
e = ecs_lookup_path(cursor->world, 0, value);
|
|
}
|
|
|
|
if (!e) {
|
|
ecs_err("unresolved entity identifier '%s'", value);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
set_T(ecs_entity_t, ptr, e);
|
|
break;
|
|
}
|
|
case EcsOpPop:
|
|
ecs_err("excess element '%s' in scope", value);
|
|
return -1;
|
|
default:
|
|
ecs_err("unsupported conversion from string '%s' to '%s'",
|
|
value, flecs_meta_op_kind_str(op->kind));
|
|
return -1;
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
return -1;
|
|
}
|
|
|
|
switch(op->kind) {
|
|
case EcsOpChar:
|
|
set_T(ecs_char_t, ptr, value[1]);
|
|
break;
|
|
|
|
default:
|
|
case EcsOpEntity:
|
|
case EcsOpString:
|
|
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;
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
default:
|
|
conversion_error(cursor, op, "entity");
|
|
return -1;
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
default:
|
|
conversion_error(cursor, op, "null");
|
|
return -1;
|
|
}
|
|
|
|
flecs_meta_cursor_restore_scope(cursor, scope);
|
|
return 0;
|
|
}
|
|
|
|
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_f32_t*)ptr != 0;
|
|
case EcsOpF64: return *(ecs_f64_t*)ptr != 0;
|
|
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;
|
|
default: ecs_throw(ECS_INVALID_PARAMETER,
|
|
"invalid element for bool");
|
|
}
|
|
|
|
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;
|
|
default: ecs_throw(ECS_INVALID_PARAMETER,
|
|
"invalid element for char");
|
|
}
|
|
|
|
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 *(ecs_bool_t*)ptr;
|
|
case EcsOpI8: return *(ecs_i8_t*)ptr;
|
|
case EcsOpU8: return *(ecs_u8_t*)ptr;
|
|
case EcsOpChar: return *(ecs_char_t*)ptr;
|
|
case EcsOpByte: return *(ecs_u8_t*)ptr;
|
|
case EcsOpI16: return *(ecs_i16_t*)ptr;
|
|
case EcsOpU16: return *(ecs_u16_t*)ptr;
|
|
case EcsOpI32: return *(ecs_i32_t*)ptr;
|
|
case EcsOpU32: return *(ecs_u32_t*)ptr;
|
|
case EcsOpI64: return *(ecs_i64_t*)ptr;
|
|
case EcsOpU64: return flecs_uto(int64_t, *(ecs_u64_t*)ptr);
|
|
case EcsOpIPtr: return *(ecs_iptr_t*)ptr;
|
|
case EcsOpUPtr: return flecs_uto(int64_t, *(ecs_uptr_t*)ptr);
|
|
case EcsOpF32: return (int64_t)*(ecs_f32_t*)ptr;
|
|
case EcsOpF64: return (int64_t)*(ecs_f64_t*)ptr;
|
|
case EcsOpString: return atoi(*(const char**)ptr);
|
|
case EcsOpEnum: return *(ecs_i32_t*)ptr;
|
|
case EcsOpBitmask: return *(ecs_u32_t*)ptr;
|
|
case EcsOpEntity:
|
|
ecs_throw(ECS_INVALID_PARAMETER,
|
|
"invalid conversion from entity to int");
|
|
break;
|
|
default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int");
|
|
}
|
|
|
|
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, *(ecs_i8_t*)ptr);
|
|
case EcsOpU8: return *(ecs_u8_t*)ptr;
|
|
case EcsOpChar: return flecs_ito(uint64_t, *(ecs_char_t*)ptr);
|
|
case EcsOpByte: return flecs_ito(uint64_t, *(ecs_u8_t*)ptr);
|
|
case EcsOpI16: return flecs_ito(uint64_t, *(ecs_i16_t*)ptr);
|
|
case EcsOpU16: return *(ecs_u16_t*)ptr;
|
|
case EcsOpI32: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr);
|
|
case EcsOpU32: return *(ecs_u32_t*)ptr;
|
|
case EcsOpI64: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr);
|
|
case EcsOpU64: return *(ecs_u64_t*)ptr;
|
|
case EcsOpIPtr: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr);
|
|
case EcsOpUPtr: return *(ecs_uptr_t*)ptr;
|
|
case EcsOpF32: return flecs_ito(uint64_t, *(ecs_f32_t*)ptr);
|
|
case EcsOpF64: return flecs_ito(uint64_t, *(ecs_f64_t*)ptr);
|
|
case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr));
|
|
case EcsOpEnum: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr);
|
|
case EcsOpBitmask: return *(ecs_u32_t*)ptr;
|
|
case EcsOpEntity: return *(ecs_entity_t*)ptr;
|
|
default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint");
|
|
}
|
|
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);
|
|
switch(op->kind) {
|
|
case EcsOpBool: return *(ecs_bool_t*)ptr;
|
|
case EcsOpI8: return *(ecs_i8_t*)ptr;
|
|
case EcsOpU8: return *(ecs_u8_t*)ptr;
|
|
case EcsOpChar: return *(ecs_char_t*)ptr;
|
|
case EcsOpByte: return *(ecs_u8_t*)ptr;
|
|
case EcsOpI16: return *(ecs_i16_t*)ptr;
|
|
case EcsOpU16: return *(ecs_u16_t*)ptr;
|
|
case EcsOpI32: return *(ecs_i32_t*)ptr;
|
|
case EcsOpU32: return *(ecs_u32_t*)ptr;
|
|
case EcsOpI64: return (double)*(ecs_i64_t*)ptr;
|
|
case EcsOpU64: return (double)*(ecs_u64_t*)ptr;
|
|
case EcsOpIPtr: return (double)*(ecs_iptr_t*)ptr;
|
|
case EcsOpUPtr: return (double)*(ecs_uptr_t*)ptr;
|
|
case EcsOpF32: return (double)*(ecs_f32_t*)ptr;
|
|
case EcsOpF64: return *(ecs_f64_t*)ptr;
|
|
case EcsOpString: return atof(*(const char**)ptr);
|
|
case EcsOpEnum: return *(ecs_i32_t*)ptr;
|
|
case EcsOpBitmask: return *(ecs_u32_t*)ptr;
|
|
case EcsOpEntity:
|
|
ecs_throw(ECS_INVALID_PARAMETER,
|
|
"invalid conversion from entity to float");
|
|
break;
|
|
default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float");
|
|
}
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string");
|
|
}
|
|
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;
|
|
default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity");
|
|
}
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file expr/serialize.c
|
|
* @brief Serialize (component) values to flecs string format.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_EXPR
|
|
|
|
static
|
|
int expr_ser_type(
|
|
const ecs_world_t *world,
|
|
ecs_vector_t *ser,
|
|
const void *base,
|
|
ecs_strbuf_t *str);
|
|
|
|
static
|
|
int 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);
|
|
|
|
static
|
|
int expr_ser_type_op(
|
|
const ecs_world_t *world,
|
|
ecs_meta_type_op_t *op,
|
|
const void *base,
|
|
ecs_strbuf_t *str);
|
|
|
|
static
|
|
ecs_primitive_kind_t expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) {
|
|
return kind - EcsOpPrimitive;
|
|
}
|
|
|
|
/* Serialize a primitive value */
|
|
static
|
|
int expr_ser_primitive(
|
|
const ecs_world_t *world,
|
|
ecs_primitive_kind_t kind,
|
|
const void *base,
|
|
ecs_strbuf_t *str)
|
|
{
|
|
switch(kind) {
|
|
case EcsBool:
|
|
if (*(bool*)base) {
|
|
ecs_strbuf_appendlit(str, "true");
|
|
} else {
|
|
ecs_strbuf_appendlit(str, "false");
|
|
}
|
|
break;
|
|
case EcsChar: {
|
|
char chbuf[3];
|
|
char ch = *(char*)base;
|
|
if (ch) {
|
|
ecs_chresc(chbuf, *(char*)base, '"');
|
|
ecs_strbuf_appendch(str, '"');
|
|
ecs_strbuf_appendstr(str, chbuf);
|
|
ecs_strbuf_appendch(str, '"');
|
|
} else {
|
|
ecs_strbuf_appendch(str, '0');
|
|
}
|
|
break;
|
|
}
|
|
case EcsByte:
|
|
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint8_t*)base));
|
|
break;
|
|
case EcsU8:
|
|
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint8_t*)base));
|
|
break;
|
|
case EcsU16:
|
|
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint16_t*)base));
|
|
break;
|
|
case EcsU32:
|
|
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint32_t*)base));
|
|
break;
|
|
case EcsU64:
|
|
ecs_strbuf_append(str, "%llu", *(uint64_t*)base);
|
|
break;
|
|
case EcsI8:
|
|
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int8_t*)base));
|
|
break;
|
|
case EcsI16:
|
|
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int16_t*)base));
|
|
break;
|
|
case EcsI32:
|
|
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int32_t*)base));
|
|
break;
|
|
case EcsI64:
|
|
ecs_strbuf_appendint(str, *(int64_t*)base);
|
|
break;
|
|
case EcsF32:
|
|
ecs_strbuf_appendflt(str, (double)*(float*)base, 0);
|
|
break;
|
|
case EcsF64:
|
|
ecs_strbuf_appendflt(str, *(double*)base, 0);
|
|
break;
|
|
case EcsIPtr:
|
|
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(intptr_t*)base));
|
|
break;
|
|
case EcsUPtr:
|
|
ecs_strbuf_append(str, "%u", *(uintptr_t*)base);
|
|
break;
|
|
case EcsString: {
|
|
char *value = *(char**)base;
|
|
if (value) {
|
|
ecs_size_t length = ecs_stresc(NULL, 0, '"', value);
|
|
if (length == ecs_os_strlen(value)) {
|
|
ecs_strbuf_appendch(str, '"');
|
|
ecs_strbuf_appendstrn(str, value, length);
|
|
ecs_strbuf_appendch(str, '"');
|
|
} else {
|
|
char *out = ecs_os_malloc(length + 3);
|
|
ecs_stresc(out + 1, length, '"', value);
|
|
out[0] = '"';
|
|
out[length + 1] = '"';
|
|
out[length + 2] = '\0';
|
|
ecs_strbuf_appendstr_zerocpy(str, out);
|
|
}
|
|
} else {
|
|
ecs_strbuf_appendlit(str, "null");
|
|
}
|
|
break;
|
|
}
|
|
case EcsEntity: {
|
|
ecs_entity_t e = *(ecs_entity_t*)base;
|
|
if (!e) {
|
|
ecs_strbuf_appendch(str, '0');
|
|
} else {
|
|
ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
ecs_err("invalid primitive kind");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Serialize enumeration */
|
|
static
|
|
int 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 value = *(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(
|
|
enum_type->constants, ecs_enum_constant_t, value);
|
|
if (!constant) {
|
|
char *path = ecs_get_fullpath(world, op->type);
|
|
ecs_err("value %d is not valid for enum type '%s'", value, path);
|
|
ecs_os_free(path);
|
|
goto error;
|
|
}
|
|
|
|
ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant));
|
|
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
/* Serialize bitmask */
|
|
static
|
|
int 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 = *(uint32_t*)ptr;
|
|
ecs_map_key_t key;
|
|
ecs_bitmask_constant_t *constant;
|
|
int count = 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 ((constant = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) {
|
|
if ((value & key) == key) {
|
|
ecs_strbuf_list_appendstr(str,
|
|
ecs_get_name(world, constant->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)
|
|
{
|
|
ecs_strbuf_list_push(str, "[", ", ");
|
|
|
|
const void *ptr = base;
|
|
|
|
int i;
|
|
for (i = 0; i < elem_count; i ++) {
|
|
ecs_strbuf_list_next(str);
|
|
if (expr_ser_type_ops(world, ops, op_count, ptr, str, 1)) {
|
|
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)
|
|
{
|
|
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_vector_first(ser->ops, ecs_meta_type_op_t);
|
|
int32_t op_count = ecs_vector_count(ser->ops);
|
|
|
|
return expr_ser_elements(
|
|
world, ops, op_count, base, elem_count, comp->size, str);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
ecs_vector_t *value = *(ecs_vector_t**)base;
|
|
if (!value) {
|
|
ecs_strbuf_appendlit(str, "null");
|
|
return 0;
|
|
}
|
|
|
|
const EcsVector *v = ecs_get(world, op->type, EcsVector);
|
|
ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
const EcsComponent *comp = ecs_get(world, v->type, EcsComponent);
|
|
ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t count = ecs_vector_count(value);
|
|
void *array = ecs_vector_first_t(value, comp->size, comp->alignment);
|
|
|
|
/* Serialize contiguous buffer of vector */
|
|
return expr_ser_type_elements(world, v->type, array, count, str);
|
|
}
|
|
|
|
/* Forward serialization to the different type kinds */
|
|
static
|
|
int expr_ser_type_op(
|
|
const ecs_world_t *world,
|
|
ecs_meta_type_op_t *op,
|
|
const void *ptr,
|
|
ecs_strbuf_t *str)
|
|
{
|
|
switch(op->kind) {
|
|
case EcsOpPush:
|
|
case EcsOpPop:
|
|
/* Should not be parsed as single op */
|
|
ecs_throw(ECS_INVALID_PARAMETER, NULL);
|
|
break;
|
|
case EcsOpEnum:
|
|
if (expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case EcsOpBitmask:
|
|
if (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;
|
|
default:
|
|
if (expr_ser_primitive(world, expr_op_to_primitive_kind(op->kind),
|
|
ECS_OFFSET(ptr, op->offset), str))
|
|
{
|
|
/* Unknown operation */
|
|
ecs_err("unknown serializer operation kind (%d)", op->kind);
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
/* Iterate over a slice of the type ops array */
|
|
static
|
|
int 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)
|
|
{
|
|
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))
|
|
{
|
|
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;
|
|
default:
|
|
if (expr_ser_type_op(world, op, base, str)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
/* Iterate over the type ops of a type */
|
|
static
|
|
int expr_ser_type(
|
|
const ecs_world_t *world,
|
|
ecs_vector_t *v_ops,
|
|
const void *base,
|
|
ecs_strbuf_t *str)
|
|
{
|
|
ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t);
|
|
int32_t count = ecs_vector_count(v_ops);
|
|
return expr_ser_type_ops(world, ops, count, base, str, 0);
|
|
}
|
|
|
|
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 (expr_ser_type(world, ser->ops, ptr, buf_out)) {
|
|
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_primitive_to_expr_buf(
|
|
const ecs_world_t *world,
|
|
ecs_primitive_kind_t kind,
|
|
const void *base,
|
|
ecs_strbuf_t *str)
|
|
{
|
|
return expr_ser_primitive(world, kind, base, str);
|
|
}
|
|
|
|
#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);
|
|
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);
|
|
}
|
|
} 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);
|
|
|
|
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);
|
|
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(
|
|
ecs_vars_t *vars,
|
|
const char *name)
|
|
{
|
|
return flecs_vars_scope_lookup(vars->cur, name);
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file expr/strutil.c
|
|
* @brief String parsing utilities.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_EXPR
|
|
|
|
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;
|
|
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;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @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)
|
|
{
|
|
const char *start = ptr;
|
|
char *token_ptr = token;
|
|
|
|
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[ptr - start];
|
|
token_ptr[0] = '|';
|
|
token_ptr[1] = '\0';
|
|
token_ptr ++;
|
|
ptr ++;
|
|
start = 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;
|
|
}
|
|
|
|
/* 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(
|
|
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_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);
|
|
default: ecs_abort(ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
}
|
|
|
|
/** 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));
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** 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;
|
|
default:
|
|
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;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* 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) {
|
|
ecs_parser_error(name, expr, ptr - expr,
|
|
"invalid non-primitive type in binary expression");
|
|
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;
|
|
}
|
|
|
|
/* 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_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) {
|
|
return -1;
|
|
}
|
|
|
|
if (!flecs_oper_valid_for_type(type, op)) {
|
|
ecs_parser_error(name, expr, ptr - expr, "invalid operator for type");
|
|
return -1;
|
|
}
|
|
|
|
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_OP(lvalue, rvalue, storage, ==);
|
|
break;
|
|
case EcsCondNeq:
|
|
ECS_BINARY_COND_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;
|
|
default:
|
|
ecs_parser_error(name, expr, ptr - expr, "unsupported operator");
|
|
return -1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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_fluff(ptr, NULL);
|
|
|
|
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_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;
|
|
expr = expr ? expr : ptr;
|
|
|
|
ptr = ecs_parse_fluff(ptr, NULL);
|
|
|
|
/* Check for postfix operators */
|
|
ecs_expr_oper_t unary_op = EcsExprOperUnknown;
|
|
if (ptr[0] == '-' && !isdigit(ptr[1])) {
|
|
unary_op = EcsMin;
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
}
|
|
|
|
/* 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(
|
|
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 (!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_fluff(ptr + 1, NULL);
|
|
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, "expected '{'");
|
|
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;
|
|
}
|
|
|
|
const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, &token[1]);
|
|
if (!var) {
|
|
ecs_parser_error(name, expr, ptr - expr,
|
|
"unresolved variable '%s'", token);
|
|
return NULL;
|
|
}
|
|
|
|
ecs_meta_set_value(&cur, &var->value);
|
|
is_lvalue = true;
|
|
|
|
} else {
|
|
const char *tptr = ecs_parse_fluff(ptr, NULL);
|
|
for (; ptr != tptr; ptr ++) {
|
|
if (ptr[0] == '\n') {
|
|
newline = true;
|
|
}
|
|
}
|
|
|
|
if (ptr[0] == ':') {
|
|
/* Member assignment */
|
|
ptr ++;
|
|
if (ecs_meta_member(&cur, token) != 0) {
|
|
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_fluff(ptr, NULL);
|
|
}
|
|
|
|
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 addons/stats.c
|
|
* @brief Stats addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_SYSTEM
|
|
#endif
|
|
|
|
#ifdef FLECS_PIPELINE
|
|
#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 (delta_world_time != 0 && delta_frame_count != 0) {
|
|
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_sparse_count(ecs_eis(world)));
|
|
ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_sparse_not_alive_count(ecs_eis(world)));
|
|
|
|
ECS_GAUGE_RECORD(&s->ids.count, t, world->info.id_count);
|
|
ECS_GAUGE_RECORD(&s->ids.tag_count, t, world->info.tag_id_count);
|
|
ECS_GAUGE_RECORD(&s->ids.component_count, t, world->info.component_id_count);
|
|
ECS_GAUGE_RECORD(&s->ids.pair_count, t, world->info.pair_id_count);
|
|
ECS_GAUGE_RECORD(&s->ids.wildcard_count, t, world->info.wildcard_id_count);
|
|
ECS_GAUGE_RECORD(&s->ids.type_count, t, ecs_sparse_count(&world->type_info));
|
|
ECS_COUNTER_RECORD(&s->ids.create_count, t, world->info.id_create_total);
|
|
ECS_COUNTER_RECORD(&s->ids.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));
|
|
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_GAUGE_RECORD(&s->tables.tag_only_count, t, world->info.tag_table_count);
|
|
ECS_GAUGE_RECORD(&s->tables.trivial_only_count, t, world->info.trivial_table_count);
|
|
ECS_GAUGE_RECORD(&s->tables.storage_count, t, world->info.table_storage_count);
|
|
ECS_GAUGE_RECORD(&s->tables.record_count, t, world->info.table_record_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_REST
|
|
ECS_COUNTER_RECORD(&s->rest.request_count, t, ecs_rest_request_count);
|
|
ECS_COUNTER_RECORD(&s->rest.entity_count, t, ecs_rest_entity_count);
|
|
ECS_COUNTER_RECORD(&s->rest.entity_error_count, t, ecs_rest_entity_error_count);
|
|
ECS_COUNTER_RECORD(&s->rest.query_count, t, ecs_rest_query_count);
|
|
ECS_COUNTER_RECORD(&s->rest.query_error_count, t, ecs_rest_query_error_count);
|
|
ECS_COUNTER_RECORD(&s->rest.query_name_count, t, ecs_rest_query_name_count);
|
|
ECS_COUNTER_RECORD(&s->rest.query_name_error_count, t, ecs_rest_query_name_error_count);
|
|
ECS_COUNTER_RECORD(&s->rest.query_name_from_cache_count, t, ecs_rest_query_name_from_cache_count);
|
|
ECS_COUNTER_RECORD(&s->rest.enable_count, t, ecs_rest_enable_count);
|
|
ECS_COUNTER_RECORD(&s->rest.enable_error_count, t, ecs_rest_enable_error_count);
|
|
ECS_COUNTER_RECORD(&s->rest.world_stats_count, t, ecs_rest_world_stats_count);
|
|
ECS_COUNTER_RECORD(&s->rest.pipeline_stats_count, t, ecs_rest_pipeline_stats_count);
|
|
ECS_COUNTER_RECORD(&s->rest.stats_error_count, t, ecs_rest_stats_error_count);
|
|
#endif
|
|
|
|
#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);
|
|
ECS_GAUGE_RECORD(&s->active, t, !ecs_has_id(world, system, EcsEmpty));
|
|
ECS_GAUGE_RECORD(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled));
|
|
|
|
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_initialized(&s->system_stats) && !sys_count) {
|
|
ecs_map_fini(&s->system_stats);
|
|
}
|
|
ecs_map_init_if(&s->system_stats, ecs_system_stats_t, NULL, sys_count);
|
|
|
|
/* Make sure vector is large enough to store all systems & sync points */
|
|
ecs_entity_t *systems = NULL;
|
|
if (pip_count) {
|
|
ecs_vector_set_count(&s->systems, ecs_entity_t, pip_count);
|
|
systems = ecs_vector_first(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_vector_free(s->systems);
|
|
s->systems = NULL;
|
|
}
|
|
|
|
/* 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)) {
|
|
int i;
|
|
for (i = 0; i < it.count; i ++) {
|
|
ecs_system_stats_t *sys_stats = ecs_map_ensure(
|
|
&s->system_stats, ecs_system_stats_t, it.entities[i]);
|
|
sys_stats->query.t = s->t;
|
|
ecs_system_stats_get(world, it.entities[i], sys_stats);
|
|
}
|
|
}
|
|
|
|
s->t = t_next(s->t);
|
|
|
|
return true;
|
|
error:
|
|
return false;
|
|
}
|
|
|
|
void ecs_pipeline_stats_fini(
|
|
ecs_pipeline_stats_t *stats)
|
|
{
|
|
ecs_map_fini(&stats->system_stats);
|
|
ecs_vector_free(stats->systems);
|
|
}
|
|
|
|
void ecs_pipeline_stats_reduce(
|
|
ecs_pipeline_stats_t *dst,
|
|
const ecs_pipeline_stats_t *src)
|
|
{
|
|
int32_t system_count = ecs_vector_count(src->systems);
|
|
ecs_vector_set_count(&dst->systems, ecs_entity_t, system_count);
|
|
ecs_entity_t *dst_systems = ecs_vector_first(dst->systems, ecs_entity_t);
|
|
ecs_entity_t *src_systems = ecs_vector_first(src->systems, ecs_entity_t);
|
|
ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count);
|
|
|
|
ecs_map_init_if(&dst->system_stats, ecs_system_stats_t,
|
|
NULL, ecs_map_count(&src->system_stats));
|
|
|
|
ecs_map_iter_t it = ecs_map_iter(&src->system_stats);
|
|
ecs_system_stats_t *sys_src, *sys_dst;
|
|
ecs_entity_t key;
|
|
while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) {
|
|
sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key);
|
|
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)
|
|
{
|
|
ecs_map_init_if(&dst->system_stats, ecs_system_stats_t,
|
|
NULL, ecs_map_count(&src->system_stats));
|
|
|
|
ecs_map_iter_t it = ecs_map_iter(&src->system_stats);
|
|
ecs_system_stats_t *sys_src, *sys_dst;
|
|
ecs_entity_t key;
|
|
while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) {
|
|
sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key);
|
|
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)
|
|
{
|
|
ecs_map_iter_t it = ecs_map_iter(&stats->system_stats);
|
|
ecs_system_stats_t *sys;
|
|
ecs_entity_t key;
|
|
while ((sys = ecs_map_next(&it, ecs_system_stats_t, &key))) {
|
|
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)
|
|
{
|
|
ecs_map_init_if(&dst->system_stats, ecs_system_stats_t, NULL,
|
|
ecs_map_count(&src->system_stats));
|
|
|
|
ecs_map_iter_t it = ecs_map_iter(&src->system_stats);
|
|
ecs_system_stats_t *sys_src, *sys_dst;
|
|
ecs_entity_t key;
|
|
while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) {
|
|
sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key);
|
|
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("id count", t, &s->ids.count);
|
|
flecs_gauge_print("tag id count", t, &s->ids.tag_count);
|
|
flecs_gauge_print("component id count", t, &s->ids.component_count);
|
|
flecs_gauge_print("pair id count", t, &s->ids.pair_count);
|
|
flecs_gauge_print("wildcard id count", t, &s->ids.wildcard_count);
|
|
flecs_gauge_print("type count", t, &s->ids.type_count);
|
|
flecs_counter_print("id create count", t, &s->ids.create_count);
|
|
flecs_counter_print("id delete count", t, &s->ids.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_gauge_print("tag table count", t, &s->tables.tag_only_count);
|
|
flecs_gauge_print("trivial table count", t, &s->tables.trivial_only_count);
|
|
flecs_gauge_print("table storage count", t, &s->tables.storage_count);
|
|
flecs_gauge_print("table cache record count", t, &s->tables.record_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/units.c
|
|
* @brief Units addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_UNITS
|
|
|
|
ECS_DECLARE(EcsUnitPrefixes);
|
|
|
|
ECS_DECLARE(EcsYocto);
|
|
ECS_DECLARE(EcsZepto);
|
|
ECS_DECLARE(EcsAtto);
|
|
ECS_DECLARE(EcsFemto);
|
|
ECS_DECLARE(EcsPico);
|
|
ECS_DECLARE(EcsNano);
|
|
ECS_DECLARE(EcsMicro);
|
|
ECS_DECLARE(EcsMilli);
|
|
ECS_DECLARE(EcsCenti);
|
|
ECS_DECLARE(EcsDeci);
|
|
ECS_DECLARE(EcsDeca);
|
|
ECS_DECLARE(EcsHecto);
|
|
ECS_DECLARE(EcsKilo);
|
|
ECS_DECLARE(EcsMega);
|
|
ECS_DECLARE(EcsGiga);
|
|
ECS_DECLARE(EcsTera);
|
|
ECS_DECLARE(EcsPeta);
|
|
ECS_DECLARE(EcsExa);
|
|
ECS_DECLARE(EcsZetta);
|
|
ECS_DECLARE(EcsYotta);
|
|
|
|
ECS_DECLARE(EcsKibi);
|
|
ECS_DECLARE(EcsMebi);
|
|
ECS_DECLARE(EcsGibi);
|
|
ECS_DECLARE(EcsTebi);
|
|
ECS_DECLARE(EcsPebi);
|
|
ECS_DECLARE(EcsExbi);
|
|
ECS_DECLARE(EcsZebi);
|
|
ECS_DECLARE(EcsYobi);
|
|
|
|
ECS_DECLARE(EcsDuration);
|
|
ECS_DECLARE(EcsPicoSeconds);
|
|
ECS_DECLARE(EcsNanoSeconds);
|
|
ECS_DECLARE(EcsMicroSeconds);
|
|
ECS_DECLARE(EcsMilliSeconds);
|
|
ECS_DECLARE(EcsSeconds);
|
|
ECS_DECLARE(EcsMinutes);
|
|
ECS_DECLARE(EcsHours);
|
|
ECS_DECLARE(EcsDays);
|
|
|
|
ECS_DECLARE(EcsTime);
|
|
ECS_DECLARE(EcsDate);
|
|
|
|
ECS_DECLARE(EcsMass);
|
|
ECS_DECLARE(EcsGrams);
|
|
ECS_DECLARE(EcsKiloGrams);
|
|
|
|
ECS_DECLARE(EcsElectricCurrent);
|
|
ECS_DECLARE(EcsAmpere);
|
|
|
|
ECS_DECLARE(EcsAmount);
|
|
ECS_DECLARE(EcsMole);
|
|
|
|
ECS_DECLARE(EcsLuminousIntensity);
|
|
ECS_DECLARE(EcsCandela);
|
|
|
|
ECS_DECLARE(EcsForce);
|
|
ECS_DECLARE(EcsNewton);
|
|
|
|
ECS_DECLARE(EcsLength);
|
|
ECS_DECLARE(EcsMeters);
|
|
ECS_DECLARE(EcsPicoMeters);
|
|
ECS_DECLARE(EcsNanoMeters);
|
|
ECS_DECLARE(EcsMicroMeters);
|
|
ECS_DECLARE(EcsMilliMeters);
|
|
ECS_DECLARE(EcsCentiMeters);
|
|
ECS_DECLARE(EcsKiloMeters);
|
|
ECS_DECLARE(EcsMiles);
|
|
ECS_DECLARE(EcsPixels);
|
|
|
|
ECS_DECLARE(EcsPressure);
|
|
ECS_DECLARE(EcsPascal);
|
|
ECS_DECLARE(EcsBar);
|
|
|
|
ECS_DECLARE(EcsSpeed);
|
|
ECS_DECLARE(EcsMetersPerSecond);
|
|
ECS_DECLARE(EcsKiloMetersPerSecond);
|
|
ECS_DECLARE(EcsKiloMetersPerHour);
|
|
ECS_DECLARE(EcsMilesPerHour);
|
|
|
|
ECS_DECLARE(EcsAcceleration);
|
|
|
|
ECS_DECLARE(EcsTemperature);
|
|
ECS_DECLARE(EcsKelvin);
|
|
ECS_DECLARE(EcsCelsius);
|
|
ECS_DECLARE(EcsFahrenheit);
|
|
|
|
ECS_DECLARE(EcsData);
|
|
ECS_DECLARE(EcsBits);
|
|
ECS_DECLARE(EcsKiloBits);
|
|
ECS_DECLARE(EcsMegaBits);
|
|
ECS_DECLARE(EcsGigaBits);
|
|
ECS_DECLARE(EcsBytes);
|
|
ECS_DECLARE(EcsKiloBytes);
|
|
ECS_DECLARE(EcsMegaBytes);
|
|
ECS_DECLARE(EcsGigaBytes);
|
|
ECS_DECLARE(EcsKibiBytes);
|
|
ECS_DECLARE(EcsGibiBytes);
|
|
ECS_DECLARE(EcsMebiBytes);
|
|
|
|
ECS_DECLARE(EcsDataRate);
|
|
ECS_DECLARE(EcsBitsPerSecond);
|
|
ECS_DECLARE(EcsKiloBitsPerSecond);
|
|
ECS_DECLARE(EcsMegaBitsPerSecond);
|
|
ECS_DECLARE(EcsGigaBitsPerSecond);
|
|
ECS_DECLARE(EcsBytesPerSecond);
|
|
ECS_DECLARE(EcsKiloBytesPerSecond);
|
|
ECS_DECLARE(EcsMegaBytesPerSecond);
|
|
ECS_DECLARE(EcsGigaBytesPerSecond);
|
|
|
|
ECS_DECLARE(EcsPercentage);
|
|
|
|
ECS_DECLARE(EcsAngle);
|
|
ECS_DECLARE(EcsRadians);
|
|
ECS_DECLARE(EcsDegrees);
|
|
|
|
ECS_DECLARE(EcsBel);
|
|
ECS_DECLARE(EcsDeciBel);
|
|
|
|
ECS_DECLARE(EcsFrequency);
|
|
ECS_DECLARE(EcsHertz);
|
|
ECS_DECLARE(EcsKiloHertz);
|
|
ECS_DECLARE(EcsMegaHertz);
|
|
ECS_DECLARE(EcsGigaHertz);
|
|
|
|
ECS_DECLARE(EcsUri);
|
|
ECS_DECLARE(EcsUriHyperlink);
|
|
ECS_DECLARE(EcsUriImage);
|
|
ECS_DECLARE(EcsUriFile);
|
|
|
|
void FlecsUnitsImport(
|
|
ecs_world_t *world)
|
|
{
|
|
ECS_MODULE(world, FlecsUnits);
|
|
|
|
ecs_set_name_prefix(world, "Ecs");
|
|
|
|
EcsUnitPrefixes = ecs_entity_init(world, &(ecs_entity_desc_t){
|
|
.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 addons/snapshot.c
|
|
* @brief Snapshot addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_SNAPSHOT
|
|
|
|
|
|
/* World snapshot */
|
|
struct ecs_snapshot_t {
|
|
ecs_world_t *world;
|
|
ecs_sparse_t *entity_index;
|
|
ecs_vector_t *tables;
|
|
ecs_entity_t last_id;
|
|
ecs_filter_t filter;
|
|
};
|
|
|
|
/** 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)
|
|
{
|
|
if (!ecs_table_count(table)) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_data_t *result = ecs_os_calloc_t(ecs_data_t);
|
|
int32_t i, column_count = table->storage_count;
|
|
result->columns = flecs_wdup_n(world, ecs_vec_t, column_count,
|
|
main_data->columns);
|
|
|
|
/* Copy entities and records */
|
|
ecs_allocator_t *a = &world->allocator;
|
|
result->entities = ecs_vec_copy_t(a, &main_data->entities, ecs_entity_t);
|
|
result->records = ecs_vec_copy_t(a, &main_data->records, ecs_record_t*);
|
|
|
|
/* Copy each column */
|
|
for (i = 0; i < column_count; i ++) {
|
|
ecs_vec_t *column = &result->columns[i];
|
|
ecs_type_info_t *ti = table->type_info[i];
|
|
int32_t size = ti->size;
|
|
ecs_copy_t copy = ti->hooks.copy;
|
|
if (copy) {
|
|
ecs_vec_t dst = ecs_vec_copy(a, column, size);
|
|
int32_t count = ecs_vec_count(column);
|
|
void *dst_ptr = ecs_vec_first(&dst);
|
|
void *src_ptr = ecs_vec_first(column);
|
|
|
|
ecs_xtor_t ctor = ti->hooks.ctor;
|
|
if (ctor) {
|
|
ctor(dst_ptr, count, ti);
|
|
}
|
|
|
|
copy(dst_ptr, src_ptr, count, ti);
|
|
*column = dst;
|
|
} else {
|
|
*column = ecs_vec_copy(a, column, 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_vector_get(
|
|
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_world_t*)world, &table->type);
|
|
l->data = flecs_duplicate_data((ecs_world_t*)world, table, &table->data);
|
|
}
|
|
|
|
static
|
|
ecs_snapshot_t* snapshot_create(
|
|
const ecs_world_t *world,
|
|
const ecs_sparse_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_world_t*)world, 0);
|
|
|
|
result->world = (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) {
|
|
result->entity_index = flecs_sparse_copy(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;
|
|
result->tables = ecs_vector_new(ecs_table_leaf_t, (int32_t)table_count);
|
|
ecs_vector_set_count(&result->tables, ecs_table_leaf_t, (int32_t)table_count);
|
|
ecs_table_leaf_t *arr = ecs_vector_first(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 = 0; t < table_count; t ++) {
|
|
ecs_table_t *table = flecs_sparse_get(
|
|
&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 = world->info.last_id;
|
|
|
|
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 = world->info.last_id;
|
|
|
|
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_sparse_restore(ecs_eis(world), snapshot->entity_index);
|
|
flecs_sparse_free(snapshot->entity_index);
|
|
|
|
world->info.last_id = snapshot->last_id;
|
|
|
|
ecs_table_leaf_t *leafs = ecs_vector_first(
|
|
snapshot->tables, ecs_table_leaf_t);
|
|
int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables);
|
|
int32_t snapshot_count = ecs_vector_count(snapshot->tables);
|
|
|
|
for (i = 0; i <= count; i ++) {
|
|
ecs_table_t *world_table = flecs_sparse_get(
|
|
&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(
|
|
&world->store.tables, ecs_table_t, i);
|
|
if (table->flags & EcsTableHasBuiltins) {
|
|
continue;
|
|
}
|
|
|
|
int32_t tcount = ecs_table_count(table);
|
|
if (tcount) {
|
|
flecs_notify_on_set(world, table, 0, tcount, NULL, 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_vector_first(
|
|
snapshot->tables, ecs_table_leaf_t);
|
|
int32_t l = 0, snapshot_count = ecs_vector_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_get(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) {
|
|
flecs_notify_on_set(
|
|
world, table, old_count, new_count, NULL, true);
|
|
}
|
|
|
|
flecs_wfree_n(world, ecs_vec_t, table->storage_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 (snapshot->entity_index) {
|
|
/* 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_vector_free(snapshot->tables);
|
|
|
|
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_vector_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_vector_first(iter->tables, ecs_table_leaf_t);
|
|
int32_t count = ecs_vector_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_sparse_free(snapshot->entity_index);
|
|
|
|
ecs_table_leaf_t *tables = ecs_vector_first(snapshot->tables, ecs_table_leaf_t);
|
|
int32_t i, count = ecs_vector_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_vector_free(snapshot->tables);
|
|
ecs_os_free(snapshot);
|
|
}
|
|
|
|
#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_get_system_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_get_system_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);
|
|
}
|
|
|
|
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;
|
|
|
|
if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) {
|
|
#ifdef FLECS_TIMER
|
|
if (desc->interval != 0) {
|
|
ecs_set_interval(world, entity, desc->interval);
|
|
}
|
|
|
|
if (desc->rate) {
|
|
ecs_set_rate(world, entity, desc->rate, desc->tick_source);
|
|
} else if (desc->tick_source) {
|
|
ecs_set_tick_source(world, entity, desc->tick_source);
|
|
}
|
|
#else
|
|
ecs_abort(ECS_UNSUPPORTED, "timer module not available");
|
|
#endif
|
|
}
|
|
|
|
if (ecs_get_name(world, entity)) {
|
|
ecs_trace("#[green]system#[reset] %s created",
|
|
ecs_get_name(world, entity));
|
|
}
|
|
|
|
ecs_defer_end(world);
|
|
} else {
|
|
ecs_system_t *system = ecs_poly(poly->poly, ecs_system_t);
|
|
|
|
if (desc->run) {
|
|
system->run = desc->run;
|
|
}
|
|
if (desc->callback) {
|
|
system->action = desc->callback;
|
|
}
|
|
if (desc->ctx) {
|
|
system->ctx = desc->ctx;
|
|
}
|
|
if (desc->binding_ctx) {
|
|
system->binding_ctx = desc->binding_ctx;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
ecs_poly_modified(world, entity, ecs_system_t);
|
|
|
|
return entity;
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
void FlecsSystemImport(
|
|
ecs_world_t *world)
|
|
{
|
|
ECS_MODULE(world, FlecsSystem);
|
|
|
|
ecs_set_name_prefix(world, "Ecs");
|
|
|
|
flecs_bootstrap_tag(world, EcsSystem);
|
|
flecs_bootstrap_component(world, EcsTickSource);
|
|
|
|
/* Put following tags in flecs.core so they can be looked up
|
|
* without using the flecs.systems prefix. */
|
|
ecs_entity_t old_scope = ecs_set_scope(world, EcsFlecsCore);
|
|
flecs_bootstrap_tag(world, EcsMonitor);
|
|
ecs_set_scope(world, old_scope);
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file json/json.c
|
|
* @brief JSON serializer utilities.
|
|
*/
|
|
|
|
/**
|
|
* @file json/json.h
|
|
* @brief Internal functions for JSON addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_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);
|
|
|
|
ecs_primitive_kind_t flecs_json_op_to_primitive_kind(
|
|
ecs_meta_type_op_kind_t kind);
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef FLECS_JSON
|
|
|
|
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_zerocpy(buf, 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, '"');
|
|
}
|
|
|
|
void flecs_json_label(
|
|
ecs_strbuf_t *buf,
|
|
const ecs_world_t *world,
|
|
ecs_entity_t e)
|
|
{
|
|
const char *lbl = NULL;
|
|
#ifdef FLECS_DOC
|
|
lbl = ecs_doc_get_name(world, e);
|
|
#else
|
|
lbl = ecs_get_name(world, e);
|
|
#endif
|
|
|
|
if (lbl) {
|
|
ecs_strbuf_appendch(buf, '"');
|
|
ecs_strbuf_appendstr(buf, lbl);
|
|
ecs_strbuf_appendch(buf, '"');
|
|
} 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, '"');
|
|
ecs_id_str_buf(world, id, buf);
|
|
ecs_strbuf_appendch(buf, '"');
|
|
}
|
|
|
|
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(
|
|
const ecs_world_t *world,
|
|
ecs_vector_t *ser,
|
|
const void *base,
|
|
ecs_strbuf_t *str);
|
|
|
|
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 = *(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(
|
|
enum_type->constants, ecs_enum_constant_t, value);
|
|
if (!constant) {
|
|
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 = *(uint32_t*)ptr;
|
|
ecs_map_key_t key;
|
|
ecs_bitmask_constant_t *constant;
|
|
|
|
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 ((constant = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) {
|
|
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 */
|
|
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)
|
|
{
|
|
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, 1)) {
|
|
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)
|
|
{
|
|
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_vector_first(ser->ops, ecs_meta_type_op_t);
|
|
int32_t op_count = ecs_vector_count(ser->ops);
|
|
|
|
return json_ser_elements(
|
|
world, ops, op_count, base, elem_count, comp->size, str);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
ecs_vector_t *value = *(ecs_vector_t**)base;
|
|
if (!value) {
|
|
ecs_strbuf_appendlit(str, "null");
|
|
return 0;
|
|
}
|
|
|
|
const EcsVector *v = ecs_get(world, op->type, EcsVector);
|
|
ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
const EcsComponent *comp = ecs_get(world, v->type, EcsComponent);
|
|
ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t count = ecs_vector_count(value);
|
|
void *array = ecs_vector_first_t(value, comp->size, comp->alignment);
|
|
|
|
/* Serialize contiguous buffer of vector */
|
|
return json_ser_type_elements(world, v->type, array, count, str);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
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)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"');
|
|
break;
|
|
case EcsOpF64:
|
|
ecs_strbuf_appendflt(str,
|
|
*(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"');
|
|
break;
|
|
case EcsOpEnum:
|
|
if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case EcsOpBitmask:
|
|
if (json_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case EcsOpArray:
|
|
if (json_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case EcsOpVector:
|
|
if (json_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case EcsOpEntity: {
|
|
ecs_entity_t e = *(ecs_entity_t*)ECS_OFFSET(ptr, op->offset);
|
|
if (!e) {
|
|
ecs_strbuf_appendch(str, '0');
|
|
} else {
|
|
flecs_json_path(str, world, e);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (ecs_primitive_to_expr_buf(world,
|
|
flecs_json_op_to_primitive_kind(op->kind),
|
|
ECS_OFFSET(ptr, op->offset), str))
|
|
{
|
|
/* Unknown operation */
|
|
ecs_throw(ECS_INTERNAL_ERROR, NULL);
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
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))
|
|
{
|
|
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;
|
|
default:
|
|
if (json_ser_type_op(world, op, base, str)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
/* Iterate over the type ops of a type */
|
|
static
|
|
int json_ser_type(
|
|
const ecs_world_t *world,
|
|
ecs_vector_t *v_ops,
|
|
const void *base,
|
|
ecs_strbuf_t *str)
|
|
{
|
|
ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t);
|
|
int32_t count = ecs_vector_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 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 (!desc || !desc->serialize_meta_ids) {
|
|
if (pred == EcsIsA || pred == EcsChildOf ||
|
|
pred == ecs_id(EcsIdentifier))
|
|
{
|
|
return true;
|
|
}
|
|
#ifdef FLECS_DOC
|
|
if (pred == ecs_id(EcsDocDescription)) {
|
|
return true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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 (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 (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 (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 (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;
|
|
}
|
|
|
|
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 (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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static
|
|
bool flecs_json_skip_variable(
|
|
const char *name)
|
|
{
|
|
if (!name || name[0] == '_' || name[0] == '.') {
|
|
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_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_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;
|
|
}
|
|
|
|
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 = ecs_get_typeid(world, it->terms[i].id);
|
|
if (typeid) {
|
|
flecs_json_serialize_id(world, typeid, buf);
|
|
ecs_strbuf_appendch(buf, ':');
|
|
ecs_type_info_to_json_buf(world, typeid, buf);
|
|
} else {
|
|
flecs_json_serialize_id(world, it->terms[i].id, buf);
|
|
ecs_strbuf_appendlit(buf, ":0");
|
|
}
|
|
}
|
|
|
|
flecs_json_object_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 = 0; 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_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);
|
|
}
|
|
|
|
static
|
|
void flecs_json_serialize_iter_result_is_set(
|
|
const ecs_iter_t *it,
|
|
ecs_strbuf_t *buf)
|
|
{
|
|
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);
|
|
}
|
|
|
|
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 = 0; 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 = 0; 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 = 0; 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
|
|
void flecs_json_serialize_iter_result_entities(
|
|
const ecs_world_t *world,
|
|
const ecs_iter_t *it,
|
|
ecs_strbuf_t *buf)
|
|
{
|
|
int32_t count = it->count;
|
|
if (!it->count) {
|
|
return;
|
|
}
|
|
|
|
flecs_json_memberl(buf, "entities");
|
|
flecs_json_array_push(buf);
|
|
|
|
ecs_entity_t *entities = it->entities;
|
|
|
|
for (int i = 0; i < count; i ++) {
|
|
flecs_json_next(buf);
|
|
flecs_json_path(buf, world, entities[i]);
|
|
}
|
|
|
|
flecs_json_array_pop(buf);
|
|
}
|
|
|
|
static
|
|
void flecs_json_serialize_iter_result_entity_labels(
|
|
const ecs_world_t *world,
|
|
const ecs_iter_t *it,
|
|
ecs_strbuf_t *buf)
|
|
{
|
|
int32_t count = it->count;
|
|
if (!it->count) {
|
|
return;
|
|
}
|
|
|
|
flecs_json_memberl(buf, "entity_labels");
|
|
flecs_json_array_push(buf);
|
|
|
|
ecs_entity_t *entities = it->entities;
|
|
|
|
for (int i = 0; i < count; i ++) {
|
|
flecs_json_next(buf);
|
|
flecs_json_label(buf, world, entities[i]);
|
|
}
|
|
|
|
flecs_json_array_pop(buf);
|
|
}
|
|
|
|
static
|
|
void flecs_json_serialize_iter_result_entity_ids(
|
|
const ecs_iter_t *it,
|
|
ecs_strbuf_t *buf)
|
|
{
|
|
int32_t count = it->count;
|
|
if (!it->count) {
|
|
return;
|
|
}
|
|
|
|
flecs_json_memberl(buf, "entity_ids");
|
|
flecs_json_array_push(buf);
|
|
|
|
ecs_entity_t *entities = it->entities;
|
|
|
|
for (int i = 0; i < count; i ++) {
|
|
flecs_json_next(buf);
|
|
flecs_json_number(buf, (double)entities[i]);
|
|
}
|
|
|
|
flecs_json_array_pop(buf);
|
|
}
|
|
|
|
static
|
|
void flecs_json_serialize_iter_result_colors(
|
|
const ecs_world_t *world,
|
|
const ecs_iter_t *it,
|
|
ecs_strbuf_t *buf)
|
|
{
|
|
int32_t count = it->count;
|
|
if (!it->count) {
|
|
return;
|
|
}
|
|
|
|
flecs_json_memberl(buf, "colors");
|
|
flecs_json_array_push(buf);
|
|
|
|
ecs_entity_t *entities = it->entities;
|
|
|
|
for (int i = 0; i < count; i ++) {
|
|
flecs_json_next(buf);
|
|
flecs_json_color(buf, world, entities[i]);
|
|
}
|
|
|
|
flecs_json_array_pop(buf);
|
|
}
|
|
|
|
static
|
|
void flecs_json_serialize_iter_result_values(
|
|
const ecs_world_t *world,
|
|
const ecs_iter_t *it,
|
|
ecs_strbuf_t *buf)
|
|
{
|
|
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)) {
|
|
ecs_assert(ptr == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
flecs_json_array_push(buf);
|
|
flecs_json_array_pop(buf);
|
|
continue;
|
|
}
|
|
|
|
if (ecs_field_is_self(it, i + 1)) {
|
|
int32_t count = it->count;
|
|
array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser);
|
|
} else {
|
|
array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser);
|
|
}
|
|
}
|
|
|
|
flecs_json_array_pop(buf);
|
|
}
|
|
|
|
static
|
|
void 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)
|
|
{
|
|
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_ids) {
|
|
flecs_json_serialize_iter_result_ids(world, it, buf);
|
|
}
|
|
|
|
/* Include information on which entity the term is matched with */
|
|
if (!desc || desc->serialize_ids) {
|
|
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) {
|
|
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 labels for entities */
|
|
if (desc && desc->serialize_entity_labels) {
|
|
flecs_json_serialize_iter_result_entity_labels(world, it, buf);
|
|
}
|
|
|
|
/* Write ids for entities */
|
|
if (desc && desc->serialize_entity_ids) {
|
|
flecs_json_serialize_iter_result_entity_ids(it, buf);
|
|
}
|
|
|
|
/* Write colors for entities */
|
|
if (desc && desc->serialize_colors) {
|
|
flecs_json_serialize_iter_result_colors(world, it, buf);
|
|
}
|
|
|
|
/* Serialize component values */
|
|
if (!desc || desc->serialize_values) {
|
|
flecs_json_serialize_iter_result_values(world, it, buf);
|
|
}
|
|
|
|
flecs_json_object_pop(buf);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
flecs_json_object_push(buf);
|
|
|
|
/* Serialize component ids of the terms (usually provided by query) */
|
|
if (!desc || desc->serialize_term_ids) {
|
|
flecs_json_serialize_iter_ids(world, it, buf);
|
|
}
|
|
|
|
/* Serialize type info if enabled */
|
|
if (desc && desc->serialize_type_info) {
|
|
flecs_json_serialize_type_info(world, it, buf);
|
|
}
|
|
|
|
/* Serialize variable names, if iterator has any */
|
|
flecs_json_serialize_iter_variables(it, buf);
|
|
|
|
/* Serialize results */
|
|
flecs_json_memberl(buf, "results");
|
|
flecs_json_array_push(buf);
|
|
|
|
/* Use instancing for improved performance */
|
|
ECS_BIT_SET(it->flags, EcsIterIsInstanced);
|
|
|
|
ecs_iter_next_action_t next = it->next;
|
|
while (next(it)) {
|
|
flecs_json_serialize_iter_result(world, it, buf, desc);
|
|
}
|
|
|
|
flecs_json_array_pop(buf);
|
|
|
|
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);
|
|
}
|
|
|
|
#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;
|
|
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;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
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;
|
|
default:
|
|
if (json_typeinfo_ser_primitive(
|
|
flecs_json_op_to_primitive_kind(op->kind), str))
|
|
{
|
|
/* Unknown operation */
|
|
ecs_throw(ECS_INTERNAL_ERROR, NULL);
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
ecs_entity_t unit = op->unit;
|
|
if (unit) {
|
|
flecs_json_next(str);
|
|
flecs_json_next(str);
|
|
|
|
flecs_json_object_push(str);
|
|
json_typeinfo_ser_unit(world, str, unit);
|
|
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)
|
|
{
|
|
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 && op != ops) {
|
|
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);
|
|
break;
|
|
case EcsOpPop:
|
|
flecs_json_object_pop(str);
|
|
break;
|
|
default:
|
|
if (json_typeinfo_ser_type_op(world, op, str)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t);
|
|
int32_t count = ecs_vector_count(ser->ops);
|
|
|
|
return json_typeinfo_ser_type_ops(world, ops, count, buf);
|
|
}
|
|
|
|
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 json/deserialize.c
|
|
* @brief Deserialize JSON strings into (component) values.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_JSON
|
|
|
|
const char* ecs_parse_json(
|
|
const ecs_world_t *world,
|
|
const char *ptr,
|
|
ecs_entity_t type,
|
|
void *data_out,
|
|
const ecs_parse_json_desc_t *desc)
|
|
{
|
|
char token[ECS_MAX_TOKEN_SIZE];
|
|
int depth = 0;
|
|
|
|
const char *name = NULL;
|
|
const char *expr = NULL;
|
|
|
|
ptr = ecs_parse_fluff(ptr, NULL);
|
|
|
|
ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out);
|
|
if (cur.valid == false) {
|
|
return NULL;
|
|
}
|
|
|
|
if (desc) {
|
|
name = desc->name;
|
|
expr = desc->expr;
|
|
}
|
|
|
|
while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) {
|
|
|
|
ptr = ecs_parse_fluff(ptr, NULL);
|
|
|
|
if (!ecs_os_strcmp(token, "{")) {
|
|
depth ++;
|
|
if (ecs_meta_push(&cur) != 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (ecs_meta_is_collection(&cur)) {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected '['");
|
|
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, "[")) {
|
|
depth ++;
|
|
if (ecs_meta_push(&cur) != 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (!ecs_meta_is_collection(&cur)) {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected '{'");
|
|
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 (ecs_meta_next(&cur) != 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
else if (!ecs_os_strcmp(token, "null")) {
|
|
if (ecs_meta_set_null(&cur) != 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
else if (token[0] == '\"') {
|
|
if (ptr[0] == ':') {
|
|
/* Member assignment */
|
|
ptr ++;
|
|
|
|
/* Strip trailing " */
|
|
ecs_size_t len = ecs_os_strlen(token);
|
|
if (token[len - 1] != '"') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected \"");
|
|
return NULL;
|
|
} else {
|
|
token[len - 1] = '\0';
|
|
}
|
|
|
|
if (ecs_meta_dotmember(&cur, token + 1) != 0) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
if (ecs_meta_set_string_literal(&cur, token) != 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
if (ecs_meta_set_string(&cur, token) != 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (!depth) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
const char* flecs_parse_json_path(
|
|
const ecs_world_t *world,
|
|
const char *name,
|
|
const char *expr,
|
|
const char *ptr,
|
|
char *token,
|
|
ecs_entity_t *out)
|
|
{
|
|
/* Start of id. Ids can have two elements, in case of pairs. */
|
|
if (ptr[0] != '"') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected starting '\"'");
|
|
goto error;
|
|
}
|
|
|
|
const char *token_start = ptr;
|
|
ptr = ecs_parse_expr_token(name, expr, ptr, token);
|
|
if (!ptr) {
|
|
goto error;
|
|
}
|
|
|
|
ecs_size_t len = ecs_os_strlen(token);
|
|
if (token_start[len - 1] != '"') {
|
|
ecs_parser_error(name, expr, ptr - expr, "missing closing '\"'");
|
|
goto error;
|
|
}
|
|
|
|
token[len - 1] = '\0';
|
|
ecs_entity_t result = ecs_lookup_fullpath(world, token + 1);
|
|
if (!result) {
|
|
ecs_parser_error(name, expr, ptr - expr,
|
|
"unresolved identifier '%s'", token);
|
|
goto error;
|
|
}
|
|
|
|
*out = result;
|
|
|
|
return ecs_parse_fluff(ptr, NULL);
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
const char* ecs_parse_json_values(
|
|
ecs_world_t *world,
|
|
ecs_entity_t e,
|
|
const char *ptr,
|
|
const ecs_parse_json_desc_t *desc)
|
|
{
|
|
char token[ECS_MAX_TOKEN_SIZE];
|
|
const char *name = NULL;
|
|
const char *expr = ptr;
|
|
if (desc) {
|
|
name = desc->name;
|
|
expr = desc->expr;
|
|
}
|
|
|
|
const char *ids = NULL, *values = NULL;
|
|
|
|
ptr = ecs_parse_fluff(ptr, NULL);
|
|
if (ptr[0] != '{') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected '{'");
|
|
goto error;
|
|
}
|
|
|
|
/* Find start of ids array */
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
if (ecs_os_strncmp(ptr, "\"ids\"", 5)) {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected '\"ids\"'");
|
|
goto error;
|
|
}
|
|
|
|
ptr = ecs_parse_fluff(ptr + 5, NULL);
|
|
if (ptr[0] != ':') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected ':'");
|
|
goto error;
|
|
}
|
|
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
if (ptr[0] != '[') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected '['");
|
|
goto error;
|
|
}
|
|
|
|
ids = ecs_parse_fluff(ptr + 1, NULL);
|
|
|
|
/* Find start of values array */
|
|
const char *vptr = ptr;
|
|
int32_t bracket_depth = 0;
|
|
while (vptr[0]) {
|
|
if (vptr[0] == '[') {
|
|
bracket_depth ++;
|
|
}
|
|
if (vptr[0] == ']') {
|
|
bracket_depth --;
|
|
if (!bracket_depth) {
|
|
break;
|
|
}
|
|
}
|
|
vptr ++;
|
|
}
|
|
|
|
if (!vptr[0]) {
|
|
ecs_parser_error(name, expr, ptr - expr,
|
|
"found end of string while looking for values");
|
|
goto error;
|
|
}
|
|
|
|
ptr = ecs_parse_fluff(vptr + 1, NULL);
|
|
if (ptr[0] == '}') {
|
|
/* String doesn't contain values, which is valid */
|
|
return ptr + 1;
|
|
}
|
|
|
|
if (ptr[0] != ',') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected ','");
|
|
goto error;
|
|
}
|
|
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
if (ecs_os_strncmp(ptr, "\"values\"", 8)) {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected '\"values\"'");
|
|
goto error;
|
|
}
|
|
|
|
ptr = ecs_parse_fluff(ptr + 8, NULL);
|
|
if (ptr[0] != ':') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected ':'");
|
|
goto error;
|
|
}
|
|
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
if (ptr[0] != '[') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected '['");
|
|
goto error;
|
|
}
|
|
|
|
values = ptr;
|
|
|
|
/* Parse ids & values */
|
|
while (ids[0]) {
|
|
ecs_entity_t first = 0, second = 0, type_id = 0;
|
|
ecs_id_t id;
|
|
|
|
if (ids[0] == ']') {
|
|
/* Last id found */
|
|
if (values[0] != ']') {
|
|
ecs_parser_error(name, expr, values - expr, "expected ']'");
|
|
}
|
|
ptr = values;
|
|
break;
|
|
} else if (ids[0] == '[') {
|
|
ids = ecs_parse_fluff(ids + 1, NULL);
|
|
ids = flecs_parse_json_path(world, name, expr, ids, token, &first);
|
|
if (!ids) {
|
|
goto error;
|
|
}
|
|
|
|
if (ids[0] == ',') {
|
|
/* Id is a pair*/
|
|
ids = ecs_parse_fluff(ids + 1, NULL);
|
|
ids = flecs_parse_json_path(world, name, expr, ids, token,
|
|
&second);
|
|
if (!ids) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (ids[0] == ']') {
|
|
/* End of id array */
|
|
} else {
|
|
ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'");
|
|
goto error;
|
|
}
|
|
|
|
ids = ecs_parse_fluff(ids + 1, NULL);
|
|
if (ids[0] == ',') {
|
|
/* Next id */
|
|
ids = ecs_parse_fluff(ids + 1, NULL);
|
|
} else if (ids[0] != ']') {
|
|
/* End of ids array */
|
|
ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'");
|
|
goto error;
|
|
}
|
|
} else {
|
|
ecs_parser_error(name, expr, ids - 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, ptr - expr,
|
|
"id '%s' is not a valid component", idstr);
|
|
ecs_os_free(idstr);
|
|
goto error;
|
|
}
|
|
|
|
ecs_parse_json_desc_t parse_desc = {
|
|
.name = name,
|
|
.expr = expr,
|
|
};
|
|
values = ecs_parse_json(world, values + 1, type_id, comp_ptr, &parse_desc);
|
|
if (!values) {
|
|
goto error;
|
|
}
|
|
|
|
if (values[0] != ']' && values[0] != ',') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected ',' or ']'");
|
|
goto error;
|
|
}
|
|
|
|
ecs_modified_id(world, e, id);
|
|
}
|
|
|
|
ptr = ecs_parse_fluff(ptr + 1, NULL);
|
|
if (ptr[0] != '}') {
|
|
ecs_parser_error(name, expr, ptr - expr, "expected '}'");
|
|
}
|
|
|
|
return ptr + 1;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file addons/rest.c
|
|
* @brief Rest addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_REST
|
|
|
|
/* Time interval used to determine when to return a result from the cache. Use
|
|
* a short interval so that clients still get realtime data, but multiple
|
|
* clients requesting for the same data can reuse the same result. */
|
|
#define FLECS_REST_CACHE_TIMEOUT ((ecs_ftime_t)0.5)
|
|
|
|
typedef struct {
|
|
char *content;
|
|
int32_t content_length;
|
|
ecs_ftime_t time;
|
|
} ecs_rest_cached_t;
|
|
|
|
typedef struct {
|
|
ecs_world_t *world;
|
|
ecs_entity_t entity;
|
|
ecs_http_server_t *srv;
|
|
ecs_map_t reply_cache;
|
|
int32_t rc;
|
|
ecs_ftime_t time;
|
|
} ecs_rest_ctx_t;
|
|
|
|
/* Global statistics */
|
|
int64_t ecs_rest_request_count = 0;
|
|
int64_t ecs_rest_entity_count = 0;
|
|
int64_t ecs_rest_entity_error_count = 0;
|
|
int64_t ecs_rest_query_count = 0;
|
|
int64_t ecs_rest_query_error_count = 0;
|
|
int64_t ecs_rest_query_name_count = 0;
|
|
int64_t ecs_rest_query_name_error_count = 0;
|
|
int64_t ecs_rest_query_name_from_cache_count = 0;
|
|
int64_t ecs_rest_enable_count = 0;
|
|
int64_t ecs_rest_enable_error_count = 0;
|
|
int64_t ecs_rest_set_count = 0;
|
|
int64_t ecs_rest_set_error_count = 0;
|
|
int64_t ecs_rest_delete_count = 0;
|
|
int64_t ecs_rest_delete_error_count = 0;
|
|
int64_t ecs_rest_world_stats_count = 0;
|
|
int64_t ecs_rest_pipeline_stats_count = 0;
|
|
int64_t ecs_rest_stats_error_count = 0;
|
|
|
|
static
|
|
void flecs_rest_free_reply_cache(ecs_map_t *reply_cache) {
|
|
if (ecs_map_is_initialized(reply_cache)) {
|
|
ecs_map_iter_t it = ecs_map_iter(reply_cache);
|
|
ecs_rest_cached_t *reply;
|
|
while ((reply = ecs_map_next(&it, ecs_rest_cached_t, 0))) {
|
|
ecs_os_free(reply->content);
|
|
}
|
|
ecs_map_fini(reply_cache);
|
|
}
|
|
}
|
|
|
|
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);
|
|
flecs_rest_free_reply_cache(&impl->reply_cache);
|
|
ecs_os_free(impl);
|
|
}
|
|
}
|
|
ecs_os_free(ptr->ipaddr);
|
|
})
|
|
|
|
static char *rest_last_err;
|
|
|
|
static
|
|
void flecs_rest_capture_log(
|
|
int32_t level,
|
|
const char *file,
|
|
int32_t line,
|
|
const char *msg)
|
|
{
|
|
(void)file; (void)line;
|
|
|
|
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 = (char*)value;
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_rest_parse_json_ser_entity_params(
|
|
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, "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);
|
|
}
|
|
|
|
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, "ids", &desc->serialize_ids);
|
|
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, "entities", &desc->serialize_entities);
|
|
flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels);
|
|
flecs_rest_bool_param(req, "entity_ids", &desc->serialize_entity_ids);
|
|
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);
|
|
}
|
|
|
|
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_os_linc(&ecs_rest_entity_count);
|
|
|
|
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;
|
|
ecs_os_linc(&ecs_rest_entity_error_count);
|
|
return true;
|
|
}
|
|
|
|
ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT;
|
|
flecs_rest_parse_json_ser_entity_params(&desc, req);
|
|
|
|
ecs_entity_to_json_buf(world, e, &reply->body, &desc);
|
|
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_os_linc(&ecs_rest_set_count);
|
|
|
|
ecs_entity_t e;
|
|
if (!(e = flecs_rest_entity_from_path(world, reply, path))) {
|
|
ecs_os_linc(&ecs_rest_set_error_count);
|
|
return true;
|
|
}
|
|
|
|
const char *data = ecs_http_get_param(req, "data");
|
|
ecs_parse_json_desc_t desc = {0};
|
|
desc.expr = data;
|
|
desc.name = path;
|
|
if (ecs_parse_json_values(world, e, data, &desc) == NULL) {
|
|
flecs_reply_error(reply, "invalid request");
|
|
reply->code = 400;
|
|
ecs_os_linc(&ecs_rest_set_error_count);
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static
|
|
bool flecs_rest_delete(
|
|
ecs_world_t *world,
|
|
ecs_http_reply_t *reply,
|
|
const char *path)
|
|
{
|
|
ecs_os_linc(&ecs_rest_set_count);
|
|
|
|
ecs_entity_t e;
|
|
if (!(e = flecs_rest_entity_from_path(world, reply, path))) {
|
|
ecs_os_linc(&ecs_rest_delete_error_count);
|
|
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_os_linc(&ecs_rest_enable_count);
|
|
|
|
ecs_entity_t e;
|
|
if (!(e = flecs_rest_entity_from_path(world, reply, path))) {
|
|
ecs_os_linc(&ecs_rest_enable_error_count);
|
|
return true;
|
|
}
|
|
|
|
ecs_enable(world, e, enable);
|
|
|
|
return true;
|
|
}
|
|
|
|
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 = ECS_ITER_TO_JSON_INIT;
|
|
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);
|
|
|
|
ecs_iter_t pit = ecs_page_iter(it, offset, limit);
|
|
ecs_iter_to_json_buf(world, &pit, &reply->body, &desc);
|
|
}
|
|
|
|
static
|
|
bool flecs_rest_reply_existing_query(
|
|
ecs_world_t *world,
|
|
ecs_rest_ctx_t *impl,
|
|
const ecs_http_request_t* req,
|
|
ecs_http_reply_t *reply,
|
|
const char *name)
|
|
{
|
|
ecs_os_linc(&ecs_rest_query_name_count);
|
|
|
|
ecs_entity_t q = ecs_lookup_fullpath(world, name);
|
|
if (!q) {
|
|
flecs_reply_error(reply, "unresolved identifier '%s'", name);
|
|
reply->code = 404;
|
|
ecs_os_linc(&ecs_rest_query_name_error_count);
|
|
return true;
|
|
}
|
|
|
|
ecs_map_init_if(&impl->reply_cache, ecs_rest_cached_t, NULL, 1);
|
|
ecs_rest_cached_t *cached = ecs_map_get(&impl->reply_cache,
|
|
ecs_rest_cached_t, q);
|
|
if (cached) {
|
|
if ((impl->time - cached->time) > FLECS_REST_CACHE_TIMEOUT) {
|
|
ecs_os_free(cached->content);
|
|
} else {
|
|
/* Cache hit */
|
|
ecs_strbuf_appendstr_zerocpyn_const(
|
|
&reply->body, cached->content, cached->content_length);
|
|
ecs_os_linc(&ecs_rest_query_name_from_cache_count);
|
|
return true;
|
|
}
|
|
} else {
|
|
cached = ecs_map_ensure(&impl->reply_cache, ecs_rest_cached_t, q);
|
|
}
|
|
|
|
/* Cache miss */
|
|
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;
|
|
ecs_os_linc(&ecs_rest_query_name_error_count);
|
|
return true;
|
|
}
|
|
|
|
ecs_iter_t it;
|
|
ecs_iter_poly(world, poly->poly, &it, NULL);
|
|
flecs_rest_iter_to_reply(world, req, reply, &it);
|
|
|
|
cached->content_length = ecs_strbuf_written(&reply->body);
|
|
cached->content = ecs_strbuf_get(&reply->body);
|
|
ecs_strbuf_reset(&reply->body);
|
|
ecs_strbuf_appendstr_zerocpyn_const(
|
|
&reply->body, cached->content, cached->content_length);
|
|
cached->time = impl->time;
|
|
|
|
return true;
|
|
}
|
|
|
|
static
|
|
bool flecs_rest_reply_query(
|
|
ecs_world_t *world,
|
|
ecs_rest_ctx_t *impl,
|
|
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, impl, req, reply, q_name);
|
|
}
|
|
|
|
ecs_os_linc(&ecs_rest_query_count);
|
|
|
|
const char *q = ecs_http_get_param(req, "q");
|
|
if (!q) {
|
|
ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'");
|
|
reply->code = 400; /* bad request */
|
|
ecs_os_linc(&ecs_rest_query_error_count);
|
|
return true;
|
|
}
|
|
|
|
ecs_dbg_2("rest: request query '%s'", q);
|
|
bool prev_color = ecs_log_enable_colors(false);
|
|
ecs_os_api_log_t 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) {
|
|
char *err = flecs_rest_get_captured_log();
|
|
char *escaped_err = ecs_astresc('"', err);
|
|
flecs_reply_error(reply, escaped_err);
|
|
ecs_os_linc(&ecs_rest_query_error_count);
|
|
reply->code = 400; /* bad request */
|
|
ecs_os_free(escaped_err);
|
|
ecs_os_free(err);
|
|
} 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_ = 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_GAUGE_APPEND(reply, stats, tables.tag_only_count, "Tables with only tags");
|
|
ECS_GAUGE_APPEND(reply, stats, tables.trivial_only_count, "Tables with only trivial types (no hooks)");
|
|
ECS_GAUGE_APPEND(reply, stats, tables.record_count, "Table records registered with search indices");
|
|
ECS_GAUGE_APPEND(reply, stats, tables.storage_count, "Component storages for all tables");
|
|
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, ids.count, "Component, tag and pair ids in use");
|
|
ECS_GAUGE_APPEND(reply, stats, ids.tag_count, "Tag ids in use");
|
|
ECS_GAUGE_APPEND(reply, stats, ids.component_count, "Component ids in use");
|
|
ECS_GAUGE_APPEND(reply, stats, ids.pair_count, "Pair ids in use");
|
|
ECS_GAUGE_APPEND(reply, stats, ids.wildcard_count, "Wildcard ids in use");
|
|
ECS_GAUGE_APPEND(reply, stats, ids.type_count, "Registered component types");
|
|
ECS_COUNTER_APPEND(reply, stats, ids.create_count, "Number of new component, tag and pair ids created");
|
|
ECS_COUNTER_APPEND(reply, stats, ids.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, rest.request_count, "Received requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.entity_count, "Received entity/ requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.entity_error_count, "Failed entity/ requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.query_count, "Received query/ requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.query_error_count, "Failed query/ requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.query_name_count, "Received named query/ requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.query_name_error_count, "Failed named query/ requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.query_name_from_cache_count, "Named query/ requests from cache");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.enable_count, "Received enable/ and disable/ requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.enable_error_count, "Failed enable/ and disable/ requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.world_stats_count, "Received world stats requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.pipeline_stats_count, "Received pipeline stats requests");
|
|
ECS_COUNTER_APPEND(reply, stats, rest.stats_error_count, "Failed stats requests");
|
|
|
|
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_vector_count(stats->stats.systems);
|
|
ecs_entity_t *ids = ecs_vector_first(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(
|
|
&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_strbuf_list_pop(reply, "}");
|
|
}
|
|
}
|
|
|
|
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;
|
|
ecs_os_linc(&ecs_rest_stats_error_count);
|
|
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);
|
|
ecs_os_linc(&ecs_rest_world_stats_count);
|
|
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);
|
|
ecs_os_linc(&ecs_rest_pipeline_stats_count);
|
|
return true;
|
|
|
|
} else {
|
|
flecs_reply_error(reply, "bad request (unsupported category)");
|
|
reply->code = 400;
|
|
ecs_os_linc(&ecs_rest_stats_error_count);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#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);
|
|
used += table->data.records.count * ECS_SIZEOF(ecs_record_t*);
|
|
allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t);
|
|
allocated += table->data.records.size * ECS_SIZEOF(ecs_record_t*);
|
|
|
|
int32_t i, storage_count = table->storage_count;
|
|
ecs_type_info_t **ti = table->type_info;
|
|
ecs_vec_t *storages = table->data.columns;
|
|
|
|
for (i = 0; i < storage_count; i ++) {
|
|
used += storages[i].count * ti[i]->size;
|
|
allocated += storages[i].size * ti[i]->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_append(reply, "\"refcount\":%d", table->refcount);
|
|
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(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;
|
|
|
|
ecs_os_linc(&ecs_rest_request_count);
|
|
|
|
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, impl, 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);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t);
|
|
ecs_http_server_t *srv = ecs_http_server_init(&(ecs_http_server_desc_t){
|
|
.ipaddr = rest[i].ipaddr,
|
|
.port = rest[i].port,
|
|
.callback = flecs_rest_reply,
|
|
.ctx = srv_ctx
|
|
});
|
|
|
|
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);
|
|
ecs_os_free(srv_ctx);
|
|
continue;
|
|
}
|
|
|
|
srv_ctx->world = it->world;
|
|
srv_ctx->entity = it->entities[i];
|
|
srv_ctx->srv = srv;
|
|
srv_ctx->rc = 1;
|
|
|
|
rest[i].impl = srv_ctx;
|
|
|
|
ecs_http_server_start(srv_ctx->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) {
|
|
ctx->time += it->delta_time;
|
|
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_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_observer(world, {
|
|
.filter = {
|
|
.terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }}
|
|
},
|
|
.events = {EcsOnAdd, EcsOnRemove},
|
|
.callback = DisableRest
|
|
});
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file addons/coredoc.c
|
|
* @brief Core doc addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_COREDOC
|
|
|
|
#define URL_ROOT "https://www.flecs.dev/flecs/md_docs_Relationships.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 = (char*)"size", .type = ecs_id(ecs_i32_t)},
|
|
{.name = (char*)"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, "Flecs module with builtin components");
|
|
|
|
ecs_doc_set_brief(world, EcsWorld, "Entity associated with world");
|
|
|
|
ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all 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, ecs_id(EcsIdentifier), "Component used for entity names");
|
|
ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name");
|
|
ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol");
|
|
|
|
ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property");
|
|
ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property");
|
|
ecs_doc_set_brief(world, EcsFinal, "Final relationship property");
|
|
ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property");
|
|
ecs_doc_set_brief(world, EcsTag, "Tag relationship property");
|
|
ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property");
|
|
ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property");
|
|
ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property");
|
|
ecs_doc_set_brief(world, EcsWith, "With relationship property");
|
|
ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property");
|
|
ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property");
|
|
ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity");
|
|
ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property");
|
|
ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property");
|
|
ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property");
|
|
ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship");
|
|
ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship");
|
|
ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship");
|
|
ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event");
|
|
ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event");
|
|
ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event");
|
|
ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event");
|
|
|
|
ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property");
|
|
ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property");
|
|
ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property");
|
|
ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property");
|
|
ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property");
|
|
ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property");
|
|
ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property");
|
|
ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property");
|
|
ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property");
|
|
ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties");
|
|
ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties");
|
|
ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties");
|
|
ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties");
|
|
ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties");
|
|
ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship");
|
|
ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship");
|
|
|
|
/* 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/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
|
|
|
|
#if defined(ECS_TARGET_WINDOWS)
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#pragma comment(lib, "Ws2_32.lib")
|
|
#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>
|
|
typedef int ecs_http_socket_t;
|
|
#endif
|
|
|
|
#if !defined(MSG_NOSIGNAL)
|
|
#define MSG_NOSIGNAL (0)
|
|
#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)
|
|
|
|
/* 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)
|
|
|
|
/* Max length of headers in reply */
|
|
#define ECS_HTTP_REPLY_HEADER_SIZE (1024)
|
|
|
|
/* 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 cur;
|
|
int32_t count;
|
|
ecs_os_thread_t thread;
|
|
int32_t wait_ms;
|
|
} ecs_http_send_queue_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_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;
|
|
};
|
|
|
|
/** 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 */
|
|
void *res;
|
|
} 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
|
|
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);
|
|
return getnameinfo(addr, (uint32_t)addr_len, host, (uint32_t)host_len,
|
|
port, (uint32_t)port_len, flags);
|
|
}
|
|
|
|
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);
|
|
return bind(sock, addr, (uint32_t)addr_len);
|
|
}
|
|
|
|
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_free(ecs_http_reply_t* response) {
|
|
ecs_assert(response != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_os_free(response->body.content);
|
|
}
|
|
|
|
static
|
|
void http_request_free(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->server->requests != 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(req->pub.conn->server->requests, 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(conn->pub.server->connections, 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
|
|
void 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 {
|
|
char *res = ecs_strbuf_get(&frag->buf);
|
|
if (res) {
|
|
ecs_http_request_impl_t *req = flecs_sparse_add(
|
|
srv->requests, ecs_http_request_impl_t);
|
|
req->pub.id = flecs_sparse_last_id(srv->requests);
|
|
req->conn_id = conn->pub.id;
|
|
|
|
req->pub.conn = (ecs_http_connection_t*)conn;
|
|
req->pub.method = frag->method;
|
|
req->pub.path = res + 1;
|
|
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]];
|
|
http_decode_url_str((char*)req->pub.params[i].value);
|
|
}
|
|
|
|
req->pub.header_count = frag->header_count;
|
|
req->pub.param_count = frag->param_count;
|
|
req->res = res;
|
|
|
|
ecs_os_linc(&ecs_http_request_received_count);
|
|
}
|
|
}
|
|
|
|
ecs_os_mutex_unlock(srv->lock);
|
|
}
|
|
|
|
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->buf.max = ECS_HTTP_METHOD_LEN_MAX;
|
|
frag->state = HttpFragStateMethod;
|
|
frag->header_buf_ptr = frag->header_buf;
|
|
/* fallthrough */
|
|
case HttpFragStateMethod:
|
|
if (c == ' ') {
|
|
http_parse_method(frag);
|
|
frag->state = HttpFragStatePath;
|
|
frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX;
|
|
} 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;
|
|
/* fallthrough */
|
|
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;
|
|
}
|
|
/* fallthrough */
|
|
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;
|
|
ecs_assert(sq->count <= ECS_HTTP_SEND_QUEUE_MAX, ECS_INTERNAL_ERROR, NULL);
|
|
if (sq->count == ECS_HTTP_SEND_QUEUE_MAX) {
|
|
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 */
|
|
return &sq->requests[(sq->cur + sq->count ++) % ECS_HTTP_SEND_QUEUE_MAX];
|
|
}
|
|
|
|
static
|
|
ecs_http_send_request_t* http_send_queue_get(
|
|
ecs_http_server_t *srv)
|
|
{
|
|
ecs_http_send_request_t *result = NULL;
|
|
ecs_os_mutex_lock(srv->lock);
|
|
ecs_http_send_queue_t *sq = &srv->send_queue;
|
|
if (sq->count) {
|
|
result = &sq->requests[sq->cur];
|
|
sq->cur = (sq->cur + 1) % ECS_HTTP_SEND_QUEUE_MAX;
|
|
sq->count --;
|
|
}
|
|
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.count) {
|
|
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);
|
|
}
|
|
} 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;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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_appendlit(hdrs, "Server: flecs\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;
|
|
char *content = ecs_strbuf_get(&reply->body);
|
|
int32_t content_length = reply->body.length - 1;
|
|
|
|
/* 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);
|
|
char *headers = ecs_strbuf_get(&hdrs);
|
|
ecs_size_t headers_length = ecs_strbuf_written(&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_request(
|
|
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};
|
|
|
|
while ((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 */
|
|
return;
|
|
}
|
|
|
|
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 {
|
|
http_enqueue_request(conn, conn_id, &frag);
|
|
}
|
|
return;
|
|
} else {
|
|
ecs_os_linc(&ecs_http_request_invalid_count);
|
|
}
|
|
}
|
|
|
|
/* Partial request received, cleanup resources */
|
|
ecs_strbuf_reset(&frag.buf);
|
|
|
|
/* No request was enqueued, flag connection so it'll get purged */
|
|
ecs_os_mutex_lock(srv->lock);
|
|
if (conn->pub.id == conn_id) {
|
|
/* Only flag connection if it was still alive */
|
|
conn->dequeue_timeout = ECS_HTTP_CONNECTION_PURGE_TIMEOUT;
|
|
conn->dequeue_retries = ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT;
|
|
}
|
|
ecs_os_mutex_unlock(srv->lock);
|
|
}
|
|
|
|
static
|
|
void 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);
|
|
|
|
/* Create new connection */
|
|
ecs_os_mutex_lock(srv->lock);
|
|
ecs_http_connection_impl_t *conn = flecs_sparse_add(
|
|
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);
|
|
|
|
http_recv_request(srv, conn, conn_id, sock_conn);
|
|
|
|
ecs_dbg_2("http: request received from '%s:%s'",
|
|
remote_host, remote_port);
|
|
}
|
|
|
|
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 (SOCKET_ERROR == 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;
|
|
int 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);
|
|
|
|
ecs_http_socket_t sock_conn;
|
|
struct sockaddr_storage remote_addr;
|
|
ecs_size_t remote_addr_len;
|
|
|
|
while (srv->should_run) {
|
|
remote_addr_len = ECS_SIZEOF(remote_addr);
|
|
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_init_connection(srv, sock_conn, &remote_addr, remote_addr_len);
|
|
}
|
|
|
|
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_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);
|
|
}
|
|
}
|
|
|
|
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_free(&reply);
|
|
http_request_free(req);
|
|
http_connection_free(conn);
|
|
}
|
|
|
|
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(
|
|
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(
|
|
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);
|
|
}
|
|
}
|
|
|
|
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->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 = 100 * 1000 * 1000;
|
|
}
|
|
|
|
srv->connections = flecs_sparse_new(NULL, NULL, ecs_http_connection_impl_t);
|
|
srv->requests = flecs_sparse_new(NULL, NULL, ecs_http_request_impl_t);
|
|
|
|
/* Start at id 1 */
|
|
flecs_sparse_new_id(srv->connections);
|
|
flecs_sparse_new_id(srv->requests);
|
|
|
|
#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);
|
|
flecs_sparse_free(srv->connections);
|
|
flecs_sparse_free(srv->requests);
|
|
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_free(flecs_sparse_get_dense(
|
|
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(
|
|
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;
|
|
}
|
|
|
|
#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);
|
|
})
|
|
|
|
void ecs_doc_set_name(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
const char *name)
|
|
{
|
|
ecs_set_pair(world, entity, EcsDocDescription, EcsName, {
|
|
.value = (char*)name
|
|
});
|
|
}
|
|
|
|
void ecs_doc_set_brief(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
const char *description)
|
|
{
|
|
ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, {
|
|
.value = (char*)description
|
|
});
|
|
}
|
|
|
|
void ecs_doc_set_detail(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
const char *description)
|
|
{
|
|
ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, {
|
|
.value = (char*)description
|
|
});
|
|
}
|
|
|
|
void ecs_doc_set_link(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
const char *link)
|
|
{
|
|
ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, {
|
|
.value = (char*)link
|
|
});
|
|
}
|
|
|
|
void ecs_doc_set_color(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
const char *color)
|
|
{
|
|
ecs_set_pair(world, entity, EcsDocDescription, EcsDocColor, {
|
|
.value = (char*)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);
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file addons/parser.c
|
|
* @brief Parser addon.
|
|
*/
|
|
|
|
|
|
#ifdef FLECS_PARSER
|
|
|
|
#include <ctype.h>
|
|
|
|
#define ECS_ANNOTATION_LENGTH_MAX (16)
|
|
|
|
#define TOK_NEWLINE '\n'
|
|
#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_WILDCARD '*'
|
|
#define TOK_VARIABLE '$'
|
|
#define TOK_PAREN_OPEN '('
|
|
#define TOK_PAREN_CLOSE ')'
|
|
|
|
#define TOK_SELF "self"
|
|
#define TOK_UP "up"
|
|
#define TOK_DOWN "down"
|
|
#define TOK_CASCADE "cascade"
|
|
#define TOK_PARENT "parent"
|
|
|
|
#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_eol_and_whitespace(
|
|
const char *ptr)
|
|
{
|
|
while (isspace(*ptr)) {
|
|
ptr ++;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/** Skip spaces when parsing signature */
|
|
const char* ecs_parse_whitespace(
|
|
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)) {
|
|
break;
|
|
}
|
|
|
|
tptr[0] = ch;
|
|
tptr ++;
|
|
}
|
|
|
|
tptr[0] = '\0';
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static
|
|
bool flecs_is_newline_comment(
|
|
const char *ptr)
|
|
{
|
|
if (ptr[0] == '/' && ptr[1] == '/') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const char* ecs_parse_fluff(
|
|
const char *ptr,
|
|
char **last_comment)
|
|
{
|
|
const char *last_comment_start = NULL;
|
|
|
|
do {
|
|
/* Skip whitespaces before checking for a comment */
|
|
ptr = ecs_parse_whitespace(ptr);
|
|
|
|
/* Newline comment, skip until newline character */
|
|
if (flecs_is_newline_comment(ptr)) {
|
|
ptr += 2;
|
|
last_comment_start = ptr;
|
|
|
|
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]) || flecs_is_newline_comment(ptr));
|
|
|
|
if (last_comment) {
|
|
*last_comment = (char*)last_comment_start;
|
|
}
|
|
|
|
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_whitespace(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_whitespace(ptr);
|
|
if (next_ptr[0] == ':' && next_ptr != ptr) {
|
|
/* Whitespace between token and : is significant */
|
|
ptr = next_ptr - 1;
|
|
} else {
|
|
ptr = next_ptr;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static
|
|
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])) {
|
|
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 ++;
|
|
}
|
|
|
|
out->name = ecs_os_strdup(tptr);
|
|
|
|
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_abort(ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
}
|
|
|
|
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_whitespace(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_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_whitespace(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_whitespace(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)
|
|
{
|
|
(void)column;
|
|
|
|
int32_t arg = 0;
|
|
|
|
do {
|
|
if (flecs_valid_token_start_char(ptr[0])) {
|
|
if (arg == 2) {
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"too many arguments in term");
|
|
return NULL;
|
|
}
|
|
|
|
ptr = ecs_parse_identifier(name, expr, ptr, token);
|
|
if (!ptr) {
|
|
return NULL;
|
|
}
|
|
|
|
ecs_term_id_t *term_id = NULL;
|
|
|
|
if (arg == 0) {
|
|
term_id = &term->src;
|
|
} else if (arg == 1) {
|
|
term_id = &term->second;
|
|
}
|
|
|
|
/* 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);
|
|
return NULL;
|
|
}
|
|
|
|
ptr = ecs_parse_whitespace(ptr + 1);
|
|
ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr,
|
|
NULL, term_id, TOK_PAREN_CLOSE);
|
|
if (!ptr) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Check for term flags */
|
|
} else if (!ecs_os_strcmp(token, TOK_CASCADE) ||
|
|
!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) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Regular identifier */
|
|
} else if (flecs_parse_identifier(token, term_id)) {
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"invalid identifier '%s'", token);
|
|
return NULL;
|
|
}
|
|
|
|
if (ptr[0] == TOK_AND) {
|
|
ptr = ecs_parse_whitespace(ptr + 1);
|
|
|
|
term->id_flags = ECS_PAIR;
|
|
|
|
} else if (ptr[0] == TOK_PAREN_CLOSE) {
|
|
ptr = ecs_parse_whitespace(ptr + 1);
|
|
break;
|
|
|
|
} else {
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"expected ',' or ')'");
|
|
return NULL;
|
|
}
|
|
|
|
} else {
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"expected identifier or set expression");
|
|
return NULL;
|
|
}
|
|
|
|
arg ++;
|
|
|
|
} while (true);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
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)
|
|
{
|
|
const char *ptr = expr;
|
|
char token[ECS_MAX_TOKEN_SIZE] = {0};
|
|
ecs_term_t term = { .move = true /* parser never owns resources */ };
|
|
|
|
ptr = ecs_parse_whitespace(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_whitespace(ptr);
|
|
}
|
|
|
|
if (flecs_valid_operator_char(ptr[0])) {
|
|
term.oper = flecs_parse_operator(ptr[0]);
|
|
ptr = ecs_parse_whitespace(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_token_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;
|
|
|
|
/* 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_whitespace(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_whitespace(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_whitespace(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_whitespace(ptr + 1);
|
|
} else {
|
|
ptr = ecs_parse_whitespace(ptr);
|
|
}
|
|
|
|
if (ptr[0] == TOK_PAREN_OPEN) {
|
|
ptr ++;
|
|
if (ptr[0] == TOK_PAREN_CLOSE) {
|
|
term.src.flags = EcsIsEntity;
|
|
term.src.id = 0;
|
|
ptr ++;
|
|
ptr = ecs_parse_whitespace(ptr);
|
|
} else {
|
|
ptr = flecs_parse_arguments(
|
|
world, name, expr, (ptr - expr), ptr, token, &term);
|
|
}
|
|
|
|
goto parse_done;
|
|
}
|
|
|
|
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_whitespace(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 ++;
|
|
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_whitespace(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_whitespace(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 ++;
|
|
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) {
|
|
if (!ECS_HAS_ID_FLAG(term.id_flags, PAIR)) {
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"invalid combination of role '%s' with pair",
|
|
ecs_id_flag_str(term.id_flags));
|
|
goto error;
|
|
}
|
|
} else {
|
|
term.id_flags = ECS_PAIR;
|
|
}
|
|
|
|
ptr = ecs_parse_whitespace(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] == ':') || /* 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_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_term_id_t *src = &term->src;
|
|
|
|
bool prev_or = false;
|
|
if (ptr != expr) {
|
|
if (ptr[0]) {
|
|
if (ptr[0] == ',') {
|
|
ptr ++;
|
|
} else if (ptr[0] == '|') {
|
|
ptr += 2;
|
|
prev_or = true;
|
|
} else {
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"invalid preceding token");
|
|
}
|
|
}
|
|
}
|
|
|
|
ptr = ecs_parse_eol_and_whitespace(ptr);
|
|
if (!ptr[0]) {
|
|
*term = (ecs_term_t){0};
|
|
return (char*)ptr;
|
|
}
|
|
|
|
if (ptr == expr && !strcmp(expr, "0")) {
|
|
return (char*)&ptr[1];
|
|
}
|
|
|
|
/* Parse next element */
|
|
ptr = flecs_parse_term(world, name, ptr, term);
|
|
if (!ptr) {
|
|
goto error;
|
|
}
|
|
|
|
/* Check for $() notation */
|
|
if (!ecs_os_strcmp(term->first.name, "$")) {
|
|
if (term->src.name) {
|
|
ecs_os_free(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;
|
|
}
|
|
|
|
term->second.name = ecs_os_strdup(term->first.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) || prev_or) {
|
|
/* 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 (!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;
|
|
ecs_os_free(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)) {
|
|
ecs_parser_error(name, expr, (ptr - expr),
|
|
"invalid operator for empty source");
|
|
goto error;
|
|
}
|
|
|
|
/* Verify consistency of OR expression */
|
|
if (prev_or && term->oper == EcsOr) {
|
|
term->oper = EcsOr;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
ptr = ecs_parse_whitespace(ptr);
|
|
|
|
return (char*)ptr;
|
|
error:
|
|
if (term) {
|
|
ecs_term_fini(term);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @file addons/meta.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_eol_and_whitespace(ptr);
|
|
ptr = ecs_parse_digit(ptr, token);
|
|
if (!ptr) {
|
|
goto error;
|
|
}
|
|
|
|
*value_out = strtol(token, NULL, 0);
|
|
|
|
return ecs_parse_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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;
|
|
}
|
|
|
|
/* 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_eol_and_whitespace(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 == '[') {
|
|
array_start = (char*)ptr; /* safe, will not be modified */
|
|
}
|
|
}
|
|
|
|
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_eol_and_whitespace(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_eol_and_whitespace(ptr);
|
|
|
|
/* If next token is a ',' the first type was a key type */
|
|
if (*ptr == ',') {
|
|
ptr = ecs_parse_eol_and_whitespace(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_eol_and_whitespace(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);
|
|
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, "char*")) {
|
|
type = ecs_id(ecs_string_t);
|
|
} else {
|
|
type = ecs_lookup_symbol(world, typename, 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);
|
|
}
|
|
|
|
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_init(world, &(ecs_entity_desc_t){
|
|
.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);
|
|
|
|
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;
|
|
}
|
|
|
|
ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){
|
|
.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;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
#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) { }
|
|
}
|
|
|
|
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;
|
|
|
|
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_app_desc.target_fps != 0) {
|
|
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
|
|
ecs_set(world, EcsWorld, EcsRest, {.port = 0});
|
|
#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) {
|
|
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) {
|
|
ecs_err("frame action already set");
|
|
return -1;
|
|
}
|
|
|
|
frame_action = callback;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @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;
|
|
|
|
const ecs_entity_t EcsQuery = 5;
|
|
const ecs_entity_t EcsObserver = 7;
|
|
|
|
/* System module component ids */
|
|
const ecs_entity_t EcsSystem = 10;
|
|
const ecs_entity_t ecs_id(EcsTickSource) = 11;
|
|
|
|
/** Timer module component ids */
|
|
const ecs_entity_t ecs_id(EcsTimer) = 13;
|
|
const ecs_entity_t ecs_id(EcsRateFilter) = 14;
|
|
|
|
/** Meta module component ids */
|
|
const ecs_entity_t ecs_id(EcsMetaType) = 15;
|
|
const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = 16;
|
|
const ecs_entity_t ecs_id(EcsPrimitive) = 17;
|
|
const ecs_entity_t ecs_id(EcsEnum) = 18;
|
|
const ecs_entity_t ecs_id(EcsBitmask) = 19;
|
|
const ecs_entity_t ecs_id(EcsMember) = 20;
|
|
const ecs_entity_t ecs_id(EcsStruct) = 21;
|
|
const ecs_entity_t ecs_id(EcsArray) = 22;
|
|
const ecs_entity_t ecs_id(EcsVector) = 23;
|
|
const ecs_entity_t ecs_id(EcsUnit) = 24;
|
|
const ecs_entity_t ecs_id(EcsUnitPrefix) = 25;
|
|
|
|
/* Core scopes & entities */
|
|
const ecs_entity_t EcsWorld = ECS_HI_COMPONENT_ID + 0;
|
|
const ecs_entity_t EcsFlecs = ECS_HI_COMPONENT_ID + 1;
|
|
const ecs_entity_t EcsFlecsCore = ECS_HI_COMPONENT_ID + 2;
|
|
const ecs_entity_t EcsFlecsInternals = ECS_HI_COMPONENT_ID + 3;
|
|
const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 4;
|
|
const ecs_entity_t EcsPrivate = ECS_HI_COMPONENT_ID + 5;
|
|
const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 6;
|
|
const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 7;
|
|
|
|
const ecs_entity_t EcsSlotOf = ECS_HI_COMPONENT_ID + 8;
|
|
const ecs_entity_t EcsFlag = ECS_HI_COMPONENT_ID + 9;
|
|
|
|
/* Relationship properties */
|
|
const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10;
|
|
const ecs_entity_t EcsAny = ECS_HI_COMPONENT_ID + 11;
|
|
const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 12;
|
|
const ecs_entity_t EcsVariable = ECS_HI_COMPONENT_ID + 13;
|
|
|
|
const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 14;
|
|
const ecs_entity_t EcsReflexive = ECS_HI_COMPONENT_ID + 15;
|
|
const ecs_entity_t EcsSymmetric = ECS_HI_COMPONENT_ID + 16;
|
|
const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 17;
|
|
const ecs_entity_t EcsDontInherit = ECS_HI_COMPONENT_ID + 18;
|
|
const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 19;
|
|
const ecs_entity_t EcsUnion = ECS_HI_COMPONENT_ID + 20;
|
|
const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 21;
|
|
const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 22;
|
|
const ecs_entity_t EcsWith = ECS_HI_COMPONENT_ID + 23;
|
|
const ecs_entity_t EcsOneOf = ECS_HI_COMPONENT_ID + 24;
|
|
|
|
/* Builtin relationships */
|
|
const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 25;
|
|
const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 26;
|
|
const ecs_entity_t EcsDependsOn = ECS_HI_COMPONENT_ID + 27;
|
|
|
|
/* Identifier tags */
|
|
const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 30;
|
|
const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 31;
|
|
const ecs_entity_t EcsAlias = ECS_HI_COMPONENT_ID + 32;
|
|
|
|
/* Events */
|
|
const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 33;
|
|
const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 34;
|
|
const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 35;
|
|
const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 36;
|
|
const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 37;
|
|
const ecs_entity_t EcsOnCreateTable = ECS_HI_COMPONENT_ID + 38;
|
|
const ecs_entity_t EcsOnDeleteTable = ECS_HI_COMPONENT_ID + 39;
|
|
const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 40;
|
|
const ecs_entity_t EcsOnTableFill = ECS_HI_COMPONENT_ID + 41;
|
|
const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 42;
|
|
const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 43;
|
|
const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 44;
|
|
const ecs_entity_t EcsOnComponentHooks = ECS_HI_COMPONENT_ID + 45;
|
|
const ecs_entity_t EcsOnDeleteTarget = ECS_HI_COMPONENT_ID + 46;
|
|
|
|
/* Actions */
|
|
const ecs_entity_t EcsRemove = ECS_HI_COMPONENT_ID + 50;
|
|
const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51;
|
|
const ecs_entity_t EcsPanic = ECS_HI_COMPONENT_ID + 52;
|
|
|
|
/* Misc */
|
|
const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55;
|
|
|
|
/* Systems */
|
|
const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61;
|
|
const ecs_entity_t EcsEmpty = ECS_HI_COMPONENT_ID + 62;
|
|
const ecs_entity_t ecs_id(EcsPipeline) = ECS_HI_COMPONENT_ID + 63;
|
|
const ecs_entity_t EcsOnStart = ECS_HI_COMPONENT_ID + 64;
|
|
const ecs_entity_t EcsPreFrame = ECS_HI_COMPONENT_ID + 65;
|
|
const ecs_entity_t EcsOnLoad = ECS_HI_COMPONENT_ID + 66;
|
|
const ecs_entity_t EcsPostLoad = ECS_HI_COMPONENT_ID + 67;
|
|
const ecs_entity_t EcsPreUpdate = ECS_HI_COMPONENT_ID + 68;
|
|
const ecs_entity_t EcsOnUpdate = ECS_HI_COMPONENT_ID + 69;
|
|
const ecs_entity_t EcsOnValidate = ECS_HI_COMPONENT_ID + 70;
|
|
const ecs_entity_t EcsPostUpdate = ECS_HI_COMPONENT_ID + 71;
|
|
const ecs_entity_t EcsPreStore = ECS_HI_COMPONENT_ID + 72;
|
|
const ecs_entity_t EcsOnStore = ECS_HI_COMPONENT_ID + 73;
|
|
const ecs_entity_t EcsPostFrame = ECS_HI_COMPONENT_ID + 74;
|
|
|
|
const ecs_entity_t EcsPhase = ECS_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) = ECS_HI_COMPONENT_ID + 80;
|
|
const ecs_entity_t ecs_id(ecs_char_t) = ECS_HI_COMPONENT_ID + 81;
|
|
const ecs_entity_t ecs_id(ecs_byte_t) = ECS_HI_COMPONENT_ID + 82;
|
|
const ecs_entity_t ecs_id(ecs_u8_t) = ECS_HI_COMPONENT_ID + 83;
|
|
const ecs_entity_t ecs_id(ecs_u16_t) = ECS_HI_COMPONENT_ID + 84;
|
|
const ecs_entity_t ecs_id(ecs_u32_t) = ECS_HI_COMPONENT_ID + 85;
|
|
const ecs_entity_t ecs_id(ecs_u64_t) = ECS_HI_COMPONENT_ID + 86;
|
|
const ecs_entity_t ecs_id(ecs_uptr_t) = ECS_HI_COMPONENT_ID + 87;
|
|
const ecs_entity_t ecs_id(ecs_i8_t) = ECS_HI_COMPONENT_ID + 88;
|
|
const ecs_entity_t ecs_id(ecs_i16_t) = ECS_HI_COMPONENT_ID + 89;
|
|
const ecs_entity_t ecs_id(ecs_i32_t) = ECS_HI_COMPONENT_ID + 90;
|
|
const ecs_entity_t ecs_id(ecs_i64_t) = ECS_HI_COMPONENT_ID + 91;
|
|
const ecs_entity_t ecs_id(ecs_iptr_t) = ECS_HI_COMPONENT_ID + 92;
|
|
const ecs_entity_t ecs_id(ecs_f32_t) = ECS_HI_COMPONENT_ID + 93;
|
|
const ecs_entity_t ecs_id(ecs_f64_t) = ECS_HI_COMPONENT_ID + 94;
|
|
const ecs_entity_t ecs_id(ecs_string_t) = ECS_HI_COMPONENT_ID + 95;
|
|
const ecs_entity_t ecs_id(ecs_entity_t) = ECS_HI_COMPONENT_ID + 96;
|
|
const ecs_entity_t EcsConstant = ECS_HI_COMPONENT_ID + 97;
|
|
const ecs_entity_t EcsQuantity = ECS_HI_COMPONENT_ID + 98;
|
|
|
|
/* Doc module components */
|
|
const ecs_entity_t ecs_id(EcsDocDescription) =ECS_HI_COMPONENT_ID + 100;
|
|
const ecs_entity_t EcsDocBrief = ECS_HI_COMPONENT_ID + 101;
|
|
const ecs_entity_t EcsDocDetail = ECS_HI_COMPONENT_ID + 102;
|
|
const ecs_entity_t EcsDocLink = ECS_HI_COMPONENT_ID + 103;
|
|
const ecs_entity_t EcsDocColor = ECS_HI_COMPONENT_ID + 104;
|
|
|
|
/* REST module components */
|
|
const ecs_entity_t ecs_id(EcsRest) = ECS_HI_COMPONENT_ID + 105;
|
|
|
|
/* Default lookup path */
|
|
static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 };
|
|
|
|
/* -- 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_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)) {
|
|
ecs_assert(!(world->flags & EcsWorldMultiThreaded),
|
|
ECS_INVALID_OPERATION, NULL);
|
|
return &world->stages[0];
|
|
|
|
} else if (ecs_poly_is(world, ecs_stage_t)) {
|
|
ecs_stage_t *stage = (ecs_stage_t*)world;
|
|
*world_ptr = stage->world;
|
|
return stage;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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_world_t*)ecs_get_world(stage_world);
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
|
|
bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly);
|
|
bool is_deferred = ecs_is_deferred(world);
|
|
|
|
if (!is_readonly && !is_deferred) {
|
|
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 = is_deferred;
|
|
|
|
/* Silence readonly checks */
|
|
world->flags &= ~EcsWorldReadonly;
|
|
|
|
/* Hack around safety checks (this ought to look ugly) */
|
|
ecs_world_t *temp_world = world;
|
|
ecs_stage_t *stage = flecs_stage_from_world(&temp_world);
|
|
state->defer_count = stage->defer;
|
|
state->commands = stage->commands;
|
|
state->defer_stack = stage->defer_stack;
|
|
flecs_stack_init(&stage->defer_stack);
|
|
state->scope = stage->scope;
|
|
state->with = stage->with;
|
|
stage->defer = 0;
|
|
ecs_vec_init_t(NULL, &stage->commands, 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->commands, ecs_cmd_t);
|
|
stage->commands = state->commands;
|
|
flecs_stack_fini(&stage->defer_stack);
|
|
stage->defer_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);
|
|
ecs_monitor_t *m;
|
|
while ((m = ecs_map_next(&it, ecs_monitor_t, NULL))) {
|
|
if (!m->is_dirty) {
|
|
continue;
|
|
}
|
|
|
|
m->is_dirty = false;
|
|
|
|
ecs_vector_each(m->queries, ecs_query_t*, q_ptr, {
|
|
flecs_query_notify(world, *q_ptr, &(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_initialized(monitors)) {
|
|
ecs_monitor_t *m = ecs_map_get(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, ecs_monitor_t, &world->allocator, 1);
|
|
|
|
ecs_monitor_t *m = ecs_map_ensure(monitors, ecs_monitor_t, id);
|
|
ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_query_t **q = ecs_vector_add(&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_initialized(monitors)) {
|
|
return;
|
|
}
|
|
|
|
ecs_monitor_t *m = ecs_map_get(monitors, ecs_monitor_t, id);
|
|
if (!m) {
|
|
return;
|
|
}
|
|
|
|
int32_t i, count = ecs_vector_count(m->queries);
|
|
ecs_query_t **queries = ecs_vector_first(m->queries, ecs_query_t*);
|
|
for (i = 0; i < count; i ++) {
|
|
if (queries[i] == query) {
|
|
ecs_vector_remove(m->queries, ecs_query_t*, i);
|
|
count --;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!count) {
|
|
ecs_vector_free(m->queries);
|
|
ecs_map_remove(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));
|
|
|
|
/* Initialize entity index */
|
|
flecs_sparse_init(&world->store.entity_index,
|
|
&world->allocator, &world->allocators.sparse_chunk,
|
|
ecs_record_t);
|
|
flecs_sparse_set_id_source(&world->store.entity_index,
|
|
&world->info.last_id);
|
|
|
|
/* Initialize root table */
|
|
flecs_sparse_init(&world->store.tables,
|
|
&world->allocator, &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(&world->store.tables,
|
|
ecs_table_t, 0);
|
|
ecs_assert(first->id == 0, ECS_INTERNAL_ERROR, NULL);
|
|
(void)first;
|
|
|
|
for (i = 1; i < count; i ++) {
|
|
ecs_table_t *t = flecs_sparse_get_dense(&world->store.tables,
|
|
ecs_table_t, i);
|
|
flecs_table_release(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(&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_roots(ecs_world_t *world) {
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0));
|
|
|
|
ecs_run_aperiodic(world, EcsAperiodicEmptyTables);
|
|
|
|
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;
|
|
|
|
/* 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]);
|
|
|
|
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;
|
|
|
|
/* Count backwards so that we're always deleting the last entity in the
|
|
* table which reduces moving components around */
|
|
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);
|
|
|
|
ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(r->row);
|
|
if (!(flags & EcsEntityObservedTarget)) {
|
|
continue; /* Filter out entities that aren't objects */
|
|
}
|
|
|
|
ecs_delete(world, entities[i]);
|
|
}
|
|
}
|
|
|
|
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_release(world, &world->store.root);
|
|
flecs_sparse_clear(&world->store.entity_index);
|
|
flecs_hashmap_fini(&world->store.table_map);
|
|
ecs_vector_free(world->store.records);
|
|
ecs_vector_free(world->store.marked_ids);
|
|
}
|
|
|
|
/* 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;
|
|
ecs_sparse_t *entity_index = &world->store.entity_index;
|
|
it->entities = (ecs_entity_t*)flecs_sparse_ids(entity_index);
|
|
it->count = flecs_sparse_count(entity_index);
|
|
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_world_t*)world,
|
|
.real_world = (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, void*);
|
|
ecs_map_params_init(&a->query_table_list, &world->allocator,
|
|
ecs_query_table_list_t);
|
|
|
|
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, ECS_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_CHUNK_SIZE);
|
|
flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t);
|
|
flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_CHUNK_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_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_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);
|
|
#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);
|
|
|
|
flecs_world_allocators_init(world);
|
|
|
|
world->self = world;
|
|
flecs_sparse_init(&world->type_info, &world->allocator,
|
|
&world->allocators.sparse_chunk, ecs_type_info_t);
|
|
ecs_map_init_w_params(&world->id_index_hi, &world->allocators.ptr);
|
|
flecs_sparse_init(&world->id_index_lo, NULL,
|
|
&world->allocators.id_record_chunk, ecs_id_record_t);
|
|
flecs_observable_init(&world->observable);
|
|
world->iterable.init = flecs_world_iter_init;
|
|
world->pending_tables = flecs_sparse_new(
|
|
&world->allocator, &world->allocators.sparse_chunk,
|
|
ecs_table_t*);
|
|
world->pending_buffer = flecs_sparse_new(
|
|
&world->allocator, &world->allocators.sparse_chunk,
|
|
ecs_table_t*);
|
|
flecs_name_index_init(&world->aliases, &world->allocator);
|
|
flecs_name_index_init(&world->symbols, &world->allocator);
|
|
|
|
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);
|
|
|
|
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_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;
|
|
}
|
|
|
|
#define ARG(short, long, action)\
|
|
if (i < argc) {\
|
|
if (argv[i][0] == '-') {\
|
|
if (argv[i][1] == '-') {\
|
|
if (long && !strcmp(&argv[i][2], long ? long : "")) {\
|
|
action;\
|
|
parsed = true;\
|
|
}\
|
|
} else {\
|
|
if (short && argv[i][1] == short) {\
|
|
action;\
|
|
parsed = true;\
|
|
}\
|
|
}\
|
|
}\
|
|
}
|
|
|
|
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(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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_vector_add(&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_vector_add(&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(tables, ecs_table_t, i);
|
|
flecs_table_remove_actions(world, table);
|
|
}
|
|
}
|
|
|
|
/* Invoke fini actions */
|
|
static
|
|
void flecs_fini_actions(
|
|
ecs_world_t *world)
|
|
{
|
|
ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, {
|
|
elem->action(world, elem->ctx);
|
|
});
|
|
|
|
ecs_vector_free(world->fini_actions);
|
|
}
|
|
|
|
/* 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(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_has_id(world, e, EcsOneOf)) {
|
|
return e;
|
|
} else {
|
|
return ecs_get_target(world, e, EcsOneOf, 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. After this point no more
|
|
* user code is executed. */
|
|
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_initialized(&world->monitors.monitors),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_dbg_1("#[bold]cleanup world datastructures");
|
|
ecs_log_push_1();
|
|
flecs_sparse_fini(&world->store.entity_index);
|
|
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 + ECS_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 (world->info.target_fps == (ecs_ftime_t)0 || 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_context(
|
|
const ecs_world_t *world)
|
|
{
|
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
world = ecs_get_world(world);
|
|
return world->context;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
void ecs_set_context(
|
|
ecs_world_t *world,
|
|
void *context)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
world->context = context;
|
|
}
|
|
|
|
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 > world->info.last_id,
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
|
|
if (world->info.last_id < id_start) {
|
|
world->info.last_id = id_start - 1;
|
|
}
|
|
|
|
world->info.min_id = id_start;
|
|
world->info.max_id = 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;
|
|
}
|
|
|
|
void ecs_set_entity_generation(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity_with_generation)
|
|
{
|
|
flecs_sparse_set_generation(
|
|
&world->store.entity_index, 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_get(&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(
|
|
&world->type_info, ecs_type_info_t, component);
|
|
ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ti_mut->component = component;
|
|
const char *sym = ecs_get_symbol(world, component);
|
|
if (sym) {
|
|
ti_mut->name = ecs_os_strdup(sym);
|
|
}
|
|
} else {
|
|
ti_mut = (ecs_type_info_t*)ti;
|
|
}
|
|
|
|
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(&world->type_info, 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((char*)ti->name);
|
|
ti->name = NULL;
|
|
}
|
|
}
|
|
|
|
void flecs_type_info_free(
|
|
ecs_world_t *world,
|
|
ecs_entity_t component)
|
|
{
|
|
if (world->flags & EcsWorldFini) {
|
|
/* 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_remove_get(
|
|
&world->type_info, ecs_type_info_t, component);
|
|
if (ti) {
|
|
flecs_type_info_fini(ti);
|
|
}
|
|
}
|
|
|
|
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 (world->info.target_fps == (ecs_ftime_t)0.0) {
|
|
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 (sleep_time != 0) {
|
|
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) || (user_delta_time == 0)) {
|
|
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 (world->info.target_fps != 0) {
|
|
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 (delta_time == 0);
|
|
|
|
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(user_delta_time != 0 || 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 (user_delta_time == 0) {
|
|
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_notify_queries(
|
|
ecs_world_t *world,
|
|
ecs_query_event_t *event)
|
|
{
|
|
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_table_cache_iter_t it;
|
|
const ecs_table_record_t *tr;
|
|
if (flecs_table_cache_all_iter(&idr->cache, &it)) {
|
|
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);
|
|
if (!count) {
|
|
continue;
|
|
}
|
|
|
|
EcsPoly *queries = ecs_table_get_column(table, tr->column, 0);
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_query_t *query = queries[i].poly;
|
|
if (!ecs_poly_is(query, ecs_query_t)) {
|
|
/* EcsQuery can also contain filters or rules */
|
|
continue;
|
|
}
|
|
|
|
if (query->flags & EcsQueryIsSubquery) {
|
|
continue;
|
|
}
|
|
|
|
ecs_poly_assert(query, ecs_query_t);
|
|
flecs_query_notify(world, query, event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void flecs_delete_table(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
flecs_table_release(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_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 datastructures 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(
|
|
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)) {
|
|
/* Only emit an event when there was a change in the
|
|
* administration. It is possible that a table ended up in the
|
|
* pending_tables list by going from empty->non-empty, but then
|
|
* became empty again. By the time we run this code, no changes
|
|
* in the administration would actually be made. */
|
|
int32_t table_count = ecs_table_count(table);
|
|
ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty;
|
|
if (ecs_should_log_3()) {
|
|
ecs_dbg_3("table %u state change (%s)", (uint32_t)table->id,
|
|
table_count ? "non-empty" : "empty");
|
|
}
|
|
|
|
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(world->pending_tables, ecs_table_t*,
|
|
(uint32_t)table->id)[0] = table;
|
|
}
|
|
|
|
bool ecs_id_in_use(
|
|
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;
|
|
|
|
ecs_time_measure(&start);
|
|
if (time_budget_seconds != 0) {
|
|
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->refcount > 1) {
|
|
/* Don't delete claimed tables */
|
|
continue;
|
|
}
|
|
|
|
if (table->type.count < min_id_count) {
|
|
continue;
|
|
}
|
|
|
|
uint16_t gen = ++ table->generation;
|
|
if (delete_generation && (gen > delete_generation)) {
|
|
if (flecs_table_release(world, table)) {
|
|
delete_count ++;
|
|
}
|
|
} else if (clear_generation && (gen > clear_generation)) {
|
|
if (flecs_table_shrink(world, table)) {
|
|
clear_count ++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
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 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)
|
|
{
|
|
observable->events = ecs_sparse_new(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_sparse_t *triggers = observable->events;
|
|
|
|
ecs_assert(!ecs_map_is_initialized(&observable->on_add.event_ids),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(!ecs_map_is_initialized(&observable->on_remove.event_ids),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(!ecs_map_is_initialized(&observable->on_set.event_ids),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(!ecs_map_is_initialized(&observable->un_set.event_ids),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t i, count = flecs_sparse_count(triggers);
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_event_record_t *er =
|
|
ecs_sparse_get_dense(triggers, 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_initialized(&er->event_ids),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
flecs_sparse_free(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_event_record_t*)&o->on_add;
|
|
else if (event == EcsOnRemove) return (ecs_event_record_t*)&o->on_remove;
|
|
else if (event == EcsOnSet) return (ecs_event_record_t*)&o->on_set;
|
|
else if (event == EcsUnSet) return (ecs_event_record_t*)&o->un_set;
|
|
else if (event == EcsWildcard) return (ecs_event_record_t*)&o->on_wildcard;
|
|
|
|
/* User events */
|
|
return flecs_sparse_get(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(o->events, ecs_event_record_t, event);
|
|
er->event = event;
|
|
return er;
|
|
}
|
|
|
|
static
|
|
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);
|
|
|
|
ecs_event_record_t *er = flecs_event_record_get(o, event);
|
|
if (er) {
|
|
if (ecs_map_is_initialized(&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;
|
|
return ecs_map_get_ptr(&er->event_ids, ecs_event_id_record_t*, id);
|
|
}
|
|
|
|
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_event_id_record_t **idt = ecs_map_ensure(
|
|
&er->event_ids, ecs_event_id_record_t*, id);
|
|
if (!idt[0]) {
|
|
idt[0] = 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 {
|
|
if (ecs_map_remove(&er->event_ids, id) == 0) {
|
|
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;
|
|
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 (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_check_observers_for_event(
|
|
const ecs_poly_t *object,
|
|
ecs_id_t id,
|
|
ecs_entity_t event)
|
|
{
|
|
ecs_observable_t *observable = ecs_get_observable(object);
|
|
ecs_event_record_t *er = flecs_event_record_get_if(observable, event);
|
|
if (!er) {
|
|
return false;
|
|
}
|
|
|
|
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_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 acyclic relationships */
|
|
ecs_id_record_t *cur = tgt_idr;
|
|
while ((cur = cur->acyclic.next)) {
|
|
cur->reachable.generation ++; /* Invalidate cache */
|
|
|
|
ecs_table_cache_iter_t idt;
|
|
if (!flecs_table_cache_all_iter(&cur->cache, &idt)) {
|
|
continue;
|
|
}
|
|
|
|
/* Get traversed relationship */
|
|
ecs_entity_t trav = ECS_PAIR_FIRST(cur->id);
|
|
|
|
const ecs_table_record_t *tr;
|
|
while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) {
|
|
ecs_table_t *table = tr->hdr.table;
|
|
if (!ecs_table_count(table)) {
|
|
continue;
|
|
}
|
|
|
|
bool owned = flecs_id_record_get_table(idr, table);
|
|
|
|
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. */
|
|
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->observed_count) {
|
|
continue;
|
|
}
|
|
|
|
ecs_record_t **records = ecs_vec_first(&table->data.records);
|
|
for (e = 0; e < entity_count; e ++) {
|
|
ecs_id_record_t *idr_t = records[e]->idr;
|
|
if (idr_t) {
|
|
/* Only notify for entities that are used in pairs with
|
|
* acyclic relationships */
|
|
flecs_emit_propagate(world, it, idr, idr_t,
|
|
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 acyclic relationships */
|
|
ecs_id_record_t *cur = tgt_idr;
|
|
while ((cur = cur->acyclic.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->observed_count) {
|
|
continue;
|
|
}
|
|
|
|
int32_t e, entity_count = ecs_table_count(table);
|
|
ecs_record_t **records = ecs_vec_first(&table->data.records);
|
|
|
|
for (e = 0; e < entity_count; e ++) {
|
|
ecs_id_record_t *idr_t = records[e]->idr;
|
|
if (idr_t) {
|
|
/* Only notify for entities that are used in pairs with
|
|
* acyclic 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_record_t **recs = ecs_vec_get_t(&table->data.records,
|
|
ecs_record_t*, offset);
|
|
int32_t i;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_record_t *record = recs[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 acyclic relationship, propagate */
|
|
flecs_emit_propagate_invalidate_tables(world, idr_t);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_override_copy(
|
|
const ecs_type_info_t *ti,
|
|
void *dst,
|
|
const void *src,
|
|
int32_t count)
|
|
{
|
|
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(dst, src, count, ti);
|
|
dst = ECS_OFFSET(dst, size);
|
|
}
|
|
} else {
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_os_memcpy(dst, src, size);
|
|
dst = ECS_OFFSET(dst, size);
|
|
}
|
|
}
|
|
}
|
|
|
|
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. */
|
|
const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
|
|
if (!tr) {
|
|
continue;
|
|
}
|
|
|
|
int32_t column = tr->column;
|
|
column = ecs_table_type_to_storage_index(table, column);
|
|
ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
const ecs_type_info_t *ti = idr->type_info;
|
|
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_size_t size = ti->size;
|
|
ecs_vec_t *vec = &table->data.columns[column];
|
|
return ecs_vec_get(vec, size, it->offset);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
void flecs_emit_forward_up(
|
|
ecs_world_t *world,
|
|
ecs_event_record_t *er,
|
|
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,
|
|
ecs_event_record_t *er,
|
|
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_storage_index(tgt_table, column);
|
|
if (storage_i != -1) {
|
|
ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_vec_t *vec = &tgt_table->data.columns[storage_i];
|
|
ecs_size_t size = idr->type_info->size;
|
|
it->ptrs[0] = ecs_vec_get(vec, size, offset);
|
|
it->sizes[0] = size;
|
|
}
|
|
|
|
const 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,
|
|
ecs_event_record_t *er,
|
|
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,
|
|
ecs_event_record_t *er,
|
|
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->column,
|
|
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,
|
|
ecs_event_record_t *er,
|
|
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);
|
|
}
|
|
|
|
if (it->event == EcsOnAdd || it->event == EcsOnRemove) {
|
|
/* Only OnAdd/OnRemove events can validate a cache */
|
|
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,
|
|
ecs_event_record_t *er,
|
|
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_current(world, tgt);
|
|
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_record_t *tgt_record = flecs_entities_get(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,
|
|
ecs_event_record_t *er,
|
|
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 a cache */
|
|
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->column, 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;
|
|
int32_t offset = desc->offset;
|
|
int32_t i, r, count = desc->count;
|
|
ecs_flags32_t table_flags = table->flags;
|
|
|
|
/* 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;
|
|
|
|
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,
|
|
.table = table,
|
|
.field_count = 1,
|
|
.ids = &ids_cache,
|
|
.ptrs = &ptrs_cache,
|
|
.sizes = &sizes_cache,
|
|
.columns = &columns_cache,
|
|
.sources = &sources_cache,
|
|
.other_table = desc->other_table,
|
|
.offset = offset,
|
|
.count = count,
|
|
.param = (void*)desc->param,
|
|
.flags = desc->flags | EcsIterIsValid
|
|
};
|
|
|
|
/* 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. */
|
|
world->event_id ++;
|
|
|
|
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. */
|
|
ecs_event_record_t *er = flecs_event_record_get_if(observable, event);
|
|
ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard);
|
|
ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet);
|
|
ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet);
|
|
|
|
ecs_data_t *storage = NULL;
|
|
ecs_vec_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 (acyclic) 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;
|
|
|
|
/* Set if event has been propagated */
|
|
bool propagated = false;
|
|
|
|
/* Does table has observed entities */
|
|
bool has_observed = table_flags & EcsTableHasObserved;
|
|
|
|
/* 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;
|
|
|
|
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 acyclic 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 & EcsIdAcyclic)) {
|
|
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);
|
|
}
|
|
|
|
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 = ecs_table_type_to_storage_index(
|
|
base_table, 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];
|
|
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);
|
|
const 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->column, storage_i = -1;
|
|
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 = ecs_table_type_to_storage_index(table, column);
|
|
if (storage_i != -1) {
|
|
/* If this is a component, fetch pointer & size */
|
|
ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_vec_t *vec = &columns[storage_i];
|
|
ecs_size_t size = idr->type_info->size;
|
|
void *ptr = ecs_vec_get(vec, 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(ti, ptr, override_ptr, 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);
|
|
flecs_observers_invoke(world, &ider->up, &it, table, EcsIsA);
|
|
}
|
|
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);
|
|
flecs_observers_invoke(world, &ider->self_up, &it, table, 0);
|
|
}
|
|
|
|
if (!ider_count || !count || !has_observed) {
|
|
continue;
|
|
}
|
|
|
|
/* If event is propagated, we don't have to manually invalidate entities
|
|
* lower in the tree(s). */
|
|
propagated = true;
|
|
|
|
/* The table->observed_count value indicates if the table contains any
|
|
* entities that are used as targets of acyclic 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;
|
|
|
|
ecs_record_t **recs = ecs_vec_get_t(&storage->records,
|
|
ecs_record_t*, offset);
|
|
for (r = 0; r < count; r ++) {
|
|
ecs_record_t *record = recs[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 acyclic pairs, propagate */
|
|
ecs_entity_t e = entities[r];
|
|
it.sources[0] = e;
|
|
|
|
flecs_emit_propagate(world, &it, idr, idr_t, iders, ider_count);
|
|
}
|
|
}
|
|
|
|
it.table = table;
|
|
it.entities = entities;
|
|
it.count = count;
|
|
it.sources[0] = 0;
|
|
}
|
|
|
|
if (count && can_forward && has_observed && !propagated) {
|
|
flecs_emit_propagate_invalidate(world, table, offset, count);
|
|
}
|
|
|
|
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:
|
|
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_world_t*)ecs_get_world(stage);
|
|
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;
|
|
}
|
|
flecs_emit(world, stage, desc);
|
|
}
|
|
|
|
/**
|
|
* @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) && !(term_id->flags & EcsIsVariable)) {
|
|
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)
|
|
{
|
|
char *name = term_id->name;
|
|
if (!name) {
|
|
return 0;
|
|
}
|
|
|
|
if (term_id->flags & EcsIsVariable) {
|
|
if (!ecs_os_strcmp(name, "This")) {
|
|
term_id->id = EcsThis;
|
|
if (free_name) {
|
|
ecs_os_free(term_id->name);
|
|
}
|
|
term_id->name = NULL;
|
|
}
|
|
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);
|
|
if (scope && !e) {
|
|
e = ecs_lookup_child(world, scope, name);
|
|
}
|
|
|
|
if (!e) {
|
|
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, "$") || !ecs_os_strcmp(name, "."))
|
|
{
|
|
term_id->flags &= ~EcsIsEntity;
|
|
term_id->flags |= EcsIsVariable;
|
|
}
|
|
|
|
/* Check if looked up id is alive (relevant for numerical ids) */
|
|
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) {
|
|
ecs_os_free(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 | 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;
|
|
}
|
|
|
|
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_filter_finalize_ctx_t *ctx)
|
|
{
|
|
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) {
|
|
role = term->id_flags = ECS_PAIR;
|
|
}
|
|
|
|
if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) {
|
|
term->id = first | role;
|
|
} else {
|
|
if (!ECS_HAS_ID_FLAG(role, PAIR)) {
|
|
flecs_filter_error(ctx, "invalid role for pair");
|
|
return -1;
|
|
}
|
|
|
|
term->id = ecs_pair(first, second);
|
|
}
|
|
|
|
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 (term_first != 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 (ecs_entity_t_lo(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(
|
|
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 (first->flags & EcsIsEntity) {
|
|
first_id = first->id;
|
|
}
|
|
if (second->flags & EcsIsEntity) {
|
|
second_id = second->id;
|
|
}
|
|
|
|
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)
|
|
&& !ecs_has_id(world, first_id, EcsReflexive))
|
|
{
|
|
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, EcsAcyclic)) {
|
|
char *r_str = ecs_get_fullpath(world, term->src.trav);
|
|
flecs_filter_error(ctx,
|
|
"cannot traverse non-Acyclic 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 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);
|
|
}
|
|
|
|
if (!term->id) {
|
|
if (flecs_term_populate_id(term, ctx)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ecs_entity_t first_id = 0;
|
|
if (term->first.flags & EcsIsEntity) {
|
|
first_id = term->first.id;
|
|
}
|
|
|
|
if (first_id) {
|
|
/* If component id is final, don't attempt component inheritance */
|
|
if (ecs_has_id(world, first_id, EcsFinal)) {
|
|
if (first_flags & EcsDown) {
|
|
flecs_filter_error(ctx, "final id cannot be traversed down");
|
|
return -1;
|
|
}
|
|
first->flags &= ~EcsDown;
|
|
first->trav = 0;
|
|
}
|
|
/* Don't traverse ids that cannot be inherited */
|
|
if (ecs_has_id(world, first_id, EcsDontInherit)) {
|
|
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;
|
|
}
|
|
/* Add traversal flags for transitive relationships */
|
|
if (!(second_flags & EcsTraverseFlags)) {
|
|
if (ecs_has_id(world, first_id, EcsTransitive)) {
|
|
second->flags |= EcsSelf|EcsUp|EcsTraverseAll;
|
|
second->trav = first_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (flecs_term_verify(world, term, ctx)) {
|
|
return -1;
|
|
}
|
|
|
|
term->idr = flecs_query_id_record_get(world, term->id);
|
|
|
|
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;
|
|
}
|
|
|
|
world = ecs_get_world(world);
|
|
const ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
|
if (idr && idr->flags & EcsIdMarkedForDelete) {
|
|
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)
|
|
{
|
|
ecs_os_free(term->first.name);
|
|
ecs_os_free(term->src.name);
|
|
ecs_os_free(term->second.name);
|
|
ecs_os_free(term->name);
|
|
|
|
term->first.name = NULL;
|
|
term->src.name = NULL;
|
|
term->second.name = NULL;
|
|
term->name = 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;
|
|
bool is_or = false, prev_or = false;
|
|
ecs_entity_t prev_src_id = 0;
|
|
int32_t filter_terms = 0;
|
|
|
|
ecs_filter_finalize_ctx_t ctx = {0};
|
|
ctx.world = world;
|
|
ctx.filter = f;
|
|
|
|
f->flags |= EcsFilterMatchOnlyThis;
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
ctx.term_index = i;
|
|
if (flecs_term_finalize(world, term, &ctx)) {
|
|
return -1;
|
|
}
|
|
|
|
is_or = term->oper == EcsOr;
|
|
field_count += !(is_or && prev_or);
|
|
term->field_index = field_count - 1;
|
|
|
|
if (prev_or && is_or) {
|
|
if (prev_src_id != term->src.id) {
|
|
flecs_filter_error(&ctx, "mismatching src.id for OR terms");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
prev_src_id = term->src.id;
|
|
prev_or = is_or;
|
|
|
|
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->inout == EcsInOutNone) {
|
|
filter_terms ++;
|
|
}
|
|
|
|
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 ++;
|
|
}
|
|
}
|
|
}
|
|
|
|
f->field_count = field_count;
|
|
|
|
if (filter_terms == term_count) {
|
|
ECS_BIT_SET(f->flags, EcsFilterNoData);
|
|
}
|
|
|
|
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_filter_t*)poly);
|
|
iter[0] = ecs_term_chain_iter(&iter[1], filter);
|
|
} else {
|
|
iter[0] = ecs_filter_iter(world, (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 (ecs_os_has_threading()) {
|
|
ecs_os_adec(&term->idr->keep_alive);
|
|
} else {
|
|
term->idr->keep_alive --;
|
|
}
|
|
}
|
|
ecs_term_fini(&filter->terms[i]);
|
|
}
|
|
|
|
if (filter->terms_owned) {
|
|
ecs_os_free(filter->terms);
|
|
}
|
|
}
|
|
|
|
filter->terms = NULL;
|
|
|
|
if (filter->owned) {
|
|
ecs_os_free(filter);
|
|
}
|
|
}
|
|
|
|
void ecs_filter_fini(
|
|
ecs_filter_t *filter)
|
|
{
|
|
if (filter->owned && 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->owned = true;
|
|
}
|
|
if (!storage_terms) {
|
|
f->terms_owned = true;
|
|
}
|
|
|
|
ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced);
|
|
ECS_BIT_SET(f->flags, EcsFilterMatchAnything);
|
|
f->flags |= desc->flags;
|
|
|
|
/* 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 < ECS_TERM_DESC_CACHE_SIZE; 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};
|
|
int32_t expr_size = 0;
|
|
|
|
if (entity) {
|
|
name = ecs_get_name(world, entity);
|
|
}
|
|
|
|
while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){
|
|
if (!ecs_term_is_initialized(&term)) {
|
|
break;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
expr_terms[expr_count ++] = term;
|
|
if (ptr[0] == '\n') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ptr) {
|
|
/* Set terms in filter object to make sur they get cleaned up */
|
|
f->terms = expr_terms;
|
|
f->term_count = expr_count;
|
|
f->terms_owned = true;
|
|
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) {
|
|
/* If no storage is provided, create it */
|
|
if (!storage_terms) {
|
|
f->terms = ecs_os_calloc_n(ecs_term_t, term_count + expr_count);
|
|
f->term_count = term_count + expr_count;
|
|
ecs_assert(f->terms_owned == true, ECS_INTERNAL_ERROR, NULL);
|
|
} else {
|
|
f->terms = storage_terms;
|
|
f->term_count = storage_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] = (char*)".";
|
|
f->iterable.init = flecs_filter_iter_init;
|
|
|
|
f->dtor = (ecs_poly_dtor_t)flecs_filter_fini;
|
|
f->world = world;
|
|
f->entity = entity;
|
|
|
|
if (entity && f->owned) {
|
|
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;
|
|
dst->terms = ecs_os_malloc_n(ecs_term_t, term_count);
|
|
dst->terms_owned = true;
|
|
|
|
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->terms_owned) {
|
|
dst->terms = src->terms;
|
|
dst->terms_owned = true;
|
|
} else {
|
|
ecs_filter_copy(dst, src);
|
|
}
|
|
src->terms = NULL;
|
|
src->term_count = 0;
|
|
} else {
|
|
ecs_os_memset_t(dst, 0, ecs_filter_t);
|
|
}
|
|
}
|
|
|
|
static
|
|
void 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_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 term_str_w_strbuf(
|
|
const ecs_world_t *world,
|
|
const ecs_term_t *term,
|
|
ecs_strbuf_t *buf)
|
|
{
|
|
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|EcsDown;
|
|
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.flags & EcsIsEntity && term->first.id != 0) {
|
|
if (ecs_has_id(world, term->first.id, EcsFinal)) {
|
|
def_first_mask = EcsSelf;
|
|
}
|
|
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) {
|
|
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,");
|
|
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, "(");
|
|
}
|
|
filter_str_add_id(world, buf, &term->first, false, def_first_mask);
|
|
if (obj_set) {
|
|
ecs_strbuf_appendlit(buf, ",");
|
|
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, '|');
|
|
}
|
|
|
|
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 {
|
|
filter_str_add_id(world, buf, &term->src, true, def_src_mask);
|
|
}
|
|
if (obj_set) {
|
|
ecs_strbuf_appendlit(buf, ",");
|
|
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;
|
|
term_str_w_strbuf(world, term, &buf);
|
|
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;
|
|
int32_t or_count = 0;
|
|
|
|
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 + , */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i) {
|
|
if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) {
|
|
ecs_strbuf_appendlit(&buf, " || ");
|
|
} else {
|
|
ecs_strbuf_appendlit(&buf, ", ");
|
|
}
|
|
}
|
|
|
|
if (term->oper != EcsOr) {
|
|
or_count = 0;
|
|
}
|
|
|
|
if (or_count < 1) {
|
|
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) {
|
|
ecs_strbuf_appendlit(&buf, "[none] ");
|
|
}
|
|
}
|
|
|
|
if (term->oper == EcsOr) {
|
|
or_count ++;
|
|
}
|
|
|
|
term_str_w_strbuf(world, term, &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;
|
|
}
|
|
|
|
match_table = ecs_get_table(world, src_id);
|
|
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);
|
|
|
|
column = flecs_search_relation_w_idr(world, match_table,
|
|
column, id, src->trav, src->flags, &source, id_out, &tr, term->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;
|
|
}
|
|
|
|
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;
|
|
|
|
bool is_or = false;
|
|
bool or_result = false;
|
|
int32_t match_count = 1;
|
|
if (matches_left) {
|
|
match_count = *matches_left;
|
|
}
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
if (i == skip_term) {
|
|
continue;
|
|
}
|
|
|
|
ecs_term_t *term = &terms[i];
|
|
ecs_term_id_t *src = &term->src;
|
|
ecs_oper_kind_t oper = term->oper;
|
|
const ecs_table_t *match_table = table;
|
|
int32_t t_i = term->field_index;
|
|
|
|
if (!is_or && oper == EcsOr) {
|
|
is_or = true;
|
|
or_result = false;
|
|
} else if (is_or && oper != EcsOr) {
|
|
if (!or_result) {
|
|
return false;
|
|
}
|
|
|
|
is_or = false;
|
|
}
|
|
|
|
ecs_entity_t src_id = src->id;
|
|
if (!src_id) {
|
|
if (ids) {
|
|
ids[t_i] = term->id;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!ecs_term_match_this(term)) {
|
|
match_table = ecs_get_table(world, src_id);
|
|
} else {
|
|
if (ECS_BIT_IS_SET(iter_flags, EcsIterIgnoreThis)) {
|
|
or_result = true;
|
|
continue;
|
|
}
|
|
|
|
/* If filter contains This terms, table must be provided */
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
int32_t match_index = 0;
|
|
|
|
bool result = flecs_term_match_table(world, term, match_table,
|
|
ids ? &ids[t_i] : NULL,
|
|
columns ? &columns[t_i] : NULL,
|
|
sources ? &sources[t_i] : NULL,
|
|
&match_index,
|
|
first,
|
|
iter_flags);
|
|
|
|
if (is_or) {
|
|
or_result |= result;
|
|
if (result) {
|
|
/* If Or term matched, skip following Or terms */
|
|
for (; i < count && terms[i].oper == EcsOr; i ++) { }
|
|
i -- ;
|
|
}
|
|
} else if (!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 !is_or || or_result;
|
|
}
|
|
|
|
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(
|
|
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;
|
|
}
|
|
|
|
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(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(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_world_t*)world,
|
|
.world = (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 accomodate 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);
|
|
|
|
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 = (ecs_world_t*)world,
|
|
.world = chain_it->world,
|
|
.terms = term,
|
|
.field_count = 1,
|
|
.chain_it = (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 */
|
|
int32_t index = flecs_search_relation_w_idr(world, table, 0,
|
|
term->id, src->trav, src->flags, source, id, 0, term->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->observed_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->column;
|
|
iter->column = tr->column + 1;
|
|
iter->id = flecs_to_public_id(table->type.array[tr->column]);
|
|
}
|
|
|
|
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->column;
|
|
iter->column = tr->column + 1;
|
|
iter->id = flecs_to_public_id(table->type.array[tr->column]);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (term->inout != EcsInOutNone) {
|
|
it->ptrs = &iter->ptr;
|
|
} else {
|
|
it->ptrs = NULL;
|
|
}
|
|
|
|
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, false, false)) {
|
|
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, it->sizes);
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
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);
|
|
const ecs_world_t *world = ecs_get_world(stage);
|
|
|
|
if (!(flags & EcsIterMatchVar)) {
|
|
flecs_process_pending_tables(world);
|
|
}
|
|
|
|
ecs_iter_t it = {
|
|
.real_world = (ecs_world_t*)world,
|
|
.world = (ecs_world_t*)stage,
|
|
.terms = filter ? filter->terms : NULL,
|
|
.next = ecs_filter_next,
|
|
.flags = flags
|
|
};
|
|
|
|
ecs_filter_iter_t *iter = &it.priv.iter.filter;
|
|
iter->pivot_term = -1;
|
|
|
|
flecs_init_filter_iter(&it, filter);
|
|
ECS_BIT_COND(it.flags, EcsIterIsInstanced,
|
|
ECS_BIT_IS_SET(filter->flags, EcsFilterIsInstanced));
|
|
|
|
/* 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, EcsIterIsFilter,
|
|
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 = (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 = {
|
|
.terms = filter->terms,
|
|
.field_count = filter->field_count,
|
|
.world = chain_it->world,
|
|
.real_world = chain_it->real_world,
|
|
.chain_it = (ecs_iter_t*)chain_it,
|
|
.next = ecs_filter_next
|
|
};
|
|
|
|
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)) {
|
|
ecs_iter_fini(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;
|
|
}
|
|
|
|
/* 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 {
|
|
/* 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:
|
|
it->offset = 0;
|
|
flecs_iter_populate_data(world, it, table, 0,
|
|
table ? ecs_table_count(table) : 0, it->ptrs, it->sizes);
|
|
ECS_BIT_SET(it->flags, EcsIterIsValid);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @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->column;
|
|
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;
|
|
}
|
|
|
|
static
|
|
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)
|
|
{
|
|
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 && !offset) {
|
|
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 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) {
|
|
return ecs_search(world, table, id, id_out);
|
|
}
|
|
|
|
if (!table) return -1;
|
|
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
|
|
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;
|
|
|
|
const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
|
|
if (!tr) {
|
|
return 0;
|
|
}
|
|
|
|
int32_t i = tr->column, 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;
|
|
}
|
|
return flecs_relation_depth_walk(world, idr, table, table);
|
|
}
|
|
|
|
/**
|
|
* @file observer.h
|
|
* @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;
|
|
}
|
|
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_ensure(observers, ecs_observer_t*,
|
|
observer->filter.entity)[0] = 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);
|
|
|
|
if (ecs_map_remove(id_observers, observer->filter.entity) == 0) {
|
|
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_world_t *world,
|
|
ecs_observer_t *observer,
|
|
ecs_table_t *table)
|
|
{
|
|
int32_t *last_event_id = observer->last_event_id;
|
|
if (last_event_id && last_event_id[0] == world->event_id) {
|
|
return true;
|
|
}
|
|
|
|
if (!table) {
|
|
return false;
|
|
}
|
|
|
|
ecs_filter_t *filter = &observer->filter;
|
|
if (!(filter->flags & EcsFilterMatchPrefab) &&
|
|
(table->flags & EcsTableIsPrefab))
|
|
{
|
|
return true;
|
|
}
|
|
if (!(filter->flags & EcsFilterMatchDisabled) &&
|
|
(table->flags & EcsTableIsDisabled))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static
|
|
void flecs_observer_invoke(
|
|
ecs_world_t *world,
|
|
ecs_iter_t *it,
|
|
ecs_observer_t *observer,
|
|
ecs_iter_action_t callback,
|
|
ecs_table_t *table,
|
|
int32_t term_index)
|
|
{
|
|
ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_table_lock(it->world, table);
|
|
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];
|
|
ecs_entity_t observer_src = term->src.id;
|
|
if (observer_src && !(term->src.flags & EcsIsEntity)) {
|
|
observer_src = 0;
|
|
}
|
|
|
|
if (term->oper != EcsNot) {
|
|
ecs_assert((it->offset + it->count) <= ecs_table_count(table),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
int32_t i, count = it->count;
|
|
ecs_size_t size = it->sizes[0];
|
|
ecs_entity_t src = it->sources[0];
|
|
bool instanced = filter->flags & EcsFilterIsInstanced;
|
|
|
|
if (!observer_src && ((count == 1) || (size == 0) || (src == 0) || instanced)) {
|
|
ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced);
|
|
callback(it);
|
|
} else {
|
|
ECS_BIT_CLEAR(it->flags, EcsIterIsInstanced);
|
|
ecs_entity_t *entities = it->entities;
|
|
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();
|
|
ecs_table_unlock(it->world, table);
|
|
}
|
|
|
|
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)
|
|
{
|
|
ecs_filter_t *filter = &observer->filter;
|
|
ecs_term_t *term = &filter->terms[0];
|
|
if (flecs_ignore_observer(world, observer, table)) {
|
|
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, EcsIterIsFilter, is_filter);
|
|
it->system = observer->filter.entity;
|
|
it->ctx = observer->ctx;
|
|
it->binding_ctx = observer->binding_ctx;
|
|
it->term_index = observer->term_index;
|
|
it->terms = term;
|
|
|
|
ecs_entity_t event = it->event;
|
|
it->event = flecs_get_observer_event(term, event);
|
|
void *ptrs = it->ptrs;
|
|
if (is_filter) {
|
|
it->ptrs = NULL;
|
|
}
|
|
|
|
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, table, 0);
|
|
}
|
|
|
|
it->event = event;
|
|
it->ptrs = ptrs;
|
|
}
|
|
|
|
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_initialized(observers)) {
|
|
ecs_map_iter_t oit = ecs_map_iter(observers);
|
|
ecs_observer_t *o;
|
|
while ((o = ecs_map_next_ptr(&oit, ecs_observer_t*, NULL))) {
|
|
ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL);
|
|
flecs_uni_observer_invoke(world, o, it, table, trav);
|
|
}
|
|
}
|
|
}
|
|
|
|
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] == world->event_id) {
|
|
/* 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.terms = o->filter.terms;
|
|
user_it.flags = 0;
|
|
ECS_BIT_COND(user_it.flags, EcsIterIsFilter,
|
|
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);
|
|
|
|
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;
|
|
|
|
if (flecs_filter_match_table(world, &o->filter, table, user_it.ids,
|
|
user_it.columns, user_it.sources, NULL, NULL, false, -1,
|
|
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.sizes);
|
|
|
|
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);
|
|
flecs_observer_invoke(world, &user_it, o, o->callback, table, pivot_term);
|
|
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;
|
|
flecs_observer_invoke(it->real_world, it, o, o->callback, it->table, 0);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_default_multi_observer_run_callback(ecs_iter_t *it) {
|
|
flecs_multi_observer_invoke(it);
|
|
}
|
|
|
|
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, it->table, 0);
|
|
ecs_log_pop_3();
|
|
}
|
|
|
|
/* 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_trigger_existing(
|
|
ecs_world_t *world,
|
|
ecs_observer_t *observer)
|
|
{
|
|
ecs_iter_action_t callback = observer->callback;
|
|
|
|
/* 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];
|
|
callback(&it);
|
|
}
|
|
|
|
ecs_iter_fini(&it);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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.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];
|
|
run(&it);
|
|
world->event_id ++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
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_trigger_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;
|
|
}
|
|
|
|
/* 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 < ECS_OBSERVER_DESC_EVENT_COUNT_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 {
|
|
/* If existing entity handle was provided, override existing params */
|
|
if (desc->callback) {
|
|
ecs_poly(poly->poly, ecs_observer_t)->callback = desc->callback;
|
|
}
|
|
if (desc->ctx) {
|
|
ecs_poly(poly->poly, ecs_observer_t)->ctx = desc->ctx;
|
|
}
|
|
if (desc->binding_ctx) {
|
|
ecs_poly(poly->poly, ecs_observer_t)->binding_ctx = desc->binding_ctx;
|
|
}
|
|
}
|
|
|
|
ecs_poly_modified(world, entity, ecs_observer_t);
|
|
|
|
return entity;
|
|
error:
|
|
ecs_delete(world, entity);
|
|
return 0;
|
|
}
|
|
|
|
void* ecs_get_observer_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_get_observer_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) {
|
|
/* Child observers get deleted up by entity cleanup logic */
|
|
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 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_table_t*)table;
|
|
result->empty = empty;
|
|
|
|
flecs_table_cache_list_insert(cache, result);
|
|
|
|
if (table) {
|
|
ecs_map_set_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 **oldptr = ecs_map_get(&cache->index,
|
|
ecs_table_cache_hdr_t*, table->id);
|
|
ecs_assert(oldptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_table_cache_hdr_t *old = *oldptr;
|
|
ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_table_cache_hdr_t *prev = old->prev, *next = old->next;
|
|
if (prev) {
|
|
ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL);
|
|
prev->next = elem;
|
|
}
|
|
if (next) {
|
|
ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL);
|
|
next->prev = elem;
|
|
}
|
|
|
|
if (cache->empty_tables.first == old) {
|
|
cache->empty_tables.first = elem;
|
|
}
|
|
if (cache->empty_tables.last == old) {
|
|
cache->empty_tables.last = elem;
|
|
}
|
|
if (cache->tables.first == old) {
|
|
cache->tables.first = elem;
|
|
}
|
|
if (cache->tables.last == old) {
|
|
cache->tables.last = elem;
|
|
}
|
|
|
|
*oldptr = elem;
|
|
elem->prev = prev;
|
|
elem->next = next;
|
|
}
|
|
|
|
void* ecs_table_cache_get(
|
|
const ecs_table_cache_t *cache,
|
|
const ecs_table_t *table)
|
|
{
|
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
if (table) {
|
|
return ecs_map_get_ptr(&cache->index, ecs_table_cache_hdr_t*, table->id);
|
|
} 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,
|
|
const ecs_table_t *table,
|
|
ecs_table_cache_hdr_t *elem)
|
|
{
|
|
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
if (!ecs_map_is_initialized(&cache->index)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!elem) {
|
|
elem = ecs_map_get_ptr(
|
|
&cache->index, ecs_table_cache_hdr_t*, table->id);
|
|
if (!elem) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(elem->table == table, 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_ptr(
|
|
&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 false;
|
|
}
|
|
}
|
|
|
|
it->cur = next;
|
|
it->next = next->next;
|
|
return next;
|
|
}
|
|
|
|
/**
|
|
* @file os_api.h
|
|
* @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;
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
ecs_os_api_t ecs_os_api = {
|
|
.flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors,
|
|
.log_level_ = -1 /* Disable tracing by default, but log warnings/errors */
|
|
};
|
|
#else
|
|
/* Disable colors by default for emscripten */
|
|
ecs_os_api_t ecs_os_api = {
|
|
.flags_ = EcsOsApiHighResolutionTimer,
|
|
.log_level_ = -1 /* Disable tracing by default, but log warnings/errors */
|
|
};
|
|
#endif
|
|
|
|
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(
|
|
FILE *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(
|
|
FILE *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;
|
|
if (level >= 0) {
|
|
stream = stdout;
|
|
} else {
|
|
stream = stderr;
|
|
}
|
|
|
|
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);
|
|
time_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;
|
|
}
|
|
}
|
|
|
|
/* 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_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 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->node.table;
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
match->node.group_id = flecs_query_get_group_id(query, table);
|
|
} else {
|
|
match->node.group_id = 0;
|
|
}
|
|
}
|
|
|
|
static
|
|
ecs_query_table_list_t* flecs_query_get_group(
|
|
ecs_query_t *query,
|
|
uint64_t group_id)
|
|
{
|
|
return ecs_map_get(&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 group_id)
|
|
{
|
|
bool created = false;
|
|
if (query->on_group_create) {
|
|
created = !ecs_map_has(&query->groups, group_id);
|
|
}
|
|
|
|
ecs_query_table_list_t *group = ecs_map_ensure(
|
|
&query->groups, ecs_query_table_list_t, group_id);
|
|
if (created) {
|
|
group->info.ctx = query->on_group_create(
|
|
query->filter.world, group_id, query->group_by_ctx);
|
|
}
|
|
|
|
return group;
|
|
}
|
|
|
|
static
|
|
void flecs_query_remove_group(
|
|
ecs_query_t *query,
|
|
uint64_t group_id)
|
|
{
|
|
if (query->on_group_delete) {
|
|
ecs_query_table_list_t *group = ecs_map_get(
|
|
&query->groups, ecs_query_table_list_t, group_id);
|
|
if (group) {
|
|
query->on_group_delete(
|
|
query->filter.world, group_id, group->info.ctx, query->group_by_ctx);
|
|
}
|
|
}
|
|
|
|
ecs_map_remove(&query->groups, group_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_node_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;
|
|
|
|
/* Find closest smaller group id */
|
|
while ((list = ecs_map_next(&it, ecs_query_table_list_t, &id))) {
|
|
if (id >= group_id) {
|
|
continue;
|
|
}
|
|
|
|
if (!list->last) {
|
|
ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
continue;
|
|
}
|
|
|
|
if (!closest_list || ((group_id - id) < (group_id - closest_id))) {
|
|
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_node_t *node)
|
|
{
|
|
ecs_query_table_match_t *match = node->match;
|
|
uint64_t group_id = match->node.group_id;
|
|
|
|
/* If query has grouping enabled & this is a new/empty group, find
|
|
* the insertion point for the group */
|
|
ecs_query_table_node_t *insert_after = flecs_query_find_group_insertion_node(
|
|
query, group_id);
|
|
|
|
if (!insert_after) {
|
|
/* This group should appear first in the query list */
|
|
ecs_query_table_node_t *query_first = query->list.first;
|
|
if (query_first) {
|
|
/* If this is not the first match for the query, insert before it */
|
|
node->next = query_first;
|
|
query_first->prev = node;
|
|
query->list.first = node;
|
|
} else {
|
|
/* If this is the first match of the query, initialize its list */
|
|
ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
query->list.first = node;
|
|
query->list.last = node;
|
|
}
|
|
} else {
|
|
ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* This group should appear after another group */
|
|
ecs_query_table_node_t *insert_before = insert_after->next;
|
|
node->prev = insert_after;
|
|
insert_after->next = node;
|
|
node->next = insert_before;
|
|
if (insert_before) {
|
|
insert_before->prev = node;
|
|
} else {
|
|
ecs_assert(query->list.last == insert_after,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* This group should appear last in the query list */
|
|
query->list.last = node;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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_node_t *node)
|
|
{
|
|
ecs_query_table_match_t *match = node->match;
|
|
if (query->group_by) {
|
|
return flecs_query_get_group(query, match->node.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_node_t *node)
|
|
{
|
|
ecs_query_table_match_t *match = node->match;
|
|
if (query->group_by) {
|
|
return flecs_query_ensure_group(query, match->node.group_id);
|
|
} else {
|
|
return &query->list;
|
|
}
|
|
}
|
|
|
|
/* Remove node from list */
|
|
static
|
|
void flecs_query_remove_table_node(
|
|
ecs_query_t *query,
|
|
ecs_query_table_node_t *node)
|
|
{
|
|
ecs_query_table_node_t *prev = node->prev;
|
|
ecs_query_table_node_t *next = node->next;
|
|
|
|
ecs_assert(prev != node, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(next != node, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_query_table_list_t *list = flecs_query_get_node_list(query, node);
|
|
|
|
if (!list || !list->first) {
|
|
/* If list contains no nodes, the node must be empty */
|
|
ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
return;
|
|
}
|
|
|
|
ecs_assert(prev != NULL || query->list.first == node,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(next != NULL || query->list.last == node,
|
|
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) {
|
|
ecs_query_table_match_t *match = node->match;
|
|
uint64_t group_id = match->node.group_id;
|
|
|
|
/* Make sure query.list is updated if this is the first or last group */
|
|
if (query->list.first == node) {
|
|
ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
query->list.first = next;
|
|
prev = next;
|
|
}
|
|
if (query->list.last == node) {
|
|
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->match->node.group_id != group_id) {
|
|
/* The previous node belonged to another group */
|
|
prev = next;
|
|
}
|
|
if (next && next->match->node.group_id != group_id) {
|
|
/* The next node belonged to another group */
|
|
next = prev;
|
|
}
|
|
|
|
/* Do check again, in case both prev & next belonged to another group */
|
|
if ((!prev && !next) || (prev && prev->match->node.group_id != group_id)) {
|
|
/* There are no more matches left in this group */
|
|
flecs_query_remove_group(query, group_id);
|
|
list = NULL;
|
|
}
|
|
}
|
|
|
|
if (list) {
|
|
if (list->first == node) {
|
|
list->first = next;
|
|
}
|
|
if (list->last == node) {
|
|
list->last = prev;
|
|
}
|
|
}
|
|
|
|
node->prev = NULL;
|
|
node->next = NULL;
|
|
|
|
query->match_count ++;
|
|
}
|
|
|
|
/* Add node to list */
|
|
static
|
|
void flecs_query_insert_table_node(
|
|
ecs_query_t *query,
|
|
ecs_query_table_node_t *node)
|
|
{
|
|
/* Node should not be part of an existing list */
|
|
ecs_assert(node->prev == NULL && node->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, node->match);
|
|
|
|
ecs_query_table_list_t *list = flecs_query_ensure_node_list(query, node);
|
|
if (list->last) {
|
|
ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_query_table_node_t *last = list->last;
|
|
ecs_query_table_node_t *last_next = last->next;
|
|
|
|
node->prev = last;
|
|
node->next = last_next;
|
|
last->next = node;
|
|
|
|
if (last_next) {
|
|
last_next->prev = node;
|
|
}
|
|
|
|
list->last = node;
|
|
|
|
if (query->group_by) {
|
|
/* Make sure to update query list if this is the last group */
|
|
if (query->list.last == last) {
|
|
query->list.last = node;
|
|
}
|
|
}
|
|
} else {
|
|
ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
list->first = node;
|
|
list->last = node;
|
|
|
|
if (query->group_by) {
|
|
/* Initialize group with its first node */
|
|
flecs_query_create_group(query, node);
|
|
}
|
|
}
|
|
|
|
if (query->group_by) {
|
|
list->info.table_count ++;
|
|
list->info.match_count ++;
|
|
}
|
|
|
|
query->list.info.table_count ++;
|
|
query->match_count ++;
|
|
|
|
ecs_assert(node->prev != node, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(node->next != node, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(list->last == node, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(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);
|
|
ecs_query_table_node_t *node = &result->node;
|
|
|
|
node->match = result;
|
|
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 *dirty_state;
|
|
int32_t column;
|
|
} table_dirty_state_t;
|
|
|
|
static
|
|
void flecs_query_get_dirty_state(
|
|
ecs_query_t *query,
|
|
ecs_query_table_match_t *match,
|
|
int32_t term,
|
|
table_dirty_state_t *out)
|
|
{
|
|
ecs_world_t *world = query->filter.world;
|
|
ecs_entity_t subject = match->sources[term];
|
|
ecs_table_t *table;
|
|
int32_t column = -1;
|
|
|
|
if (!subject) {
|
|
table = match->node.table;
|
|
column = match->storage_columns[term];
|
|
} else {
|
|
table = ecs_get_table(world, subject);
|
|
int32_t ref_index = -match->columns[term] - 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;
|
|
column = ecs_table_type_to_storage_index(table, column);
|
|
}
|
|
}
|
|
|
|
out->table = table;
|
|
out->column = column;
|
|
out->dirty_state = flecs_table_get_dirty_state(world, table);
|
|
}
|
|
|
|
/* 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, t = -1, term_count = f->term_count;
|
|
table_dirty_state_t cur_dirty_state;
|
|
|
|
for (i = 0; i < term_count; i ++) {
|
|
if (t == f->terms[i].field_index) {
|
|
if (monitor[t + 1] != -1) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
t = f->terms[i].field_index;
|
|
monitor[t + 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 */
|
|
}
|
|
|
|
/* If term is not matched on this, don't track */
|
|
if (!ecs_term_match_this(&f->terms[i])) {
|
|
continue;
|
|
}
|
|
|
|
int32_t column = match->columns[t];
|
|
if (column == 0) {
|
|
continue; /* Don't track terms that aren't matched */
|
|
}
|
|
|
|
flecs_query_get_dirty_state(query, match, t, &cur_dirty_state);
|
|
if (cur_dirty_state.column == -1) {
|
|
continue; /* Don't track terms that aren't stored */
|
|
}
|
|
|
|
monitor[t + 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->node.table;
|
|
int32_t *dirty_state = flecs_table_get_dirty_state(query->filter.world, table);
|
|
ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
table_dirty_state_t cur;
|
|
|
|
monitor[0] = dirty_state[0]; /* Did table gain/lose entities */
|
|
|
|
int32_t i, term_count = query->filter.term_count;
|
|
for (i = 0; i < term_count; i ++) {
|
|
int32_t t = query->filter.terms[i].field_index;
|
|
if (monitor[t + 1] == -1) {
|
|
continue;
|
|
}
|
|
|
|
flecs_query_get_dirty_state(query, match, t, &cur);
|
|
ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL);
|
|
monitor[t + 1] = cur.dirty_state[cur.column + 1];
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
ecs_table_t *table = match->node.table;
|
|
int32_t *dirty_state = flecs_table_get_dirty_state(query->filter.world, table);
|
|
ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
table_dirty_state_t cur;
|
|
|
|
int32_t state = monitor[term];
|
|
if (state == -1) {
|
|
return false;
|
|
}
|
|
|
|
if (!term) {
|
|
return monitor[0] != dirty_state[0];
|
|
}
|
|
|
|
flecs_query_get_dirty_state(query, match, term - 1, &cur);
|
|
ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
return monitor[term] != cur.dirty_state[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)
|
|
{
|
|
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->node.table;
|
|
int32_t *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;
|
|
}
|
|
|
|
ecs_world_t *world = query->filter.world;
|
|
int32_t i, field_count = query->filter.field_count;
|
|
int32_t *storage_columns = match->storage_columns;
|
|
int32_t *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 (column >= 0) {
|
|
/* owned component */
|
|
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);
|
|
|
|
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->column;
|
|
column = ecs_table_type_to_storage_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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_node_t *cur, *end = table->last->node.next;
|
|
|
|
for (cur = &table->first->node; 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)) {
|
|
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_node_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->node.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);
|
|
qm->sizes = flecs_balloc(&query->allocators.sizes);
|
|
|
|
/* Insert match to iteration list if table is not empty */
|
|
if (!table || ecs_table_count(table) != 0) {
|
|
flecs_query_insert_table_node(query, &qm->node);
|
|
}
|
|
|
|
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);
|
|
ecs_os_memcpy_n(qm->sizes, it->sizes, ecs_size_t, field_count);
|
|
|
|
if (table) {
|
|
/* Initialize storage columns for faster access to component storage */
|
|
for (i = 0; i < field_count; i ++) {
|
|
int32_t column = qm->columns[i];
|
|
if (column > 0) {
|
|
qm->storage_columns[i] = ecs_table_type_to_storage_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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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, EcsIterIsFilter);
|
|
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 */
|
|
qt = flecs_bcalloc(&world->allocators.query_table);
|
|
ecs_table_cache_insert(&query->cache, it.table, &qt->hdr);
|
|
table = it.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_initialized(&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|EcsIterIsFilter|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) {
|
|
qt = flecs_bcalloc(&world->allocators.query_table);
|
|
ecs_table_cache_insert(&query->cache, it.table, &qt->hdr);
|
|
table = it.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_type_info_t *ti = table->type_info[column_index];
|
|
ecs_vec_t *column = &data->columns[column_index];
|
|
size = ti->size;
|
|
ptr = ecs_vec_first(column);
|
|
}
|
|
|
|
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_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;
|
|
}
|
|
|
|
int to_sort = 0;
|
|
|
|
sort_helper_t *helper = ecs_os_malloc_n(sort_helper_t, table_count);
|
|
ecs_query_table_node_t *cur, *end = list->last->next;
|
|
for (cur = list->first; cur != end; cur = cur->next) {
|
|
ecs_query_table_match_t *match = cur->match;
|
|
ecs_table_t *table = match->node.table;
|
|
ecs_data_t *data = &table->data;
|
|
|
|
ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t index = -1;
|
|
if (id) {
|
|
index = ecs_search(world, table->storage_table, id, 0);
|
|
}
|
|
|
|
if (index != -1) {
|
|
ecs_type_info_t *ti = table->type_info[index];
|
|
ecs_vec_t *column = &data->columns[index];
|
|
int32_t size = ti->size;
|
|
helper[to_sort].ptr = ecs_vec_first(column);
|
|
helper[to_sort].elem_size = size;
|
|
helper[to_sort].shared = false;
|
|
} else if (id) {
|
|
/* Find component in prefab */
|
|
ecs_entity_t base = 0;
|
|
ecs_search_relation(world, table, 0, id,
|
|
EcsIsA, EcsUp, &base, 0, 0);
|
|
|
|
/* If a base was not found, the query should not have allowed using
|
|
* the component for sorting */
|
|
ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
const EcsComponent *cptr = ecs_get(world, id, EcsComponent);
|
|
ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
helper[to_sort].ptr = ecs_get_id(world, base, id);
|
|
helper[to_sort].elem_size = cptr->size;
|
|
helper[to_sort].shared = true;
|
|
} else {
|
|
helper[to_sort].ptr = NULL;
|
|
helper[to_sort].elem_size = 0;
|
|
helper[to_sort].shared = false;
|
|
}
|
|
|
|
helper[to_sort].match = match;
|
|
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->match != cur_helper->match) {
|
|
cur = ecs_vector_add(&query->table_slices, ecs_query_table_node_t);
|
|
ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
cur->match = 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_vector_count(query->table_slices);
|
|
ecs_query_table_node_t *nodes = ecs_vector_first(
|
|
query->table_slices, ecs_query_table_node_t);
|
|
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;
|
|
|
|
ecs_os_free(helper);
|
|
}
|
|
|
|
static
|
|
void flecs_query_build_sorted_tables(
|
|
ecs_query_t *query)
|
|
{
|
|
ecs_vector_clear(query->table_slices);
|
|
|
|
if (query->group_by) {
|
|
/* Populate sorted node list in grouping order */
|
|
ecs_query_table_node_t *cur = query->list.first;
|
|
if (cur) {
|
|
do {
|
|
/* Find list for current group */
|
|
ecs_query_table_match_t *match = cur->match;
|
|
ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
uint64_t group_id = match->node.group_id;
|
|
ecs_query_table_list_t *list = ecs_map_get(&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 i, order_by_term = -1;
|
|
|
|
/* Find term that iterates over component (must be at least one) */
|
|
if (order_by_component) {
|
|
const ecs_filter_t *f = &query->filter;
|
|
int32_t term_count = f->term_count;
|
|
for (i = 0; i < term_count; i ++) {
|
|
ecs_term_t *term = &f->terms[i];
|
|
if (!ecs_term_match_this(term)) {
|
|
continue;
|
|
}
|
|
|
|
if (term->id == order_by_component) {
|
|
order_by_term = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ecs_assert(order_by_term != -1, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
/* Iterate over non-empty tables. Don't bother with empty tables as they
|
|
* have nothing to sort */
|
|
|
|
bool tables_sorted = false;
|
|
|
|
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;
|
|
|
|
ecs_table_t *storage_table = table->storage_table;
|
|
if (storage_table) {
|
|
column = ecs_search(world, storage_table,
|
|
order_by_component, 0);
|
|
}
|
|
|
|
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) {
|
|
query->flags |= EcsQueryHasOutColumns;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if ((src->flags & EcsTraverseFlags) == EcsSelf) {
|
|
if (src->flags & EcsIsEntity) {
|
|
flecs_add_flag(world, term->src.id, EcsEntityObserved);
|
|
}
|
|
}
|
|
}
|
|
|
|
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->node);
|
|
} else {
|
|
ecs_assert(ecs_table_count(table) != 0,
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
flecs_query_insert_table_node(query, &cur->node);
|
|
}
|
|
}
|
|
}
|
|
|
|
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_query_t **elem = ecs_vector_add(&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) {
|
|
ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*);
|
|
int32_t i, count = ecs_vector_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);
|
|
flecs_bfree(&query->allocators.sizes, cur->sizes);
|
|
|
|
if (cur->monitor) {
|
|
flecs_bfree(&query->allocators.monitors, cur->monitor);
|
|
}
|
|
if (!elem->hdr.empty) {
|
|
flecs_query_remove_table_node(query, &cur->node);
|
|
}
|
|
|
|
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 *qt = ecs_table_cache_remove(
|
|
&query->cache, table, NULL);
|
|
if (qt) {
|
|
flecs_query_table_free(query, qt);
|
|
}
|
|
}
|
|
|
|
/* 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, EcsIterIsFilter);
|
|
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_bcalloc(&world->allocators.query_table);
|
|
ecs_table_cache_insert(&query->cache, table, &qt->hdr);
|
|
}
|
|
|
|
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->node.group_id) {
|
|
/* Update table group */
|
|
flecs_query_remove_table_node(query, &qm->node);
|
|
flecs_query_insert_table_node(query, &qm->node);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t i, count = ecs_vector_count(parent->subqueries);
|
|
ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*);
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
if (sq[i] == sub) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ecs_vector_remove(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) {
|
|
flecs_query_notify_subqueries(world, query, event);
|
|
}
|
|
}
|
|
notify = false;
|
|
break;
|
|
case EcsQueryTableUnmatch:
|
|
/* Deletion of table */
|
|
flecs_query_unmatch_table(query, event->table);
|
|
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);
|
|
|
|
query->order_by_component = order_by_component;
|
|
query->order_by = order_by;
|
|
query->sort_table = action;
|
|
|
|
ecs_vector_free(query->table_slices);
|
|
query->table_slices = NULL;
|
|
|
|
flecs_query_sort_tables(world, query);
|
|
|
|
if (!query->table_slices) {
|
|
flecs_query_build_sorted_tables(query);
|
|
}
|
|
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_query_t*)poly);
|
|
iter[0] = ecs_term_chain_iter(&iter[1], filter);
|
|
} else {
|
|
iter[0] = ecs_query_iter(world, (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_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;
|
|
}
|
|
|
|
ecs_entity_t event = it->event;
|
|
if (event == EcsOnTableEmpty) {
|
|
flecs_query_update_table(query, table, true);
|
|
} else
|
|
if (event == EcsOnTableFill) {
|
|
flecs_query_update_table(query, table, false);
|
|
}
|
|
}
|
|
|
|
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.sizes,
|
|
field_count * ECS_SIZEOF(ecs_size_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.sizes);
|
|
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);
|
|
ecs_query_table_list_t *group;
|
|
uint64_t group_id;
|
|
while ((group = ecs_map_next(&it, ecs_query_table_list_t, &group_id))) {
|
|
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_vector_free(query->subqueries);
|
|
ecs_vector_free(query->table_slices);
|
|
ecs_filter_fini(&query->filter);
|
|
|
|
flecs_query_allocators_fini(query);
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
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;
|
|
|
|
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);
|
|
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(
|
|
ecs_query_t *query)
|
|
{
|
|
ecs_poly_assert(query, ecs_query_t);
|
|
return &query->filter;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
query->prev_match_count = query->match_count;
|
|
|
|
/* Prepare iterator */
|
|
|
|
int32_t table_count;
|
|
if (query->table_slices) {
|
|
table_count = ecs_vector_count(query->table_slices);
|
|
} 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_vector_first(query->table_slices, ecs_query_table_node_t);
|
|
}
|
|
|
|
ecs_flags32_t flags = 0;
|
|
ECS_BIT_COND(flags, EcsIterIsFilter, ECS_BIT_IS_SET(query->filter.flags,
|
|
EcsFilterNoData));
|
|
ECS_BIT_COND(flags, EcsIterIsInstanced, ECS_BIT_IS_SET(query->filter.flags,
|
|
EcsFilterIsInstanced));
|
|
|
|
ecs_iter_t result = {
|
|
.real_world = world,
|
|
.world = (ecs_world_t*)stage,
|
|
.terms = query->filter.terms,
|
|
.field_count = query->filter.field_count,
|
|
.table_count = table_count,
|
|
.flags = flags,
|
|
.priv.iter.query = it,
|
|
.next = ecs_query_next,
|
|
};
|
|
|
|
ecs_filter_t *filter = &query->filter;
|
|
if (filter->flags & EcsFilterMatchOnlyThis) {
|
|
/* When the query only matches This terms, we can reuse the storage from
|
|
* the cache to populate the iterator */
|
|
flecs_iter_init(stage, &result, flecs_iter_cache_ptrs);
|
|
} else {
|
|
/* Check if non-This terms (like singleton terms) still match */
|
|
ecs_iter_t fit = flecs_filter_iter_w_flags(
|
|
(ecs_world_t*)stage, &query->filter, EcsIterIgnoreThis);
|
|
if (!ecs_filter_next(&fit)) {
|
|
/* No match, so return nothing */
|
|
ecs_iter_fini(&fit);
|
|
goto noresults;
|
|
}
|
|
|
|
/* Initialize iterator with private storage for ids, ptrs, sizes and
|
|
* columns so we have a place to store the non-This data */
|
|
flecs_iter_init(stage, &result, flecs_iter_cache_ptrs | flecs_iter_cache_ids |
|
|
flecs_iter_cache_columns | flecs_iter_cache_sizes);
|
|
|
|
/* Copy the data */
|
|
int32_t term_count = filter->field_count;
|
|
if (term_count) {
|
|
if (result.ptrs) {
|
|
ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, term_count);
|
|
}
|
|
ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, term_count);
|
|
ecs_os_memcpy_n(result.sizes, fit.sizes, ecs_size_t, term_count);
|
|
ecs_os_memcpy_n(result.columns, fit.columns, int32_t, term_count);
|
|
}
|
|
|
|
ecs_iter_fini(&fit);
|
|
}
|
|
|
|
return result;
|
|
error:
|
|
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_node_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(
|
|
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(
|
|
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->node.table;
|
|
if (!table) {
|
|
return;
|
|
}
|
|
|
|
int32_t *dirty_state = table->dirty_state;
|
|
if (dirty_state) {
|
|
int32_t *storage_columns = qm->storage_columns;
|
|
ecs_filter_t *filter = &query->filter;
|
|
ecs_term_t *terms = filter->terms;
|
|
int32_t i, count = filter->term_count;
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_term_t *term = &terms[i];
|
|
if (term->inout == EcsIn || term->inout == EcsInOutNone) {
|
|
/* Don't mark readonly terms dirty */
|
|
continue;
|
|
}
|
|
|
|
int32_t field = term->field_index;
|
|
int32_t column = storage_columns[field];
|
|
if (column < 0) {
|
|
continue;
|
|
}
|
|
|
|
dirty_state[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_node_t *node = iter->node;
|
|
ecs_query_t *query = iter->query;
|
|
|
|
if ((query->flags & EcsQueryHasOutColumns)) {
|
|
ecs_query_table_node_t *prev = iter->prev;
|
|
if (prev && it->count) {
|
|
flecs_query_mark_columns_dirty(query, prev->match);
|
|
}
|
|
}
|
|
|
|
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:
|
|
ecs_iter_fini(it);
|
|
return false;
|
|
}
|
|
|
|
void ecs_query_populate(
|
|
ecs_iter_t *it)
|
|
{
|
|
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_table_node_t *node = iter->prev;
|
|
ecs_assert(node != NULL, ECS_INVALID_OPERATION, NULL);
|
|
|
|
ecs_table_t *table = node->table;
|
|
ecs_query_table_match_t *match = node->match;
|
|
ecs_query_t *query = iter->query;
|
|
ecs_world_t *world = query->filter.world;
|
|
const ecs_filter_t *filter = &query->filter;
|
|
bool only_this = filter->flags & EcsFilterMatchOnlyThis;
|
|
|
|
/* Match has been iterated, update monitor for change tracking */
|
|
if (query->flags & EcsQueryHasMonitor) {
|
|
flecs_query_sync_match_monitor(query, match);
|
|
}
|
|
|
|
if (only_this) {
|
|
/* If query has only This terms, reuse cache storage */
|
|
it->ids = match->ids;
|
|
it->columns = match->columns;
|
|
it->sizes = match->sizes;
|
|
} else {
|
|
/* If query has non-This terms make sure not to overwrite them */
|
|
int32_t t, term_count = filter->term_count;
|
|
for (t = 0; t < term_count; t ++) {
|
|
ecs_term_t *term = &filter->terms[t];
|
|
if (!ecs_term_match_this(term)) {
|
|
continue;
|
|
}
|
|
|
|
int32_t field = term->field_index;
|
|
it->ids[field] = match->ids[field];
|
|
it->columns[field] = match->columns[field];
|
|
it->sizes[field] = match->sizes[field];
|
|
}
|
|
}
|
|
|
|
it->sources = match->sources;
|
|
it->references = ecs_vec_first(&match->refs);
|
|
it->instance_count = 0;
|
|
|
|
flecs_iter_populate_data(world, it, table, 0, ecs_table_count(table),
|
|
it->ptrs, NULL);
|
|
error:
|
|
return;
|
|
}
|
|
|
|
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_world_t *world = query->filter.world;
|
|
ecs_flags32_t flags = query->flags;
|
|
const ecs_filter_t *filter = &query->filter;
|
|
bool only_this = filter->flags & EcsFilterMatchOnlyThis;
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
|
|
ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter;
|
|
ecs_query_table_node_t *node, *next, *prev, *last;
|
|
if ((prev = iter->prev)) {
|
|
/* Match has been iterated, update monitor for change tracking */
|
|
if (flags & EcsQueryHasMonitor) {
|
|
flecs_query_sync_match_monitor(query, prev->match);
|
|
}
|
|
if (flags & EcsQueryHasOutColumns) {
|
|
flecs_query_mark_columns_dirty(query, prev->match);
|
|
}
|
|
}
|
|
|
|
flecs_iter_validate(it);
|
|
iter->skip_count = 0;
|
|
last = iter->last;
|
|
|
|
for (node = iter->node; node != last; node = next) {
|
|
ecs_assert(ent_it != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_table_range_t *range = &ent_it->range;
|
|
ecs_query_table_match_t *match = node->match;
|
|
ecs_table_t *table = match->node.table;
|
|
|
|
next = node->next;
|
|
|
|
if (table) {
|
|
range->offset = node->offset;
|
|
range->count = node->count;
|
|
if (!range->count) {
|
|
range->count = ecs_table_count(table);
|
|
ecs_assert(range->count != 0, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
if (match->entity_filter.has_filter) {
|
|
ent_it->entity_filter = &match->entity_filter;
|
|
ent_it->columns = match->columns;
|
|
ent_it->range.table = table;
|
|
switch(flecs_entity_filter_next(ent_it)) {
|
|
case 1: continue;
|
|
case -1: next = node;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
it->group_id = match->node.group_id;
|
|
} else {
|
|
range->offset = 0;
|
|
range->count = 0;
|
|
}
|
|
|
|
if (only_this) {
|
|
/* If query has only This terms, reuse cache storage */
|
|
it->ids = match->ids;
|
|
it->columns = match->columns;
|
|
it->sizes = match->sizes;
|
|
} else {
|
|
/* If query has non-This terms make sure not to overwrite them */
|
|
int32_t t, term_count = filter->term_count;
|
|
for (t = 0; t < term_count; t ++) {
|
|
ecs_term_t *term = &filter->terms[t];
|
|
if (!ecs_term_match_this(term)) {
|
|
continue;
|
|
}
|
|
|
|
int32_t field = term->field_index;
|
|
it->ids[field] = match->ids[field];
|
|
it->columns[field] = match->columns[field];
|
|
it->sizes[field] = match->sizes[field];
|
|
}
|
|
}
|
|
|
|
it->sources = match->sources;
|
|
it->references = ecs_vec_first(&match->refs);
|
|
it->instance_count = 0;
|
|
|
|
flecs_iter_populate_data(world, it, table, range->offset, range->count,
|
|
it->ptrs, NULL);
|
|
|
|
iter->node = next;
|
|
iter->prev = node;
|
|
goto yield;
|
|
}
|
|
|
|
error:
|
|
ecs_iter_fini(it);
|
|
return false;
|
|
|
|
yield:
|
|
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);
|
|
}
|
|
|
|
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(
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @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].
|
|
*/
|
|
|
|
|
|
/* Marker object used to differentiate a component vs. a tag edge */
|
|
static ecs_table_diff_t ecs_table_edge_is_component;
|
|
|
|
/* Id sequence (type) utilities */
|
|
|
|
static
|
|
uint64_t 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)
|
|
{
|
|
ecs_map_init_w_params_if(&edges->hi, &world->allocators.ptr);
|
|
|
|
ecs_graph_edge_t **ep = ecs_map_ensure(&edges->hi, ecs_graph_edge_t*, id);
|
|
ecs_graph_edge_t *edge = ep[0];
|
|
if (edge) {
|
|
return edge;
|
|
}
|
|
|
|
if (id < ECS_HI_COMPONENT_ID) {
|
|
edge = &edges->lo[id];
|
|
} else {
|
|
edge = flecs_bcalloc(&world->allocators.graph_edge);
|
|
}
|
|
|
|
ep[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 < ECS_HI_COMPONENT_ID) {
|
|
if (!edges->lo) {
|
|
edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo);
|
|
}
|
|
edge = &edges->lo[id];
|
|
} else {
|
|
ecs_map_init_w_params_if(&edges->hi, &world->allocators.ptr);
|
|
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 && diff != &ecs_table_edge_is_component) {
|
|
flecs_table_diff_free(world, diff);
|
|
}
|
|
|
|
/* If edge id is low, clear it from fast lookup array */
|
|
if (id < ECS_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(ecs_map_is_initialized(&edges->hi), 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;
|
|
ecs_os_zeromem(&edges->hi);
|
|
}
|
|
|
|
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->type_info = NULL;
|
|
table->flags = 0;
|
|
table->dirty_state = NULL;
|
|
table->lock = 0;
|
|
table->refcount = 1;
|
|
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(&world->store.tables, 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;
|
|
|
|
flecs_init_table(world, result, prev);
|
|
|
|
flecs_notify_queries(world, &(ecs_query_event_t) {
|
|
.kind = EcsQueryTableMatch,
|
|
.table = result
|
|
});
|
|
|
|
/* Update counters */
|
|
world->info.table_count ++;
|
|
world->info.table_record_count += result->record_count;
|
|
world->info.table_storage_count += result->storage_count;
|
|
world->info.empty_table_count ++;
|
|
world->info.table_create_total ++;
|
|
|
|
if (!result->storage_count) {
|
|
world->info.tag_table_count ++;
|
|
} else {
|
|
world->info.trivial_table_count += !(result->flags & EcsTableIsComplex);
|
|
}
|
|
|
|
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.
|
|
* Store whether the id is a tag or not, so that we can still tell
|
|
* whether an UnSet handler should be called or not. */
|
|
if (node->storage_table != next->storage_table) {
|
|
edge->diff = &ecs_table_edge_is_component;
|
|
}
|
|
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];
|
|
if (ECS_HAS_ID_FLAG(id, OVERRIDE)) {
|
|
flecs_type_add(world, dst_type, id & ~ECS_OVERRIDE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flags & EcsTableHasIsA) {
|
|
const 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->column, 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;
|
|
}
|
|
|
|
const ecs_table_record_t *tr = flecs_id_record_get_table(
|
|
idr_with_wildcard, table);
|
|
if (tr) {
|
|
int32_t i = tr->column, 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 */
|
|
const 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->column] = 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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static
|
|
void flecs_table_populate_diff(
|
|
ecs_graph_edge_t *edge,
|
|
ecs_id_t *add_ptr,
|
|
ecs_id_t *remove_ptr,
|
|
ecs_table_diff_t *out)
|
|
{
|
|
if (out) {
|
|
ecs_table_diff_t *diff = edge->diff;
|
|
|
|
if (diff && diff != &ecs_table_edge_is_component) {
|
|
*out = *diff;
|
|
} else {
|
|
if (add_ptr) {
|
|
out->added.array = add_ptr;
|
|
out->added.count = 1;
|
|
} else {
|
|
out->added.count = 0;
|
|
}
|
|
|
|
if (remove_ptr) {
|
|
out->removed.array = remove_ptr;
|
|
out->removed.count = 1;
|
|
} else {
|
|
out->removed.count = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
flecs_table_populate_diff(edge, NULL, id_ptr, diff);
|
|
}
|
|
|
|
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);
|
|
|
|
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) {
|
|
flecs_table_populate_diff(edge, id_ptr, NULL, diff);
|
|
}
|
|
|
|
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};
|
|
|
|
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;
|
|
ecs_graph_edge_t *edge;
|
|
uint64_t key;
|
|
|
|
/* Cleanup outgoing edges */
|
|
it = ecs_map_iter(add_hi);
|
|
while ((edge = ecs_map_next_ptr(&it, ecs_graph_edge_t*, &key))) {
|
|
flecs_table_disconnect_edge(world, key, edge);
|
|
}
|
|
|
|
it = ecs_map_iter(remove_hi);
|
|
while ((edge = ecs_map_next_ptr(&it, ecs_graph_edge_t*, &key))) {
|
|
flecs_table_disconnect_edge(world, key, edge);
|
|
}
|
|
|
|
/* Cleanup incoming add edges */
|
|
ecs_graph_edge_hdr_t *next, *cur = node_refs->next;
|
|
if (cur) {
|
|
do {
|
|
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 {
|
|
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);
|
|
table_node->add.lo = NULL;
|
|
table_node->remove.lo = 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)
|
|
{
|
|
return flecs_table_traverse_add(world, table, &id, NULL);
|
|
}
|
|
|
|
ecs_table_t* ecs_table_remove_id(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_id_t id)
|
|
{
|
|
return flecs_table_traverse_remove(world, table, &id, NULL);
|
|
}
|
|
|
|
/**
|
|
* @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_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_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, sizes, ecs_size_t, it->field_count);
|
|
|
|
if (!ECS_BIT_IS_SET(it->flags, EcsIterIsFilter)) {
|
|
INIT_CACHE(it, stack, fields, ptrs, void*, it->field_count);
|
|
} else {
|
|
it->ptrs = NULL;
|
|
}
|
|
}
|
|
|
|
void flecs_iter_validate(
|
|
ecs_iter_t *it)
|
|
{
|
|
ECS_BIT_SET(it->flags, EcsIterIsValid);
|
|
}
|
|
|
|
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, sizes, ecs_size_t, it->field_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
|
|
ecs_size_t flecs_iter_get_size_for_id(
|
|
ecs_world_t *world,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
|
if (!idr) {
|
|
return 0;
|
|
}
|
|
|
|
if (idr->flags & EcsIdUnion) {
|
|
return ECS_SIZEOF(ecs_entity_t);
|
|
}
|
|
|
|
if (idr->type_info) {
|
|
return idr->type_info->size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
bool flecs_iter_populate_term_data(
|
|
ecs_world_t *world,
|
|
ecs_iter_t *it,
|
|
int32_t t,
|
|
int32_t column,
|
|
void **ptr_out,
|
|
ecs_size_t *size_out)
|
|
{
|
|
bool is_shared = false;
|
|
ecs_table_t *table;
|
|
void *data;
|
|
ecs_size_t size = 0;
|
|
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) {
|
|
if (size_out) {
|
|
size = flecs_iter_get_size_for_id(world, it->ids[t]);
|
|
}
|
|
goto no_data;
|
|
}
|
|
|
|
if (column < 0) {
|
|
is_shared = true;
|
|
|
|
/* Data is not from This */
|
|
if (it->references) {
|
|
/* 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;
|
|
}
|
|
|
|
/* If cached references were provided, the code that populated
|
|
* the iterator also had a chance to cache sizes, so size array
|
|
* should already have been assigned. This saves us from having
|
|
* to do additional lookups to find the component size. */
|
|
ecs_assert(size_out == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
return is_shared;
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
ecs_entity_t subj = it->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_t *s_table = table->storage_table;
|
|
ecs_table_record_t *tr;
|
|
|
|
if (!s_table || !(tr = flecs_table_record_get(world, s_table, id))){
|
|
u_index = flecs_table_column_to_union_index(table, -column - 1);
|
|
if (u_index != -1) {
|
|
goto has_union;
|
|
}
|
|
goto no_data;
|
|
}
|
|
|
|
/* We now have row and column, so we can get the storage for the id
|
|
* which gives us the pointer and size */
|
|
column = tr->column;
|
|
ecs_type_info_t *ti = table->type_info[column];
|
|
ecs_vec_t *s = &table->data.columns[column];
|
|
size = ti->size;
|
|
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_storage_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;
|
|
}
|
|
|
|
ecs_type_info_t *ti = table->type_info[storage_column];
|
|
size = ti->size;
|
|
if (!it->count) {
|
|
goto no_data;
|
|
}
|
|
|
|
ecs_vec_t *s = &table->data.columns[storage_column];
|
|
data = ecs_vec_first(s);
|
|
|
|
/* Fallthrough to has_data */
|
|
}
|
|
|
|
has_data:
|
|
if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row);
|
|
if (size_out) size_out[0] = size;
|
|
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_switch_t *sw = &table->data.sw_columns[u_index];
|
|
data = ecs_vec_first(flecs_switch_values(sw));
|
|
size = ECS_SIZEOF(ecs_entity_t);
|
|
goto has_data;
|
|
}
|
|
|
|
no_data:
|
|
if (ptr_out) ptr_out[0] = NULL;
|
|
if (size_out) size_out[0] = size;
|
|
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_size_t *sizes)
|
|
{
|
|
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),
|
|
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, EcsIterIsFilter)) {
|
|
ECS_BIT_CLEAR(it->flags, EcsIterHasShared);
|
|
|
|
if (!sizes) {
|
|
return;
|
|
}
|
|
|
|
/* Fetch sizes, skip fetching data */
|
|
for (t = 0; t < field_count; t ++) {
|
|
sizes[t] = flecs_iter_get_size_for_id(world, it->ids[t]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool has_shared = false;
|
|
|
|
if (ptrs && sizes) {
|
|
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],
|
|
&sizes[t]);
|
|
}
|
|
} else if (ptrs || sizes) {
|
|
for (t = 0; t < field_count; t ++) {
|
|
ecs_assert(it->columns != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t column = it->columns[t];
|
|
void **ptr = NULL;
|
|
if (ptrs) {
|
|
ptr = &ptrs[t];
|
|
}
|
|
ecs_size_t *size = NULL;
|
|
if (sizes) {
|
|
size = &sizes[t];
|
|
}
|
|
|
|
has_shared |= flecs_iter_populate_term_data(world, it, t, column,
|
|
ptr, size);
|
|
}
|
|
}
|
|
|
|
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 ++) {
|
|
int32_t column = it->columns[t];
|
|
if (column >= 0) {
|
|
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 term)
|
|
{
|
|
ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(!size || ecs_field_size(it, term) == size ||
|
|
(!ecs_field_size(it, term) && (!it->ptrs || !it->ptrs[term - 1])),
|
|
ECS_INVALID_PARAMETER, NULL);
|
|
|
|
(void)size;
|
|
|
|
if (!term) {
|
|
return it->entities;
|
|
}
|
|
|
|
if (!it->ptrs) {
|
|
return NULL;
|
|
}
|
|
|
|
return it->ptrs[term - 1];
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
bool ecs_field_is_readonly(
|
|
const ecs_iter_t *it,
|
|
int32_t term_index)
|
|
{
|
|
ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_term_t *term = &it->terms[term_index - 1];
|
|
ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
if (term->inout == EcsIn) {
|
|
return true;
|
|
} else {
|
|
ecs_term_id_t *src = &term->src;
|
|
|
|
if (term->inout == EcsInOutDefault) {
|
|
if (!(ecs_term_match_this(term))) {
|
|
return true;
|
|
}
|
|
|
|
if (!(src->flags & EcsSelf)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
error:
|
|
return false;
|
|
}
|
|
|
|
bool ecs_field_is_writeonly(
|
|
const ecs_iter_t *it,
|
|
int32_t term_index)
|
|
{
|
|
ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_term_t *term = &it->terms[term_index - 1];
|
|
ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
if (term->inout == EcsOut) {
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
|
|
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(it != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(index >= 0, 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(it != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL);
|
|
return it->ids[index - 1];
|
|
}
|
|
|
|
ecs_entity_t ecs_field_src(
|
|
const ecs_iter_t *it,
|
|
int32_t index)
|
|
{
|
|
ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(index >= 0, 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(it != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
if (index == 0) {
|
|
return sizeof(ecs_entity_t);
|
|
} else {
|
|
return (size_t)it->sizes[index - 1];
|
|
}
|
|
}
|
|
|
|
char* ecs_iter_str(
|
|
const ecs_iter_t *it)
|
|
{
|
|
ecs_world_t *world = it->world;
|
|
ecs_strbuf_t buf = ECS_STRBUF_INIT;
|
|
int i;
|
|
|
|
if (it->field_count) {
|
|
ecs_strbuf_list_push(&buf, "term: ", ",");
|
|
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, "subj: ", ",");
|
|
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");
|
|
}
|
|
|
|
if (it->variable_count) {
|
|
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] == '_' || var_name[0] == '.') {
|
|
/* 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, "vars: ", ",");
|
|
}
|
|
|
|
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, EcsIterIsFilter);
|
|
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, EcsIterIsFilter);
|
|
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, EcsIterIsFilter);
|
|
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) {
|
|
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) {
|
|
/* 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) {
|
|
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 < ECS_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);
|
|
|
|
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_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 < ECS_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;
|
|
}
|
|
|
|
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.iter.page = (ecs_page_iter_t){
|
|
.offset = offset,
|
|
.limit = limit,
|
|
.remaining = limit
|
|
};
|
|
result.next = ecs_page_next;
|
|
result.chain_it = (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.iter.worker = (ecs_worker_iter_t){
|
|
.index = index,
|
|
.count = count
|
|
};
|
|
result.next = ecs_worker_next;
|
|
result.chain_it = (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 {
|
|
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 Miscallaneous functions.
|
|
*/
|
|
|
|
#include <time.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);
|
|
}
|
|
|
|
int flecs_entity_compare_qsort(
|
|
const void *e1,
|
|
const void *e2)
|
|
{
|
|
ecs_entity_t v1 = *(ecs_entity_t*)e1;
|
|
ecs_entity_t v2 = *(ecs_entity_t*)e2;
|
|
return flecs_entity_compare(v1, NULL, v2, NULL);
|
|
}
|
|
|
|
uint64_t flecs_string_hash(
|
|
const void *ptr)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*
|
|
This code was taken from sokol_time.h
|
|
|
|
zlib/libpng license
|
|
Copyright (c) 2018 Andre Weissflog
|
|
This software is provided 'as-is', without any express or implied warranty.
|
|
In no event will the authors be held liable for any damages arising from the
|
|
use of this software.
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software in a
|
|
product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not
|
|
be misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source
|
|
distribution.
|
|
*/
|
|
|
|
/**
|
|
* @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(
|
|
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");
|
|
|
|
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;
|
|
}
|
|
|
|
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 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.
|
|
*/
|
|
|
|
|
|
/* -- 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 & EcsWorldFini) {
|
|
return;
|
|
}
|
|
|
|
ecs_vector_t *marked_ids = world->store.marked_ids;
|
|
int32_t i, count = ecs_vector_count(marked_ids);
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_marked_id_t *mid = ecs_vector_get(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;
|
|
}
|
|
}
|
|
|
|
if (ecs_id_in_use(world, ecs_pair(rel, EcsWildcard))) {
|
|
char *r_str = ecs_get_fullpath(world, rel);
|
|
char *p_str = ecs_get_fullpath(world, property);
|
|
|
|
ecs_throw(ECS_ID_IN_USE,
|
|
"cannot change property '%s' 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,
|
|
EcsEntityObservedId);
|
|
}
|
|
|
|
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_OBJECT_FLAG(ECS_PAIR_SECOND(id)),
|
|
EcsIdOnDeleteObjectMask,
|
|
EcsEntityObservedId);
|
|
}
|
|
|
|
static
|
|
void flecs_register_acyclic(ecs_iter_t *it) {
|
|
flecs_register_id_flag_for_relation(it, EcsAcyclic, EcsIdAcyclic,
|
|
EcsIdAcyclic, 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_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_entity_t rel = ECS_PAIR_FIRST(pair);
|
|
ecs_entity_t obj = ECS_PAIR_SECOND(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_init(world, &(ecs_observer_desc_t){
|
|
.entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}),
|
|
.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -- Triggers for keeping hashed ids in sync -- */
|
|
|
|
static
|
|
void flecs_on_parent_change(ecs_iter_t *it) {
|
|
ecs_world_t *world = it->world;
|
|
ecs_table_t *other_table = it->other_table, *table = it->table;
|
|
|
|
int32_t col = ecs_search(it->real_world, table,
|
|
ecs_pair(ecs_id(EcsIdentifier), EcsName), 0);
|
|
bool has_name = col != -1;
|
|
bool other_has_name = ecs_search(it->real_world, other_table,
|
|
ecs_pair(ecs_id(EcsIdentifier), EcsName), 0) != -1;
|
|
|
|
if (!has_name && !other_has_name) {
|
|
/* If tables don't have names, index does not need to be updated */
|
|
return;
|
|
}
|
|
|
|
ecs_id_t to_pair = it->event_id;
|
|
ecs_id_t from_pair = ecs_childof(0);
|
|
|
|
/* Find the other ChildOf relationship */
|
|
ecs_search(it->real_world, other_table,
|
|
ecs_pair(EcsChildOf, EcsWildcard), &from_pair);
|
|
|
|
bool to_has_name = has_name, from_has_name = other_has_name;
|
|
if (it->event == EcsOnRemove) {
|
|
if (from_pair != ecs_childof(0)) {
|
|
/* Because ChildOf is an exclusive relationship, events always come
|
|
* in OnAdd/OnRemove pairs (add for the new, remove for the old
|
|
* parent). We only need one of those events, so filter out the
|
|
* OnRemove events except for the case where a parent is removed and
|
|
* not replaced with another parent. */
|
|
return;
|
|
}
|
|
|
|
ecs_id_t temp = from_pair;
|
|
from_pair = to_pair;
|
|
to_pair = temp;
|
|
|
|
to_has_name = other_has_name;
|
|
from_has_name = has_name;
|
|
}
|
|
|
|
/* Get the table column with names */
|
|
EcsIdentifier *names = ecs_table_get_pair(it->real_world,
|
|
table, EcsIdentifier, EcsName, it->offset);
|
|
|
|
ecs_hashmap_t *from_index = 0;
|
|
if (from_has_name) {
|
|
from_index = flecs_id_name_index_get(world, from_pair);
|
|
}
|
|
ecs_hashmap_t *to_index = NULL;
|
|
if (to_has_name) {
|
|
to_index = flecs_id_name_index_ensure(world, to_pair);
|
|
}
|
|
|
|
int32_t i, count = it->count;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_entity_t e = it->entities[i];
|
|
EcsIdentifier *name = &names[i];
|
|
|
|
uint64_t index_hash = name->index_hash;
|
|
if (from_index && index_hash) {
|
|
flecs_name_index_remove(from_index, e, index_hash);
|
|
}
|
|
const char *name_str = name->value;
|
|
if (to_index && name_str) {
|
|
ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL);
|
|
flecs_name_index_ensure(
|
|
to_index, e, name_str, name->length, name->hash);
|
|
name->index = to_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* -- Iterable mixins -- */
|
|
|
|
static
|
|
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_vec_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, record, false, false);
|
|
record->row = ECS_ROW_TO_RECORD(index, 0);
|
|
|
|
EcsComponent *component = ecs_vec_first(&columns[0]);
|
|
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]);
|
|
name_col[index].value = ecs_os_strdup(name);
|
|
name_col[index].length = name_length;
|
|
name_col[index].hash = flecs_hash(name, name_length);
|
|
name_col[index].index_hash = 0;
|
|
name_col[index].index = NULL;
|
|
|
|
EcsIdentifier *symbol_col = ecs_vec_first(&columns[2]);
|
|
symbol_col[index].value = ecs_os_strdup(symbol);
|
|
symbol_col[index].length = symbol_length;
|
|
symbol_col[index].hash = flecs_hash(symbol, symbol_length);
|
|
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 |
|
|
EcsIdAcyclic | EcsIdTag;
|
|
idr = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, EcsWildcard));
|
|
idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit |
|
|
EcsIdAcyclic | EcsIdTag | EcsIdExclusive;
|
|
|
|
idr = flecs_id_record_ensure(
|
|
world, ecs_pair(ecs_id(EcsIdentifier), EcsWildcard));
|
|
idr->flags |= EcsIdDontInherit;
|
|
|
|
world->idr_childof_0 = flecs_id_record_ensure(world,
|
|
ecs_pair(EcsChildOf, 0));
|
|
|
|
ecs_id_t ids[] = {
|
|
ecs_id(EcsComponent),
|
|
EcsFinal,
|
|
ecs_pair(ecs_id(EcsIdentifier), EcsName),
|
|
ecs_pair(ecs_id(EcsIdentifier), EcsSymbol),
|
|
ecs_pair(EcsChildOf, EcsFlecsCore),
|
|
ecs_pair(EcsOnDelete, 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->records, ecs_record_t*, EcsFirstUserComponentId);
|
|
ecs_vec_init_t(a, &data->columns[0], EcsComponent, EcsFirstUserComponentId);
|
|
ecs_vec_init_t(a, &data->columns[1], EcsIdentifier, EcsFirstUserComponentId);
|
|
ecs_vec_init_t(a, &data->columns[2], 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_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, 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);
|
|
|
|
/* Bootstrap 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 });
|
|
|
|
/* Cache often used id records */
|
|
flecs_init_id_records(world);
|
|
|
|
/* Create table for initial components */
|
|
ecs_table_t *table = flecs_bootstrap_component_table(world);
|
|
assert(table != NULL);
|
|
|
|
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);
|
|
|
|
world->info.last_component_id = EcsFirstUserComponentId;
|
|
world->info.last_id = 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 });
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.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 & EcsEntityObservedAcyclic, 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, EcsTag);
|
|
flecs_bootstrap_tag(world, EcsUnion);
|
|
flecs_bootstrap_tag(world, EcsExclusive);
|
|
flecs_bootstrap_tag(world, EcsAcyclic);
|
|
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, EcsDefaultChildComponent);
|
|
|
|
/* 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, 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, EcsDefaultChildComponent, EcsTag);
|
|
ecs_add_id(world, EcsUnion, EcsTag);
|
|
ecs_add_id(world, EcsFlag, 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, 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
|
|
};
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {
|
|
{ .id = ecs_pair(EcsChildOf, EcsWildcard), .src.flags = EcsSelf },
|
|
match_prefab
|
|
},
|
|
.events = { EcsOnAdd, EcsOnRemove },
|
|
.yield_existing = true,
|
|
.callback = flecs_on_parent_change
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab },
|
|
.events = {EcsOnAdd},
|
|
.callback = flecs_register_final
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {
|
|
{ .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf },
|
|
match_prefab
|
|
},
|
|
.events = {EcsOnAdd, EcsOnRemove},
|
|
.callback = flecs_register_on_delete
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {
|
|
{ .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf },
|
|
match_prefab
|
|
},
|
|
.events = {EcsOnAdd, EcsOnRemove},
|
|
.callback = flecs_register_on_delete_object
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {
|
|
{ .id = EcsAcyclic, .src.flags = EcsSelf },
|
|
match_prefab
|
|
},
|
|
.events = {EcsOnAdd, EcsOnRemove},
|
|
.callback = flecs_register_acyclic
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab },
|
|
.events = {EcsOnAdd},
|
|
.callback = flecs_register_exclusive
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab },
|
|
.events = {EcsOnAdd},
|
|
.callback = flecs_register_symmetric
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.entity = ecs_entity(world, { .name = "RegisterDontInherit" }),
|
|
.filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab },
|
|
.events = {EcsOnAdd},
|
|
.callback = flecs_register_dont_inherit
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.filter.terms = {
|
|
{ .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf },
|
|
match_prefab
|
|
},
|
|
.events = {EcsOnAdd},
|
|
.callback = flecs_register_with
|
|
});
|
|
|
|
ecs_observer_init(world, &(ecs_observer_desc_t){
|
|
.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_init(world, &(ecs_observer_desc_t){
|
|
.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_init(world, &(ecs_observer_desc_t){
|
|
.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);
|
|
|
|
/* Acyclic components */
|
|
ecs_add_id(world, EcsIsA, EcsAcyclic);
|
|
ecs_add_id(world, EcsDependsOn, EcsAcyclic);
|
|
ecs_add_id(world, EcsWith, EcsAcyclic);
|
|
|
|
/* DontInherit components */
|
|
ecs_add_id(world, EcsPrefab, EcsDontInherit);
|
|
|
|
/* Transitive relationships are always Acyclic */
|
|
ecs_add_pair(world, EcsTransitive, 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);
|
|
|
|
/* 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_log_pop();
|
|
}
|
|
|
|
/**
|
|
* @file hierarchy.c
|
|
* @brief API for entity paths and name lookups.
|
|
*/
|
|
|
|
#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 (ecs_is_valid(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;
|
|
}
|
|
|
|
static
|
|
bool flecs_is_string_number(
|
|
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;
|
|
}
|
|
|
|
static
|
|
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;
|
|
}
|
|
|
|
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_init(world, &(ecs_observer_desc_t){
|
|
.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_is_string_number(name)) {
|
|
ecs_entity_t result = flecs_name_to_id(world, name);
|
|
if (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_is_string_number(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)
|
|
{
|
|
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_fullpath(world, name);
|
|
}
|
|
|
|
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;
|
|
|
|
ecs_entity_t *lookup_path = ecs_get_lookup_path(stage);
|
|
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);
|
|
|
|
ecs_entity_t *cur = stage->lookup_path;
|
|
stage->lookup_path = (ecs_entity_t*)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);
|
|
return 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;
|
|
}
|
|
|
|
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;
|
|
|
|
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) {
|
|
ecs_add_pair(world, e, EcsChildOf, cur);
|
|
} else if (last_elem && root_path) {
|
|
ecs_remove_pair(world, e, EcsChildOf, EcsWildcard);
|
|
}
|
|
|
|
ecs_set_name(world, 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 {
|
|
if (parent) {
|
|
ecs_add_pair(world, entity, EcsChildOf, parent);
|
|
}
|
|
ecs_set_name(world, 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)
|
|
{
|
|
if (!sep) {
|
|
sep = ".";
|
|
}
|
|
|
|
return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix);
|
|
}
|
|
|
|
/**
|
|
* @file id_record.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.
|
|
*/
|
|
|
|
|
|
#define ECS_HI_ID_RECORD_ID (4096 * 65536)
|
|
|
|
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 & EcsIdAcyclic) {
|
|
flecs_id_record_elem_insert(widr, idr, &idr->acyclic);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 & EcsIdAcyclic) {
|
|
flecs_id_record_elem_remove(idr, &idr->acyclic);
|
|
}
|
|
}
|
|
}
|
|
|
|
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_ptr, *idr_t = NULL;
|
|
ecs_id_t hash = flecs_id_record_hash(id);
|
|
if (hash >= ECS_HI_ID_RECORD_ID) {
|
|
idr = flecs_bcalloc(&world->allocators.id_record);
|
|
idr_ptr = ecs_map_ensure(&world->id_index_hi, ecs_id_record_t*, hash);
|
|
ecs_assert(idr_ptr[0] == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
idr_ptr[0] = idr;
|
|
} else {
|
|
idr = flecs_sparse_ensure_fast(&world->id_index_lo,
|
|
ecs_id_record_t, 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);
|
|
|
|
ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK;
|
|
if (ECS_HAS_ID_FLAG(id, PAIR)) {
|
|
rel = ecs_pair_first(world, id);
|
|
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);
|
|
if (tgt) {
|
|
tgt = ecs_get_alive(world, tgt);
|
|
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
/* Check constraints */
|
|
if (tgt && !ecs_id_is_wildcard(tgt)) {
|
|
/* Check if target of relationship satisfies OneOf property */
|
|
ecs_entity_t oneof = flecs_get_oneof(world, rel);
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!is_wildcard && ECS_IS_PAIR(id) && (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 objecs 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;
|
|
rel = ecs_get_alive(world, rel);
|
|
ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
/* Initialize type info if id is not a tag */
|
|
if (!is_wildcard && (!role || ECS_IS_PAIR(id))) {
|
|
if (!(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, EcsEntityObservedId);
|
|
if (tgt) {
|
|
/* Flag for OnDeleteTarget policies */
|
|
flecs_add_flag(world, tgt, EcsEntityObservedTarget);
|
|
if (idr->flags & EcsIdAcyclic) {
|
|
/* Flag used to determine if object should be traversed when
|
|
* propagating events or with super/subset queries */
|
|
ecs_record_t *r = flecs_add_flag(
|
|
world, tgt, EcsEntityObservedAcyclic);
|
|
|
|
/* Add reference to (*, tgt) id record to entity record */
|
|
r->idr = idr_t;
|
|
}
|
|
}
|
|
|
|
/* Flags for events */
|
|
if (flecs_check_observers_for_event(world, id, EcsOnAdd)) {
|
|
idr->flags |= EcsIdHasOnAdd;
|
|
}
|
|
if (flecs_check_observers_for_event(world, id, EcsOnRemove)) {
|
|
idr->flags |= EcsIdHasOnRemove;
|
|
}
|
|
if (flecs_check_observers_for_event(world, id, EcsOnSet)) {
|
|
idr->flags |= EcsIdHasOnSet;
|
|
}
|
|
if (flecs_check_observers_for_event(world, id, EcsUnSet)) {
|
|
idr->flags |= EcsIdHasUnSet;
|
|
}
|
|
|
|
if (ecs_should_log_1()) {
|
|
char *id_str = ecs_id_str(world, id);
|
|
ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str);
|
|
ecs_os_free(id_str);
|
|
}
|
|
|
|
/* Update counters */
|
|
world->info.id_create_total ++;
|
|
|
|
if (!is_wildcard) {
|
|
world->info.id_count ++;
|
|
|
|
if (idr->type_info) {
|
|
world->info.component_id_count ++;
|
|
} else {
|
|
world->info.tag_id_count ++;
|
|
}
|
|
|
|
if (ECS_IS_PAIR(id)) {
|
|
world->info.pair_id_count ++;
|
|
}
|
|
} else {
|
|
world->info.wildcard_id_count ++;
|
|
}
|
|
|
|
return idr;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
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);
|
|
ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0),
|
|
ECS_ID_IN_USE, "cannot delete id that is in use");
|
|
|
|
if (ECS_IS_PAIR(id)) {
|
|
ecs_entity_t rel = ecs_pair_first(world, id);
|
|
ecs_entity_t obj = ECS_PAIR_SECOND(id);
|
|
if (!ecs_id_is_wildcard(id)) {
|
|
if (ECS_PAIR_FIRST(id) != EcsFlag) {
|
|
/* If id is not a wildcard, remove it from the wildcard lists */
|
|
flecs_remove_id_elem(idr, ecs_pair(rel, EcsWildcard));
|
|
flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, obj));
|
|
}
|
|
} 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 ++;
|
|
|
|
if (!ecs_id_is_wildcard(id)) {
|
|
world->info.id_count --;
|
|
|
|
if (ECS_IS_PAIR(id)) {
|
|
world->info.pair_id_count --;
|
|
}
|
|
|
|
if (idr->type_info) {
|
|
world->info.component_id_count --;
|
|
} else {
|
|
world->info.tag_id_count --;
|
|
}
|
|
} else {
|
|
world->info.wildcard_id_count --;
|
|
}
|
|
|
|
/* 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 >= ECS_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;
|
|
}
|
|
|
|
ecs_id_t hash = flecs_id_record_hash(id);
|
|
ecs_id_record_t *idr = NULL;
|
|
if (hash >= ECS_HI_ID_RECORD_ID) {
|
|
ecs_id_record_t **idr_ptr = ecs_map_get(&world->id_index_hi,
|
|
ecs_id_record_t*, hash);
|
|
ecs_assert(!idr_ptr || idr_ptr[0] != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
if (idr_ptr) {
|
|
idr = idr_ptr[0];
|
|
}
|
|
} else {
|
|
idr = flecs_sparse_get_any(&world->id_index_lo, ecs_id_record_t, hash);
|
|
if (idr && !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))) {
|
|
/* Tables can hold claims on each other, so releasing a table can
|
|
* cause the next element to get invalidated. Claim the next table
|
|
* so that we can safely iterate. */
|
|
ecs_table_t *next = NULL;
|
|
if (it.next) {
|
|
next = it.next->table;
|
|
flecs_table_claim(world, next);
|
|
}
|
|
|
|
/* Release current table */
|
|
flecs_table_release(world, tr->hdr.table);
|
|
|
|
/* Check if the only thing keeping the next table alive is our
|
|
* claim. If so, move to the next record before releasing */
|
|
if (next) {
|
|
if (next->refcount == 1) {
|
|
it.next = it.next->next;
|
|
}
|
|
|
|
flecs_table_release(world, next);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool flecs_id_record_set_type_info(
|
|
ecs_world_t *world,
|
|
ecs_id_record_t *idr,
|
|
const ecs_type_info_t *ti)
|
|
{
|
|
if (!ecs_id_is_wildcard(idr->id)) {
|
|
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_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);
|
|
|
|
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_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);
|
|
}
|
|
|
|
const 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)
|
|
{
|
|
ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi);
|
|
ecs_id_record_t *idr;
|
|
while ((idr = ecs_map_next_ptr(&it, ecs_id_record_t*, NULL))) {
|
|
flecs_id_record_release(world, idr);
|
|
}
|
|
|
|
int32_t i, count = flecs_sparse_count(&world->id_index_lo);
|
|
for (i = count - 1; i >= 0; i --) {
|
|
idr = flecs_sparse_get_dense(&world->id_index_lo,
|
|
ecs_id_record_t, 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);
|
|
flecs_sparse_fini(&world->id_index_lo);
|
|
flecs_sparse_free(world->pending_tables);
|
|
flecs_sparse_free(world->pending_buffer);
|
|
}
|
|
|