eco2d/code/vendors/flecs/flecs.c

23509 lines
659 KiB
C

#ifndef FLECS_IMPL
#include "flecs.h"
#endif
#ifndef FLECS_PRIVATE_H
#define FLECS_PRIVATE_H
#ifndef FLECS_TYPES_PRIVATE_H
#define FLECS_TYPES_PRIVATE_H
#ifndef __MACH__
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#endif
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <math.h>
#ifdef _MSC_VER
//FIXME
#else
#include <sys/param.h> /* attempt to define endianness */
#endif
#ifdef linux
# include <endian.h> /* attempt to define endianness */
#endif
/**
* @file 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 macro's for working with the
* entity index.
*/
#ifndef FLECS_ENTITY_INDEX_H
#define FLECS_ENTITY_INDEX_H
#ifdef __cplusplus
extern "C" {
#endif
#define ecs_eis_get(world, entity) ecs_sparse_get_sparse((world->store).entity_index, ecs_record_t, entity)
#define ecs_eis_get_any(world, entity) ecs_sparse_get_sparse_any((world->store).entity_index, ecs_record_t, entity)
#define ecs_eis_set(world, entity, ...) (ecs_sparse_set((world->store).entity_index, ecs_record_t, entity, (__VA_ARGS__)))
#define ecs_eis_get_or_create(world, entity) ecs_sparse_get_or_create((world->store).entity_index, ecs_record_t, entity)
#define ecs_eis_delete(world, entity) ecs_sparse_remove((world->store).entity_index, entity)
#define ecs_eis_set_generation(world, entity) ecs_sparse_set_generation((world->store).entity_index, entity)
#define ecs_eis_is_alive(world, entity) ecs_sparse_is_alive((world->store).entity_index, entity)
#define ecs_eis_exists(world, entity) ecs_sparse_exists((world->store).entity_index, entity)
#define ecs_eis_recycle(world) ecs_sparse_new_id((world->store).entity_index)
#define ecs_eis_clear_entity(world, entity, is_watched) ecs_eis_set((world->store).entity_index, entity, &(ecs_record_t){NULL, is_watched})
#define ecs_eis_set_size(world, size) ecs_sparse_set_size((world->store).entity_index, size)
#define ecs_eis_count(world) ecs_sparse_count((world->store).entity_index)
#define ecs_eis_clear(world) ecs_sparse_clear((world->store).entity_index)
#define ecs_eis_copy(world) ecs_sparse_copy((world->store).entity_index)
#define ecs_eis_free(world) ecs_sparse_free((world->store).entity_index)
#define ecs_eis_memory(world, allocd, used) ecs_sparse_memory((world->store).entity_index, allocd, used)
#ifdef __cplusplus
}
#endif
#endif
#define ECS_MAX_JOBS_PER_WORKER (16)
/** These values are used to verify validity of the pointers passed into the API
* and to allow for passing a thread as a world to some API calls (this allows
* for transparently passing thread context to API functions) */
#define ECS_WORLD_MAGIC (0x65637377)
#define ECS_THREAD_MAGIC (0x65637374)
/* Maximum number of entities that can be added in a single operation.
* Increasing this value will increase consumption of stack space. */
#define ECS_MAX_ADD_REMOVE (32)
/* Maximum length of an entity name, including 0 terminator */
#define ECS_MAX_NAME_LENGTH (64)
/** Callback used by the system signature expression parser. */
typedef int (*ecs_parse_action_t)(
ecs_world_t *world,
const char *id,
const char *expr,
int64_t column,
ecs_sig_from_kind_t from_kind,
ecs_sig_oper_kind_t oper_kind,
ecs_sig_inout_kind_t inout_kind,
ecs_entity_t flags,
const char *component,
const char *source,
const char *trait,
const char *name,
void *ctx);
/** Component-specific data */
typedef struct ecs_c_info_t {
ecs_entity_t component;
ecs_vector_t *on_add; /* Systems ran after adding this component */
ecs_vector_t *on_remove; /* Systems ran after removing this component */
EcsComponentLifecycle lifecycle; /* Component lifecycle callbacks */
bool lifecycle_set;
} ecs_c_info_t;
/* Table event type for notifying tables of world events */
typedef enum ecs_table_eventkind_t {
EcsTableQueryMatch,
EcsTableQueryUnmatch,
EcsTableComponentInfo
} ecs_table_eventkind_t;
typedef struct ecs_table_event_t {
ecs_table_eventkind_t kind;
/* Query event */
ecs_query_t *query;
int32_t matched_table_index;
/* Component info event */
ecs_entity_t component;
/* 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;
/** A component column. */
struct ecs_column_t {
ecs_vector_t *data; /**< Column data */
int16_t size; /**< Column element size */
int16_t alignment; /**< Column element alignment */
};
/** A switch column. */
typedef struct ecs_sw_column_t {
ecs_switch_t *data; /**< Column data */
ecs_type_t type; /**< Switch type */
} ecs_sw_column_t;
/** A bitset column. */
typedef struct ecs_bs_column_t {
ecs_bitset_t data; /**< Column data */
} ecs_bs_column_t;
/** Stage-specific component data */
struct ecs_data_t {
ecs_vector_t *entities; /**< Entity identifiers */
ecs_vector_t *record_ptrs; /**< Ptrs to records in main entity index */
ecs_column_t *columns; /**< Component columns */
ecs_sw_column_t *sw_columns; /**< Switch columns */
ecs_bs_column_t *bs_columns; /**< Bitset columns */
};
/** 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;
/** Flags for quickly checking for special properties of a table. */
#define EcsTableHasBuiltins 1u /**< Does table have builtin components */
#define EcsTableIsPrefab 2u /**< Does the table store prefabs */
#define EcsTableHasBase 4u /**< Does the table type has INSTANCEOF */
#define EcsTableHasParent 8u /**< Does the table type has CHILDOF */
#define EcsTableHasComponentData 16u /**< Does the table have component data */
#define EcsTableHasXor 32u /**< Does the table type has XOR */
#define EcsTableIsDisabled 64u /**< Does the table type has EcsDisabled */
#define EcsTableHasCtors 128u
#define EcsTableHasDtors 256u
#define EcsTableHasCopy 512u
#define EcsTableHasMove 1024u
#define EcsTableHasOnAdd 2048u
#define EcsTableHasOnRemove 4096u
#define EcsTableHasOnSet 8192u
#define EcsTableHasUnSet 16384u
#define EcsTableHasMonitors 32768u
#define EcsTableHasSwitch 65536u
#define EcsTableHasDisabled 131072u
/* Composite constants */
#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors)
#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasSwitch | EcsTableHasDisabled)
#define EcsTableHasAddActions (EcsTableHasBase | EcsTableHasSwitch | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet | EcsTableHasMonitors)
#define EcsTableHasRemoveActions (EcsTableHasBase | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet | EcsTableHasMonitors)
/** Edge used for traversing the table graph. */
typedef struct ecs_edge_t {
ecs_table_t *add; /**< Edges traversed when adding */
ecs_table_t *remove; /**< Edges traversed when removing */
} ecs_edge_t;
/** Quey matched with table with backref to query table administration.
* This type is used to store a matched query together with the array index of
* where the table is stored in the query administration. This type is used when
* an action that originates on a table needs to invoke a query (system) and a
* fast lookup is required for the query administration, as is the case with
* OnSet and Monitor systems. */
typedef struct ecs_matched_query_t {
ecs_query_t *query; /**< The query matched with the table */
int32_t matched_table_index; /**< Table index in the query type */
} ecs_matched_query_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 {
ecs_type_t type; /**< Identifies table type in type_index */
ecs_c_info_t **c_info; /**< Cached pointers to component info */
ecs_edge_t *lo_edges; /**< Edges to other tables */
ecs_map_t *hi_edges;
ecs_data_t *data; /**< Component storage */
ecs_vector_t *queries; /**< Queries matched with table */
ecs_vector_t *monitors; /**< Monitor systems matched with table */
ecs_vector_t **on_set; /**< OnSet systems, broken up by column */
ecs_vector_t *on_set_all; /**< All OnSet systems */
ecs_vector_t *on_set_override; /**< All OnSet systems with overrides */
ecs_vector_t *un_set_all; /**< All UnSet systems */
int32_t *dirty_state; /**< Keep track of changes in columns */
int32_t alloc_count; /**< Increases when columns are reallocd */
uint32_t id; /**< Table id in sparse set */
ecs_flags32_t flags; /**< Flags for testing table properties */
int32_t column_count; /**< Number of data columns in table */
int32_t sw_column_count;
int32_t sw_column_offset;
int32_t bs_column_count;
int32_t bs_column_offset;
};
/* Sparse query column */
typedef struct ecs_sparse_column_t {
ecs_sw_column_t *sw_column;
ecs_entity_t sw_case;
int32_t signature_column_index;
} ecs_sparse_column_t;
/* Bitset query column */
typedef struct ecs_bitset_column_t {
ecs_bs_column_t *bs_column;
int32_t column_index;
} ecs_bitset_column_t;
/** Type containing data for a table matched with a query. */
typedef struct ecs_matched_table_t {
ecs_iter_table_t iter_data; /**< Precomputed data for iterators */
ecs_vector_t *sparse_columns; /**< Column ids of sparse columns */
ecs_vector_t *bitset_columns; /**< Column ids with disabled flags */
int32_t *monitor; /**< Used to monitor table for changes */
int32_t rank; /**< Rank used to sort tables */
} ecs_matched_table_t;
/** Type used to track location of table in queries' table lists.
* When a table becomes empty or non-empty a signal is sent to a query, which
* moves the table to or from an empty list. While this ensures that when
* iterating no time is spent on iterating over empty tables, doing a linear
* search for the table in either list can take a significant amount of time if
* a query is matched with many tables.
*
* To avoid a linear search, the query has a map with table indices that can
* return the location of the table in either list in constant time.
*
* If a table is matched multiple times by a query, such as can happen when a
* query matches traits, a table can occupy multiple indices.
*/
typedef struct ecs_table_indices_t {
int32_t *indices; /* If indices are negative, table is in empty list */
int32_t count;
} ecs_table_indices_t;
/** Type storing an entity range within a table.
* This type is used for iterating in orer across archetypes. A sorting function
* constructs a list of the ranges across archetypes that are in order so that
* when the query iterates over the archetypes, it only needs to iterate the
* list of ranges. */
typedef struct ecs_table_slice_t {
ecs_matched_table_t *table; /**< Reference to the matched table */
int32_t start_row; /**< Start of range */
int32_t count; /**< Number of entities in range */
} ecs_table_slice_t;
#define EcsQueryNeedsTables (1) /* Query needs matching with tables */
#define EcsQueryMonitor (2) /* Query needs to be registered as a monitor */
#define EcsQueryOnSet (4) /* Query needs to be registered as on_set system */
#define EcsQueryUnSet (8) /* Query needs to be registered as un_set system */
#define EcsQueryMatchDisabled (16) /* Does query match disabled */
#define EcsQueryMatchPrefab (32) /* Does query match prefabs */
#define EcsQueryHasRefs (64) /* Does query have references */
#define EcsQueryHasTraits (128) /* Does query have traits */
#define EcsQueryIsSubquery (256) /* Is query a subquery */
#define EcsQueryIsOrphaned (512) /* Is subquery orphaned */
#define EcsQueryHasOutColumns (1024) /* Does query have out columns */
#define EcsQueryHasOptional (2048) /* Does query have optional columns */
#define EcsQueryNoActivation (EcsQueryMonitor | EcsQueryOnSet | EcsQueryUnSet)
/* Query event type for notifying queries of world events */
typedef enum ecs_query_eventkind_t {
EcsQueryTableMatch,
EcsQueryTableEmpty,
EcsQueryTableNonEmpty,
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 that is automatically matched against active tables */
struct ecs_query_t {
/* Signature of query */
ecs_sig_t sig;
/* Reference to world */
ecs_world_t *world;
/* Tables matched with query */
ecs_vector_t *tables;
ecs_vector_t *empty_tables;
ecs_map_t *table_indices;
/* Handle to system (optional) */
ecs_entity_t system;
/* Used for sorting */
ecs_entity_t sort_on_component;
ecs_compare_action_t compare;
ecs_vector_t *table_slices;
/* Used for table sorting */
ecs_entity_t rank_on_component;
ecs_rank_type_action_t group_table;
/* Subqueries */
ecs_query_t *parent;
ecs_vector_t *subqueries;
/* The query kind determines how it is registered with tables */
ecs_flags32_t flags;
int32_t cascade_by; /* Identify CASCADE column */
int32_t match_count; /* How often have tables been (un)matched */
int32_t prev_match_count; /* Used to track if sorting is needed */
bool needs_reorder; /* Whether next iteration should reorder */
};
/** Keep track of how many [in] columns are active for [out] columns of OnDemand
* systems. */
typedef struct ecs_on_demand_out_t {
ecs_entity_t system; /* Handle to system */
int32_t count; /* Total number of times [out] columns are used */
} ecs_on_demand_out_t;
/** Keep track of which OnDemand systems are matched with which [in] columns */
typedef struct ecs_on_demand_in_t {
int32_t count; /* Number of active systems with [in] column */
ecs_vector_t *systems; /* Systems that have this column as [out] column */
} ecs_on_demand_in_t;
/** Types for deferred operations */
typedef enum ecs_op_kind_t {
EcsOpNew,
EcsOpClone,
EcsOpBulkNew,
EcsOpAdd,
EcsOpRemove,
EcsOpSet,
EcsOpMut,
EcsOpModified,
EcsOpDelete,
EcsOpClear,
EcsOpEnable,
EcsOpDisable
} ecs_op_kind_t;
typedef struct ecs_op_1_t {
ecs_entity_t entity; /* Entity id */
void *value; /* Value (used for set / get_mut) */
ecs_size_t size; /* Size of value */
bool clone_value; /* Clone entity with value (used for clone) */
} ecs_op_1_t;
typedef struct ecs_op_n_t {
ecs_entity_t *entities;
void **bulk_data;
int32_t count;
} ecs_op_n_t;
typedef struct ecs_op_t {
ecs_op_kind_t kind; /* Operation kind */
ecs_entity_t scope; /* Scope of operation (for new) */
ecs_entity_t component; /* Single component (components.count = 1) */
ecs_entities_t components; /* Multiple components */
union {
ecs_op_1_t _1;
ecs_op_n_t _n;
} is;
} ecs_op_t;
/** A stage is a data structure in which delta's are stored until it is safe to
* merge those delta's with the main world stage. A stage allows flecs systems
* to arbitrarily add/remove/set components and create/delete entities while
* iterating. Additionally, worker threads have their own stage that lets them
* mutate the state of entities without requiring locks. */
struct ecs_stage_t {
/* This points to the world pointer associated with the stage. Even though
* stages belong to the same world, when multithreaded, an application will
* receive a pointer not to the world, but to a thread. This allows for
* transparently passing the thread context without having to fallback on
* more expensive methods such as thread local storage. This world pointer
* is stored in the stage, so that it can be easily passed around when for
* example invoking callbacks, and prevents the API from passing around two
* world pointers (or constantly obtaining the real world when needed). */
ecs_world_t *world;
int32_t id; /* Unique id that identifies the stage */
/* Are operations deferred? */
int32_t defer;
ecs_vector_t *defer_queue;
ecs_vector_t *defer_merge_queue;
/* One-shot actions to be executed after the merge */
ecs_vector_t *post_frame_actions;
/* Namespacing */
ecs_table_t *scope_table; /* Table for current scope */
ecs_entity_t scope; /* Entity of current scope */
/* If a system is progressing it will set this field to its columns. This
* will be used in debug mode to verify that a system is not doing
* unanounced adding/removing of components, as this could cause
* unpredictable behavior during a merge. */
#ifndef NDEBUG
ecs_entity_t system;
ecs_vector_t *system_columns;
#endif
};
typedef struct ecs_store_t {
/* Entity lookup table for (table, row) */
ecs_sparse_t *entity_index;
/* Table graph */
ecs_sparse_t *tables;
ecs_table_t root;
/* Lookup map for tables */
ecs_map_t *table_map;
} ecs_store_t;
/** Supporting type to store looked up or derived entity data */
typedef struct ecs_entity_info_t {
ecs_record_t *record; /* Main stage record in entity index */
ecs_table_t *table; /* Table. Not set if entity is empty */
ecs_data_t *data; /* Stage-specific table columns */
int32_t row; /* Actual row (stripped from is_watched bit) */
bool is_watched; /* Is entity being watched */
} ecs_entity_info_t;
/** A type desribing a worker thread. When a system is invoked by a worker
* thread, it receives a pointer to an ecs_thread_t instead of a pointer to an
* ecs_world_t (provided by the ecs_iter_t type). When this ecs_thread_t is passed down
* into the flecs API, the API functions are able to tell whether this is an
* ecs_thread_t or an ecs_world_t by looking at the 'magic' number. This allows the
* API to transparently resolve the stage to which updates should be written,
* without requiring different API calls when working in multi threaded mode. */
typedef struct ecs_thread_t {
int32_t magic; /* Magic number to verify thread pointer */
ecs_world_t *world; /* Reference to world */
ecs_stage_t *stage; /* Stage for thread */
ecs_os_thread_t thread; /* Thread handle */
int32_t index; /* Index of thread */
} ecs_thread_t;
/** Supporting type to store looked up component data in specific table */
typedef struct ecs_column_info_t {
ecs_entity_t id;
ecs_c_info_t *ci;
int32_t column;
} ecs_column_info_t;
/* Component monitors */
typedef struct ecs_component_monitor_t {
bool dirty_flags[ECS_HI_COMPONENT_ID];
ecs_vector_t *monitors[ECS_HI_COMPONENT_ID];
bool rematch;
} ecs_component_monitor_t;
/* fini actions */
typedef struct ecs_action_elem_t {
ecs_fini_action_t action;
void *ctx;
} ecs_action_elem_t;
/* Alias */
typedef struct ecs_alias_t {
char *name;
ecs_entity_t entity;
} ecs_alias_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 {
int32_t magic; /* Magic number to verify world pointer */
void *context; /* Application context */
ecs_vector_t *fini_actions; /* Callbacks to execute when world exits */
ecs_c_info_t c_info[ECS_HI_COMPONENT_ID]; /* Component callbacks & triggers */
ecs_map_t *t_info; /* Tag triggers */
/* Is entity range checking enabled? */
bool range_check_enabled;
/* -- Data storage -- */
ecs_store_t store;
/* -- Queries -- */
/* Persistent queries registered with the world. Persistent queries are
* stateful and automatically matched with existing and new tables. */
ecs_vector_t *queries;
/* Keep track of components that were added/removed to/from monitored
* entities. Monitored entities are entities that a query has matched with
* specifically, as is the case with PARENT / CASCADE columns, FromEntity
* columns and columns matched from prefabs.
* When these entities change type, queries may have to be rematched.
* Queries register themselves as component monitors for specific components
* and when these components change they are rematched. The component
* monitors are evaluated during a merge. */
ecs_component_monitor_t component_monitors;
/* Parent monitors are like normal component monitors except that the
* conditions under which a parent component is flagged dirty is different.
* Parent component flags are marked dirty when an entity that is a parent
* adds or removes a CHILDOF flag. In that case, every component of that
* parent will be marked dirty. This allows column modifiers like CASCADE
* to correctly determine when the depth ranking of a table has changed. */
ecs_component_monitor_t parent_monitors;
/* -- Systems -- */
ecs_entity_t pipeline; /* Current pipeline */
ecs_map_t *on_activate_components; /* Trigger on activate of [in] column */
ecs_map_t *on_enable_components; /* Trigger on enable of [in] column */
ecs_vector_t *fini_tasks; /* Tasks to execute on ecs_fini */
/* -- Lookup Indices -- */
ecs_map_t *type_handles; /* Handles to named types */
/* -- Aliasses -- */
ecs_vector_t *aliases;
/* -- Staging -- */
ecs_stage_t stage; /* Main storage */
ecs_stage_t temp_stage; /* Stage for when processing systems */
ecs_vector_t *worker_stages; /* Stages for worker threads */
int32_t stage_count; /* Number of stages in world */
/* -- Hierarchy administration -- */
ecs_map_t *child_tables; /* Child tables per parent entity */
const char *name_prefix; /* Remove prefix from C names in modules */
/* -- Multithreading -- */
ecs_vector_t *workers; /* Worker threads */
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 */
FLECS_FLOAT fps_sleep; /* Sleep time to prevent fps overshoot */
/* -- Metrics -- */
ecs_world_info_t stats;
/* -- Settings from command line arguments -- */
int arg_fps;
int arg_threads;
/* -- World lock -- */
ecs_os_mutex_t mutex; /* Locks the world if locking enabled */
ecs_os_mutex_t thr_sync; /* Used to signal threads at end of frame */
ecs_os_cond_t thr_cond; /* Used to signal threads at end of frame */
/* -- Defered operation count -- */
int32_t new_count;
int32_t bulk_new_count;
int32_t delete_count;
int32_t clear_count;
int32_t add_count;
int32_t remove_count;
int32_t set_count;
int32_t discard_count;
/* -- World state -- */
bool quit_workers; /* Signals worker threads to quit */
bool in_progress; /* Is world being progressed */
bool is_merging; /* Is world currently being merged */
bool is_fini; /* Is the world being cleaned up? */
bool auto_merge; /* Are stages auto-merged by ecs_progress */
bool measure_frame_time; /* Time spent on each frame */
bool measure_system_time; /* Time spent by each system */
bool should_quit; /* Did a system signal that app should quit */
bool locking_enabled; /* Lock world when in progress */
};
#endif
////////////////////////////////////////////////////////////////////////////////
//// Core bootstrap functions
////////////////////////////////////////////////////////////////////////////////
#define ECS_TYPE_DECL(component)\
static const ecs_entity_t __##component = ecs_typeid(component);\
ECS_VECTOR_DECL(FLECS__T##component, ecs_entity_t, 1)
#define ECS_TYPE_IMPL(component)\
ECS_VECTOR_IMPL(FLECS__T##component, ecs_entity_t, &__##component, 1)
/* Bootstrap world */
void ecs_bootstrap(
ecs_world_t *world);
ecs_type_t ecs_bootstrap_type(
ecs_world_t *world,
ecs_entity_t entity);
#define ecs_bootstrap_component(world, name)\
ecs_new_component(world, ecs_typeid(name), #name, sizeof(name), ECS_ALIGNOF(name))
#define ecs_bootstrap_tag(world, name)\
ecs_set(world, name, EcsName, {.value = &#name[ecs_os_strlen("Ecs")], .symbol = (char*)#name});\
ecs_add_entity(world, name, ECS_CHILDOF | ecs_get_scope(world))
////////////////////////////////////////////////////////////////////////////////
//// Entity API
////////////////////////////////////////////////////////////////////////////////
/* Mark an entity as being watched. This is used to trigger automatic rematching
* when entities used in system expressions change their components. */
void ecs_set_watch(
ecs_world_t *world,
ecs_entity_t entity);
/* Does one of the entity containers has specified component */
ecs_entity_t ecs_find_in_type(
ecs_world_t *world,
ecs_type_t table_type,
ecs_entity_t component,
ecs_entity_t flags);
/* Obtain entity info */
bool ecs_get_info(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_info_t *info);
void ecs_run_monitors(
ecs_world_t *world,
ecs_table_t *dst_table,
ecs_vector_t *v_dst_monitors,
int32_t dst_row,
int32_t count,
ecs_vector_t *v_src_monitors);
////////////////////////////////////////////////////////////////////////////////
//// World API
////////////////////////////////////////////////////////////////////////////////
/* Notify systems that there is a new table, which triggers matching */
void ecs_notify_queries_of_table(
ecs_world_t *world,
ecs_table_t *table);
/* Get current thread-specific stage */
ecs_stage_t *ecs_get_stage(
ecs_world_t **world_ptr);
/* Get component callbacks */
ecs_c_info_t *ecs_get_c_info(
ecs_world_t *world,
ecs_entity_t component);
/* Get or create component callbacks */
ecs_c_info_t * ecs_get_or_create_c_info(
ecs_world_t *world,
ecs_entity_t component);
void ecs_eval_component_monitors(
ecs_world_t *world);
void ecs_component_monitor_mark(
ecs_component_monitor_t *mon,
ecs_entity_t component);
void ecs_component_monitor_register(
ecs_component_monitor_t *mon,
ecs_entity_t component,
ecs_query_t *query);
void ecs_notify_tables(
ecs_world_t *world,
ecs_table_event_t *event);
void ecs_notify_queries(
ecs_world_t *world,
ecs_query_event_t *event);
////////////////////////////////////////////////////////////////////////////////
//// Stage API
////////////////////////////////////////////////////////////////////////////////
/* Initialize stage data structures */
void ecs_stage_init(
ecs_world_t *world,
ecs_stage_t *stage);
/* Deinitialize stage */
void ecs_stage_deinit(
ecs_world_t *world,
ecs_stage_t *stage);
/* Merge stage with main stage */
void ecs_stage_merge(
ecs_world_t *world,
ecs_stage_t *stage);
/* Post-frame merge actions */
void ecs_stage_merge_post_frame(
ecs_world_t *world,
ecs_stage_t *stage);
/* Begin defer for stage */
bool ecs_stage_defer_begin(
ecs_world_t *world,
ecs_stage_t *stage);
bool ecs_stage_defer_end(
ecs_world_t *world,
ecs_stage_t *stage);
/* Delete table from stage */
void ecs_delete_table(
ecs_world_t *world,
ecs_table_t *table);
////////////////////////////////////////////////////////////////////////////////
//// Defer API
////////////////////////////////////////////////////////////////////////////////
bool ecs_defer_none(
ecs_world_t *world,
ecs_stage_t *stage);
bool ecs_defer_modified(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entity_t component);
bool ecs_defer_new(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entities_t *components);
bool ecs_defer_clone(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entity_t src,
bool clone_value);
bool ecs_defer_bulk_new(
ecs_world_t *world,
ecs_stage_t *stage,
int32_t count,
ecs_entities_t *components,
void **component_data,
const ecs_entity_t **ids_out);
bool ecs_defer_delete(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity);
bool ecs_defer_clear(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity);
bool ecs_defer_enable(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entity_t component,
bool enable);
bool ecs_defer_add(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entities_t *components);
bool ecs_defer_remove(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entities_t *components);
bool ecs_defer_set(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_op_kind_t op_kind,
ecs_entity_t entity,
ecs_entity_t component,
ecs_size_t size,
const void *value,
void **value_out,
bool *is_added);
bool ecs_defer_flush(
ecs_world_t *world,
ecs_stage_t *stage);
////////////////////////////////////////////////////////////////////////////////
//// Type API
////////////////////////////////////////////////////////////////////////////////
/* Merge add/remove types */
ecs_type_t ecs_type_merge_intern(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_type_t cur_id,
ecs_type_t to_add_id,
ecs_type_t to_remove_id);
/* Test if type_id_1 contains type_id_2 */
ecs_entity_t ecs_type_contains(
ecs_world_t *world,
ecs_type_t type_id_1,
ecs_type_t type_id_2,
bool match_all,
bool match_prefab);
/* Add component to type */
ecs_type_t ecs_type_add_intern(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_type_t type,
ecs_entity_t component);
/* Find entity in prefabs of type */
ecs_entity_t ecs_find_entity_in_prefabs(
ecs_world_t *world,
ecs_entity_t entity,
ecs_type_t type,
ecs_entity_t component,
ecs_entity_t previous);
void ecs_get_column_info(
ecs_world_t *world,
ecs_table_t *table,
ecs_entities_t *components,
ecs_column_info_t *cinfo,
bool get_all);
void ecs_run_add_actions(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t row,
int32_t count,
ecs_entities_t *added,
bool get_all,
bool run_on_set);
void ecs_run_remove_actions(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t row,
int32_t count,
ecs_entities_t *removed,
bool get_all);
void ecs_run_set_systems(
ecs_world_t *world,
ecs_entities_t *components,
ecs_table_t *table,
ecs_data_t *data,
int32_t row,
int32_t count,
bool set_all);
////////////////////////////////////////////////////////////////////////////////
//// Table API
////////////////////////////////////////////////////////////////////////////////
/** Find or create table for a set of components */
ecs_table_t* ecs_table_find_or_create(
ecs_world_t *world,
ecs_entities_t *type);
/* Get table data */
ecs_data_t *ecs_table_get_data(
ecs_table_t *table);
/* Get or create data */
ecs_data_t *ecs_table_get_or_create_data(
ecs_table_t *table);
/* Initialize columns for data */
ecs_data_t* ecs_init_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *result);
/* Activates / deactivates table for systems. A deactivated table will not be
* evaluated when the system is invoked. Tables automatically get activated /
* deactivated when they become non-empty / empty.
*
* If a query is provided, the table will only be activated / deactivated for
* that query. */
void ecs_table_activate(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query,
bool activate);
/* Clear all entities from a table. */
void ecs_table_clear(
ecs_world_t *world,
ecs_table_t *table);
/* Reset a table to its initial state */
void ecs_table_reset(
ecs_world_t *world,
ecs_table_t *table);
/* Clear all entities from the table. Do not invoke OnRemove systems */
void ecs_table_clear_silent(
ecs_world_t *world,
ecs_table_t *table);
/* Clear table data. Don't call OnRemove handlers. */
void ecs_table_clear_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data);
/* Return number of entities in data */
int32_t ecs_table_data_count(
ecs_data_t *data);
/* Add a new entry to the table for the specified entity */
int32_t ecs_table_append(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
ecs_entity_t entity,
ecs_record_t *record,
bool construct);
/* Delete an entity from the table. */
void ecs_table_delete(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t index,
bool destruct);
/* Move a row from one table to another */
void ecs_table_move(
ecs_world_t *world,
ecs_entity_t dst_entity,
ecs_entity_t src_entity,
ecs_table_t *new_table,
ecs_data_t *new_data,
int32_t new_index,
ecs_table_t *old_table,
ecs_data_t *old_data,
int32_t old_index);
/* Grow table with specified number of records. Populate table with entities,
* starting from specified entity id. */
int32_t ecs_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 ecs_table_set_size(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t count);
/* Match table with filter */
bool ecs_table_match_filter(
ecs_world_t *world,
ecs_table_t *table,
const ecs_filter_t *filter);
/* Get dirty state for table columns */
int32_t* ecs_table_get_dirty_state(
ecs_table_t *table);
/* Get monitor for monitoring table changes */
int32_t* ecs_table_get_monitor(
ecs_table_t *table);
/* Initialize root table */
void ecs_init_root_table(
ecs_world_t *world);
/* Unset components in table */
void ecs_table_unset(
ecs_world_t *world,
ecs_table_t *table);
/* Free table */
void ecs_table_free(
ecs_world_t *world,
ecs_table_t *table);
/* Merge table data */
void ecs_table_merge_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data);
/* Replace data */
void ecs_table_replace_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data);
/* Merge data of one table into another table */
ecs_data_t* ecs_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 ecs_table_swap(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t row_1,
int32_t row_2);
ecs_table_t *ecs_table_traverse_add(
ecs_world_t *world,
ecs_table_t *table,
ecs_entities_t *to_add,
ecs_entities_t *added);
ecs_table_t *ecs_table_traverse_remove(
ecs_world_t *world,
ecs_table_t *table,
ecs_entities_t *to_remove,
ecs_entities_t *removed);
void ecs_table_mark_dirty(
ecs_table_t *table,
ecs_entity_t component);
const EcsComponent* ecs_component_from_id(
ecs_world_t *world,
ecs_entity_t e);
int32_t ecs_table_switch_from_case(
ecs_world_t *world,
ecs_table_t *table,
ecs_entity_t add);
void ecs_table_notify(
ecs_world_t *world,
ecs_table_t *table,
ecs_table_event_t *event);
void ecs_table_clear_edges(
ecs_world_t *world,
ecs_table_t *table);
////////////////////////////////////////////////////////////////////////////////
//// Query API
////////////////////////////////////////////////////////////////////////////////
void ecs_query_set_iter(
ecs_world_t *world,
ecs_query_t *query,
ecs_iter_t *it,
int32_t table_index,
int32_t row,
int32_t count);
void ecs_query_rematch(
ecs_world_t *world,
ecs_query_t *query);
void ecs_run_monitor(
ecs_world_t *world,
ecs_matched_query_t *monitor,
ecs_entities_t *components,
int32_t row,
int32_t count,
ecs_entity_t *entities);
bool ecs_query_match(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query,
ecs_match_failure_t *failure_info);
void ecs_query_notify(
ecs_world_t *world,
ecs_query_t *query,
ecs_query_event_t *event);
////////////////////////////////////////////////////////////////////////////////
//// Signature API
////////////////////////////////////////////////////////////////////////////////
/* Check if all non-table column constraints are met */
bool ecs_sig_check_constraints(
ecs_world_t *world,
ecs_sig_t *sig);
////////////////////////////////////////////////////////////////////////////////
//// Time API
////////////////////////////////////////////////////////////////////////////////
void ecs_os_time_setup(void);
uint64_t ecs_os_time_now(void);
void ecs_os_time_sleep(
int32_t sec,
int32_t nanosec);
/* Increase or reset timer resolution (Windows only) */
FLECS_API
void ecs_increase_timer_resolution(
bool enable);
////////////////////////////////////////////////////////////////////////////////
//// Utilities
////////////////////////////////////////////////////////////////////////////////
void ecs_hash(
const void *data,
ecs_size_t length,
uint64_t *result);
/* Convert 64 bit signed integer to 16 bit */
int8_t ecs_to_i8(
int64_t v);
/* Convert 64 bit signed integer to 16 bit */
int16_t ecs_to_i16(
int64_t v);
/* Convert 64 bit unsigned integer to 32 bit */
uint32_t ecs_to_u32(
uint64_t v);
/* Convert signed integer to size_t */
size_t ecs_to_size_t(
int64_t size);
/* Convert size_t to ecs_size_t */
ecs_size_t ecs_from_size_t(
size_t size);
/* Get next power of 2 */
int32_t ecs_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 ecs_to_row(
uint64_t value);
/* Get 64bit integer from ecs_record_t */
uint64_t ecs_from_row(
ecs_record_t record);
/* Get actual row from record row */
int32_t ecs_record_to_row(
int32_t row,
bool *is_watched_out);
/* Convert actual row to record row */
int32_t ecs_row_to_record(
int32_t row,
bool is_watched);
/* Convert type to entity array */
ecs_entities_t ecs_type_to_entities(
ecs_type_t type);
/* Convert a symbol name to an entity name by removing the prefix */
const char* ecs_name_from_symbol(
ecs_world_t *world,
const char *type_name);
/* Lookup an entity by name with a specific id */
ecs_entity_t ecs_lookup_w_id(
ecs_world_t *world,
ecs_entity_t e,
const char *name);
/* Set entity name with symbol */
void ecs_set_symbol(
ecs_world_t *world,
ecs_entity_t e,
const char *name);
/* Utility that print a descriptive error string*/
//void ecs_print_error_string(const char *error_description, const char* signature, const char* system_id, const char* component_id);
//void ecs_print_error_string(const char* signature, const char *system_id, const char *error_description, const char *component_id);
/* Utility that parses system signature */
int ecs_parse_expr(
ecs_world_t *world,
const char *name,
const char *sig,
ecs_parse_action_t action,
void *ctx);
#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);
#endif
static
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, ecs_to_size_t(size), 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;
}
static
char* ecs_colorize(
char *msg)
{
ecs_strbuf_t buff = ECS_STRBUF_INIT;
char *ptr, ch, prev = '\0';
bool isNum = false;
char isStr = '\0';
bool isVar = false;
bool overrideColor = false;
bool autoColor = true;
bool dontAppend = false;
bool use_colors = true;
for (ptr = msg; (ch = *ptr); ptr++) {
dontAppend = false;
if (!overrideColor) {
if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL);
isNum = false;
}
if (isStr && (isStr == ch) && prev != '\\') {
isStr = '\0';
} else if (((ch == '\'') || (ch == '"')) && !isStr &&
!isalpha(prev) && (prev != '\\'))
{
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN);
isStr = ch;
}
if ((isdigit(ch) || (ch == '%' && isdigit(prev)) ||
(ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar &&
!isalpha(prev) && !isdigit(prev) && (prev != '_') &&
(prev != '.'))
{
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_GREEN);
isNum = true;
}
if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL);
isVar = false;
}
if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) {
if (use_colors) ecs_strbuf_appendstr(&buff, 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 (use_colors) ecs_strbuf_appendstr(&buff, ECS_GREEN);
} else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_RED);
} else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_BLUE);
} else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_MAGENTA);
} else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN);
} else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_YELLOW);
} else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_GREY);
} else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL);
} else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_BOLD);
} else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL);
} else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) {
overrideColor = false;
if (use_colors) ecs_strbuf_appendstr(&buff, 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 (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL);
overrideColor = false;
isNum = false;
isStr = false;
isVar = false;
}
}
if (!dontAppend) {
ecs_strbuf_appendstrn(&buff, ptr, 1);
}
if (!overrideColor) {
if (((ch == '\'') || (ch == '"')) && !isStr) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL);
}
}
prev = ch;
}
if (isNum || isStr || isVar || overrideColor) {
if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL);
}
return ecs_strbuf_get(&buff);
}
static int trace_indent = 0;
static int trace_level = 0;
static
void ecs_log_print(
int level,
const char *file,
int32_t line,
const char *fmt,
va_list valist)
{
(void)level;
(void)line;
if (level > trace_level) {
return;
}
/* Massage filename so it doesn't take up too much space */
char filebuff[256];
ecs_os_strcpy(filebuff, file);
file = filebuff;
char *file_ptr = strrchr(file, '/');
if (file_ptr) {
file = file_ptr + 1;
}
/* Extension is likely the same for all files */
file_ptr = strrchr(file, '.');
if (file_ptr) {
*file_ptr = '\0';
}
char indent[32];
int i;
for (i = 0; i < trace_indent; i ++) {
indent[i * 2] = '|';
indent[i * 2 + 1] = ' ';
}
indent[i * 2] = '\0';
char *msg = ecs_vasprintf(fmt, valist);
char *color_msg = ecs_colorize(msg);
if (level >= 0) {
ecs_os_log("%sinfo%s: %s%s%s%s",
ECS_MAGENTA, ECS_NORMAL, ECS_GREY, indent, ECS_NORMAL, color_msg);
} else if (level == -2) {
ecs_os_warn("%swarn%s: %s%s%s%s",
ECS_YELLOW, ECS_NORMAL, ECS_GREY, indent, ECS_NORMAL, color_msg);
} else if (level <= -2) {
ecs_os_err("%serr %s: %s%s%s%s",
ECS_RED, ECS_NORMAL, ECS_GREY, indent, ECS_NORMAL, color_msg);
}
ecs_os_free(color_msg);
ecs_os_free(msg);
}
void _ecs_trace(
int level,
const char *file,
int32_t line,
const char *fmt,
...)
{
va_list valist;
va_start(valist, fmt);
ecs_log_print(level, file, line, fmt, valist);
}
void _ecs_warn(
const char *file,
int32_t line,
const char *fmt,
...)
{
va_list valist;
va_start(valist, fmt);
ecs_log_print(-2, file, line, fmt, valist);
}
void _ecs_err(
const char *file,
int32_t line,
const char *fmt,
...)
{
va_list valist;
va_start(valist, fmt);
ecs_log_print(-3, file, line, fmt, valist);
}
void ecs_log_push(void) {
trace_indent ++;
}
void ecs_log_pop(void) {
trace_indent --;
}
void ecs_tracing_enable(
int level)
{
trace_level = level;
}
void _ecs_parser_error(
const char *name,
const char *expr,
int64_t column,
const char *fmt,
...)
{
if (trace_level >= -2) {
va_list valist;
va_start(valist, fmt);
char *msg = ecs_vasprintf(fmt, valist);
ecs_os_err("%s:%d: error: %s", name, column + 1, msg);
ecs_os_err(" %s", expr);
ecs_os_err(" %*s^", column, "");
ecs_os_free(msg);
}
ecs_os_abort();
}
void _ecs_abort(
int32_t error_code,
const char *param,
const char *file,
int32_t line)
{
if (param) {
ecs_err("abort %s:%d: %s (%s)",
file, line, ecs_strerror(error_code), param);
} else {
ecs_err("abort %s:%d: %s", file, line, ecs_strerror(error_code));
}
ecs_os_abort();
}
void _ecs_assert(
bool condition,
int32_t error_code,
const char *param,
const char *condition_str,
const char *file,
int32_t line)
{
if (!condition) {
if (param) {
ecs_err("assert(%s) %s:%d: %s (%s)",
condition_str, file, line, ecs_strerror(error_code), param);
} else {
ecs_err("assert(%s) %s:%d: %s",
condition_str, file, line, ecs_strerror(error_code));
}
ecs_os_abort();
}
}
const char* ecs_strerror(
int32_t error_code)
{
switch (error_code) {
case ECS_INVALID_ENTITY:
return "invalid entity";
case ECS_INVALID_PARAMETER:
return "invalid parameters";
case ECS_INVALID_COMPONENT_ID:
return "invalid component id";
case ECS_INVALID_TYPE_EXPRESSION:
return "invalid type expression";
case ECS_INVALID_SIGNATURE:
return "invalid system signature";
case ECS_INVALID_EXPRESSION:
return "invalid type expression/signature";
case ECS_MISSING_SYSTEM_CONTEXT:
return "missing system context";
case ECS_UNKNOWN_COMPONENT_ID:
return "unknown component id";
case ECS_UNKNOWN_TYPE_ID:
return "unknown type id";
case ECS_TYPE_NOT_AN_ENTITY:
return "type contains more than one entity";
case ECS_NOT_A_COMPONENT:
return "handle is not a component";
case ECS_INTERNAL_ERROR:
return "internal error";
case ECS_MORE_THAN_ONE_PREFAB:
return "more than one prefab added to entity";
case ECS_ALREADY_DEFINED:
return "entity has already been defined";
case ECS_INVALID_COMPONENT_SIZE:
return "the specified size does not match the component";
case ECS_OUT_OF_MEMORY:
return "out of memory";
case ECS_MODULE_UNDEFINED:
return "module is undefined";
case ECS_COLUMN_INDEX_OUT_OF_RANGE:
return "column index out of range";
case ECS_COLUMN_IS_NOT_SHARED:
return "column is not shared";
case ECS_COLUMN_IS_SHARED:
return "column is shared";
case ECS_COLUMN_HAS_NO_DATA:
return "column has no data";
case ECS_COLUMN_TYPE_MISMATCH:
return "column retrieved with mismatching type";
case ECS_INVALID_WHILE_MERGING:
return "operation is invalid while merging";
case ECS_INVALID_WHILE_ITERATING:
return "operation is invalid while iterating";
case ECS_INVALID_FROM_WORKER:
return "operation is invalid from worker thread";
case ECS_UNRESOLVED_IDENTIFIER:
return "unresolved identifier";
case ECS_OUT_OF_RANGE:
return "index is out of range";
case ECS_COLUMN_IS_NOT_SET:
return "column is not set (use ecs_column_test for optional columns)";
case ECS_UNRESOLVED_REFERENCE:
return "unresolved reference for system";
case ECS_THREAD_ERROR:
return "failed to create thread";
case ECS_MISSING_OS_API:
return "missing implementation for OS API function";
case ECS_TYPE_TOO_LARGE:
return "type contains too many entities";
case ECS_INVALID_PREFAB_CHILD_TYPE:
return "a prefab child type must have at least one INSTANCEOF element";
case ECS_UNSUPPORTED:
return "operation is unsupported";
case ECS_NO_OUT_COLUMNS:
return "on demand system has no out columns";
case ECS_COLUMN_ACCESS_VIOLATION:
return "invalid access to readonly column (use const)";
case ECS_DESERIALIZE_COMPONENT_ID_CONFLICT:
return "serialized data contains conflicting component id";
case ECS_DESERIALIZE_COMPONENT_SIZE_CONFLICT:
return "serialized data contains conflicting component size";
case ECS_DESERIALIZE_FORMAT_ERROR:
return "serialized data has invalid format";
case ECS_INVALID_REACTIVE_SIGNATURE:
return "signature is not valid for reactive system (must contain at least one ANY column)";
case ECS_INCONSISTENT_COMPONENT_NAME:
return "component redefined with a different name";
case ECS_TYPE_CONSTRAINT_VIOLATION:
return "type constraint violated";
case ECS_COMPONENT_NOT_REGISTERED:
return "component is not registered";
case ECS_INCONSISTENT_COMPONENT_ID:
return "component redefined with a different id";
case ECS_INVALID_CASE:
return "case not supported for type";
case ECS_COMPONENT_NAME_IN_USE:
return "component name is already in use";
case ECS_INCONSISTENT_NAME:
return "entity redefined with different name";
case ECS_INCONSISTENT_COMPONENT_ACTION:
return "registered mismatching component action";
case ECS_INVALID_OPERATION:
return "invalid operation";
}
return "unknown error code";
}
ecs_data_t* ecs_init_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *result)
{
ecs_type_t type = table->type;
int32_t i,
count = table->column_count,
sw_count = table->sw_column_count,
bs_count = table->bs_column_count;
/* Root tables don't have columns */
if (!count && !sw_count && !bs_count) {
result->columns = NULL;
return result;
}
ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t);
if (count && !sw_count) {
result->columns = ecs_os_calloc(ECS_SIZEOF(ecs_column_t) * count);
} else if (count || sw_count) {
/* If a table has switch columns, store vector with the case values
* as a regular column, so it's easier to access for systems. To
* enable this, we need to allocate more space. */
int32_t type_count = ecs_vector_count(type);
result->columns = ecs_os_calloc(ECS_SIZEOF(ecs_column_t) * type_count);
}
if (count) {
for (i = 0; i < count; i ++) {
ecs_entity_t e = entities[i];
/* Is the column a component? */
const EcsComponent *component = ecs_component_from_id(world, e);
if (component) {
/* Is the component associated wit a (non-empty) type? */
if (component->size) {
/* This is a regular component column */
result->columns[i].size = ecs_to_i16(component->size);
result->columns[i].alignment = ecs_to_i16(component->alignment);
} else {
/* This is a tag */
}
} else {
/* This is an entity that was added to the type */
}
}
}
if (sw_count) {
int32_t sw_offset = table->sw_column_offset;
result->sw_columns = ecs_os_calloc(ECS_SIZEOF(ecs_sw_column_t) * sw_count);
for (i = 0; i < sw_count; i ++) {
ecs_entity_t e = entities[i + sw_offset];
ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL);
e = e & ECS_COMPONENT_MASK;
const EcsType *type_ptr = ecs_get(world, e, EcsType);
ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_type_t sw_type = type_ptr->normalized;
ecs_entity_t *sw_array = ecs_vector_first(sw_type, ecs_entity_t);
int32_t sw_array_count = ecs_vector_count(sw_type);
ecs_switch_t *sw = ecs_switch_new(
sw_array[0],
sw_array[sw_array_count - 1],
0);
result->sw_columns[i].data = sw;
result->sw_columns[i].type = sw_type;
int32_t column_id = i + table->sw_column_offset;
result->columns[column_id].data = ecs_switch_values(sw);
result->columns[column_id].size = sizeof(ecs_entity_t);
result->columns[column_id].alignment = ECS_ALIGNOF(ecs_entity_t);
}
}
if (bs_count) {
result->bs_columns = ecs_os_calloc(ECS_SIZEOF(ecs_bs_column_t) * bs_count);
for (i = 0; i < bs_count; i ++) {
ecs_bitset_init(&result->bs_columns[i].data);
}
}
return result;
}
static
ecs_flags32_t get_component_action_flags(
ecs_c_info_t *c_info)
{
ecs_flags32_t flags = 0;
if (c_info->lifecycle.ctor) {
flags |= EcsTableHasCtors;
}
if (c_info->lifecycle.dtor) {
flags |= EcsTableHasDtors;
}
if (c_info->lifecycle.copy) {
flags |= EcsTableHasCopy;
}
if (c_info->lifecycle.move) {
flags |= EcsTableHasMove;
}
if (c_info->on_add) {
flags |= EcsTableHasOnAdd;
}
if (c_info->on_remove) {
flags |= EcsTableHasOnRemove;
}
return flags;
}
/* Check if table has instance of component, including traits */
static
bool has_component(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t component)
{
ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t);
int32_t i, count = ecs_vector_count(type);
for (i = 0; i < count; i ++) {
if (component == ecs_get_typeid(world, entities[i])) {
return true;
}
}
return false;
}
static
void notify_component_info(
ecs_world_t *world,
ecs_table_t *table,
ecs_entity_t component)
{
ecs_type_t table_type = table->type;
if (!component || has_component(world, table_type, component)){
int32_t column_count = ecs_vector_count(table_type);
ecs_assert(!component || column_count != 0, ECS_INTERNAL_ERROR, NULL);
if (!column_count) {
return;
}
if (!table->c_info) {
table->c_info = ecs_os_calloc(
ECS_SIZEOF(ecs_c_info_t*) * column_count);
}
/* Reset lifecycle flags before recomputing */
table->flags &= ~EcsTableHasLifecycle;
/* Recompute lifecycle flags */
ecs_entity_t *array = ecs_vector_first(table_type, ecs_entity_t);
int32_t i;
for (i = 0; i < column_count; i ++) {
ecs_entity_t c = ecs_get_typeid(world, array[i]);
if (!c) {
continue;
}
ecs_c_info_t *c_info = ecs_get_c_info(world, c);
if (c_info) {
ecs_flags32_t flags = get_component_action_flags(c_info);
table->flags |= flags;
}
/* Store pointer to c_info for fast access */
table->c_info[i] = c_info;
}
}
}
static
void run_un_set_handlers(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data)
{
int32_t count = ecs_vector_count(data->entities);
if (count) {
ecs_run_monitors(world, table, table->un_set_all, 0, count, NULL);
}
}
static
int compare_matched_query(
const void *ptr1,
const void *ptr2)
{
const ecs_matched_query_t *m1 = ptr1;
const ecs_matched_query_t *m2 = ptr2;
ecs_query_t *q1 = m1->query;
ecs_query_t *q2 = m2->query;
ecs_assert(q1 != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(q2 != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_entity_t s1 = q1->system;
ecs_entity_t s2 = q2->system;
ecs_assert(s1 != 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(s2 != 0, ECS_INTERNAL_ERROR, NULL);
return (s1 > s2) - (s1 < s2);
}
static
void add_monitor(
ecs_vector_t **array,
ecs_query_t *query,
int32_t matched_table_index)
{
/* Add the system to a list that contains all OnSet systems matched with
* this table. This makes it easy to get the list of systems that need to be
* executed when all components are set, like when new_w_data is used */
ecs_matched_query_t *m = ecs_vector_add(array, ecs_matched_query_t);
ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL);
m->query = query;
m->matched_table_index = matched_table_index;
/* Sort the system list so that it is easy to get the difference OnSet
* OnSet systems between two tables. */
qsort(
ecs_vector_first(*array, ecs_matched_query_t),
ecs_to_size_t(ecs_vector_count(*array)),
ECS_SIZEOF(ecs_matched_query_t),
compare_matched_query);
}
/* This function is called when a query is matched with a table. A table keeps
* a list of tables that match so that they can be notified when the table
* becomes empty / non-empty. */
static
void register_monitor(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query,
int32_t matched_table_index)
{
(void)world;
ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL);
/* First check if system is already registered as monitor. It is possible
* the query just wants to update the matched_table_index (for example, if
* query tables got reordered) */
ecs_vector_each(table->monitors, ecs_matched_query_t, m, {
if (m->query == query) {
m->matched_table_index = matched_table_index;
return;
}
});
add_monitor(&table->monitors, query, matched_table_index);
#ifndef NDEBUG
char *str = ecs_type_str(world, table->type);
ecs_trace_2("monitor #[green]%s#[reset] registered with table #[red]%s",
ecs_get_name(world, query->system), str);
ecs_os_free(str);
#endif
}
static
bool is_override(
ecs_world_t *world,
ecs_table_t *table,
ecs_entity_t comp)
{
if (!(table->flags & EcsTableHasBase)) {
return false;
}
ecs_type_t type = table->type;
int32_t i, count = ecs_vector_count(type);
ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t);
for (i = count - 1; i >= 0; i --) {
ecs_entity_t e = entities[i];
if (ECS_HAS_ROLE(e, INSTANCEOF)) {
if (ecs_has_entity(world, e & ECS_COMPONENT_MASK, comp)) {
return true;
}
} else {
/* ECS_INSTANCEOF will always appear at the end of a type */
return false;
}
}
return false;
}
static
void register_on_set(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query,
int32_t matched_table_index)
{
(void)world;
if (table->column_count) {
if (!table->on_set) {
table->on_set =
ecs_os_calloc(ECS_SIZEOF(ecs_vector_t) * table->column_count);
}
/* Get the matched table which holds the list of actual components */
ecs_matched_table_t *matched_table = ecs_vector_get(
query->tables, ecs_matched_table_t, matched_table_index);
/* Keep track of whether query matches overrides. When a component is
* removed, diffing these arrays between the source and detination
* tables gives the list of OnSet systems to run, after exposing the
* component that was overridden. */
bool match_override = false;
/* Add system to each matched column. This makes it easy to get the list
* of systems when setting a single component. */
ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns,
ecs_sig_column_t);
int32_t i, count = ecs_vector_count(query->sig.columns);
for (i = 0; i < count; i ++) {
ecs_sig_column_t *column = &columns[i];
ecs_sig_oper_kind_t oper_kind = column->oper_kind;
ecs_sig_from_kind_t from_kind = column->from_kind;
if ((from_kind != EcsFromAny && from_kind != EcsFromOwned) ||
(oper_kind != EcsOperAnd && oper_kind != EcsOperOptional))
{
continue;
}
ecs_entity_t comp = matched_table->iter_data.components[i];
int32_t index = ecs_type_index_of(table->type, comp);
if (index == -1) {
continue;
}
if (index >= table->column_count) {
continue;
}
ecs_vector_t *set_c = table->on_set[index];
ecs_matched_query_t *m = ecs_vector_add(&set_c, ecs_matched_query_t);
m->query = query;
m->matched_table_index = matched_table_index;
table->on_set[index] = set_c;
match_override |= is_override(world, table, comp);
}
if (match_override) {
add_monitor(&table->on_set_override, query, matched_table_index);
}
}
add_monitor(&table->on_set_all, query, matched_table_index);
}
static
void register_un_set(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query,
int32_t matched_table_index)
{
(void)world;
table->flags |= EcsTableHasUnSet;
add_monitor(&table->un_set_all, query, matched_table_index);
}
/* -- Private functions -- */
/* If table goes from 0 to >0 entities or from >0 entities to 0 entities notify
* queries. This allows systems associated with queries to move inactive tables
* out of the main loop. */
void ecs_table_activate(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query,
bool activate)
{
if (query) {
ecs_query_notify(world, query, &(ecs_query_event_t) {
.kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty,
.table = table
});
} else {
ecs_vector_t *queries = table->queries;
ecs_query_t **buffer = ecs_vector_first(queries, ecs_query_t*);
int32_t i, count = ecs_vector_count(queries);
for (i = 0; i < count; i ++) {
ecs_query_notify(world, buffer[i], &(ecs_query_event_t) {
.kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty,
.table = table
});
}
}
}
/* This function is called when a query is matched with a table. A table keeps
* a list of tables that match so that they can be notified when the table
* becomes empty / non-empty. */
static
void register_query(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query,
int32_t matched_table_index)
{
/* Register system with the table */
if (!(query->flags & EcsQueryNoActivation)) {
#ifndef NDEBUG
/* Sanity check if query has already been added */
int32_t i, count = ecs_vector_count(table->queries);
for (i = 0; i < count; i ++) {
ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i);
ecs_assert(*q != query, ECS_INTERNAL_ERROR, NULL);
}
#endif
ecs_query_t **q = ecs_vector_add(&table->queries, ecs_query_t*);
if (q) *q = query;
ecs_data_t *data = ecs_table_get_data(table);
if (data && ecs_vector_count(data->entities)) {
ecs_table_activate(world, table, query, true);
}
}
/* Register the query as a monitor */
if (query->flags & EcsQueryMonitor) {
table->flags |= EcsTableHasMonitors;
register_monitor(world, table, query, matched_table_index);
}
/* Register the query as an on_set system */
if (query->flags & EcsQueryOnSet) {
register_on_set(world, table, query, matched_table_index);
}
/* Register the query as an un_set system */
if (query->flags & EcsQueryUnSet) {
register_un_set(world, table, query, matched_table_index);
}
}
/* This function is called when a query is unmatched with a table. This can
* happen for queries that have shared components expressions in their signature
* and those shared components changed (for example, a base removed a comp). */
static
void unregister_query(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query)
{
(void)world;
if (!(query->flags & EcsQueryNoActivation)) {
int32_t i, count = ecs_vector_count(table->queries);
for (i = 0; i < count; i ++) {
ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i);
if (*q == query) {
break;
}
}
/* Query must have been registered with table */
ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL);
/* Remove query */
ecs_vector_remove_index(table->queries, ecs_query_t*, i);
}
}
static
ecs_data_t* get_data_intern(
ecs_table_t *table,
bool create)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = table->data;
if (data) {
return data;
}
if (!data && !create) {
return NULL;
}
return table->data = ecs_os_calloc(ECS_SIZEOF(ecs_data_t));
}
ecs_data_t* ecs_table_get_data(
ecs_table_t *table)
{
return get_data_intern(table, false);
}
ecs_data_t* ecs_table_get_or_create_data(
ecs_table_t *table)
{
return get_data_intern(table, true);
}
static
void ctor_component(
ecs_world_t * world,
ecs_c_info_t * cdata,
ecs_column_t * column,
ecs_entity_t * entities,
int32_t row,
int32_t count)
{
/* A new component is constructed */
ecs_xtor_t ctor;
if (cdata && (ctor = cdata->lifecycle.ctor)) {
void *ctx = cdata->lifecycle.ctx;
int16_t size = column->size;
int16_t alignment = column->alignment;
void *ptr = ecs_vector_get_t(column->data, size, alignment, row);
ctor(world, cdata->component, entities, ptr,
ecs_to_size_t(size), count, ctx);
}
}
static
void dtor_component(
ecs_world_t * world,
ecs_c_info_t * cdata,
ecs_column_t * column,
ecs_entity_t * entities,
int32_t row,
int32_t count)
{
if (!count) {
return;
}
/* An old component is destructed */
ecs_xtor_t dtor;
if (cdata && (dtor = cdata->lifecycle.dtor)) {
void *ctx = cdata->lifecycle.ctx;
int16_t size = column->size;
int16_t alignment = column->alignment;
void *ptr = ecs_vector_get_t(column->data, size, alignment, row);
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);
dtor(world, cdata->component, entities, ptr,
ecs_to_size_t(size), count, ctx);
}
}
static
void dtor_all_components(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count)
{
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
int32_t column_count = table->column_count;
int32_t i;
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &data->columns[i];
dtor_component(
world, table->c_info[i], column, entities, row,
count);
}
}
static
void run_remove_actions(
ecs_world_t * world,
ecs_table_t * table,
int32_t row,
int32_t count)
{
if (count) {
ecs_run_monitors(world, table, NULL, row, count, table->un_set_all);
}
}
void ecs_table_clear_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data)
{
if (!data) {
return;
}
int32_t count = ecs_table_data_count(data);
dtor_all_components(world, table, data, 0, count);
ecs_column_t *columns = data->columns;
if (columns) {
int32_t c, column_count = table->column_count;
for (c = 0; c < column_count; c ++) {
ecs_vector_free(columns[c].data);
}
ecs_os_free(columns);
data->columns = NULL;
}
ecs_sw_column_t *sw_columns = data->sw_columns;
if (sw_columns) {
int32_t c, column_count = table->sw_column_count;
for (c = 0; c < column_count; c ++) {
ecs_switch_free(sw_columns[c].data);
}
ecs_os_free(sw_columns);
data->sw_columns = NULL;
}
ecs_bs_column_t *bs_columns = data->bs_columns;
if (bs_columns) {
int32_t c, column_count = table->bs_column_count;
for (c = 0; c < column_count; c ++) {
ecs_bitset_deinit(&bs_columns[c].data);
}
ecs_os_free(bs_columns);
data->bs_columns = NULL;
}
ecs_vector_free(data->entities);
ecs_vector_free(data->record_ptrs);
data->entities = NULL;
data->record_ptrs = NULL;
}
/* Clear columns. Deactivate table in systems if necessary, but do not invoke
* OnRemove handlers. This is typically used when restoring a table to a
* previous state. */
void ecs_table_clear_silent(
ecs_world_t * world,
ecs_table_t * table)
{
ecs_data_t *data = ecs_table_get_data(table);
if (!data) {
return;
}
int32_t count = ecs_vector_count(data->entities);
ecs_table_clear_data(world, table, data);
if (count) {
ecs_table_activate(world, table, 0, false);
}
}
/* Delete all entities in table, invoke OnRemove handlers. This function is used
* when an application invokes delete_w_filter. Use ecs_table_clear_silent, as
* the table may have to be deactivated with systems. */
void ecs_table_clear(
ecs_world_t * world,
ecs_table_t * table)
{
ecs_data_t *data = ecs_table_get_data(table);
if (data) {
run_remove_actions(world, table, 0, ecs_table_data_count(data));
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
int32_t i, count = ecs_vector_count(data->entities);
for(i = 0; i < count; i ++) {
ecs_eis_delete(world, entities[i]);
}
ecs_table_clear_silent(world, table);
}
}
/* Unset all components in table. This function is called before a table is
* deleted, and invokes all UnSet handlers, if any */
void ecs_table_unset(
ecs_world_t * world,
ecs_table_t * table)
{
(void)world;
ecs_data_t *data = ecs_table_get_data(table);
if (data) {
run_un_set_handlers(world, table, data);
}
}
/* Free table resources. Do not invoke handlers and do not activate/deactivate
* table with systems. This function is used when the world is freed. */
void ecs_table_free(
ecs_world_t * world,
ecs_table_t * table)
{
(void)world;
ecs_data_t *data = ecs_table_get_data(table);
if (data) {
run_remove_actions(world, table, 0, ecs_table_data_count(data));
ecs_table_clear_data(world, table, data);
}
ecs_table_clear_edges(world, table);
ecs_os_free(table->lo_edges);
ecs_map_free(table->hi_edges);
ecs_vector_free(table->queries);
ecs_vector_free((ecs_vector_t*)table->type);
ecs_os_free(table->dirty_state);
ecs_vector_free(table->monitors);
ecs_vector_free(table->on_set_all);
ecs_vector_free(table->on_set_override);
ecs_vector_free(table->un_set_all);
if (table->c_info) {
ecs_os_free(table->c_info);
}
if (table->on_set) {
int32_t i;
for (i = 0; i < table->column_count; i ++) {
ecs_vector_free(table->on_set[i]);
}
ecs_os_free(table->on_set);
}
table->id = 0;
ecs_os_free(table->data);
}
/* Reset a table to its initial state. */
void ecs_table_reset(
ecs_world_t * world,
ecs_table_t * table)
{
(void)world;
ecs_os_free(table->lo_edges);
ecs_map_free(table->hi_edges);
table->lo_edges = NULL;
table->hi_edges = NULL;
}
static
void mark_table_dirty(
ecs_table_t *table,
int32_t index)
{
if (table->dirty_state) {
table->dirty_state[index] ++;
}
}
void ecs_table_mark_dirty(
ecs_table_t *table,
ecs_entity_t component)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
if (table->dirty_state) {
int32_t index = ecs_type_index_of(table->type, component);
ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL);
table->dirty_state[index] ++;
}
}
static
void move_switch_columns(
ecs_table_t * new_table,
ecs_data_t * new_data,
int32_t new_index,
ecs_table_t * old_table,
ecs_data_t * old_data,
int32_t old_index,
int32_t count)
{
int32_t i_old = 0, old_column_count = old_table->sw_column_count;
int32_t i_new = 0, new_column_count = new_table->sw_column_count;
if (!old_column_count || !new_column_count) {
return;
}
ecs_sw_column_t *old_columns = old_data->sw_columns;
ecs_sw_column_t *new_columns = new_data->sw_columns;
ecs_type_t new_type = new_table->type;
ecs_type_t old_type = old_table->type;
int32_t offset_new = new_table->sw_column_offset;
int32_t offset_old = old_table->sw_column_offset;
ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t);
ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t);
for (; (i_new < new_column_count) && (i_old < old_column_count);) {
ecs_entity_t new_component = new_components[i_new + offset_new];
ecs_entity_t old_component = old_components[i_old + offset_old];
if (new_component == old_component) {
ecs_switch_t *old_switch = old_columns[i_old].data;
ecs_switch_t *new_switch = new_columns[i_new].data;
ecs_switch_ensure(new_switch, new_index + count);
int i;
for (i = 0; i < count; i ++) {
uint64_t value = ecs_switch_get(old_switch, old_index + i);
ecs_switch_set(new_switch, new_index + i, value);
}
}
i_new += new_component <= old_component;
i_old += new_component >= old_component;
}
}
static
void move_bitset_columns(
ecs_table_t * new_table,
ecs_data_t * new_data,
int32_t new_index,
ecs_table_t * old_table,
ecs_data_t * old_data,
int32_t old_index,
int32_t count)
{
int32_t i_old = 0, old_column_count = old_table->bs_column_count;
int32_t i_new = 0, new_column_count = new_table->bs_column_count;
if (!old_column_count || !new_column_count) {
return;
}
ecs_bs_column_t *old_columns = old_data->bs_columns;
ecs_bs_column_t *new_columns = new_data->bs_columns;
ecs_type_t new_type = new_table->type;
ecs_type_t old_type = old_table->type;
int32_t offset_new = new_table->bs_column_offset;
int32_t offset_old = old_table->bs_column_offset;
ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t);
ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t);
for (; (i_new < new_column_count) && (i_old < old_column_count);) {
ecs_entity_t new_component = new_components[i_new + offset_new];
ecs_entity_t old_component = old_components[i_old + offset_old];
if (new_component == old_component) {
ecs_bitset_t *old_bs = &old_columns[i_old].data;
ecs_bitset_t *new_bs = &new_columns[i_new].data;
ecs_bitset_ensure(new_bs, new_index + count);
int i;
for (i = 0; i < count; i ++) {
uint64_t value = ecs_bitset_get(old_bs, old_index + i);
ecs_bitset_set(new_bs, new_index + i, value);
}
}
i_new += new_component <= old_component;
i_old += new_component >= old_component;
}
}
static
void ensure_data(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t * column_count_out,
int32_t * sw_column_count_out,
int32_t * bs_column_count_out,
ecs_column_t ** columns_out,
ecs_sw_column_t ** sw_columns_out,
ecs_bs_column_t ** bs_columns_out)
{
int32_t column_count = table->column_count;
int32_t sw_column_count = table->sw_column_count;
int32_t bs_column_count = table->bs_column_count;
ecs_column_t *columns = NULL;
ecs_sw_column_t *sw_columns = NULL;
ecs_bs_column_t *bs_columns = NULL;
/* It is possible that the table data was created without content.
* Now that data is going to be written to the table, initialize */
if (column_count | sw_column_count | bs_column_count) {
columns = data->columns;
sw_columns = data->sw_columns;
bs_columns = data->bs_columns;
if (!columns && !sw_columns && !bs_columns) {
ecs_init_data(world, table, data);
columns = data->columns;
sw_columns = data->sw_columns;
bs_columns = data->bs_columns;
ecs_assert(sw_column_count == 0 || sw_columns != NULL,
ECS_INTERNAL_ERROR, NULL);
ecs_assert(bs_column_count == 0 || bs_columns != NULL,
ECS_INTERNAL_ERROR, NULL);
}
*column_count_out = column_count;
*sw_column_count_out = sw_column_count;
*bs_column_count_out = bs_column_count;
*columns_out = columns;
*sw_columns_out = sw_columns;
*bs_columns_out = bs_columns;
}
}
static
void grow_column(
ecs_world_t * world,
ecs_entity_t * entities,
ecs_column_t * column,
ecs_c_info_t * c_info,
int32_t to_add,
int32_t new_size,
bool construct)
{
ecs_vector_t *vec = column->data;
int16_t alignment = column->alignment;
int32_t size = column->size;
int32_t count = ecs_vector_count(vec);
int32_t old_size = ecs_vector_size(vec);
int32_t new_count = count + to_add;
bool can_realloc = new_size != old_size;
ecs_assert(new_size >= new_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;
if (c_info && count && can_realloc && (move = c_info->lifecycle.move)) {
ecs_xtor_t ctor = c_info->lifecycle.ctor;
ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL);
/* Create new vector */
ecs_vector_t *new_vec = ecs_vector_new_t(size, alignment, new_size);
ecs_vector_set_count_t(&new_vec, size, alignment, new_count);
void *old_buffer = ecs_vector_first_t(
vec, size, alignment);
void *new_buffer = ecs_vector_first_t(
new_vec, size, alignment);
/* First construct elements (old and new) in new buffer */
ctor(world, c_info->component, entities, new_buffer,
ecs_to_size_t(size), construct ? new_count : count,
c_info->lifecycle.ctx);
/* Move old elements */
move(world, c_info->component, entities, entities,
new_buffer, old_buffer, ecs_to_size_t(size), count,
c_info->lifecycle.ctx);
/* Free old vector */
ecs_vector_free(vec);
column->data = new_vec;
} else {
/* If array won't realloc or has no move, simply add new elements */
if (can_realloc) {
ecs_vector_set_size_t(&vec, size, alignment, new_size);
}
void *elem = ecs_vector_addn_t(&vec, size, alignment, to_add);
ecs_xtor_t ctor;
if (construct && c_info && (ctor = c_info->lifecycle.ctor)) {
/* If new elements need to be constructed and component has a
* constructor, construct */
ctor(world, c_info->component, &entities[count], elem,
ecs_to_size_t(size), to_add, c_info->lifecycle.ctx);
}
column->data = vec;
}
ecs_assert(ecs_vector_size(column->data) == new_size,
ECS_INTERNAL_ERROR, NULL);
}
static
int32_t 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 = ecs_table_data_count(data);
int32_t column_count = table->column_count;
int32_t sw_column_count = table->sw_column_count;
int32_t bs_column_count = table->bs_column_count;
ecs_column_t *columns = NULL;
ecs_sw_column_t *sw_columns = NULL;
ecs_bs_column_t *bs_columns = NULL;
ensure_data(world, table, data, &column_count, &sw_column_count,
&bs_column_count, &columns, &sw_columns, &bs_columns);
/* Add record to record ptr array */
ecs_vector_set_size(&data->record_ptrs, ecs_record_t*, size);
ecs_record_t **r = ecs_vector_addn(&data->record_ptrs, ecs_record_t*, to_add);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
if (ecs_vector_size(data->record_ptrs) > size) {
size = ecs_vector_size(data->record_ptrs);
}
/* Add entity to column with entity ids */
ecs_vector_set_size(&data->entities, ecs_entity_t, size);
ecs_entity_t *e = ecs_vector_addn(&data->entities, ecs_entity_t, to_add);
ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(ecs_vector_size(data->entities) == size, ECS_INTERNAL_ERROR, NULL);
/* Initialize entity ids and record ptrs */
int32_t i;
if (ids) {
for (i = 0; i < to_add; i ++) {
e[i] = ids[i];
}
} 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_c_info_t **c_info_array = table->c_info;
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &columns[i];
if (!column->size) {
continue;
}
ecs_c_info_t *c_info = NULL;
if (c_info_array) {
c_info = c_info_array[i];
}
grow_column(world, entities, column, c_info, to_add, size, true);
ecs_assert(ecs_vector_size(columns[i].data) == size,
ECS_INTERNAL_ERROR, NULL);
}
/* Add elements to each switch column */
for (i = 0; i < sw_column_count; i ++) {
ecs_switch_t *sw = sw_columns[i].data;
ecs_switch_addn(sw, to_add);
}
/* Add elements to each bitset column */
for (i = 0; i < bs_column_count; i ++) {
ecs_bitset_t *bs = &bs_columns[i].data;
ecs_bitset_addn(bs, to_add);
}
/* If the table is monitored indicate that there has been a change */
mark_table_dirty(table, 0);
if (!world->in_progress && !cur_count) {
ecs_table_activate(world, table, 0, true);
}
table->alloc_count ++;
/* Return index of first added entity */
return cur_count;
}
static
void fast_append(
ecs_column_t *columns,
int32_t column_count)
{
/* Add elements to each column array */
int32_t i;
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &columns[i];
int16_t size = column->size;
if (size) {
int16_t alignment = column->alignment;
ecs_vector_add_t(&column->data, size, alignment);
}
}
}
int32_t ecs_table_append(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
ecs_entity_t entity,
ecs_record_t * record,
bool construct)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
/* Get count & size before growing entities array. This tells us whether the
* arrays will realloc */
int32_t count = ecs_vector_count(data->entities);
int32_t size = ecs_vector_size(data->entities);
int32_t column_count = table->column_count;
int32_t sw_column_count = table->sw_column_count;
int32_t bs_column_count = table->bs_column_count;
ecs_column_t *columns = NULL;
ecs_sw_column_t *sw_columns = NULL;
ecs_bs_column_t *bs_columns = NULL;
ensure_data(world, table, data, &column_count, &sw_column_count,
&bs_column_count, &columns, &sw_columns, &bs_columns);
/* Grow buffer with entity ids, set new element to new entity */
ecs_entity_t *e = ecs_vector_add(&data->entities, ecs_entity_t);
ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL);
*e = entity;
/* Keep track of alloc count. This allows references to check if cached
* pointers need to be updated. */
table->alloc_count += (count == size);
/* Add record ptr to array with record ptrs */
ecs_record_t **r = ecs_vector_add(&data->record_ptrs, 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 */
mark_table_dirty(table, 0);
/* 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 (!world->in_progress && !count) {
ecs_table_activate(world, table, 0, true);
}
ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL);
/* Fast path: no switch columns, no lifecycle actions */
if (!(table->flags & EcsTableIsComplex)) {
fast_append(columns, column_count);
return count;
}
ecs_c_info_t **c_info_array = table->c_info;
ecs_entity_t *entities = ecs_vector_first(
data->entities, ecs_entity_t);
/* Reobtain size to ensure that the columns have the same size as the
* entities and record vectors. This keeps reasoning about when allocations
* occur easier. */
size = ecs_vector_size(data->entities);
/* Grow component arrays with 1 element */
int32_t i;
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &columns[i];
if (!column->size) {
continue;
}
ecs_c_info_t *c_info = NULL;
if (c_info_array) {
c_info = c_info_array[i];
}
grow_column(world, entities, column, c_info, 1, size, construct);
ecs_assert(
ecs_vector_size(columns[i].data) == ecs_vector_size(data->entities),
ECS_INTERNAL_ERROR, NULL);
ecs_assert(
ecs_vector_count(columns[i].data) == ecs_vector_count(data->entities),
ECS_INTERNAL_ERROR, NULL);
}
/* Add element to each switch column */
for (i = 0; i < sw_column_count; i ++) {
ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_switch_t *sw = sw_columns[i].data;
ecs_switch_add(sw);
columns[i + table->sw_column_offset].data = ecs_switch_values(sw);
}
/* Add element to each bitset column */
for (i = 0; i < bs_column_count; i ++) {
ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_bitset_t *bs = &bs_columns[i].data;
ecs_bitset_addn(bs, 1);
}
return count;
}
static
void fast_delete_last(
ecs_column_t *columns,
int32_t column_count)
{
int i;
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &columns[i];
ecs_vector_remove_last(column->data);
}
}
static
void fast_delete(
ecs_column_t *columns,
int32_t column_count,
int32_t index)
{
int i;
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &columns[i];
int16_t size = column->size;
if (size) {
int16_t alignment = column->alignment;
ecs_vector_remove_index_t(column->data, size, alignment, index);
}
}
}
void ecs_table_delete(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t index,
bool destruct)
{
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_vector_t *entity_column = data->entities;
int32_t count = ecs_vector_count(entity_column);
ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL);
count --;
ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL);
ecs_c_info_t **c_info_array = table->c_info;
int32_t column_count = table->column_count;
int32_t i;
ecs_entity_t *entities = ecs_vector_first(entity_column, ecs_entity_t);
ecs_entity_t entity_to_move = entities[count];
/* Move last entity id to index */
entities[index] = entity_to_move;
ecs_vector_remove_last(entity_column);
/* Move last record ptr to index */
ecs_vector_t *record_column = data->record_ptrs;
ecs_record_t **records = ecs_vector_first(record_column, ecs_record_t*);
ecs_assert(count < ecs_vector_count(record_column), ECS_INTERNAL_ERROR, NULL);
ecs_record_t *record_to_move = records[count];
records[index] = record_to_move;
ecs_vector_remove_last(record_column);
/* Update record of moved entity in entity index */
if (index != count) {
if (record_to_move) {
if (record_to_move->row >= 0) {
record_to_move->row = index + 1;
} else {
record_to_move->row = -(index + 1);
}
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 */
mark_table_dirty(table, 0);
if (!count) {
ecs_table_activate(world, table, NULL, false);
}
/* Move each component value in array to index */
ecs_column_t *columns = data->columns;
if (!(table->flags & EcsTableIsComplex)) {
if (index == count) {
fast_delete_last(columns, column_count);
} else {
fast_delete(columns, column_count, index);
}
return;
}
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &columns[i];
int16_t size = column->size;
int16_t alignment = column->alignment;
if (size) {
ecs_c_info_t *c_info = c_info_array ? c_info_array[i] : NULL;
ecs_xtor_t dtor;
void *dst = ecs_vector_get_t(column->data, size, alignment, index);
ecs_move_t move;
if (c_info && (count != index) && (move = c_info->lifecycle.move)) {
void *ctx = c_info->lifecycle.ctx;
void *src = ecs_vector_get_t(column->data, size, alignment, count);
ecs_entity_t component = c_info->component;
/* If the delete is not destructing the component, the component
* was already deleted, most likely by a move. In that case we
* still need to move, but we need to make sure we're moving
* into an element that is initialized with valid memory, so
* call the constructor. */
if (!destruct) {
ecs_xtor_t ctor = c_info->lifecycle.ctor;
ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL);
ctor(world, c_info->component, &entity_to_move, dst,
ecs_to_size_t(size), 1, c_info->lifecycle.ctx);
}
/* Move last element into deleted element */
move(world, component, &entity_to_move, &entity_to_move, dst, src,
ecs_to_size_t(size), 1, ctx);
/* Memory has been copied, we can now simply remove last */
ecs_vector_remove_last(column->data);
} else {
if (destruct && c_info && (dtor = c_info->lifecycle.dtor)) {
dtor(world, c_info->component, &entities[index], dst,
ecs_to_size_t(size), 1, c_info->lifecycle.ctx);
}
ecs_vector_remove_index_t(column->data, size, alignment, index);
}
}
}
/* Remove elements from switch columns */
ecs_sw_column_t *sw_columns = data->sw_columns;
int32_t sw_column_count = table->sw_column_count;
for (i = 0; i < sw_column_count; i ++) {
ecs_switch_remove(sw_columns[i].data, index);
}
/* Remove elements from bitset columns */
ecs_bs_column_t *bs_columns = data->bs_columns;
int32_t bs_column_count = table->bs_column_count;
for (i = 0; i < bs_column_count; i ++) {
ecs_bitset_remove(&bs_columns[i].data, index);
}
}
static
void fast_move(
ecs_table_t * new_table,
ecs_data_t * new_data,
int32_t new_index,
ecs_table_t * old_table,
ecs_data_t * old_data,
int32_t old_index)
{
ecs_type_t new_type = new_table->type;
ecs_type_t old_type = old_table->type;
int32_t i_new = 0, new_column_count = new_table->column_count;
int32_t i_old = 0, old_column_count = old_table->column_count;
ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t);
ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t);
ecs_column_t *old_columns = old_data->columns;
ecs_column_t *new_columns = new_data->columns;
for (; (i_new < new_column_count) && (i_old < old_column_count);) {
ecs_entity_t new_component = new_components[i_new];
ecs_entity_t old_component = old_components[i_old];
if (new_component == old_component) {
ecs_column_t *new_column = &new_columns[i_new];
ecs_column_t *old_column = &old_columns[i_old];
int16_t size = new_column->size;
if (size) {
int16_t alignment = new_column->alignment;
void *dst = ecs_vector_get_t(new_column->data, size, alignment, new_index);
void *src = ecs_vector_get_t(old_column->data, size, alignment, old_index);
ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_os_memcpy(dst, src, size);
}
}
i_new += new_component <= old_component;
i_old += new_component >= old_component;
}
}
void ecs_table_move(
ecs_world_t * world,
ecs_entity_t dst_entity,
ecs_entity_t src_entity,
ecs_table_t *new_table,
ecs_data_t *new_data,
int32_t new_index,
ecs_table_t *old_table,
ecs_data_t *old_data,
int32_t old_index)
{
ecs_assert(new_table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(old_index >= 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(new_index >= 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(old_data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(new_data != NULL, ECS_INTERNAL_ERROR, NULL);
if (!((new_table->flags | old_table->flags) & EcsTableIsComplex)) {
fast_move(new_table, new_data, new_index, old_table, old_data, old_index);
return;
}
move_switch_columns(
new_table, new_data, new_index, old_table, old_data, old_index, 1);
move_bitset_columns(
new_table, new_data, new_index, old_table, old_data, old_index, 1);
bool same_entity = dst_entity == src_entity;
ecs_type_t new_type = new_table->type;
ecs_type_t old_type = old_table->type;
int32_t i_new = 0, new_column_count = new_table->column_count;
int32_t i_old = 0, old_column_count = old_table->column_count;
ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t);
ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t);
ecs_column_t *old_columns = old_data->columns;
ecs_column_t *new_columns = new_data->columns;
for (; (i_new < new_column_count) && (i_old < old_column_count);) {
ecs_entity_t new_component = new_components[i_new];
ecs_entity_t old_component = old_components[i_old];
if (new_component == old_component) {
ecs_column_t *new_column = &new_columns[i_new];
ecs_column_t *old_column = &old_columns[i_old];
int16_t size = new_column->size;
int16_t alignment = new_column->alignment;
if (size) {
void *dst = ecs_vector_get_t(new_column->data, size, alignment, new_index);
void *src = ecs_vector_get_t(old_column->data, size, alignment, old_index);
ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_c_info_t *cdata = new_table->c_info[i_new];
if (same_entity) {
ecs_move_t move;
if (cdata && (move = cdata->lifecycle.move)) {
void *ctx = cdata->lifecycle.ctx;
ecs_xtor_t ctor = cdata->lifecycle.ctor;
/* Ctor should always be set if copy is set */
ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL);
/* Construct a new value, move the value to it */
ctor(world, new_component, &dst_entity, dst,
ecs_to_size_t(size), 1, ctx);
move(world, new_component, &dst_entity, &src_entity,
dst, src, ecs_to_size_t(size), 1, ctx);
} else {
ecs_os_memcpy(dst, src, size);
}
} else {
ecs_copy_t copy;
if (cdata && (copy = cdata->lifecycle.copy)) {
void *ctx = cdata->lifecycle.ctx;
ecs_xtor_t ctor = cdata->lifecycle.ctor;
/* Ctor should always be set if copy is set */
ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL);
ctor(world, new_component, &dst_entity, dst,
ecs_to_size_t(size), 1, ctx);
copy(world, new_component, &dst_entity, &src_entity,
dst, src, ecs_to_size_t(size), 1, ctx);
} else {
ecs_os_memcpy(dst, src, size);
}
}
}
} else {
if (new_component < old_component) {
ctor_component(world, new_table->c_info[i_new],
&new_columns[i_new], &dst_entity, new_index, 1);
} else {
dtor_component(world, old_table->c_info[i_old],
&old_columns[i_old], &src_entity, old_index, 1);
}
}
i_new += new_component <= old_component;
i_old += new_component >= old_component;
}
for (; (i_new < new_column_count); i_new ++) {
ctor_component(world, new_table->c_info[i_new],
&new_columns[i_new], &dst_entity, new_index, 1);
}
for (; (i_old < old_column_count); i_old ++) {
dtor_component(world, old_table->c_info[i_old],
&old_columns[i_old], &src_entity, old_index, 1);
}
}
int32_t ecs_table_appendn(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t to_add,
const ecs_entity_t *ids)
{
int32_t cur_count = ecs_table_data_count(data);
return grow_data(world, table, data, to_add, cur_count + to_add, ids);
}
void ecs_table_set_size(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t size)
{
int32_t cur_count = ecs_table_data_count(data);
if (cur_count < size) {
grow_data(world, table, data, 0, size, NULL);
} else if (!size) {
/* Initialize columns if 0 is passed. This is a shortcut to initialize
* columns when, for example, an API call is inserting bulk data. */
int32_t column_count = table->column_count;
int32_t sw_column_count = table->sw_column_count;
int32_t bs_column_count = table->bs_column_count;
ecs_column_t *columns;
ecs_sw_column_t *sw_columns;
ecs_bs_column_t *bs_columns;
ensure_data(world, table, data, &column_count, &sw_column_count,
&bs_column_count, &columns, &sw_columns, &bs_columns);
}
}
int32_t ecs_table_data_count(
ecs_data_t *data)
{
return data ? ecs_vector_count(data->entities) : 0;
}
static
void 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_column_count;
if (!column_count) {
return;
}
ecs_sw_column_t *columns = data->sw_columns;
for (i = 0; i < column_count; i ++) {
ecs_switch_t *sw = columns[i].data;
ecs_switch_swap(sw, row_1, row_2);
}
}
static
void 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_column_count;
if (!column_count) {
return;
}
ecs_bs_column_t *columns = data->bs_columns;
for (i = 0; i < column_count; i ++) {
ecs_bitset_t *bs = &columns[i].data;
ecs_bitset_swap(bs, row_1, row_2);
}
}
void ecs_table_swap(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t row_1,
int32_t row_2)
{
(void)world;
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_column_t *columns = data->columns;
ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL);
if (row_1 == row_2) {
return;
}
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
ecs_entity_t e1 = entities[row_1];
ecs_entity_t e2 = entities[row_2];
ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*);
ecs_record_t *record_ptr_1 = record_ptrs[row_1];
ecs_record_t *record_ptr_2 = record_ptrs[row_2];
ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL);
/* Swap entities */
entities[row_1] = e2;
entities[row_2] = e1;
record_ptr_1->row = row_2;
record_ptr_2->row = row_1;
record_ptrs[row_1] = record_ptr_2;
record_ptrs[row_2] = record_ptr_1;
if (row_2 < 0) {
record_ptr_1->row --;
} else {
record_ptr_1->row ++;
}
if (row_1 < 0) {
record_ptr_2->row --;
} else {
record_ptr_2->row ++;
}
/* Swap columns */
int32_t i, column_count = table->column_count;
for (i = 0; i < column_count; i ++) {
int16_t size = columns[i].size;
int16_t alignment = columns[i].alignment;
void *ptr = ecs_vector_first_t(columns[i].data, size, alignment);
if (size) {
void *tmp = ecs_os_alloca(size);
void *el_1 = ECS_OFFSET(ptr, size * row_1);
void *el_2 = ECS_OFFSET(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);
}
}
swap_switch_columns(table, data, row_1, row_2);
swap_bitset_columns(table, data, row_1, row_2);
/* If the table is monitored indicate that there has been a change */
mark_table_dirty(table, 0);
}
static
void merge_vector(
ecs_vector_t **dst_out,
ecs_vector_t *src,
int16_t size,
int16_t alignment)
{
ecs_vector_t *dst = *dst_out;
int32_t dst_count = ecs_vector_count(dst);
if (!dst_count) {
if (dst) {
ecs_vector_free(dst);
}
*dst_out = src;
/* If the new table is not empty, copy the contents from the
* src into the dst. */
} else {
int32_t src_count = ecs_vector_count(src);
ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count);
void *dst_ptr = ecs_vector_first_t(dst, size, alignment);
void *src_ptr = ecs_vector_first_t(src, size, alignment);
dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count);
ecs_os_memcpy(dst_ptr, src_ptr, size * src_count);
ecs_vector_free(src);
*dst_out = dst;
}
}
static
void merge_column(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t column_id,
ecs_vector_t *src)
{
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
ecs_c_info_t *c_info = table->c_info[column_id];
ecs_column_t *column = &data->columns[column_id];
ecs_vector_t *dst = column->data;
int16_t size = column->size;
int16_t alignment = column->alignment;
int32_t dst_count = ecs_vector_count(dst);
if (!dst_count) {
if (dst) {
ecs_vector_free(dst);
}
column->data = src;
/* If the new table is not empty, copy the contents from the
* src into the dst. */
} else {
int32_t src_count = ecs_vector_count(src);
ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count);
column->data = dst;
/* Construct new values */
if (c_info) {
ctor_component(
world, c_info, column, entities, dst_count, src_count);
}
void *dst_ptr = ecs_vector_first_t(dst, size, alignment);
void *src_ptr = ecs_vector_first_t(src, size, alignment);
dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count);
/* Move values into column */
ecs_move_t move;
if (c_info && (move = c_info->lifecycle.move)) {
move(world, c_info->component, entities, entities,
dst_ptr, src_ptr, ecs_to_size_t(size), src_count,
c_info->lifecycle.ctx);
} else {
ecs_os_memcpy(dst_ptr, src_ptr, size * src_count);
}
ecs_vector_free(src);
}
}
static
void merge_table_data(
ecs_world_t * world,
ecs_table_t * new_table,
ecs_table_t * old_table,
int32_t old_count,
int32_t new_count,
ecs_data_t * old_data,
ecs_data_t * new_data)
{
int32_t i_new, new_component_count = new_table->column_count;
int32_t i_old = 0, old_component_count = old_table->column_count;
ecs_entity_t *new_components = ecs_vector_first(new_table->type, ecs_entity_t);
ecs_entity_t *old_components = ecs_vector_first(old_table->type, ecs_entity_t);
ecs_column_t *old_columns = old_data->columns;
ecs_column_t *new_columns = new_data->columns;
if (!new_columns && !new_data->entities) {
ecs_init_data(world, new_table, new_data);
new_columns = new_data->columns;
}
if (!old_count) {
return;
}
/* Merge entities */
merge_vector(&new_data->entities, old_data->entities, ECS_SIZEOF(ecs_entity_t),
ECS_ALIGNOF(ecs_entity_t));
old_data->entities = NULL;
ecs_entity_t *entities = ecs_vector_first(new_data->entities, ecs_entity_t);
ecs_assert(ecs_vector_count(new_data->entities) == old_count + new_count,
ECS_INTERNAL_ERROR, NULL);
/* Merge entity index record pointers */
merge_vector(&new_data->record_ptrs, old_data->record_ptrs,
ECS_SIZEOF(ecs_record_t*), ECS_ALIGNOF(ecs_record_t*));
old_data->record_ptrs = NULL;
for (i_new = 0; (i_new < new_component_count) && (i_old < old_component_count); ) {
ecs_entity_t new_component = new_components[i_new];
ecs_entity_t old_component = old_components[i_old];
int16_t size = new_columns[i_new].size;
int16_t alignment = new_columns[i_new].alignment;
if ((new_component & ECS_ROLE_MASK) ||
(old_component & ECS_ROLE_MASK))
{
break;
}
if (new_component == old_component) {
merge_column(world, new_table, new_data, i_new,
old_columns[i_old].data);
old_columns[i_old].data = NULL;
/* Mark component column as dirty */
mark_table_dirty(new_table, i_new + 1);
i_new ++;
i_old ++;
} else if (new_component < old_component) {
/* New column does not occur in old table, make sure vector is large
* enough. */
if (size) {
ecs_column_t *column = &new_columns[i_new];
ecs_vector_set_count_t(&column->data, size, alignment,
old_count + new_count);
/* Construct new values */
ecs_c_info_t *c_info;
ecs_xtor_t ctor;
if ((c_info = new_table->c_info[i_new]) &&
(ctor = c_info->lifecycle.ctor))
{
ctor_component(world, c_info, column,
entities, 0, old_count + new_count);
}
}
i_new ++;
} else if (new_component > old_component) {
if (size) {
ecs_column_t *column = &old_columns[i_old];
/* Destruct old values */
ecs_c_info_t *c_info;
ecs_xtor_t dtor;
if ((c_info = old_table->c_info[i_old]) &&
(dtor = c_info->lifecycle.dtor))
{
dtor_component(world, c_info, column,
entities, 0, old_count);
}
/* Old column does not occur in new table, remove */
ecs_vector_free(column->data);
column->data = NULL;
i_old ++;
}
}
}
move_switch_columns(
new_table, new_data, new_count, old_table, old_data, 0, old_count);
/* Initialize remaining columns */
for (; i_new < new_component_count; i_new ++) {
ecs_column_t *column = &new_columns[i_new];
int16_t size = column->size;
int16_t alignment = column->alignment;
if (size) {
ecs_vector_set_count_t(&column->data, size, alignment,
old_count + new_count);
/* Construct new values */
ecs_c_info_t *c_info;
ecs_xtor_t ctor;
if ((c_info = new_table->c_info[i_new]) &&
(ctor = c_info->lifecycle.ctor))
{
ctor_component(world, c_info, column,
entities, 0, old_count + new_count);
}
}
}
/* Destroy remaining columns */
for (; i_old < old_component_count; i_old ++) {
ecs_column_t *column = &old_columns[i_old];
/* Destruct old values */
ecs_c_info_t *c_info;
ecs_xtor_t dtor;
if ((c_info = old_table->c_info[i_old]) &&
(dtor = c_info->lifecycle.dtor))
{
dtor_component(world, c_info, column, entities,
0, old_count);
}
/* Old column does not occur in new table, remove */
ecs_vector_free(column->data);
column->data = NULL;
}
/* Mark entity column as dirty */
mark_table_dirty(new_table, 0);
}
int32_t ecs_table_count(
ecs_table_t *table)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = table->data;
if (!data) {
return 0;
}
return ecs_table_data_count(data);
}
ecs_data_t* ecs_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)
{
ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL);
bool move_data = false;
/* If there is nothing to merge to, just clear the old table */
if (!new_table) {
ecs_table_clear_data(world, old_table, old_data);
return NULL;
}
/* If there is no data to merge, drop out */
if (!old_data) {
return NULL;
}
if (!new_data) {
new_data = ecs_table_get_or_create_data(new_table);
if (new_table == old_table) {
move_data = true;
}
}
ecs_entity_t *old_entities = ecs_vector_first(old_data->entities, ecs_entity_t);
int32_t old_count = ecs_vector_count(old_data->entities);
int32_t new_count = ecs_vector_count(new_data->entities);
ecs_record_t **old_records = ecs_vector_first(
old_data->record_ptrs, ecs_record_t*);
/* First, update entity index so old entities point to new type */
int32_t i;
for(i = 0; i < old_count; i ++) {
ecs_record_t *record;
if (new_table != old_table) {
record = old_records[i];
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL);
} else {
record = ecs_eis_get_or_create(world, old_entities[i]);
}
bool is_monitored = record->row < 0;
record->row = ecs_row_to_record(new_count + i, is_monitored);
record->table = new_table;
}
/* Merge table columns */
if (move_data) {
*new_data = *old_data;
} else {
merge_table_data(world, new_table, old_table, old_count, new_count,
old_data, new_data);
}
new_table->alloc_count ++;
if (!new_count && old_count) {
ecs_table_activate(world, new_table, NULL, true);
}
return new_data;
}
void ecs_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);
if (table_data) {
prev_count = ecs_vector_count(table_data->entities);
run_remove_actions(world, table, 0, ecs_table_data_count(table_data));
ecs_table_clear_data(world, table, table_data);
}
if (data) {
table_data = ecs_table_get_or_create_data(table);
*table_data = *data;
} else {
return;
}
int32_t count = ecs_table_count(table);
if (!prev_count && count) {
ecs_table_activate(world, table, 0, true);
} else if (prev_count && !count) {
ecs_table_activate(world, table, 0, false);
}
}
bool ecs_table_match_filter(
ecs_world_t * world,
ecs_table_t * table,
const ecs_filter_t * filter)
{
if (!filter) {
return true;
}
ecs_type_t type = table->type;
if (filter->include) {
/* If filter kind is exact, types must be the same */
if (filter->include_kind == EcsMatchExact) {
if (type != filter->include) {
return false;
}
/* Default for include_kind is MatchAll */
} else if (!ecs_type_contains(world, type, filter->include,
filter->include_kind != EcsMatchAny, true))
{
return false;
}
}
if (filter->exclude) {
/* If filter kind is exact, types must be the same */
if (filter->exclude_kind == EcsMatchExact) {
if (type == filter->exclude) {
return false;
}
/* Default for exclude_kind is MatchAny */
} else if (ecs_type_contains(world, type, filter->exclude,
filter->exclude_kind == EcsMatchAll, true))
{
return false;
}
}
return true;
}
int32_t* ecs_table_get_dirty_state(
ecs_table_t *table)
{
if (!table->dirty_state) {
table->dirty_state = ecs_os_calloc(ECS_SIZEOF(int32_t) * (table->column_count + 1));
ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
}
return table->dirty_state;
}
int32_t* ecs_table_get_monitor(
ecs_table_t *table)
{
int32_t *dirty_state = ecs_table_get_dirty_state(table);
ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
int32_t column_count = table->column_count;
return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t));
}
void ecs_table_notify(
ecs_world_t * world,
ecs_table_t * table,
ecs_table_event_t * event)
{
if (world->is_fini) {
return;
}
switch(event->kind) {
case EcsTableQueryMatch:
register_query(
world, table, event->query, event->matched_table_index);
break;
case EcsTableQueryUnmatch:
unregister_query(
world, table, event->query);
break;
case EcsTableComponentInfo:
notify_component_info(world, table, event->component);
break;
}
}
static
const ecs_entity_t* new_w_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_entities_t *component_ids,
int32_t count,
void **c_info,
int32_t *row_out);
static
void* get_component_w_index(
ecs_entity_info_t *info,
int32_t index)
{
ecs_data_t *data = info->data;
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_column_t *columns = data->columns;
ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(index < info->table->column_count, ECS_INVALID_COMPONENT_ID, NULL);
ecs_column_t *column = &columns[index];
ecs_vector_t *data_vec = column->data;
int16_t size = column->size;
/* If size is 0, component does not have a value. This is likely caused by
* an application trying to call ecs_get with a tag. */
ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL);
/* This function should not be called if an entity does not exist in the
* provided table. Therefore if the component is found in the table, and an
* entity exists for it, the vector cannot be NULL */
ecs_assert(data_vec != NULL, ECS_INTERNAL_ERROR, NULL);
void *ptr = ecs_vector_first_t(data_vec, size, column->alignment);
/* This could only happen when the vector is empty, which should not be
* possible since the vector should at least have one element */
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);
return ECS_OFFSET(ptr, info->row * size);
}
/* Get pointer to single component value */
static
void* get_component(
ecs_entity_info_t *info,
ecs_entity_t component)
{
ecs_assert(info->table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(info->row >= 0, ECS_INTERNAL_ERROR, NULL);
ecs_table_t *table = info->table;
ecs_type_t type = table->type;
ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t);
/* The table column_count contains the maximum column index that actually
* contains data. This excludes component ids that do not have data, such
* as tags. Therefore it is faster to iterate column_count vs. all the
* elements in the type.
*
* The downside of this is that the code can't always detect when an
* application attempts to get the value of a tag (which is not allowed). To
* ensure consistent behavior in debug mode, the entire type is iterated as
* this guarantees that the code will assert when attempting to obtain the
* value of a tag. */
#ifndef NDEBUG
int i, count = ecs_vector_count(type);
#else
int i, count = table->column_count;
#endif
for (i = 0; i < count; i ++) {
if (ids[i] == component) {
return get_component_w_index(info, i);
}
}
return NULL;
}
/* Utility to compute actual row from row in record */
static
int32_t set_row_info(
ecs_entity_info_t *info,
int32_t row)
{
return info->row = ecs_record_to_row(row, &info->is_watched);
}
/* Utility to set info from main stage record */
static
void set_info_from_record(
ecs_entity_t e,
ecs_entity_info_t * info,
ecs_record_t * record)
{
(void)e;
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL);
info->record = record;
ecs_table_t *table = record->table;
set_row_info(info, record->row);
info->table = table;
if (!info->table) {
return;
}
ecs_data_t *data = ecs_table_get_data(table);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
info->data = data;
ecs_assert(ecs_vector_count(data->entities) > info->row,
ECS_INTERNAL_ERROR, NULL);
}
/* Get info from main stage */
bool ecs_get_info(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entity_info_t * info)
{
info->table = NULL;
info->record = NULL;
info->data = NULL;
info->is_watched = false;
if (entity & ECS_ROLE) {
return false;
}
ecs_record_t *record = ecs_eis_get(world, entity);
if (!record) {
return false;
}
set_info_from_record(entity, info, record);
return true;
}
static
ecs_c_info_t *get_c_info(
ecs_world_t *world,
ecs_entity_t component)
{
ecs_entity_t real_id = ecs_get_typeid(world, component);
if (real_id) {
return ecs_get_c_info(world, real_id);
} else {
return NULL;
}
}
void ecs_get_column_info(
ecs_world_t * world,
ecs_table_t * table,
ecs_entities_t * components,
ecs_column_info_t * cinfo,
bool get_all)
{
int32_t column_count = table->column_count;
ecs_entity_t *type_array = ecs_vector_first(table->type, ecs_entity_t);
if (get_all) {
int32_t i, count = ecs_vector_count(table->type);
for (i = 0; i < count; i ++) {
ecs_entity_t id = type_array[i];
cinfo[i].id = id;
cinfo[i].ci = get_c_info(world, id);
cinfo[i].column = i;
}
} else {
ecs_entity_t *array = components->array;
int32_t i, cur, count = components->count;
for (i = 0; i < count; i ++) {
ecs_entity_t id = array[i];
cinfo[i].id = id;
cinfo[i].ci = get_c_info(world, id);
cinfo[i].column = -1;
for (cur = 0; cur < column_count; cur ++) {
if (type_array[cur] == id) {
cinfo[i].column = cur;
break;
}
}
}
}
}
static
void run_component_trigger_for_entities(
ecs_world_t * world,
ecs_vector_t * trigger_vec,
ecs_entity_t component,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count,
ecs_entity_t *entities)
{
(void)world;
int32_t i, trigger_count = ecs_vector_count(trigger_vec);
if (trigger_count) {
EcsTrigger *triggers = ecs_vector_first(trigger_vec, EcsTrigger);
int32_t index = ecs_type_index_of(table->type, component);
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL);
index ++;
ecs_entity_t components[1] = { component };
ecs_type_t types[1] = { ecs_type_from_entity(world, component) };
int32_t columns[1] = { index };
/* If this is a tag, don't try to retrieve data */
if (table->column_count < index) {
columns[0] = 0;
} else {
ecs_column_t *column = &data->columns[index - 1];
if (!column->size) {
columns[0] = 0;
}
}
ecs_iter_table_t table_data = {
.table = table,
.columns = columns,
.components = components,
.types = types
};
ecs_iter_t it = {
.world = world,
.table = &table_data,
.table_count = 1,
.inactive_table_count = 1,
.column_count = 1,
.table_columns = data->columns,
.entities = entities,
.offset = row,
.count = count,
};
for (i = 0; i < trigger_count; i ++) {
it.system = triggers[i].self;
it.param = triggers[i].ctx;
triggers[i].action(&it);
}
}
}
static
void ecs_run_component_trigger(
ecs_world_t * world,
ecs_vector_t * trigger_vec,
ecs_entity_t component,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count)
{
ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL);
if (table->flags & EcsTableIsPrefab) {
return;
}
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(row < ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL);
ecs_assert((row + count) <= ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL);
entities = ECS_OFFSET(entities, ECS_SIZEOF(ecs_entity_t) * row);
run_component_trigger_for_entities(
world, trigger_vec, component, table, data, row, count, entities);
}
#ifdef FLECS_SYSTEM
static
void run_set_systems_for_entities(
ecs_world_t * world,
ecs_entities_t * components,
ecs_table_t * table,
int32_t row,
int32_t count,
ecs_entity_t * entities,
bool set_all)
{
if (set_all) {
/* Run OnSet systems for all components of the entity. This usually
* happens when an entity is created directly in its target table. */
ecs_vector_t *queries = table->on_set_all;
ecs_vector_each(queries, ecs_matched_query_t, m, {
ecs_run_monitor(world, m, components, row, count, entities);
});
} else {
/* Run OnSet systems for a specific component. This usually happens when
* an application calls ecs_set or ecs_modified. The entity's table
* stores a vector for each component with the OnSet systems for that
* component. This vector maintains the same order as the table's type,
* which makes finding the correct set of systems as simple as getting
* the index of a component id in the table type.
*
* One thing to note is that the system may be invoked for a table that
* is not the same as the entity for which the system is invoked. This
* can happen in the case of instancing, where adding an INSTANCEOF
* relationship conceptually adds components to an entity, but the
* actual components are stored on the base entity. */
ecs_vector_t **on_set_systems = table->on_set;
if (on_set_systems) {
int32_t index = ecs_type_index_of(table->type, components->array[0]);
/* This should never happen, as an OnSet system should only ever be
* invoked for entities that have the component for which this
* function was invoked. */
ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL);
ecs_vector_t *queries = on_set_systems[index];
ecs_vector_each(queries, ecs_matched_query_t, m, {
ecs_run_monitor(world, m, components, row, count, entities);
});
}
}
}
#endif
void ecs_run_set_systems(
ecs_world_t * world,
ecs_entities_t * components,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count,
bool set_all)
{
(void)world;
(void)components;
(void)table;
(void)data;
(void)row;
(void)count;
(void)set_all;
#ifdef FLECS_SYSTEM
if (!count || !data) {
return;
}
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(row < ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL);
ecs_assert((row + count) <= ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL);
entities = ECS_OFFSET(entities, ECS_SIZEOF(ecs_entity_t) * row);
run_set_systems_for_entities(world, components, table, row,
count, entities, set_all);
#endif
}
void ecs_run_monitors(
ecs_world_t * world,
ecs_table_t * dst_table,
ecs_vector_t * v_dst_monitors,
int32_t dst_row,
int32_t count,
ecs_vector_t *v_src_monitors)
{
(void)world;
(void)dst_table;
(void)v_dst_monitors;
(void)dst_row;
(void)count;
(void)v_src_monitors;
#ifdef FLECS_SYSTEM
if (v_dst_monitors == v_src_monitors) {
return;
}
if (!v_dst_monitors) {
return;
}
ecs_assert(!(dst_table->flags & EcsTableIsPrefab), ECS_INTERNAL_ERROR, NULL);
if (!v_src_monitors) {
ecs_vector_each(v_dst_monitors, ecs_matched_query_t, monitor, {
ecs_run_monitor(world, monitor, NULL, dst_row, count, NULL);
});
} else {
/* If both tables have monitors, run the ones that dst_table has and
* src_table doesn't have */
int32_t i, m_count = ecs_vector_count(v_dst_monitors);
int32_t j = 0, src_count = ecs_vector_count(v_src_monitors);
ecs_matched_query_t *dst_monitors = ecs_vector_first(v_dst_monitors, ecs_matched_query_t);
ecs_matched_query_t *src_monitors = ecs_vector_first(v_src_monitors, ecs_matched_query_t);
for (i = 0; i < m_count; i ++) {
ecs_matched_query_t *dst = &dst_monitors[i];
ecs_entity_t system = dst->query->system;
ecs_assert(system != 0, ECS_INTERNAL_ERROR, NULL);
ecs_matched_query_t *src = 0;
while (j < src_count) {
src = &src_monitors[j];
if (src->query->system < system) {
j ++;
} else {
break;
}
}
if (src && src->query->system == system) {
continue;
}
ecs_run_monitor(world, dst, NULL, dst_row, count, NULL);
}
}
#endif
}
static
int32_t find_prefab(
ecs_type_t type,
int32_t n)
{
int32_t i, count = ecs_vector_count(type);
ecs_entity_t *buffer = ecs_vector_first(type, ecs_entity_t);
for (i = n + 1; i < count; i ++) {
ecs_entity_t e = buffer[i];
if (ECS_HAS_ROLE(e, INSTANCEOF)) {
return i;
}
}
return -1;
}
static
void instantiate(
ecs_world_t *world,
ecs_entity_t base,
ecs_table_t *table,
ecs_data_t *data,
int32_t row,
int32_t count);
static
void instantiate_children(
ecs_world_t * world,
ecs_entity_t base,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count,
ecs_table_t * child_table)
{
ecs_type_t type = child_table->type;
ecs_data_t *child_data = ecs_table_get_data(child_table);
if (!child_data || !ecs_table_data_count(child_data)) {
return;
}
int32_t column_count = child_table->column_count;
ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t);
int32_t type_count = ecs_vector_count(type);
/* Instantiate child table for each instance */
/* Create component array for creating the table */
ecs_entities_t components = {
.array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * type_count + 1)
};
void **c_info = ecs_os_alloca(ECS_SIZEOF(void*) * column_count);
/* 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 i, base_index = -1, pos = 0;
for (i = 0; i < type_count; i ++) {
ecs_entity_t c = type_array[i];
/* Make sure instances don't have EcsPrefab */
if (c == EcsPrefab) {
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_ROLE(c, CHILDOF) && (c & ECS_COMPONENT_MASK) == base) {
base_index = pos;
}
/* Store pointer to component array. We'll use this component array to
* create our new entities in bulk with new_w_data */
if (i < column_count) {
ecs_column_t *column = &child_data->columns[i];
c_info[pos] = ecs_vector_first_t(
column->data, column->size, column->alignment);
}
components.array[pos] = c;
pos ++;
}
ecs_assert(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;
pos ++;
}
components.count = pos;
/* Instantiate the prefab child table for each new instance */
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
int32_t child_count = ecs_vector_count(child_data->entities);
for (i = row; i < count + row; i ++) {
ecs_entity_t instance = entities[i];
/* Replace CHILDOF element in the component array with instance id */
components.array[base_index] = ECS_CHILDOF | instance;
/* Find or create table */
ecs_table_t *i_table = ecs_table_find_or_create(world, &components);
ecs_assert(i_table != NULL, 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. */
int j;
ecs_entity_t *children = ecs_vector_first(
child_data->entities, ecs_entity_t);
#ifndef NDEBUG
for (j = 0; j < child_count; j ++) {
ecs_entity_t child = children[j];
ecs_assert(child != instance, ECS_INVALID_PARAMETER, NULL);
}
#endif
/* Create children */
int32_t child_row;
new_w_data(world, i_table, NULL, child_count, c_info, &child_row);
/* If prefab child table has children itself, recursively instantiate */
ecs_data_t *i_data = ecs_table_get_data(i_table);
for (j = 0; j < child_count; j ++) {
ecs_entity_t child = children[j];
instantiate(world, child, i_table, i_data, child_row + j, 1);
}
}
}
static
void instantiate(
ecs_world_t * world,
ecs_entity_t base,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count)
{
/* If base is a parent, instantiate children of base for instances */
ecs_vector_t *child_tables = ecs_map_get_ptr(
world->child_tables, ecs_vector_t*, base);
if (child_tables) {
ecs_vector_each(child_tables, ecs_table_t*, child_table_ptr, {
instantiate_children(
world, base, table, data, row, count, *child_table_ptr);
});
}
}
static
bool override_component(
ecs_world_t *world,
ecs_entity_t component,
ecs_type_t type,
ecs_data_t *data,
ecs_column_t *column,
int32_t row,
int32_t count);
static
bool override_from_base(
ecs_world_t * world,
ecs_entity_t base,
ecs_entity_t component,
ecs_data_t * data,
ecs_column_t * column,
int32_t row,
int32_t count)
{
ecs_entity_info_t base_info;
ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL);
if (!ecs_get_info(world, base, &base_info) || !base_info.table) {
return false;
}
void *base_ptr = get_component(&base_info, component);
if (base_ptr) {
int16_t data_size = column->size;
void *data_array = ecs_vector_first_t(
column->data, column->size, column->alignment);
void *data_ptr = ECS_OFFSET(data_array, data_size * row);
component = ecs_get_typeid(world, component);
ecs_c_info_t *cdata = ecs_get_c_info(world, component);
int32_t index;
ecs_copy_t copy = cdata ? cdata->lifecycle.copy : NULL;
if (copy) {
ecs_entity_t *entities = ecs_vector_first(
data->entities, ecs_entity_t);
void *ctx = cdata->lifecycle.ctx;
for (index = 0; index < count; index ++) {
copy(world, component, &entities[row], &base,
data_ptr, base_ptr, ecs_to_size_t(data_size), 1, ctx);
data_ptr = ECS_OFFSET(data_ptr, data_size);
}
} else {
for (index = 0; index < count; index ++) {
ecs_os_memcpy(data_ptr, base_ptr, data_size);
data_ptr = ECS_OFFSET(data_ptr, data_size);
}
}
return true;
} else {
/* If component not found on base, check if base itself inherits */
ecs_type_t base_type = base_info.table->type;
return override_component(world, component, base_type, data, column,
row, count);
}
}
static
bool override_component(
ecs_world_t * world,
ecs_entity_t component,
ecs_type_t type,
ecs_data_t * data,
ecs_column_t * column,
int32_t row,
int32_t count)
{
ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t);
int32_t i, type_count = ecs_vector_count(type);
/* Walk prefabs */
i = type_count - 1;
do {
ecs_entity_t e = type_array[i];
if (e < ECS_TYPE_ROLE_START) {
break;
}
if (ECS_HAS_ROLE(e, INSTANCEOF)) {
if (override_from_base(world, e & ECS_COMPONENT_MASK, component,
data, column, row, count))
{
return true;
}
}
} while (--i >= 0);
return false;
}
static
void ecs_components_override(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count,
ecs_column_info_t * component_info,
int32_t component_count,
bool run_on_set)
{
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(component_count != 0, ECS_INTERNAL_ERROR, NULL);
ecs_table_t *table_without_base = table;
ecs_column_t *columns = data->columns;
ecs_type_t type = table->type;
int32_t column_count = table->column_count;
int i;
for (i = 0; i < component_count; i ++) {
ecs_entity_t component = component_info[i].id;
if (component >= ECS_HI_COMPONENT_ID) {
if (ECS_HAS_ROLE(component, INSTANCEOF)) {
ecs_entity_t base = component & ECS_COMPONENT_MASK;
/* Illegal to create an instance of 0 */
ecs_assert(base != 0, ECS_INVALID_PARAMETER, NULL);
instantiate(world, base, table, data, row, count);
/* If table has on_set systems, get table without the base
* entity that was just added. This is needed to determine the
* diff between the on_set systems of the current table and the
* table without the base, as these are the systems that need to
* be invoked */
ecs_entities_t to_remove = {
.array = &component,
.count = 1
};
table_without_base = ecs_table_traverse_remove(world,
table_without_base, &to_remove, NULL);
}
}
int32_t column_index = component_info[i].column;
if (column_index == -1 || column_index >= column_count) {
continue;
}
ecs_column_t *column = &columns[column_index];
if (override_component(world, component, type, data, column,
row, count))
{
ecs_entities_t to_remove = {
.array = &component,
.count = 1
};
table_without_base = ecs_table_traverse_remove(world,
table_without_base, &to_remove, NULL);
}
}
/* Run OnSet actions when a base entity is added to the entity for
* components not overridden by the entity. */
if (run_on_set && table_without_base != table) {
ecs_run_monitors(world, table, table->on_set_all, row, count,
table_without_base->on_set_all);
}
}
static
void set_switch(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t * data,
int32_t row,
int32_t count,
ecs_entities_t *entities,
bool reset)
{
ecs_entity_t *array = entities->array;
int32_t i, comp_count = entities->count;
for (i = 0; i < comp_count; i ++) {
ecs_entity_t e = array[i];
if (ECS_HAS_ROLE(e, CASE)) {
e = e & ECS_COMPONENT_MASK;
ecs_entity_t sw_case = 0;
if (!reset) {
sw_case = e;
ecs_assert(sw_case != 0, ECS_INTERNAL_ERROR, NULL);
}
int32_t sw_index = ecs_table_switch_from_case(world, table, e);
ecs_assert(sw_index != -1, ECS_INTERNAL_ERROR, NULL);
ecs_switch_t *sw = data->sw_columns[sw_index].data;
ecs_assert(sw != NULL, ECS_INTERNAL_ERROR, NULL);
int32_t r;
for (r = 0; r < count; r ++) {
ecs_switch_set(sw, row + r, sw_case);
}
}
}
}
static
void ecs_components_switch(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t row,
int32_t count,
ecs_entities_t *added,
ecs_entities_t *removed)
{
if (added) {
set_switch(world, table, data, row, count, added, false);
}
if (removed) {
set_switch(world, table, data, row, count, removed, true);
}
}
static
void ecs_components_on_add(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count,
ecs_column_info_t * component_info,
int32_t component_count)
{
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
int i;
for (i = 0; i < component_count; i ++) {
ecs_c_info_t *c_info = component_info[i].ci;
ecs_vector_t *triggers;
if (!c_info || !(triggers = c_info->on_add)) {
continue;
}
ecs_entity_t component = component_info[i].id;
ecs_run_component_trigger(
world, triggers, component, table, data, row, count);
}
}
static
void ecs_components_on_remove(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count,
ecs_column_info_t * component_info,
int32_t component_count)
{
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
int i;
for (i = 0; i < component_count; i ++) {
ecs_c_info_t *c_info = component_info[i].ci;
ecs_vector_t *triggers;
if (!c_info || !(triggers = c_info->on_remove)) {
continue;
}
ecs_entity_t component = component_info[i].id;
ecs_run_component_trigger(
world, triggers, component, table, data, row, count);
}
}
void ecs_run_add_actions(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count,
ecs_entities_t * added,
bool get_all,
bool run_on_set)
{
ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(added->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL);
ecs_column_info_t cinfo[ECS_MAX_ADD_REMOVE];
ecs_get_column_info(world, table, added, cinfo, get_all);
int added_count = added->count;
if (table->flags & EcsTableHasBase) {
ecs_components_override(
world, table, data, row, count, cinfo,
added_count, run_on_set);
}
if (table->flags & EcsTableHasSwitch) {
ecs_components_switch(world, table, data, row, count, added, NULL);
}
if (table->flags & EcsTableHasOnAdd) {
ecs_components_on_add(world, table, data, row, count,
cinfo, added_count);
}
}
void ecs_run_remove_actions(
ecs_world_t * world,
ecs_table_t * table,
ecs_data_t * data,
int32_t row,
int32_t count,
ecs_entities_t * removed,
bool get_all)
{
ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(removed->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL);
ecs_column_info_t cinfo[ECS_MAX_ADD_REMOVE];
ecs_get_column_info(world, table, removed, cinfo, get_all);
int removed_count = removed->count;
if (table->flags & EcsTableHasOnRemove) {
ecs_components_on_remove(world, table, data,
row, count, cinfo, removed_count);
}
}
static
int32_t new_entity(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entity_info_t * info,
ecs_table_t * new_table,
ecs_entities_t * added)
{
ecs_record_t *record = info->record;
ecs_data_t *new_data = ecs_table_get_or_create_data(new_table);
int32_t new_row;
ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL);
if (!record) {
record = ecs_eis_get_or_create(world, entity);
}
new_row = ecs_table_append(
world, new_table, new_data, entity, record, true);
record->table = new_table;
record->row = ecs_row_to_record(new_row, info->is_watched);
ecs_assert(
ecs_vector_count(new_data[0].entities) > new_row,
ECS_INTERNAL_ERROR, NULL);
if (new_table->flags & EcsTableHasAddActions) {
ecs_run_add_actions(
world, new_table, new_data, new_row, 1, added, true, true);
if (new_table->flags & EcsTableHasMonitors) {
ecs_run_monitors(
world, new_table, new_table->monitors, new_row, 1, NULL);
}
}
info->data = new_data;
return new_row;
}
static
int32_t move_entity(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entity_info_t * info,
ecs_table_t * src_table,
ecs_data_t * src_data,
int32_t src_row,
ecs_table_t * dst_table,
ecs_entities_t * added,
ecs_entities_t * removed)
{
ecs_data_t *dst_data = ecs_table_get_or_create_data(dst_table);
ecs_assert(src_data != dst_data, ECS_INTERNAL_ERROR, NULL);
ecs_assert(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL);
ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(src_data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(ecs_vector_count(src_data->entities) > src_row,
ECS_INTERNAL_ERROR, NULL);
ecs_record_t *record = info->record;
ecs_assert(!record || record == ecs_eis_get(world, entity),
ECS_INTERNAL_ERROR, NULL);
int32_t dst_row = ecs_table_append(world, dst_table, dst_data, entity,
record, false);
record->table = dst_table;
record->row = ecs_row_to_record(dst_row, info->is_watched);
ecs_assert(ecs_vector_count(src_data->entities) > src_row,
ECS_INTERNAL_ERROR, NULL);
/* Copy entity & components from src_table to dst_table */
if (src_table->type) {
ecs_table_move(world, entity, entity, dst_table, dst_data, dst_row,
src_table, src_data, src_row);
/* If components were removed, invoke remove actions before deleting */
if (removed && (src_table->flags & EcsTableHasRemoveActions)) {
/* If entity was moved, invoke UnSet monitors for each component that
* the entity no longer has */
ecs_run_monitors(world, dst_table, src_table->un_set_all,
src_row, 1, dst_table->un_set_all);
ecs_run_remove_actions(
world, src_table, src_data, src_row, 1, removed, false);
}
}
ecs_table_delete(world, src_table, src_data, src_row, false);
/* If components were added, invoke add actions */
if (src_table != dst_table || (added && added->count)) {
if (added && (dst_table->flags & EcsTableHasAddActions)) {
ecs_run_add_actions(
world, dst_table, dst_data, dst_row, 1, added, false, true);
}
/* Run monitors */
if (dst_table->flags & EcsTableHasMonitors) {
ecs_run_monitors(world, dst_table, dst_table->monitors, dst_row,
1, src_table->monitors);
}
/* If removed components were overrides, run OnSet systems for those, as
* the value of those components changed from the removed component to
* the value of component on the base entity */
if (removed && dst_table->flags & EcsTableHasBase) {
ecs_run_monitors(world, dst_table, src_table->on_set_override,
dst_row, 1, dst_table->on_set_override);
}
}
info->data = dst_data;
return dst_row;
}
static
void delete_entity(
ecs_world_t * world,
ecs_table_t * src_table,
ecs_data_t * src_data,
int32_t src_row,
ecs_entities_t * removed)
{
if (removed) {
ecs_run_monitors(world, src_table, src_table->un_set_all,
src_row, 1, NULL);
/* Invoke remove actions before deleting */
if (src_table->flags & EcsTableHasRemoveActions) {
ecs_run_remove_actions(
world, src_table, src_data, src_row, 1, removed, true);
}
}
ecs_table_delete(world, src_table, src_data, src_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
bool update_component_monitor_w_array(
ecs_world_t *world,
ecs_component_monitor_t * mon,
ecs_entities_t * entities)
{
bool childof_changed = false;
if (!entities) {
return false;
}
int i;
for (i = 0; i < entities->count; i ++) {
ecs_entity_t component = entities->array[i];
if (component < ECS_HI_COMPONENT_ID) {
ecs_component_monitor_mark(mon, component);
} else if (ECS_HAS_ROLE(component, CHILDOF)) {
childof_changed = true;
} else if (ECS_HAS_ROLE(component, INSTANCEOF)) {
/* If an INSTANCEOF relationship is added to a monitored entity (can
* be either a parent or a base) component monitors need to be
* evaluated for the components of the prefab. */
ecs_entity_t base = component & ECS_COMPONENT_MASK;
ecs_type_t type = ecs_get_type(world, base);
ecs_entities_t base_entities = ecs_type_to_entities(type);
/* This evaluates the component monitor for all components of the
* base entity. If the base entity contains INSTANCEOF relationships
* these will be evaluated recursively as well. */
update_component_monitor_w_array(world, mon, &base_entities);
}
}
return childof_changed;
}
static
void update_component_monitors(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entities_t * added,
ecs_entities_t * removed)
{
bool childof_changed = update_component_monitor_w_array(
world, &world->component_monitors, added);
childof_changed |= update_component_monitor_w_array(
world, &world->component_monitors, removed);
/* If this entity is a parent, check if anything changed that could impact
* its place in the hierarchy. If so, we need to mark all of the parent's
* entities as dirty. */
if (childof_changed &&
ecs_map_get(world->child_tables, ecs_vector_t*, entity))
{
ecs_type_t type = ecs_get_type(world, entity);
ecs_entities_t entities = ecs_type_to_entities(type);
update_component_monitor_w_array(world,
&world->parent_monitors, &entities);
}
}
static
void commit(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entity_info_t * info,
ecs_table_t * dst_table,
ecs_entities_t * added,
ecs_entities_t * removed)
{
ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL);
ecs_table_t *src_table = info->table;
if (src_table == dst_table) {
/* If source and destination table are the same no action is needed *
* However, if a component was added in the process of traversing a
* table, this suggests that a case switch could have occured. */
if (((added && added->count) || (removed && removed->count)) &&
src_table && src_table->flags & EcsTableHasSwitch)
{
ecs_components_switch(
world, src_table, info->data, info->row, 1, added, removed);
}
return;
}
if (src_table) {
ecs_data_t *src_data = info->data;
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL);
if (dst_table->type) {
info->row = move_entity(world, entity, info, src_table,
src_data, info->row, dst_table, added, removed);
info->table = dst_table;
} else {
delete_entity(
world, src_table, src_data, info->row,
removed);
ecs_eis_set(world, entity, &(ecs_record_t){
NULL, (info->is_watched == true) * -1
});
}
} else {
if (dst_table->type) {
info->row = new_entity(world, entity, info, dst_table, added);
info->table = dst_table;
}
}
/* 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 (info->is_watched) {
update_component_monitors(world, entity, added, removed);
}
if ((!src_table || !src_table->type) && world->range_check_enabled) {
ecs_assert(!world->stats.max_id || entity <= world->stats.max_id, ECS_OUT_OF_RANGE, 0);
ecs_assert(entity >= world->stats.min_id, ECS_OUT_OF_RANGE, 0);
}
}
static
void* get_base_component(
ecs_world_t * world,
ecs_stage_t * stage,
ecs_entity_info_t * info,
ecs_entity_t component)
{
ecs_type_t type = info->table->type;
ecs_entity_t *type_buffer = ecs_vector_first(type, ecs_entity_t);
int32_t p = -1;
void *ptr = NULL;
while (!ptr && (p = find_prefab(type, p)) != -1) {
ecs_entity_t prefab = type_buffer[p] & ECS_COMPONENT_MASK;
ecs_entity_info_t prefab_info;
if (ecs_get_info(world, prefab, &prefab_info) && prefab_info.table) {
ptr = get_component(&prefab_info, component);
if (!ptr) {
ptr = get_base_component(
world, stage, &prefab_info, component);
}
}
}
return ptr;
}
static
void new(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entities_t * to_add)
{
ecs_entity_info_t info = {0};
ecs_table_t *table = ecs_table_traverse_add(
world, world->stage.scope_table, to_add, NULL);
new_entity(world, entity, &info, table, to_add);
}
static
const ecs_entity_t* new_w_data(
ecs_world_t * world,
ecs_table_t * table,
ecs_entities_t * component_ids,
int32_t count,
void ** component_data,
int32_t * row_out)
{
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL);
int32_t sparse_count = ecs_eis_count(world);
const ecs_entity_t *ids = ecs_sparse_new_ids(world->store.entity_index, count);
ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_type_t type = table->type;
if (!type) {
return ids;
}
ecs_entities_t component_array = { 0 };
if (!component_ids) {
component_ids = &component_array;
component_array.array = ecs_vector_first(type, ecs_entity_t);
component_array.count = ecs_vector_count(type);
}
ecs_data_t *data = ecs_table_get_or_create_data(table);
int32_t row = ecs_table_appendn(world, table, data, count, ids);
ecs_entities_t added = ecs_type_to_entities(type);
/* Update entity index. */
int i;
ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*);
for (i = 0; i < count; i ++) {
record_ptrs[row + i] = ecs_eis_set(world, ids[i],
&(ecs_record_t){
.table = table,
.row = row + i + 1
});
}
ecs_defer_none(world, &world->stage);
ecs_run_add_actions(world, table, data, row, count, &added,
true, component_data == NULL);
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;
for (c_i = 0; c_i < component_ids->count; c_i ++) {
ecs_entity_t c = component_ids->array[c_i];
/* Bulk copy column data into new table */
int32_t table_index = ecs_type_index_of(type, c);
ecs_assert(table_index >= 0, ECS_INTERNAL_ERROR, NULL);
if (table_index >= table->column_count) {
continue;
}
ecs_column_t *column = &data->columns[table_index];
int16_t size = column->size;
int16_t alignment = column->alignment;
void *ptr = ecs_vector_first_t(column->data, size, alignment);
ptr = ECS_OFFSET(ptr, size * row);
/* Copy component data */
void *src_ptr = component_data[c_i];
if (!src_ptr) {
continue;
}
ecs_c_info_t *cdata = get_c_info(world, c);
ecs_copy_t copy;
if (cdata && (copy = cdata->lifecycle.copy)) {
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
copy(world, c, entities, entities, ptr, src_ptr,
ecs_to_size_t(size), count, cdata->lifecycle.ctx);
} else {
ecs_os_memcpy(ptr, src_ptr, size * count);
}
};
ecs_run_set_systems(world, &added, table, data, row, count, true);
}
ecs_run_monitors(world, table, table->monitors, row, count, NULL);
ecs_defer_flush(world, &world->stage);
if (row_out) {
*row_out = row;
}
ids = ecs_sparse_ids(world->store.entity_index);
return &ids[sparse_count];
}
static
bool has_type(
ecs_world_t *world,
ecs_entity_t entity,
ecs_type_t type,
bool match_any,
bool match_prefabs)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
if (!entity) {
return false;
}
if (!type) {
return true;
}
ecs_world_t *world_arg = world;
ecs_type_t entity_type = ecs_get_type(world_arg, entity);
return ecs_type_contains(
world, entity_type, type, match_any, match_prefabs) != 0;
}
static
void add_remove(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entities_t * to_add,
ecs_entities_t * to_remove)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(to_add->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL);
ecs_assert(to_remove->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL);
ecs_entity_info_t info;
ecs_get_info(world, entity, &info);
ecs_entity_t add_buffer[ECS_MAX_ADD_REMOVE];
ecs_entity_t remove_buffer[ECS_MAX_ADD_REMOVE];
ecs_entities_t added = { .array = add_buffer };
ecs_entities_t removed = { .array = remove_buffer };
ecs_table_t *src_table = info.table;
ecs_table_t *dst_table = ecs_table_traverse_remove(
world, src_table, to_remove, &removed);
dst_table = ecs_table_traverse_add(
world, dst_table, to_add, &added);
commit(world, entity, &info, dst_table, &added, &removed);
}
static
void add_entities_w_info(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entity_info_t * info,
ecs_entities_t * components)
{
ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL);
ecs_entity_t buffer[ECS_MAX_ADD_REMOVE];
ecs_entities_t added = { .array = buffer };
ecs_table_t *src_table = info->table;
ecs_table_t *dst_table = ecs_table_traverse_add(
world, src_table, components, &added);
commit(world, entity, info, dst_table, &added, NULL);
}
static
void remove_entities_w_info(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entity_info_t * info,
ecs_entities_t * components)
{
ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL);
ecs_entity_t buffer[ECS_MAX_ADD_REMOVE];
ecs_entities_t removed = { .array = buffer };
ecs_table_t *src_table = info->table;
ecs_table_t *dst_table = ecs_table_traverse_remove(
world, src_table, components, &removed);
commit(world, entity, info, dst_table, NULL, &removed);
}
static
void add_entities(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entities_t * components)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
if (ecs_defer_add(world, stage, entity, components)) {
return;
}
ecs_entity_info_t info;
ecs_get_info(world, entity, &info);
ecs_entity_t buffer[ECS_MAX_ADD_REMOVE];
ecs_entities_t added = { .array = buffer };
ecs_table_t *src_table = info.table;
ecs_table_t *dst_table = ecs_table_traverse_add(
world, src_table, components, &added);
commit(world, entity, &info, dst_table, &added, NULL);
ecs_defer_flush(world, stage);
}
static
void remove_entities(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entities_t * components)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
if (ecs_defer_remove(world, stage, entity, components)) {
return;
}
ecs_entity_info_t info;
ecs_get_info(world, entity, &info);
ecs_entity_t buffer[ECS_MAX_ADD_REMOVE];
ecs_entities_t removed = { .array = buffer };
ecs_table_t *src_table = info.table;
ecs_table_t *dst_table = ecs_table_traverse_remove(
world, src_table, components, &removed);
commit(world, entity, &info, dst_table, NULL, &removed);
ecs_defer_flush(world, stage);
}
static
void *get_mutable(
ecs_world_t * world,
ecs_entity_t entity,
ecs_entity_t component,
ecs_entity_info_t * info,
bool * is_added)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(component != 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert((component & ECS_COMPONENT_MASK) == component || ECS_HAS_ROLE(component, TRAIT), ECS_INVALID_PARAMETER, NULL);
void *dst = NULL;
if (ecs_get_info(world, entity, info) && info->table) {
dst = get_component(info, component);
}
ecs_table_t *table = info->table;
if (!dst) {
ecs_entities_t to_add = {
.array = &component,
.count = 1
};
add_entities_w_info(world, entity, info, &to_add);
ecs_get_info(world, entity, info);
ecs_assert(info->table != NULL, ECS_INTERNAL_ERROR, NULL);
dst = get_component(info, component);
if (is_added) {
*is_added = table != info->table;
}
return dst;
} else {
if (is_added) {
*is_added = false;
}
return dst;
}
}
/* -- Private functions -- */
int32_t ecs_record_to_row(
int32_t row,
bool *is_watched_out)
{
bool is_watched = row < 0;
row = row * -(is_watched * 2 - 1) - 1 * (row != 0);
*is_watched_out = is_watched;
return row;
}
int32_t ecs_row_to_record(
int32_t row,
bool is_watched)
{
return (row + 1) * -(is_watched * 2 - 1);
}
ecs_entities_t ecs_type_to_entities(
ecs_type_t type)
{
return (ecs_entities_t){
.array = ecs_vector_first(type, ecs_entity_t),
.count = ecs_vector_count(type)
};
}
void ecs_set_watch(
ecs_world_t *world,
ecs_entity_t entity)
{
(void)world;
ecs_record_t *record = ecs_eis_get(world, entity);
if (!record) {
ecs_record_t new_record = {.row = -1, .table = NULL};
ecs_eis_set(world, entity, &new_record);
} else {
if (record->row > 0) {
record->row *= -1;
} else if (record->row == 0) {
/* If entity is empty, there is no index to change the sign of. In
* this case, set the index to -1, and assign an empty type. */
record->row = -1;
record->table = NULL;
}
}
}
ecs_entity_t ecs_find_in_type(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t component,
ecs_entity_t flags)
{
ecs_vector_each(type, ecs_entity_t, c_ptr, {
ecs_entity_t c = *c_ptr;
if (flags) {
if ((c & flags) != flags) {
continue;
}
}
ecs_entity_t e = c & ECS_COMPONENT_MASK;
if (component) {
ecs_type_t component_type = ecs_get_type(world, e);
if (!ecs_type_has_entity(world, component_type, component)) {
continue;
}
}
return e;
});
return 0;
}
/* -- Public functions -- */
ecs_entity_t ecs_new_id(
ecs_world_t *world)
{
ecs_entity_t entity;
int32_t thread_count = ecs_vector_count(world->workers);
if (thread_count >= 1) {
/* Can't atomically increase number above max int */
ecs_assert(
world->stats.last_id < UINT_MAX, ECS_INTERNAL_ERROR, NULL);
entity = (ecs_entity_t)ecs_os_ainc((int32_t*)&world->stats.last_id);
} else {
entity = ecs_eis_recycle(world);
}
ecs_assert(!world->stats.max_id || entity <= world->stats.max_id,
ECS_OUT_OF_RANGE, NULL);
return entity;
}
ecs_entity_t ecs_new_component_id(
ecs_world_t *world)
{
if (world->in_progress) {
/* Can't issue new id while iterating when in multithreaded mode */
ecs_assert(ecs_vector_count(world->workers) <= 1,
ECS_INVALID_WHILE_ITERATING, NULL);
}
ecs_entity_t id;
if (world->stats.last_component_id < ECS_HI_COMPONENT_ID) {
do {
id = world->stats.last_component_id ++;
} while (ecs_exists(world, id) && id < ECS_HI_COMPONENT_ID);
}
if (world->stats.last_component_id >= ECS_HI_COMPONENT_ID) {
/* If the low component ids are depleted, return a regular entity id */
id = ecs_new_id(world);
}
return id;
}
ecs_entity_t ecs_new_w_type(
ecs_world_t *world,
ecs_type_t type)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_entity_t entity = ecs_new_id(world);
if (type || world->stage.scope) {
ecs_entities_t to_add = ecs_type_to_entities(type);
if (ecs_defer_new(world, stage, entity, &to_add)) {
return entity;
}
new(world, entity, &to_add);
ecs_defer_flush(world, stage);
} else {
ecs_eis_set(world, entity, &(ecs_record_t){ 0 });
}
return entity;
}
ecs_entity_t ecs_new_w_entity(
ecs_world_t *world,
ecs_entity_t component)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_entity_t entity = ecs_new_id(world);
if (component || stage->scope) {
ecs_entities_t to_add = {
.array = &component,
.count = 1
};
if (ecs_defer_new(world, stage, entity, &to_add)) {
return entity;
}
ecs_entity_t old_scope = 0;
if (ECS_HAS_ROLE(component, CHILDOF)) {
old_scope = ecs_set_scope(world, 0);
}
new(world, entity, &to_add);
if (ECS_HAS_ROLE(component, CHILDOF)) {
ecs_set_scope(world, old_scope);
}
ecs_defer_flush(world, stage);
} else {
ecs_eis_set(world, entity, &(ecs_record_t){ 0 });
}
return entity;
}
const ecs_entity_t* ecs_bulk_new_w_data(
ecs_world_t *world,
int32_t count,
ecs_entities_t * components,
void * data)
{
ecs_stage_t *stage = ecs_get_stage(&world);
const ecs_entity_t *ids;
if (ecs_defer_bulk_new(world, stage, count, components, data, &ids)) {
return ids;
}
ecs_type_t type = ecs_type_find(world, components->array, components->count);
ecs_table_t *table = ecs_table_from_type(world, type);
ids = new_w_data(world, table, NULL, count, data, NULL);
ecs_defer_flush(world, stage);
return ids;
}
const ecs_entity_t* ecs_bulk_new_w_type(
ecs_world_t *world,
ecs_type_t type,
int32_t count)
{
ecs_stage_t *stage = ecs_get_stage(&world);
const ecs_entity_t *ids;
ecs_entities_t components = ecs_type_to_entities(type);
if (ecs_defer_bulk_new(world, stage, count, &components, NULL, &ids)) {
return ids;
}
ecs_table_t *table = ecs_table_from_type(world, type);
ids = new_w_data(world, table, NULL, count, NULL, NULL);
ecs_defer_flush(world, stage);
return ids;
}
const ecs_entity_t* ecs_bulk_new_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
int32_t count)
{
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_entities_t components = {
.array = &entity,
.count = 1
};
const ecs_entity_t *ids;
if (ecs_defer_bulk_new(world, stage, count, &components, NULL, &ids)) {
return ids;
}
ecs_table_t *table = ecs_table_find_or_create(world, &components);
ids = new_w_data(world, table, NULL, count, NULL, NULL);
ecs_defer_flush(world, stage);
return ids;
}
void ecs_clear(
ecs_world_t *world,
ecs_entity_t entity)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
if (ecs_defer_clear(world, stage, entity)) {
return;
}
ecs_entity_info_t info;
info.table = NULL;
ecs_get_info(world, entity, &info);
ecs_table_t *table = info.table;
if (table) {
ecs_type_t type = table->type;
/* Remove all components */
ecs_entities_t to_remove = ecs_type_to_entities(type);
remove_entities_w_info(world, entity, &info, &to_remove);
}
ecs_defer_flush(world, stage);
}
void ecs_delete_children(
ecs_world_t *world,
ecs_entity_t parent)
{
ecs_vector_t *child_tables = ecs_map_get_ptr(
world->child_tables, ecs_vector_t*, parent);
if (child_tables) {
ecs_table_t **tables = ecs_vector_first(child_tables, ecs_table_t*);
int32_t i, count = ecs_vector_count(child_tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = tables[i];
/* Recursively delete entities of children */
ecs_data_t *data = ecs_table_get_data(table);
if (data) {
ecs_entity_t *entities = ecs_vector_first(
data->entities, ecs_entity_t);
int32_t child, child_count = ecs_vector_count(data->entities);
for (child = 0; child < child_count; child ++) {
ecs_delete_children(world, entities[child]);
}
}
/* Clear components from table (invokes destructors, OnRemove) */
ecs_table_clear(world, table);
/* Delete table */
ecs_delete_table(world, table);
};
ecs_vector_free(child_tables);
}
ecs_map_remove(world->child_tables, parent);
}
void ecs_delete(
ecs_world_t *world,
ecs_entity_t entity)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
if (ecs_defer_delete(world, stage, entity)) {
return;
}
ecs_record_t *r = ecs_sparse_remove_get(
world->store.entity_index, ecs_record_t, entity);
if (r) {
ecs_entity_info_t info = {0};
set_info_from_record(entity, &info, r);
if (info.is_watched) {
ecs_delete_children(world, entity);
if (r->table) {
ecs_entities_t to_remove = ecs_type_to_entities(r->table->type);
update_component_monitors(world, entity, NULL, &to_remove);
}
}
/* If entity has components, remove them */
ecs_table_t *table = info.table;
if (table) {
ecs_type_t type = table->type;
ecs_entities_t to_remove = ecs_type_to_entities(type);
delete_entity(world, table, info.data, info.row, &to_remove);
r->table = NULL;
}
r->row = 0;
}
ecs_defer_flush(world, stage);
}
void ecs_add_type(
ecs_world_t *world,
ecs_entity_t entity,
ecs_type_t type)
{
ecs_entities_t components = ecs_type_to_entities(type);
add_entities(world, entity, &components);
}
void ecs_add_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t to_add)
{
ecs_entities_t components = { .array = &to_add, .count = 1 };
add_entities(world, entity, &components);
}
void ecs_remove_type(
ecs_world_t *world,
ecs_entity_t entity,
ecs_type_t type)
{
ecs_entities_t components = ecs_type_to_entities(type);
remove_entities(world, entity, &components);
}
void ecs_remove_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t to_remove)
{
ecs_entities_t components = { .array = &to_remove, .count = 1 };
remove_entities(world, entity, &components);
}
void ecs_add_remove_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t to_add,
ecs_entity_t to_remove)
{
ecs_entities_t components_add = { .array = &to_add, .count = 1 };
ecs_entities_t components_remove = { .array = &to_remove, .count = 1 };
add_remove(world, entity, &components_add, &components_remove);
}
void ecs_add_remove_type(
ecs_world_t *world,
ecs_entity_t entity,
ecs_type_t to_add,
ecs_type_t to_remove)
{
ecs_entities_t components_add = ecs_type_to_entities(to_add);
ecs_entities_t components_remove = ecs_type_to_entities(to_remove);
add_remove(world, entity, &components_add, &components_remove);
}
ecs_entity_t ecs_clone(
ecs_world_t *world,
ecs_entity_t dst,
ecs_entity_t src,
bool copy_value)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
if (!dst) {
dst = ecs_new_id(world);
}
if (ecs_defer_clone(world, stage, dst, src, copy_value)) {
return dst;
}
ecs_entity_info_t src_info;
bool found = ecs_get_info(world, src, &src_info);
ecs_table_t *src_table = src_info.table;
if (!found || !src_table) {
return dst;
}
ecs_type_t src_type = src_table->type;
ecs_entities_t to_add = ecs_type_to_entities(src_type);
ecs_entity_info_t dst_info = {0};
dst_info.row = new_entity(world, dst, &dst_info, src_table, &to_add);
if (copy_value) {
ecs_table_move(world, dst, src, src_table, dst_info.data,
dst_info.row, src_table, src_info.data, src_info.row);
int i;
for (i = 0; i < to_add.count; i ++) {
ecs_run_set_systems(world, &to_add,
src_table, src_info.data, dst_info.row, 1, true);
}
}
ecs_defer_flush(world, stage);
return dst;
}
const void* ecs_get_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_entity_info_t info;
void *ptr = NULL;
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL);
bool found = ecs_get_info(world, entity, &info);
if (found) {
if (!info.table) {
return NULL;
}
ptr = get_component(&info, component);
if (!ptr) {
if (component != ecs_typeid(EcsName) && component != EcsPrefab) {
ptr = get_base_component(
world, stage, &info, component);
}
}
}
return ptr;
}
const void* ecs_get_ref_w_entity(
ecs_world_t * world,
ecs_ref_t * ref,
ecs_entity_t entity,
ecs_entity_t component)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(ref != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!entity || !ref->entity || entity == ref->entity, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!component || !ref->component || component == ref->component, ECS_INVALID_PARAMETER, NULL);
ecs_record_t *record = ref->record;
entity |= ref->entity;
if (!record) {
record = ecs_eis_get(world, entity);
}
if (!record || !record->table) {
return NULL;
}
ecs_table_t *table = record->table;
if (ref->record == record &&
ref->table == table &&
ref->row == record->row &&
ref->alloc_count == table->alloc_count)
{
return ref->ptr;
}
component |= ref->component;
ref->entity = entity;
ref->component = component;
ref->table = table;
ref->row = record->row;
ref->alloc_count = table->alloc_count;
ecs_entity_info_t info = {0};
set_info_from_record(entity, &info, record);
ref->ptr = get_component(&info, component);
ref->record = record;
return ref->ptr;
}
void* ecs_get_mut_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component,
bool * is_added)
{
ecs_stage_t *stage = ecs_get_stage(&world);
void *result;
if (ecs_defer_set(
world, stage, EcsOpMut, entity, component, 0, NULL, &result, is_added))
{
return result;
}
ecs_entity_info_t info;
result = get_mutable(world, entity, component, &info, is_added);
ecs_defer_flush(world, stage);
return result;
}
void ecs_modified_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component)
{
ecs_stage_t *stage = ecs_get_stage(&world);
if (ecs_defer_modified(world, stage, entity, component)) {
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_assert(ecs_has_entity(world, entity, component),
ECS_INVALID_PARAMETER, NULL);
ecs_entity_info_t info = {0};
if (ecs_get_info(world, entity, &info)) {
ecs_entities_t added = {
.array = &component,
.count = 1
};
ecs_run_set_systems(world, &added,
info.table, info.data, info.row, 1, false);
}
ecs_defer_flush(world, stage);
}
static
ecs_entity_t assign_ptr_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component,
size_t size,
void * ptr,
bool is_move,
bool notify)
{
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_entities_t added = {
.array = &component,
.count = 1
};
if (!entity) {
entity = ecs_new_id(world);
ecs_entity_t scope = stage->scope;
if (scope) {
ecs_add_entity(world, entity, ECS_CHILDOF | scope);
}
}
if (ecs_defer_set(world, stage, EcsOpSet, entity, component,
ecs_from_size_t(size), ptr, NULL, NULL))
{
return entity;
}
ecs_entity_info_t info;
void *dst = get_mutable(world, entity, component, &info, NULL);
/* This can no longer happen since we defer operations */
ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL);
if (ptr) {
ecs_entity_t real_id = ecs_get_typeid(world, component);
ecs_c_info_t *cdata = get_c_info(world, real_id);
if (cdata) {
if (is_move) {
ecs_move_t move = cdata->lifecycle.move;
if (move) {
move(world, real_id, &entity, &entity, dst, ptr, size, 1,
cdata->lifecycle.ctx);
} else {
ecs_os_memcpy(dst, ptr, ecs_from_size_t(size));
}
} else {
ecs_copy_t copy = cdata->lifecycle.copy;
if (copy) {
copy(world, real_id, &entity, &entity, dst, ptr, size, 1,
cdata->lifecycle.ctx);
} else {
ecs_os_memcpy(dst, ptr, ecs_from_size_t(size));
}
}
} else {
ecs_os_memcpy(dst, ptr, ecs_from_size_t(size));
}
} else {
memset(dst, 0, size);
}
ecs_table_mark_dirty(info.table, component);
if (notify) {
ecs_run_set_systems(world, &added,
info.table, info.data, info.row, 1, false);
}
ecs_defer_flush(world, stage);
return entity;
}
ecs_entity_t ecs_set_ptr_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component,
size_t size,
const void *ptr)
{
/* Safe to cast away const: function won't modify if move arg is false */
return assign_ptr_w_entity(
world, entity, component, size, (void*)ptr, false, true);
}
ecs_entity_t ecs_get_case(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t sw_id)
{
ecs_entity_info_t info;
ecs_table_t *table;
if (!ecs_get_info(world, entity, &info) || !(table = info.table)) {
return 0;
}
sw_id = sw_id | ECS_SWITCH;
ecs_type_t type = table->type;
int32_t index = ecs_type_index_of(type, sw_id);
if (index == -1) {
return 0;
}
index -= table->sw_column_offset;
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL);
/* Data cannot be NULl, since entity is stored in the table */
ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_switch_t *sw = info.data->sw_columns[index].data;
return ecs_switch_get(sw, info.row);
}
void ecs_enable_component_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component,
bool enable)
{
ecs_stage_t *stage = ecs_get_stage(&world);
if (ecs_defer_enable(
world, stage, entity, component, enable))
{
return;
} else {
/* Operations invoked by enable/disable should not be deferred */
stage->defer --;
}
ecs_entity_info_t info;
ecs_get_info(world, entity, &info);
ecs_entity_t bs_id = (component & ECS_COMPONENT_MASK) | ECS_DISABLED;
ecs_table_t *table = info.table;
int32_t index = -1;
if (table) {
index = ecs_type_index_of(table->type, bs_id);
}
if (index == -1) {
ecs_add_entity(world, entity, bs_id);
ecs_enable_component_w_entity(world, entity, component, enable);
return;
}
index -= table->bs_column_offset;
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL);
/* Data cannot be NULl, since entity is stored in the table */
ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_bitset_t *bs = &info.data->bs_columns[index].data;
ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_bitset_set(bs, info.row, enable);
}
bool ecs_is_component_enabled_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component)
{
ecs_entity_info_t info;
ecs_table_t *table;
if (!ecs_get_info(world, entity, &info) || !(table = info.table)) {
return false;
}
ecs_entity_t bs_id = (component & ECS_COMPONENT_MASK) | ECS_DISABLED;
ecs_type_t type = table->type;
int32_t index = ecs_type_index_of(type, bs_id);
if (index == -1) {
/* If table does not have DISABLED column for component, component is
* always enabled, if the entity has it */
return ecs_has_entity(world, entity, component);
}
index -= table->bs_column_offset;
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL);
/* Data cannot be NULl, since entity is stored in the table */
ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_bitset_t *bs = &info.data->bs_columns[index].data;
return ecs_bitset_get(bs, info.row);
}
bool ecs_has_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component)
{
if (ECS_HAS_ROLE(component, CASE)) {
ecs_entity_info_t info;
ecs_table_t *table;
if (!ecs_get_info(world, entity, &info) || !(table = info.table)) {
return false;
}
int32_t index = ecs_table_switch_from_case(world, table, component);
ecs_assert(index < table->sw_column_count, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = info.data;
ecs_switch_t *sw = data->sw_columns[index].data;
ecs_entity_t value = ecs_switch_get(sw, info.row);
return value == (component & ECS_COMPONENT_MASK);
} else {
ecs_type_t type = ecs_get_type(world, entity);
return ecs_type_has_entity(world, type, component);
}
}
bool ecs_has_type(
ecs_world_t *world,
ecs_entity_t entity,
ecs_type_t type)
{
return has_type(world, entity, type, true, true);
}
ecs_entity_t ecs_get_parent_w_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t component)
{
ecs_type_t type = ecs_get_type(world, entity);
ecs_entity_t parent = ecs_find_in_type(world, type, component, ECS_CHILDOF);
return parent;
}
const char* ecs_get_name(
ecs_world_t *world,
ecs_entity_t entity)
{
if (entity == EcsSingleton) {
return "$";
}
const EcsName *id = ecs_get(world, entity, EcsName);
if (id) {
return id->value;
} else {
return NULL;
}
}
ecs_type_t ecs_type_from_entity(
ecs_world_t *world,
ecs_entity_t entity)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
if (!entity) {
return NULL;
}
const EcsType *type = ecs_get(world, entity, EcsType);
if (type) {
return type->normalized;
}
return ecs_type_find(world, &entity, 1);
}
ecs_entity_t ecs_type_to_entity(
ecs_world_t *world,
ecs_type_t type)
{
(void)world;
if (!type) {
return 0;
}
/* If array contains n entities, it cannot be reduced to a single entity */
if (ecs_vector_count(type) != 1) {
ecs_abort(ECS_TYPE_NOT_AN_ENTITY, NULL);
}
return *(ecs_vector_first(type, ecs_entity_t));
}
bool ecs_is_alive(
ecs_world_t *world,
ecs_entity_t e)
{
return ecs_eis_is_alive(world, e);
}
bool ecs_exists(
ecs_world_t *world,
ecs_entity_t e)
{
return ecs_eis_exists(world, e);
}
ecs_type_t ecs_get_type(
ecs_world_t *world,
ecs_entity_t entity)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_record_t *record = NULL;
record = ecs_eis_get(world, entity);
ecs_table_t *table;
if (record && (table = record->table)) {
return table->type;
}
return NULL;
}
ecs_entity_t ecs_get_typeid(
ecs_world_t *world,
ecs_entity_t e)
{
if (ECS_HAS_ROLE(e, TRAIT)) {
ecs_entity_t trait = ecs_entity_t_hi(e & ECS_COMPONENT_MASK);
if (ecs_has(world, trait, EcsComponent)) {
/* This is not a trait tag, trait is the value */
return trait;
} else {
/* This is a trait tag, component is the value */
return ecs_entity_t_lo(e);
}
} else if (e & ECS_ROLE_MASK) {
return 0;
}
return e;
}
int32_t ecs_count_type(
ecs_world_t *world,
ecs_type_t type)
{
if (!type) {
return 0;
}
return ecs_count_w_filter(world, &(ecs_filter_t){
.include = type
});
}
int32_t ecs_count_entity(
ecs_world_t *world,
ecs_entity_t entity)
{
if (!entity) {
return 0;
}
/* Get temporary type that just contains entity */
ECS_VECTOR_STACK(type, ecs_entity_t, &entity, 1);
return ecs_count_w_filter(world, &(ecs_filter_t){
.include = type
});
}
int32_t ecs_count_w_filter(
ecs_world_t *world,
const ecs_filter_t *filter)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_sparse_t *tables = world->store.tables;
int32_t i, count = ecs_sparse_count(tables);
int32_t result = 0;
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(tables, ecs_table_t, i);
if (!filter || ecs_table_match_filter(world, table, filter)) {
result += ecs_table_count(table);
}
}
return result;
}
bool ecs_defer_begin(
ecs_world_t *world)
{
ecs_stage_t *stage = ecs_get_stage(&world);
if (world->in_progress) {
return ecs_stage_defer_begin(world, stage);
} else {
return ecs_defer_none(world, stage);
}
}
bool ecs_defer_end(
ecs_world_t *world)
{
ecs_stage_t *stage = ecs_get_stage(&world);
if (world->in_progress) {
return ecs_stage_defer_end(world, stage);
} else {
return ecs_defer_flush(world, stage);
}
}
static
size_t append_to_str(
char **buffer,
const char *str,
size_t bytes_left,
size_t *required)
{
char *ptr = *buffer;
size_t len = strlen(str);
size_t to_write;
if (bytes_left < len) {
to_write = bytes_left;
bytes_left = 0;
} else {
to_write = len;
bytes_left -= len;
}
if (to_write) {
ecs_os_memcpy(ptr, str, to_write);
}
(*required) += len;
(*buffer) += to_write;
return bytes_left;
}
const char* ecs_role_str(
ecs_entity_t entity)
{
if (ECS_HAS_ROLE(entity, CHILDOF)) {
return "CHILDOF";
} else
if (ECS_HAS_ROLE(entity, INSTANCEOF)) {
return "INSTANCEOF";
} else
if (ECS_HAS_ROLE(entity, TRAIT)) {
return "TRAIT";
} else
if (ECS_HAS_ROLE(entity, DISABLED)) {
return "DISABLED";
} else
if (ECS_HAS_ROLE(entity, XOR)) {
return "XOR";
} else
if (ECS_HAS_ROLE(entity, OR)) {
return "OR";
} else
if (ECS_HAS_ROLE(entity, AND)) {
return "AND";
} else
if (ECS_HAS_ROLE(entity, NOT)) {
return "NOT";
} else
if (ECS_HAS_ROLE(entity, SWITCH)) {
return "SWITCH";
} else
if (ECS_HAS_ROLE(entity, CASE)) {
return "CASE";
} else
if (ECS_HAS_ROLE(entity, OWNED)) {
return "OWNED";
} else {
return "UNKNOWN";
}
}
size_t ecs_entity_str(
ecs_world_t *world,
ecs_entity_t entity,
char *buffer,
size_t buffer_len)
{
char *ptr = buffer;
size_t bytes_left = buffer_len - 1, required = 0;
if (entity & ECS_ROLE_MASK) {
const char *role = ecs_role_str(entity);
bytes_left = append_to_str(&ptr, role, bytes_left, &required);
bytes_left = append_to_str(&ptr, "|", bytes_left, &required);
}
ecs_entity_t e = entity & ECS_COMPONENT_MASK;
if (ECS_HAS_ROLE(entity, TRAIT)) {
ecs_entity_t lo = ecs_entity_t_lo(e);
ecs_entity_t hi = ecs_entity_t_hi(e);
if (hi) {
char *hi_path = ecs_get_fullpath(world, hi);
bytes_left = append_to_str(&ptr, hi_path, bytes_left, &required);
ecs_os_free(hi_path);
bytes_left = append_to_str(&ptr, ">", bytes_left, &required);
}
char *lo_path = ecs_get_fullpath(world, lo);
bytes_left = append_to_str(&ptr, lo_path, bytes_left, &required);
ecs_os_free(lo_path);
} else {
char *path = ecs_get_fullpath(world, e);
bytes_left = append_to_str(&ptr, path, bytes_left, &required);
ecs_os_free(path);
}
ptr[0] = '\0';
return required;
}
static
void flush_bulk_new(
ecs_world_t * world,
ecs_op_t * op)
{
ecs_entity_t *ids = op->is._n.entities;
void **bulk_data = op->is._n.bulk_data;
if (bulk_data) {
ecs_entity_t *components = op->components.array;
int c, c_count = op->components.count;
for (c = 0; c < c_count; c ++) {
ecs_entity_t component = components[c];
const EcsComponent *cptr = ecs_component_from_id(world, component);
ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL);
size_t size = ecs_to_size_t(cptr->size);
void *ptr, *data = bulk_data[c];
int i, count = op->is._n.count;
for (i = 0, ptr = data; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) {
assign_ptr_w_entity(world, ids[i], component, size, ptr,
true, true);
}
ecs_os_free(data);
}
ecs_os_free(bulk_data);
} else {
int i, count = op->is._n.count;
for (i = 0; i < count; i ++) {
add_entities(world, ids[i], &op->components);
}
}
if (op->components.count > 1) {
ecs_os_free(op->components.array);
}
ecs_os_free(ids);
}
static
void discard_op(
ecs_op_t * op)
{
ecs_assert(op->kind != EcsOpBulkNew, ECS_INTERNAL_ERROR, NULL);
void *value = op->is._1.value;
if (value) {
ecs_os_free(value);
}
ecs_entity_t *components = op->components.array;
if (components) {
ecs_os_free(components);
}
}
static
bool valid_components(
ecs_world_t * world,
ecs_entities_t * entities)
{
ecs_entity_t *array = entities->array;
int32_t i, count = entities->count;
for (i = 0; i < count; i ++) {
ecs_entity_t e = array[i];
if (ECS_HAS_ROLE(e, CHILDOF)) {
e &= ECS_COMPONENT_MASK;
if (ecs_exists(world, e) && !ecs_is_alive(world, e)) {
return false;
}
}
}
return true;
}
/* Leave safe section. Run all deferred commands. */
bool ecs_defer_flush(
ecs_world_t * world,
ecs_stage_t * stage)
{
if (!--stage->defer) {
ecs_vector_t *defer_queue = stage->defer_queue;
stage->defer_queue = NULL;
if (defer_queue) {
ecs_op_t *ops = ecs_vector_first(defer_queue, ecs_op_t);
int32_t i, count = ecs_vector_count(defer_queue);
for (i = 0; i < count; i ++) {
ecs_op_t *op = &ops[i];
ecs_entity_t e = op->is._1.entity;
if (op->kind == EcsOpBulkNew) {
e = 0;
}
/* 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. */
if (e && !ecs_is_alive(world, e) && ecs_eis_exists(world, e)) {
ecs_assert(op->kind != EcsOpNew && op->kind != EcsOpClone,
ECS_INTERNAL_ERROR, NULL);
world->discard_count ++;
discard_op(op);
continue;
}
if (op->components.count == 1) {
op->components.array = &op->component;
}
switch(op->kind) {
case EcsOpNew:
if (op->scope) {
ecs_add_entity(world, e, ECS_CHILDOF | op->scope);
}
/* Fallthrough */
case EcsOpAdd:
if (valid_components(world, &op->components)) {
world->add_count ++;
add_entities(world, e, &op->components);
} else {
ecs_delete(world, e);
}
break;
case EcsOpRemove:
remove_entities(world, e, &op->components);
break;
case EcsOpClone:
ecs_clone(world, e, op->component, op->is._1.clone_value);
break;
case EcsOpSet:
assign_ptr_w_entity(world, e,
op->component, ecs_to_size_t(op->is._1.size),
op->is._1.value, true, true);
break;
case EcsOpMut:
assign_ptr_w_entity(world, e,
op->component, ecs_to_size_t(op->is._1.size),
op->is._1.value, true, false);
break;
case EcsOpModified:
ecs_modified_w_entity(world, e, op->component);
break;
case EcsOpDelete: {
ecs_delete(world, e);
break;
}
case EcsOpEnable:
ecs_enable_component_w_entity(
world, e, op->component, true);
break;
case EcsOpDisable:
ecs_enable_component_w_entity(
world, e, op->component, false);
break;
case EcsOpClear:
ecs_clear(world, e);
break;
case EcsOpBulkNew:
flush_bulk_new(world, op);
/* Continue since flush_bulk_new is repsonsible for cleaning
* up resources. */
continue;
}
if (op->components.count > 1) {
ecs_os_free(op->components.array);
}
if (op->is._1.value) {
ecs_os_free(op->is._1.value);
}
};
if (defer_queue != stage->defer_merge_queue) {
ecs_vector_free(defer_queue);
}
}
return true;
}
return false;
}
static
ecs_op_t* new_defer_op(ecs_stage_t *stage) {
ecs_op_t *result = ecs_vector_add(&stage->defer_queue, ecs_op_t);
ecs_os_memset(result, 0, ECS_SIZEOF(ecs_op_t));
return result;
}
static
void new_defer_component_ids(
ecs_op_t *op,
ecs_entities_t *components)
{
int32_t components_count = components->count;
if (components_count == 1) {
ecs_entity_t component = components->array[0];
op->component = component;
op->components = (ecs_entities_t) {
.array = NULL,
.count = 1
};
} else if (components_count) {
ecs_size_t array_size = components_count * ECS_SIZEOF(ecs_entity_t);
op->components.array = ecs_os_malloc(array_size);
ecs_os_memcpy(op->components.array, components->array, array_size);
op->components.count = components_count;
} else {
op->component = 0;
op->components = (ecs_entities_t){ 0 };
}
}
static
bool defer_add_remove(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_op_kind_t op_kind,
ecs_entity_t entity,
ecs_entities_t *components)
{
if (stage->defer) {
ecs_entity_t scope = stage->scope;
if (components) {
if (!components->count && !scope) {
return true;
}
}
ecs_op_t *op = new_defer_op(stage);
op->kind = op_kind;
op->scope = scope;
op->is._1.entity = entity;
new_defer_component_ids(op, components);
if (op_kind == EcsOpNew) {
world->new_count ++;
} else if (op_kind == EcsOpAdd) {
world->add_count ++;
} else if (op_kind == EcsOpRemove) {
world->remove_count ++;
}
return true;
} else {
stage->defer ++;
}
return false;
}
bool ecs_defer_none(
ecs_world_t *world,
ecs_stage_t *stage)
{
(void)world;
return (++ stage->defer) == 1;
}
bool ecs_defer_modified(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entity_t component)
{
(void)world;
if (stage->defer) {
ecs_op_t *op = new_defer_op(stage);
op->kind = EcsOpModified;
op->component = component;
op->is._1.entity = entity;
return true;
} else {
stage->defer ++;
}
return false;
}
bool ecs_defer_clone(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entity_t src,
bool clone_value)
{
(void)world;
if (stage->defer) {
ecs_op_t *op = new_defer_op(stage);
op->kind = EcsOpClone;
op->component = src;
op->is._1.entity = entity;
op->is._1.clone_value = clone_value;
return true;
} else {
stage->defer ++;
}
return false;
}
bool ecs_defer_delete(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity)
{
(void)world;
if (stage->defer) {
ecs_op_t *op = new_defer_op(stage);
op->kind = EcsOpDelete;
op->is._1.entity = entity;
world->delete_count ++;
return true;
} else {
stage->defer ++;
}
return false;
}
bool ecs_defer_clear(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity)
{
(void)world;
if (stage->defer) {
ecs_op_t *op = new_defer_op(stage);
op->kind = EcsOpClear;
op->is._1.entity = entity;
world->clear_count ++;
return true;
} else {
stage->defer ++;
}
return false;
}
bool ecs_defer_enable(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entity_t component,
bool enable)
{
(void)world;
if (stage->defer) {
ecs_op_t *op = new_defer_op(stage);
op->kind = enable ? EcsOpEnable : EcsOpDisable;
op->is._1.entity = entity;
op->component = component;
return true;
} else {
stage->defer ++;
}
return false;
}
bool ecs_defer_bulk_new(
ecs_world_t *world,
ecs_stage_t *stage,
int32_t count,
ecs_entities_t *components_ids,
void **component_data,
const ecs_entity_t **ids_out)
{
if (stage->defer) {
ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t));
void **defer_data = NULL;
world->bulk_new_count ++;
/* Use ecs_new_id as this is thread safe */
int i;
for (i = 0; i < count; i ++) {
ids[i] = ecs_new_id(world);
}
/* Create private copy for component data */
if (component_data) {
int c, c_count = components_ids->count;
ecs_entity_t *components = components_ids->array;
defer_data = ecs_os_malloc(ECS_SIZEOF(void*) * c_count);
for (c = 0; c < c_count; c ++) {
ecs_entity_t comp = components[c];
const EcsComponent *cptr = ecs_component_from_id(world, comp);
ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_size_t size = cptr->size;
void *data = ecs_os_malloc(size * count);
defer_data[c] = data;
ecs_c_info_t *cinfo = NULL;
ecs_entity_t real_id = ecs_get_typeid(world, comp);
if (real_id) {
cinfo = ecs_get_c_info(world, real_id);
}
ecs_xtor_t ctor;
if (cinfo && (ctor = cinfo->lifecycle.ctor)) {
void *ctx = cinfo->lifecycle.ctx;
ctor(world, comp, ids, data, ecs_to_size_t(size), count, ctx);
ecs_move_t move;
if ((move = cinfo->lifecycle.move)) {
move(world, comp, ids, ids, data, component_data[c],
ecs_to_size_t(size), count, ctx);
} else {
ecs_os_memcpy(data, component_data[c], size * count);
}
} else {
ecs_os_memcpy(data, component_data[c], size * count);
}
}
}
/* Store data in op */
ecs_op_t *op = new_defer_op(stage);
op->kind = EcsOpBulkNew;
op->is._n.entities = ids;
op->is._n.bulk_data = defer_data;
op->is._n.count = count;
new_defer_component_ids(op, components_ids);
*ids_out = ids;
return true;
} else {
stage->defer ++;
}
return false;
}
bool ecs_defer_new(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entities_t *components)
{
return defer_add_remove(world, stage, EcsOpNew, entity, components);
}
bool ecs_defer_add(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entities_t *components)
{
return defer_add_remove(world, stage, EcsOpAdd, entity, components);
}
bool ecs_defer_remove(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t entity,
ecs_entities_t *components)
{
return defer_add_remove(world, stage, EcsOpRemove, entity, components);
}
bool ecs_defer_set(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_op_kind_t op_kind,
ecs_entity_t entity,
ecs_entity_t component,
ecs_size_t size,
const void *value,
void **value_out,
bool *is_added)
{
if (stage->defer) {
world->set_count ++;
if (!size) {
const EcsComponent *cptr = ecs_component_from_id(world, component);
ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL);
size = cptr->size;
}
ecs_op_t *op = new_defer_op(stage);
op->kind = op_kind;
op->component = component;
op->is._1.entity = entity;
op->is._1.size = size;
op->is._1.value = ecs_os_malloc(size);
if (!value) {
value = ecs_get_w_entity(world, entity, component);
if (is_added) {
*is_added = value == NULL;
}
}
ecs_c_info_t *c_info = NULL;
ecs_entity_t real_id = ecs_get_typeid(world, component);
if (real_id) {
c_info = ecs_get_c_info(world, real_id);
}
ecs_xtor_t ctor;
if (c_info && (ctor = c_info->lifecycle.ctor)) {
ctor(world, component, &entity, op->is._1.value,
ecs_to_size_t(size), 1, c_info->lifecycle.ctx);
ecs_copy_t copy;
if (value) {
if ((copy = c_info->lifecycle.copy)) {
copy(world, component, &entity, &entity, op->is._1.value, value,
ecs_to_size_t(size), 1, c_info->lifecycle.ctx);
} else {
ecs_os_memcpy(op->is._1.value, value, size);
}
}
} else if (value) {
ecs_os_memcpy(op->is._1.value, value, size);
}
if (value_out) {
*value_out = op->is._1.value;
}
return true;
} else {
stage->defer ++;
}
return false;
}
void ecs_stage_merge(
ecs_world_t *world,
ecs_stage_t *stage)
{
ecs_assert(stage != &world->stage, ECS_INTERNAL_ERROR, NULL);
ecs_assert(stage->defer == 0, ECS_INVALID_PARAMETER, NULL);
if (ecs_vector_count(stage->defer_merge_queue)) {
stage->defer ++;
stage->defer_queue = stage->defer_merge_queue;
ecs_defer_flush(world, stage);
ecs_vector_clear(stage->defer_merge_queue);
ecs_assert(stage->defer_queue == NULL, ECS_INVALID_PARAMETER, NULL);
}
}
bool ecs_stage_defer_begin(
ecs_world_t *world,
ecs_stage_t *stage)
{
(void)world;
if (ecs_defer_none(world, stage)) {
stage->defer_queue = stage->defer_merge_queue;
return true;
}
return false;
}
bool ecs_stage_defer_end(
ecs_world_t *world,
ecs_stage_t *stage)
{
(void)world;
stage->defer --;
if (!stage->defer) {
stage->defer_merge_queue = stage->defer_queue;
stage->defer_queue = NULL;
return true;
}
return false;
}
void ecs_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 ecs_stage_init(
ecs_world_t *world,
ecs_stage_t *stage)
{
memset(stage, 0, sizeof(ecs_stage_t));
if (stage == &world->stage) {
stage->id = 0;
} else if (stage == &world->temp_stage) {
stage->id = 1;
}
}
void ecs_stage_deinit(
ecs_world_t *world,
ecs_stage_t *stage)
{
(void)world;
ecs_vector_free(stage->defer_queue);
ecs_vector_free(stage->defer_merge_queue);
}
/** Resize the vector buffer */
static
ecs_vector_t* 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 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 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_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_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;
}
}
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_vector_t *vector = *array_inout;
int32_t count, size;
if (vector) {
ecs_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;
}
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);
}
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)
{
ecs_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
ecs_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_index(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_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_index(
ecs_vector_t *vector,
ecs_size_t elem_size,
int16_t offset,
int32_t index)
{
ecs_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;
ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
int32_t size = vector->size;
int32_t count = vector->count;
if (count < size) {
size = count;
vector = resize(vector, offset, size * elem_size);
vector->size = size;
*array_inout = vector;
}
}
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_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 = ecs_next_pow_of_2(elem_count);
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_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_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)
{
if (!vector) {
return NULL;
}
ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL);
int32_t count = vector->count;
if (index >= count) {
return 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_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_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);
}
}
void _ecs_vector_memory(
const ecs_vector_t *vector,
ecs_size_t elem_size,
int16_t offset,
int32_t *allocd,
int32_t *used)
{
if (!vector) {
return;
}
ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL);
if (allocd) {
*allocd += vector->size * elem_size + offset;
}
if (used) {
*used += vector->count * elem_size;
}
}
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;
}
/** The number of elements in a single chunk */
#define CHUNK_COUNT (4096)
/** Compute the chunk index from an id by stripping the first 12 bits */
#define CHUNK(index) ((int32_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;
struct ecs_sparse_t {
ecs_vector_t *dense; /* Dense array with indices to sparse array. The
* dense array stores both alive and not alive
* sparse indices. The 'count' member keeps
* track of which indices are alive. */
ecs_vector_t *chunks; /* Chunks with sparse arrays & data */
ecs_size_t size; /* Element size */
int32_t count; /* Number of alive entries */
uint64_t max_id_local; /* Local max index (if no global is set) */
uint64_t *max_id; /* Maximum issued sparse index */
};
static
chunk_t* 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. */
result->sparse = ecs_os_calloc(ECS_SIZEOF(int32_t) * CHUNK_COUNT);
/* Initialize the data array with zero's to guarantee that data is
* always initialized. When an entry is removed, data is reset back to
* zero. Initialize now, as this can take advantage of calloc. */
result->data = ecs_os_calloc(sparse->size * CHUNK_COUNT);
ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL);
return result;
}
static
void chunk_free(
chunk_t *chunk)
{
ecs_os_free(chunk->sparse);
ecs_os_free(chunk->data);
}
static
chunk_t* get_chunk(
const ecs_sparse_t *sparse,
int32_t chunk_index)
{
/* 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* get_or_create_chunk(
ecs_sparse_t *sparse,
int32_t chunk_index)
{
chunk_t *chunk = get_chunk(sparse, chunk_index);
if (chunk) {
return chunk;
}
return chunk_new(sparse, chunk_index);
}
static
void grow_dense(
ecs_sparse_t *sparse)
{
ecs_vector_add(&sparse->dense, uint64_t);
}
static
uint64_t strip_generation(
uint64_t *index_out)
{
uint64_t index = *index_out;
uint64_t gen = index & ECS_GENERATION_MASK;
*index_out -= gen;
return gen;
}
static
void 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 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 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 get_id(
const ecs_sparse_t *sparse)
{
return sparse->max_id[0];
}
static
void 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 create_id(
ecs_sparse_t *sparse,
int32_t dense)
{
uint64_t index = inc_id(sparse);
grow_dense(sparse);
chunk_t *chunk = 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);
assign_index(chunk, dense_array, index, dense);
return index;
}
/* Create new id */
static
uint64_t 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 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* try_sparse_any(
const ecs_sparse_t *sparse,
uint64_t index)
{
strip_generation(&index);
chunk_t *chunk = 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* try_sparse(
const ecs_sparse_t *sparse,
uint64_t index)
{
chunk_t *chunk = 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 = 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* get_sparse(
const ecs_sparse_t *sparse,
int32_t dense,
uint64_t index)
{
strip_generation(&index);
chunk_t *chunk = get_chunk(sparse, CHUNK(index));
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 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 = get_or_create_chunk(sparse, CHUNK(index_b));
assign_index(chunk_a, dense_array, index_a, b);
assign_index(chunk_b, dense_array, index_b, a);
}
ecs_sparse_t* _ecs_sparse_new(
ecs_size_t size)
{
ecs_sparse_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_sparse_t));
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;
/* Consume first value in dense array as 0 is used in the sparse array to
* indicate that a sparse element hasn't been paired yet. */
ecs_vector_add(&result->dense, uint64_t);
result->count = 1;
return result;
}
void ecs_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 ecs_sparse_clear(
ecs_sparse_t *sparse)
{
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_vector_each(sparse->chunks, chunk_t, chunk, {
chunk_free(chunk);
});
ecs_vector_free(sparse->chunks);
ecs_vector_set_count(&sparse->dense, uint64_t, 1);
sparse->chunks = NULL;
sparse->count = 1;
sparse->max_id_local = 0;
}
void ecs_sparse_free(
ecs_sparse_t *sparse)
{
if (sparse) {
ecs_sparse_clear(sparse);
ecs_vector_free(sparse->dense);
ecs_os_free(sparse);
}
}
uint64_t ecs_sparse_new_id(
ecs_sparse_t *sparse)
{
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
return new_index(sparse);
}
const uint64_t* ecs_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 remaining = dense_count - count;
int32_t i, to_create = new_count - remaining;
if (to_create > 0) {
ecs_sparse_set_size(sparse, dense_count + to_create);
uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t);
for (i = 0; i < to_create; i ++) {
uint64_t index = create_id(sparse, count + i);
dense_array[dense_count + i] = index;
}
}
sparse->count += new_count;
return ecs_vector_get(sparse->dense, uint64_t, count);
}
void* _ecs_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 = new_index(sparse);
chunk_t *chunk = get_chunk(sparse, CHUNK(index));
ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL);
return DATA(chunk->data, size, OFFSET(index));
}
uint64_t ecs_sparse_last_id(
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* _ecs_sparse_get_or_create(
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 = strip_generation(&index);
chunk_t *chunk = 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. */
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 */
}
} else {
/* Element is not paired yet. Must add a new element to dense array */
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 >= get_id(sparse)) {
set_id(sparse, index + 1);
}
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 = get_or_create_chunk(sparse, CHUNK(unused));
assign_index(unused_chunk, dense_array, unused, dense_count);
}
assign_index(chunk, dense_array, index, count);
dense_array[count] |= gen;
}
return DATA(chunk->data, sparse->size, offset);
}
void* _ecs_sparse_set(
ecs_sparse_t * sparse,
ecs_size_t elem_size,
uint64_t index,
void * value)
{
void *ptr = _ecs_sparse_get_or_create(sparse, elem_size, index);
ecs_os_memcpy(ptr, value, elem_size);
return ptr;
}
void* _ecs_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 = get_or_create_chunk(sparse, CHUNK(index));
uint64_t gen = 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 | 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 */
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 ecs_sparse_remove(
ecs_sparse_t *sparse,
uint64_t index)
{
void *ptr = _ecs_sparse_remove_get(sparse, 0, index);
if (ptr) {
ecs_os_memset(ptr, 0, sparse->size);
}
}
void ecs_sparse_set_generation(
ecs_sparse_t *sparse,
uint64_t index)
{
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index));
uint64_t index_w_gen = index;
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 ecs_sparse_exists(
ecs_sparse_t *sparse,
uint64_t index)
{
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index));
strip_generation(&index);
int32_t offset = OFFSET(index);
int32_t dense = chunk->sparse[offset];
return dense != 0;
}
void* _ecs_sparse_get(
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 get_sparse(sparse, dense_index, dense_array[dense_index]);
}
bool ecs_sparse_is_alive(
const ecs_sparse_t *sparse,
uint64_t index)
{
return try_sparse(sparse, index) != NULL;
}
void* _ecs_sparse_get_sparse(
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 try_sparse(sparse, index);
}
void* _ecs_sparse_get_sparse_any(
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 try_sparse_any(sparse, index);
}
int32_t ecs_sparse_count(
const ecs_sparse_t *sparse)
{
if (!sparse) {
return 0;
}
return sparse->count - 1;
}
int32_t ecs_sparse_size(
const ecs_sparse_t *sparse)
{
if (!sparse) {
return 0;
}
return ecs_vector_count(sparse->dense) - 1;
}
const uint64_t* ecs_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 ecs_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)
{
ecs_sparse_set_size(dst, ecs_sparse_size(src));
const uint64_t *indices = ecs_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 = _ecs_sparse_get_sparse(src, size, index);
void *dst_ptr = _ecs_sparse_get_or_create(dst, size, index);
ecs_sparse_set_generation(dst, index);
ecs_os_memcpy(dst_ptr, src_ptr, size);
}
set_id(dst, get_id(src));
ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL);
}
ecs_sparse_t* ecs_sparse_copy(
const ecs_sparse_t *src)
{
if (!src) {
return NULL;
}
ecs_sparse_t *dst = _ecs_sparse_new(src->size);
sparse_copy(dst, src);
return dst;
}
void ecs_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);
}
}
void ecs_sparse_memory(
ecs_sparse_t *sparse,
int32_t *allocd,
int32_t *used)
{
(void)sparse;
(void)allocd;
(void)used;
}
#ifdef FLECS_READER_WRITER
static
void ecs_name_writer_alloc(
ecs_name_writer_t *writer,
int32_t len)
{
writer->len = len;
if (writer->len > writer->max_len) {
ecs_os_free(writer->name);
writer->name = ecs_os_malloc(writer->len);
writer->max_len = writer->len;
}
writer->written = 0;
}
static
bool ecs_name_writer_write(
ecs_name_writer_t *writer,
const char *buffer)
{
ecs_size_t written = writer->len - writer->written;
char *name_ptr = ECS_OFFSET(writer->name, writer->written);
ecs_assert(name_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(buffer != NULL, ECS_INTERNAL_ERROR, NULL);
if (written >= ECS_SIZEOF(int32_t)) {
ecs_os_memcpy(name_ptr, buffer, ECS_SIZEOF(int32_t));
writer->written += ECS_SIZEOF(int32_t);
return writer->written != writer->len;
} else {
ecs_os_memcpy(name_ptr, buffer, written);
writer->written += written;
return false;
}
}
static
void ecs_name_writer_reset(
ecs_name_writer_t *writer)
{
writer->name = NULL;
writer->max_len = 0;
writer->len = 0;
}
static
void ecs_table_writer_register_table(
ecs_writer_t *stream)
{
ecs_world_t *world = stream->world;
ecs_table_writer_t *writer = &stream->table;
ecs_type_t type = ecs_type_find(world, writer->type_array, writer->type_count);
ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL);
writer->table = ecs_table_from_type(world, type);
ecs_assert(writer->table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_os_free(writer->type_array);
writer->type_array = NULL;
ecs_data_t *data = ecs_table_get_or_create_data(writer->table);
if (data->entities) {
/* Remove any existing entities from entity index */
ecs_vector_each(data->entities, ecs_entity_t, e_ptr, {
ecs_eis_delete(world, *e_ptr);
/* Don't increase generation to ensure the restored data exactly
* matches the data in the blob */
ecs_eis_set_generation(world, *e_ptr);
});
return;
} else {
/* Set size of table to 0. This will initialize columns */
ecs_table_set_size(world, writer->table, data, 0);
}
ecs_assert(writer->table != NULL, ECS_INTERNAL_ERROR, NULL);
}
static
void ecs_table_writer_finalize_table(
ecs_writer_t *stream)
{
ecs_world_t *world = stream->world;
ecs_table_writer_t *writer = &stream->table;
/* Register entities in table in entity index */
ecs_data_t *data = ecs_table_get_data(writer->table);
ecs_vector_t *entity_vector = data->entities;
ecs_entity_t *entities = ecs_vector_first(entity_vector, ecs_entity_t);
ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*);
int32_t i, count = ecs_vector_count(entity_vector);
for (i = 0; i < count; i ++) {
ecs_record_t *record_ptr = ecs_eis_get_any(world, entities[i]);
if (record_ptr) {
if (record_ptr->table != writer->table) {
ecs_table_t *table = record_ptr->table;
ecs_data_t *table_data = ecs_table_get_data(table);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_table_delete(world,
table, table_data, record_ptr->row - 1, false);
}
} else {
record_ptr = ecs_eis_get_or_create(world, entities[i]);
}
record_ptr->row = i + 1;
record_ptr->table = writer->table;
record_ptrs[i] = record_ptr;
/* Strip entity from generation */
ecs_entity_t id = entities[i] & ECS_ENTITY_MASK;
if (id >= world->stats.last_id) {
world->stats.last_id = id + 1;
}
if (id < ECS_HI_COMPONENT_ID) {
if (id >= world->stats.last_component_id) {
world->stats.last_component_id = id + 1;
}
}
}
}
static
void ecs_table_writer_prepare_column(
ecs_writer_t *stream,
int32_t size)
{
ecs_table_writer_t *writer = &stream->table;
ecs_data_t *data = ecs_table_get_or_create_data(writer->table);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
if (writer->column_index) {
ecs_column_t *column = &data->columns[writer->column_index - 1];
if (size) {
int32_t old_count = ecs_vector_count(column->data);
_ecs_vector_set_count(&column->data, ECS_VECTOR_U(size, 0), writer->row_count);
/* Initialize new elements to 0 */
void *buffer = ecs_vector_first_t(column->data, size, 0);
ecs_os_memset(ECS_OFFSET(buffer, old_count * size), 0,
(writer->row_count - old_count) * size);
}
writer->column_vector = column->data;
writer->column_size = ecs_to_i16(size);
} else {
ecs_vector_set_count(
&data->entities, ecs_entity_t, writer->row_count);
ecs_vector_set_count(
&data->record_ptrs, ecs_record_t*, writer->row_count);
writer->column_vector = data->entities;
writer->column_size = ECS_SIZEOF(ecs_entity_t);
}
writer->column_data = ecs_vector_first_t(writer->column_vector,
writer->column_size,
writer->column_alignment);
writer->column_written = 0;
}
static
void ecs_table_writer_next(
ecs_writer_t *stream)
{
ecs_table_writer_t *writer = &stream->table;
switch(writer->state) {
case EcsTableTypeSize:
writer->state = EcsTableType;
break;
case EcsTableType:
writer->state = EcsTableSize;
break;
case EcsTableSize:
writer->state = EcsTableColumn;
break;
case EcsTableColumnHeader:
writer->state = EcsTableColumnSize;
break;
case EcsTableColumnSize:
writer->state = EcsTableColumnData;
break;
case EcsTableColumnNameHeader:
writer->state = EcsTableColumnNameLength;
break;
case EcsTableColumnNameLength:
writer->state = EcsTableColumnName;
break;
case EcsTableColumnName:
writer->row_index ++;
if (writer->row_index < writer->row_count) {
writer->state = EcsTableColumnNameLength;
break;
}
// fall through
case EcsTableColumnData:
writer->column_index ++;
if (writer->column_index > writer->table->column_count) {
ecs_table_writer_finalize_table(stream);
stream->state = EcsStreamHeader;
writer->column_written = 0;
writer->state = 0;
writer->column_index = 0;
writer->row_index = 0;
} else {
writer->state = EcsTableColumn;
}
break;
default:
ecs_abort(ECS_INTERNAL_ERROR, NULL);
break;
}
}
static
ecs_size_t ecs_table_writer(
const char *buffer,
ecs_size_t size,
ecs_writer_t *stream)
{
ecs_table_writer_t *writer = &stream->table;
ecs_size_t written = 0;
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(size >= ECS_SIZEOF(int32_t), ECS_INVALID_PARAMETER, NULL);
if (!writer->state) {
writer->state = EcsTableTypeSize;
}
switch(writer->state) {
case EcsTableTypeSize:
ecs_os_memcpy(&writer->type_count, buffer, ECS_SIZEOF(int32_t));
writer->type_array = ecs_os_malloc(writer->type_count * ECS_SIZEOF(ecs_entity_t));
writer->type_max_count = writer->type_count;
writer->type_written = 0;
written = ECS_SIZEOF(int32_t);
ecs_table_writer_next(stream);
break;
case EcsTableType:
ecs_os_memcpy(ECS_OFFSET(writer->type_array, writer->type_written), buffer, ECS_SIZEOF(int32_t));
written = ECS_SIZEOF(int32_t);
writer->type_written += written;
if (writer->type_written == (writer->type_count * ECS_SIZEOF(ecs_entity_t))) {
ecs_table_writer_register_table(stream);
ecs_table_writer_next(stream);
}
break;
case EcsTableSize:
ecs_os_memcpy(&writer->row_count, buffer, ECS_SIZEOF(int32_t));
written += ECS_SIZEOF(int32_t);
ecs_table_writer_next(stream);
break;
case EcsTableColumn:
ecs_os_memcpy(&writer->state, buffer, ECS_SIZEOF(ecs_blob_header_kind_t));
if (writer->state != EcsTableColumnHeader &&
writer->state != EcsTableColumnNameHeader)
{
stream->error = ECS_DESERIALIZE_FORMAT_ERROR;
goto error;
}
written += ECS_SIZEOF(int32_t);
break;
case EcsTableColumnHeader:
case EcsTableColumnSize: {
int32_t column_size;
memcpy(&column_size, buffer, ECS_SIZEOF(int32_t));
ecs_table_writer_prepare_column(stream, column_size);
ecs_table_writer_next(stream);
written += ECS_SIZEOF(int32_t);
ecs_table_writer_next(stream);
/* If column has no size, there will be no column data, so skip to the
* next state. */
if (!writer->column_size) {
ecs_table_writer_next(stream);
}
break;
}
case EcsTableColumnData: {
written = writer->row_count * writer->column_size - writer->column_written;
if (written > size) {
written = size;
}
ecs_os_memcpy(ECS_OFFSET(writer->column_data, writer->column_written), buffer, written);
writer->column_written += written;
written = (((written - 1) / ECS_SIZEOF(int32_t)) + 1) * ECS_SIZEOF(int32_t);
if (writer->column_written == writer->row_count * writer->column_size) {
ecs_table_writer_next(stream);
}
break;
}
case EcsTableColumnNameHeader:
ecs_table_writer_prepare_column(stream, ECS_SIZEOF(EcsName));
ecs_table_writer_next(stream);
// fall through
case EcsTableColumnNameLength: {
int32_t name_size;
memcpy(&name_size, buffer, ECS_SIZEOF(int32_t));
ecs_name_writer_alloc(&writer->name, name_size);
written = ECS_SIZEOF(int32_t);
ecs_table_writer_next(stream);
break;
}
case EcsTableColumnName: {
written = ECS_SIZEOF(int32_t);
if (!ecs_name_writer_write(&writer->name, buffer)) {
EcsName *name_ptr = &((EcsName*)writer->column_data)[writer->row_index];
name_ptr->value = writer->name.name;
if (name_ptr->alloc_value) {
ecs_os_free(name_ptr->alloc_value);
}
name_ptr->alloc_value = writer->name.name;
/* Don't overwrite entity name */
ecs_name_writer_reset(&writer->name);
ecs_table_writer_next(stream);
}
break;
}
default:
ecs_abort(ECS_INTERNAL_ERROR, NULL);
break;
}
ecs_assert(written <= size, ECS_INTERNAL_ERROR, NULL);
return written;
error:
return -1;
}
int ecs_writer_write(
const char *buffer,
int32_t size,
ecs_writer_t *writer)
{
int32_t written = 0, total_written = 0, remaining = size;
if (!size) {
return 0;
}
ecs_assert(size >= ECS_SIZEOF(int32_t), ECS_INVALID_PARAMETER, NULL);
ecs_assert(size % 4 == 0, ECS_INVALID_PARAMETER, NULL);
while (total_written < size) {
if (writer->state == EcsStreamHeader) {
writer->state = *(ecs_blob_header_kind_t*)ECS_OFFSET(buffer,
total_written);
if (writer->state != EcsTableHeader) {
writer->error = ECS_DESERIALIZE_FORMAT_ERROR;
goto error;
}
written = ECS_SIZEOF(ecs_blob_header_kind_t);
} else
if (writer->state == EcsTableHeader) {
written = ecs_table_writer(ECS_OFFSET(buffer, total_written),
remaining, writer);
}
if (!written) {
break;
}
if (written == (ecs_size_t)-1) {
goto error;
}
remaining -= written;
total_written += written;
}
ecs_assert(total_written <= size, ECS_INTERNAL_ERROR, NULL);
return total_written == 0;
error:
return -1;
}
ecs_writer_t ecs_writer_init(
ecs_world_t *world)
{
return (ecs_writer_t){
.world = world,
.state = EcsStreamHeader,
};
}
#endif
#ifdef FLECS_MODULE
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 = ecs_to_i8(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 init_action,
const char *module_name,
void *handles_out,
size_t handles_size)
{
ecs_assert(!world->in_progress, ECS_INVALID_WHILE_ITERATING, NULL);
ecs_entity_t old_scope = ecs_set_scope(world, 0);
const char *old_name_prefix = world->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_1("import %s", module_name);
ecs_log_push();
/* Load module */
init_action(world);
/* Lookup module entity (must be registered by module) */
e = ecs_lookup_fullpath(world, module_name);
ecs_assert(e != 0, ECS_MODULE_UNDEFINED, module_name);
ecs_log_pop();
}
/* Copy value of module component in handles_out parameter */
if (handles_size && handles_out) {
void *handles_ptr = ecs_get_mut_w_entity(world, e, e, NULL);
ecs_os_memcpy(handles_out, handles_ptr, ecs_from_size_t(handles_size));
}
/* Restore to previous state */
ecs_set_scope(world, old_scope);
world->name_prefix = old_name_prefix;
return e;
}
ecs_entity_t ecs_import_from_library(
ecs_world_t *world,
const char *library_name,
const char *module_name)
{
ecs_assert(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_os_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 = ecs_to_i8(toupper(ch));
bptr ++;
capitalize = false;
} else {
*bptr = ecs_to_i8(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_os_err("failed to find library file for '%s'", library_name);
if (module != module_name) {
ecs_os_free(module);
}
return 0;
} else {
ecs_trace_1("found file '%s' for library '%s'",
library_filename, library_name);
}
ecs_os_dl_t dl = ecs_os_dlopen(library_filename);
if (!dl) {
ecs_os_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_1("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_os_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_1("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, NULL, 0);
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;
}
ecs_entity_t ecs_new_module(
ecs_world_t *world,
ecs_entity_t e,
const char *name,
size_t size,
size_t alignment)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
assert(world->magic == ECS_WORLD_MAGIC);
if (!e) {
char *module_path = ecs_module_path_from_c(name);
e = ecs_new_from_fullpath(world, module_path);
EcsName *name_ptr = ecs_get_mut(world, e, EcsName, NULL);
ecs_os_free(name_ptr->symbol);
/* Assign full path to symbol. This allows for modules to be redefined
* in C++ without causing name conflicts */
name_ptr->symbol = module_path;
}
ecs_entity_t result = ecs_new_component(world, e, NULL, size, alignment);
ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL);
/* Add module tag */
ecs_add_entity(world, result, EcsModule);
/* Add module to itself. This way we have all the module information stored
* in a single contained entity that we can use for namespacing */
ecs_set_ptr_w_entity(world, result, result, size, NULL);
/* Set the current scope to the module */
ecs_set_scope(world, result);
return result;
}
#endif
#ifdef FLECS_QUEUE
struct ecs_queue_t {
ecs_vector_t *data;
int32_t index;
};
ecs_queue_t* _ecs_queue_new(
ecs_size_t elem_size,
int16_t offset,
int32_t elem_count)
{
ecs_queue_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_queue_t));
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
result->data = _ecs_vector_new(elem_size, offset, elem_count);
result->index = 0;
return result;
}
ecs_queue_t* _ecs_queue_from_array(
ecs_size_t elem_size,
int16_t offset,
int32_t elem_count,
void *array)
{
ecs_queue_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_queue_t));
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
result->data = _ecs_vector_from_array(elem_size, offset, elem_count, array);
result->index = 0;
return result;
}
void* _ecs_queue_push(
ecs_queue_t *buffer,
ecs_size_t elem_size,
int16_t offset)
{
int32_t size = ecs_vector_size(buffer->data);
int32_t count = ecs_vector_count(buffer->data);
void *result;
if (count == buffer->index) {
result = _ecs_vector_add(&buffer->data, elem_size, offset);
} else {
result = _ecs_vector_get(buffer->data, elem_size, offset, buffer->index);
}
buffer->index = (buffer->index + 1) % size;
return result;
}
void ecs_queue_free(
ecs_queue_t *buffer)
{
ecs_vector_free(buffer->data);
ecs_os_free(buffer);
}
void* _ecs_queue_get(
ecs_queue_t *buffer,
ecs_size_t elem_size,
int16_t offset,
int32_t index)
{
int32_t count = ecs_vector_count(buffer->data);
int32_t size = ecs_vector_size(buffer->data);
index = ((buffer->index - count + size) + (int32_t)index) % size;
return _ecs_vector_get(buffer->data, elem_size, offset, index);
}
void* _ecs_queue_last(
ecs_queue_t *buffer,
ecs_size_t elem_size,
int16_t offset)
{
int32_t index = buffer->index;
if (!index) {
index = ecs_vector_size(buffer->data);
}
return _ecs_vector_get(buffer->data, elem_size, offset, index - 1);
}
int32_t ecs_queue_index(
ecs_queue_t *buffer)
{
return buffer->index;
}
int32_t ecs_queue_count(
ecs_queue_t *buffer)
{
return ecs_vector_count(buffer->data);
}
#endif
#ifdef FLECS_STATS
#ifdef FLECS_SYSTEM
#ifndef FLECS_SYSTEM_PRIVATE_H
#define FLECS_SYSTEM_PRIVATE_H
typedef struct EcsSystem {
ecs_iter_action_t action; /* Callback to be invoked for matching it */
void *ctx; /* Userdata for system */
ecs_entity_t entity; /* Entity id of system, used for ordering */
ecs_query_t *query; /* System query */
ecs_on_demand_out_t *on_demand; /* Keep track of [out] column refs */
ecs_system_status_action_t status_action; /* Status action */
void *status_ctx; /* User data for status action */
ecs_entity_t tick_source; /* Tick source associated with system */
int32_t invoke_count; /* Number of times system is invoked */
FLECS_FLOAT time_spent; /* Time spent on running system */
FLECS_FLOAT time_passed; /* Time passed since last invocation */
} EcsSystem;
/* Invoked when system becomes active / inactive */
void ecs_system_activate(
ecs_world_t *world,
ecs_entity_t system,
bool activate,
const EcsSystem *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,
EcsSystem *system_data,
FLECS_FLOAT delta_time,
int32_t offset,
int32_t limit,
const ecs_filter_t *filter,
void *param,
bool ran_by_app);
#endif
#endif
#ifdef FLECS_PIPELINE
#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 and contains
* information about the set of systems that need to be ran before a merge. */
typedef struct ecs_pipeline_op_t {
int32_t count; /**< Number of systems to run before merge */
} ecs_pipeline_op_t;
typedef struct EcsPipelineQuery {
ecs_query_t *query;
ecs_query_t *build_query;
int32_t match_count;
ecs_vector_t *ops;
} EcsPipelineQuery;
////////////////////////////////////////////////////////////////////////////////
//// Pipeline API
////////////////////////////////////////////////////////////////////////////////
int32_t ecs_pipeline_update(
ecs_world_t *world,
ecs_entity_t pipeline);
int32_t ecs_pipeline_begin(
ecs_world_t *world,
ecs_entity_t pipeline);
void ecs_pipeline_end(
ecs_world_t *world);
void ecs_pipeline_progress(
ecs_world_t *world,
ecs_entity_t pipeline,
FLECS_FLOAT delta_time);
////////////////////////////////////////////////////////////////////////////////
//// Worker API
////////////////////////////////////////////////////////////////////////////////
void ecs_worker_begin(
ecs_world_t *world);
bool ecs_worker_sync(
ecs_world_t *world);
void ecs_worker_end(
ecs_world_t *world);
void ecs_workers_progress(
ecs_world_t *world);
#endif
#endif
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 _record_gauge(
ecs_gauge_t *m,
int32_t t,
float value)
{
m->avg[t] = value;
m->min[t] = value;
m->max[t] = value;
}
static
float _record_counter(
ecs_counter_t *m,
int32_t t,
float value)
{
int32_t tp = t_prev(t);
float prev = m->value[tp];
m->value[t] = value;
_record_gauge((ecs_gauge_t*)m, t, value - prev);
return value - prev;
}
/* Macro's to silence conversion warnings without adding casts everywhere */
#define record_gauge(m, t, value)\
_record_gauge(m, t, (float)value)
#define record_counter(m, t, value)\
_record_counter(m, t, (float)value)
static
void print_value(
const char *name,
float value)
{
ecs_size_t len = ecs_os_strlen(name);
printf("%s: %*s %.2f\n", name, 32 - len, "", value);
}
static
void print_gauge(
const char *name,
int32_t t,
const ecs_gauge_t *m)
{
print_value(name, m->avg[t]);
}
static
void print_counter(
const char *name,
int32_t t,
const ecs_counter_t *m)
{
print_value(name, m->rate.avg[t]);
}
void ecs_gauge_reduce(
ecs_gauge_t *dst,
int32_t t_dst,
ecs_gauge_t *src,
int32_t t_src)
{
bool min_set = false;
dst->min[t_dst] = 0;
dst->avg[t_dst] = 0;
dst->max[t_dst] = 0;
int32_t i;
for (i = 0; i < ECS_STAT_WINDOW; i ++) {
int32_t t = (t_src + i) % ECS_STAT_WINDOW;
dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW;
if (!min_set || (src->min[t] < dst->min[t_dst])) {
dst->min[t_dst] = src->min[t];
min_set = true;
}
if ((src->max[t] > dst->max[t_dst])) {
dst->max[t_dst] = src->max[t];
}
}
}
void ecs_get_world_stats(
ecs_world_t *world,
ecs_world_stats_t *s)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL);
int32_t t = s->t = t_next(s->t);
float delta_world_time = record_counter(&s->world_time_total_raw, t, world->stats.world_time_total_raw);
record_counter(&s->world_time_total, t, world->stats.world_time_total);
record_counter(&s->frame_time_total, t, world->stats.frame_time_total);
record_counter(&s->system_time_total, t, world->stats.system_time_total);
record_counter(&s->merge_time_total, t, world->stats.merge_time_total);
float delta_frame_count = record_counter(&s->frame_count_total, t, world->stats.frame_count_total);
record_counter(&s->merge_count_total, t, world->stats.merge_count_total);
record_counter(&s->pipeline_build_count_total, t, world->stats.pipeline_build_count_total);
record_counter(&s->systems_ran_frame, t, world->stats.systems_ran_frame);
if (delta_world_time != 0.0 && delta_frame_count != 0.0) {
record_gauge(
&s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count));
} else {
record_gauge(&s->fps, t, 0);
}
record_gauge(&s->entity_count, t, ecs_sparse_count(world->store.entity_index));
record_gauge(&s->component_count, t, ecs_count_entity(world, ecs_typeid(EcsComponent)));
record_gauge(&s->query_count, t, ecs_vector_count(world->queries));
record_gauge(&s->system_count, t, ecs_count_entity(world, ecs_typeid(EcsSystem)));
record_counter(&s->new_count, t, world->new_count);
record_counter(&s->bulk_new_count, t, world->bulk_new_count);
record_counter(&s->delete_count, t, world->delete_count);
record_counter(&s->clear_count, t, world->clear_count);
record_counter(&s->add_count, t, world->add_count);
record_counter(&s->remove_count, t, world->remove_count);
record_counter(&s->set_count, t, world->set_count);
record_counter(&s->discard_count, t, world->discard_count);
/* Compute table statistics */
int32_t empty_table_count = 0;
int32_t singleton_table_count = 0;
int32_t matched_table_count = 0, matched_entity_count = 0;
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i);
int32_t entity_count = ecs_table_count(table);
if (!entity_count) {
empty_table_count ++;
}
/* Singleton tables are tables that have just one entity that also has
* itself in the table type. */
if (entity_count == 1) {
ecs_data_t *data = ecs_table_get_data(table);
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
if (ecs_type_has_entity(world, table->type, entities[0])) {
singleton_table_count ++;
}
}
/* If this table matches with queries and is not empty, increase the
* matched table & matched entity count. These statistics can be used to
* compute actual fragmentation ratio for queries. */
int32_t queries_matched = ecs_vector_count(table->queries);
if (queries_matched && entity_count) {
matched_table_count ++;
matched_entity_count += entity_count;
}
}
record_gauge(&s->matched_table_count, t, matched_table_count);
record_gauge(&s->matched_entity_count, t, matched_entity_count);
record_gauge(&s->table_count, t, count);
record_gauge(&s->empty_table_count, t, empty_table_count);
record_gauge(&s->singleton_table_count, t, singleton_table_count);
}
void ecs_get_query_stats(
ecs_world_t *world,
ecs_query_t *query,
ecs_query_stats_t *s)
{
(void)world;
int32_t t = s->t = t_next(s->t);
int32_t i, entity_count = 0, count = ecs_vector_count(query->tables);
ecs_matched_table_t *matched_tables = ecs_vector_first(
query->tables, ecs_matched_table_t);
for (i = 0; i < count; i ++) {
ecs_matched_table_t *matched = &matched_tables[i];
if (matched->iter_data.table) {
entity_count += ecs_table_count(matched->iter_data.table);
}
}
record_gauge(&s->matched_table_count, t, count);
record_gauge(&s->matched_empty_table_count, t,
ecs_vector_count(query->empty_tables));
record_gauge(&s->matched_entity_count, t, entity_count);
}
#ifdef FLECS_SYSTEM
bool ecs_get_system_stats(
ecs_world_t *world,
ecs_entity_t system,
ecs_system_stats_t *s)
{
const EcsSystem *ptr = ecs_get(world, system, EcsSystem);
if (!ptr) {
return false;
}
ecs_get_query_stats(world, ptr->query, &s->query_stats);
int32_t t = s->query_stats.t;
record_counter(&s->time_spent, t, ptr->time_spent);
record_counter(&s->invoke_count, t, ptr->invoke_count);
record_gauge(&s->active, t, !ecs_has_entity(world, system, EcsInactive));
record_gauge(&s->enabled, t, !ecs_has_entity(world, system, EcsDisabled));
return true;
}
#endif
#ifdef FLECS_PIPELINE
static ecs_system_stats_t* get_system_stats(
ecs_map_t *systems,
ecs_entity_t system)
{
ecs_system_stats_t *s = ecs_map_get(systems, ecs_system_stats_t, system);
if (!s) {
ecs_system_stats_t stats;
memset(&stats, 0, sizeof(ecs_system_stats_t));
ecs_map_set(systems, system, &stats);
s = ecs_map_get(systems, ecs_system_stats_t, system);
ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL);
}
return s;
}
bool ecs_get_pipeline_stats(
ecs_world_t *world,
ecs_entity_t pipeline,
ecs_pipeline_stats_t *s)
{
const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery);
if (!pq) {
return false;
}
/* First find out how many systems are matched by the pipeline */
ecs_iter_t it = ecs_query_iter(pq->query);
int32_t count = 0;
while (ecs_query_next(&it)) {
count += it.count;
}
if (!s->system_stats) {
s->system_stats = ecs_map_new(ecs_system_stats_t, count);
}
/* Also count synchronization points */
ecs_vector_t *ops = pq->ops;
ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t);
ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t);
count += ecs_vector_count(ops);
/* Make sure vector is large enough to store all systems & sync points */
ecs_vector_set_count(&s->systems, ecs_entity_t, count - 1);
ecs_entity_t *systems = ecs_vector_first(s->systems, ecs_entity_t);
/* Populate systems vector, keep track of sync points */
it = ecs_query_iter(pq->query);
int32_t i_system = 0, ran_since_merge = 0;
while (ecs_query_next(&it)) {
int32_t i;
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 */
}
ecs_system_stats_t *sys_stats = get_system_stats(
s->system_stats, it.entities[i]);
ecs_get_system_stats(world, it.entities[i], sys_stats);
}
}
ecs_assert(i_system == (count - 1), ECS_INTERNAL_ERROR, NULL);
return true;
}
#endif
void ecs_dump_world_stats(
ecs_world_t *world,
const ecs_world_stats_t *s)
{
int32_t t = s->t;
print_counter("Frame", t, &s->frame_count_total);
printf("-------------------------------------\n");
print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total);
print_counter("systems ran last frame", t, &s->systems_ran_frame);
printf("\n");
print_value("target FPS", world->stats.target_fps);
print_value("time scale", world->stats.time_scale);
printf("\n");
print_gauge("actual FPS", t, &s->fps);
print_counter("frame time", t, &s->frame_time_total);
print_counter("system time", t, &s->system_time_total);
print_counter("merge time", t, &s->merge_time_total);
print_counter("simulation time elapsed", t, &s->world_time_total);
printf("\n");
print_gauge("entity count", t, &s->entity_count);
print_gauge("component count", t, &s->component_count);
print_gauge("query count", t, &s->query_count);
print_gauge("system count", t, &s->system_count);
print_gauge("table count", t, &s->table_count);
print_gauge("singleton table count", t, &s->singleton_table_count);
print_gauge("empty table count", t, &s->empty_table_count);
printf("\n");
print_counter("deferred new operations", t, &s->new_count);
print_counter("deferred bulk_new operations", t, &s->bulk_new_count);
print_counter("deferred delete operations", t, &s->delete_count);
print_counter("deferred clear operations", t, &s->clear_count);
print_counter("deferred add operations", t, &s->add_count);
print_counter("deferred remove operations", t, &s->remove_count);
print_counter("deferred set operations", t, &s->set_count);
print_counter("discarded operations", t, &s->discard_count);
printf("\n");
}
#endif
#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;
};
static
ecs_data_t* duplicate_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *main_data)
{
ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t));
int32_t i, column_count = table->column_count;
ecs_entity_t *components = ecs_vector_first(table->type, ecs_entity_t);
result->columns = ecs_os_memdup(
main_data->columns, ECS_SIZEOF(ecs_column_t) * column_count);
/* Copy entities */
result->entities = ecs_vector_copy(main_data->entities, ecs_entity_t);
ecs_entity_t *entities = ecs_vector_first(result->entities, ecs_entity_t);
/* Copy record ptrs */
result->record_ptrs = ecs_vector_copy(main_data->record_ptrs, ecs_record_t*);
/* Copy each column */
for (i = 0; i < column_count; i ++) {
ecs_entity_t component = components[i];
ecs_column_t *column = &result->columns[i];
if (component > ECS_HI_COMPONENT_ID) {
column->data = NULL;
continue;
}
ecs_c_info_t *cdata = ecs_get_c_info(world, component);
int16_t size = column->size;
int16_t alignment = column->alignment;
ecs_copy_t copy;
if (cdata && (copy = cdata->lifecycle.copy)) {
int32_t count = ecs_vector_count(column->data);
ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, count);
ecs_vector_set_count_t(&dst_vec, size, alignment, count);
void *dst_ptr = ecs_vector_first_t(dst_vec, size, alignment);
void *ctx = cdata->lifecycle.ctx;
ecs_xtor_t ctor = cdata->lifecycle.ctor;
if (ctor) {
ctor(world, component, entities, dst_ptr, ecs_to_size_t(size),
count, ctx);
}
void *src_ptr = ecs_vector_first_t(column->data, size, alignment);
copy(world, component, entities, entities, dst_ptr, src_ptr,
ecs_to_size_t(size), count, ctx);
column->data = dst_vec;
} else {
column->data = ecs_vector_copy_t(column->data, size, alignment);
}
}
return result;
}
static
ecs_snapshot_t* snapshot_create(
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(ECS_SIZEOF(ecs_snapshot_t));
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
result->world = 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 = ecs_sparse_copy(entity_index);
result->tables = ecs_vector_new(ecs_table_leaf_t, 0);
}
ecs_iter_t iter_stack;
if (!iter) {
iter_stack = ecs_filter_iter(world, NULL);
iter = &iter_stack;
next = ecs_filter_next;
}
/* If an iterator is provided, this is a filterred snapshot. In this case we
* have to patch the entity index one by one upon restore, as we don't want
* to affect entities that were not part of the snapshot. */
else {
result->entity_index = NULL;
}
/* Iterate tables in iterator */
while (next(iter)) {
ecs_table_t *t = iter->table->table;
if (t->flags & EcsTableHasBuiltins) {
continue;
}
ecs_data_t *data = ecs_table_get_data(t);
if (!data || !data->entities || !ecs_vector_count(data->entities)) {
continue;
}
ecs_table_leaf_t *l = ecs_vector_add(&result->tables, ecs_table_leaf_t);
l->table = t;
l->type = t->type;
l->data = duplicate_data(world, t, data);
}
return result;
}
/** Create a snapshot */
ecs_snapshot_t* ecs_snapshot_take(
ecs_world_t *world)
{
ecs_snapshot_t *result = snapshot_create(
world,
world->store.entity_index,
NULL,
NULL);
result->last_id = world->stats.last_id;
return result;
}
/** Create a filtered snapshot */
ecs_snapshot_t* ecs_snapshot_take_w_iter(
ecs_iter_t *iter,
ecs_iter_next_action_t next)
{
ecs_world_t *world = iter->world;
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_snapshot_t *result = snapshot_create(
world,
world->store.entity_index,
iter,
next);
result->last_id = world->stats.last_id;
return result;
}
/** Restore a snapshot */
void ecs_snapshot_restore(
ecs_world_t *world,
ecs_snapshot_t *snapshot)
{
bool is_filtered = true;
if (snapshot->entity_index) {
ecs_sparse_restore(world->store.entity_index, snapshot->entity_index);
ecs_sparse_free(snapshot->entity_index);
is_filtered = false;
}
if (!is_filtered) {
world->stats.last_id = snapshot->last_id;
}
ecs_table_leaf_t *leafs = ecs_vector_first(snapshot->tables, ecs_table_leaf_t);
int32_t l = 0, count = ecs_vector_count(snapshot->tables);
int32_t t, table_count = ecs_sparse_count(world->store.tables);
for (t = 0; t < table_count; t ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, t);
if (table->flags & EcsTableHasBuiltins) {
continue;
}
ecs_table_leaf_t *leaf = NULL;
if (l < count) {
leaf = &leafs[l];
}
if (leaf && leaf->table == table) {
/* If the snapshot is filtered, update the entity index for the
* entities in the snapshot. If the snapshot was not filtered
* the entity index would have been replaced entirely, and this
* is not necessary. */
if (is_filtered) {
ecs_vector_each(leaf->data->entities, ecs_entity_t, e_ptr, {
ecs_record_t *r = ecs_eis_get(world, *e_ptr);
if (r && r->table) {
ecs_data_t *data = ecs_table_get_data(r->table);
/* Data must be not NULL, otherwise entity index could
* not point to it */
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
bool is_monitored;
int32_t row = ecs_record_to_row(r->row, &is_monitored);
/* Always delete entity, so that even if the entity is
* in the current table, there won't be duplicates */
ecs_table_delete(world, r->table, data, row, true);
} else {
ecs_eis_set_generation(world, *e_ptr);
}
});
int32_t old_count = ecs_table_count(table);
int32_t new_count = ecs_table_data_count(leaf->data);
ecs_data_t *data = ecs_table_get_data(table);
data = ecs_table_merge(world, table, table, data, leaf->data);
/* Run OnSet systems for merged entities */
ecs_entities_t components = ecs_type_to_entities(table->type);
ecs_run_set_systems(world, &components, table, data,
old_count, new_count, true);
ecs_os_free(leaf->data->columns);
} else {
ecs_table_replace_data(world, table, leaf->data);
}
ecs_os_free(leaf->data);
l ++;
} else {
/* If the snapshot is not filtered, the snapshot should restore the
* world to the exact state it was in. When a snapshot is filtered,
* it should only update the entities that were in the snapshot.
* If a table is found that was not in the snapshot, and the
* snapshot was not filtered, clear the table. */
if (!is_filtered) {
/* Use clear_silent so no triggers are fired */
ecs_table_clear_silent(world, table);
}
}
table->alloc_count ++;
}
/* If snapshot was not filtered, run OnSet systems now. This cannot be done
* while restoring the snapshot, because the world is in an inconsistent
* state while restoring. When a snapshot is filtered, the world is not left
* in an inconsistent state, which makes running OnSet systems while
* restoring safe */
if (!is_filtered) {
for (t = 0; t < table_count; t ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, t);
if (table->flags & EcsTableHasBuiltins) {
continue;
}
ecs_entities_t components = ecs_type_to_entities(table->type);
ecs_data_t *table_data = ecs_table_get_data(table);
int32_t entity_count = ecs_table_data_count(table_data);
ecs_run_set_systems(world, &components, table,
table_data, 0, entity_count, true);
}
}
ecs_vector_free(snapshot->tables);
ecs_os_free(snapshot);
}
ecs_iter_t ecs_snapshot_iter(
ecs_snapshot_t *snapshot,
const ecs_filter_t *filter)
{
ecs_snapshot_iter_t iter = {
.filter = filter ? *filter : (ecs_filter_t){0},
.tables = snapshot->tables,
.index = 0
};
return (ecs_iter_t){
.world = snapshot->world,
.table_count = ecs_vector_count(snapshot->tables),
.iter.snapshot = iter
};
}
bool ecs_snapshot_next(
ecs_iter_t *it)
{
ecs_snapshot_iter_t *iter = &it->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;
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = tables[i].data;
/* Table must have data or it wouldn't have been added */
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
if (!ecs_table_match_filter(it->world, table, &iter->filter)) {
continue;
}
iter->table.table = table;
it->table = &iter->table;
it->table_columns = data->columns;
it->count = ecs_table_data_count(data);
it->entities = ecs_vector_first(data->entities, ecs_entity_t);
iter->index = i + 1;
return true;
}
return false;
}
/** Cleanup snapshot */
void ecs_snapshot_free(
ecs_snapshot_t *snapshot)
{
ecs_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 *leaf = &tables[i];
ecs_table_clear_data(snapshot->world, leaf->table, leaf->data);
ecs_os_free(leaf->data);
}
ecs_vector_free(snapshot->tables);
ecs_os_free(snapshot);
}
#endif
#ifdef FLECS_DBG
ecs_table_t *ecs_dbg_find_table(
ecs_world_t *world,
ecs_type_t type)
{
ecs_table_t *table = ecs_table_from_type(world, type);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
return table;
}
void ecs_dbg_table(
ecs_world_t *world,
ecs_table_t *table,
ecs_dbg_table_t *dbg_out)
{
ecs_assert(dbg_out != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL);
*dbg_out = (ecs_dbg_table_t){.table = table};
dbg_out->type = table->type;
dbg_out->systems_matched = table->queries;
/* Determine components from parent/base entities */
ecs_entity_t *entities = ecs_vector_first(table->type, ecs_entity_t);
int32_t i, count = ecs_vector_count(table->type);
for (i = 0; i < count; i ++) {
ecs_entity_t e = entities[i];
if (ECS_HAS_ROLE(e, CHILDOF)) {
ecs_dbg_entity_t parent_dbg;
ecs_dbg_entity(world, e & ECS_COMPONENT_MASK, &parent_dbg);
ecs_dbg_table_t parent_table_dbg;
ecs_dbg_table(world, parent_dbg.table, &parent_table_dbg);
/* Owned and shared components are available from container */
dbg_out->container = ecs_type_merge(
world, dbg_out->container, parent_dbg.type, NULL);
dbg_out->container = ecs_type_merge(
world, dbg_out->container, parent_table_dbg.shared, NULL);
/* Add entity to list of parent entities */
dbg_out->parent_entities = ecs_type_add(
world, dbg_out->parent_entities, e & ECS_COMPONENT_MASK);
}
if (ECS_HAS_ROLE(e, INSTANCEOF)) {
ecs_dbg_entity_t base_dbg;
ecs_dbg_entity(world, e & ECS_COMPONENT_MASK, &base_dbg);
ecs_dbg_table_t base_table_dbg;
ecs_dbg_table(world, base_dbg.table, &base_table_dbg);
/* Owned and shared components are available from base */
dbg_out->shared = ecs_type_merge(
world, dbg_out->shared, base_dbg.type, NULL);
dbg_out->shared = ecs_type_merge(
world, dbg_out->shared, base_table_dbg.shared, NULL);
/* Never inherit EcsName or EcsPrefab */
dbg_out->shared = ecs_type_merge(
world, dbg_out->shared, NULL, ecs_type(EcsName));
dbg_out->shared = ecs_type_merge(
world, dbg_out->shared, NULL, ecs_type_from_entity(world, EcsPrefab));
/* Shared components are always masked by owned components */
dbg_out->shared = ecs_type_merge(
world, dbg_out->shared, NULL, table->type);
/* Add entity to list of base entities */
dbg_out->base_entities = ecs_type_add(
world, dbg_out->base_entities, e & ECS_COMPONENT_MASK);
/* Add base entities of entity to list of base entities */
dbg_out->base_entities = ecs_type_add(
world, base_table_dbg.base_entities, e & ECS_COMPONENT_MASK);
}
}
ecs_data_t *data = ecs_table_get_data(table);
if (data) {
dbg_out->entities = ecs_vector_first(data->entities, ecs_entity_t);
dbg_out->entities_count = ecs_vector_count(data->entities);
}
}
ecs_table_t* ecs_dbg_get_table(
ecs_world_t *world,
int32_t index)
{
if (ecs_sparse_count(world->store.tables) <= index) {
return NULL;
}
return ecs_sparse_get(
world->store.tables, ecs_table_t, index);
}
bool ecs_dbg_filter_table(
ecs_world_t *world,
ecs_table_t *table,
ecs_filter_t *filter)
{
return ecs_table_match_filter(world, table, filter);
}
void ecs_dbg_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_dbg_entity_t *dbg_out)
{
*dbg_out = (ecs_dbg_entity_t){.entity = entity};
ecs_entity_info_t info = { 0 };
if (ecs_get_info(world, entity, &info)) {
dbg_out->table = info.table;
dbg_out->row = info.row;
dbg_out->is_watched = info.is_watched;
dbg_out->type = info.table ? info.table->type : NULL;
}
}
#endif
#ifdef FLECS_READER_WRITER
static
bool iter_table(
ecs_table_reader_t *reader,
ecs_iter_t *it,
ecs_iter_next_action_t next,
bool skip_builtin)
{
bool table_found = false;
while (next(it)) {
ecs_table_t *table = it->table->table;
reader->table = table;
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = ecs_table_get_data(table);
reader->data = data;
reader->table_index ++;
if (skip_builtin && reader->table->flags & EcsTableHasBuiltins) {
continue;
}
if (!data || !it->count) {
continue;
}
table_found = true;
break;
}
return table_found;
}
static
void next_table(
ecs_reader_t *stream,
ecs_table_reader_t *reader)
{
int32_t count;
/* First iterate all component tables, as component data must always be
* stored in a blob before anything else */
bool table_found = iter_table(
reader, &stream->component_iter, stream->component_next, false);
/* If all components have been added, add the regular data tables. Make sure
* to not add component tables again, in case the provided iterator also
* matches component tables. */
if (!table_found) {
table_found = iter_table(
reader, &stream->data_iter, stream->data_next, true);
count = stream->data_iter.count;
} else {
count = stream->component_iter.count;
}
if (!table_found) {
stream->state = EcsFooterSegment;
} else {
reader->type = reader->table->type;
reader->total_columns = reader->table->column_count + 1;
reader->column_index = 0;
reader->row_count = count;
}
}
static
void ecs_table_reader_next(
ecs_reader_t *stream)
{
ecs_table_reader_t *reader = &stream->table;
switch(reader->state) {
case EcsTableHeader:
reader->state = EcsTableTypeSize;
break;
case EcsTableTypeSize:
reader->state = EcsTableType;
reader->type_written = 0;
break;
case EcsTableType:
reader->state = EcsTableSize;
break;
case EcsTableSize: {
reader->state = EcsTableColumnHeader;
break;
}
case EcsTableColumnHeader:
reader->state = EcsTableColumnSize;
if (!reader->column_index) {
reader->column_vector = reader->data->entities;
reader->column_size = ECS_SIZEOF(ecs_entity_t);
} else {
ecs_column_t *column =
&reader->data->columns[reader->column_index - 1];
reader->column_vector = column->data;
reader->column_size = column->size;
reader->column_alignment = column->alignment;
}
break;
case EcsTableColumnSize:
reader->state = EcsTableColumnData;
reader->column_data = ecs_vector_first_t(reader->column_vector,
reader->column_size, reader->column_alignment);
reader->column_written = 0;
break;
case EcsTableColumnNameHeader: {
reader->state = EcsTableColumnNameLength;
ecs_column_t *column =
&reader->data->columns[reader->column_index - 1];
reader->column_vector = column->data;
reader->column_data = ecs_vector_first(reader->column_vector, EcsName);
reader->row_index = 0;
break;
}
case EcsTableColumnNameLength:
reader->state = EcsTableColumnName;
reader->row_index ++;
break;
case EcsTableColumnName:
if (reader->row_index < reader->row_count) {
reader->state = EcsTableColumnNameLength;
break;
}
// fall through
case EcsTableColumnData:
reader->column_index ++;
if (reader->column_index == reader->total_columns) {
reader->state = EcsTableHeader;
next_table(stream, reader);
} else {
ecs_entity_t *type_buffer = ecs_vector_first(reader->type, ecs_entity_t);
if (reader->column_index >= 1) {
ecs_entity_t e = type_buffer[reader->column_index - 1];
if (e != ecs_typeid(EcsName)) {
reader->state = EcsTableColumnHeader;
} else {
reader->state = EcsTableColumnNameHeader;
}
} else {
reader->state = EcsTableColumnHeader;
}
}
break;
default:
ecs_abort(ECS_INTERNAL_ERROR, NULL);
}
return;
}
static
ecs_size_t ecs_table_reader(
char *buffer,
ecs_size_t size,
ecs_reader_t *stream)
{
if (!size) {
return 0;
}
if (size < ECS_SIZEOF(int32_t)) {
return -1;
}
ecs_table_reader_t *reader = &stream->table;
ecs_size_t read = 0;
if (!reader->state) {
next_table(stream, reader);
reader->state = EcsTableHeader;
}
switch(reader->state) {
case EcsTableHeader:
ecs_os_memcpy(buffer, &(ecs_blob_header_kind_t){EcsTableHeader}, ECS_SIZEOF(EcsTableHeader));
read = ECS_SIZEOF(ecs_blob_header_kind_t);
ecs_table_reader_next(stream);
break;
case EcsTableTypeSize:
ecs_os_memcpy(buffer, &(int32_t){ecs_vector_count(reader->type)}, ECS_SIZEOF(int32_t));
read = ECS_SIZEOF(int32_t);
ecs_table_reader_next(stream);
break;
case EcsTableType: {
ecs_entity_t *type_array = ecs_vector_first(reader->type, ecs_entity_t);
ecs_os_memcpy(buffer, ECS_OFFSET(type_array, reader->type_written), ECS_SIZEOF(int32_t));
reader->type_written += ECS_SIZEOF(int32_t);
read = ECS_SIZEOF(int32_t);
if (reader->type_written == ecs_vector_count(reader->type) * ECS_SIZEOF(ecs_entity_t)) {
ecs_table_reader_next(stream);
}
break;
}
case EcsTableSize:
ecs_os_memcpy(buffer, &(int32_t){ecs_table_count(reader->table)}, ECS_SIZEOF(int32_t));
read = ECS_SIZEOF(int32_t);
ecs_table_reader_next(stream);
break;
case EcsTableColumnHeader:
ecs_os_memcpy(buffer, &(ecs_blob_header_kind_t){EcsTableColumnHeader}, ECS_SIZEOF(ecs_blob_header_kind_t));
read = ECS_SIZEOF(ecs_blob_header_kind_t);
ecs_table_reader_next(stream);
break;
case EcsTableColumnSize: {
int32_t column_size = reader->column_size;
ecs_os_memcpy(buffer, &column_size, ECS_SIZEOF(int32_t));
read = ECS_SIZEOF(ecs_blob_header_kind_t);
ecs_table_reader_next(stream);
if (!reader->column_size) {
ecs_table_reader_next(stream);
}
break;
}
case EcsTableColumnData: {
ecs_size_t column_bytes = reader->column_size * reader->row_count;
read = column_bytes - reader->column_written;
if (read > size) {
read = size;
}
ecs_os_memcpy(buffer, ECS_OFFSET(reader->column_data, reader->column_written), read);
reader->column_written += read;
ecs_assert(reader->column_written <= column_bytes, ECS_INTERNAL_ERROR, NULL);
ecs_size_t align = (((read - 1) / ECS_SIZEOF(int32_t)) + 1) * ECS_SIZEOF(int32_t);
if (align != read) {
/* Initialize padding bytes to 0 to keep valgrind happy */
ecs_os_memset(ECS_OFFSET(buffer, read), 0, align - read);
/* Set read to align so that data is always aligned to 4 bytes */
read = align;
/* Buffer sizes are expected to be aligned to 4 bytes and the rest
* of the serialized data is aligned to 4 bytes. Should never happen
* that adding padding bytes exceeds the size. */
ecs_assert(read <= size, ECS_INTERNAL_ERROR, NULL);
}
if (reader->column_written == column_bytes) {
ecs_table_reader_next(stream);
}
break;
}
case EcsTableColumnNameHeader:
ecs_os_memcpy(buffer, &(ecs_blob_header_kind_t){EcsTableColumnNameHeader}, ECS_SIZEOF(ecs_blob_header_kind_t));
read = ECS_SIZEOF(ecs_blob_header_kind_t);
ecs_table_reader_next(stream);
break;
case EcsTableColumnNameLength: {
reader->name = ((EcsName*)reader->column_data)[reader->row_index].value;
reader->name_len = ecs_os_strlen(reader->name) + 1;
reader->name_written = 0;
int32_t name_len = reader->name_len;
ecs_os_memcpy(buffer, &name_len, ECS_SIZEOF(int32_t));
// *(int32_t*)buffer = reader->name_len;
read = ECS_SIZEOF(int32_t);
ecs_table_reader_next(stream);
break;
}
case EcsTableColumnName:
read = reader->name_len - reader->name_written;
if (read >= ECS_SIZEOF(int32_t)) {
int32_t i;
for (i = 0; i < 4; i ++) {
*(char*)ECS_OFFSET(buffer, i) =
*(char*)ECS_OFFSET(reader->name, reader->name_written + i);
}
reader->name_written += ECS_SIZEOF(int32_t);
} else {
ecs_os_memcpy(buffer, ECS_OFFSET(reader->name, reader->name_written), read);
ecs_os_memset(ECS_OFFSET(buffer, read), 0, ECS_SIZEOF(int32_t) - read);
reader->name_written += read;
}
/* Always align buffer to multiples of 4 bytes */
read = ECS_SIZEOF(int32_t);
if (reader->name_written == reader->name_len) {
ecs_table_reader_next(stream);
}
break;
default:
ecs_abort(ECS_INTERNAL_ERROR, NULL);
}
ecs_assert(read % 4 == 0, ECS_INTERNAL_ERROR, NULL);
return read;
}
int32_t ecs_reader_read(
char *buffer,
int32_t size,
ecs_reader_t *reader)
{
int32_t read, total_read = 0, remaining = size;
if (!size) {
return 0;
}
ecs_assert(size >= ECS_SIZEOF(int32_t), ECS_INVALID_PARAMETER, NULL);
ecs_assert(size % 4 == 0, ECS_INVALID_PARAMETER, NULL);
if (reader->state == EcsTableSegment) {
while ((read = ecs_table_reader(ECS_OFFSET(buffer, total_read), remaining, reader))) {
remaining -= read;
total_read += read;
if (reader->state != EcsTableSegment) {
break;
}
ecs_assert(remaining % 4 == 0, ECS_INTERNAL_ERROR, NULL);
}
}
return total_read;
}
ecs_reader_t ecs_reader_init(
ecs_world_t *world)
{
ecs_reader_t result = {
.world = world,
.state = EcsTableSegment,
.component_iter = ecs_filter_iter(world, &(ecs_filter_t){
.include = ecs_type(EcsComponent)
}),
.component_next = ecs_filter_next,
.data_iter = ecs_filter_iter(world, NULL),
.data_next = ecs_filter_next
};
return result;
}
ecs_reader_t ecs_reader_init_w_iter(
ecs_iter_t *it,
ecs_iter_next_action_t next)
{
ecs_world_t *world = it->world;
ecs_reader_t result = {
.world = world,
.state = EcsTableSegment,
.component_iter = ecs_filter_iter(world, &(ecs_filter_t){
.include = ecs_type(EcsComponent)
}),
.component_next = ecs_filter_next,
.data_iter = *it,
.data_next = next
};
return result;
}
#endif
#ifdef FLECS_BULK
static
void bulk_delete(
ecs_world_t *world,
const ecs_filter_t *filter,
bool is_delete)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL);
(void)stage;
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i);
if (table->flags & EcsTableHasBuiltins) {
continue;
}
if (!ecs_table_match_filter(world, table, filter)) {
continue;
}
/* Remove entities from index */
ecs_data_t *data = ecs_table_get_data(table);
if (!data) {
/* If table has no data, there's nothing to delete */
continue;
}
ecs_vector_t *entities = NULL;
if (data) {
entities = data->entities;
}
ecs_vector_each(entities, ecs_entity_t, e_ptr, {
ecs_eis_delete(world, *e_ptr);
})
/* Both filters passed, clear table */
if (is_delete) {
ecs_table_clear(world, table);
} else {
ecs_table_clear_silent(world, table);
}
}
}
static
void merge_table(
ecs_world_t *world,
ecs_table_t *dst_table,
ecs_table_t *src_table,
ecs_entities_t *to_add,
ecs_entities_t *to_remove)
{
if (!dst_table->type) {
/* If this removes all components, clear table */
ecs_table_clear(world, src_table);
} else {
/* Merge table into dst_table */
if (dst_table != src_table) {
ecs_data_t *src_data = ecs_table_get_data(src_table);
int32_t dst_count = ecs_table_count(dst_table);
int32_t src_count = ecs_table_count(src_table);
if (to_remove && to_remove->count && src_data) {
ecs_run_remove_actions(world, src_table,
src_data, 0, src_count, to_remove, false);
}
ecs_data_t *dst_data = ecs_table_get_data(dst_table);
dst_data = ecs_table_merge(
world, dst_table, src_table, dst_data, src_data);
if (to_add && to_add->count && dst_data) {
ecs_run_add_actions(world, dst_table, dst_data,
dst_count, src_count, to_add, false, true);
}
}
}
}
/* -- Public API -- */
void ecs_bulk_delete(
ecs_world_t *world,
const ecs_filter_t *filter)
{
bulk_delete(world, filter, true);
}
void ecs_bulk_add_remove_type(
ecs_world_t *world,
ecs_type_t to_add,
ecs_type_t to_remove,
const ecs_filter_t *filter)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL);
(void)stage;
ecs_entities_t to_add_array = ecs_type_to_entities(to_add);
ecs_entities_t to_remove_array = ecs_type_to_entities(to_remove);
ecs_entities_t added = {
.array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_add_array.count),
.count = 0
};
ecs_entities_t removed = {
.array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_remove_array.count),
.count = 0
};
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i);
if (table->flags & EcsTableHasBuiltins) {
continue;
}
if (!ecs_table_match_filter(world, table, filter)) {
continue;
}
ecs_table_t *dst_table = ecs_table_traverse_remove(
world, table, &to_remove_array, &removed);
dst_table = ecs_table_traverse_add(
world, dst_table, &to_add_array, &added);
ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL);
ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL);
if (table == dst_table || (!added.count && !removed.count)) {
continue;
}
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL);
merge_table(world, dst_table, table, &added, &removed);
added.count = 0;
removed.count = 0;
}
}
void ecs_bulk_add_type(
ecs_world_t *world,
ecs_type_t to_add,
const ecs_filter_t *filter)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(to_add != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL);
(void)stage;
ecs_entities_t to_add_array = ecs_type_to_entities(to_add);
ecs_entities_t added = {
.array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_add_array.count),
.count = 0
};
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i);
if (table->flags & EcsTableHasBuiltins) {
continue;
}
if (!ecs_table_match_filter(world, table, filter)) {
continue;
}
ecs_table_t *dst_table = ecs_table_traverse_add(
world, table, &to_add_array, &added);
ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL);
if (!added.count) {
continue;
}
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL);
merge_table(world, dst_table, table, &added, NULL);
added.count = 0;
}
}
void ecs_bulk_add_entity(
ecs_world_t *world,
ecs_entity_t to_add,
const ecs_filter_t *filter)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(to_add != 0, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL);
(void)stage;
ecs_entities_t to_add_array = { .array = &to_add, .count = 1 };
ecs_entity_t added_entity;
ecs_entities_t added = {
.array = &added_entity,
.count = 0
};
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i);
if (table->flags & EcsTableHasBuiltins) {
continue;
}
if (!ecs_table_match_filter(world, table, filter)) {
continue;
}
ecs_table_t *dst_table = ecs_table_traverse_add(
world, table, &to_add_array, &added);
ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL);
if (!added.count) {
continue;
}
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL);
merge_table(world, dst_table, table, &added, NULL);
added.count = 0;
}
}
void ecs_bulk_remove_type(
ecs_world_t *world,
ecs_type_t to_remove,
const ecs_filter_t *filter)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(to_remove != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL);
(void)stage;
ecs_entities_t to_remove_array = ecs_type_to_entities(to_remove);
ecs_entities_t removed = {
.array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_remove_array.count),
.count = 0
};
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i);
if (table->flags & EcsTableHasBuiltins) {
continue;
}
if (!ecs_table_match_filter(world, table, filter)) {
continue;
}
ecs_table_t *dst_table = ecs_table_traverse_remove(
world, table, &to_remove_array, &removed);
ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL);
if (!removed.count) {
continue;
}
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL);
merge_table(world, dst_table, table, NULL, &removed);
removed.count = 0;
}
}
void ecs_bulk_remove_entity(
ecs_world_t *world,
ecs_entity_t to_remove,
const ecs_filter_t *filter)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(to_remove != 0, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL);
(void)stage;
ecs_entities_t to_remove_array = { .array = &to_remove, .count = 1 };
ecs_entity_t removed_entity;
ecs_entities_t removed = {
.array = &removed_entity,
.count = 0
};
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i);
if (table->flags & EcsTableHasBuiltins) {
continue;
}
if (!ecs_table_match_filter(world, table, filter)) {
continue;
}
ecs_table_t *dst_table = ecs_table_traverse_remove(
world, table, &to_remove_array, &removed);
ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL);
if (!removed.count) {
continue;
}
ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL);
merge_table(world, dst_table, table, NULL, &removed);
removed.count = 0;
}
}
#endif
#ifdef FLECS_DIRECT_ACCESS
/* Prefix with "da" so that they don't conflict with other get_column's */
static
ecs_column_t *da_get_column(
ecs_table_t *table,
int32_t column)
{
ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(column <= table->column_count, ECS_INVALID_PARAMETER, NULL);
ecs_data_t *data = table->data;
if (data && data->columns) {
return &table->data->columns[column];
} else {
return NULL;
}
}
static
ecs_column_t *da_get_or_create_column(
ecs_world_t *world,
ecs_table_t *table,
int32_t column)
{
ecs_column_t *c = da_get_column(table, column);
if (!c && (!table->data || !table->data->columns)) {
ecs_data_t *data = ecs_table_get_or_create_data(table);
ecs_init_data(world, table, data);
c = da_get_column(table, column);
}
ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL);
return c;
}
static
ecs_entity_t* get_entity_array(
ecs_table_t *table,
int32_t row)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(table->data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(table->data->entities != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_entity_t *array = ecs_vector_first(table->data->entities, ecs_entity_t);
return &array[row];
}
/* -- Public API -- */
ecs_type_t ecs_table_get_type(
ecs_table_t *table)
{
return table->type;
}
ecs_record_t* ecs_record_find(
ecs_world_t *world,
ecs_entity_t entity)
{
ecs_record_t *r = ecs_eis_get(world, entity);
if (r) {
return r;
} else {
return NULL;
}
}
ecs_record_t* ecs_record_ensure(
ecs_world_t *world,
ecs_entity_t entity)
{
ecs_record_t *r = ecs_eis_get_or_create(world, entity);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
return r;
}
ecs_record_t ecs_table_insert(
ecs_world_t *world,
ecs_table_t *table,
ecs_entity_t entity,
ecs_record_t *record)
{
ecs_data_t *data = ecs_table_get_or_create_data(table);
int32_t index = ecs_table_append(world, table, data, entity, record, true);
if (record) {
record->table = table;
record->row = index + 1;
}
return (ecs_record_t){table, index + 1};
}
int32_t ecs_table_find_column(
ecs_table_t *table,
ecs_entity_t component)
{
ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(component != 0, ECS_INVALID_PARAMETER, NULL);
return ecs_type_index_of(table->type, component);
}
ecs_vector_t* ecs_table_get_column(
ecs_table_t *table,
int32_t column)
{
ecs_column_t *c = da_get_column(table, column);
return c ? c->data : NULL;
}
ecs_vector_t* ecs_table_set_column(
ecs_world_t *world,
ecs_table_t *table,
int32_t column,
ecs_vector_t* vector)
{
ecs_column_t *c = da_get_or_create_column(world, table, column);
if (vector) {
ecs_vector_assert_size(vector, c->size);
} else {
ecs_vector_t *entities = ecs_table_get_entities(table);
if (entities) {
int32_t count = ecs_vector_count(entities);
vector = ecs_table_get_column(table, column);
if (!vector) {
vector = ecs_vector_new_t(c->size, c->alignment, count);
} else {
ecs_vector_set_count_t(&vector, c->size, c->alignment, count);
}
}
}
c->data = vector;
return vector;
}
ecs_vector_t* ecs_table_get_entities(
ecs_table_t *table)
{
ecs_data_t *data = table->data;
if (!data) {
return NULL;
}
return data->entities;
}
ecs_vector_t* ecs_table_get_records(
ecs_table_t *table)
{
ecs_data_t *data = table->data;
if (!data) {
return NULL;
}
return data->record_ptrs;
}
void ecs_table_set_entities(
ecs_table_t *table,
ecs_vector_t *entities,
ecs_vector_t *records)
{
ecs_vector_assert_size(entities, sizeof(ecs_entity_t));
ecs_vector_assert_size(records, sizeof(ecs_record_t*));
ecs_assert(ecs_vector_count(entities) == ecs_vector_count(records),
ECS_INVALID_PARAMETER, NULL);
ecs_data_t *data = table->data;
if (!data) {
data = ecs_table_get_or_create_data(table);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
}
data->entities = entities;
data->record_ptrs = records;
}
void ecs_records_clear(
ecs_vector_t *records)
{
int32_t i, count = ecs_vector_count(records);
ecs_record_t **r = ecs_vector_first(records, ecs_record_t*);
for (i = 0; i < count; i ++) {
r[i]->table = NULL;
if (r[i]->row < 0) {
r[i]->row = -1;
} else {
r[i]->row = 0;
}
}
}
void ecs_records_update(
ecs_world_t *world,
ecs_vector_t *entities,
ecs_vector_t *records,
ecs_table_t *table)
{
int32_t i, count = ecs_vector_count(records);
ecs_entity_t *e = ecs_vector_first(entities, ecs_entity_t);
ecs_record_t **r = ecs_vector_first(records, ecs_record_t*);
for (i = 0; i < count; i ++) {
r[i] = ecs_record_ensure(world, e[i]);
ecs_assert(r[i] != NULL, ECS_INTERNAL_ERROR, NULL);
r[i]->table = table;
r[i]->row = i + 1;
}
}
void ecs_table_delete_column(
ecs_world_t *world,
ecs_table_t *table,
int32_t column,
ecs_vector_t *vector)
{
if (!vector) {
vector = ecs_table_get_column(table, column);
if (!vector) {
return;
}
ecs_assert(table->data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(table->data->columns != NULL, ECS_INTERNAL_ERROR, NULL);
table->data->columns[column].data = NULL;
}
ecs_column_t *c = da_get_or_create_column(world, table, column);
ecs_vector_assert_size(vector, c->size);
ecs_c_info_t *c_info = table->c_info[column];
ecs_xtor_t dtor;
if (c_info && (dtor = c_info->lifecycle.dtor)) {
ecs_entity_t *entities = get_entity_array(table, 0);
int16_t alignment = c->alignment;
int32_t count = ecs_vector_count(vector);
void *ptr = ecs_vector_first_t(vector, c->size, alignment);
dtor(world, c_info->component, entities, ptr, ecs_to_size_t(c->size),
count, c_info->lifecycle.ctx);
}
if (c->data == vector) {
c->data = NULL;
}
ecs_vector_free(vector);
}
void* ecs_record_get_column(
ecs_record_t *r,
int32_t column,
size_t c_size)
{
(void)c_size;
ecs_table_t *table = r->table;
ecs_column_t *c = da_get_column(table, column);
if (!c) {
return NULL;
}
int16_t size = c->size;
ecs_assert(!ecs_from_size_t(c_size) || ecs_from_size_t(c_size) == c->size,
ECS_INVALID_PARAMETER, NULL);
void *array = ecs_vector_first_t(c->data, c->size, c->alignment);
bool is_watched;
int32_t row = ecs_record_to_row(r->row, &is_watched);
return ECS_OFFSET(array, size * row);
}
void ecs_record_copy_to(
ecs_world_t *world,
ecs_record_t *r,
int32_t column,
size_t c_size,
const void *value,
int32_t count)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL);
ecs_table_t *table = r->table;
ecs_column_t *c = da_get_or_create_column(world, table, column);
int16_t size = c->size;
ecs_assert(!ecs_from_size_t(c_size) || ecs_from_size_t(c_size) == c->size,
ECS_INVALID_PARAMETER, NULL);
int16_t alignment = c->alignment;
bool is_monitored;
int32_t row = ecs_record_to_row(r->row, &is_monitored);
void *ptr = ecs_vector_get_t(c->data, size, alignment, row);
ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_c_info_t *c_info = table->c_info[column];
ecs_copy_t copy;
if (c_info && (copy = c_info->lifecycle.copy)) {
ecs_entity_t *entities = get_entity_array(table, row);
copy(world, c_info->component, entities, entities, ptr, value, c_size,
count, c_info->lifecycle.ctx);
} else {
ecs_os_memcpy(ptr, value, size * count);
}
}
void ecs_record_copy_pod_to(
ecs_world_t *world,
ecs_record_t *r,
int32_t column,
size_t c_size,
const void *value,
int32_t count)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL);
(void)c_size;
ecs_table_t *table = r->table;
ecs_column_t *c = da_get_or_create_column(world, table, column);
int16_t size = c->size;
ecs_assert(!ecs_from_size_t(c_size) || ecs_from_size_t(c_size) == c->size,
ECS_INVALID_PARAMETER, NULL);
int16_t alignment = c->alignment;
bool is_monitored;
int32_t row = ecs_record_to_row(r->row, &is_monitored);
void *ptr = ecs_vector_get_t(c->data, size, alignment, row);
ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_os_memcpy(ptr, value, size * count);
}
void ecs_record_move_to(
ecs_world_t *world,
ecs_record_t *r,
int32_t column,
size_t c_size,
void *value,
int32_t count)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL);
ecs_table_t *table = r->table;
ecs_column_t *c = da_get_or_create_column(world, table, column);
int16_t size = c->size;
ecs_assert(!ecs_from_size_t(c_size) || ecs_from_size_t(c_size) == c->size,
ECS_INVALID_PARAMETER, NULL);
int16_t alignment = c->alignment;
bool is_monitored;
int32_t row = ecs_record_to_row(r->row, &is_monitored);
void *ptr = ecs_vector_get_t(c->data, size, alignment, row);
ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_c_info_t *c_info = table->c_info[column];
ecs_move_t move;
if (c_info && (move = c_info->lifecycle.move)) {
ecs_entity_t *entities = get_entity_array(table, row);
move(world, c_info->component, entities, entities, ptr, value, c_size,
count, c_info->lifecycle.ctx);
} else {
ecs_os_memcpy(ptr, value, size * count);
}
}
#endif
/* -- Private functions -- */
ecs_stage_t *ecs_get_stage(
ecs_world_t **world_ptr)
{
ecs_world_t *world = *world_ptr;
ecs_assert(world->magic == ECS_WORLD_MAGIC ||
world->magic == ECS_THREAD_MAGIC,
ECS_INTERNAL_ERROR,
NULL);
if (world->magic == ECS_WORLD_MAGIC) {
if (world->in_progress) {
return &world->temp_stage;
} else {
return &world->stage;
}
} else if (world->magic == ECS_THREAD_MAGIC) {
ecs_thread_t *thread = (ecs_thread_t*)world;
*world_ptr = thread->world;
return thread->stage;
}
return NULL;
}
/* 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 eval_component_monitor(
ecs_world_t *world,
ecs_component_monitor_t *mon)
{
if (!mon->rematch) {
return;
}
ecs_vector_t *eval[ECS_HI_COMPONENT_ID];
int32_t eval_count = 0;
int32_t i;
for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) {
if (mon->dirty_flags[i]) {
eval[eval_count ++] = mon->monitors[i];
mon->dirty_flags[i] = 0;
}
}
for (i = 0; i < eval_count; i ++) {
ecs_vector_each(eval[i], ecs_query_t*, q_ptr, {
ecs_query_notify(world, *q_ptr, &(ecs_query_event_t) {
.kind = EcsQueryTableRematch
});
});
}
mon->rematch = false;
}
void ecs_component_monitor_mark(
ecs_component_monitor_t *mon,
ecs_entity_t component)
{
/* Only flag if there are actually monitors registered, so that we
* don't waste cycles evaluating monitors if there's no interest */
if (mon->monitors[component]) {
mon->dirty_flags[component] = true;
mon->rematch = true;
}
}
void ecs_component_monitor_register(
ecs_component_monitor_t *mon,
ecs_entity_t component,
ecs_query_t *query)
{
ecs_assert(mon != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL);
/* Ignore component ids > ECS_HI_COMPONENT_ID */
if(component >= ECS_HI_COMPONENT_ID) {
return;
}
ecs_query_t **q = ecs_vector_add(&mon->monitors[component], ecs_query_t*);
*q = query;
}
static
void ecs_component_monitor_free(
ecs_component_monitor_t *mon)
{
int i;
for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) {
ecs_vector_free(mon->monitors[i]);
}
}
static
void init_store(ecs_world_t *world) {
ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t));
/* Initialize entity index */
world->store.entity_index = ecs_sparse_new(ecs_record_t);
ecs_sparse_set_id_source(world->store.entity_index, &world->stats.last_id);
/* Initialize root table */
world->store.tables = ecs_sparse_new(ecs_table_t);
/* Initialize table map */
world->store.table_map = ecs_map_new(ecs_vector_t*, 8);
/* Initialize one root table per stage */
ecs_init_root_table(world);
}
static
void clean_tables(
ecs_world_t *world)
{
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *t = ecs_sparse_get(world->store.tables, ecs_table_t, i);
ecs_table_free(world, t);
}
/* Clear the root table */
if (count) {
ecs_table_reset(world, &world->store.root);
}
}
static
void fini_store(ecs_world_t *world) {
clean_tables(world);
ecs_sparse_free(world->store.tables);
ecs_table_free(world, &world->store.root);
ecs_sparse_free(world->store.entity_index);
ecs_map_iter_t it = ecs_map_iter(world->store.table_map);
ecs_vector_t *tables;
while ((tables = ecs_map_next_ptr(&it, ecs_vector_t*, NULL))) {
ecs_vector_free(tables);
}
ecs_map_free(world->store.table_map);
}
/* -- Public functions -- */
ecs_world_t *ecs_mini(void) {
ecs_os_init();
ecs_trace_1("bootstrap");
ecs_log_push();
if (!ecs_os_has_heap()) {
ecs_abort(ECS_MISSING_OS_API, NULL);
}
if (!ecs_os_has_threading()) {
ecs_trace_1("threading not available");
}
if (!ecs_os_has_time()) {
ecs_trace_1("time management not available");
}
ecs_world_t *world = ecs_os_calloc(sizeof(ecs_world_t));
ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL);
world->magic = ECS_WORLD_MAGIC;
memset(&world->c_info, 0, sizeof(ecs_c_info_t) * ECS_HI_COMPONENT_ID);
world->t_info = ecs_map_new(ecs_c_info_t, 0);
world->fini_actions = NULL;
world->aliases = NULL;
world->queries = ecs_vector_new(ecs_query_t*, 0);
world->fini_tasks = ecs_vector_new(ecs_entity_t, 0);
world->child_tables = NULL;
world->name_prefix = NULL;
memset(&world->component_monitors, 0, sizeof(world->component_monitors));
memset(&world->parent_monitors, 0, sizeof(world->parent_monitors));
world->type_handles = ecs_map_new(ecs_entity_t, 0);
world->on_activate_components = ecs_map_new(ecs_on_demand_in_t, 0);
world->on_enable_components = ecs_map_new(ecs_on_demand_in_t, 0);
world->stage_count = 2;
world->worker_stages = NULL;
world->workers = NULL;
world->workers_waiting = 0;
world->workers_running = 0;
world->quit_workers = false;
world->in_progress = false;
world->is_merging = false;
world->is_fini = false;
world->auto_merge = true;
world->measure_frame_time = false;
world->measure_system_time = false;
world->should_quit = false;
world->locking_enabled = false;
world->pipeline = 0;
world->frame_start_time = (ecs_time_t){0, 0};
if (ecs_os_has_time()) {
ecs_os_get_time(&world->world_start_time);
}
world->stats.target_fps = 0;
world->stats.last_id = 0;
world->stats.delta_time_raw = 0;
world->stats.delta_time = 0;
world->stats.time_scale = 1.0;
world->stats.frame_time_total = 0;
world->stats.sleep_err = 0;
world->stats.system_time_total = 0;
world->stats.merge_time_total = 0;
world->stats.world_time_total = 0;
world->stats.frame_count_total = 0;
world->stats.merge_count_total = 0;
world->stats.systems_ran_frame = 0;
world->stats.pipeline_build_count_total = 0;
world->range_check_enabled = true;
world->fps_sleep = 0;
world->context = NULL;
world->arg_fps = 0;
world->arg_threads = 0;
ecs_stage_init(world, &world->stage);
ecs_stage_init(world, &world->temp_stage);
init_store(world);
world->stage.world = world;
world->temp_stage.world = world;
ecs_bootstrap(world);
ecs_log_pop();
return world;
}
ecs_world_t *ecs_init(void) {
ecs_world_t *world = ecs_mini();
#ifdef FLECS_MODULE_H
ecs_trace_1("import builtin modules");
ecs_log_push();
#ifdef FLECS_SYSTEM_H
ECS_IMPORT(world, FlecsSystem);
#endif
#ifdef FLECS_PIPELINE_H
ECS_IMPORT(world, FlecsPipeline);
#endif
#ifdef FLECS_TIMER_H
ECS_IMPORT(world, FlecsTimer);
#endif
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[])
{
(void)argc;
(void)argv;
return ecs_init();
}
static
void on_demand_in_map_deinit(
ecs_map_t *map)
{
ecs_map_iter_t it = ecs_map_iter(map);
ecs_on_demand_in_t *elem;
while ((elem = ecs_map_next(&it, ecs_on_demand_in_t, NULL))) {
ecs_vector_free(elem->systems);
}
ecs_map_free(map);
}
static
void ctor_init_zero(
ecs_world_t *world,
ecs_entity_t component,
const ecs_entity_t *entity_ptr,
void *ptr,
size_t size,
int32_t count,
void *ctx)
{
(void)world;
(void)component;
(void)entity_ptr;
(void)ctx;
ecs_os_memset(ptr, 0, ecs_from_size_t(size) * count);
}
void ecs_notify_tables(
ecs_world_t *world,
ecs_table_event_t *event)
{
ecs_sparse_t *tables = world->store.tables;
int32_t i, count = ecs_sparse_count(tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(tables, ecs_table_t, i);
ecs_table_notify(world, table, event);
}
}
void ecs_set_component_actions_w_entity(
ecs_world_t *world,
ecs_entity_t component,
EcsComponentLifecycle *lifecycle)
{
#ifndef NDEBUG
const EcsComponent *component_ptr = ecs_get(world, component, EcsComponent);
/* Cannot register lifecycle actions for things that aren't a component */
ecs_assert(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL);
/* Cannot register lifecycle actions for components with size 0 */
ecs_assert(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL);
#endif
ecs_c_info_t *c_info = ecs_get_or_create_c_info(world, component);
ecs_assert(c_info != NULL, ECS_INTERNAL_ERROR, NULL);
if (c_info->lifecycle_set) {
ecs_assert(c_info->component == component, ECS_INTERNAL_ERROR, NULL);
ecs_assert(c_info->lifecycle.ctor == lifecycle->ctor,
ECS_INCONSISTENT_COMPONENT_ACTION, NULL);
ecs_assert(c_info->lifecycle.dtor == lifecycle->dtor,
ECS_INCONSISTENT_COMPONENT_ACTION, NULL);
ecs_assert(c_info->lifecycle.copy == lifecycle->copy,
ECS_INCONSISTENT_COMPONENT_ACTION, NULL);
ecs_assert(c_info->lifecycle.move == lifecycle->move,
ECS_INCONSISTENT_COMPONENT_ACTION, NULL);
} else {
c_info->component = component;
c_info->lifecycle = *lifecycle;
c_info->lifecycle_set = true;
/* 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 (!lifecycle->ctor && (lifecycle->dtor || lifecycle->copy || lifecycle->move)) {
c_info->lifecycle.ctor = ctor_init_zero;
}
ecs_notify_tables(world, &(ecs_table_event_t) {
.kind = EcsTableComponentInfo,
.component = component
});
}
}
bool ecs_component_has_actions(
ecs_world_t *world,
ecs_entity_t component)
{
ecs_c_info_t *c_info = ecs_get_c_info(world, component);
return (c_info != NULL) && c_info->lifecycle_set;
}
void ecs_atfini(
ecs_world_t *world,
ecs_fini_action_t action,
void *ctx)
{
ecs_assert(action != NULL, ECS_INTERNAL_ERROR, 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;
}
void ecs_run_post_frame(
ecs_world_t *world,
ecs_fini_action_t action,
void *ctx)
{
ecs_assert(action != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_stage_t *stage = ecs_get_stage(&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;
}
/* Unset data in tables */
static
void fini_unset_tables(
ecs_world_t *world)
{
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i);
ecs_table_unset(world, table);
}
}
/* Invoke fini actions */
static
void 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 component lifecycle callbacks & systems */
static
void fini_component_lifecycle(
ecs_world_t *world)
{
int32_t i;
for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) {
ecs_vector_free(world->c_info[i].on_add);
ecs_vector_free(world->c_info[i].on_remove);
}
ecs_map_iter_t it = ecs_map_iter(world->t_info);
ecs_c_info_t *c_info;
while ((c_info = ecs_map_next(&it, ecs_c_info_t, NULL))) {
ecs_vector_free(c_info->on_add);
ecs_vector_free(c_info->on_remove);
}
ecs_map_free(world->t_info);
}
/* Cleanup queries */
static
void fini_queries(
ecs_world_t *world)
{
/* Set world->queries to NULL, so ecs_query_free won't attempt to remove
* itself from the vector */
ecs_vector_t *query_vec = world->queries;
world->queries = NULL;
int32_t i, count = ecs_vector_count(query_vec);
ecs_query_t **queries = ecs_vector_first(query_vec, ecs_query_t*);
for (i = 0; i < count; i ++) {
ecs_query_free(queries[i]);
}
ecs_vector_free(query_vec);
}
/* Cleanup stages */
static
void fini_stages(
ecs_world_t *world)
{
ecs_stage_deinit(world, &world->stage);
ecs_stage_deinit(world, &world->temp_stage);
}
/* Cleanup child table admin */
static
void fini_child_tables(
ecs_world_t *world)
{
ecs_map_iter_t it = ecs_map_iter(world->child_tables);
ecs_vector_t *tables;
while ((tables = ecs_map_next_ptr(&it, ecs_vector_t*, NULL))) {
ecs_vector_free(tables);
}
ecs_map_free(world->child_tables);
}
/* Cleanup aliases */
static
void fini_aliases(
ecs_world_t *world)
{
int32_t i, count = ecs_vector_count(world->aliases);
ecs_alias_t *aliases = ecs_vector_first(world->aliases, ecs_alias_t);
for (i = 0; i < count; i ++) {
ecs_os_free(aliases[i].name);
}
ecs_vector_free(world->aliases);
}
/* Cleanup misc structures */
static
void fini_misc(
ecs_world_t *world)
{
on_demand_in_map_deinit(world->on_activate_components);
on_demand_in_map_deinit(world->on_enable_components);
ecs_map_free(world->type_handles);
ecs_vector_free(world->fini_tasks);
ecs_component_monitor_free(&world->component_monitors);
ecs_component_monitor_free(&world->parent_monitors);
}
/* The destroyer of worlds */
int ecs_fini(
ecs_world_t *world)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!world->in_progress, ECS_INVALID_OPERATION, NULL);
ecs_assert(!world->is_merging, ECS_INVALID_OPERATION, NULL);
ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL);
world->is_fini = true;
fini_unset_tables(world);
fini_actions(world);
if (world->locking_enabled) {
ecs_os_mutex_free(world->mutex);
}
fini_stages(world);
fini_store(world);
fini_component_lifecycle(world);
fini_queries(world);
fini_child_tables(world);
fini_aliases(world);
fini_misc(world);
/* In case the application tries to use the memory of the freed world, this
* will trigger an assert */
world->magic = 0;
ecs_increase_timer_resolution(0);
/* End of the world */
ecs_os_free(world);
ecs_os_fini();
return 0;
}
void ecs_dim(
ecs_world_t *world,
int32_t entity_count)
{
assert(world->magic == ECS_WORLD_MAGIC);
ecs_eis_set_size(world, entity_count + ECS_HI_COMPONENT_ID);
}
void ecs_dim_type(
ecs_world_t *world,
ecs_type_t type,
int32_t entity_count)
{
assert(world->magic == ECS_WORLD_MAGIC);
if (type) {
ecs_table_t *table = ecs_table_from_type(world, type);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = ecs_table_get_or_create_data(table);
ecs_table_set_size(world, table, data, entity_count);
}
}
void ecs_eval_component_monitors(
ecs_world_t *world)
{
eval_component_monitor(world, &world->component_monitors);
eval_component_monitor(world, &world->parent_monitors);
}
void ecs_merge(
ecs_world_t *world)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
assert(world->is_merging == false);
bool measure_frame_time = world->measure_frame_time;
world->is_merging = true;
ecs_time_t t_start;
if (measure_frame_time) {
ecs_os_get_time(&t_start);
}
ecs_stage_merge(world, &world->temp_stage);
ecs_vector_each(world->worker_stages, ecs_stage_t, stage, {
ecs_stage_merge(world, stage);
});
world->is_merging = false;
ecs_eval_component_monitors(world);
if (measure_frame_time) {
world->stats.merge_time_total += (FLECS_FLOAT)ecs_time_measure(&t_start);
}
world->stats.merge_count_total ++;
}
void ecs_set_automerge(
ecs_world_t *world,
bool auto_merge)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
world->auto_merge = auto_merge;
}
void ecs_measure_frame_time(
ecs_world_t *world,
bool enable)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL);
if (world->stats.target_fps == 0.0 || enable) {
world->measure_frame_time = enable;
}
}
void ecs_measure_system_time(
ecs_world_t *world,
bool enable)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL);
world->measure_system_time = enable;
}
/* Increase timer resolution based on target fps */
static void set_timer_resolution(FLECS_FLOAT fps)
{
if(fps >= 60.0f) ecs_increase_timer_resolution(1);
else ecs_increase_timer_resolution(0);
}
void ecs_set_target_fps(
ecs_world_t *world,
FLECS_FLOAT fps)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL);
if (!world->arg_fps) {
ecs_measure_frame_time(world, true);
world->stats.target_fps = fps;
set_timer_resolution(fps);
}
}
void* ecs_get_context(
ecs_world_t *world)
{
ecs_get_stage(&world);
return world->context;
}
void ecs_set_context(
ecs_world_t *world,
void *context)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
world->context = context;
}
void ecs_set_entity_range(
ecs_world_t *world,
ecs_entity_t id_start,
ecs_entity_t id_end)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
ecs_assert(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!id_end || id_end > world->stats.last_id, ECS_INVALID_PARAMETER, NULL);
if (world->stats.last_id < id_start) {
world->stats.last_id = id_start - 1;
}
world->stats.min_id = id_start;
world->stats.max_id = id_end;
}
bool ecs_enable_range_check(
ecs_world_t *world,
bool enable)
{
bool old_value = world->range_check_enabled;
world->range_check_enabled = enable;
return old_value;
}
int32_t ecs_get_thread_index(
ecs_world_t *world)
{
if (world->magic == ECS_THREAD_MAGIC) {
ecs_thread_t *thr = (ecs_thread_t*)world;
return thr->index;
} else if (world->magic == ECS_WORLD_MAGIC) {
return 0;
} else {
ecs_abort(ECS_INTERNAL_ERROR, NULL);
}
}
int32_t ecs_get_threads(
ecs_world_t *world)
{
return ecs_vector_count(world->workers);
}
bool ecs_enable_locking(
ecs_world_t *world,
bool enable)
{
if (enable) {
if (!world->locking_enabled) {
world->mutex = ecs_os_mutex_new();
world->thr_sync = ecs_os_mutex_new();
world->thr_cond = ecs_os_cond_new();
}
} else {
if (world->locking_enabled) {
ecs_os_mutex_free(world->mutex);
ecs_os_mutex_free(world->thr_sync);
ecs_os_cond_free(world->thr_cond);
}
}
bool old = world->locking_enabled;
world->locking_enabled = enable;
return old;
}
void ecs_lock(
ecs_world_t *world)
{
ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL);
ecs_os_mutex_lock(world->mutex);
}
void ecs_unlock(
ecs_world_t *world)
{
ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL);
ecs_os_mutex_unlock(world->mutex);
}
void ecs_begin_wait(
ecs_world_t *world)
{
ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL);
ecs_os_mutex_lock(world->thr_sync);
ecs_os_cond_wait(world->thr_cond, world->thr_sync);
}
void ecs_end_wait(
ecs_world_t *world)
{
ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL);
ecs_os_mutex_unlock(world->thr_sync);
}
ecs_c_info_t * ecs_get_c_info(
ecs_world_t *world,
ecs_entity_t component)
{
ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(!(component & ECS_ROLE_MASK), ECS_INTERNAL_ERROR, NULL);
if (component < ECS_HI_COMPONENT_ID) {
ecs_c_info_t *c_info = &world->c_info[component];
if (c_info->component) {
ecs_assert(c_info->component == component, ECS_INTERNAL_ERROR, NULL);
return c_info;
} else {
return NULL;
}
} else {
return ecs_map_get(world->t_info, ecs_c_info_t, component);
}
}
ecs_c_info_t * ecs_get_or_create_c_info(
ecs_world_t *world,
ecs_entity_t component)
{
ecs_c_info_t *c_info = ecs_get_c_info(world, component);
if (!c_info) {
if (component < ECS_HI_COMPONENT_ID) {
c_info = &world->c_info[component];
ecs_assert(c_info->component == 0, ECS_INTERNAL_ERROR, NULL);
c_info->component = component;
} else {
ecs_c_info_t t_info = { 0 };
ecs_map_set(world->t_info, component, &t_info);
c_info = ecs_map_get(world->t_info, ecs_c_info_t, component);
ecs_assert(c_info != NULL, ECS_INTERNAL_ERROR, NULL);
}
}
return c_info;
}
bool ecs_staging_begin(
ecs_world_t *world)
{
bool in_progress = world->in_progress;
world->in_progress = true;
return in_progress;
}
void ecs_staging_end(
ecs_world_t *world)
{
ecs_assert(world->in_progress == true, ECS_INVALID_OPERATION, NULL);
world->in_progress = false;
if (world->auto_merge) {
ecs_merge(world);
}
}
static
double insert_sleep(
ecs_world_t *world,
ecs_time_t *stop)
{
ecs_time_t start = *stop;
double delta_time = ecs_time_measure(stop);
if (world->stats.target_fps == 0) {
return delta_time;
}
double target_delta_time = (1.0 / world->stats.target_fps);
double world_sleep_err =
world->stats.sleep_err / (double)world->stats.frame_count_total;
/* Calculate the time we need to sleep by taking the measured delta from the
* previous frame, and subtracting it from target_delta_time. */
double sleep = target_delta_time - delta_time;
/* Pick a sleep interval that is 20 times lower than the time one frame
* should take. This means that this function at most iterates 20 times in
* a busy loop */
double sleep_time = target_delta_time / 20;
/* Measure at least two frames before interpreting sleep error */
if (world->stats.frame_count_total > 1) {
/* If the ratio between the sleep error and the sleep time is too high,
* just do a busy loop */
if (world_sleep_err / sleep_time > 0.1) {
sleep_time = 0;
}
}
/* If the time we need to sleep is large enough to warrant a sleep, sleep */
if (sleep > (sleep_time - world_sleep_err)) {
if (sleep_time > sleep) {
/* Make sure we don't sleep longer than we should */
sleep_time = sleep;
}
double sleep_err = 0;
int32_t iterations = 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(sleep_time);
}
ecs_time_t now = start;
double prev_delta_time = delta_time;
delta_time = ecs_time_measure(&now);
/* Measure the error of the sleep by taking the difference between
* the time we expected to sleep, and the measured time. This
* assumes that a sleep is less accurate than a high resolution
* timer which should be true in most cases. */
sleep_err = delta_time - prev_delta_time - sleep_time;
iterations ++;
} while ((target_delta_time - delta_time) > (sleep_time - world_sleep_err));
/* Add sleep error measurement to sleep error, with a bias towards the
* latest measured values. */
world->stats.sleep_err = (FLECS_FLOAT)
(world_sleep_err * 0.9 + sleep_err * 0.1) *
(FLECS_FLOAT)world->stats.frame_count_total;
}
/* Make last minute corrections if due to a larger clock error delta_time
* is still more than 5% away from the target. The 5% buffer is to account
* for the fact that measuring the time also takes time. */
while (delta_time < target_delta_time * 0.95) {
ecs_time_t now = start;
delta_time = ecs_time_measure(&now);
}
return delta_time;
}
static
FLECS_FLOAT start_measure_frame(
ecs_world_t *world,
FLECS_FLOAT user_delta_time)
{
double delta_time = 0;
if (world->measure_frame_time || (user_delta_time == 0)) {
ecs_time_t t = world->frame_start_time;
do {
if (world->frame_start_time.sec) {
delta_time = insert_sleep(world, &t);
ecs_time_measure(&t);
} else {
ecs_time_measure(&t);
if (world->stats.target_fps != 0) {
delta_time = 1.0 / world->stats.target_fps;
} else {
delta_time = 1.0 / 60.0; /* Best guess */
}
}
/* 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->stats.world_time_total_raw += (FLECS_FLOAT)delta_time;
}
return (FLECS_FLOAT)delta_time;
}
static
void stop_measure_frame(
ecs_world_t* world)
{
if (world->measure_frame_time) {
ecs_time_t t = world->frame_start_time;
world->stats.frame_time_total += (FLECS_FLOAT)ecs_time_measure(&t);
}
}
FLECS_FLOAT ecs_frame_begin(
ecs_world_t *world,
FLECS_FLOAT user_delta_time)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
ecs_assert(world->in_progress == false, ECS_INVALID_OPERATION, NULL);
ecs_assert(user_delta_time != 0 || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time");
if (world->locking_enabled) {
ecs_lock(world);
}
/* Start measuring total frame time */
FLECS_FLOAT delta_time = start_measure_frame(world, user_delta_time);
if (user_delta_time == 0) {
user_delta_time = delta_time;
}
world->stats.delta_time_raw = user_delta_time;
world->stats.delta_time = user_delta_time * world->stats.time_scale;
/* Keep track of total scaled time passed in world */
world->stats.world_time_total += world->stats.delta_time;
ecs_eval_component_monitors(world);
return user_delta_time;
}
void ecs_frame_end(
ecs_world_t *world)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
ecs_assert(world->in_progress == false, ECS_INVALID_OPERATION, NULL);
world->stats.frame_count_total ++;
ecs_stage_merge_post_frame(world, &world->temp_stage);
ecs_vector_each(world->worker_stages, ecs_stage_t, stage, {
ecs_stage_merge_post_frame(world, stage);
});
if (world->locking_enabled) {
ecs_unlock(world);
ecs_os_mutex_lock(world->thr_sync);
ecs_os_cond_broadcast(world->thr_cond);
ecs_os_mutex_unlock(world->thr_sync);
}
stop_measure_frame(world);
}
const ecs_world_info_t* ecs_get_world_info(
ecs_world_t *world)
{
return &world->stats;
}
void ecs_notify_queries(
ecs_world_t *world,
ecs_query_event_t *event)
{
int32_t i, count = ecs_vector_count(world->queries);
ecs_query_t **queries = ecs_vector_first(world->queries, ecs_query_t*);
for (i = 0; i < count; i ++) {
ecs_query_notify(world, queries[i], event);
}
}
void ecs_delete_table(
ecs_world_t *world,
ecs_table_t *table)
{
/* Notify queries that table is to be removed */
ecs_notify_queries(
world, &(ecs_query_event_t){
.kind = EcsQueryTableUnmatch,
.table = table
});
uint32_t id = table->id;
/* Free resources associated with table */
ecs_table_free(world, table);
/* Remove table from sparse set */
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL);
ecs_sparse_remove(world->store.tables, id);
/* Don't do generations as we want table ids to remain 32 bit */
ecs_sparse_set_generation(world->store.tables, id);
}
static
ecs_switch_header_t *get_header(
const ecs_switch_t *sw,
uint64_t value)
{
if (value == 0) {
return NULL;
}
value = (uint32_t)value;
ecs_assert(value >= sw->min, ECS_INTERNAL_ERROR, NULL);
ecs_assert(value <= sw->max, ECS_INTERNAL_ERROR, NULL);
uint64_t index = value - sw->min;
return &sw->headers[index];
}
static
void remove_node(
ecs_switch_header_t *hdr,
ecs_switch_node_t *nodes,
ecs_switch_node_t *node,
int32_t element)
{
/* The node is currently assigned to a value */
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;
}
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);
}
ecs_switch_t* ecs_switch_new(
uint64_t min,
uint64_t max,
int32_t elements)
{
ecs_assert(min != max, ECS_INVALID_PARAMETER, NULL);
/* Min must be larger than 0, as 0 is an invalid entity id, and should
* therefore never occur as case id */
ecs_assert(min > 0, ECS_INVALID_PARAMETER, NULL);
ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t));
result->min = (uint32_t)min;
result->max = (uint32_t)max;
int32_t count = (int32_t)(max - min) + 1;
result->headers = ecs_os_calloc(ECS_SIZEOF(ecs_switch_header_t) * count);
result->nodes = ecs_vector_new(ecs_switch_node_t, elements);
result->values = ecs_vector_new(uint64_t, elements);
int64_t i;
for (i = 0; i < count; i ++) {
result->headers[i].element = -1;
result->headers[i].count = 0;
}
ecs_switch_node_t *nodes = ecs_vector_first(
result->nodes, ecs_switch_node_t);
uint64_t *values = ecs_vector_first(
result->values, uint64_t);
for (i = 0; i < elements; i ++) {
nodes[i].prev = -1;
nodes[i].next = -1;
values[i] = 0;
}
return result;
}
void ecs_switch_free(
ecs_switch_t *sw)
{
ecs_os_free(sw->headers);
ecs_vector_free(sw->nodes);
ecs_vector_free(sw->values);
ecs_os_free(sw);
}
void ecs_switch_add(
ecs_switch_t *sw)
{
ecs_switch_node_t *node = ecs_vector_add(&sw->nodes, ecs_switch_node_t);
uint64_t *value = ecs_vector_add(&sw->values, uint64_t);
node->prev = -1;
node->next = -1;
*value = 0;
}
void ecs_switch_set_count(
ecs_switch_t *sw,
int32_t count)
{
int32_t old_count = ecs_vector_count(sw->nodes);
if (old_count == count) {
return;
}
ecs_vector_set_count(&sw->nodes, ecs_switch_node_t, count);
ecs_vector_set_count(&sw->values, uint64_t, count);
ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t);
uint64_t *values = ecs_vector_first(sw->values, uint64_t);
int32_t i;
for (i = old_count; i < count; i ++) {
ecs_switch_node_t *node = &nodes[i];
node->prev = -1;
node->next = -1;
values[i] = 0;
}
}
void ecs_switch_ensure(
ecs_switch_t *sw,
int32_t count)
{
int32_t old_count = ecs_vector_count(sw->nodes);
if (old_count >= count) {
return;
}
ecs_switch_set_count(sw, count);
}
void ecs_switch_addn(
ecs_switch_t *sw,
int32_t count)
{
int32_t old_count = ecs_vector_count(sw->nodes);
ecs_switch_set_count(sw, old_count + count);
}
void ecs_switch_set(
ecs_switch_t *sw,
int32_t element,
uint64_t value)
{
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL);
ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL);
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL);
uint64_t *values = ecs_vector_first(sw->values, uint64_t);
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_vector_first(sw->nodes, ecs_switch_node_t);
ecs_switch_node_t *node = &nodes[element];
ecs_switch_header_t *cur_hdr = get_header(sw, cur_value);
ecs_switch_header_t *dst_hdr = get_header(sw, value);
/* 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) {
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 ecs_switch_remove(
ecs_switch_t *sw,
int32_t element)
{
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL);
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL);
uint64_t *values = ecs_vector_first(sw->values, uint64_t);
uint64_t value = values[element];
ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t);
ecs_switch_node_t *node = &nodes[element];
/* If node is currently assigned to a case, remove it from the list */
if (value != 0) {
ecs_switch_header_t *hdr = get_header(sw, value);
ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL);
remove_node(hdr, nodes, node, element);
}
/* Remove element from arrays */
ecs_vector_remove_index(sw->nodes, ecs_switch_node_t, element);
ecs_vector_remove_index(sw->values, uint64_t, element);
/* When the element was removed and the list was not empty, the last element
* of the list got moved to the location of the removed node. Update the
* linked list so that nodes that previously pointed to the last element
* point to the moved node.
*
* The 'node' variable is guaranteed to point to the moved element, if the
* nodes list is not empty.
*
* If count is equal to the removed index, nothing needs to be done.
*/
int32_t count = ecs_vector_count(sw->nodes);
if (count != 0 && count != element) {
int32_t prev = node->prev;
if (prev != -1) {
/* If the former last node was not the first node, update its
* prev to point to its new index, which is the index of the removed
* element. */
ecs_assert(prev >= 0, ECS_INVALID_PARAMETER, NULL);
nodes[prev].next = element;
} else {
/* If the former last node was the first node of its kind, find the
* header for the value of the node. The header should have at
* least one element. */
ecs_switch_header_t *hdr = get_header(sw, values[element]);
if (hdr && hdr->element != -1) {
ecs_assert(hdr->element == ecs_vector_count(sw->nodes),
ECS_INTERNAL_ERROR, NULL);
hdr->element = element;
}
}
}
}
uint64_t ecs_switch_get(
const ecs_switch_t *sw,
int32_t element)
{
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL);
ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL);
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL);
uint64_t *values = ecs_vector_first(sw->values, uint64_t);
return values[element];
}
ecs_vector_t* ecs_switch_values(
const ecs_switch_t *sw)
{
return sw->values;
}
int32_t ecs_switch_case_count(
const ecs_switch_t *sw,
uint64_t value)
{
ecs_switch_header_t *hdr = get_header(sw, value);
if (!hdr) {
return 0;
}
return hdr->count;
}
void ecs_switch_swap(
ecs_switch_t *sw,
int32_t elem_1,
int32_t elem_2)
{
uint64_t v1 = ecs_switch_get(sw, elem_1);
uint64_t v2 = ecs_switch_get(sw, elem_2);
ecs_switch_set(sw, elem_2, v1);
ecs_switch_set(sw, elem_1, v2);
}
int32_t ecs_switch_first(
const ecs_switch_t *sw,
uint64_t value)
{
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert((uint32_t)value <= sw->max, ECS_INVALID_PARAMETER, NULL);
ecs_assert((uint32_t)value >= sw->min, ECS_INVALID_PARAMETER, NULL);
ecs_switch_header_t *hdr = get_header(sw, value);
ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL);
return hdr->element;
}
int32_t ecs_switch_next(
const ecs_switch_t *sw,
int32_t element)
{
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL);
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL);
ecs_switch_node_t *nodes = ecs_vector_first(
sw->nodes, ecs_switch_node_t);
return nodes[element].next;
}
#ifndef _MSC_VER
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#endif
/*
-------------------------------------------------------------------------------
lookup3.c, by Bob Jenkins, May 2006, Public Domain.
http://burtleburtle.net/bob/c/lookup3.c
-------------------------------------------------------------------------------
*/
#ifdef _MSC_VER
//FIXME
#else
#include <sys/param.h> /* attempt to define endianness */
#endif
#ifdef 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;
}
void ecs_hash(
const void *data,
ecs_size_t length,
uint64_t *result)
{
uint32_t h_1 = 0;
uint32_t h_2 = 0;
hashlittle2(
data,
ecs_to_size_t(length),
&h_1,
&h_2);
*result = h_1 | ((uint64_t)h_2 << 32);
}
ecs_iter_t ecs_filter_iter(
ecs_world_t *world,
const ecs_filter_t *filter)
{
ecs_filter_iter_t iter = {
.filter = filter ? *filter : (ecs_filter_t){0},
.tables = world->store.tables,
.index = 0
};
return (ecs_iter_t){
.world = world,
.iter.filter = iter
};
}
bool ecs_filter_next(
ecs_iter_t *it)
{
ecs_filter_iter_t *iter = &it->iter.filter;
ecs_sparse_t *tables = iter->tables;
int32_t count = ecs_sparse_count(tables);
int32_t i;
for (i = iter->index; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(tables, ecs_table_t, i);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = ecs_table_get_data(table);
if (!data) {
continue;
}
if (!ecs_table_match_filter(it->world, table, &iter->filter)) {
continue;
}
iter->table.table = table;
it->table = &iter->table;
it->table_columns = data->columns;
it->count = ecs_table_count(table);
it->entities = ecs_vector_first(data->entities, ecs_entity_t);
iter->index = i + 1;
return true;
}
return false;
}
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 ecs_bitset_init(
ecs_bitset_t* bs)
{
bs->size = 0;
bs->count = 0;
bs->data = NULL;
}
void ecs_bitset_ensure(
ecs_bitset_t *bs,
int32_t count)
{
if (count > bs->count) {
bs->count = count;
ensure(bs, count);
}
}
void ecs_bitset_deinit(
ecs_bitset_t *bs)
{
ecs_os_free(bs->data);
}
void ecs_bitset_addn(
ecs_bitset_t *bs,
int32_t count)
{
int32_t elem = bs->count += count;
ensure(bs, elem);
}
void ecs_bitset_set(
ecs_bitset_t *bs,
int32_t elem,
bool value)
{
ecs_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL);
int32_t hi = elem >> 6;
int32_t lo = elem & 0x3F;
uint64_t v = bs->data[hi];
bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo);
}
bool ecs_bitset_get(
const ecs_bitset_t *bs,
int32_t elem)
{
ecs_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL);
return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F)));
}
int32_t ecs_bitset_count(
const ecs_bitset_t *bs)
{
return bs->count;
}
void ecs_bitset_remove(
ecs_bitset_t *bs,
int32_t elem)
{
ecs_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL);
int32_t last = bs->count - 1;
bool last_value = ecs_bitset_get(bs, last);
ecs_bitset_set(bs, elem, last_value);
bs->count --;
}
void ecs_bitset_swap(
ecs_bitset_t *bs,
int32_t elem_a,
int32_t elem_b)
{
ecs_assert(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL);
ecs_assert(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL);
bool a = ecs_bitset_get(bs, elem_a);
bool b = ecs_bitset_get(bs, elem_b);
ecs_bitset_set(bs, elem_a, b);
ecs_bitset_set(bs, elem_b, a);
}
/* Add an extra element to the buffer */
static
void ecs_strbuf_grow(
ecs_strbuf_t *b)
{
/* Allocate new element */
ecs_strbuf_element_embedded *e = ecs_os_malloc(sizeof(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 ecs_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(sizeof(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* ecs_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 ecs_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 ecs_strbuf_memLeft(
ecs_strbuf_t *b)
{
if (b->max) {
return b->max - b->size - b->current->pos;
} else {
return INT_MAX;
}
}
static
void ecs_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;
}
}
/* Quick custom function to copy a maxium number of characters and
* simultaneously determine length of source string. */
static
int32_t fast_strncpy(
char * dst,
const char * src,
int n_cpy,
int n)
{
ecs_assert(n_cpy >= 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(n >= 0, ECS_INTERNAL_ERROR, NULL);
const char *ptr, *orig = src;
char ch;
for (ptr = src; (ptr - orig < n) && (ch = *ptr); ptr ++) {
if (ptr - orig < n_cpy) {
*dst = ch;
dst ++;
}
}
ecs_assert(ptr - orig < INT32_MAX, ECS_INTERNAL_ERROR, NULL);
return (int32_t)(ptr - orig);
}
/* Append a format string to a buffer */
static
bool ecs_strbuf_vappend_intern(
ecs_strbuf_t *b,
const char* str,
va_list args)
{
bool result = true;
va_list arg_cpy;
if (!str) {
return result;
}
ecs_strbuf_init(b);
int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b);
int32_t memLeft = ecs_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(
ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args);
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 */
ecs_strbuf_grow(b);
/* Copy entire string to new buffer */
ecs_os_vsprintf(ecs_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);
ecs_strbuf_grow_str(b, dst, dst, memRequired);
}
} else {
/* Buffer max has been reached */
result = false;
}
va_end(arg_cpy);
return result;
}
static
bool ecs_strbuf_append_intern(
ecs_strbuf_t *b,
const char* str,
int n)
{
bool result = true;
if (!str) {
return result;
}
ecs_strbuf_init(b);
int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b);
int32_t memLeft = ecs_strbuf_memLeft(b);
if (memLeft <= 0) {
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;
if (n < 0) n = INT_MAX;
memRequired = fast_strncpy(ecs_strbuf_ptr(b), str, max_copy, n);
if (memRequired <= memLeftInElement) {
/* Element was large enough to fit string */
b->current->pos += memRequired;
} else if ((memRequired - memLeftInElement) < memLeft) {
/* Element was not large enough, but buffer still has space */
b->current->pos += memLeftInElement;
memRequired -= memLeftInElement;
/* Current element was too small, copy remainder into new element */
if (memRequired < ECS_STRBUF_ELEMENT_SIZE) {
/* A standard-size buffer is large enough for the new string */
ecs_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(
ecs_strbuf_ptr(b),
str + memLeftInElement,
(size_t)memRequired);
} else {
ecs_os_strcpy(ecs_strbuf_ptr(b), str + memLeftInElement);
}
/* Update to number of characters copied to new buffer */
b->current->pos += memRequired;
} else {
char *remainder = ecs_os_strdup(str + memLeftInElement);
ecs_strbuf_grow_str(b, remainder, remainder, memRequired);
}
} else {
/* Buffer max has been reached */
result = false;
}
return result;
}
bool ecs_strbuf_vappend(
ecs_strbuf_t *b,
const char* fmt,
va_list args)
{
bool result = ecs_strbuf_vappend_intern(
b, fmt, args
);
return result;
}
bool ecs_strbuf_append(
ecs_strbuf_t *b,
const char* fmt,
...)
{
va_list args;
va_start(args, fmt);
bool result = ecs_strbuf_vappend_intern(
b, fmt, args
);
va_end(args);
return result;
}
bool ecs_strbuf_appendstrn(
ecs_strbuf_t *b,
const char* str,
int32_t len)
{
return ecs_strbuf_append_intern(
b, str, len
);
}
bool ecs_strbuf_appendstr_zerocpy(
ecs_strbuf_t *b,
char* str)
{
ecs_strbuf_init(b);
ecs_strbuf_grow_str(b, str, str, 0);
return true;
}
bool ecs_strbuf_appendstr_zerocpy_const(
ecs_strbuf_t *b,
const char* str)
{
/* Removes const modifier, but logic prevents changing / delete string */
ecs_strbuf_init(b);
ecs_strbuf_grow_str(b, (char*)str, NULL, 0);
return true;
}
bool ecs_strbuf_appendstr(
ecs_strbuf_t *b,
const char* str)
{
return ecs_strbuf_append_intern(
b, str, -1
);
}
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_appendstr(dst_buffer, src_buffer->buf);
} 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) {
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';
}
} else {
result = NULL;
}
b->elementCount = 0;
return result;
}
void ecs_strbuf_reset(ecs_strbuf_t *b) {
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 *buffer,
const char *list_open,
const char *separator)
{
buffer->list_sp ++;
buffer->list_stack[buffer->list_sp].count = 0;
buffer->list_stack[buffer->list_sp].separator = separator;
if (list_open) {
ecs_strbuf_appendstr(buffer, list_open);
}
}
void ecs_strbuf_list_pop(
ecs_strbuf_t *buffer,
const char *list_close)
{
buffer->list_sp --;
if (list_close) {
ecs_strbuf_appendstr(buffer, list_close);
}
}
void ecs_strbuf_list_next(
ecs_strbuf_t *buffer)
{
int32_t list_sp = buffer->list_sp;
if (buffer->list_stack[list_sp].count != 0) {
ecs_strbuf_appendstr(buffer, buffer->list_stack[list_sp].separator);
}
buffer->list_stack[list_sp].count ++;
}
bool ecs_strbuf_list_append(
ecs_strbuf_t *buffer,
const char *fmt,
...)
{
ecs_strbuf_list_next(buffer);
va_list args;
va_start(args, fmt);
bool result = ecs_strbuf_vappend_intern(
buffer, fmt, args
);
va_end(args);
return result;
}
bool ecs_strbuf_list_appendstr(
ecs_strbuf_t *buffer,
const char *str)
{
ecs_strbuf_list_next(buffer);
return ecs_strbuf_appendstr(buffer, str);
}
#define ECS_ANNOTATION_LENGTH_MAX (16)
#define TOK_SOURCE ':'
#define TOK_AND ','
#define TOK_OR "||"
#define TOK_NOT '!'
#define TOK_OPTIONAL '?'
#define TOK_ROLE '|'
#define TOK_TRAIT '>'
#define TOK_FOR "FOR"
#define TOK_NAME_SEP '.'
#define TOK_ANNOTATE_OPEN '['
#define TOK_ANNOTATE_CLOSE ']'
#define TOK_WILDCARD '*'
#define TOK_SINGLETON '$'
#define TOK_ANY "ANY"
#define TOK_OWNED "OWNED"
#define TOK_SHARED "SHARED"
#define TOK_SYSTEM "SYSTEM"
#define TOK_PARENT "PARENT"
#define TOK_CASCADE "CASCADE"
#define TOK_ROLE_CHILDOF "CHILDOF"
#define TOK_ROLE_INSTANCEOF "INSTANCEOF"
#define TOK_ROLE_TRAIT "TRAIT"
#define TOK_ROLE_AND "AND"
#define TOK_ROLE_OR "OR"
#define TOK_ROLE_XOR "XOR"
#define TOK_ROLE_NOT "NOT"
#define TOK_ROLE_SWITCH "SWITCH"
#define TOK_ROLE_CASE "CASE"
#define TOK_IN "in"
#define TOK_OUT "out"
#define TOK_INOUT "inout"
#define ECS_MAX_TOKEN_SIZE (256)
typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE];
/** Skip spaces when parsing signature */
static
const char *skip_space(
const char *ptr)
{
while (isspace(*ptr)) {
ptr ++;
}
return ptr;
}
static
int entity_compare(
const void *ptr1,
const void *ptr2)
{
ecs_entity_t e1 = *(ecs_entity_t*)ptr1;
ecs_entity_t e2 = *(ecs_entity_t*)ptr2;
return (e1 > e2) - (e1 < e2);
}
static
void vec_add_entity(
ecs_vector_t **vec,
ecs_entity_t entity)
{
ecs_entity_t *e = ecs_vector_add(vec, ecs_entity_t);
*e = entity;
/* Keep array sorted so that we can use it in type compare operations */
ecs_vector_sort(*vec, ecs_entity_t, entity_compare);
}
/* -- Private functions -- */
static
bool valid_identifier_char(
char ch)
{
if (ch && (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.' ||
ch == TOK_SINGLETON || ch == TOK_WILDCARD))
{
return true;
}
return false;
}
static
bool valid_operator_char(
char ch)
{
if (ch == TOK_OPTIONAL || ch == TOK_NOT) {
return true;
}
return false;
}
static
const char* parse_identifier(
const char *name,
const char *sig,
int64_t column,
const char *ptr,
char *token_out)
{
ptr = skip_space(ptr);
char *tptr = token_out, ch = ptr[0];
if (!valid_identifier_char(ch)) {
ecs_parser_error(name, sig, column, "invalid identifier", ptr);
return NULL;
}
for (; (ch = *ptr); ptr ++) {
if (!valid_identifier_char(ch)) {
break;
}
tptr[0] = ch;
tptr ++;
}
tptr[0] = '\0';
return skip_space(ptr);
}
static
ecs_entity_t parse_role(
const char *name,
const char *sig,
int64_t column,
const char *token)
{
if (!ecs_os_strcmp(token, TOK_ROLE_CHILDOF)) {
return ECS_CHILDOF;
} else if (!ecs_os_strcmp(token, TOK_ROLE_INSTANCEOF)) {
return ECS_INSTANCEOF;
} else if (!ecs_os_strcmp(token, TOK_ROLE_TRAIT)) {
return ECS_TRAIT;
} else 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_XOR)) {
return ECS_XOR;
} else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) {
return ECS_NOT;
} else if (!ecs_os_strcmp(token, TOK_ROLE_SWITCH)) {
return ECS_SWITCH;
} else if (!ecs_os_strcmp(token, TOK_ROLE_CASE)) {
return ECS_CASE;
} else if (!ecs_os_strcmp(token, TOK_OWNED)) {
return ECS_OWNED;
} else {
ecs_parser_error(name, sig, column, "invalid role '%s'", token);
return 0;
}
}
static
ecs_sig_from_kind_t parse_source(
const char *token)
{
if (!ecs_os_strcmp(token, TOK_PARENT)) {
return EcsFromParent;
} else if (!ecs_os_strcmp(token, TOK_SYSTEM)) {
return EcsFromSystem;
} else if (!ecs_os_strcmp(token, TOK_ANY)) {
return EcsFromAny;
} else if (!ecs_os_strcmp(token, TOK_OWNED)) {
return EcsFromOwned;
} else if (!ecs_os_strcmp(token, TOK_SHARED)) {
return EcsFromShared;
} else if (!ecs_os_strcmp(token, TOK_CASCADE)) {
return EcsCascade;
} else {
return EcsFromEntity;
}
}
static
ecs_sig_oper_kind_t parse_operator(
char ch)
{
if (ch == TOK_OPTIONAL) {
return EcsOperOptional;
} else if (ch == TOK_NOT) {
return EcsOperNot;
} else {
ecs_abort(ECS_INTERNAL_ERROR, NULL);
}
}
static
const char* parse_annotation(
const char *name,
const char *sig,
int64_t column,
const char *ptr,
ecs_sig_inout_kind_t *inout_kind_out)
{
char token[ECS_MAX_TOKEN_SIZE];
ptr = parse_identifier(name, sig, column, ptr, token);
if (!ptr) {
return NULL;
}
if (!strcmp(token, "in")) {
*inout_kind_out = EcsIn;
} else
if (!strcmp(token, "out")) {
*inout_kind_out = EcsOut;
} else
if (!strcmp(token, "inout")) {
*inout_kind_out = EcsInOut;
}
ptr = skip_space(ptr);
if (ptr[0] != TOK_ANNOTATE_CLOSE) {
ecs_parser_error(name, sig, column, "expected ]");
return NULL;
}
return ptr + 1;
}
typedef struct sig_element_t {
ecs_entity_t role;
ecs_sig_inout_kind_t inout_kind;
ecs_sig_from_kind_t from_kind;
ecs_sig_oper_kind_t oper_kind;
char *trait;
char *source;
char *component;
char *name;
} sig_element_t;
static
const char* parse_element(
const char *name,
const char *sig,
sig_element_t *elem_out)
{
bool explicit_inout = false;
const char *ptr = sig;
char token[ECS_MAX_TOKEN_SIZE] = {0};
sig_element_t elem = {
.inout_kind = EcsInOut,
.from_kind = EcsFromOwned,
.oper_kind = EcsOperAnd
};
ptr = skip_space(ptr);
/* Inout specifiers always come first */
if (ptr[0] == TOK_ANNOTATE_OPEN) {
explicit_inout = true;
ptr = parse_annotation(name, sig, (ptr - sig), ptr + 1, &elem.inout_kind);
if (!ptr) {
return NULL;
}
ptr = skip_space(ptr);
}
if (valid_operator_char(ptr[0])) {
elem.oper_kind = parse_operator(ptr[0]);
ptr = skip_space(ptr + 1);
}
/* If next token is the start of an identifier, it could be either a type
* role, source or component identifier */
if (valid_identifier_char(ptr[0])) {
ptr = parse_identifier(name, sig, (ptr - sig), ptr, token);
if (!ptr) {
return NULL;
}
/* Is token a source identifier? */
if (ptr[0] == TOK_SOURCE) {
ptr ++;
goto parse_source;
}
/* Is token a type role? */
if (ptr[0] == TOK_ROLE && ptr[1] != TOK_ROLE) {
ptr ++;
goto parse_role;
}
/* Is token a trait? (using shorthand notation) */
if (!ecs_os_strncmp(ptr, TOK_FOR, 3)) {
elem.role = ECS_TRAIT;
ptr += 3;
goto parse_trait;
}
/* If it is neither, the next token must be a component */
goto parse_component;
/* If next token is the source token, this is an empty source */
} else if (ptr[0] == TOK_SOURCE) {
goto empty_source;
/* Nothing else expected here */
} else {
ecs_parser_error(name, sig, (ptr - sig),
"unexpected character '%c'", ptr[0]);
return NULL;
}
empty_source:
elem.from_kind = EcsFromEmpty;
ptr = skip_space(ptr + 1);
if (valid_identifier_char(ptr[0])) {
ptr = parse_identifier(name, sig, (ptr - sig), ptr, token);
if (!ptr) {
return NULL;
}
goto parse_component;
} else {
ecs_parser_error(name, sig, (ptr - sig),
"expected identifier after source operator");
return NULL;
}
parse_source:
elem.from_kind = parse_source(token);
if (elem.from_kind == EcsFromEntity) {
elem.source = ecs_os_strdup(token);
}
ptr = skip_space(ptr);
if (valid_identifier_char(ptr[0])) {
ptr = parse_identifier(name, sig, (ptr - sig), ptr, token);
if (!ptr) {
return NULL;
}
/* Is the next token a role? */
if (ptr[0] == TOK_ROLE && ptr[1] != TOK_ROLE) {
ptr++;
goto parse_role;
}
/* Is token a trait? (using shorthand notation) */
if (!ecs_os_strncmp(ptr, TOK_FOR, 3)) {
elem.role = ECS_TRAIT;
ptr += 3;
goto parse_trait;
}
/* If not, it's a component */
goto parse_component;
} else {
ecs_parser_error(name, sig, (ptr - sig),
"expected identifier after source");
return NULL;
}
parse_role:
elem.role = parse_role(name, sig, (ptr - sig), token);
if (!elem.role) {
return NULL;
}
ptr = skip_space(ptr);
/* If next token is the source token, this is an empty source */
if (valid_identifier_char(ptr[0])) {
ptr = parse_identifier(name, sig, (ptr - sig), ptr, token);
if (!ptr) {
return NULL;
}
/* Is token a trait? */
if (ptr[0] == TOK_TRAIT) {
ptr ++;
goto parse_trait;
}
/* If not, it's a component */
goto parse_component;
} else {
ecs_parser_error(name, sig, (ptr - sig),
"expected identifier after role");
return NULL;
}
parse_trait:
elem.trait = ecs_os_strdup(token);
ptr = skip_space(ptr);
if (valid_identifier_char(ptr[0])) {
ptr = parse_identifier(name, sig, (ptr - sig), ptr, token);
if (!ptr) {
return NULL;
}
/* Can only be a component */
goto parse_component;
} else {
ecs_parser_error(name, sig, (ptr - sig),
"expected identifier after trait");
return NULL;
}
parse_component:
elem.component = ecs_os_strdup(token);
ptr = skip_space(ptr);
if (valid_identifier_char(ptr[0])) {
ptr = parse_identifier(name, sig, (ptr - sig), ptr, token);
if (!ptr) {
return NULL;
}
/* Can only be a name */
goto parse_name;
} else {
/* If nothing else, parsing of this element is done */
goto parse_done;
}
parse_name:
elem.name = ecs_os_strdup(token);
ptr = skip_space(ptr);
parse_done:
if (ptr[0] != TOK_AND && ecs_os_strncmp(ptr, TOK_OR, 2) && ptr[0]) {
ecs_parser_error(name, sig, (ptr - sig),
"expected end of expression or next element");
return NULL;
}
if (!ecs_os_strcmp(elem.component, "0")) {
if (ptr[0]) {
ecs_parser_error(name, sig, (ptr - sig),
"unexpected element after 0");
return NULL;
}
if (elem.from_kind != EcsFromOwned) {
ecs_parser_error(name, sig, (ptr - sig),
"invalid source modifier for 0");
return NULL;
}
}
if (!explicit_inout) {
if (elem.from_kind != EcsFromOwned) {
elem.inout_kind = EcsIn;
}
}
*elem_out = elem;
return ptr;
}
int ecs_parse_expr(
ecs_world_t *world,
const char *name,
const char *sig,
ecs_parse_action_t action,
void *ctx)
{
sig_element_t elem;
bool is_or = false;
const char *ptr = sig;
while ((ptr = parse_element(name, ptr, &elem))) {
if (is_or) {
ecs_assert(elem.oper_kind == EcsOperAnd, ECS_INVALID_SIGNATURE, sig);
elem.oper_kind = EcsOperOr;
}
if (action(world, name, sig, ptr - sig,
elem.from_kind, elem.oper_kind, elem.inout_kind, elem.role,
elem.component, elem.source, elem.trait, elem.name, ctx))
{
if (!name) {
return -1;
}
ecs_abort(ECS_INVALID_SIGNATURE, sig);
}
ecs_os_free(elem.component);
ecs_os_free(elem.source);
ecs_os_free(elem.trait);
ecs_os_free(elem.name);
is_or = false;
if (!strncmp(ptr, TOK_OR, 2)) {
is_or = true;
if (elem.from_kind == EcsFromEmpty) {
ecs_parser_error(name, sig, ptr - sig,
"invalid empty source in or expression");
return -1;
}
if (elem.from_kind == EcsFromSystem) {
ecs_parser_error(name, sig, ptr - sig,
"invalid system source in or expression");
return -1;
}
}
if (ptr[0]) {
ptr ++;
if (is_or) {
ptr ++;
}
}
ptr = skip_space(ptr);
if (!ptr[0]) {
break;
}
}
if (!ptr) {
if (!name) {
return -1;
}
ecs_abort(ECS_INVALID_SIGNATURE, sig);
}
return 0;
}
/** Parse callback that adds component to the components array for a system */
static
int ecs_parse_signature_action(
ecs_world_t *world,
const char *name,
const char *expr,
int64_t column,
ecs_sig_from_kind_t from_kind,
ecs_sig_oper_kind_t oper_kind,
ecs_sig_inout_kind_t inout_kind,
ecs_entity_t role,
const char *entity_id,
const char *source_id,
const char *trait_id,
const char *arg_name,
void *data)
{
ecs_sig_t *sig = data;
bool is_singleton = false;
ecs_assert(sig != NULL, ECS_INTERNAL_ERROR, NULL);
if (entity_id[0] == '$') {
if (from_kind == EcsFromEntity) {
ecs_parser_error(name, expr, column,
"singleton component '%s' cannot have a source", entity_id);
}
from_kind = EcsFromEntity;
is_singleton = true;
entity_id ++;
}
/* Lookup component handle by string identifier */
ecs_entity_t source = 0, component = ecs_lookup_fullpath(world, entity_id);
if (!component) {
/* "0" is a valid expression used to indicate that a system matches no
* components */
if (!strcmp(entity_id, "0")) {
/* No need to add 0 component to signature */
return 0;
} else {
ecs_parser_error(name, expr, column,
"unresolved component identifier '%s'", entity_id);
}
}
if (is_singleton) {
source = component;
}
/* Lookup trait handle by string identifier */
if (trait_id) {
ecs_entity_t trait = ecs_lookup_fullpath(world, trait_id);
if (!trait) {
ecs_parser_error(name, expr, column,
"unresolved trait identifier '%s'", trait_id);
} else {
component = ecs_entity_t_comb(component, trait);
}
}
component |= role;
if (!source && from_kind == EcsFromEntity) {
source = ecs_lookup_fullpath(world, source_id);
if (!source) {
ecs_parser_error(name, expr, column,
"unresolved source identifier '%s'", source_id);
}
}
return ecs_sig_add(
world, sig, from_kind, oper_kind, inout_kind, component, source,
arg_name);
}
void ecs_sig_init(
ecs_world_t *world,
const char *name,
const char *expr,
ecs_sig_t *sig)
{
if (expr && ecs_os_strlen(expr)) {
sig->expr = ecs_os_strdup(expr);
} else {
sig->expr = ecs_os_strdup("0");
}
ecs_parse_expr(world, name, sig->expr, ecs_parse_signature_action, sig);
}
void ecs_sig_deinit(
ecs_sig_t *sig)
{
ecs_vector_each(sig->columns, ecs_sig_column_t, column, {
if (column->oper_kind == EcsOperOr) {
ecs_vector_free(column->is.type);
}
ecs_os_free(column->name);
});
ecs_vector_free(sig->columns);
ecs_os_free(sig->expr);
}
int ecs_sig_add(
ecs_world_t *world,
ecs_sig_t *sig,
ecs_sig_from_kind_t from_kind,
ecs_sig_oper_kind_t oper_kind,
ecs_sig_inout_kind_t inout_kind,
ecs_entity_t component,
ecs_entity_t source,
const char *arg_name)
{
ecs_sig_column_t *elem;
/* If component has AND role, all components of specified type must match */
if (ECS_HAS_ROLE(component, AND)) {
elem = ecs_vector_add(&sig->columns, ecs_sig_column_t);
component &= ECS_ENTITY_MASK;
const EcsType *type = ecs_get(world, component, EcsType);
if (!type) {
ecs_parser_error(sig->name, sig->expr, 0,
"AND flag can only be applied to types");
}
elem->is.component = component;
elem->from_kind = from_kind;
elem->oper_kind = EcsOperAll;
elem->inout_kind = inout_kind;
elem->source = source;
} else
/* If component has OR role, add type as OR column */
if (ECS_HAS_ROLE(component, OR)) {
elem = ecs_vector_add(&sig->columns, ecs_sig_column_t);
component &= ECS_ENTITY_MASK;
const EcsType *type = ecs_get(world, component, EcsType);
if (!type) {
ecs_parser_error(sig->name, sig->expr, 0,
"OR flag can only be applied to types");
}
elem->is.type = ecs_vector_copy(type->normalized, ecs_entity_t);
elem->from_kind = from_kind;
elem->oper_kind = EcsOperOr;
elem->inout_kind = inout_kind;
elem->source = source;
} else
/* AND (default) and optional columns are stored the same way */
if (oper_kind != EcsOperOr) {
elem = ecs_vector_add(&sig->columns, ecs_sig_column_t);
elem->from_kind = from_kind;
elem->oper_kind = oper_kind;
elem->inout_kind = inout_kind;
elem->is.component = component;
elem->source = source;
/* OR columns store a type id instead of a single component */
} else {
ecs_assert(inout_kind != EcsOut, ECS_INVALID_SIGNATURE, NULL);
elem = ecs_vector_last(sig->columns, ecs_sig_column_t);
if (elem->from_kind != from_kind) {
/* Cannot mix FromEntity and FromComponent in OR */
ecs_parser_error(sig->name, sig->expr, 0,
"cannot mix source kinds in || expression");
goto error;
}
if (elem->oper_kind != EcsOperAnd && elem->oper_kind != EcsOperOr) {
ecs_parser_error(sig->name, sig->expr, 0,
"cannot mix operators in || expression");
goto error;
}
if (elem->oper_kind == EcsOperAnd) {
ecs_entity_t prev = elem->is.component;
elem->is.type = NULL;
vec_add_entity(&elem->is.type, prev);
vec_add_entity(&elem->is.type, component);
} else {
vec_add_entity(&elem->is.type, component);
}
elem->from_kind = from_kind;
elem->oper_kind = oper_kind;
}
if (arg_name) {
elem->name = ecs_os_strdup(arg_name);
} else {
elem->name = NULL;
}
return 0;
error:
return -1;
}
/* Check if system meets constraints of non-table columns */
bool ecs_sig_check_constraints(
ecs_world_t *world,
ecs_sig_t *sig)
{
ecs_vector_each(sig->columns, ecs_sig_column_t, elem, {
ecs_sig_from_kind_t from_kind = elem->from_kind;
ecs_sig_oper_kind_t oper_kind = elem->oper_kind;
if (from_kind == EcsFromEntity) {
ecs_type_t type = ecs_get_type(world, elem->source);
if (ecs_type_has_entity(world, type, elem->is.component)) {
if (oper_kind == EcsOperNot) {
return false;
}
} else {
if (oper_kind != EcsOperNot) {
return false;
}
}
}
});
return true;
}
ecs_entity_t ecs_find_entity_in_prefabs(
ecs_world_t *world,
ecs_entity_t entity,
ecs_type_t type,
ecs_entity_t component,
ecs_entity_t previous)
{
int32_t i, count = ecs_vector_count(type);
ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t);
/* Walk from back to front, as prefabs are always located
* at the end of the type. */
for (i = count - 1; i >= 0; i --) {
ecs_entity_t e = array[i];
if (ECS_HAS_ROLE(e, INSTANCEOF)) {
ecs_entity_t prefab = e & ECS_COMPONENT_MASK;
ecs_type_t prefab_type = ecs_get_type(world, prefab);
if (prefab == previous) {
continue;
}
if (ecs_type_owns_entity(
world, prefab_type, component, true))
{
return prefab;
} else {
prefab = ecs_find_entity_in_prefabs(
world, prefab, prefab_type, component, entity);
if (prefab) {
return prefab;
}
}
} else {
/* If this is not a prefab, the following entities won't
* be prefabs either because the array is sorted, and
* the prefab bit is 2^63 which ensures that prefabs are
* guaranteed to be the last entities in the type */
break;
}
}
return 0;
}
/* -- Private functions -- */
/* O(n) algorithm to check whether type 1 is equal or superset of type 2 */
ecs_entity_t ecs_type_contains(
ecs_world_t *world,
ecs_type_t type_1,
ecs_type_t type_2,
bool match_all,
bool match_prefab)
{
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_get_stage(&world);
if (!type_1) {
return 0;
}
ecs_assert(type_2 != NULL, ECS_INTERNAL_ERROR, NULL);
if (type_1 == type_2) {
return *(ecs_vector_first(type_1, ecs_entity_t));
}
int32_t i_2, i_1 = 0;
ecs_entity_t e1 = 0;
ecs_entity_t *t1_array = ecs_vector_first(type_1, ecs_entity_t);
ecs_entity_t *t2_array = ecs_vector_first(type_2, ecs_entity_t);
ecs_assert(t1_array != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(t2_array != NULL, ECS_INTERNAL_ERROR, NULL);
int32_t t1_count = ecs_vector_count(type_1);
int32_t t2_count = ecs_vector_count(type_2);
for (i_2 = 0; i_2 < t2_count; i_2 ++) {
ecs_entity_t e2 = t2_array[i_2];
if (i_1 >= t1_count) {
return 0;
}
e1 = t1_array[i_1];
if (e2 > e1) {
do {
i_1 ++;
if (i_1 >= t1_count) {
return 0;
}
e1 = t1_array[i_1];
} while (e2 > e1);
}
if (e1 != e2) {
if (match_prefab && e2 !=
ecs_typeid(EcsName) && e2 !=
EcsPrefab && e2 !=
EcsDisabled)
{
if (ecs_find_entity_in_prefabs(world, 0, type_1, e2, 0)) {
e1 = e2;
}
}
if (e1 != e2) {
if (match_all) return 0;
} else if (!match_all) {
return e1;
}
} else {
if (!match_all) return e1;
i_1 ++;
if (i_1 < t1_count) {
e1 = t1_array[i_1];
}
}
}
if (match_all) {
return e1;
} else {
return 0;
}
}
/* -- Public API -- */
int32_t ecs_type_index_of(
ecs_type_t type,
ecs_entity_t entity)
{
ecs_vector_each(type, ecs_entity_t, c_ptr, {
if (*c_ptr == entity) {
return c_ptr_i;
}
});
return -1;
}
ecs_type_t ecs_type_merge(
ecs_world_t *world,
ecs_type_t type,
ecs_type_t to_add,
ecs_type_t to_remove)
{
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_get_stage(&world);
ecs_table_t *table = ecs_table_from_type(world, type);
ecs_entities_t add_array = ecs_type_to_entities(to_add);
ecs_entities_t remove_array = ecs_type_to_entities(to_remove);
table = ecs_table_traverse_remove(
world, table, &remove_array, NULL);
table = ecs_table_traverse_add(
world, table, &add_array, NULL);
if (!table) {
return NULL;
} else {
return table->type;
}
}
ecs_type_t ecs_type_find(
ecs_world_t *world,
ecs_entity_t *array,
int32_t count)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_get_stage(&world);
ecs_entities_t entities = {
.array = array,
.count = count
};
ecs_table_t *table = ecs_table_find_or_create(world, &entities);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
return table->type;
}
static
bool has_trait(
ecs_entity_t trait,
ecs_entity_t e)
{
return trait == ecs_entity_t_hi(e & ECS_COMPONENT_MASK);
}
static
bool has_case(
ecs_world_t *world,
ecs_entity_t sw_case,
ecs_entity_t e)
{
const EcsType *type_ptr = ecs_get(world, e & ECS_COMPONENT_MASK, EcsType);
ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
return ecs_type_has_entity(world, type_ptr->normalized, sw_case);
}
static
int match_entity(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t e,
ecs_entity_t match_with)
{
if (ECS_HAS_ROLE(match_with, TRAIT)) {
ecs_entity_t hi = ecs_entity_t_hi(match_with & ECS_COMPONENT_MASK);
ecs_entity_t lo = ecs_entity_t_lo(match_with);
if (lo == EcsWildcard) {
ecs_assert(hi != 0, ECS_INTERNAL_ERROR, NULL);
if (!ECS_HAS_ROLE(e, TRAIT) || !has_trait(hi, e)) {
return 0;
}
ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t);
int32_t i, count = ecs_vector_count(type);
ecs_entity_t comp = ecs_entity_t_lo(e);
for (i = 0; i < count; i ++) {
if (comp == ids[i]) {
return 2;
}
}
return -1;
} else if (!hi) {
if (ECS_HAS_ROLE(e, TRAIT) && has_trait(lo, e)) {
return 1;
}
}
} else
if (ECS_HAS_ROLE(match_with, CASE)) {
ecs_entity_t sw_case = match_with & ECS_COMPONENT_MASK;
if (ECS_HAS_ROLE(e, SWITCH) && has_case(world, sw_case, e)) {
return 1;
} else {
return 0;
}
}
if (e == match_with) {
return 1;
}
return 0;
}
static
bool search_type(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t entity,
bool owned)
{
if (!type) {
return false;
}
if (!entity) {
return true;
}
ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t);
int32_t i, count = ecs_vector_count(type);
int matched = 0;
for (i = 0; i < count; i ++) {
int ret = match_entity(world, type, ids[i], entity);
switch(ret) {
case 0: break;
case 1: return true;
case -1: return false;
case 2: matched ++; break;
default: ecs_abort(ECS_INTERNAL_ERROR, NULL);
}
}
if (!matched && !owned && entity != EcsPrefab && entity != EcsDisabled) {
for (i = count - 1; i >= 0; i --) {
ecs_entity_t e = ids[i];
if (!ECS_HAS_ROLE(e, INSTANCEOF)) {
break;
}
ecs_entity_t base = e & ECS_COMPONENT_MASK;
ecs_type_t base_type = ecs_get_type(world, base);
if (search_type(world, base_type, entity, false)) {
return true;
}
}
}
return matched != 0;
}
bool ecs_type_has_entity(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t entity)
{
return search_type(world, type, entity, false);
}
bool ecs_type_owns_entity(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t entity,
bool owned)
{
return search_type(world, type, entity, owned);
}
bool ecs_type_has_type(
ecs_world_t *world,
ecs_type_t type,
ecs_type_t has)
{
return ecs_type_contains(world, type, has, true, false) != 0;
}
bool ecs_type_owns_type(
ecs_world_t *world,
ecs_type_t type,
ecs_type_t has,
bool owned)
{
return ecs_type_contains(world, type, has, true, !owned) != 0;
}
ecs_type_t ecs_type_add(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t e)
{
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_get_stage(&world);
ecs_table_t *table = ecs_table_from_type(world, type);
ecs_entities_t entities = {
.array = &e,
.count = 1
};
table = ecs_table_traverse_add(world, table, &entities, NULL);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
return table->type;
}
ecs_type_t ecs_type_remove(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t e)
{
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_get_stage(&world);
ecs_table_t *table = ecs_table_from_type(world, type);
ecs_entities_t entities = {
.array = &e,
.count = 1
};
table = ecs_table_traverse_remove(world, table, &entities, NULL);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
return table->type;
}
char* ecs_type_str(
ecs_world_t *world,
ecs_type_t type)
{
if (!type) {
return ecs_os_strdup("");
}
ecs_vector_t *chbuf = ecs_vector_new(char, 32);
char *dst;
ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t);
int32_t i, count = ecs_vector_count(type);
for (i = 0; i < count; i ++) {
ecs_entity_t e = entities[i];
char buffer[256];
ecs_size_t len;
if (i) {
*(char*)ecs_vector_add(&chbuf, char) = ',';
}
if (e == 1) {
ecs_os_strcpy(buffer, "EcsComponent");
len = ecs_os_strlen("EcsComponent");
} else {
len = ecs_from_size_t(ecs_entity_str(world, e, buffer, 256));
}
dst = ecs_vector_addn(&chbuf, char, len);
ecs_os_memcpy(dst, buffer, len);
}
*(char*)ecs_vector_add(&chbuf, char) = '\0';
char* result = ecs_os_strdup(ecs_vector_first(chbuf, char));
ecs_vector_free(chbuf);
return result;
}
ecs_entity_t ecs_type_get_entity_for_xor(
ecs_world_t *world,
ecs_type_t type,
ecs_entity_t xor)
{
ecs_assert(
ecs_type_owns_entity(world, type, ECS_XOR | xor, true),
ECS_INVALID_PARAMETER, NULL);
const EcsType *type_ptr = ecs_get(world, xor, EcsType);
ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_type_t xor_type = type_ptr->normalized;
ecs_assert(xor_type != NULL, ECS_INTERNAL_ERROR, NULL);
int32_t i, count = ecs_vector_count(type);
ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t);
for (i = 0; i < count; i ++) {
if (ecs_type_owns_entity(world, xor_type, array[i], true)) {
return array[i];
}
}
return 0;
}
int32_t ecs_type_trait_index_of(
ecs_type_t type,
int32_t start_index,
ecs_entity_t trait)
{
int32_t i, count = ecs_vector_count(type);
ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t);
for (i = start_index; i < count; i ++) {
ecs_entity_t e = array[i];
if (ECS_HAS_ROLE(e, TRAIT)) {
e &= ECS_COMPONENT_MASK;
if (trait == ecs_entity_t_hi(e)) {
return i;
}
}
}
return -1;
}
void ecs_os_api_impl(ecs_os_api_t *api);
static bool ecs_os_api_initialized = false;
static int ecs_os_api_init_count = 0;
ecs_os_api_t ecs_os_api;
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;
}
}
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_();
}
}
}
static
void ecs_log(const char *fmt, va_list args) {
vfprintf(stdout, fmt, args);
fprintf(stdout, "\n");
}
static
void ecs_log_error(const char *fmt, va_list args) {
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
}
static
void ecs_log_debug(const char *fmt, va_list args) {
vfprintf(stdout, fmt, args);
fprintf(stdout, "\n");
}
static
void ecs_log_warning(const char *fmt, va_list args) {
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
}
void ecs_os_dbg(const char *fmt, ...) {
#ifndef NDEBUG
va_list args;
va_start(args, fmt);
if (ecs_os_api.log_debug_) {
ecs_os_api.log_debug_(fmt, args);
}
va_end(args);
#else
(void)fmt;
#endif
}
void ecs_os_warn(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
if (ecs_os_api.log_warning_) {
ecs_os_api.log_warning_(fmt, args);
}
va_end(args);
}
void ecs_os_log(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
if (ecs_os_api.log_) {
ecs_os_api.log_(fmt, args);
}
va_end(args);
}
void ecs_os_err(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
if (ecs_os_api.log_error_) {
ecs_os_api.log_error_(fmt, args);
}
va_end(args);
}
static
void ecs_os_gettime(ecs_time_t *time)
{
uint64_t now = ecs_os_time_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_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_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_api_realloc_count ++;
} else {
/* If not actually reallocing, treat as malloc */
ecs_os_api_malloc_count ++;
}
return realloc(ptr, (size_t)size);
}
static
void ecs_os_api_free(void *ptr) {
if (ptr) {
ecs_os_api_free_count ++;
}
free(ptr);
}
static
char* ecs_os_api_strdup(const char *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;
}
/* 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_OS_LINUX)
ecs_strbuf_appendstr(&lib, "lib");
ecs_strbuf_appendstr(&lib, file_base);
ecs_strbuf_appendstr(&lib, ".so");
#elif defined(ECS_OS_DARWIN)
ecs_strbuf_appendstr(&lib, "lib");
ecs_strbuf_appendstr(&lib, file_base);
ecs_strbuf_appendstr(&lib, ".dylib");
#elif defined(ECS_OS_WINDOWS)
ecs_strbuf_appendstr(&lib, file_base);
ecs_strbuf_appendstr(&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_appendstr(&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;
}
ecs_os_time_setup();
/* 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.sleep_ = ecs_os_time_sleep;
ecs_os_api.get_time_ = ecs_os_gettime;
/* Logging */
ecs_os_api.log_ = ecs_log;
ecs_os_api.log_error_ = ecs_log_error;
ecs_os_api.log_debug_ = ecs_log_debug;
ecs_os_api.log_warning_ = ecs_log_warning;
/* 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;
}
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);
}
bool ecs_os_has_time(void) {
return
(ecs_os_api.get_time_ != NULL) &&
(ecs_os_api.sleep_ != NULL);
}
bool ecs_os_has_logging(void) {
return
(ecs_os_api.log_ != NULL) &&
(ecs_os_api.log_error_ != NULL) &&
(ecs_os_api.log_debug_ != NULL) &&
(ecs_os_api.log_warning_ != 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);
}
#ifdef FLECS_SYSTEMS_H
#endif
static
void activate_table(
ecs_world_t *world,
ecs_query_t *query,
ecs_table_t *table,
bool active);
static
ecs_entity_t components_contains(
ecs_world_t *world,
ecs_type_t table_type,
ecs_type_t type,
ecs_entity_t *entity_out,
bool match_all)
{
ecs_vector_each(table_type, ecs_entity_t, c_ptr, {
ecs_entity_t entity = *c_ptr;
if (ECS_HAS_ROLE(entity, CHILDOF)) {
entity &= ECS_COMPONENT_MASK;
ecs_record_t *record = ecs_eis_get(world, entity);
ecs_assert(record != 0, ECS_INTERNAL_ERROR, NULL);
if (record->table) {
ecs_entity_t component = ecs_type_contains(
world, record->table->type, type, match_all, true);
if (component) {
if (entity_out) *entity_out = entity;
return component;
}
}
}
});
return 0;
}
/* Get actual entity on which specified component is stored */
static
ecs_entity_t get_entity_for_component(
ecs_world_t *world,
ecs_entity_t entity,
ecs_type_t type,
ecs_entity_t component)
{
if (entity) {
ecs_record_t *record = ecs_eis_get(world, entity);
ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL);
if (record->table) {
type = record->table->type;
} else {
type = NULL;
}
}
ecs_vector_each(type, ecs_entity_t, c_ptr, {
if (*c_ptr == component) {
return entity;
}
});
return ecs_find_entity_in_prefabs(world, entity, type, component, 0);
}
#ifndef NDEBUG
static
ecs_entity_t get_cascade_component(
ecs_query_t *query)
{
ecs_sig_column_t *column = ecs_vector_first(query->sig.columns, ecs_sig_column_t);
return column[query->cascade_by - 1].is.component;
}
#endif
static
int32_t rank_by_depth(
ecs_world_t *world,
ecs_entity_t rank_by_component,
ecs_type_t type)
{
int32_t result = 0;
int32_t i, count = ecs_vector_count(type);
ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t);
for (i = count - 1; i >= 0; i --) {
if (ECS_HAS_ROLE(array[i], CHILDOF)) {
ecs_type_t c_type = ecs_get_type(world, array[i] & ECS_COMPONENT_MASK);
int32_t j, c_count = ecs_vector_count(c_type);
ecs_entity_t *c_array = ecs_vector_first(c_type, ecs_entity_t);
for (j = 0; j < c_count; j ++) {
if (c_array[j] == rank_by_component) {
result ++;
result += rank_by_depth(world, rank_by_component, c_type);
break;
}
}
if (j != c_count) {
break;
}
} else if (!(array[i] & ECS_ROLE_MASK)) {
/* No more parents after this */
break;
}
}
return result;
}
static
int table_compare(
const void *t1,
const void *t2)
{
const ecs_matched_table_t *table_1 = t1;
const ecs_matched_table_t *table_2 = t2;
return table_1->rank - table_2->rank;
}
static
bool has_auto_activation(
ecs_query_t *q)
{
/* Only a basic query with no additional features does table activation */
return !(q->flags & EcsQueryNoActivation);
}
static
void order_ranked_tables(
ecs_world_t *world,
ecs_query_t *query)
{
if (query->group_table) {
ecs_vector_sort(query->tables, ecs_matched_table_t, table_compare);
/* Recompute the table indices by first resetting all indices, and then
* re-adding them one by one. */
if (has_auto_activation(query)) {
ecs_map_iter_t it = ecs_map_iter(query->table_indices);
ecs_table_indices_t *ti;
while ((ti = ecs_map_next(&it, ecs_table_indices_t, NULL))) {
/* If table is registered, it must have at least one index */
int32_t count = ti->count;
ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL);
(void)count;
/* Only active tables are reordered, so don't reset inactive
* tables */
if (ti->indices[0] >= 0) {
ti->count = 0;
}
}
}
/* Re-register monitors after tables have been reordered. This will update
* the table administration with the new matched_table ids, so that when a
* monitor is executed we can quickly find the right matched_table. */
if (query->flags & EcsQueryMonitor) {
ecs_vector_each(query->tables, ecs_matched_table_t, table, {
ecs_table_notify(world, table->iter_data.table, &(ecs_table_event_t){
.kind = EcsTableQueryMatch,
.query = query,
.matched_table_index = table_i
});
});
}
/* Update table index */
if (has_auto_activation(query)) {
ecs_vector_each(query->tables, ecs_matched_table_t, table, {
ecs_table_indices_t *ti = ecs_map_get(query->table_indices,
ecs_table_indices_t, table->iter_data.table->id);
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
ti->indices[ti->count] = table_i;
ti->count ++;
});
}
}
query->match_count ++;
query->needs_reorder = false;
}
static
void group_table(
ecs_world_t *world,
ecs_query_t *query,
ecs_matched_table_t *table)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
if (query->group_table) {
ecs_assert(table->iter_data.table != NULL, ECS_INTERNAL_ERROR, NULL);
table->rank = query->group_table(
world, query->rank_on_component, table->iter_data.table->type);
} else {
table->rank = 0;
}
}
/* Rank all tables of query. Only necessary if a new ranking function was
* provided or if a monitored entity set the component used for ranking. */
static
void group_tables(
ecs_world_t *world,
ecs_query_t *query)
{
if (query->group_table) {
ecs_vector_each(query->tables, ecs_matched_table_t, table, {
group_table(world, query, table);
});
ecs_vector_each(query->empty_tables, ecs_matched_table_t, table, {
group_table(world, query, table);
});
}
}
#ifndef NDEBUG
static
const char* query_name(
ecs_world_t *world,
ecs_query_t *q)
{
if (q->system) {
return ecs_get_name(world, q->system);
} else {
return q->sig.expr;
}
}
#endif
static
void get_comp_and_src(
ecs_world_t *world,
ecs_query_t *query,
ecs_type_t table_type,
ecs_sig_column_t *column,
ecs_sig_oper_kind_t op,
ecs_sig_from_kind_t from,
ecs_entity_t *component_out,
ecs_entity_t *entity_out)
{
ecs_entity_t component = 0, entity = 0;
if (op == EcsOperNot) {
entity = column->source;
}
/* Column that retrieves data from self or a fixed entity */
if (from == EcsFromAny || from == EcsFromEntity ||
from == EcsFromOwned || from == EcsFromShared)
{
if (op == EcsOperAnd || op == EcsOperNot || op == EcsOperOptional) {
component = column->is.component;
} else if (op == EcsOperAll) {
component = column->is.component & ECS_COMPONENT_MASK;
} else if (op == EcsOperOr) {
component = ecs_type_contains(
world, table_type, column->is.type,
false, true);
}
if (from == EcsFromEntity) {
entity = column->source;
}
/* Column that just passes a handle to the system (no data) */
} else if (from == EcsFromEmpty) {
component = column->is.component;
/* Column that retrieves data from a dynamic entity */
} else if (from == EcsFromParent || from == EcsCascade) {
if (op == EcsOperAnd ||
op == EcsOperOptional)
{
component = column->is.component;
entity = ecs_find_in_type(
world, table_type, component, ECS_CHILDOF);
} else if (op == EcsOperOr) {
component = components_contains(
world,
table_type,
column->is.type,
&entity,
false);
}
/* Column that retrieves data from a system */
} else if (from == EcsFromSystem) {
if (op == EcsOperAnd) {
component = column->is.component;
}
entity = query->system;
}
*component_out = component;
*entity_out = entity;
}
typedef struct trait_offset_t {
int32_t index;
int32_t count;
} trait_offset_t;
/* Get index for specified trait. Take into account that a trait can be matched
* multiple times per table, by keeping an offset of the last found index */
static
int32_t get_trait_index(
ecs_type_t table_type,
ecs_entity_t component,
int32_t column_index,
trait_offset_t *trait_offsets,
int32_t count)
{
int32_t result;
/* The count variable keeps track of the number of times a trait has been
* matched with the current table. Compare the count to check if the index
* was already resolved for this iteration */
if (trait_offsets[column_index].count == count) {
/* If it was resolved, return the last stored index. Subtract one as the
* index is offset by one, to ensure we're not getting stuck on the same
* index. */
result = trait_offsets[column_index].index - 1;
} else {
/* First time for this iteration that the trait index is resolved, look
* it up in the type. */
result = ecs_type_trait_index_of(table_type,
trait_offsets[column_index].index, component);
trait_offsets[column_index].index = result + 1;
trait_offsets[column_index].count = count;
}
return result;
}
static
int32_t get_component_index(
ecs_world_t *world,
ecs_table_t *table,
ecs_type_t table_type,
ecs_entity_t *component_out,
int32_t column_index,
ecs_sig_oper_kind_t op,
trait_offset_t *trait_offsets,
int32_t count)
{
int32_t result = 0;
ecs_entity_t component = *component_out;
if (component) {
/* If requested component is a case, find the corresponding switch to
* lookup in the table */
if (ECS_HAS_ROLE(component, CASE)) {
result = ecs_table_switch_from_case(
world, table, component);
ecs_assert(result != -1, ECS_INTERNAL_ERROR, NULL);
result += table->sw_column_offset;
} else
if (ECS_HAS_ROLE(component, TRAIT)) {
/* If only the lo part of the trait identifier is set, interpret it
* as the trait to match. This will match any instance of the trait
* on the entity and in a signature looks like "TRAIT | MyTrait". */
if (!ecs_entity_t_hi(component & ECS_COMPONENT_MASK)) {
ecs_assert(trait_offsets != NULL,
ECS_INTERNAL_ERROR, NULL);
/* Strip the TRAIT role */
component &= ECS_COMPONENT_MASK;
/* Get index of trait. Start looking from the last trait index
* as this may not be the first instance of the trait. */
result = get_trait_index(
table_type, component, column_index, trait_offsets, count);
if (result != -1) {
/* If component of current column is a trait, get the actual
* trait type for the table, so the system can see which
* component the trait was applied to */
ecs_entity_t *trait = ecs_vector_get(
table_type, ecs_entity_t, result);
*component_out = *trait;
/* Check if the trait is a tag or whether it has data */
if (ecs_get(world, component, EcsComponent) == NULL) {
/* If trait has no data associated with it, use the
* component to which the trait has been added */
component = ecs_entity_t_lo(*trait);
}
}
} else {
/* If trait does have the hi part of the identifier set, this is
* a fully qualified trait identifier. In a signature this looks
* like "TRAIT | MyTrait > Comp". */
ecs_entity_t lo = ecs_entity_t_lo(component);
if (lo == EcsWildcard) {
ecs_assert(trait_offsets != NULL,
ECS_INTERNAL_ERROR, NULL);
/* Get id for the trait to lookup by taking the trait from
* the high 32 bits, move it to the low 32 bits, and reapply
* the TRAIT mask. */
component = ecs_entity_t_hi(component & ECS_COMPONENT_MASK);
/* If the low part of the identifier is the wildcard entity,
* this column is requesting the component to which the
* trait is applied. First, find the component identifier */
result = get_trait_index(table_type, component,
column_index, trait_offsets, count);
/* Type must have the trait, otherwise table would not have
* matched */
ecs_assert(result != -1, ECS_INTERNAL_ERROR, NULL);
/* Get component id at returned index */
ecs_entity_t *trait = ecs_vector_get(
table_type, ecs_entity_t, result);
ecs_assert(trait != NULL, ECS_INTERNAL_ERROR, NULL);
/* Get the lower part of the trait id. This is the component
* we're looking for. */
component = ecs_entity_t_lo(*trait);
*component_out = component;
/* Now lookup the component as usual */
}
/* If the low part is a regular entity (component), then
* this query exactly matches a single trait instance. In
* this case we can simply do a lookup of the trait
* identifier in the table type. */
result = ecs_type_index_of(table_type, component);
}
} else {
/* Get column index for component */
result = ecs_type_index_of(table_type, component);
}
/* If column is found, add one to the index, as column zero in
* a table is reserved for entity id's */
if (result != -1) {
result ++;
}
/* Check if component is a tag. If it is, set table_data to
* zero, so that a system won't try to access the data */
if (!ECS_HAS_ROLE(component, CASE) &&
!ECS_HAS_ROLE(component, SWITCH))
{
component = ecs_get_typeid(world, component);
const EcsComponent *data = ecs_get(
world, component, EcsComponent);
if (!data || !data->size) {
result = 0;
}
}
/* ecs_table_column_offset may return -1 if the component comes
* from a prefab. If so, the component will be resolved as a
* reference (see below) */
}
if (op == EcsOperAll) {
result = 0;
} else if (op == EcsOperOptional) {
/* If table doesn't have the field, mark it as no data */
if (!ecs_type_has_entity(world, table_type, component)) {
result = 0;
}
}
return result;
}
static
ecs_vector_t* add_ref(
ecs_world_t *world,
ecs_query_t *query,
ecs_type_t table_type,
ecs_vector_t *references,
ecs_entity_t component,
ecs_entity_t entity,
ecs_sig_from_kind_t from)
{
const EcsComponent *c_info = ecs_get(world, component, EcsComponent);
ecs_entity_t e;
ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t);
/* Find the entity for the component */
if (from == EcsFromEntity || from == EcsFromEmpty) {
e = entity;
} else if (from == EcsCascade) {
e = entity;
} else if (from == EcsFromSystem) {
e = entity;
} else {
e = get_entity_for_component(
world, entity, table_type, component);
}
if (from != EcsCascade) {
ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL);
}
*ref = (ecs_ref_t){0};
ref->entity = e;
ref->component = component;
if (ecs_has(world, component, EcsComponent)) {
if (c_info->size && from != EcsFromEmpty) {
if (e) {
ecs_get_ref_w_entity(
world, ref, e, component);
ecs_set_watch(world, e);
}
query->flags |= EcsQueryHasRefs;
}
}
return references;
}
static
ecs_entity_t is_column_trait(
ecs_sig_column_t *column)
{
ecs_sig_from_kind_t from_kind = column->from_kind;
ecs_sig_oper_kind_t oper_kind = column->oper_kind;
/* For now traits are only supported on owned columns */
if (from_kind == EcsFromOwned && oper_kind == EcsOperAnd) {
ecs_entity_t c = column->is.component;
if (ECS_HAS_ROLE(c, TRAIT)) {
if (!(ecs_entity_t_hi(c & ECS_COMPONENT_MASK))) {
return c;
} else
if (ecs_entity_t_lo(c) == EcsWildcard) {
return ecs_entity_t_hi(c & ECS_COMPONENT_MASK);
}
}
}
return 0;
}
static
int32_t type_trait_count(
ecs_type_t type,
ecs_entity_t trait)
{
int32_t i, count = ecs_vector_count(type);
ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t);
int32_t result = 0;
trait &= ECS_COMPONENT_MASK;
for (i = 0; i < count; i ++) {
ecs_entity_t e = entities[i];
if (ECS_HAS_ROLE(e, TRAIT)) {
e &= ECS_COMPONENT_MASK;
if (ecs_entity_t_hi(e) == trait) {
result ++;
}
}
}
return result;
}
/* For each trait that the query subscribes for, count the occurrences in the
* table. Cardinality of subscribed for traits must be the same as in the table
* or else the table won't match. */
static
int32_t count_traits(
ecs_query_t *query,
ecs_type_t type)
{
ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t);
int32_t i, count = ecs_vector_count(query->sig.columns);
int32_t first_count = 0, trait_count = 0;
for (i = 0; i < count; i ++) {
ecs_entity_t trait = is_column_trait(&columns[i]);
if (trait) {
trait_count = type_trait_count(type, trait);
if (!first_count) {
first_count = trait_count;
} else {
if (first_count != trait_count) {
/* The traits that this query subscribed for occur in the
* table but don't have the same cardinality. Ignore the
* table. This could typically happen for empty tables along
* a path in the table graph. */
return -1;
}
}
}
}
return first_count;
}
static
ecs_type_t get_column_type(
ecs_world_t *world,
ecs_sig_oper_kind_t oper_kind,
ecs_entity_t component)
{
if (oper_kind == EcsOperAll) {
const EcsType *type = ecs_get(world, component, EcsType);
ecs_assert(type != NULL, ECS_INVALID_PARAMETER, NULL);
return type->normalized;
} else {
return ecs_type_from_entity(world, component);
}
}
/** Add table to system, compute offsets for system components in table it */
static
void add_table(
ecs_world_t *world,
ecs_query_t *query,
ecs_table_t *table)
{
ecs_type_t table_type = NULL;
int32_t c, column_count = ecs_vector_count(query->sig.columns);
if (table) {
table_type = table->type;
}
int32_t trait_cur = 0, trait_count = count_traits(query, table_type);
/* If the query has traits, we need to account for the fact that a table may
* have multiple components to which the trait is applied, which means the
* table has to be registered with the query multiple times, with different
* table columns. If so, allocate a small array for each trait in which the
* last added table index of the trait is stored, so that in the next
* iteration we can start the search from the correct offset type. */
trait_offset_t *trait_offsets = NULL;
if (trait_count) {
trait_offsets = ecs_os_calloc(
ECS_SIZEOF(trait_offset_t) * column_count);
}
/* From here we recurse */
int32_t *table_indices = NULL;
int32_t table_indices_count = 0;
int32_t matched_table_index = 0;
ecs_matched_table_t table_data;
ecs_vector_t *references = NULL;
add_trait:
table_data = (ecs_matched_table_t){ .iter_data.table = table };
if (table) {
table_type = table->type;
}
/* If grouping is enabled for query, assign the group rank to the table */
group_table(world, query, &table_data);
if (column_count) {
/* Array that contains the system column to table column mapping */
table_data.iter_data.columns = ecs_os_malloc(ECS_SIZEOF(int32_t) * column_count);
ecs_assert(table_data.iter_data.columns != NULL, ECS_OUT_OF_MEMORY, NULL);
/* Store the components of the matched table. In the case of OR expressions,
* components may differ per matched table. */
table_data.iter_data.components = ecs_os_malloc(ECS_SIZEOF(ecs_entity_t) * column_count);
ecs_assert(table_data.iter_data.components != NULL, ECS_OUT_OF_MEMORY, NULL);
/* Also cache types, so no lookup is needed while iterating */
table_data.iter_data.types = ecs_os_malloc(ECS_SIZEOF(ecs_type_t) * column_count);
ecs_assert(table_data.iter_data.types != NULL, ECS_OUT_OF_MEMORY, NULL);
}
/* Walk columns parsed from the system signature */
ecs_sig_column_t *columns = ecs_vector_first(
query->sig.columns, ecs_sig_column_t);
for (c = 0; c < column_count; c ++) {
ecs_sig_column_t *column = &columns[c];
ecs_entity_t entity = 0, component = 0;
ecs_sig_oper_kind_t op = column->oper_kind;
ecs_sig_from_kind_t from = column->from_kind;
if (op == EcsOperNot) {
from = EcsFromEmpty;
}
table_data.iter_data.columns[c] = 0;
/* Get actual component and component source for current column */
get_comp_and_src(world, query, table_type, column, op, from, &component,
&entity);
/* This column does not retrieve data from a static entity (either
* EcsFromSystem or EcsFromParent) and is not just a handle */
if (!entity && from != EcsFromEmpty) {
int32_t index = get_component_index(world, table, table_type,
&component, c, op, trait_offsets, trait_cur + 1);
if (index == -1) {
if (from == EcsFromOwned && op == EcsOperOptional) {
index = 0;
}
} else {
if (from == EcsFromShared && op == EcsOperOptional) {
index = 0;
}
}
table_data.iter_data.columns[c] = index;
/* If the column is a case, we should only iterate the entities in
* the column for this specific case. Add a sparse column with the
* case id so we can find the correct entities when iterating */
if (ECS_HAS_ROLE(component, CASE)) {
ecs_sparse_column_t *sc = ecs_vector_add(
&table_data.sparse_columns, ecs_sparse_column_t);
sc->signature_column_index = c;
sc->sw_case = component & ECS_COMPONENT_MASK;
sc->sw_column = NULL;
}
/* If table has a disabled bitmask for components, check if there is
* a disabled column for the queried for component. If so, cache it
* in a vector as the iterator will need to skip the entity when the
* component is disabled. */
if (index && (table->flags & EcsTableHasDisabled)) {
ecs_entity_t bs_id =
(component & ECS_COMPONENT_MASK) | ECS_DISABLED;
int32_t bs_index = ecs_type_index_of(table->type, bs_id);
if (bs_index != -1) {
ecs_bitset_column_t *elem = ecs_vector_add(
&table_data.bitset_columns, ecs_bitset_column_t);
elem->column_index = bs_index;
elem->bs_column = NULL;
}
}
}
/* Check if a the component is a reference. If 'entity' is set, the
* component must be resolved from another entity, which is the case
* for FromEntity and FromContainer.
*
* If no entity is set but the component is not found in the table, it
* must come from a prefab. This is guaranteed, as at this point it is
* already validated that the table matches with the system.
*
* If the column from is Cascade, there may not be an entity in case the
* current table contains root entities. In that case, still add a
* reference field. The application can, after the table has matched,
* change the set of components, so that this column will turn into a
* reference. Having the reference already linked to the system table
* makes changing this administation easier when the change happens.
*/
if ((entity || table_data.iter_data.columns[c] == -1 || from == EcsCascade)) {
references = add_ref(world, query, table_type, references,
component, entity, from);
table_data.iter_data.columns[c] = -ecs_vector_count(references);
}
table_data.iter_data.components[c] = component;
table_data.iter_data.types[c] = get_column_type(world, op, component);
}
/* Initially always add table to inactive group. If the system is registered
* with the table and the table is not empty, the table will send an
* activate signal to the system. */
ecs_matched_table_t *table_elem;
if (table && has_auto_activation(query)) {
table_elem = ecs_vector_add(&query->empty_tables,
ecs_matched_table_t);
/* Store table index */
matched_table_index = ecs_vector_count(query->empty_tables);
table_indices_count ++;
table_indices = ecs_os_realloc(
table_indices, table_indices_count * ECS_SIZEOF(int32_t));
table_indices[table_indices_count - 1] = -matched_table_index;
#ifndef NDEBUG
char *type_expr = ecs_type_str(world, table->type);
ecs_trace_2("query #[green]%s#[reset] matched with table #[green][%s]",
query_name(world, query), type_expr);
ecs_os_free(type_expr);
#endif
} else {
/* If no table is provided to function, this is a system that contains
* no columns that require table matching. In this case, the system will
* only have one "dummy" table that caches data from the system columns.
* Always add this dummy table to the list of active tables, since it
* would never get activated otherwise. */
table_elem = ecs_vector_add(&query->tables, ecs_matched_table_t);
/* If query doesn't automatically activates/inactivates tables, we can
* get the count to determine the current table index. */
matched_table_index = ecs_vector_count(query->tables) - 1;
ecs_assert(matched_table_index >= 0, ECS_INTERNAL_ERROR, NULL);
}
if (references) {
ecs_size_t ref_size = ECS_SIZEOF(ecs_ref_t) * ecs_vector_count(references);
table_data.iter_data.references = ecs_os_malloc(ref_size);
ecs_os_memcpy(table_data.iter_data.references,
ecs_vector_first(references, ecs_ref_t), ref_size);
ecs_vector_free(references);
references = NULL;
}
*table_elem = table_data;
/* Use tail recursion when adding table for multiple traits */
trait_cur ++;
if (trait_cur < trait_count) {
goto add_trait;
}
/* Register table indices before sending out the match signal. This signal
* can cause table activation, and table indices are needed for that. */
if (table_indices) {
ecs_table_indices_t *ti = ecs_map_ensure(
query->table_indices, ecs_table_indices_t, table->id);
if (ti->indices) {
ecs_os_free(ti->indices);
}
ti->indices = table_indices;
ti->count = table_indices_count;
}
if (table && !(query->flags & EcsQueryIsSubquery)) {
ecs_table_notify(world, table, &(ecs_table_event_t){
.kind = EcsTableQueryMatch,
.query = query,
.matched_table_index = matched_table_index
});
} else if (table && ecs_table_count(table)) {
activate_table(world, query, table, true);
}
if (trait_offsets) {
ecs_os_free(trait_offsets);
}
}
static
bool match_column(
ecs_world_t *world,
ecs_type_t type,
ecs_sig_from_kind_t from_kind,
ecs_entity_t component,
ecs_entity_t source,
ecs_match_failure_t *failure_info)
{
if (from_kind == EcsFromAny) {
failure_info->reason = EcsMatchFromSelf;
return ecs_type_has_entity(world, type, component);
} else if (from_kind == EcsFromOwned) {
failure_info->reason = EcsMatchFromOwned;
return ecs_type_owns_entity(world, type, component, true);
} else if (from_kind == EcsFromShared) {
failure_info->reason = EcsMatchFromShared;
return !ecs_type_owns_entity(world, type, component, true) &&
ecs_type_owns_entity(world, type, component, false);
} else if (from_kind == EcsFromParent) {
failure_info->reason = EcsMatchFromContainer;
return ecs_find_in_type(world, type, component, ECS_CHILDOF) != 0;
} else if (from_kind == EcsFromEntity) {
failure_info->reason = EcsMatchFromEntity;
ecs_type_t source_type = ecs_get_type(world, source);
return ecs_type_has_entity(world, source_type, component);
} else {
return true;
}
}
/* Match table with system */
bool ecs_query_match(
ecs_world_t *world,
ecs_table_t *table,
ecs_query_t *query,
ecs_match_failure_t *failure_info)
{
/* Prevent having to add if not null checks everywhere */
ecs_match_failure_t tmp_failure_info;
if (!failure_info) {
failure_info = &tmp_failure_info;
}
failure_info->reason = EcsMatchOk;
failure_info->column = 0;
if (!(query->flags & EcsQueryNeedsTables)) {
failure_info->reason = EcsMatchSystemIsATask;
return false;
}
ecs_type_t type, table_type = table->type;
/* Don't match disabled entities */
if (!(query->flags & EcsQueryMatchDisabled) && ecs_type_owns_entity(
world, table_type, EcsDisabled, true))
{
failure_info->reason = EcsMatchEntityIsDisabled;
return false;
}
/* Don't match prefab entities */
if (!(query->flags & EcsQueryMatchPrefab) && ecs_type_owns_entity(
world, table_type, EcsPrefab, true))
{
failure_info->reason = EcsMatchEntityIsPrefab;
return false;
}
/* Check if trait cardinality matches traits in query, if any */
if (count_traits(query, table->type) == -1) {
return false;
}
int32_t i, column_count = ecs_vector_count(query->sig.columns);
ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t);
for (i = 0; i < column_count; i ++) {
ecs_sig_column_t *elem = &columns[i];
ecs_sig_from_kind_t from_kind = elem->from_kind;
ecs_sig_oper_kind_t oper_kind = elem->oper_kind;
failure_info->column = i + 1;
if (oper_kind == EcsOperAnd) {
if (!match_column(
world, table_type, from_kind, elem->is.component,
elem->source, failure_info))
{
return false;
}
} else if (oper_kind == EcsOperNot) {
if (match_column(
world, table_type, from_kind, elem->is.component,
elem->source, failure_info))
{
return false;
}
} else if (oper_kind == EcsOperOr || oper_kind == EcsOperAll) {
bool match_all = oper_kind == EcsOperAll;
if (match_all) {
const EcsType *type_ptr = ecs_get(world, elem->is.component, EcsType);
type = type_ptr->normalized;
} else {
type = elem->is.type;
}
if (from_kind == EcsFromAny) {
if (!ecs_type_contains(
world, table_type, type, match_all, true))
{
failure_info->reason = EcsMatchOrFromSelf;
return false;
}
} else if (from_kind == EcsFromOwned) {
if (!ecs_type_contains(
world, table_type, type, match_all, false))
{
failure_info->reason = EcsMatchOrFromOwned;
return false;
}
} else if (from_kind == EcsFromShared) {
if (ecs_type_contains(
world, table_type, type, match_all, false) ||
!ecs_type_contains(
world, table_type, type, match_all, true))
{
failure_info->reason = EcsMatchOrFromShared;
return false;
}
} else if (from_kind == EcsFromParent) {
if (!(table->flags & EcsTableHasParent)) {
failure_info->reason = EcsMatchOrFromContainer;
return false;
}
if (!components_contains(
world, table_type, type, NULL, match_all))
{
failure_info->reason = EcsMatchOrFromContainer;
return false;
}
}
}
}
return true;
}
/** Match existing tables against system (table is created before system) */
static
void match_tables(
ecs_world_t *world,
ecs_query_t *query)
{
int32_t i, count = ecs_sparse_count(world->store.tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = ecs_sparse_get(
world->store.tables, ecs_table_t, i);
if (ecs_query_match(world, table, query, NULL)) {
add_table(world, query, table);
}
}
order_ranked_tables(world, query);
}
#define ELEM(ptr, size, index) ECS_OFFSET(ptr, size * index)
static
int32_t qsort_partition(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
ecs_entity_t *entities,
void *ptr,
int32_t elem_size,
int32_t lo,
int32_t hi,
ecs_compare_action_t compare)
{
int32_t p = (hi + lo) / 2;
void *pivot = ELEM(ptr, elem_size, p);
ecs_entity_t pivot_e = entities[p];
int32_t i = lo - 1, j = hi + 1;
void *el;
repeat:
{
do {
i ++;
el = ELEM(ptr, elem_size, i);
} while ( compare(entities[i], el, pivot_e, pivot) < 0);
do {
j --;
el = ELEM(ptr, elem_size, j);
} while ( compare(entities[j], el, pivot_e, pivot) > 0);
if (i >= j) {
return j;
}
ecs_table_swap(world, table, data, i, j);
if (p == i) {
pivot = ELEM(ptr, elem_size, j);
pivot_e = entities[j];
} else if (p == j) {
pivot = ELEM(ptr, elem_size, i);
pivot_e = entities[i];
}
goto repeat;
}
}
static
void qsort_array(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
ecs_entity_t *entities,
void *ptr,
int32_t size,
int32_t lo,
int32_t hi,
ecs_compare_action_t compare)
{
if ((hi - lo) < 1) {
return;
}
int32_t p = qsort_partition(
world, table, data, entities, ptr, size, lo, hi, compare);
qsort_array(world, table, data, entities, ptr, size, lo, p, compare);
qsort_array(world, table, data, entities, ptr, size, p + 1, hi, compare);
}
static
void sort_table(
ecs_world_t *world,
ecs_table_t *table,
int32_t column_index,
ecs_compare_action_t compare)
{
ecs_data_t *data = ecs_table_get_data(table);
if (!data || !data->entities) {
/* Nothing to sort */
return;
}
int32_t count = ecs_table_data_count(data);
if (count < 2) {
return;
}
ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t);
void *ptr = NULL;
int32_t size = 0;
if (column_index != -1) {
ecs_column_t *column = &data->columns[column_index];
size = column->size;
ptr = ecs_vector_first_t(column->data, size, column->alignment);
}
qsort_array(world, table, data, entities, ptr, size, 0, count - 1, compare);
}
/* Helper struct for building sorted table ranges */
typedef struct sort_helper_t {
ecs_matched_table_t *table;
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 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 build_sorted_table_range(
ecs_query_t *query,
int32_t start,
int32_t end)
{
ecs_world_t *world = query->world;
ecs_entity_t component = query->sort_on_component;
ecs_compare_action_t compare = query->compare;
/* Fetch data from all matched tables */
ecs_matched_table_t *tables = ecs_vector_first(query->tables, ecs_matched_table_t);
sort_helper_t *helper = ecs_os_malloc((end - start) * ECS_SIZEOF(sort_helper_t));
int i, to_sort = 0;
for (i = start; i < end; i ++) {
ecs_matched_table_t *table_data = &tables[i];
ecs_table_t *table = table_data->iter_data.table;
ecs_data_t *data = ecs_table_get_data(table);
ecs_vector_t *entities;
if (!data || !(entities = data->entities) || !ecs_table_count(table)) {
continue;
}
int32_t index = ecs_type_index_of(table->type, component);
if (index != -1) {
ecs_column_t *column = &data->columns[index];
int16_t size = column->size;
int16_t align = column->alignment;
helper[to_sort].ptr = ecs_vector_first_t(column->data, size, align);
helper[to_sort].elem_size = size;
helper[to_sort].shared = false;
} else if (component) {
/* Find component in prefab */
ecs_entity_t base = ecs_find_entity_in_prefabs(
world, 0, table->type, component, 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, component, EcsComponent);
ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL);
helper[to_sort].ptr = ecs_get_w_entity(world, base, component);
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].table = table_data;
helper[to_sort].entities = ecs_vector_first(entities, ecs_entity_t);
helper[to_sort].row = 0;
helper[to_sort].count = ecs_table_count(table);
to_sort ++;
}
ecs_table_slice_t *cur = 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;
}
}
sort_helper_t *cur_helper = &helper[min];
if (!cur || cur->table != cur_helper->table) {
cur = ecs_vector_add(&query->table_slices, ecs_table_slice_t);
ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL);
cur->table = cur_helper->table;
cur->start_row = cur_helper->row;
cur->count = 1;
} else {
cur->count ++;
}
cur_helper->row ++;
} while (proceed);
ecs_os_free(helper);
}
static
void build_sorted_tables(
ecs_query_t *query)
{
/* Clean previous sorted tables */
ecs_vector_free(query->table_slices);
query->table_slices = NULL;
int32_t i, count = ecs_vector_count(query->tables);
ecs_matched_table_t *tables = ecs_vector_first(query->tables, ecs_matched_table_t);
ecs_matched_table_t *table = NULL;
int32_t start = 0, rank = 0;
for (i = 0; i < count; i ++) {
table = &tables[i];
if (rank != table->rank) {
if (start != i) {
build_sorted_table_range(query, start, i);
start = i;
}
rank = table->rank;
}
}
if (start != i) {
build_sorted_table_range(query, start, i);
}
}
static
bool tables_dirty(
ecs_query_t *query)
{
if (query->needs_reorder) {
order_ranked_tables(query->world, query);
}
int32_t i, count = ecs_vector_count(query->tables);
ecs_matched_table_t *tables = ecs_vector_first(query->tables,
ecs_matched_table_t);
bool is_dirty = false;
for (i = 0; i < count; i ++) {
ecs_matched_table_t *table_data = &tables[i];
ecs_table_t *table = table_data->iter_data.table;
if (!table_data->monitor) {
table_data->monitor = ecs_table_get_monitor(table);
is_dirty = true;
}
int32_t *dirty_state = ecs_table_get_dirty_state(table);
int32_t t, type_count = table->column_count;
for (t = 0; t < type_count + 1; t ++) {
is_dirty = is_dirty || (dirty_state[t] != table_data->monitor[t]);
}
}
is_dirty = is_dirty || (query->match_count != query->prev_match_count);
return is_dirty;
}
static
void tables_reset_dirty(
ecs_query_t *query)
{
query->prev_match_count = query->match_count;
int32_t i, count = ecs_vector_count(query->tables);
ecs_matched_table_t *tables = ecs_vector_first(
query->tables, ecs_matched_table_t);
for (i = 0; i < count; i ++) {
ecs_matched_table_t *table_data = &tables[i];
ecs_table_t *table = table_data->iter_data.table;
if (!table_data->monitor) {
/* If one table doesn't have a monitor, none of the tables will have
* a monitor, so early out. */
return;
}
int32_t *dirty_state = ecs_table_get_dirty_state(table);
int32_t t, type_count = table->column_count;
for (t = 0; t < type_count + 1; t ++) {
table_data->monitor[t] = dirty_state[t];
}
}
}
static
void sort_tables(
ecs_world_t *world,
ecs_query_t *query)
{
ecs_compare_action_t compare = query->compare;
if (!compare) {
return;
}
ecs_entity_t sort_on_component = query->sort_on_component;
/* Iterate over active tables. Don't bother with inactive tables, since
* they're empty */
int32_t i, count = ecs_vector_count(query->tables);
ecs_matched_table_t *tables = ecs_vector_first(
query->tables, ecs_matched_table_t);
bool tables_sorted = false;
for (i = 0; i < count; i ++) {
ecs_matched_table_t *table_data = &tables[i];
ecs_table_t *table = table_data->iter_data.table;
/* If no monitor had been created for the table yet, create it now */
bool is_dirty = false;
if (!table_data->monitor) {
table_data->monitor = ecs_table_get_monitor(table);
/* A new table is always dirty */
is_dirty = true;
}
int32_t *dirty_state = ecs_table_get_dirty_state(table);
is_dirty = is_dirty || (dirty_state[0] != table_data->monitor[0]);
int32_t index = -1;
if (sort_on_component) {
/* Get index of sorted component. We only care if the component we're
* sorting on has changed or if entities have been added / re(moved) */
index = ecs_type_index_of(table->type, sort_on_component);
if (index != -1) {
ecs_assert(index < ecs_vector_count(table->type), ECS_INTERNAL_ERROR, NULL);
is_dirty = is_dirty || (dirty_state[index + 1] != table_data->monitor[index + 1]);
} else {
/* Table does not contain component which means the sorted
* component is shared. Table does not need to be sorted */
continue;
}
}
/* Check both if entities have moved (element 0) or if the component
* we're sorting on has changed (index + 1) */
if (is_dirty) {
/* Sort the table */
sort_table(world, table, index, compare);
tables_sorted = true;
}
}
if (tables_sorted || query->match_count != query->prev_match_count) {
build_sorted_tables(query);
query->match_count ++; /* Increase version if tables changed */
}
}
static
bool has_refs(
ecs_sig_t *sig)
{
int32_t i, count = ecs_vector_count(sig->columns);
ecs_sig_column_t *columns = ecs_vector_first(sig->columns, ecs_sig_column_t);
for (i = 0; i < count; i ++) {
ecs_sig_from_kind_t from_kind = columns[i].from_kind;
if (columns[i].oper_kind == EcsOperNot && from_kind == EcsFromEmpty) {
/* Special case: if oper kind is Not and the query contained a
* shared expression, the expression is translated to FromId to
* prevent resolving the ref */
return true;
} else if (from_kind != EcsFromAny && from_kind != EcsFromEmpty) {
/* If the component is not from the entity being iterated over, and
* the column is not just passing an id, it must be a reference to
* another entity. */
return true;
}
}
return false;
}
static
bool has_traits(
ecs_sig_t *sig)
{
int32_t i, count = ecs_vector_count(sig->columns);
ecs_sig_column_t *columns = ecs_vector_first(sig->columns, ecs_sig_column_t);
for (i = 0; i < count; i ++) {
if (is_column_trait(&columns[i])) {
return true;
}
}
return false;
}
static
void register_monitors(
ecs_world_t *world,
ecs_query_t *query)
{
ecs_vector_each(query->sig.columns, ecs_sig_column_t, column, {
/* If component is requested with CASCADE source register component as a
* parent monitor. Parent monitors keep track of whether an entity moved
* in the hierarchy, which potentially requires the query to reorder its
* tables.
* Also register a regular component monitor for EcsCascade columns.
* This ensures that when the component used in the CASCADE column
* is added or removed tables are updated accordingly*/
if (column->from_kind == EcsCascade) {
if (column->oper_kind != EcsOperOr) {
ecs_component_monitor_register(
&world->parent_monitors, column->is.component, query);
ecs_component_monitor_register(
&world->component_monitors, column->is.component, query);
} else {
ecs_vector_each(column->is.type, ecs_entity_t, e_ptr, {
ecs_component_monitor_register(
&world->parent_monitors, *e_ptr, query);
ecs_component_monitor_register(
&world->component_monitors, *e_ptr, query);
});
}
/* FromSelf also requires registering a monitor, as FromSelf columns can
* be matched with prefabs. The only column kinds that do not require
* registering a monitor are FromOwned and FromNothing. */
} else if (column->from_kind == EcsFromAny ||
column->from_kind == EcsFromShared ||
column->from_kind == EcsFromEntity ||
column->from_kind == EcsFromParent)
{
if (column->oper_kind != EcsOperOr) {
ecs_component_monitor_register(
&world->component_monitors, column->is.component, query);
} else {
ecs_vector_each(column->is.type, ecs_entity_t, e_ptr, {
ecs_component_monitor_register(
&world->component_monitors, *e_ptr, query);
});
}
}
});
}
static
void process_signature(
ecs_world_t *world,
ecs_query_t *query)
{
int i, count = ecs_vector_count(query->sig.columns);
ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t);
for (i = 0; i < count; i ++) {
ecs_sig_column_t *column = &columns[i];
ecs_sig_oper_kind_t op = column->oper_kind;
ecs_sig_from_kind_t from = column->from_kind;
ecs_sig_inout_kind_t inout = column->inout_kind;
if (inout != EcsIn) {
query->flags |= EcsQueryHasOutColumns;
}
if (op == EcsOperOptional) {
query->flags |= EcsQueryHasOptional;
}
if (!(query->flags & EcsQueryMatchDisabled)) {
if (op == EcsOperOr) {
/* If the signature explicitly indicates interest in EcsDisabled,
* signal that disabled entities should be matched. By default,
* disabled entities are not matched. */
if (ecs_type_owns_entity(
world, column->is.type, EcsDisabled, true))
{
query->flags |= EcsQueryMatchDisabled;
}
} else if (op == EcsOperAnd || op == EcsOperOptional) {
if (column->is.component == EcsDisabled) {
query->flags |= EcsQueryMatchDisabled;
}
}
}
if (!(query->flags & EcsQueryMatchPrefab)) {
if (op == EcsOperOr) {
/* If the signature explicitly indicates interest in EcsPrefab,
* signal that disabled entities should be matched. By default,
* prefab entities are not matched. */
if (ecs_type_owns_entity(
world, column->is.type, EcsPrefab, true))
{
query->flags |= EcsQueryMatchPrefab;
}
} else if (op == EcsOperAnd || op == EcsOperOptional) {
if (column->is.component == EcsPrefab) {
query->flags |= EcsQueryMatchPrefab;
}
}
}
if (from == EcsFromAny ||
from == EcsFromOwned ||
from == EcsFromShared ||
from == EcsFromParent)
{
query->flags |= EcsQueryNeedsTables;
}
if (from == EcsCascade) {
query->cascade_by = i + 1;
query->rank_on_component = column->is.component;
}
if (from == EcsFromEntity) {
ecs_assert(column->source != 0, ECS_INTERNAL_ERROR, NULL);
ecs_set_watch(world, column->source);
}
}
query->flags |= (ecs_flags32_t)(has_refs(&query->sig) * EcsQueryHasRefs);
query->flags |= (ecs_flags32_t)(has_traits(&query->sig) * EcsQueryHasTraits);
if (!(query->flags & EcsQueryIsSubquery)) {
register_monitors(world, query);
}
}
static
bool match_table(
ecs_world_t *world,
ecs_query_t *query,
ecs_table_t *table)
{
if (ecs_query_match(world, table, query, NULL)) {
add_table(world, query, table);
return true;
}
return false;
}
/* Move table from empty to non-empty list, or vice versa */
static
int32_t move_table(
ecs_query_t *query,
ecs_table_t *table,
int32_t index,
ecs_vector_t **dst_array,
ecs_vector_t *src_array,
bool activate)
{
(void)table;
int32_t new_index = 0;
int32_t last_src_index = ecs_vector_count(src_array) - 1;
ecs_assert(last_src_index >= 0, ECS_INTERNAL_ERROR, NULL);
ecs_matched_table_t *mt = ecs_vector_last(src_array, ecs_matched_table_t);
/* The last table of the source array will be moved to the location of the
* table to move, do some bookkeeping to keep things consistent. */
if (last_src_index) {
ecs_table_indices_t *ti = ecs_map_get(query->table_indices,
ecs_table_indices_t, mt->iter_data.table->id);
int i, count = ti->count;
for (i = 0; i < count; i ++) {
int32_t old_index = ti->indices[i];
if (activate) {
if (old_index >= 0) {
/* old_index should be negative if activate is true, since
* we're moving from the empty list to the non-empty list.
* However, if the last table in the source array is also
* the table being moved, this can happen. */
ecs_assert(table == mt->iter_data.table,
ECS_INTERNAL_ERROR, NULL);
continue;
}
/* If activate is true, src = the empty list, and index should
* be negative. */
old_index = old_index * -1 - 1; /* Normalize */
}
/* Ensure to update correct index, as there can be more than one */
if (old_index == last_src_index) {
if (activate) {
ti->indices[i] = index * -1 - 1;
} else {
ti->indices[i] = index;
}
break;
}
}
/* If the src array contains tables, there must be a table that will get
* moved. */
ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL);
} else {
/* If last_src_index is 0, the table to move was the only table in the
* src array, so no other administration needs to be updated. */
}
/* Actually move the table. Only move from src to dst if we have a
* dst_array, otherwise just remove it from src. */
if (dst_array) {
new_index = ecs_vector_count(*dst_array);
ecs_vector_move_index(dst_array, src_array, ecs_matched_table_t, index);
/* Make sure table is where we expect it */
mt = ecs_vector_last(*dst_array, ecs_matched_table_t);
ecs_assert(mt->iter_data.table == table, ECS_INTERNAL_ERROR, NULL);
ecs_assert(ecs_vector_count(*dst_array) == (new_index + 1),
ECS_INTERNAL_ERROR, NULL);
} else {
ecs_vector_remove_index(src_array, ecs_matched_table_t, index);
}
/* Ensure that src array has now one element less */
ecs_assert(ecs_vector_count(src_array) == last_src_index,
ECS_INTERNAL_ERROR, NULL);
/* Return new index for table */
if (activate) {
/* Table is now active, index is positive */
return new_index;
} else {
/* Table is now inactive, index is negative */
return new_index * -1 - 1;
}
}
/** Table activation happens when a table was or becomes empty. Deactivated
* tables are not considered by the system in the main loop. */
static
void activate_table(
ecs_world_t *world,
ecs_query_t *query,
ecs_table_t *table,
bool active)
{
(void)world;
ecs_vector_t *src_array, *dst_array;
int32_t activated = 0;
if (active) {
src_array = query->empty_tables;
dst_array = query->tables;
} else {
src_array = query->tables;
dst_array = query->empty_tables;
}
ecs_table_indices_t *ti = ecs_map_get(
query->table_indices, ecs_table_indices_t, table->id);
if (ti) {
int32_t i, count = ti->count;
for (i = 0; i < count; i ++) {
int32_t index = ti->indices[i];
if (index < 0) {
if (!active) {
/* If table is already inactive, no need to move */
continue;
}
index = index * -1 - 1;
} else {
if (active) {
/* If table is already active, no need to move */
continue;
}
}
ecs_matched_table_t *mt = ecs_vector_get(
src_array, ecs_matched_table_t, index);
ecs_assert(mt->iter_data.table == table, ECS_INTERNAL_ERROR, NULL);
(void)mt;
activated ++;
ti->indices[i] = move_table(
query, table, index, &dst_array, src_array, active);
}
if (activated) {
/* Activate system if registered with query */
#ifdef FLECS_SYSTEMS_H
if (query->system) {
int32_t dst_count = ecs_vector_count(dst_array);
if (active) {
if (dst_count == 1) {
ecs_system_activate(world, query->system, true, NULL);
}
} else if (ecs_vector_count(src_array) == 0) {
ecs_system_activate(world, query->system, false, NULL);
}
}
#endif
}
if (active) {
query->tables = dst_array;
} else {
query->empty_tables = dst_array;
}
}
if (!activated) {
/* Received an activate event for a table we're not matched with. This
* can only happen if this is a subquery */
ecs_assert((query->flags & EcsQueryIsSubquery) != 0,
ECS_INTERNAL_ERROR, NULL);
return;
}
/* Signal query it needs to reorder tables. Doing this in place could slow
* down scenario's where a large number of tables is matched with an ordered
* query. Since each table would trigger the activate signal, there would be
* as many sorts as added tables, vs. only one when ordering happens when an
* iterator is obtained. */
query->needs_reorder = true;
}
static
void 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;
/* Iterate matched tables, match them with subquery */
ecs_matched_table_t *tables = ecs_vector_first(parent->tables, ecs_matched_table_t);
int32_t i, count = ecs_vector_count(parent->tables);
for (i = 0; i < count; i ++) {
ecs_matched_table_t *table = &tables[i];
match_table(world, subquery, table->iter_data.table);
activate_table(world, subquery, table->iter_data.table, true);
}
/* Do the same for inactive tables */
tables = ecs_vector_first(parent->empty_tables, ecs_matched_table_t);
count = ecs_vector_count(parent->empty_tables);
for (i = 0; i < count; i ++) {
ecs_matched_table_t *table = &tables[i];
match_table(world, subquery, table->iter_data.table);
}
}
static
void 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];
ecs_query_notify(world, sub, &sub_event);
}
}
}
static
void free_matched_table(
ecs_matched_table_t *table)
{
ecs_os_free(table->iter_data.columns);
ecs_os_free(table->iter_data.components);
ecs_os_free((ecs_vector_t**)table->iter_data.types);
ecs_os_free(table->iter_data.references);
ecs_os_free(table->sparse_columns);
ecs_os_free(table->bitset_columns);
ecs_os_free(table->monitor);
}
/** Check if a table was matched with the system */
static
ecs_table_indices_t* get_table_indices(
ecs_query_t *query,
ecs_table_t *table)
{
return ecs_map_get(query->table_indices, ecs_table_indices_t, table->id);
}
static
void resolve_cascade_container(
ecs_world_t *world,
ecs_query_t *query,
ecs_table_indices_t *ti,
ecs_type_t table_type)
{
int32_t i, count = ti->count;
for (i = 0; i < count; i ++) {
int32_t table_data_index = ti->indices[i];
ecs_matched_table_t *table_data;
if (table_data_index >= 0) {
table_data = ecs_vector_get(
query->tables, ecs_matched_table_t, table_data_index);
} else {
table_data = ecs_vector_get(
query->empty_tables, ecs_matched_table_t, table_data_index);
}
ecs_assert(table_data->iter_data.references != 0, ECS_INTERNAL_ERROR, NULL);
/* Obtain reference index */
int32_t *column_indices = table_data->iter_data.columns;
int32_t column = query->cascade_by - 1;
int32_t ref_index = -column_indices[column] - 1;
/* Obtain pointer to the reference data */
ecs_ref_t *references = table_data->iter_data.references;
ecs_ref_t *ref = &references[ref_index];
ecs_assert(ref->component == get_cascade_component(query),
ECS_INTERNAL_ERROR, NULL);
/* Resolve container entity */
ecs_entity_t container = ecs_find_in_type(
world, table_type, ref->component, ECS_CHILDOF);
/* If container was found, update the reference */
if (container) {
references[ref_index].entity = container;
ecs_get_ref_w_entity(
world, &references[ref_index], container,
ref->component);
} else {
references[ref_index].entity = 0;
}
}
}
/* Remove table */
static
void remove_table(
ecs_query_t *query,
ecs_table_t *table,
ecs_vector_t *tables,
int32_t index,
bool empty)
{
ecs_matched_table_t *mt = ecs_vector_get(
tables, ecs_matched_table_t, index);
if (!mt) {
/* Query was notified of a table it doesn't match with, this can only
* happen if query is a subquery. */
ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL);
return;
}
ecs_assert(mt->iter_data.table == table, ECS_INTERNAL_ERROR, NULL);
(void)table;
/* Free table before moving, as the move will cause another table to occupy
* the memory of mt */
free_matched_table(mt);
move_table(query, mt->iter_data.table, index, NULL, tables, empty);
}
static
void unmatch_table(
ecs_query_t *query,
ecs_table_t *table,
ecs_table_indices_t *ti)
{
if (!ti) {
ti = get_table_indices(query, table);
if (!ti) {
return;
}
}
int32_t i, count = ti->count;
for (i = 0; i < count; i ++) {
int32_t index = ti->indices[i];
if (index < 0) {
index = index * -1 - 1;
remove_table(query, table, query->empty_tables, index, true);
} else {
remove_table(query, table, query->tables, index, false);
}
}
ecs_os_free(ti->indices);
ecs_map_remove(query->table_indices, table->id);
}
static
void rematch_table(
ecs_world_t *world,
ecs_query_t *query,
ecs_table_t *table)
{
ecs_table_indices_t *match = get_table_indices(query, table);
if (ecs_query_match(world, table, query, NULL)) {
/* If the table matches, and it is not currently matched, add */
if (match == NULL) {
add_table(world, query, table);
/* If table still matches and has cascade column, reevaluate the
* sources of references. This may have changed in case
* components were added/removed to container entities */
} else if (query->cascade_by) {
resolve_cascade_container(
world, query, match, table->type);
/* If query has optional columns, it is possible that a column that
* previously had data no longer has data, or vice versa. Do a full
* rematch to make sure data is consistent. */
} else if (query->flags & EcsQueryHasOptional) {
unmatch_table(query, table, match);
if (!(query->flags & EcsQueryIsSubquery)) {
ecs_table_notify(world, table, &(ecs_table_event_t){
.kind = EcsTableQueryUnmatch,
.query = query
});
}
add_table(world, query, table);
}
} else {
/* Table no longer matches, remove */
if (match != NULL) {
unmatch_table(query, table, match);
if (!(query->flags & EcsQueryIsSubquery)) {
ecs_table_notify(world, table, &(ecs_table_event_t){
.kind = EcsTableQueryUnmatch,
.query = query
});
}
notify_subqueries(world, query, &(ecs_query_event_t){
.kind = EcsQueryTableUnmatch,
.table = table
});
}
}
}
/* Rematch system with tables after a change happened to a watched entity */
static
void rematch_tables(
ecs_world_t *world,
ecs_query_t *query,
ecs_query_t *parent_query)
{
if (parent_query) {
ecs_matched_table_t *tables = ecs_vector_first(parent_query->tables, ecs_matched_table_t);
int32_t i, count = ecs_vector_count(parent_query->tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = tables[i].iter_data.table;
rematch_table(world, query, table);
}
tables = ecs_vector_first(parent_query->empty_tables, ecs_matched_table_t);
count = ecs_vector_count(parent_query->empty_tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = tables[i].iter_data.table;
rematch_table(world, query, table);
}
} else {
ecs_sparse_t *tables = world->store.tables;
int32_t i, count = ecs_sparse_count(tables);
for (i = 0; i < count; i ++) {
/* Is the system currently matched with the table? */
ecs_table_t *table = ecs_sparse_get(tables, ecs_table_t, i);
rematch_table(world, query, table);
}
}
group_tables(world, query);
order_ranked_tables(world, query);
/* Enable/disable system if constraints are (not) met. If the system is
* already dis/enabled this operation has no side effects. */
if (query->system) {
if (ecs_sig_check_constraints(world, &query->sig)) {
ecs_remove_entity(world, query->system, EcsDisabledIntern);
} else {
ecs_add_entity(world, query->system, EcsDisabledIntern);
}
}
}
static
void 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_index(parent->subqueries, ecs_query_t*, i);
}
/* -- Private API -- */
void ecs_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 (match_table(world, query, event->table)) {
if (query->subqueries) {
notify_subqueries(world, query, event);
}
}
notify = false;
break;
case EcsQueryTableUnmatch:
/* Deletion of table */
unmatch_table(query, event->table, NULL);
break;
case EcsQueryTableRematch:
/* Rematch tables of query */
rematch_tables(world, query, event->parent_query);
break;
case EcsQueryTableEmpty:
/* Table is empty, deactivate */
activate_table(world, query, event->table, false);
break;
case EcsQueryTableNonEmpty:
/* Table is non-empty, activate */
activate_table(world, query, event->table, true);
break;
case EcsQueryOrphan:
ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL);
query->flags |= EcsQueryIsOrphaned;
query->parent = NULL;
break;
}
if (notify) {
notify_subqueries(world, query, event);
}
}
/* -- Public API -- */
static
ecs_query_t* ecs_query_new_w_sig_intern(
ecs_world_t *world,
ecs_entity_t system,
ecs_sig_t *sig,
bool is_subquery)
{
ecs_query_t *result = ecs_os_calloc(sizeof(ecs_query_t));
result->world = world;
result->sig = *sig;
result->table_indices = ecs_map_new(ecs_table_indices_t, 0);
result->tables = ecs_vector_new(ecs_matched_table_t, 0);
result->empty_tables = ecs_vector_new(ecs_matched_table_t, 0);
result->system = system;
result->prev_match_count = -1;
if (is_subquery) {
result->flags |= EcsQueryIsSubquery;
}
process_signature(world, result);
ecs_trace_2("query #[green]%s#[reset] created with expression #[red]%s",
query_name(world, result), result->sig.expr);
ecs_log_push();
if (!is_subquery) {
/* Register query with world */
ecs_query_t **elem = ecs_vector_add(&world->queries, ecs_query_t*);
*elem = result;
if (result->flags & EcsQueryNeedsTables) {
if (ecs_has_entity(world, system, EcsMonitor)) {
result->flags |= EcsQueryMonitor;
}
if (ecs_has_entity(world, system, EcsOnSet)) {
result->flags |= EcsQueryOnSet;
}
if (ecs_has_entity(world, system, EcsUnSet)) {
result->flags |= EcsQueryUnSet;
}
match_tables(world, result);
} else {
/* Add stub table that resolves references (if any) so everything is
* preprocessed when the query is evaluated. */
add_table(world, result, NULL);
}
}
if (result->cascade_by) {
result->group_table = rank_by_depth;
}
ecs_log_pop();
/* Make sure application can't try to free sig resources */
*sig = (ecs_sig_t){ 0 };
return result;
}
ecs_query_t* ecs_query_new_w_sig(
ecs_world_t *world,
ecs_entity_t system,
ecs_sig_t *sig)
{
return ecs_query_new_w_sig_intern(world, system, sig, false);
}
ecs_query_t* ecs_query_new(
ecs_world_t *world,
const char *expr)
{
ecs_sig_t sig = { 0 };
ecs_sig_init(world, NULL, expr, &sig);
return ecs_query_new_w_sig(world, 0, &sig);
}
ecs_query_t* ecs_subquery_new(
ecs_world_t *world,
ecs_query_t *parent,
const char *expr)
{
ecs_sig_t sig = { 0 };
ecs_sig_init(world, NULL, expr, &sig);
ecs_query_t *result = ecs_query_new_w_sig_intern(world, 0, &sig, true);
result->parent = parent;
add_subquery(world, parent, result);
return result;
}
ecs_sig_t* ecs_query_get_sig(
ecs_query_t *query)
{
return &query->sig;
}
void ecs_query_free(
ecs_query_t *query)
{
ecs_world_t *world = query->world;
if ((query->flags & EcsQueryIsSubquery) &&
!(query->flags & EcsQueryIsOrphaned))
{
remove_subquery(query->parent, query);
}
notify_subqueries(world, query, &(ecs_query_event_t){
.kind = EcsQueryOrphan
});
ecs_vector_each(query->empty_tables, ecs_matched_table_t, table, {
if (!(query->flags & EcsQueryIsSubquery)) {
ecs_table_notify(world, table->iter_data.table, &(ecs_table_event_t){
.kind = EcsTableQueryUnmatch,
.query = query
});
}
free_matched_table(table);
});
ecs_vector_each(query->tables, ecs_matched_table_t, table, {
if (!(query->flags & EcsQueryIsSubquery)) {
ecs_table_notify(world, table->iter_data.table, &(ecs_table_event_t){
.kind = EcsTableQueryUnmatch,
.query = query
});
}
free_matched_table(table);
});
ecs_map_iter_t it = ecs_map_iter(query->table_indices);
ecs_table_indices_t *ti;
while ((ti = ecs_map_next(&it, ecs_table_indices_t, NULL))) {
ecs_os_free(ti->indices);
}
ecs_map_free(query->table_indices);
ecs_vector_free(query->subqueries);
ecs_vector_free(query->tables);
ecs_vector_free(query->empty_tables);
ecs_vector_free(query->table_slices);
ecs_sig_deinit(&query->sig);
/* Find query in vector */
if (!(query->flags & EcsQueryIsSubquery) && world->queries) {
int32_t index = -1;
ecs_vector_each(world->queries, ecs_query_t*, q_ptr, {
if (*q_ptr == query) {
index = q_ptr_i;
}
});
ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL);
ecs_vector_remove_index(world->queries, ecs_query_t*, index);
}
ecs_os_free(query);
}
/* Create query iterator */
ecs_iter_t ecs_query_iter_page(
ecs_query_t *query,
int32_t offset,
int32_t limit)
{
ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL);
ecs_world_t *world = query->world;
if (query->needs_reorder) {
order_ranked_tables(world, query);
}
sort_tables(world, query);
if (!world->in_progress && query->flags & EcsQueryHasRefs) {
ecs_eval_component_monitors(world);
}
tables_reset_dirty(query);
int32_t table_count;
if (query->table_slices) {
table_count = ecs_vector_count(query->table_slices);
} else {
table_count = ecs_vector_count(query->tables);
}
ecs_query_iter_t it = {
.page_iter = {
.offset = offset,
.limit = limit,
.remaining = limit
},
.index = 0,
};
return (ecs_iter_t){
.world = world,
.query = query,
.column_count = ecs_vector_count(query->sig.columns),
.table_count = table_count,
.inactive_table_count = ecs_vector_count(query->empty_tables),
.iter.query = it
};
}
ecs_iter_t ecs_query_iter(
ecs_query_t *query)
{
return ecs_query_iter_page(query, 0, 0);
}
void ecs_query_set_iter(
ecs_world_t *world,
ecs_query_t *query,
ecs_iter_t *it,
int32_t table_index,
int32_t row,
int32_t count)
{
ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL);
ecs_matched_table_t *table_data = ecs_vector_get(
query->tables, ecs_matched_table_t, table_index);
ecs_assert(table_data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_table_t *table = table_data->iter_data.table;
ecs_data_t *data = ecs_table_get_data(table);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_entity_t *entity_buffer = ecs_vector_first(data->entities, ecs_entity_t);
it->entities = &entity_buffer[row];
it->world = world;
it->query = query;
it->column_count = ecs_vector_count(query->sig.columns);
it->table_count = 1;
it->inactive_table_count = 0;
it->table_columns = data->columns;
it->table = &table_data->iter_data;
it->offset = row;
it->count = count;
it->total_count = count;
}
static
int ecs_page_iter_next(
ecs_page_iter_t *it,
ecs_page_cursor_t *cur)
{
int32_t offset = it->offset;
int32_t limit = it->limit;
if (!(offset || limit)) {
return cur->count == 0;
}
int32_t count = cur->count;
int32_t remaining = it->remaining;
if (offset) {
if (offset > count) {
/* No entities to iterate in current table */
it->offset -= count;
return 1;
} else {
cur->first += offset;
count = cur->count -= offset;
it->offset = 0;
}
}
if (remaining) {
if (remaining > count) {
it->remaining -= count;
} else {
count = cur->count = remaining;
it->remaining = 0;
}
} else if (limit) {
/* Limit hit: no more entities left to iterate */
return -1;
}
return count == 0;
}
static
int find_smallest_column(
ecs_table_t *table,
ecs_matched_table_t *table_data,
ecs_vector_t *sparse_columns)
{
ecs_sparse_column_t *sparse_column_array =
ecs_vector_first(sparse_columns, ecs_sparse_column_t);
int32_t i, count = ecs_vector_count(sparse_columns);
int32_t min = INT_MAX, index = 0;
for (i = 0; i < count; i ++) {
/* The array with sparse queries for the matched table */
ecs_sparse_column_t *sparse_column = &sparse_column_array[i];
/* Pointer to the switch column struct of the table */
ecs_sw_column_t *sc = sparse_column->sw_column;
/* If the sparse column pointer hadn't been retrieved yet, do it now */
if (!sc) {
/* Get the table column index from the signature column index */
int32_t table_column_index = table_data->iter_data.columns[
sparse_column->signature_column_index];
/* Translate the table column index to switch column index */
table_column_index -= table->sw_column_offset;
ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL);
/* Get the sparse column */
ecs_data_t *data = ecs_table_get_data(table);
sc = sparse_column->sw_column =
&data->sw_columns[table_column_index - 1];
}
/* Find the smallest column */
ecs_switch_t *sw = sc->data;
int32_t case_count = ecs_switch_case_count(sw, sparse_column->sw_case);
if (case_count < min) {
min = case_count;
index = i + 1;
}
}
return index;
}
static
int sparse_column_next(
ecs_table_t *table,
ecs_matched_table_t *matched_table,
ecs_vector_t *sparse_columns,
ecs_query_iter_t *iter,
ecs_page_cursor_t *cur)
{
bool first_iteration = false;
int32_t sparse_smallest;
if (!(sparse_smallest = iter->sparse_smallest)) {
sparse_smallest = iter->sparse_smallest = find_smallest_column(
table, matched_table, sparse_columns);
first_iteration = true;
}
sparse_smallest -= 1;
ecs_sparse_column_t *columns = ecs_vector_first(
sparse_columns, ecs_sparse_column_t);
ecs_sparse_column_t *column = &columns[sparse_smallest];
ecs_switch_t *sw, *sw_smallest = column->sw_column->data;
ecs_entity_t case_smallest = column->sw_case;
/* Find next entity to iterate in sparse column */
int32_t first;
if (first_iteration) {
first = ecs_switch_first(sw_smallest, case_smallest);
} else {
first = ecs_switch_next(sw_smallest, iter->sparse_first);
}
if (first == -1) {
goto done;
}
/* Check if entity matches with other sparse columns, if any */
int32_t i, count = ecs_vector_count(sparse_columns);
do {
for (i = 0; i < count; i ++) {
if (i == sparse_smallest) {
/* Already validated this one */
continue;
}
column = &columns[i];
sw = column->sw_column->data;
if (ecs_switch_get(sw, first) != column->sw_case) {
first = ecs_switch_next(sw_smallest, first);
if (first == -1) {
goto done;
}
}
}
} while (i != count);
cur->first = iter->sparse_first = first;
cur->count = 1;
return 0;
done:
/* Iterated all elements in the sparse list, we should move to the
* next matched table. */
iter->sparse_smallest = 0;
iter->sparse_first = 0;
return -1;
}
#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF)
static
int bitset_column_next(
ecs_table_t *table,
ecs_vector_t *bitset_columns,
ecs_query_iter_t *iter,
ecs_page_cursor_t *cur)
{
/* 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_vector_count(bitset_columns);
ecs_bitset_column_t *columns = ecs_vector_first(
bitset_columns, ecs_bitset_column_t);
int32_t bs_offset = table->bs_column_offset;
int32_t first = iter->bitset_first;
int32_t last = 0;
for (i = 0; i < count; i ++) {
ecs_bitset_column_t *column = &columns[i];
ecs_bs_column_t *bs_column = columns[i].bs_column;
if (!bs_column) {
ecs_data_t *data = table->data;
int32_t index = column->column_index;
ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL);
bs_column = &data->bs_columns[index - bs_offset];
columns[i].bs_column = bs_column;
}
ecs_bitset_t *bs = &bs_column->data;
int32_t bs_elem_count = bs->count;
int32_t bs_block = first >> 6;
int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1;
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) {
elem_count = bs_elem_count;
}
cur->first = first;
cur->count = elem_count;
iter->bitset_first = first;
}
/* Keep track of last processed element for iteration */
iter->bitset_first = last;
return 0;
done:
return -1;
}
static
void mark_columns_dirty(
ecs_query_t *query,
ecs_matched_table_t *table_data)
{
ecs_table_t *table = table_data->iter_data.table;
if (table && table->dirty_state) {
int32_t i, count = ecs_vector_count(query->sig.columns);
ecs_sig_column_t *columns = ecs_vector_first(
query->sig.columns, ecs_sig_column_t);
for (i = 0; i < count; i ++) {
if (columns[i].inout_kind != EcsIn) {
int32_t table_column = table_data->iter_data.columns[i];
if (table_column > 0) {
table->dirty_state[table_column] ++;
}
}
}
}
}
/* Return next table */
bool ecs_query_next(
ecs_iter_t *it)
{
ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_query_iter_t *iter = &it->iter.query;
ecs_page_iter_t *piter = &iter->page_iter;
ecs_world_t *world = it->world;
ecs_query_t *query = it->query;
ecs_get_stage(&world);
ecs_table_slice_t *slice = ecs_vector_first(
query->table_slices, ecs_table_slice_t);
ecs_matched_table_t *tables = ecs_vector_first(
query->tables, ecs_matched_table_t);
ecs_assert(!slice || query->compare, ECS_INTERNAL_ERROR, NULL);
ecs_page_cursor_t cur;
int32_t table_count = it->table_count;
int32_t prev_count = it->total_count;
int i;
for (i = iter->index; i < table_count; i ++) {
ecs_matched_table_t *table_data = slice ? slice[i].table : &tables[i];
ecs_table_t *table = table_data->iter_data.table;
ecs_data_t *data = NULL;
iter->index = i + 1;
if (table) {
ecs_vector_t *bitset_columns = table_data->bitset_columns;
ecs_vector_t *sparse_columns = table_data->sparse_columns;
data = ecs_table_get_data(table);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
it->table_columns = data->columns;
if (slice) {
cur.first = slice[i].start_row;
cur.count = slice[i].count;
} else {
cur.first = 0;
cur.count = ecs_table_count(table);
}
if (cur.count) {
if (bitset_columns) {
if (bitset_column_next(table, bitset_columns, iter,
&cur) == -1)
{
/* No more enabled components for table */
continue;
} else {
iter->index = i;
}
}
if (sparse_columns) {
if (sparse_column_next(table, table_data,
sparse_columns, iter, &cur) == -1)
{
/* No more elements in sparse column */
continue;
} else {
iter->index = i;
}
}
int ret = ecs_page_iter_next(piter, &cur);
if (ret < 0) {
return false;
} else if (ret > 0) {
continue;
}
} else {
continue;
}
ecs_entity_t *entity_buffer = ecs_vector_first(
data->entities, ecs_entity_t);
it->entities = &entity_buffer[cur.first];
it->offset = cur.first;
it->count = cur.count;
it->total_count = cur.count;
}
it->table = &table_data->iter_data;
it->frame_offset += prev_count;
if (query->flags & EcsQueryHasOutColumns) {
if (table) {
mark_columns_dirty(query, table_data);
}
}
return true;
}
return false;
}
bool ecs_query_next_w_filter(
ecs_iter_t *iter,
const ecs_filter_t *filter)
{
ecs_table_t *table;
do {
if (!ecs_query_next(iter)) {
return false;
}
table = iter->table->table;
} while (filter && !ecs_table_match_filter(iter->world, table, filter));
return true;
}
bool ecs_query_next_worker(
ecs_iter_t *it,
int32_t current,
int32_t total)
{
int32_t per_worker, first, prev_offset = it->offset;
do {
if (!ecs_query_next(it)) {
return false;
}
int32_t count = it->count;
per_worker = count / total;
first = per_worker * current;
count -= per_worker * total;
if (count) {
if (current < count) {
per_worker ++;
first += current;
} else {
first += count;
}
}
if (!per_worker && !(it->query->flags & EcsQueryNeedsTables)) {
if (current == 0) {
return true;
} else {
return false;
}
}
} while (!per_worker);
it->frame_offset -= prev_offset;
it->count = per_worker;
it->offset += first;
it->entities = &it->entities[first];
it->frame_offset += first;
return true;
}
void ecs_query_order_by(
ecs_world_t *world,
ecs_query_t *query,
ecs_entity_t sort_component,
ecs_compare_action_t compare)
{
ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL);
ecs_assert(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL);
query->sort_on_component = sort_component;
query->compare = compare;
ecs_vector_free(query->table_slices);
query->table_slices = NULL;
sort_tables(world, query);
if (!query->table_slices) {
build_sorted_tables(query);
}
}
void ecs_query_group_by(
ecs_world_t *world,
ecs_query_t *query,
ecs_entity_t sort_component,
ecs_rank_type_action_t group_table_action)
{
ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL);
ecs_assert(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL);
query->rank_on_component = sort_component;
query->group_table = group_table_action;
group_tables(world, query);
order_ranked_tables(world, query);
build_sorted_tables(query);
}
bool ecs_query_changed(
ecs_query_t *query)
{
ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL);
return tables_dirty(query);
}
bool ecs_query_orphaned(
ecs_query_t *query)
{
return query->flags & EcsQueryIsOrphaned;
}
const EcsComponent* ecs_component_from_id(
ecs_world_t *world,
ecs_entity_t e)
{
ecs_entity_t trait = 0;
/* If this is a trait, get the trait component from the identifier */
if (ECS_HAS_ROLE(e, TRAIT)) {
trait = e;
e = e & ECS_COMPONENT_MASK;
e = ecs_entity_t_hi(e);
}
const EcsComponent *component = ecs_get(world, e, EcsComponent);
if (!component && trait) {
/* If this is a trait column and the trait is not a component, use
* the component type of the component the trait is applied to. */
e = ecs_entity_t_lo(trait);
component = ecs_get(world, e, EcsComponent);
}
ecs_assert(!component || !ECS_HAS_ROLE(e, CHILDOF), ECS_INTERNAL_ERROR, NULL);
ecs_assert(!component || !ECS_HAS_ROLE(e, INSTANCEOF), ECS_INTERNAL_ERROR, NULL);
return component;
}
/* Count number of columns with data (excluding tags) */
static
int32_t data_column_count(
ecs_world_t * world,
ecs_table_t * table)
{
int32_t count = 0;
ecs_vector_each(table->type, ecs_entity_t, c_ptr, {
ecs_entity_t component = *c_ptr;
/* Typically all components will be clustered together at the start of
* the type as components are created from a separate id pool, and type
* vectors are sorted.
* Explicitly check for EcsComponent and EcsName since the ecs_has check
* doesn't work during bootstrap. */
if ((component == ecs_typeid(EcsComponent)) ||
(component == ecs_typeid(EcsName)) ||
ecs_component_from_id(world, component) != NULL)
{
count = c_ptr_i + 1;
}
});
return count;
}
/* Count number of switch columns */
static
int32_t switch_column_count(
ecs_table_t *table)
{
int32_t count = 0;
ecs_vector_each(table->type, ecs_entity_t, c_ptr, {
ecs_entity_t component = *c_ptr;
if (ECS_HAS_ROLE(component, SWITCH)) {
if (!count) {
table->sw_column_offset = c_ptr_i;
}
count ++;
}
});
return count;
}
/* Count number of bitset columns */
static
int32_t bitset_column_count(
ecs_table_t *table)
{
int32_t count = 0;
ecs_vector_each(table->type, ecs_entity_t, c_ptr, {
ecs_entity_t component = *c_ptr;
if (ECS_HAS_ROLE(component, DISABLED)) {
if (!count) {
table->bs_column_offset = c_ptr_i;
}
count ++;
}
});
return count;
}
static
ecs_type_t entities_to_type(
ecs_entities_t *entities)
{
if (entities->count) {
ecs_vector_t *result = NULL;
ecs_vector_set_count(&result, ecs_entity_t, entities->count);
ecs_entity_t *array = ecs_vector_first(result, ecs_entity_t);
ecs_os_memcpy(array, entities->array, ECS_SIZEOF(ecs_entity_t) * entities->count);
return result;
} else {
return NULL;
}
}
static
void register_child_table(
ecs_world_t * world,
ecs_table_t * table,
ecs_entity_t parent)
{
/* Register child table with parent */
ecs_vector_t *child_tables = ecs_map_get_ptr(
world->child_tables, ecs_vector_t*, parent);
if (!child_tables) {
child_tables = ecs_vector_new(ecs_table_t*, 1);
}
ecs_table_t **el = ecs_vector_add(&child_tables, ecs_table_t*);
*el = table;
if (!world->child_tables) {
world->child_tables = ecs_map_new(ecs_vector_t*, 1);
}
ecs_map_set(world->child_tables, parent, &child_tables);
}
static
ecs_edge_t* get_edge(
ecs_table_t *node,
ecs_entity_t e)
{
if (e < ECS_HI_COMPONENT_ID) {
if (!node->lo_edges) {
node->lo_edges = ecs_os_calloc(sizeof(ecs_edge_t) * ECS_HI_COMPONENT_ID);
}
return &node->lo_edges[e];
} else {
if (!node->hi_edges) {
node->hi_edges = ecs_map_new(ecs_edge_t, 1);
}
return ecs_map_ensure(node->hi_edges, ecs_edge_t, e);
}
}
static
void init_edges(
ecs_world_t * world,
ecs_table_t * table)
{
ecs_entity_t *entities = ecs_vector_first(table->type, ecs_entity_t);
int32_t count = ecs_vector_count(table->type);
table->lo_edges = NULL;
table->hi_edges = NULL;
/* Make add edges to own components point to self */
int32_t i;
for (i = 0; i < count; i ++) {
ecs_entity_t e = entities[i];
ecs_edge_t *edge = get_edge(table, e);
ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL);
edge->add = table;
if (count == 1) {
edge->remove = &world->store.root;
}
/* As we're iterating over the table components, also set the table
* flags. These allow us to quickly determine if the table contains
* data that needs to be handled in a special way, like prefabs or
* containers */
if (e <= EcsLastInternalComponentId) {
table->flags |= EcsTableHasBuiltins;
}
if (e == EcsPrefab) {
table->flags |= EcsTableIsPrefab;
table->flags |= EcsTableIsDisabled;
}
if (e == EcsDisabled) {
table->flags |= EcsTableIsDisabled;
}
if (e == ecs_typeid(EcsComponent)) {
table->flags |= EcsTableHasComponentData;
}
if (ECS_HAS_ROLE(e, XOR)) {
table->flags |= EcsTableHasXor;
}
if (ECS_HAS_ROLE(e, INSTANCEOF)) {
table->flags |= EcsTableHasBase;
}
if (ECS_HAS_ROLE(e, SWITCH)) {
table->flags |= EcsTableHasSwitch;
}
if (ECS_HAS_ROLE(e, DISABLED)) {
table->flags |= EcsTableHasDisabled;
}
if (ECS_HAS_ROLE(e, CHILDOF)) {
ecs_entity_t parent = e & ECS_COMPONENT_MASK;
ecs_assert(!ecs_exists(world, parent) || ecs_is_alive(world, parent), ECS_INTERNAL_ERROR, NULL);
table->flags |= EcsTableHasParent;
register_child_table(world, table, parent);
}
if (ECS_HAS_ROLE(e, CHILDOF) || ECS_HAS_ROLE(e, INSTANCEOF)) {
ecs_set_watch(world, e & ECS_COMPONENT_MASK);
}
}
/* Register component info flags for all columns */
ecs_table_notify(world, table, &(ecs_table_event_t){
.kind = EcsTableComponentInfo
});
/* Register as root table */
if (!(table->flags & EcsTableHasParent)) {
register_child_table(world, table, 0);
}
}
static
void init_table(
ecs_world_t * world,
ecs_table_t * table,
ecs_entities_t * entities)
{
table->type = entities_to_type(entities);
table->c_info = NULL;
table->data = NULL;
table->flags = 0;
table->dirty_state = NULL;
table->monitors = NULL;
table->on_set = NULL;
table->on_set_all = NULL;
table->on_set_override = NULL;
table->un_set_all = NULL;
table->alloc_count = 0;
table->queries = NULL;
table->column_count = data_column_count(world, table);
table->sw_column_count = switch_column_count(table);
table->bs_column_count = bitset_column_count(table);
init_edges(world, table);
}
static
ecs_table_t *create_table(
ecs_world_t * world,
ecs_entities_t * entities,
uint64_t hash)
{
ecs_table_t *result = ecs_sparse_add(world->store.tables, ecs_table_t);
result->id = ecs_to_u32(ecs_sparse_last_id(world->store.tables));
ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL);
init_table(world, result, entities);
#ifndef NDEBUG
char *expr = ecs_type_str(world, result->type);
ecs_trace_2("table #[green][%s]#[normal] created", expr);
ecs_os_free(expr);
#endif
ecs_log_push();
/* Store table in lookup map */
ecs_vector_t *tables = ecs_map_get_ptr(world->store.table_map, ecs_vector_t*, hash);
ecs_table_t **elem = ecs_vector_add(&tables, ecs_table_t*);
*elem = result;
ecs_map_set(world->store.table_map, hash, &tables);
ecs_notify_queries(world, &(ecs_query_event_t) {
.kind = EcsQueryTableMatch,
.table = result
});
ecs_log_pop();
return result;
}
static
void add_entity_to_type(
ecs_type_t type,
ecs_entity_t add,
ecs_entity_t replace,
ecs_entities_t *out)
{
int32_t count = ecs_vector_count(type);
ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t);
bool added = false;
int32_t i, el = 0;
for (i = 0; i < count; i ++) {
ecs_entity_t e = array[i];
if (e == replace) {
continue;
}
if (e > add && !added) {
out->array[el ++] = add;
added = true;
}
out->array[el ++] = e;
ecs_assert(el <= out->count, ECS_INTERNAL_ERROR, NULL);
}
if (!added) {
out->array[el ++] = add;
}
out->count = el;
ecs_assert(out->count != 0, ECS_INTERNAL_ERROR, NULL);
}
static
void remove_entity_from_type(
ecs_type_t type,
ecs_entity_t remove,
ecs_entities_t *out)
{
int32_t count = ecs_vector_count(type);
ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t);
int32_t i, el = 0;
for (i = 0; i < count; i ++) {
ecs_entity_t e = array[i];
if (e != remove) {
out->array[el ++] = e;
ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL);
}
}
out->count = el;
}
static
void create_backlink_after_add(
ecs_table_t * next,
ecs_table_t * prev,
ecs_entity_t add)
{
ecs_edge_t *edge = get_edge(next, add);
if (!edge->remove) {
edge->remove = prev;
}
}
static
void create_backlink_after_remove(
ecs_table_t * next,
ecs_table_t * prev,
ecs_entity_t add)
{
ecs_edge_t *edge = get_edge(next, add);
if (!edge->add) {
edge->add = prev;
}
}
static
ecs_entity_t find_xor_replace(
ecs_world_t * world,
ecs_table_t * table,
ecs_type_t type,
ecs_entity_t add)
{
if (table->flags & EcsTableHasXor) {
ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t);
int32_t i, type_count = ecs_vector_count(type);
ecs_type_t xor_type = NULL;
for (i = type_count - 1; i >= 0; i --) {
ecs_entity_t e = array[i];
if (ECS_HAS_ROLE(e, XOR)) {
ecs_entity_t e_type = e & ECS_COMPONENT_MASK;
const EcsType *type_ptr = ecs_get(world, e_type, EcsType);
ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
if (ecs_type_owns_entity(
world, type_ptr->normalized, add, true))
{
xor_type = type_ptr->normalized;
}
} else if (xor_type) {
if (ecs_type_owns_entity(world, xor_type, e, true)) {
return e;
}
}
}
}
return 0;
}
int32_t ecs_table_switch_from_case(
ecs_world_t * world,
ecs_table_t * table,
ecs_entity_t add)
{
ecs_type_t type = table->type;
ecs_data_t *data = ecs_table_get_data(table);
ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t);
int32_t i, count = table->sw_column_count;
ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL);
add = add & ECS_COMPONENT_MASK;
ecs_sw_column_t *sw_columns = NULL;
if (data && (sw_columns = data->sw_columns)) {
/* Fast path, we can get the switch type from the column data */
for (i = 0; i < count; i ++) {
ecs_type_t sw_type = sw_columns[i].type;
if (ecs_type_owns_entity(world, sw_type, add, true)) {
return i;
}
}
} else {
/* Slow path, table is empty, so we'll have to get the switch types by
* actually inspecting the switch type entities. */
for (i = 0; i < count; i ++) {
ecs_entity_t e = array[i + table->sw_column_offset];
ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL);
e = e & ECS_COMPONENT_MASK;
const EcsType *type_ptr = ecs_get(world, e, EcsType);
ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
if (ecs_type_owns_entity(
world, type_ptr->normalized, add, true))
{
return i;
}
}
}
/* If a table was not found, this is an invalid switch case */
ecs_abort(ECS_INVALID_CASE, NULL);
return -1;
}
static
ecs_table_t *find_or_create_table_include(
ecs_world_t * world,
ecs_table_t * node,
ecs_entity_t add)
{
/* If table has one or more switches and this is a case, return self */
if (ECS_HAS_ROLE(add, CASE)) {
ecs_assert((node->flags & EcsTableHasSwitch) != 0,
ECS_INVALID_CASE, NULL);
return node;
} else {
ecs_type_t type = node->type;
int32_t count = ecs_vector_count(type);
ecs_entities_t entities = {
.array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * (count + 1)),
.count = count + 1
};
/* If table has a XOR column, check if the entity that is being added to
* the table is part of the XOR type, and if it is, find the current
* entity in the table type matching the XOR type. This entity must be
* replaced in the new table, to ensure the XOR constraint isn't
* violated. */
ecs_entity_t replace = find_xor_replace(world, node, type, add);
add_entity_to_type(type, add, replace, &entities);
ecs_table_t *result = ecs_table_find_or_create(world, &entities);
if (result != node) {
create_backlink_after_add(result, node, add);
}
return result;
}
}
static
ecs_table_t *find_or_create_table_exclude(
ecs_world_t * world,
ecs_table_t * node,
ecs_entity_t remove)
{
ecs_type_t type = node->type;
int32_t count = ecs_vector_count(type);
ecs_entities_t entities = {
.array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * count),
.count = count
};
remove_entity_from_type(type, remove, &entities);
ecs_table_t *result = ecs_table_find_or_create(world, &entities);
if (!result) {
return NULL;
}
if (result != node) {
create_backlink_after_remove(result, node, remove);
}
return result;
}
ecs_table_t* ecs_table_traverse_remove(
ecs_world_t * world,
ecs_table_t * node,
ecs_entities_t * to_remove,
ecs_entities_t * removed)
{
int32_t i, count = to_remove->count;
ecs_entity_t *entities = to_remove->array;
node = node ? node : &world->store.root;
for (i = 0; i < count; i ++) {
ecs_entity_t e = entities[i];
/* Removing 0 from an entity is not valid */
ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL);
ecs_edge_t *edge = get_edge(node, e);
ecs_table_t *next = edge->remove;
if (!next) {
if (edge->add == node) {
/* Find table with all components of node except 'e' */
next = find_or_create_table_exclude(world, node, e);
if (!next) {
return NULL;
}
edge->remove = next;
} else {
/* If the add edge does not point to self, the table
* does not have the entity in to_remove. */
continue;
}
}
bool has_case = ECS_HAS_ROLE(e, CASE);
if (removed && (node != next || has_case)) {
removed->array[removed->count ++] = e;
}
node = next;
}
return node;
}
static
void find_owned_components(
ecs_world_t * world,
ecs_table_t * node,
ecs_entity_t base,
ecs_entities_t * owned)
{
/* If we're adding an INSTANCEOF relationship, check if the base
* has OWNED components that need to be added to the instance */
ecs_type_t t = ecs_get_type(world, base);
int i, count = ecs_vector_count(t);
ecs_entity_t *entities = ecs_vector_first(t, ecs_entity_t);
for (i = 0; i < count; i ++) {
ecs_entity_t e = entities[i];
if (ECS_HAS_ROLE(e, INSTANCEOF)) {
find_owned_components(world, node, e & ECS_COMPONENT_MASK, owned);
} else
if (ECS_HAS_ROLE(e, OWNED)) {
e = e & ECS_COMPONENT_MASK;
/* If entity is a type, add each component in the type */
const EcsType *t_ptr = ecs_get(world, e, EcsType);
if (t_ptr) {
ecs_type_t n = t_ptr->normalized;
int32_t j, n_count = ecs_vector_count(n);
ecs_entity_t *n_entities = ecs_vector_first(n, ecs_entity_t);
for (j = 0; j < n_count; j ++) {
owned->array[owned->count ++] = n_entities[j];
}
} else {
owned->array[owned->count ++] = e & ECS_COMPONENT_MASK;
}
}
}
}
ecs_table_t* ecs_table_traverse_add(
ecs_world_t * world,
ecs_table_t * node,
ecs_entities_t * to_add,
ecs_entities_t * added)
{
int32_t i, count = to_add->count;
ecs_entity_t *entities = to_add->array;
node = node ? node : &world->store.root;
ecs_entity_t owned_array[ECS_MAX_ADD_REMOVE];
ecs_entities_t owned = {
.array = owned_array,
.count = 0
};
for (i = 0; i < count; i ++) {
ecs_entity_t e = entities[i];
/* Adding 0 to an entity is not valid */
ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL);
ecs_edge_t *edge = get_edge(node, e);
ecs_table_t *next = edge->add;
if (!next) {
next = find_or_create_table_include(world, node, e);
ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL);
edge->add = next;
}
bool has_case = ECS_HAS_ROLE(e, CASE);
if (added && (node != next || has_case)) {
added->array[added->count ++] = e;
}
if ((node != next) && ECS_HAS_ROLE(e, INSTANCEOF)) {
find_owned_components(world, next, ECS_COMPONENT_MASK & e, &owned);
}
node = next;
}
/* In case OWNED components were found, add them as well */
if (owned.count) {
node = ecs_table_traverse_add(world, node, &owned, added);
}
return node;
}
static
int ecs_entity_compare(
const void *e1,
const void *e2)
{
ecs_entity_t v1 = *(ecs_entity_t*)e1;
ecs_entity_t v2 = *(ecs_entity_t*)e2;
if (v1 < v2) {
return -1;
} else if (v1 > v2) {
return 1;
} else {
return 0;
}
}
static
bool ecs_entity_array_is_ordered(
ecs_entities_t *entities)
{
ecs_entity_t prev = 0;
ecs_entity_t *array = entities->array;
int32_t i, count = entities->count;
for (i = 0; i < count; i ++) {
if (!array[i] && !prev) {
continue;
}
if (array[i] <= prev) {
return false;
}
prev = array[i];
}
return true;
}
static
int32_t ecs_entity_array_dedup(
ecs_entity_t *array,
int32_t count)
{
int32_t j, k;
ecs_entity_t prev = array[0];
for (k = j = 1; k < count; j ++, k++) {
ecs_entity_t e = array[k];
if (e == prev) {
k ++;
}
array[j] = e;
prev = e;
}
return count - (k - j);
}
#ifndef NDEBUG
static
int32_t count_occurrences(
ecs_world_t * world,
ecs_entities_t * entities,
ecs_entity_t entity,
int32_t constraint_index)
{
const EcsType *type_ptr = ecs_get(world, entity, EcsType);
ecs_assert(type_ptr != NULL,
ECS_INVALID_PARAMETER, "flag must be applied to type");
ecs_type_t type = type_ptr->normalized;
int32_t count = 0;
int i;
for (i = 0; i < constraint_index; i ++) {
ecs_entity_t e = entities->array[i];
if (e & ECS_ROLE_MASK) {
break;
}
if (ecs_type_has_entity(world, type, e)) {
count ++;
}
}
return count;
}
static
void verify_constraints(
ecs_world_t * world,
ecs_entities_t * entities)
{
int i, count = entities->count;
for (i = count - 1; i >= 0; i --) {
ecs_entity_t e = entities->array[i];
ecs_entity_t mask = e & ECS_ROLE_MASK;
if (!mask ||
((mask != ECS_OR) &&
(mask != ECS_XOR) &&
(mask != ECS_NOT)))
{
break;
}
ecs_entity_t entity = e & ECS_COMPONENT_MASK;
int32_t matches = count_occurrences(world, entities, entity, i);
switch(mask) {
case ECS_OR:
ecs_assert(matches >= 1, ECS_TYPE_CONSTRAINT_VIOLATION, NULL);
break;
case ECS_XOR:
ecs_assert(matches == 1, ECS_TYPE_CONSTRAINT_VIOLATION, NULL);
break;
case ECS_NOT:
ecs_assert(matches == 0, ECS_TYPE_CONSTRAINT_VIOLATION, NULL);
break;
}
}
}
#endif
static
ecs_table_t *find_or_create(
ecs_world_t * world,
ecs_entities_t * entities)
{
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
/* Make sure array is ordered and does not contain duplicates */
int32_t type_count = entities->count;
ecs_entity_t *ordered = NULL;
if (!type_count) {
return &world->store.root;
}
if (!ecs_entity_array_is_ordered(entities)) {
ecs_size_t size = ECS_SIZEOF(ecs_entity_t) * type_count;
ordered = ecs_os_alloca(size);
ecs_os_memcpy(ordered, entities->array, size);
qsort(
ordered, (size_t)type_count, sizeof(ecs_entity_t), ecs_entity_compare);
type_count = ecs_entity_array_dedup(ordered, type_count);
} else {
ordered = entities->array;
}
uint64_t hash = 0;
ecs_hash(ordered, entities->count * ECS_SIZEOF(ecs_entity_t), &hash);
ecs_vector_t *table_vec = ecs_map_get_ptr(
world->store.table_map, ecs_vector_t*, hash);
if (table_vec) {
/* Usually this will be just one, but in the case of a collision
* multiple tables can be stored using the same hash. */
int32_t i, count = ecs_vector_count(table_vec);
ecs_table_t *table, **tables = ecs_vector_first(
table_vec, ecs_table_t*);
for (i = 0; i < count; i ++) {
table = tables[i];
int32_t t, table_type_count = ecs_vector_count(table->type);
/* If number of components in table doesn't match, it's definitely
* a collision. */
if (table_type_count != type_count) {
table = NULL;
continue;
}
/* Check if components of table match */
ecs_entity_t *table_type = ecs_vector_first(
table->type, ecs_entity_t);
for (t = 0; t < type_count; t ++) {
if (table_type[t] != ordered[t]) {
table = NULL;
break;
}
}
if (table) {
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->in_progress, ECS_INTERNAL_ERROR, NULL);
ecs_entities_t ordered_entities = {
.array = ordered,
.count = type_count
};
#ifndef NDEBUG
/* Check for constraint violations */
verify_constraints(world, &ordered_entities);
#endif
/* If we get here, the table has not been found, so create it. */
ecs_table_t *result = create_table(world, &ordered_entities, hash);
ecs_assert(ordered_entities.count == ecs_vector_count(result->type),
ECS_INTERNAL_ERROR, NULL);
return result;
}
ecs_table_t* ecs_table_find_or_create(
ecs_world_t * world,
ecs_entities_t * components)
{
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
return find_or_create(world, components);
}
ecs_table_t* ecs_table_from_type(
ecs_world_t *world,
ecs_type_t type)
{
ecs_entities_t components = ecs_type_to_entities(type);
return ecs_table_find_or_create(
world, &components);
}
void ecs_init_root_table(
ecs_world_t *world)
{
ecs_entities_t entities = {
.array = NULL,
.count = 0
};
init_table(world, &world->store.root, &entities);
}
void ecs_table_clear_edges(
ecs_world_t *world,
ecs_table_t *table)
{
(void)world;
uint32_t i;
if (table->lo_edges) {
for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) {
ecs_edge_t *e = &table->lo_edges[i];
ecs_table_t *add = e->add, *remove = e->remove;
if (add) {
add->lo_edges[i].remove = NULL;
}
if (remove) {
remove->lo_edges[i].add = NULL;
}
}
}
ecs_map_iter_t it = ecs_map_iter(table->hi_edges);
ecs_edge_t *edge;
ecs_map_key_t component;
while ((edge = ecs_map_next(&it, ecs_edge_t, &component))) {
ecs_table_t *add = edge->add, *remove = edge->remove;
if (add) {
ecs_edge_t *e = get_edge(add, component);
e->remove = NULL;
if (!e->add) {
ecs_map_remove(add->hi_edges, component);
}
}
if (remove) {
ecs_edge_t *e = get_edge(remove, component);
e->add = NULL;
if (!e->remove) {
ecs_map_remove(remove->hi_edges, component);
}
}
}
}
/* The ratio used to determine whether the map should rehash. If
* (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */
#define LOAD_FACTOR (1.5)
#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t))
#define GET_ELEM(array, elem_size, index) \
ECS_OFFSET(array, (elem_size) * (index))
struct ecs_bucket_t {
ecs_map_key_t *keys; /* Array with keys */
void *payload; /* Payload array */
int32_t count; /* Number of elements in bucket */
};
struct ecs_map_t {
ecs_bucket_t *buckets;
int32_t elem_size;
int32_t bucket_count;
int32_t count;
};
/* Get bucket count for number of elements */
static
int32_t get_bucket_count(
int32_t element_count)
{
return ecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR));
}
/* Get bucket index for provided map key */
static
int32_t get_bucket_id(
int32_t bucket_count,
ecs_map_key_t key)
{
ecs_assert(bucket_count > 0, ECS_INTERNAL_ERROR, NULL);
int32_t result = (int32_t)(key & ((uint64_t)bucket_count - 1));
ecs_assert(result < INT32_MAX, ECS_INTERNAL_ERROR, NULL);
return result;
}
/* Get bucket for key */
static
ecs_bucket_t* get_bucket(
const ecs_map_t *map,
ecs_map_key_t key)
{
int32_t bucket_count = map->bucket_count;
if (!bucket_count) {
return NULL;
}
int32_t bucket_id = get_bucket_id(bucket_count, key);
ecs_assert(bucket_id < 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 = ecs_next_pow_of_2(new_count);
if (new_count && new_count > bucket_count) {
map->buckets = ecs_os_realloc(map->buckets, new_count * ECS_SIZEOF(ecs_bucket_t));
map->bucket_count = new_count;
ecs_os_memset(
ECS_OFFSET(map->buckets, bucket_count * ECS_SIZEOF(ecs_bucket_t)),
0, (new_count - bucket_count) * ECS_SIZEOF(ecs_bucket_t));
}
}
/* Free contents of bucket */
static
void clear_bucket(
ecs_bucket_t *bucket)
{
ecs_os_free(bucket->keys);
ecs_os_free(bucket->payload);
bucket->keys = NULL;
bucket->payload = NULL;
bucket->count = 0;
}
/* Clear all buckets */
static
void clear_buckets(
ecs_map_t *map)
{
ecs_bucket_t *buckets = map->buckets;
int32_t i, count = map->bucket_count;
for (i = 0; i < count; i ++) {
clear_bucket(&buckets[i]);
}
ecs_os_free(buckets);
map->buckets = NULL;
map->bucket_count = 0;
}
/* Find or create bucket for specified key */
static
ecs_bucket_t* ensure_bucket(
ecs_map_t *map,
ecs_map_key_t key)
{
if (!map->bucket_count) {
ensure_buckets(map, 2);
}
int32_t bucket_id = get_bucket_id(map->bucket_count, key);
ecs_assert(bucket_id >= 0, ECS_INTERNAL_ERROR, NULL);
return &map->buckets[bucket_id];
}
/* Add element to bucket */
static
int32_t add_to_bucket(
ecs_bucket_t *bucket,
ecs_size_t elem_size,
ecs_map_key_t key,
const void *payload)
{
int32_t index = bucket->count ++;
int32_t bucket_count = index + 1;
bucket->keys = ecs_os_realloc(bucket->keys, KEY_SIZE * bucket_count);
bucket->payload = ecs_os_realloc(bucket->payload, elem_size * bucket_count);
bucket->keys[index] = key;
if (payload) {
void *elem = GET_ELEM(bucket->payload, elem_size, index);
ecs_os_memcpy(elem, payload, elem_size);
}
return index;
}
/* Remove element from bucket */
static
void remove_from_bucket(
ecs_bucket_t *bucket,
ecs_size_t elem_size,
ecs_map_key_t key,
int32_t index)
{
(void)key;
ecs_assert(bucket->count != 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(index < bucket->count, ECS_INTERNAL_ERROR, NULL);
int32_t bucket_count = -- bucket->count;
if (index != bucket->count) {
ecs_assert(key == bucket->keys[index], ECS_INTERNAL_ERROR, NULL);
bucket->keys[index] = bucket->keys[bucket_count];
ecs_map_key_t *elem = GET_ELEM(bucket->payload, elem_size, index);
ecs_map_key_t *last_elem = GET_ELEM(bucket->payload, elem_size, bucket->count);
ecs_os_memcpy(elem, last_elem, elem_size);
}
}
/* Get payload pointer for key from bucket */
static
void* get_from_bucket(
ecs_bucket_t *bucket,
ecs_map_key_t key,
ecs_size_t elem_size)
{
ecs_map_key_t *keys = bucket->keys;
int32_t i, count = bucket->count;
for (i = 0; i < count; i ++) {
if (keys[i] == key) {
return GET_ELEM(bucket->payload, elem_size, i);
}
}
return NULL;
}
/* Grow number of buckets */
static
void 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);
ecs_size_t elem_size = map->elem_size;
ensure_buckets(map, bucket_count);
ecs_bucket_t *buckets = map->buckets;
ecs_assert(buckets != NULL, ECS_INTERNAL_ERROR, NULL);
int32_t bucket_id;
/* Iterate backwards as elements could otherwise be moved to existing
* buckets which could temporarily cause the number of elements in a
* bucket to exceed BUCKET_COUNT. */
for (bucket_id = bucket_count - 1; bucket_id >= 0; bucket_id --) {
ecs_bucket_t *bucket = &buckets[bucket_id];
int i, count = bucket->count;
ecs_map_key_t *key_array = bucket->keys;
void *payload_array = bucket->payload;
for (i = 0; i < count; i ++) {
ecs_map_key_t key = key_array[i];
void *elem = GET_ELEM(payload_array, elem_size, i);
int32_t new_bucket_id = get_bucket_id(bucket_count, key);
if (new_bucket_id != bucket_id) {
ecs_bucket_t *new_bucket = &buckets[new_bucket_id];
add_to_bucket(new_bucket, elem_size, key, elem);
remove_from_bucket(bucket, elem_size, key, i);
count --;
i --;
}
}
if (!bucket->count) {
clear_bucket(bucket);
}
}
}
ecs_map_t* _ecs_map_new(
ecs_size_t elem_size,
ecs_size_t alignment,
int32_t element_count)
{
(void)alignment;
ecs_map_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_map_t) * 1);
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
int32_t bucket_count = get_bucket_count(element_count);
result->count = 0;
result->elem_size = elem_size;
ensure_buckets(result, bucket_count);
return result;
}
void ecs_map_free(
ecs_map_t *map)
{
if (map) {
clear_buckets(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 (!map) {
return NULL;
}
ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL);
ecs_bucket_t * bucket = get_bucket(map, key);
if (!bucket) {
return NULL;
}
return get_from_bucket(bucket, key, elem_size);
}
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;
}
}
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);
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 = ensure_bucket(map, key);
ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL);
void *elem = get_from_bucket(bucket, key, elem_size);
if (!elem) {
int32_t index = add_to_bucket(bucket, elem_size, key, payload);
int32_t map_count = ++map->count;
int32_t target_bucket_count = get_bucket_count(map_count);
int32_t map_bucket_count = map->bucket_count;
if (target_bucket_count > map_bucket_count) {
rehash(map, target_bucket_count);
bucket = ensure_bucket(map, key);
return get_from_bucket(bucket, key, elem_size);
} else {
return GET_ELEM(bucket->payload, elem_size, index);
}
} else {
if (payload) {
ecs_os_memcpy(elem, payload, elem_size);
}
return elem;
}
}
void ecs_map_remove(
ecs_map_t *map,
ecs_map_key_t key)
{
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_bucket_t * bucket = get_bucket(map, key);
if (!bucket) {
return;
}
int32_t i, bucket_count = bucket->count;
for (i = 0; i < bucket_count; i ++) {
if (bucket->keys[i] == key) {
remove_from_bucket(bucket, map->elem_size, key, i);
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;
}
ecs_map_iter_t ecs_map_iter(
const ecs_map_t *map)
{
return (ecs_map_iter_t){
.map = map,
.bucket = NULL,
.bucket_index = 0,
.element_index = 0
};
}
void* _ecs_map_next(
ecs_map_iter_t *iter,
ecs_size_t elem_size,
ecs_map_key_t *key_out)
{
const ecs_map_t *map = iter->map;
if (!map) {
return NULL;
}
ecs_assert(!elem_size || elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL);
ecs_bucket_t *bucket = iter->bucket;
int32_t element_index = iter->element_index;
elem_size = map->elem_size;
do {
if (!bucket) {
int32_t bucket_index = iter->bucket_index;
ecs_bucket_t *buckets = map->buckets;
if (bucket_index < map->bucket_count) {
bucket = &buckets[bucket_index];
iter->bucket = bucket;
element_index = 0;
iter->element_index = 0;
} else {
return NULL;
}
}
if (element_index < bucket->count) {
iter->element_index = element_index + 1;
break;
} else {
bucket = NULL;
iter->bucket_index ++;
}
} while (true);
if (key_out) {
*key_out = bucket->keys[element_index];
}
return GET_ELEM(bucket->payload, elem_size, element_index);
}
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) {
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) {
rehash(map, bucket_count);
}
}
void ecs_map_memory(
ecs_map_t *map,
int32_t *allocd,
int32_t *used)
{
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
if (used) {
*used = map->count * map->elem_size;
}
if (allocd) {
*allocd += ECS_SIZEOF(ecs_map_t);
int i, bucket_count = map->bucket_count;
for (i = 0; i < bucket_count; i ++) {
ecs_bucket_t *bucket = &map->buckets[i];
*allocd += KEY_SIZE * bucket->count;
*allocd += map->elem_size * bucket->count;
}
*allocd += ECS_SIZEOF(ecs_bucket_t) * bucket_count;
}
}
static
void* get_owned_column_ptr(
const ecs_iter_t *it,
ecs_size_t size,
int32_t table_column,
int32_t row)
{
ecs_assert(it->table_columns != NULL, ECS_INTERNAL_ERROR, NULL);
(void)size;
ecs_column_t *column = &((ecs_column_t*)it->table_columns)[table_column - 1];
ecs_assert(column->size != 0, ECS_COLUMN_HAS_NO_DATA, NULL);
ecs_assert(!size || column->size == size, ECS_COLUMN_TYPE_MISMATCH, NULL);
void *buffer = ecs_vector_first_t(column->data, column->size, column->alignment);
return ECS_OFFSET(buffer, column->size * (it->offset + row));
}
static
const void* get_shared_column(
const ecs_iter_t *it,
ecs_size_t size,
int32_t table_column)
{
ecs_ref_t *refs = it->table->references;
ecs_assert(refs != NULL, ECS_INTERNAL_ERROR, NULL);
(void)size;
#ifndef NDEBUG
if (size) {
ecs_entity_t component_id = ecs_get_typeid(
it->world, refs[-table_column - 1].component);
const EcsComponent *cdata = ecs_get(
it->world, component_id, EcsComponent);
ecs_assert(cdata != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(cdata->size == size, ECS_COLUMN_TYPE_MISMATCH,
ecs_get_name(it->world, it->system));
}
#endif
ecs_ref_t *ref = &refs[-table_column - 1];
return (void*)ecs_get_ref_w_entity(
it->world, ref, ref->entity, ref->component);
}
static
bool get_table_column(
const ecs_iter_t *it,
int32_t column,
int32_t *table_column_out)
{
ecs_assert(column <= it->column_count, ECS_INVALID_PARAMETER, NULL);
int32_t table_column = 0;
if (column != 0) {
ecs_assert(it->table->columns != NULL, ECS_INTERNAL_ERROR, NULL);
table_column = it->table->columns[column - 1];
if (!table_column) {
/* column is not set */
return false;
}
}
*table_column_out = table_column;
return true;
}
static
void* get_column(
const ecs_iter_t *it,
ecs_size_t size,
int32_t column,
int32_t row)
{
int32_t table_column;
if (!column) {
return it->entities;
}
if (!get_table_column(it, column, &table_column)) {
return NULL;
}
if (table_column < 0) {
return (void*)get_shared_column(it, size, table_column);
} else {
return get_owned_column_ptr(it, size, table_column, row);
}
}
/* --- Public API --- */
void* ecs_column_w_size(
const ecs_iter_t *it,
size_t size,
int32_t column)
{
return get_column(it, ecs_from_size_t(size), column, 0);
}
void* ecs_element_w_size(
const ecs_iter_t *it,
size_t size,
int32_t column,
int32_t row)
{
return get_column(it, ecs_from_size_t(size), column, row);
}
bool ecs_is_owned(
const ecs_iter_t *it,
int32_t column)
{
int32_t table_column;
if (!get_table_column(it, column, &table_column)) {
return true;
}
return table_column >= 0;
}
bool ecs_is_readonly(
const ecs_iter_t *it,
int32_t column)
{
ecs_query_t *query = it->query;
/* If this is not a query iterator, readonly is meaningless */
ecs_assert(query != NULL, ECS_INVALID_OPERATION, NULL);
(void)query;
ecs_sig_column_t *column_data = ecs_vector_get(
it->query->sig.columns, ecs_sig_column_t, column - 1);
return column_data->inout_kind == EcsIn;
}
ecs_entity_t ecs_column_source(
const ecs_iter_t *it,
int32_t index)
{
ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL);
ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(it->table->columns != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_iter_table_t *table = it->table;
int32_t table_column = table->columns[index - 1];
if (table_column >= 0) {
return 0;
}
ecs_assert(table->references != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_ref_t *ref = &table->references[-table_column - 1];
return ref->entity;
}
ecs_type_t ecs_column_type(
const ecs_iter_t *it,
int32_t index)
{
ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL);
ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(it->table->types != NULL, ECS_INTERNAL_ERROR, NULL);
return it->table->types[index - 1];
}
ecs_entity_t ecs_column_entity(
const ecs_iter_t *it,
int32_t index)
{
ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL);
ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(it->table->components != NULL, ECS_INTERNAL_ERROR, NULL);
return it->table->components[index - 1];
}
size_t ecs_column_size(
const ecs_iter_t *it,
int32_t index)
{
ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL);
ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(it->table->columns != NULL, ECS_INTERNAL_ERROR, NULL);
int32_t table_column = it->table->columns[index - 1];
return ecs_table_column_size(it, table_column - 1);
}
int32_t ecs_column_index_from_name(
const ecs_iter_t *it,
const char *name)
{
ecs_sig_column_t *column = NULL;
if (it->query) {
int32_t i, count = ecs_vector_count(it->query->sig.columns);
for (i = 0; i < count; i ++) {
column = ecs_vector_get(
it->query->sig.columns, ecs_sig_column_t, i);
if (column->name) {
if (!strcmp(name, column->name)) {
return i + 1;
}
}
}
}
return 0;
}
ecs_type_t ecs_iter_type(
const ecs_iter_t *it)
{
/* If no table is set it means that the iterator isn't pointing to anything
* yet. The most likely cause for this is that the operation is invoked on
* a new iterator for which "next" hasn't been invoked yet, or on an
* iterator that is out of elements. */
ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_table_t *table = it->table->table;
return table->type;
}
int32_t ecs_table_component_index(
const ecs_iter_t *it,
ecs_entity_t component)
{
/* See ecs_iter_type */
ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(it->table->table != NULL, ECS_INTERNAL_ERROR, NULL);
return ecs_type_index_of(it->table->table->type, component);
}
void* ecs_table_column(
const ecs_iter_t *it,
int32_t column_index)
{
/* See ecs_iter_type */
ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(it->table->table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_table_t *table = it->table->table;
ecs_assert(column_index < ecs_vector_count(table->type),
ECS_INVALID_PARAMETER, NULL);
if (table->column_count <= column_index) {
return NULL;
}
ecs_column_t *columns = it->table_columns;
ecs_column_t *column = &columns[column_index];
return ecs_vector_first_t(column->data, column->size, column->alignment);
}
size_t ecs_table_column_size(
const ecs_iter_t *it,
int32_t column_index)
{
/* See ecs_iter_type */
ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(it->table->table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_table_t *table = it->table->table;
ecs_assert(column_index < ecs_vector_count(table->type),
ECS_INVALID_PARAMETER, NULL);
if (table->column_count <= column_index) {
return 0;
}
ecs_column_t *columns = it->table_columns;
ecs_column_t *column = &columns[column_index];
return ecs_to_size_t(column->size);
}
int8_t ecs_to_i8(
int64_t v)
{
ecs_assert(v < INT8_MAX, ECS_INTERNAL_ERROR, NULL);
return (int8_t)v;
}
int16_t ecs_to_i16(
int64_t v)
{
ecs_assert(v < INT16_MAX, ECS_INTERNAL_ERROR, NULL);
return (int16_t)v;
}
uint32_t ecs_to_u32(
uint64_t v)
{
ecs_assert(v < UINT32_MAX, ECS_INTERNAL_ERROR, NULL);
return (uint32_t)v;
}
size_t ecs_to_size_t(
int64_t size)
{
ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL);
return (size_t)size;
}
ecs_size_t ecs_from_size_t(
size_t size)
{
ecs_assert(size < INT32_MAX, ECS_INTERNAL_ERROR, NULL);
return (ecs_size_t)size;
}
int32_t ecs_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;
}
/*
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.
*/
static int ecs_os_time_initialized;
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
static double _ecs_os_time_win_freq;
static LARGE_INTEGER _ecs_os_time_win_start;
#elif defined(__APPLE__) && defined(__MACH__)
#include <mach/mach_time.h>
static mach_timebase_info_data_t _ecs_os_time_osx_timebase;
static uint64_t _ecs_os_time_osx_start;
#else /* anything else, this will need more care for non-Linux platforms */
#include <time.h>
static uint64_t _ecs_os_time_posix_start;
#endif
/* prevent 64-bit overflow when computing relative timestamp
see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3
*/
#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__))
int64_t 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
void ecs_os_time_setup(void) {
if ( ecs_os_time_initialized) {
return;
}
ecs_os_time_initialized = 1;
#if defined(_WIN32)
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&_ecs_os_time_win_start);
_ecs_os_time_win_freq = (double)freq.QuadPart / 1000000000.0;
#elif defined(__APPLE__) && defined(__MACH__)
mach_timebase_info(&_ecs_os_time_osx_timebase);
_ecs_os_time_osx_start = mach_absolute_time();
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
_ecs_os_time_posix_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec;
#endif
}
uint64_t ecs_os_time_now(void) {
ecs_assert(ecs_os_time_initialized != 0, ECS_INTERNAL_ERROR, NULL);
uint64_t now;
#if defined(_WIN32)
LARGE_INTEGER qpc_t;
QueryPerformanceCounter(&qpc_t);
now = (uint64_t)(qpc_t.QuadPart / _ecs_os_time_win_freq);
#elif defined(__APPLE__) && defined(__MACH__)
now = (uint64_t) int64_muldiv((int64_t)mach_absolute_time(), (int64_t)_ecs_os_time_osx_timebase.numer, (int64_t)_ecs_os_time_osx_timebase.denom);
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
now = ((uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec);
#endif
return now;
}
void ecs_os_time_sleep(
int32_t sec,
int32_t nanosec)
{
#ifndef _WIN32
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_os_err("nanosleep failed");
}
#else
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);
#endif
}
#if defined(_WIN32)
static ULONG win32_current_resolution;
void ecs_increase_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 && win32_current_resolution) {
pNtSetTimerResolution(win32_current_resolution, 0, &current);
win32_current_resolution = 0;
return;
} else if (!enable) {
return;
}
if (resolution == win32_current_resolution) {
return;
}
if (win32_current_resolution) {
pNtSetTimerResolution(win32_current_resolution, 0, &current);
}
if (pNtSetTimerResolution(resolution, 1, &current)) {
/* Try setting a lower resolution */
resolution *= 2;
if(pNtSetTimerResolution(resolution, 1, &current)) return;
}
win32_current_resolution = resolution;
}
#else
void ecs_increase_timer_resolution(bool enable)
{
(void)enable;
return;
}
#endif
#ifdef FLECS_PIPELINE
/* Worker thread */
static
void* worker(void *arg) {
ecs_thread_t *thread = arg;
ecs_world_t *world = thread->world;
/* Start worker thread, increase counter so main thread knows how many
* workers are ready */
ecs_os_mutex_lock(world->sync_mutex);
world->workers_running ++;
if (!world->quit_workers) {
ecs_os_cond_wait(world->worker_cond, world->sync_mutex);
}
ecs_os_mutex_unlock(world->sync_mutex);
while (!world->quit_workers) {
ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)thread, 0);
ecs_pipeline_progress(
(ecs_world_t*)thread,
world->pipeline,
world->stats.delta_time);
ecs_set_scope((ecs_world_t*)thread, old_scope);
}
ecs_os_mutex_lock(world->sync_mutex);
world->workers_running --;
ecs_os_mutex_unlock(world->sync_mutex);
return NULL;
}
/* Start threads */
static
void start_workers(
ecs_world_t *world,
int32_t threads)
{
ecs_assert(world->workers == NULL, ECS_INTERNAL_ERROR, NULL);
world->workers = ecs_vector_new(ecs_thread_t, threads);
world->worker_stages = ecs_vector_new(ecs_stage_t, threads);
int32_t i;
for (i = 0; i < threads; i ++) {
ecs_thread_t *thread =
ecs_vector_add(&world->workers, ecs_thread_t);
thread->magic = ECS_THREAD_MAGIC;
thread->world = world;
thread->thread = 0;
thread->index = i;
thread->stage = ecs_vector_add(&world->worker_stages, ecs_stage_t);
ecs_stage_init(world, thread->stage);
thread->stage->id = 2 + i; /* 0 and 1 are reserved for main and temp */
thread->stage->world = (ecs_world_t*)thread;
thread->thread = ecs_os_thread_new(worker, thread);
ecs_assert(thread->thread != 0, ECS_THREAD_ERROR, NULL);
}
}
/* Wait until all workers are running */
static
void wait_for_workers(
ecs_world_t *world)
{
int32_t thread_count = ecs_vector_count(world->workers);
bool wait = true;
do {
ecs_os_mutex_lock(world->sync_mutex);
if (world->workers_running == thread_count) {
wait = false;
}
ecs_os_mutex_unlock(world->sync_mutex);
} while (wait);
}
/* Synchronize worker threads */
static
void sync_worker(
ecs_world_t *world)
{
int32_t thread_count = ecs_vector_count(world->workers);
/* Signal that thread is waiting */
ecs_os_mutex_lock(world->sync_mutex);
if (++ world->workers_waiting == thread_count) {
/* 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);
}
/* Wait until all threads are waiting on sync point */
static
void wait_for_sync(
ecs_world_t *world)
{
int32_t thread_count = ecs_vector_count(world->workers);
ecs_os_mutex_lock(world->sync_mutex);
if (world->workers_waiting != thread_count) {
ecs_os_cond_wait(world->sync_cond, world->sync_mutex);
}
/* We should have been signalled unless all workers are waiting on sync */
ecs_assert(world->workers_waiting == thread_count,
ECS_INTERNAL_ERROR, NULL);
ecs_os_mutex_unlock(world->sync_mutex);
}
/* Signal workers that they can start/resume work */
static
void signal_workers(
ecs_world_t *world)
{
ecs_os_mutex_lock(world->sync_mutex);
ecs_os_cond_broadcast(world->worker_cond);
ecs_os_mutex_unlock(world->sync_mutex);
}
/** Stop worker threads */
static
void ecs_stop_threads(
ecs_world_t *world)
{
world->quit_workers = true;
signal_workers(world);
ecs_vector_each(world->workers, ecs_thread_t, thr, {
ecs_os_thread_join(thr->thread);
ecs_stage_deinit(world, thr->stage);
});
ecs_vector_free(world->workers);
ecs_vector_free(world->worker_stages);
world->worker_stages = NULL;
world->workers = NULL;
world->quit_workers = false;
ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL);
}
/* -- Private functions -- */
void ecs_worker_begin(
ecs_world_t *world)
{
if (world->magic == ECS_WORLD_MAGIC) {
ecs_staging_begin(world);
}
}
bool ecs_worker_sync(
ecs_world_t *world)
{
int32_t build_count = world->stats.pipeline_build_count_total;
int32_t thread_count = ecs_vector_count(world->workers);
if (!thread_count) {
ecs_staging_end(world);
ecs_pipeline_update(world, world->pipeline);
ecs_staging_begin(world);
} else {
sync_worker(world);
}
return world->stats.pipeline_build_count_total != build_count;
}
void ecs_worker_end(
ecs_world_t *world)
{
int32_t thread_count = ecs_vector_count(world->workers);
if (!thread_count) {
ecs_staging_end(world);
} else {
sync_worker(world);
}
}
void ecs_workers_progress(
ecs_world_t *world)
{
ecs_entity_t pipeline = world->pipeline;
int32_t thread_count = ecs_vector_count(world->workers);
ecs_time_t start = {0};
if (world->measure_frame_time) {
ecs_time_measure(&start);
}
if (thread_count <= 1) {
ecs_pipeline_begin(world, pipeline);
ecs_entity_t old_scope = ecs_set_scope(world, 0);
ecs_pipeline_progress(world, pipeline, world->stats.delta_time);
ecs_set_scope(world, old_scope);
ecs_pipeline_end(world);
} else {
int32_t i, sync_count = ecs_pipeline_begin(world, pipeline);
/* Make sure workers are running and ready */
wait_for_workers(world);
/* Synchronize n times for each op in the pipeline */
for (i = 0; i < sync_count; i ++) {
ecs_staging_begin(world);
/* Signal workers that they should start running systems */
world->workers_waiting = 0;
signal_workers(world);
/* Wait until all workers are waiting on sync point */
wait_for_sync(world);
/* Merge */
ecs_staging_end(world);
int32_t update_count;
if ((update_count = ecs_pipeline_update(world, pipeline))) {
/* The number of operations in the pipeline could have changed
* as result of the merge */
sync_count = update_count;
}
}
ecs_pipeline_end(world);
}
if (world->measure_frame_time) {
world->stats.system_time_total += (FLECS_FLOAT)ecs_time_measure(&start);
}
}
/* -- Public functions -- */
void ecs_set_threads(
ecs_world_t *world,
int32_t threads)
{
ecs_assert(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL);
int32_t thread_count = ecs_vector_count(world->workers);
if (!world->arg_threads && thread_count != threads) {
/* Stop existing threads */
if (ecs_vector_count(world->workers)) {
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();
world->stage_count = 2 + threads;
start_workers(world, threads);
}
/* Iterate tables, make sure the ecs_data_t arrays are large enough */
ecs_sparse_each(world->store.tables, ecs_table_t, table, {
ecs_table_get_data(table);
});
}
}
#endif
#ifdef FLECS_PIPELINE
ECS_TYPE_DECL(EcsPipelineQuery);
static ECS_CTOR(EcsPipelineQuery, ptr, {
memset(ptr, 0, _size);
})
static ECS_DTOR(EcsPipelineQuery, ptr, {
ecs_vector_free(ptr->ops);
})
static
int compare_entity(
ecs_entity_t e1,
const void *ptr1,
ecs_entity_t e2,
const void *ptr2)
{
(void)ptr1;
(void)ptr2;
return (e1 > e2) - (e1 < e2);
}
static
int rank_phase(
ecs_world_t *world,
ecs_entity_t rank_component,
ecs_type_t type)
{
const EcsType *pipeline_type = ecs_get(world, rank_component, EcsType);
ecs_assert(pipeline_type != NULL, ECS_INTERNAL_ERROR, NULL);
/* Find tag in system that belongs to pipeline */
ecs_entity_t *sys_comps = ecs_vector_first(type, ecs_entity_t);
int32_t c, t, count = ecs_vector_count(type);
ecs_entity_t *tags = ecs_vector_first(pipeline_type->normalized, ecs_entity_t);
int32_t tag_count = ecs_vector_count(pipeline_type->normalized);
ecs_entity_t result = 0;
for (c = 0; c < count; c ++) {
ecs_entity_t comp = sys_comps[c];
for (t = 0; t < tag_count; t ++) {
if (comp == tags[t]) {
result = comp;
break;
}
}
if (result) {
break;
}
}
ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(result < INT_MAX, ECS_INTERNAL_ERROR, NULL);
return (int)result;
}
typedef enum ComponentWriteState {
NotWritten = 0,
WriteToMain,
WriteToStage
} ComponentWriteState;
typedef struct write_state_t {
ecs_map_t *components;
bool wildcard;
} write_state_t;
static
int32_t get_write_state(
ecs_map_t *write_state,
ecs_entity_t component)
{
int32_t *ptr = ecs_map_get(write_state, int32_t, component);
if (ptr) {
return *ptr;
} else {
return 0;
}
}
static
void set_write_state(
write_state_t *write_state,
ecs_entity_t component,
int32_t value)
{
if (component == EcsWildcard) {
ecs_assert(value == WriteToStage, ECS_INTERNAL_ERROR, NULL);
write_state->wildcard = true;
} else {
ecs_map_set(write_state->components, component, &value);
}
}
static
void reset_write_state(
write_state_t *write_state)
{
ecs_map_clear(write_state->components);
write_state->wildcard = false;
}
static
bool check_column_component(
ecs_sig_column_t *column,
bool is_active,
ecs_entity_t component,
write_state_t *write_state)
{
int32_t state = get_write_state(write_state->components, component);
if ((column->from_kind == EcsFromAny || column->from_kind == EcsFromOwned)
&& column->oper_kind != EcsOperNot)
{
switch(column->inout_kind) {
case EcsInOut:
case EcsIn:
if (state == WriteToStage) {
return true;
} else if (write_state->wildcard) {
return true;
}
// fall through
case EcsOut:
if (is_active && column->inout_kind != EcsIn) {
set_write_state(write_state, component, WriteToMain);
}
};
} else if (column->from_kind == EcsFromEmpty ||
column->oper_kind == EcsOperNot)
{
switch(column->inout_kind) {
case EcsInOut:
case EcsOut:
if (is_active) {
set_write_state(write_state, component, WriteToStage);
}
break;
default:
break;
};
}
return false;
}
static
bool check_column(
ecs_sig_column_t *column,
bool is_active,
write_state_t *write_state)
{
if (column->oper_kind != EcsOperOr) {
return check_column_component(
column, is_active, column->is.component, write_state);
}
return false;
}
static
bool build_pipeline(
ecs_world_t *world,
ecs_entity_t pipeline,
EcsPipelineQuery *pq)
{
(void)pipeline;
ecs_query_iter(pq->query);
if (pq->match_count == pq->query->match_count) {
/* No need to rebuild the pipeline */
return false;
}
ecs_trace_2("rebuilding pipeline #[green]%s",
ecs_get_name(world, pipeline));
world->stats.pipeline_build_count_total ++;
write_state_t ws = {
.components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID),
.wildcard = false
};
ecs_pipeline_op_t *op = NULL;
ecs_vector_t *ops = NULL;
ecs_query_t *query = pq->build_query;
if (pq->ops) {
ecs_vector_free(pq->ops);
}
/* Iterate systems in pipeline, add ops for running / merging */
ecs_iter_t it = ecs_query_iter(query);
while (ecs_query_next(&it)) {
EcsSystem *sys = ecs_column(&it, EcsSystem, 1);
int i;
for (i = 0; i < it.count; i ++) {
ecs_query_t *q = sys[i].query;
if (!q) {
continue;
}
bool needs_merge = false;
bool is_active = !ecs_has_entity(
world, it.entities[i], EcsInactive);
ecs_vector_each(q->sig.columns, ecs_sig_column_t, column, {
needs_merge |= check_column(column, is_active, &ws);
});
if (needs_merge) {
/* After merge all components will be merged, so reset state */
reset_write_state(&ws);
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) {
ecs_vector_each(q->sig.columns, ecs_sig_column_t, column, {
needs_merge |= check_column(column, 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_vector_add(&ops, ecs_pipeline_op_t);
op->count = 0;
}
/* Don't increase count for inactive systems, as they are ignored by
* the query used to run the pipeline. */
if (is_active) {
op->count ++;
}
}
}
ecs_map_free(ws.components);
/* Force sort of query as this could increase the match_count */
pq->match_count = pq->query->match_count;
pq->ops = ops;
return true;
}
static
int32_t iter_reset(
const EcsPipelineQuery *pq,
ecs_iter_t *iter_out,
ecs_pipeline_op_t **op_out,
ecs_entity_t move_to)
{
ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t);
int32_t ran_since_merge = 0;
ecs_iter_t it = ecs_query_iter(pq->query);
while (ecs_query_next(&it)) {
int32_t i;
for(i = 0; i < it.count; i ++) {
ecs_entity_t e = it.entities[i];
ran_since_merge ++;
if (ran_since_merge == op->count) {
ran_since_merge = 0;
op ++;
}
if (e == move_to) {
*iter_out = it;
*op_out = op;
return i;
}
}
}
ecs_abort(ECS_UNSUPPORTED, NULL);
return -1;
}
int32_t ecs_pipeline_update(
ecs_world_t *world,
ecs_entity_t pipeline)
{
EcsPipelineQuery *pq = ecs_get_mut(world, pipeline, EcsPipelineQuery, NULL);
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL);
if (build_pipeline(world, pipeline, pq)) {
return ecs_vector_count(pq->ops);
} else {
return 0;
}
}
int32_t ecs_pipeline_begin(
ecs_world_t *world,
ecs_entity_t pipeline)
{
ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL);
ecs_eval_component_monitors(world);
EcsPipelineQuery *pq = ecs_get_mut(
world, pipeline, EcsPipelineQuery, NULL);
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL);
build_pipeline(world, pipeline, pq);
return ecs_vector_count(pq->ops);
}
void ecs_pipeline_end(
ecs_world_t *world)
{
(void)world;
}
void ecs_pipeline_progress(
ecs_world_t *world,
ecs_entity_t pipeline,
FLECS_FLOAT delta_time)
{
const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery);
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_vector_t *ops = pq->ops;
ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t);
ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t);
int32_t ran_since_merge = 0;
ecs_worker_begin(world);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_iter_t it = ecs_query_iter(pq->query);
while (ecs_query_next(&it)) {
EcsSystem *sys = ecs_column(&it, EcsSystem, 1);
int32_t i;
for(i = 0; i < it.count; i ++) {
ecs_entity_t e = it.entities[i];
ecs_run_intern(world, stage, e, &sys[i], delta_time, 0, 0,
NULL, NULL, false);
ran_since_merge ++;
world->stats.systems_ran_frame ++;
if (op != op_last && ran_since_merge == op->count) {
ran_since_merge = 0;
op++;
/* If the set of matched systems changed as a result of the
* merge, we have to reset the iterator and move it to our
* current position (system). If there are a lot of systems
* in the pipeline this can be an expensive operation, but
* should happen infrequently. */
if (ecs_worker_sync(world)) {
i = iter_reset(pq, &it, &op, e);
op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t);
sys = ecs_column(&it, EcsSystem, 1);
}
}
}
}
ecs_worker_end(world);
}
static
void add_pipeline_tags_to_sig(
ecs_world_t *world,
ecs_sig_t *sig,
ecs_type_t type)
{
(void)world;
int32_t i, count = ecs_vector_count(type);
ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t);
for (i = 0; i < count; i ++) {
if (!i) {
ecs_sig_add(world, sig, EcsFromAny, EcsOperAnd, EcsIn, entities[i],
0, NULL);
} else {
ecs_sig_add(world, sig, EcsFromAny, EcsOperOr, EcsIn, entities[i],
0, NULL);
}
}
}
static
void EcsOnAddPipeline(
ecs_iter_t *it)
{
ecs_world_t *world = it->world;
ecs_entity_t *entities = it->entities;
int32_t i;
for (i = it->count - 1; i >= 0; i --) {
ecs_entity_t pipeline = entities[i];
ecs_sig_t sig = { 0 };
const EcsType *type_ptr = ecs_get(world, pipeline, EcsType);
ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
#ifndef NDEBUG
char *str = ecs_type_str(world, type_ptr->normalized);
ecs_trace_1("pipeline #[green]%s#[normal] created with #[red][%s]",
ecs_get_name(world, pipeline), str);
ecs_os_free(str);
#endif
ecs_log_push();
/* Build signature for pipeline quey that matches EcsSystems, has the
* pipeline as a XOR column, and ignores systems with EcsInactive and
* EcsDisabledIntern. Note that EcsDisabled is automatically ignored by
* the regular query matching */
ecs_sig_add(world, &sig, EcsFromAny, EcsOperAnd, EcsIn,
ecs_typeid(EcsSystem), 0, NULL);
ecs_sig_add(world, &sig, EcsFromAny, EcsOperNot, EcsIn, EcsInactive, 0, NULL);
ecs_sig_add(world, &sig, EcsFromAny, EcsOperNot, EcsIn,
EcsDisabledIntern, 0, NULL);
add_pipeline_tags_to_sig(world, &sig, type_ptr->normalized);
/* Create the query. Sort the query by system id and phase */
ecs_query_t *query = ecs_query_new_w_sig(world, 0, &sig);
ecs_query_order_by(world, query, 0, compare_entity);
ecs_query_group_by(world, query, pipeline, rank_phase);
/* Build signature for pipeline build query. The build query includes
* systems that are inactive, as an inactive system may become active as
* a result of another system, and as a result the correct merge
* operations need to be put in place. */
ecs_sig_add(world, &sig, EcsFromAny, EcsOperAnd, EcsIn,
ecs_typeid(EcsSystem), 0, NULL);
ecs_sig_add(world, &sig, EcsFromAny, EcsOperNot, EcsIn,
EcsDisabledIntern, 0, NULL);
add_pipeline_tags_to_sig(world, &sig, type_ptr->normalized);
/* Use the same sorting functions for the build query */
ecs_query_t *build_query = ecs_query_new_w_sig(world, 0, &sig);
ecs_query_order_by(world, build_query, 0, compare_entity);
ecs_query_group_by(world, build_query, pipeline, rank_phase);
EcsPipelineQuery *pq = ecs_get_mut(
world, pipeline, EcsPipelineQuery, NULL);
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
pq->query = query;
pq->build_query = build_query;
pq->match_count = -1;
pq->ops = NULL;
ecs_log_pop();
}
}
/* -- Public API -- */
bool ecs_progress(
ecs_world_t *world,
FLECS_FLOAT user_delta_time)
{
ecs_frame_begin(world, user_delta_time);
ecs_workers_progress(world);
ecs_frame_end(world);
return !world->should_quit;
}
void ecs_set_time_scale(
ecs_world_t *world,
FLECS_FLOAT scale)
{
world->stats.time_scale = scale;
}
void ecs_reset_clock(
ecs_world_t *world)
{
world->stats.world_time_total = 0;
world->stats.world_time_total_raw = 0;
}
void ecs_quit(
ecs_world_t *world)
{
ecs_get_stage(&world);
world->should_quit = true;
}
void ecs_deactivate_systems(
ecs_world_t *world)
{
ecs_assert(!world->in_progress, ECS_INVALID_WHILE_ITERATING, NULL);
ecs_entity_t pipeline = world->pipeline;
const EcsPipelineQuery *pq = ecs_get( world, pipeline, EcsPipelineQuery);
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
/* Iterate over all systems, add EcsInvalid tag if queries aren't matched
* with any tables */
ecs_iter_t it = ecs_query_iter(pq->build_query);
/* Make sure that we defer adding the inactive tags until after iterating
* the query */
ecs_defer_none(world, &world->stage);
while( ecs_query_next(&it)) {
EcsSystem *sys = ecs_column(&it, EcsSystem, 1);
int32_t i;
for (i = 0; i < it.count; i ++) {
ecs_query_t *query = sys[i].query;
if (query) {
if (!ecs_vector_count(query->tables)) {
ecs_add_entity(world, it.entities[i], EcsInactive);
}
}
}
}
ecs_defer_flush(world, &world->stage);
}
void ecs_set_pipeline(
ecs_world_t *world,
ecs_entity_t pipeline)
{
ecs_assert( ecs_get(world, pipeline, EcsPipelineQuery) != NULL,
ECS_INVALID_PARAMETER, NULL);
world->pipeline = pipeline;
}
ecs_entity_t ecs_get_pipeline(
ecs_world_t *world)
{
return world->pipeline;
}
ecs_entity_t ecs_new_pipeline(
ecs_world_t *world,
ecs_entity_t e,
const char *name,
const char *expr)
{
assert(world->magic == ECS_WORLD_MAGIC);
ecs_entity_t result = ecs_new_type(world, e, name, expr);
ecs_assert(ecs_get(world, result, EcsType) != NULL,
ECS_INTERNAL_ERROR, NULL);
ecs_add_entity(world, result, EcsPipeline);
return result;
}
/* -- Module implementation -- */
static
void FlecsPipelineFini(
ecs_world_t *world,
void *ctx)
{
(void)ctx;
if (world->workers) {
ecs_set_threads(world, 0);
}
}
void FlecsPipelineImport(
ecs_world_t *world)
{
ECS_MODULE(world, FlecsPipeline);
ECS_IMPORT(world, FlecsSystem);
ecs_set_name_prefix(world, "Ecs");
ecs_bootstrap_tag(world, EcsPipeline);
ecs_bootstrap_component(world, EcsPipelineQuery);
/* Phases of the builtin pipeline are regular entities. Names are set so
* they can be resolved by type expressions. */
ecs_bootstrap_tag(world, EcsPreFrame);
ecs_bootstrap_tag(world, EcsOnLoad);
ecs_bootstrap_tag(world, EcsPostLoad);
ecs_bootstrap_tag(world, EcsPreUpdate);
ecs_bootstrap_tag(world, EcsOnUpdate);
ecs_bootstrap_tag(world, EcsOnValidate);
ecs_bootstrap_tag(world, EcsPostUpdate);
ecs_bootstrap_tag(world, EcsPreStore);
ecs_bootstrap_tag(world, EcsOnStore);
ecs_bootstrap_tag(world, EcsPostFrame);
ECS_TYPE_IMPL(EcsPipelineQuery);
/* Set ctor and dtor for PipelineQuery */
ecs_set(world, ecs_typeid(EcsPipelineQuery), EcsComponentLifecycle, {
.ctor = ecs_ctor(EcsPipelineQuery),
.dtor = ecs_dtor(EcsPipelineQuery)
});
/* When the Pipeline tag is added a pipeline will be created */
ECS_TRIGGER(world, EcsOnAddPipeline, EcsOnAdd, Pipeline);
/* Create the builtin pipeline */
world->pipeline = ecs_new_pipeline(world, 0, "BuiltinPipeline",
"PreFrame, OnLoad, PostLoad, PreUpdate, OnUpdate,"
" OnValidate, PostUpdate, PreStore, OnStore, PostFrame");
/* Cleanup thread administration when world is destroyed */
ecs_atfini(world, FlecsPipelineFini, NULL);
}
#endif
#ifdef FLECS_TIMER
ecs_type_t ecs_type(EcsTimer);
ecs_type_t ecs_type(EcsRateFilter);
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_column(it, EcsTimer, 1);
EcsTickSource *tick_source = ecs_column(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;
}
FLECS_FLOAT time_elapsed = timer[i].time + it->world->stats.delta_time_raw;
FLECS_FLOAT timeout = timer[i].timeout;
if (time_elapsed >= timeout) {
FLECS_FLOAT 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_column(it, EcsRateFilter, 1);
EcsTickSource *tick_dst = ecs_column(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;
}
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;
}
}
}
ecs_entity_t ecs_set_timeout(
ecs_world_t *world,
ecs_entity_t timer,
FLECS_FLOAT timeout)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
timer = ecs_set(world, timer, EcsTimer, {
.timeout = timeout,
.single_shot = true,
.active = true
});
EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL);
if (system_data) {
system_data->tick_source = timer;
}
return timer;
}
FLECS_FLOAT ecs_get_timeout(
ecs_world_t *world,
ecs_entity_t timer)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(timer != 0, ECS_INVALID_PARAMETER, NULL);
EcsTimer *value = ecs_get_mut(world, timer, EcsTimer, NULL);
if (value) {
return value->timeout;
} else {
return 0;
}
}
ecs_entity_t ecs_set_interval(
ecs_world_t *world,
ecs_entity_t timer,
FLECS_FLOAT interval)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
timer = ecs_set(world, timer, EcsTimer, {
.timeout = interval,
.active = true
});
EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL);
if (system_data) {
system_data->tick_source = timer;
}
return timer;
}
FLECS_FLOAT ecs_get_interval(
ecs_world_t *world,
ecs_entity_t timer)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
if (!timer) {
return 0;
}
EcsTimer *value = ecs_get_mut(world, timer, EcsTimer, NULL);
if (value) {
return value->timeout;
} else {
return 0;
}
}
void ecs_start_timer(
ecs_world_t *world,
ecs_entity_t timer)
{
EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL);
ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
ptr->active = true;
ptr->time = 0;
}
void ecs_stop_timer(
ecs_world_t *world,
ecs_entity_t timer)
{
EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL);
ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
ptr->active = false;
}
ecs_entity_t ecs_set_rate_filter(
ecs_world_t *world,
ecs_entity_t filter,
int32_t rate,
ecs_entity_t source)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
filter = ecs_set(world, filter, EcsRateFilter, {
.rate = rate,
.src = source
});
EcsSystem *system_data = ecs_get_mut(world, filter, EcsSystem, NULL);
if (system_data) {
system_data->tick_source = filter;
}
return filter;
}
void ecs_set_tick_source(
ecs_world_t *world,
ecs_entity_t system,
ecs_entity_t tick_source)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(system != 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(tick_source != 0, ECS_INVALID_PARAMETER, NULL);
EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL);
ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL);
system_data->tick_source = tick_source;
}
void FlecsTimerImport(
ecs_world_t *world)
{
ECS_MODULE(world, FlecsTimer);
ECS_IMPORT(world, FlecsPipeline);
ecs_set_name_prefix(world, "Ecs");
ecs_bootstrap_component(world, EcsTimer);
ecs_bootstrap_component(world, EcsRateFilter);
/* Add EcsTickSource to timers and rate filters */
ECS_SYSTEM(world, AddTickSource, EcsPreFrame, [in] Timer || RateFilter, [out] !flecs.system.TickSource);
/* Timer handling */
ECS_SYSTEM(world, ProgressTimers, EcsPreFrame, Timer, flecs.system.TickSource);
/* Rate filter handling */
ECS_SYSTEM(world, ProgressRateFilters, EcsPreFrame, [in] RateFilter, [out] flecs.system.TickSource);
}
#endif
#ifdef FLECS_SYSTEM
/* Global type variables */
ECS_TYPE_DECL(EcsComponentLifecycle);
ECS_TYPE_DECL(EcsTrigger);
ECS_TYPE_DECL(EcsSystem);
ECS_TYPE_DECL(EcsTickSource);
ECS_TYPE_DECL(EcsSignatureExpr);
ECS_TYPE_DECL(EcsSignature);
ECS_TYPE_DECL(EcsQuery);
ECS_TYPE_DECL(EcsIterAction);
ECS_TYPE_DECL(EcsContext);
static
ecs_on_demand_in_t* get_in_component(
ecs_map_t *component_map,
ecs_entity_t component)
{
ecs_on_demand_in_t *in = ecs_map_get(
component_map, ecs_on_demand_in_t, component);
if (!in) {
ecs_on_demand_in_t in_value = {0};
ecs_map_set(component_map, component, &in_value);
in = ecs_map_get(component_map, ecs_on_demand_in_t, component);
ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL);
}
return in;
}
static
void activate_in_columns(
ecs_world_t *world,
ecs_query_t *query,
ecs_map_t *component_map,
bool activate)
{
ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t);
int32_t i, count = ecs_vector_count(query->sig.columns);
for (i = 0; i < count; i ++) {
if (columns[i].inout_kind == EcsIn) {
ecs_on_demand_in_t *in = get_in_component(
component_map, columns[i].is.component);
ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL);
in->count += activate ? 1 : -1;
ecs_assert(in->count >= 0, ECS_INTERNAL_ERROR, NULL);
/* If this is the first system that registers the in component, walk
* over all already registered systems to enable them */
if (in->systems &&
((activate && in->count == 1) ||
(!activate && !in->count)))
{
ecs_on_demand_out_t **out = ecs_vector_first(
in->systems, ecs_on_demand_out_t*);
int32_t s, in_count = ecs_vector_count(in->systems);
for (s = 0; s < in_count; s ++) {
/* Increase the count of the system with the out params */
out[s]->count += activate ? 1 : -1;
/* If this is the first out column that is requested from
* the OnDemand system, enable it */
if (activate && out[s]->count == 1) {
ecs_remove_entity(world, out[s]->system, EcsDisabledIntern);
} else if (!activate && !out[s]->count) {
ecs_add_entity(world, out[s]->system, EcsDisabledIntern);
}
}
}
}
}
}
static
void register_out_column(
ecs_map_t *component_map,
ecs_entity_t component,
ecs_on_demand_out_t *on_demand_out)
{
ecs_on_demand_in_t *in = get_in_component(component_map, component);
ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL);
on_demand_out->count += in->count;
ecs_on_demand_out_t **elem = ecs_vector_add(&in->systems, ecs_on_demand_out_t*);
*elem = on_demand_out;
}
static
void register_out_columns(
ecs_world_t *world,
ecs_entity_t system,
EcsSystem *system_data)
{
ecs_query_t *query = system_data->query;
ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t);
int32_t i, out_count = 0, count = ecs_vector_count(query->sig.columns);
for (i = 0; i < count; i ++) {
if (columns[i].inout_kind == EcsOut) {
if (!system_data->on_demand) {
system_data->on_demand = ecs_os_malloc(sizeof(ecs_on_demand_out_t));
ecs_assert(system_data->on_demand != NULL, ECS_OUT_OF_MEMORY, NULL);
system_data->on_demand->system = system;
system_data->on_demand->count = 0;
}
/* If column operator is NOT and the inout kind is [out], the system
* explicitly states that it will create the component (it is not
* there, yet it is an out column). In this case it doesn't make
* sense to wait until [in] columns get activated (matched with
* entities) since the component is not there yet. Therefore add it
* to the on_enable_components list, so this system will be enabled
* when a [in] column is enabled, rather than activated */
ecs_map_t *component_map;
if (columns[i].oper_kind == EcsOperNot) {
component_map = world->on_enable_components;
} else {
component_map = world->on_activate_components;
}
register_out_column(
component_map, columns[i].is.component,
system_data->on_demand);
out_count ++;
}
}
/* If there are no out columns in the on-demand system, the system will
* never be enabled */
ecs_assert(out_count != 0, ECS_NO_OUT_COLUMNS, ecs_get_name(world, system));
}
static
void invoke_status_action(
ecs_world_t *world,
ecs_entity_t system,
const EcsSystem *system_data,
ecs_system_status_t status)
{
ecs_system_status_action_t action = system_data->status_action;
if (action) {
action(world, system, status, system_data->status_ctx);
}
}
/* Invoked when system becomes active or inactive */
void ecs_system_activate(
ecs_world_t *world,
ecs_entity_t system,
bool activate,
const EcsSystem *system_data)
{
ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL);
if (activate) {
ecs_remove_entity(world, system, EcsInactive);
}
if (!system_data) {
system_data = ecs_get(world, system, EcsSystem);
}
if (!system_data || !system_data->query) {
return;
}
/* If system contains in columns, signal that they are now in use */
activate_in_columns(
world, system_data->query, world->on_activate_components, activate);
/* Invoke system status action */
invoke_status_action(world, system, system_data,
activate ? EcsSystemActivated : EcsSystemDeactivated);
ecs_trace_2("system #[green]%s#[reset] %s",
ecs_get_name(world, system),
activate ? "activated" : "deactivated");
}
/* Actually enable or disable system */
static
void ecs_enable_system(
ecs_world_t *world,
ecs_entity_t system,
EcsSystem *system_data,
bool enabled)
{
ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL);
ecs_query_t *query = system_data->query;
if (!query) {
return;
}
if (ecs_vector_count(query->tables)) {
/* Only (de)activate system if it has non-empty tables. */
ecs_system_activate(world, system, enabled, system_data);
system_data = ecs_get_mut(world, system, EcsSystem, NULL);
}
/* Enable/disable systems that trigger on [in] enablement */
activate_in_columns(
world,
query,
world->on_enable_components,
enabled);
/* Invoke action for enable/disable status */
invoke_status_action(
world, system, system_data,
enabled ? EcsSystemEnabled : EcsSystemDisabled);
}
static
void ecs_init_system(
ecs_world_t *world,
ecs_entity_t system,
ecs_iter_action_t action,
ecs_query_t *query,
void *ctx)
{
ecs_assert(!world->in_progress, ECS_INVALID_WHILE_ITERATING, NULL);
/* Add & initialize the EcsSystem component */
bool is_added = false;
EcsSystem *sptr = ecs_get_mut(world, system, EcsSystem, &is_added);
ecs_assert(sptr != NULL, ECS_INTERNAL_ERROR, NULL);
if (!is_added) {
ecs_assert(sptr->query == query, ECS_INVALID_PARAMETER, NULL);
} else {
memset(sptr, 0, sizeof(EcsSystem));
sptr->query = query;
sptr->entity = system;
sptr->tick_source = 0;
sptr->time_spent = 0;
}
/* Sanity check to make sure creating the query didn't add any additional
* tags or components to the system */
sptr->action = action;
sptr->ctx = ctx;
/* Only run this code when the system is created for the first time */
if (is_added) {
/* If tables have been matched with this system it is active, and we
* should activate the in-columns, if any. This will ensure that any
* OnDemand systems get enabled. */
if (ecs_vector_count(query->tables)) {
ecs_system_activate(world, system, true, sptr);
} else {
/* If system isn't matched with any tables, mark it as inactive. This
* causes it to be ignored by the main loop. When the system matches
* with a table it will be activated. */
ecs_add_entity(world, system, EcsInactive);
}
/* If system is enabled, trigger enable components */
activate_in_columns(world, query, world->on_enable_components, true);
/* Check if all non-table column constraints are met. If not, disable
* system (system will be enabled once constraints are met) */
if (!ecs_sig_check_constraints(world, &query->sig)) {
ecs_add_entity(world, system, EcsDisabledIntern);
}
/* If the query has a OnDemand system tag, register its [out] columns */
if (ecs_has_entity(world, system, EcsOnDemand)) {
register_out_columns(world, system, sptr);
ecs_assert(sptr->on_demand != NULL, ECS_INTERNAL_ERROR, NULL);
/* If there are no systems currently interested in any of the [out]
* columns of the on demand system, disable it */
if (!sptr->on_demand->count) {
ecs_add_entity(world, system, EcsDisabledIntern);
}
}
/* Check if system has out columns */
int32_t i, count = ecs_vector_count(query->sig.columns);
ecs_sig_column_t *columns = ecs_vector_first(
query->sig.columns, ecs_sig_column_t);
for (i = 0; i < count; i ++) {
if (columns[i].inout_kind != EcsIn) {
break;
}
}
}
ecs_trace_1("system #[green]%s#[reset] created with #[red]%s",
ecs_get_name(world, system), query->sig.expr);
}
/* -- Public API -- */
void ecs_enable(
ecs_world_t *world,
ecs_entity_t entity,
bool enabled)
{
assert(world->magic == ECS_WORLD_MAGIC);
const EcsType *type_ptr = ecs_get( world, entity, EcsType);
if (type_ptr) {
/* If entity is a type, disable all entities in the type */
ecs_vector_each(type_ptr->normalized, ecs_entity_t, e, {
ecs_enable(world, *e, enabled);
});
} else {
if (enabled) {
ecs_remove_entity(world, entity, EcsDisabled);
} else {
ecs_add_entity(world, entity, EcsDisabled);
}
}
}
void ecs_set_system_status_action(
ecs_world_t *world,
ecs_entity_t system,
ecs_system_status_action_t action,
const void *ctx)
{
EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL);
ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL);
system_data->status_action = action;
system_data->status_ctx = (void*)ctx;
if (!ecs_has_entity(world, system, EcsDisabled)) {
/* If system is already enabled, generate enable status. The API
* should guarantee that it exactly matches enable-disable
* notifications and activate-deactivate notifications. */
invoke_status_action(world, system, system_data, EcsSystemEnabled);
/* If column system has active (non-empty) tables, also generate the
* activate status. */
if (ecs_vector_count(system_data->query->tables)) {
invoke_status_action(
world, system, system_data, EcsSystemActivated);
}
}
}
ecs_entity_t ecs_run_intern(
ecs_world_t *world,
ecs_stage_t *stage,
ecs_entity_t system,
EcsSystem *system_data,
FLECS_FLOAT delta_time,
int32_t offset,
int32_t limit,
const ecs_filter_t *filter,
void *param,
bool ran_by_app)
{
if (!param) {
param = system_data->ctx;
}
FLECS_FLOAT time_elapsed = delta_time;
ecs_entity_t tick_source = system_data->tick_source;
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;
}
}
ecs_time_t time_start;
bool measure_time = world->measure_system_time;
if (measure_time) {
ecs_os_get_time(&time_start);
}
#ifndef NDEBUG
stage->system = system;
stage->system_columns = system_data->query->sig.columns;
#endif
bool defer = false;
if (!stage->defer) {
ecs_defer_begin(stage->world);
defer = true;
}
/* Prepare the query iterator */
ecs_iter_t it = ecs_query_iter_page(system_data->query, offset, limit);
it.world = stage->world;
it.system = system;
it.delta_time = delta_time;
it.delta_system_time = time_elapsed;
it.world_time = world->stats.world_time_total;
it.frame_offset = offset;
/* Set param if provided, otherwise use system context */
if (param) {
it.param = param;
} else {
it.param = system_data->ctx;
}
ecs_iter_action_t action = system_data->action;
/* If no filter is provided, just iterate tables & invoke action */
if (ran_by_app || world == stage->world) {
while (ecs_query_next_w_filter(&it, filter)) {
action(&it);
}
} else {
ecs_thread_t *thread = (ecs_thread_t*)stage->world;
int32_t total = ecs_vector_count(world->workers);
int32_t current = thread->index;
while (ecs_query_next_worker(&it, current, total)) {
action(&it);
}
}
if (defer) {
ecs_defer_end(stage->world);
}
if (measure_time) {
system_data->time_spent += (FLECS_FLOAT)ecs_time_measure(&time_start);
}
#ifndef NDEBUG
stage->system = 0;
stage->system_columns = NULL;
#endif
system_data->invoke_count ++;
return it.interrupted_by;
}
/* -- Public API -- */
ecs_entity_t ecs_run_w_filter(
ecs_world_t *world,
ecs_entity_t system,
FLECS_FLOAT delta_time,
int32_t offset,
int32_t limit,
const ecs_filter_t *filter,
void *param)
{
ecs_stage_t *stage = ecs_get_stage(&world);
bool in_progress = ecs_staging_begin(world);
EcsSystem *system_data = (EcsSystem*)ecs_get(
world, system, EcsSystem);
assert(system_data != NULL);
ecs_entity_t interrupted_by = ecs_run_intern(
world, stage, system, system_data, delta_time, offset, limit,
filter, param, true);
/* If world wasn't in progress when we entered this function, we need to
* merge and reset the in_progress value */
if (!in_progress) {
ecs_staging_end(world);
}
return interrupted_by;
}
ecs_entity_t ecs_run(
ecs_world_t *world,
ecs_entity_t system,
FLECS_FLOAT delta_time,
void *param)
{
return ecs_run_w_filter(world, system, delta_time, 0, 0, NULL, param);
}
void ecs_run_monitor(
ecs_world_t *world,
ecs_matched_query_t *monitor,
ecs_entities_t *components,
int32_t row,
int32_t count,
ecs_entity_t *entities)
{
ecs_query_t *query = monitor->query;
ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_entity_t system = query->system;
const EcsSystem *system_data = ecs_get(world, system, EcsSystem);
ecs_assert(system_data != NULL, ECS_INTERNAL_ERROR, NULL);
if (!system_data->action) {
return;
}
ecs_iter_t it = {0};
ecs_query_set_iter( world, query, &it,
monitor->matched_table_index, row, count);
it.world = world;
it.triggered_by = components;
it.param = system_data->ctx;
if (entities) {
it.entities = entities;
}
it.system = system;
system_data->action(&it);
}
/* Generic constructor to initialize a component to 0 */
static
void sys_ctor_init_zero(
ecs_world_t *world,
ecs_entity_t component,
const ecs_entity_t *entities,
void *ptr,
size_t size,
int32_t count,
void *ctx)
{
(void)world;
(void)component;
(void)entities;
(void)ctx;
memset(ptr, 0, size * (size_t)count);
}
/* System destructor */
static
void ecs_colsystem_dtor(
ecs_world_t *world,
ecs_entity_t component,
const ecs_entity_t *entities,
void *ptr,
size_t size,
int32_t count,
void *ctx)
{
(void)component;
(void)ctx;
(void)size;
EcsSystem *system_data = ptr;
int i;
for (i = 0; i < count; i ++) {
EcsSystem *cur = &system_data[i];
ecs_entity_t e = entities[i];
/* Invoke Deactivated action for active systems */
if (cur->query && ecs_vector_count(cur->query->tables)) {
invoke_status_action(world, e, ptr, EcsSystemDeactivated);
}
/* Invoke Disabled action for enabled systems */
if (!ecs_has_entity(world, e, EcsDisabled) &&
!ecs_has_entity(world, e, EcsDisabledIntern))
{
invoke_status_action(world, e, ptr, EcsSystemDisabled);
}
ecs_os_free(cur->on_demand);
}
}
/* Register a trigger for a component */
static
EcsTrigger* trigger_find_or_create(
ecs_vector_t **triggers,
ecs_entity_t entity)
{
ecs_vector_each(*triggers, EcsTrigger, trigger, {
if (trigger->self == entity) {
return trigger;
}
});
EcsTrigger *result = ecs_vector_add(triggers, EcsTrigger);
return result;
}
static
void trigger_set(
ecs_world_t *world,
const ecs_entity_t *entities,
EcsTrigger *ct,
int32_t count)
{
EcsTrigger *el = NULL;
int i;
for (i = 0; i < count; i ++) {
ecs_entity_t c = ct[i].component;
ecs_c_info_t *c_info = ecs_get_or_create_c_info(world, c);
switch(ct[i].kind) {
case EcsOnAdd:
el = trigger_find_or_create(&c_info->on_add, entities[i]);
break;
case EcsOnRemove:
el = trigger_find_or_create(&c_info->on_remove, entities[i]);
break;
default:
ecs_abort(ECS_INVALID_PARAMETER, NULL);
break;
}
ecs_assert(el != NULL, ECS_INTERNAL_ERROR, NULL);
*el = ct[i];
el->self = entities[i];
ecs_notify_tables(world, &(ecs_table_event_t) {
.kind = EcsTableComponentInfo,
.component = c
});
ecs_trace_1("trigger #[green]%s#[normal] created for component #[red]%s",
ct[i].kind == EcsOnAdd
? "OnAdd"
: "OnRemove", ecs_get_name(world, c));
}
}
static
void OnSetTrigger(
ecs_iter_t *it)
{
EcsTrigger *ct = ecs_column(it, EcsTrigger, 1);
trigger_set(it->world, it->entities, ct, it->count);
}
static
void OnSetTriggerCtx(
ecs_iter_t *it)
{
EcsTrigger *ct = ecs_column(it, EcsTrigger, 1);
EcsContext *ctx = ecs_column(it, EcsContext, 2);
int32_t i;
for (i = 0; i < it->count; i ++) {
ct[i].ctx = (void*)ctx[i].ctx;
}
trigger_set(it->world, it->entities, ct, it->count);
}
/* System that registers component lifecycle callbacks */
static
void OnSetComponentLifecycle(
ecs_iter_t *it)
{
EcsComponentLifecycle *cl = ecs_column(it, EcsComponentLifecycle, 1);
ecs_world_t *world = it->world;
int i;
for (i = 0; i < it->count; i ++) {
ecs_entity_t e = it->entities[i];
ecs_set_component_actions_w_entity(world, e, &cl[i]);
}
}
/* Disable system when EcsDisabled is added */
static
void DisableSystem(
ecs_iter_t *it)
{
EcsSystem *system_data = ecs_column(it, EcsSystem, 1);
int32_t i;
for (i = 0; i < it->count; i ++) {
ecs_enable_system(
it->world, it->entities[i], &system_data[i], false);
}
}
/* Enable system when EcsDisabled is removed */
static
void EnableSystem(
ecs_iter_t *it)
{
EcsSystem *system_data = ecs_column(it, EcsSystem, 1);
int32_t i;
for (i = 0; i < it->count; i ++) {
ecs_enable_system(
it->world, it->entities[i], &system_data[i], true);
}
}
/* Parse a signature expression into the ecs_sig_t data structure */
static
void CreateSignature(
ecs_iter_t *it)
{
ecs_world_t *world = it->world;
ecs_entity_t *entities = it->entities;
EcsSignatureExpr *signature = ecs_column(it, EcsSignatureExpr, 1);
int32_t i;
for (i = 0; i < it->count; i ++) {
ecs_entity_t e = entities[i];
const char *name = ecs_get_name(world, e);
/* Parse the signature and add the result to the entity */
EcsSignature sig = {0};
ecs_sig_init(world, name, signature[0].expr, &sig.signature);
ecs_set_ptr(world, e, EcsSignature, &sig);
/* If sig has FromSystem columns, add components to the entity */
ecs_vector_each(sig.signature.columns, ecs_sig_column_t, column, {
if (column->from_kind == EcsFromSystem) {
ecs_add_entity(world, e, column->is.component);
}
});
}
}
/* Create a query from a signature */
static
void CreateQuery(
ecs_iter_t *it)
{
ecs_world_t *world = it->world;
ecs_entity_t *entities = it->entities;
EcsSignature *signature = ecs_column(it, EcsSignature, 1);
int32_t i;
for (i = 0; i < it->count; i ++) {
ecs_entity_t e = entities[i];
if (!ecs_has(world, e, EcsQuery)) {
EcsQuery query = {0};
query.query = ecs_query_new_w_sig(world, e, &signature[i].signature);
ecs_set_ptr(world, e, EcsQuery, &query);
}
}
}
/* Create a system from a query and an action */
static
void CreateSystem(
ecs_iter_t *it)
{
ecs_world_t *world = it->world;
ecs_entity_t *entities = it->entities;
EcsQuery *query = ecs_column(it, EcsQuery, 1);
EcsIterAction *action = ecs_column(it, EcsIterAction, 2);
EcsContext *ctx = ecs_column(it, EcsContext, 3);
int32_t i;
for (i = 0; i < it->count; i ++) {
ecs_entity_t e = entities[i];
void *ctx_ptr = NULL;
if (ctx) {
ctx_ptr = (void*)ctx[i].ctx;
}
ecs_init_system(world, e, action[i].action, query[i].query, ctx_ptr);
}
}
static
void bootstrap_set_system(
ecs_world_t *world,
const char *name,
const char *expr,
ecs_iter_action_t action)
{
ecs_sig_t sig = {0};
ecs_entity_t sys = ecs_set(world, 0, EcsName, {.value = name});
ecs_add_entity(world, sys, EcsOnSet);
ecs_sig_init(world, name, expr, &sig);
ecs_query_t *query = ecs_query_new_w_sig(world, sys, &sig);
ecs_init_system(world, sys, action, query, NULL);
}
ecs_entity_t ecs_new_system(
ecs_world_t *world,
ecs_entity_t e,
const char *name,
ecs_entity_t tag,
const char *signature,
ecs_iter_action_t action)
{
ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL);
ecs_assert(!world->in_progress, ECS_INVALID_WHILE_ITERATING, NULL);
ecs_entity_t result = ecs_lookup_w_id(world, e, name);
if (!result) {
result = ecs_new_entity(world, 0, name, NULL);
}
if (tag) {
ecs_add_entity(world, result, tag);
}
bool added = false;
EcsSignatureExpr *expr = ecs_get_mut(world, result, EcsSignatureExpr, &added);
if (added) {
expr->expr = signature;
} else {
if (!expr->expr || !signature) {
if (expr->expr != signature) {
if (expr->expr && !strcmp(expr->expr, "0")) {
/* Ok */
} else if (signature && !strcmp(signature, "0")) {
/* Ok */
} else {
ecs_abort(ECS_ALREADY_DEFINED, NULL);
}
}
} else {
if (strcmp(expr->expr, signature)) {
ecs_abort(ECS_ALREADY_DEFINED, name);
}
}
}
ecs_modified(world, result, EcsSignatureExpr);
ecs_set(world, result, EcsIterAction, {action});
return result;
}
ecs_entity_t ecs_new_trigger(
ecs_world_t *world,
ecs_entity_t e,
const char *name,
ecs_entity_t kind,
const char *component_name,
ecs_iter_action_t action)
{
assert(world->magic == ECS_WORLD_MAGIC);
ecs_entity_t component = ecs_lookup_fullpath(world, component_name);
ecs_assert(component != 0, ECS_INVALID_COMPONENT_ID, component_name);
ecs_entity_t result = ecs_lookup_w_id(world, e, name);
if (!result) {
result = ecs_new_entity(world, 0, name, NULL);
}
bool added = false;
EcsTrigger *trigger = ecs_get_mut(world, result, EcsTrigger, &added);
if (added) {
trigger->kind = kind;
trigger->action = action;
trigger->component = component;
trigger->ctx = NULL;
} else {
if (trigger->kind != kind) {
ecs_abort(ECS_ALREADY_DEFINED, name);
}
if (trigger->component != component) {
ecs_abort(ECS_ALREADY_DEFINED, name);
}
if (trigger->action != action) {
trigger->action = action;
}
}
ecs_modified(world, result, EcsTrigger);
return result;
}
void FlecsSystemImport(
ecs_world_t *world)
{
ECS_MODULE(world, FlecsSystem);
ecs_set_name_prefix(world, "Ecs");
ecs_bootstrap_component(world, EcsComponentLifecycle);
ecs_bootstrap_component(world, EcsTrigger);
ecs_bootstrap_component(world, EcsSystem);
ecs_bootstrap_component(world, EcsTickSource);
ecs_bootstrap_component(world, EcsSignatureExpr);
ecs_bootstrap_component(world, EcsSignature);
ecs_bootstrap_component(world, EcsQuery);
ecs_bootstrap_component(world, EcsIterAction);
ecs_bootstrap_component(world, EcsContext);
ecs_bootstrap_tag(world, EcsOnAdd);
ecs_bootstrap_tag(world, EcsOnRemove);
ecs_bootstrap_tag(world, EcsOnSet);
ecs_bootstrap_tag(world, EcsUnSet);
ecs_bootstrap_tag(world, EcsDisabledIntern);
ecs_bootstrap_tag(world, EcsInactive);
/* Put EcsOnDemand and EcsMonitor 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);
ecs_bootstrap_tag(world, EcsOnDemand);
ecs_bootstrap_tag(world, EcsMonitor);
ecs_set_scope(world, old_scope);
ECS_TYPE_IMPL(EcsComponentLifecycle);
ECS_TYPE_IMPL(EcsTrigger);
ECS_TYPE_IMPL(EcsSystem);
ECS_TYPE_IMPL(EcsTickSource);
ECS_TYPE_IMPL(EcsSignatureExpr);
ECS_TYPE_IMPL(EcsSignature);
ECS_TYPE_IMPL(EcsQuery);
ECS_TYPE_IMPL(EcsIterAction);
ECS_TYPE_IMPL(EcsContext);
/* Bootstrap ctor and dtor for EcsSystem */
ecs_set_component_actions_w_entity(world, ecs_typeid(EcsSystem),
&(EcsComponentLifecycle) {
.ctor = sys_ctor_init_zero,
.dtor = ecs_colsystem_dtor
});
/* Create systems necessary to create systems */
bootstrap_set_system(world, "CreateSignature", "SignatureExpr", CreateSignature);
bootstrap_set_system(world, "CreateQuery", "Signature, IterAction", CreateQuery);
bootstrap_set_system(world, "CreateSystem", "Query, IterAction, ?Context", CreateSystem);
/* From here we can create systems */
/* Register OnSet system for EcsComponentLifecycle */
ECS_SYSTEM(world, OnSetComponentLifecycle, EcsOnSet, ComponentLifecycle, SYSTEM:Hidden);
/* Register OnSet system for triggers */
ECS_SYSTEM(world, OnSetTrigger, EcsOnSet, Trigger, SYSTEM:Hidden);
/* System that sets ctx for a trigger */
ECS_SYSTEM(world, OnSetTriggerCtx, EcsOnSet, Trigger, Context, SYSTEM:Hidden);
/* Monitors that trigger when a system is enabled or disabled */
ECS_SYSTEM(world, DisableSystem, EcsMonitor, System, Disabled || DisabledIntern, SYSTEM:Hidden);
ECS_SYSTEM(world, EnableSystem, EcsMonitor, System, !Disabled, !DisabledIntern, SYSTEM:Hidden);
}
#endif
#ifdef FLECS_SYSTEM
#ifdef FLECS_DBG
int ecs_dbg_system(
ecs_world_t *world,
ecs_entity_t system,
ecs_dbg_system_t *dbg_out)
{
const EcsSystem *system_data = ecs_get(world, system, EcsSystem);
if (!system_data) {
return -1;
}
*dbg_out = (ecs_dbg_system_t){.system = system};
dbg_out->active_table_count = ecs_vector_count(system_data->query->tables);
dbg_out->inactive_table_count = ecs_vector_count(system_data->query->empty_tables);
dbg_out->enabled = !ecs_has_entity(world, system, EcsDisabled);
ecs_vector_each(system_data->query->tables, ecs_matched_table_t, mt, {
ecs_table_t *table = mt->iter_data.table;
if (table) {
dbg_out->entities_matched_count += ecs_table_count(table);
}
});
/* Inactive tables are inactive because they are empty, so no need to
* iterate them */
return 0;
}
bool ecs_dbg_match_entity(
ecs_world_t *world,
ecs_entity_t entity,
ecs_entity_t system,
ecs_match_failure_t *failure_info_out)
{
ecs_dbg_entity_t dbg;
ecs_dbg_entity(world, entity, &dbg);
const EcsSystem *system_data = ecs_get(world, system, EcsSystem);
if (!system_data) {
failure_info_out->reason = EcsMatchNotASystem;
failure_info_out->column = -1;
return false;
}
return ecs_query_match(
world, dbg.table, system_data->query, failure_info_out);
}
ecs_type_t ecs_dbg_get_column_type(
ecs_world_t *world,
ecs_entity_t system,
int32_t column_index)
{
const EcsSystem *system_data = ecs_get(world, system, EcsSystem);
if (!system_data) {
return NULL;
}
ecs_sig_column_t *columns = ecs_vector_first(
system_data->query->sig.columns, ecs_sig_column_t);
int32_t count = ecs_vector_count(system_data->query->sig.columns);
if (count < column_index) {
return NULL;
}
ecs_sig_column_t *column = &columns[column_index - 1];
ecs_sig_oper_kind_t oper_kind = column->oper_kind;
ecs_type_t result;
switch(oper_kind) {
case EcsOperOr:
result = column->is.type;
break;
default:
result = ecs_type_from_entity(world, column->is.component);
break;
}
return result;
}
#endif
#endif
/** Parse callback that adds type to type identifier for ecs_new_type */
static
int parse_type_action(
ecs_world_t *world,
const char *name,
const char *sig,
int64_t column,
ecs_sig_from_kind_t from_kind,
ecs_sig_oper_kind_t oper_kind,
ecs_sig_inout_kind_t inout_kind,
ecs_entity_t role,
const char *entity_id,
const char *source_id,
const char *trait_id,
const char *arg_name,
void *data)
{
ecs_vector_t **array = data;
(void)source_id;
(void)inout_kind;
if (arg_name) {
ecs_parser_error(name, sig, column,
"column names not supported in type expression");
return -1;
}
if (strcmp(entity_id, "0")) {
ecs_entity_t entity = 0;
if (from_kind != EcsFromOwned) {
if (!name) {
return -1;
}
ecs_parser_error(name, sig, column,
"source modifiers not supported for type expressions");
return -1;
}
entity = ecs_lookup_fullpath(world, entity_id);
if (!entity) {
if (!name) {
return -1;
}
ecs_parser_error(name, sig, column,
"unresolved identifier '%s'", entity_id);
return -1;
}
if (trait_id) {
ecs_entity_t trait = ecs_lookup_fullpath(world, trait_id);
if (!trait) {
ecs_parser_error(name, sig, column,
"unresolved trait identifier '%s'", trait_id);
return -1;
}
entity = ecs_entity_t_comb(entity, trait);
}
if (oper_kind == EcsOperAnd) {
ecs_entity_t* e_ptr = ecs_vector_add(array, ecs_entity_t);
*e_ptr = entity | role;
} else {
if (!name) {
return -1;
}
/* Only AND and OR operators are supported for type expressions */
ecs_parser_error(name, sig, column,
"invalid operator for type expression");
return -1;
}
}
return 0;
}
static
ecs_table_t* table_from_vec(
ecs_world_t *world,
ecs_vector_t *vec)
{
ecs_entity_t *array = ecs_vector_first(vec, ecs_entity_t);
int32_t count = ecs_vector_count(vec);
ecs_entities_t entities = {
.array = array,
.count = count
};
return ecs_table_find_or_create(world, &entities);
}
static
EcsType type_from_vec(
ecs_world_t *world,
ecs_vector_t *vec)
{
EcsType result = {0, 0};
ecs_table_t *table = table_from_vec(world, vec);
if (!table) {
return result;
}
result.type = table->type;
/* Create normalized type. A normalized type resolves all elements with an
* AND flag and appends them to the resulting type, where the default type
* maintains the original type hierarchy. */
ecs_vector_t *normalized = NULL;
ecs_entity_t *array = ecs_vector_first(vec, ecs_entity_t);
int32_t i, count = ecs_vector_count(vec);
for (i = 0; i < count; i ++) {
ecs_entity_t e = array[i];
if (ECS_HAS_ROLE(e, AND)) {
ecs_entity_t entity = e & ECS_COMPONENT_MASK;
const EcsType *type_ptr = ecs_get(world, entity, EcsType);
ecs_assert(type_ptr != NULL, ECS_INVALID_PARAMETER,
"flag must be applied to type");
ecs_vector_each(type_ptr->normalized, ecs_entity_t, c_ptr, {
ecs_entity_t *el = ecs_vector_add(&normalized, ecs_entity_t);
*el = *c_ptr;
})
}
}
/* Only get normalized type if it's different from the type */
if (normalized) {
ecs_entities_t normalized_array = ecs_type_to_entities(normalized);
ecs_table_t *norm_table = ecs_table_traverse_add(
world, table, &normalized_array, NULL);
result.normalized = norm_table->type;
ecs_vector_free(normalized);
} else {
result.normalized = result.type;
}
return result;
}
static
EcsType type_from_expr(
ecs_world_t *world,
const char *name,
const char *expr)
{
if (expr) {
ecs_vector_t *vec = ecs_vector_new(ecs_entity_t, 1);
ecs_parse_expr(world, name, expr, parse_type_action, &vec);
EcsType result = type_from_vec(world, vec);
ecs_vector_free(vec);
return result;
} else {
return (EcsType){0, 0};
}
}
/* If a name prefix is set with ecs_set_name_prefix, check if the entity name
* has the prefix, and if so remove it. This enables using prefixed names in C
* for components / systems while storing a canonical / language independent
* identifier. */
const char* ecs_name_from_symbol(
ecs_world_t *world,
const char *type_name)
{
const char *prefix = world->name_prefix;
if (type_name && prefix) {
ecs_size_t len = ecs_os_strlen(prefix);
if (!ecs_os_strncmp(type_name, prefix, len) &&
(isupper(type_name[len]) || type_name[len] == '_'))
{
if (type_name[len] == '_') {
return type_name + len + 1;
} else {
return type_name + len;
}
}
}
return type_name;
}
void ecs_set_symbol(
ecs_world_t *world,
ecs_entity_t e,
const char *name)
{
if (!name) {
return;
}
const char *e_name = ecs_name_from_symbol(world, name);
EcsName *name_ptr = ecs_get_mut(world, e, EcsName, NULL);
name_ptr->value = e_name;
if (name_ptr->symbol) {
ecs_os_free(name_ptr->symbol);
}
name_ptr->symbol = ecs_os_strdup(name);
}
ecs_entity_t ecs_lookup_w_id(
ecs_world_t *world,
ecs_entity_t e,
const char *name)
{
if (e) {
if (name) {
/* Make sure name is the same */
const char *existing = ecs_get_name(world, e);
if (existing && strcmp(existing, name)) {
ecs_abort(ECS_INCONSISTENT_NAME, name);
}
if (!existing) {
ecs_set_symbol(world, e, name);
}
}
}
ecs_entity_t result = e;
if (!result) {
if (!name) {
/* If neither an id nor name is specified, return 0 */
return 0;
}
result = ecs_lookup(world, name);
}
return result;
}
/* -- Public functions -- */
ecs_type_t ecs_type_from_str(
ecs_world_t *world,
const char *expr)
{
EcsType type = type_from_expr(world, NULL, expr);
return type.normalized;
}
ecs_table_t* ecs_table_from_str(
ecs_world_t *world,
const char *expr)
{
if (expr) {
ecs_vector_t *vec = ecs_vector_new(ecs_entity_t, 1);
ecs_parse_expr(world, NULL, expr, parse_type_action, &vec);
ecs_table_t *result = table_from_vec(world, vec);
ecs_vector_free(vec);
return result;
} else {
return NULL;
}
}
ecs_entity_t ecs_new_entity(
ecs_world_t *world,
ecs_entity_t e,
const char *name,
const char *expr)
{
ecs_entity_t result = ecs_lookup_w_id(world, e, name);
if (!result) {
result = ecs_new(world, 0);
ecs_set_symbol(world, result, name);
}
EcsType type = type_from_expr(world, name, expr);
ecs_add_type(world, result, type.normalized);
return result;
}
ecs_entity_t ecs_new_prefab(
ecs_world_t *world,
ecs_entity_t e,
const char *name,
const char *expr)
{
ecs_entity_t result = ecs_lookup_w_id(world, e, name);
if (!result) {
result = ecs_new(world, 0);
ecs_set_symbol(world, result, name);
}
ecs_add_entity(world, result, EcsPrefab);
EcsType type = type_from_expr(world, name, expr);
ecs_add_type(world, result, type.normalized);
return result;
}
ecs_entity_t ecs_new_component(
ecs_world_t *world,
ecs_entity_t e,
const char *name,
size_t size,
size_t alignment)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
assert(world->magic == ECS_WORLD_MAGIC);
bool in_progress = world->in_progress;
bool found = false;
/* If world is in progress component may be registered, but only when not
* in multithreading mode. */
if (in_progress) {
ecs_assert(ecs_vector_count(world->workers) < 1,
ECS_INVALID_WHILE_ITERATING, NULL);
/* Component creation should not be deferred */
ecs_defer_end(world);
world->in_progress = false;
}
ecs_entity_t result = ecs_lookup_w_id(world, e, name);
if (!result) {
result = ecs_new_component_id(world);
found = true;
}
/* ecs_new_component_id does not add the scope, so add it explicitly */
ecs_entity_t scope = world->stage.scope;
if (scope) {
ecs_add_entity(world, result, ECS_CHILDOF | scope);
}
if (found) {
ecs_set_symbol(world, result, name);
}
bool added = false;
EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent, &added);
if (added) {
ptr->size = ecs_from_size_t(size);
ptr->alignment = ecs_from_size_t(alignment);
} else {
if (ptr->size != ecs_from_size_t(size)) {
ecs_abort(ECS_INVALID_COMPONENT_SIZE, name);
}
if (ptr->alignment != ecs_from_size_t(alignment)) {
ecs_abort(ECS_INVALID_COMPONENT_SIZE, name);
}
}
ecs_modified(world, result, EcsComponent);
if (e > world->stats.last_component_id && e < ECS_HI_COMPONENT_ID) {
world->stats.last_component_id = e + 1;
}
if (in_progress) {
world->in_progress = true;
ecs_defer_begin(world);
}
return result;
}
ecs_entity_t ecs_new_type(
ecs_world_t *world,
ecs_entity_t e,
const char *name,
const char *expr)
{
assert(world->magic == ECS_WORLD_MAGIC);
ecs_entity_t result = ecs_lookup_w_id(world, e, name);
if (!result) {
result = ecs_new_entity(world, 0, name, NULL);
}
EcsType type_parsed = type_from_expr(world, name, expr);
bool added = false;
EcsType *type = ecs_get_mut(world, result, EcsType, &added);
if (added) {
type->type = type_parsed.type;
type->normalized = type_parsed.normalized;
} else {
if (type->type != type_parsed.type) {
ecs_abort(ECS_ALREADY_DEFINED, name);
}
if (type->normalized != type_parsed.normalized) {
ecs_abort(ECS_ALREADY_DEFINED, name);
}
}
/* This will allow the type to show up in debug tools */
ecs_map_set(world->type_handles, (uintptr_t)type_parsed.type, &result);
return result;
}
/* Global type variables */
ecs_type_t ecs_type(EcsComponent);
ecs_type_t ecs_type(EcsType);
ecs_type_t ecs_type(EcsName);
ecs_type_t ecs_type(EcsPrefab);
/* Component lifecycle actions for EcsName */
static ECS_CTOR(EcsName, ptr, {
ptr->value = NULL;
ptr->alloc_value = NULL;
ptr->symbol = NULL;
})
static ECS_DTOR(EcsName, ptr, {
ecs_os_free(ptr->alloc_value);
ecs_os_free(ptr->symbol);
ptr->value = NULL;
ptr->alloc_value = NULL;
ptr->symbol = NULL;
})
static ECS_COPY(EcsName, dst, src, {
if (dst->alloc_value) {
ecs_os_free(dst->alloc_value);
dst->alloc_value = NULL;
}
if (dst->symbol) {
ecs_os_free(dst->symbol);
dst->symbol = NULL;
}
if (src->alloc_value) {
dst->alloc_value = ecs_os_strdup(src->alloc_value);
dst->value = dst->alloc_value;
} else {
dst->alloc_value = NULL;
dst->value = src->value;
}
if (src->symbol) {
dst->symbol = ecs_os_strdup(src->symbol);
}
})
static ECS_MOVE(EcsName, dst, src, {
if (dst->alloc_value) {
ecs_os_free(dst->alloc_value);
}
if (dst->symbol) {
ecs_os_free(dst->symbol);
}
dst->value = src->value;
dst->alloc_value = src->alloc_value;
dst->symbol = src->symbol;
src->value = NULL;
src->alloc_value = NULL;
src->symbol = NULL;
})
/* -- Bootstrapping -- */
#define bootstrap_component(world, table, name)\
_bootstrap_component(world, table, ecs_typeid(name), #name, sizeof(name),\
ECS_ALIGNOF(name))
static
void _bootstrap_component(
ecs_world_t *world,
ecs_table_t *table,
ecs_entity_t entity,
const char *id,
ecs_size_t size,
ecs_size_t alignment)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = ecs_table_get_or_create_data(table);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_column_t *columns = data->columns;
ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL);
/* Create record in entity index */
ecs_record_t *record = ecs_eis_get_or_create(world, entity);
record->table = table;
/* Insert row into table to store EcsComponent itself */
int32_t index = ecs_table_append(world, table, data, entity, record, false);
record->row = index + 1;
/* Set size and id */
EcsComponent *c_info = ecs_vector_first(columns[0].data, EcsComponent);
EcsName *id_data = ecs_vector_first(columns[1].data, EcsName);
c_info[index].size = size;
c_info[index].alignment = alignment;
id_data[index].value = &id[ecs_os_strlen("Ecs")]; /* Skip prefix */
id_data[index].symbol = ecs_os_strdup(id);
id_data[index].alloc_value = NULL;
}
/** Create type for component */
ecs_type_t ecs_bootstrap_type(
ecs_world_t *world,
ecs_entity_t entity)
{
ecs_table_t *table = ecs_table_find_or_create(world, &(ecs_entities_t){
.array = (ecs_entity_t[]){entity},
.count = 1
});
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(table->type != NULL, ECS_INTERNAL_ERROR, NULL);
return table->type;
}
/** Bootstrap types for builtin components and tags */
static
void bootstrap_types(
ecs_world_t *world)
{
ecs_type(EcsComponent) = ecs_bootstrap_type(world, ecs_typeid(EcsComponent));
ecs_type(EcsType) = ecs_bootstrap_type(world, ecs_typeid(EcsType));
ecs_type(EcsName) = ecs_bootstrap_type(world, ecs_typeid(EcsName));
}
/** 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 EcsName components, which haven't been
* created yet */
static
ecs_table_t* bootstrap_component_table(
ecs_world_t *world)
{
ecs_entity_t entities[] = {ecs_typeid(EcsComponent), ecs_typeid(EcsName), ECS_CHILDOF | EcsFlecsCore};
ecs_entities_t array = {
.array = entities,
.count = 3
};
ecs_table_t *result = ecs_table_find_or_create(world, &array);
ecs_data_t *data = ecs_table_get_or_create_data(result);
/* Preallocate enough memory for initial components */
data->entities = ecs_vector_new(ecs_entity_t, EcsFirstUserComponentId);
data->record_ptrs = ecs_vector_new(ecs_record_t*, EcsFirstUserComponentId);
data->columns = ecs_os_malloc(sizeof(ecs_column_t) * 2);
ecs_assert(data->columns != NULL, ECS_OUT_OF_MEMORY, NULL);
data->columns[0].data = ecs_vector_new(EcsComponent, EcsFirstUserComponentId);
data->columns[0].size = sizeof(EcsComponent);
data->columns[0].alignment = ECS_ALIGNOF(EcsComponent);
data->columns[1].data = ecs_vector_new(EcsName, EcsFirstUserComponentId);
data->columns[1].size = sizeof(EcsName);
data->columns[1].alignment = ECS_ALIGNOF(EcsName);
result->column_count = 2;
return result;
}
void ecs_bootstrap(
ecs_world_t *world)
{
ecs_type(EcsComponent) = NULL;
ecs_trace_1("bootstrap core components");
ecs_log_push();
/* Create table that will hold components (EcsComponent, EcsName) */
ecs_table_t *table = bootstrap_component_table(world);
assert(table != NULL);
bootstrap_component(world, table, EcsName);
bootstrap_component(world, table, EcsComponent);
bootstrap_component(world, table, EcsType);
ecs_set_component_actions(world, EcsName, {
.ctor = ecs_ctor(EcsName),
.dtor = ecs_dtor(EcsName),
.copy = ecs_copy(EcsName),
.move = ecs_move(EcsName)
});
world->stats.last_component_id = EcsFirstUserComponentId;
world->stats.last_id = EcsFirstUserEntityId;
world->stats.min_id = 0;
world->stats.max_id = 0;
bootstrap_types(world);
ecs_set_scope(world, EcsFlecsCore);
ecs_bootstrap_tag(world, EcsModule);
ecs_bootstrap_tag(world, EcsPrefab);
ecs_bootstrap_tag(world, EcsHidden);
ecs_bootstrap_tag(world, EcsDisabled);
/* Initialize scopes */
ecs_set(world, EcsFlecs, EcsName, {.value = "flecs"});
ecs_add_entity(world, EcsFlecs, EcsModule);
ecs_set(world, EcsFlecsCore, EcsName, {.value = "core"});
ecs_add_entity(world, EcsFlecsCore, EcsModule);
ecs_add_entity(world, EcsFlecsCore, ECS_CHILDOF | EcsFlecs);
/* Initialize EcsWorld */
ecs_set(world, EcsWorld, EcsName, {.value = "World"});
ecs_assert(ecs_get_name(world, EcsWorld) != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(ecs_lookup(world, "World") == EcsWorld, ECS_INTERNAL_ERROR, NULL);
ecs_add_entity(world, EcsWorld, ECS_CHILDOF | EcsFlecsCore);
/* Initialize EcsSingleton */
ecs_set(world, EcsSingleton, EcsName, {.value = "$"});
ecs_assert(ecs_get_name(world, EcsSingleton) != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(ecs_lookup(world, "$") == EcsSingleton, ECS_INTERNAL_ERROR, NULL);
ecs_add_entity(world, EcsSingleton, ECS_CHILDOF | EcsFlecsCore);
/* Initialize EcsWildcard */
ecs_set(world, EcsWildcard, EcsName, {.value = "*"});
ecs_assert(ecs_get_name(world, EcsWildcard) != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(ecs_lookup(world, "*") == EcsWildcard, ECS_INTERNAL_ERROR, NULL);
ecs_add_entity(world, EcsWildcard, ECS_CHILDOF | EcsFlecsCore);
ecs_set_scope(world, 0);
ecs_log_pop();
}
static
bool path_append(
ecs_world_t *world,
ecs_entity_t parent,
ecs_entity_t child,
ecs_entity_t component,
const char *sep,
const char *prefix,
ecs_strbuf_t *buf)
{
ecs_type_t type = ecs_get_type(world, child);
ecs_entity_t cur = ecs_find_in_type(world, type, component, ECS_CHILDOF);
if (cur) {
if (cur != parent && cur != EcsFlecsCore) {
path_append(world, parent, cur, component, sep, prefix, buf);
ecs_strbuf_appendstr(buf, sep);
}
} else if (prefix) {
ecs_strbuf_appendstr(buf, prefix);
}
char buff[22];
const char *name = ecs_get_name(world, child);
if (!name) {
ecs_os_sprintf(buff, "%u", (uint32_t)child);
name = buff;
}
ecs_strbuf_appendstr(buf, name);
return cur != 0;
}
static
ecs_entity_t find_as_alias(
ecs_world_t *world,
const char *name)
{
int32_t i, count = ecs_vector_count(world->aliases);
ecs_alias_t *aliases = ecs_vector_first(world->aliases, ecs_alias_t);
for (i = 0; i < count; i ++) {
if (!strcmp(aliases[i].name, name)) {
return aliases[i].entity;
}
}
return 0;
}
char* ecs_get_path_w_sep(
ecs_world_t *world,
ecs_entity_t parent,
ecs_entity_t child,
ecs_entity_t component,
const char *sep,
const char *prefix)
{
ecs_strbuf_t buf = ECS_STRBUF_INIT;
if (parent != child) {
path_append(world, parent, child, component, sep, prefix, &buf);
} else {
ecs_strbuf_appendstr(&buf, "");
}
return ecs_strbuf_get(&buf);
}
static
bool is_number(
const char *name)
{
if (!isdigit(name[0])) {
return false;
}
ecs_size_t i, s = ecs_os_strlen(name);
for (i = 1; i < s; i ++) {
if (!isdigit(name[i])) {
break;
}
}
return i >= s;
}
static
ecs_entity_t name_to_id(
const char *name)
{
long int result = atol(name);
ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL);
return (ecs_entity_t)result;
}
static
ecs_entity_t find_child_in_table(
ecs_table_t *table,
const char *name)
{
/* If table doesn't have EcsName, then don't bother */
int32_t name_index = ecs_type_index_of(table->type, ecs_typeid(EcsName));
if (name_index == -1) {
return 0;
}
ecs_data_t *data = ecs_table_get_data(table);
if (!data || !data->columns) {
return 0;
}
int32_t i, count = ecs_vector_count(data->entities);
if (!count) {
return 0;
}
if (is_number(name)) {
return name_to_id(name);
}
ecs_column_t *column = &data->columns[name_index];
EcsName *names = ecs_vector_first(column->data, EcsName);
for (i = 0; i < count; i ++) {
const char *cur_name = names[i].value;
const char *cur_sym = names[i].symbol;
if ((cur_name && !strcmp(cur_name, name)) || (cur_sym && !strcmp(cur_sym, name))) {
return *ecs_vector_get(data->entities, ecs_entity_t, i);
}
}
return 0;
}
static
ecs_entity_t find_child(
ecs_world_t *world,
ecs_entity_t parent,
const char *name)
{
(void)parent;
ecs_sparse_each(world->store.tables, ecs_table_t, table, {
ecs_entity_t result = find_child_in_table(table, name);
if (result) {
return result;
}
});
return 0;
}
ecs_entity_t ecs_lookup_child(
ecs_world_t *world,
ecs_entity_t parent,
const char *name)
{
ecs_entity_t result = 0;
ecs_vector_t *child_tables = ecs_map_get_ptr(
world->child_tables, ecs_vector_t*, parent);
if (child_tables) {
ecs_vector_each(child_tables, ecs_table_t*, table_ptr, {
ecs_table_t *table = *table_ptr;
result = find_child_in_table(table, name);
if (result) {
return result;
}
});
}
return result;
}
ecs_entity_t ecs_lookup(
ecs_world_t *world,
const char *name)
{
if (!name) {
return 0;
}
if (is_number(name)) {
return name_to_id(name);
}
ecs_entity_t e = find_as_alias(world, name);
if (e) {
return e;
}
return ecs_lookup_child(world, 0, name);
}
ecs_entity_t ecs_lookup_symbol(
ecs_world_t *world,
const char *name)
{
if (!name) {
return 0;
}
if (is_number(name)) {
return name_to_id(name);
}
return find_child(world, 0, name);
}
static
bool 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 - 1;
return true;
} else {
return false;
}
}
static
const char *path_elem(
const char *path,
char *buff,
const char *sep)
{
const char *ptr;
char *bptr, ch;
for (bptr = buff, ptr = path; (ch = *ptr); ptr ++) {
ecs_assert(bptr - buff < ECS_MAX_NAME_LENGTH, ECS_INVALID_PARAMETER,
NULL);
if (is_sep(&ptr, sep)) {
*bptr = '\0';
return ptr + 1;
} else {
*bptr = ch;
bptr ++;
}
}
if (bptr != buff) {
*bptr = '\0';
return ptr;
} else {
return NULL;
}
}
static
ecs_entity_t get_parent_from_path(
ecs_world_t *world,
ecs_entity_t parent,
const char **path_ptr,
const char *prefix)
{
bool start_from_root = false;
const char *path = *path_ptr;
if (prefix) {
ecs_size_t len = ecs_os_strlen(prefix);
if (!ecs_os_strncmp(path, prefix, len)) {
path += len;
parent = 0;
start_from_root = true;
}
}
if (!start_from_root && !parent) {
ecs_stage_t *stage = ecs_get_stage(&world);
parent = stage->scope;
}
*path_ptr = path;
return parent;
}
ecs_entity_t ecs_lookup_path_w_sep(
ecs_world_t *world,
ecs_entity_t parent,
const char *path,
const char *sep,
const char *prefix)
{
if (!path) {
return 0;
}
ecs_entity_t e = find_as_alias(world, path);
if (e) {
return e;
}
char buff[ECS_MAX_NAME_LENGTH];
const char *ptr;
ecs_entity_t cur;
bool core_searched = false;
if (!sep) {
sep = ".";
}
parent = get_parent_from_path(world, parent, &path, prefix);
retry:
cur = parent;
ptr = path;
while ((ptr = path_elem(ptr, buff, sep))) {
cur = ecs_lookup_child(world, cur, buff);
if (!cur) {
goto tail;
}
}
tail:
if (!cur) {
if (!core_searched) {
if (parent) {
parent = ecs_get_parent_w_entity(world, parent, 0);
} else {
parent = EcsFlecsCore;
core_searched = true;
}
goto retry;
}
}
return cur;
}
ecs_entity_t ecs_set_scope(
ecs_world_t *world,
ecs_entity_t scope)
{
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_entity_t e = ECS_CHILDOF | scope;
ecs_entities_t to_add = {
.array = &e,
.count = 1
};
ecs_entity_t cur = stage->scope;
stage->scope = scope;
if (scope) {
stage->scope_table = ecs_table_traverse_add(
world, &world->store.root, &to_add, NULL);
} else {
stage->scope_table = &world->store.root;
}
return cur;
}
ecs_entity_t ecs_get_scope(
ecs_world_t *world)
{
ecs_stage_t *stage = ecs_get_stage(&world);
return stage->scope;
}
int32_t ecs_get_child_count(
ecs_world_t *world,
ecs_entity_t entity)
{
ecs_vector_t *tables = ecs_map_get_ptr(world->child_tables, ecs_vector_t*, entity);
if (!tables) {
return 0;
} else {
int32_t count = 0;
ecs_vector_each(tables, ecs_table_t*, table_ptr, {
ecs_table_t *table = *table_ptr;
count += ecs_table_count(table);
});
return count;
}
}
ecs_iter_t ecs_scope_iter(
ecs_world_t *world,
ecs_entity_t parent)
{
ecs_scope_iter_t iter = {
.tables = ecs_map_get_ptr(world->child_tables, ecs_vector_t*, parent),
.index = 0
};
return (ecs_iter_t) {
.world = world,
.iter.parent = iter
};
}
ecs_iter_t ecs_scope_iter_w_filter(
ecs_world_t *world,
ecs_entity_t parent,
ecs_filter_t *filter)
{
ecs_scope_iter_t iter = {
.filter = *filter,
.tables = ecs_map_get_ptr(world->child_tables, ecs_vector_t*, parent),
.index = 0
};
return (ecs_iter_t) {
.world = world,
.iter.parent = iter,
.table_count = ecs_vector_count(iter.tables)
};
}
bool ecs_scope_next(
ecs_iter_t *it)
{
ecs_scope_iter_t *iter = &it->iter.parent;
ecs_vector_t *tables = iter->tables;
ecs_filter_t filter = iter->filter;
int32_t count = ecs_vector_count(tables);
int32_t i;
for (i = iter->index; i < count; i ++) {
ecs_table_t *table = *ecs_vector_get(tables, ecs_table_t*, i);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_data_t *data = ecs_table_get_data(table);
if (!data) {
continue;
}
it->count = ecs_table_count(table);
if (!it->count) {
continue;
}
if (filter.include || filter.exclude) {
if (!ecs_table_match_filter(it->world, table, &filter)) {
continue;
}
}
iter->table.table = table;
it->table = &iter->table;
it->table_columns = data->columns;
it->count = ecs_table_count(table);
it->entities = ecs_vector_first(data->entities, ecs_entity_t);
iter->index = i + 1;
return true;
}
return false;
}
const char* ecs_set_name_prefix(
ecs_world_t *world,
const char *prefix)
{
const char *old_prefix = world->name_prefix;
world->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)
{
if (!path) {
if (!entity) {
entity = ecs_new_id(world);
}
if (parent) {
ecs_add_entity(world, entity, ECS_CHILDOF | entity);
}
return entity;
}
char buff[ECS_MAX_NAME_LENGTH];
const char *ptr = path;
parent = get_parent_from_path(world, parent, &path, prefix);
ecs_entity_t cur = parent;
while ((ptr = path_elem(ptr, buff, sep))) {
ecs_entity_t e = ecs_lookup_child(world, cur, buff);
if (!e) {
char *name = ecs_os_strdup(buff);
/* If this is the last entity in the path, use the provided id */
if (entity && !path_elem(ptr, buff, sep)) {
e = entity;
}
e = ecs_set(world, e, EcsName, {
.value = name,
.alloc_value = name
});
ecs_os_free(name);
if (cur) {
ecs_add_entity(world, e, ECS_CHILDOF | cur);
}
}
cur = e;
}
return cur;
}
ecs_entity_t ecs_new_from_path_w_sep(
ecs_world_t *world,
ecs_entity_t parent,
const char *path,
const char *sep,
const char *prefix)
{
return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix);
}
void ecs_use(
ecs_world_t *world,
ecs_entity_t entity,
const char *name)
{
ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_stage_t *stage = ecs_get_stage(&world);
ecs_assert(stage->scope == 0 , ECS_INVALID_PARAMETER, NULL);
ecs_assert(find_as_alias(world, name) == 0, ECS_ALREADY_DEFINED, NULL);
(void)stage;
ecs_alias_t *al = ecs_vector_add(&world->aliases, ecs_alias_t);
al->name = ecs_os_strdup(name);
al->entity = entity;
}