Properly link flecs library
This commit is contained in:
780
engine/libs/flecs/src/addons/alerts.c
Normal file
780
engine/libs/flecs/src/addons/alerts.c
Normal file
@@ -0,0 +1,780 @@
|
||||
/**
|
||||
* @file addons/alerts.c
|
||||
* @brief Alerts addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_ALERTS
|
||||
|
||||
ECS_COMPONENT_DECLARE(FlecsAlerts);
|
||||
|
||||
typedef struct EcsAlert {
|
||||
char *message;
|
||||
ecs_map_t instances; /* Active instances for metric */
|
||||
ecs_ftime_t retain_period; /* How long to retain the alert */
|
||||
ecs_vec_t severity_filters; /* Severity filters */
|
||||
|
||||
/* Member range monitoring */
|
||||
ecs_id_t id; /* (Component) id that contains to monitor member */
|
||||
ecs_entity_t member; /* Member to monitor */
|
||||
int32_t offset; /* Offset of member in component */
|
||||
int32_t size; /* Size of component */
|
||||
ecs_primitive_kind_t kind; /* Primitive type kind */
|
||||
ecs_ref_t ranges; /* Reference to ranges component */
|
||||
int32_t var_id; /* Variable from which to obtain data (0 = $this) */
|
||||
} EcsAlert;
|
||||
|
||||
typedef struct EcsAlertTimeout {
|
||||
ecs_ftime_t inactive_time; /* Time the alert has been inactive */
|
||||
ecs_ftime_t expire_time; /* Expiration duration */
|
||||
} EcsAlertTimeout;
|
||||
|
||||
ECS_COMPONENT_DECLARE(EcsAlertTimeout);
|
||||
|
||||
static
|
||||
ECS_CTOR(EcsAlert, ptr, {
|
||||
ecs_os_zeromem(ptr);
|
||||
ecs_map_init(&ptr->instances, NULL);
|
||||
ecs_vec_init_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t, 0);
|
||||
})
|
||||
|
||||
static
|
||||
ECS_DTOR(EcsAlert, ptr, {
|
||||
ecs_os_free(ptr->message);
|
||||
ecs_map_fini(&ptr->instances);
|
||||
ecs_vec_fini_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t);
|
||||
})
|
||||
|
||||
static
|
||||
ECS_MOVE(EcsAlert, dst, src, {
|
||||
ecs_os_free(dst->message);
|
||||
dst->message = src->message;
|
||||
src->message = NULL;
|
||||
|
||||
ecs_map_fini(&dst->instances);
|
||||
dst->instances = src->instances;
|
||||
src->instances = (ecs_map_t){0};
|
||||
|
||||
ecs_vec_fini_t(NULL, &dst->severity_filters, ecs_alert_severity_filter_t);
|
||||
dst->severity_filters = src->severity_filters;
|
||||
src->severity_filters = (ecs_vec_t){0};
|
||||
|
||||
dst->retain_period = src->retain_period;
|
||||
dst->id = src->id;
|
||||
dst->member = src->member;
|
||||
dst->offset = src->offset;
|
||||
dst->size = src->size;
|
||||
dst->kind = src->kind;
|
||||
dst->ranges = src->ranges;
|
||||
dst->var_id = src->var_id;
|
||||
})
|
||||
|
||||
static
|
||||
ECS_CTOR(EcsAlertsActive, ptr, {
|
||||
ecs_map_init(&ptr->alerts, NULL);
|
||||
ptr->info_count = 0;
|
||||
ptr->warning_count = 0;
|
||||
ptr->error_count = 0;
|
||||
})
|
||||
|
||||
static
|
||||
ECS_DTOR(EcsAlertsActive, ptr, {
|
||||
ecs_map_fini(&ptr->alerts);
|
||||
})
|
||||
|
||||
static
|
||||
ECS_MOVE(EcsAlertsActive, dst, src, {
|
||||
ecs_map_fini(&dst->alerts);
|
||||
dst->alerts = src->alerts;
|
||||
dst->info_count = src->info_count;
|
||||
dst->warning_count = src->warning_count;
|
||||
dst->error_count = src->error_count;
|
||||
src->alerts = (ecs_map_t){0};
|
||||
})
|
||||
|
||||
static
|
||||
ECS_DTOR(EcsAlertInstance, ptr, {
|
||||
ecs_os_free(ptr->message);
|
||||
})
|
||||
|
||||
static
|
||||
ECS_MOVE(EcsAlertInstance, dst, src, {
|
||||
ecs_os_free(dst->message);
|
||||
dst->message = src->message;
|
||||
src->message = NULL;
|
||||
})
|
||||
|
||||
static
|
||||
ECS_COPY(EcsAlertInstance, dst, src, {
|
||||
ecs_os_free(dst->message);
|
||||
dst->message = ecs_os_strdup(src->message);
|
||||
})
|
||||
|
||||
static
|
||||
void flecs_alerts_add_alert_to_src(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t source,
|
||||
ecs_entity_t alert,
|
||||
ecs_entity_t alert_instance)
|
||||
{
|
||||
EcsAlertsActive *active = ecs_get_mut(
|
||||
world, source, EcsAlertsActive);
|
||||
ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0);
|
||||
if (severity == EcsAlertInfo) {
|
||||
active->info_count ++;
|
||||
} else if (severity == EcsAlertWarning) {
|
||||
active->warning_count ++;
|
||||
} else if (severity == EcsAlertError) {
|
||||
active->error_count ++;
|
||||
}
|
||||
|
||||
ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert);
|
||||
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ptr[0] = alert_instance;
|
||||
ecs_modified(world, source, EcsAlertsActive);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_alerts_remove_alert_from_src(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t source,
|
||||
ecs_entity_t alert)
|
||||
{
|
||||
EcsAlertsActive *active = ecs_get_mut(
|
||||
world, source, EcsAlertsActive);
|
||||
ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_map_remove(&active->alerts, alert);
|
||||
|
||||
ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0);
|
||||
if (severity == EcsAlertInfo) {
|
||||
active->info_count --;
|
||||
} else if (severity == EcsAlertWarning) {
|
||||
active->warning_count --;
|
||||
} else if (severity == EcsAlertError) {
|
||||
active->error_count --;
|
||||
}
|
||||
|
||||
if (!ecs_map_count(&active->alerts)) {
|
||||
ecs_remove(world, source, EcsAlertsActive);
|
||||
} else {
|
||||
ecs_modified(world, source, EcsAlertsActive);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
ecs_entity_t flecs_alert_get_severity(
|
||||
ecs_world_t *world,
|
||||
ecs_iter_t *it,
|
||||
EcsAlert *alert)
|
||||
{
|
||||
int32_t i, filter_count = ecs_vec_count(&alert->severity_filters);
|
||||
ecs_alert_severity_filter_t *filters =
|
||||
ecs_vec_first(&alert->severity_filters);
|
||||
for (i = 0; i < filter_count; i ++) {
|
||||
ecs_alert_severity_filter_t *filter = &filters[i];
|
||||
if (!filter->var) {
|
||||
if (ecs_table_has_id(world, it->table, filters[i].with)) {
|
||||
return filters[i].severity;
|
||||
}
|
||||
} else {
|
||||
ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index);
|
||||
if (src && src != EcsWildcard) {
|
||||
if (ecs_has_id(world, src, filters[i].with)) {
|
||||
return filters[i].severity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_entity_t flecs_alert_out_of_range_kind(
|
||||
EcsAlert *alert,
|
||||
const EcsMemberRanges *ranges,
|
||||
const void *value_ptr)
|
||||
{
|
||||
double value = 0;
|
||||
|
||||
switch(alert->kind) {
|
||||
case EcsU8: value = *(const uint8_t*)value_ptr; break;
|
||||
case EcsU16: value = *(const uint16_t*)value_ptr; break;
|
||||
case EcsU32: value = *(const uint32_t*)value_ptr; break;
|
||||
case EcsU64: value = (double)*(const uint64_t*)value_ptr; break;
|
||||
case EcsI8: value = *(const int8_t*)value_ptr; break;
|
||||
case EcsI16: value = *(const int16_t*)value_ptr; break;
|
||||
case EcsI32: value = *(const int32_t*)value_ptr; break;
|
||||
case EcsI64: value = (double)*(const int64_t*)value_ptr; break;
|
||||
case EcsF32: value = (double)*(const float*)value_ptr; break;
|
||||
case EcsF64: value = *(const double*)value_ptr; break;
|
||||
case EcsBool:
|
||||
case EcsChar:
|
||||
case EcsByte:
|
||||
case EcsUPtr:
|
||||
case EcsIPtr:
|
||||
case EcsString:
|
||||
case EcsEntity:
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max);
|
||||
bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max);
|
||||
|
||||
if (has_error && (value < ranges->error.min || value > ranges->error.max)) {
|
||||
return EcsAlertError;
|
||||
} else if (has_warning &&
|
||||
(value < ranges->warning.min || value > ranges->warning.max))
|
||||
{
|
||||
return EcsAlertWarning;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void MonitorAlerts(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
EcsAlert *alert = ecs_field(it, EcsAlert, 1);
|
||||
EcsPoly *poly = ecs_field(it, EcsPoly, 2);
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t a = it->entities[i]; /* Alert entity */
|
||||
ecs_entity_t default_severity = ecs_get_target(
|
||||
world, a, ecs_id(EcsAlert), 0);
|
||||
ecs_rule_t *rule = poly[i].poly;
|
||||
ecs_poly_assert(rule, ecs_rule_t);
|
||||
|
||||
ecs_id_t member_id = alert[i].id;
|
||||
const EcsMemberRanges *ranges = NULL;
|
||||
if (member_id) {
|
||||
ranges = ecs_ref_get(world, &alert[i].ranges, EcsMemberRanges);
|
||||
}
|
||||
|
||||
ecs_iter_t rit = ecs_rule_iter(world, rule);
|
||||
rit.flags |= EcsIterNoData;
|
||||
rit.flags |= EcsIterIsInstanced;
|
||||
|
||||
while (ecs_rule_next(&rit)) {
|
||||
ecs_entity_t severity = flecs_alert_get_severity(
|
||||
world, &rit, &alert[i]);
|
||||
if (!severity) {
|
||||
severity = default_severity;
|
||||
}
|
||||
|
||||
const void *member_data = NULL;
|
||||
ecs_entity_t member_src = 0;
|
||||
if (ranges) {
|
||||
if (alert[i].var_id) {
|
||||
member_src = ecs_iter_get_var(&rit, alert[i].var_id);
|
||||
if (!member_src || member_src == EcsWildcard) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!member_src) {
|
||||
member_data = ecs_table_get_id(
|
||||
world, rit.table, member_id, rit.offset);
|
||||
} else {
|
||||
member_data = ecs_get_id(world, member_src, member_id);
|
||||
}
|
||||
if (!member_data) {
|
||||
continue;
|
||||
}
|
||||
member_data = ECS_OFFSET(member_data, alert[i].offset);
|
||||
}
|
||||
|
||||
int32_t j, alert_src_count = rit.count;
|
||||
for (j = 0; j < alert_src_count; j ++) {
|
||||
ecs_entity_t src_severity = severity;
|
||||
ecs_entity_t e = rit.entities[j];
|
||||
if (member_data) {
|
||||
ecs_entity_t range_severity = flecs_alert_out_of_range_kind(
|
||||
&alert[i], ranges, member_data);
|
||||
if (!member_src) {
|
||||
member_data = ECS_OFFSET(member_data, alert[i].size);
|
||||
}
|
||||
if (!range_severity) {
|
||||
continue;
|
||||
}
|
||||
if (range_severity < src_severity) {
|
||||
/* Range severity should not exceed alert severity */
|
||||
src_severity = range_severity;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_entity_t *aptr = ecs_map_ensure(&alert[i].instances, e);
|
||||
ecs_assert(aptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
if (!aptr[0]) {
|
||||
/* Alert does not yet exist for entity */
|
||||
ecs_entity_t ai = ecs_new_w_pair(world, EcsChildOf, a);
|
||||
ecs_set(world, ai, EcsAlertInstance, { .message = NULL });
|
||||
ecs_set(world, ai, EcsMetricSource, { .entity = e });
|
||||
ecs_set(world, ai, EcsMetricValue, { .value = 0 });
|
||||
ecs_add_pair(world, ai, ecs_id(EcsAlert), src_severity);
|
||||
if (ECS_NEQZERO(alert[i].retain_period)) {
|
||||
ecs_set(world, ai, EcsAlertTimeout, {
|
||||
.inactive_time = 0,
|
||||
.expire_time = alert[i].retain_period
|
||||
});
|
||||
}
|
||||
|
||||
ecs_defer_suspend(it->world);
|
||||
flecs_alerts_add_alert_to_src(world, e, a, ai);
|
||||
ecs_defer_resume(it->world);
|
||||
aptr[0] = ai;
|
||||
} else {
|
||||
/* Make sure alert severity is up to date */
|
||||
if (ecs_vec_count(&alert[i].severity_filters) || member_data) {
|
||||
ecs_entity_t cur_severity = ecs_get_target(
|
||||
world, aptr[0], ecs_id(EcsAlert), 0);
|
||||
if (cur_severity != src_severity) {
|
||||
ecs_add_pair(world, aptr[0], ecs_id(EcsAlert),
|
||||
src_severity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void MonitorAlertInstances(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
EcsAlertInstance *alert_instance = ecs_field(it, EcsAlertInstance, 1);
|
||||
EcsMetricSource *source = ecs_field(it, EcsMetricSource, 2);
|
||||
EcsMetricValue *value = ecs_field(it, EcsMetricValue, 3);
|
||||
EcsAlertTimeout *timeout = ecs_field(it, EcsAlertTimeout, 4);
|
||||
|
||||
/* Get alert component from alert instance parent (the alert) */
|
||||
ecs_id_t childof_pair;
|
||||
if (ecs_search(world, it->table, ecs_childof(EcsWildcard), &childof_pair) == -1) {
|
||||
ecs_err("alert instances must be a child of an alert");
|
||||
return;
|
||||
}
|
||||
ecs_entity_t parent = ecs_pair_second(world, childof_pair);
|
||||
ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(ecs_has(world, parent, EcsAlert), ECS_INVALID_OPERATION,
|
||||
"alert entity does not have Alert component");
|
||||
EcsAlert *alert = ecs_get_mut(world, parent, EcsAlert);
|
||||
const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery);
|
||||
ecs_assert(poly != NULL, ECS_INVALID_OPERATION,
|
||||
"alert entity does not have (Poly, Query) component");
|
||||
ecs_rule_t *rule = poly->poly;
|
||||
ecs_poly_assert(rule, ecs_rule_t);
|
||||
|
||||
ecs_id_t member_id = alert->id;
|
||||
const EcsMemberRanges *ranges = NULL;
|
||||
if (member_id) {
|
||||
ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges);
|
||||
}
|
||||
|
||||
ecs_vars_t vars = {0};
|
||||
ecs_vars_init(world, &vars);
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t ai = it->entities[i];
|
||||
ecs_entity_t e = source[i].entity;
|
||||
|
||||
/* If source of alert is no longer alive, delete alert instance even if
|
||||
* the alert has a retain period. */
|
||||
if (!ecs_is_alive(world, e)) {
|
||||
ecs_delete(world, ai);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if alert instance still matches rule */
|
||||
ecs_iter_t rit = ecs_rule_iter(world, rule);
|
||||
rit.flags |= EcsIterNoData;
|
||||
rit.flags |= EcsIterIsInstanced;
|
||||
ecs_iter_set_var(&rit, 0, e);
|
||||
|
||||
if (ecs_rule_next(&rit)) {
|
||||
bool match = true;
|
||||
|
||||
/* If alert is monitoring member range, test value against range */
|
||||
if (ranges) {
|
||||
ecs_entity_t member_src = e;
|
||||
if (alert->var_id) {
|
||||
member_src = ecs_iter_get_var(&rit, alert->var_id);
|
||||
}
|
||||
|
||||
const void *member_data = ecs_get_id(
|
||||
world, member_src, member_id);
|
||||
if (!member_data) {
|
||||
match = false;
|
||||
} else {
|
||||
member_data = ECS_OFFSET(member_data, alert->offset);
|
||||
if (flecs_alert_out_of_range_kind(
|
||||
alert, ranges, member_data) == 0)
|
||||
{
|
||||
match = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
/* Only increase alert duration if the alert was active */
|
||||
value[i].value += (double)it->delta_system_time;
|
||||
|
||||
bool generate_message = alert->message;
|
||||
if (generate_message) {
|
||||
if (alert_instance[i].message) {
|
||||
/* If a message was already generated, only regenerate if
|
||||
* rule has multiple variables. Variable values could have
|
||||
* changed, this ensures the message remains up to date. */
|
||||
generate_message = rit.variable_count > 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (generate_message) {
|
||||
if (alert_instance[i].message) {
|
||||
ecs_os_free(alert_instance[i].message);
|
||||
}
|
||||
|
||||
ecs_iter_to_vars(&rit, &vars, 0);
|
||||
alert_instance[i].message = ecs_interpolate_string(
|
||||
world, alert->message, &vars);
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
if (ECS_NEQZERO(timeout[i].inactive_time)) {
|
||||
/* The alert just became active. Remove Disabled tag */
|
||||
flecs_alerts_add_alert_to_src(world, e, parent, ai);
|
||||
ecs_remove_id(world, ai, EcsDisabled);
|
||||
}
|
||||
timeout[i].inactive_time = 0;
|
||||
}
|
||||
|
||||
/* Alert instance still matches rule, keep it alive */
|
||||
ecs_iter_fini(&rit);
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_iter_fini(&rit);
|
||||
}
|
||||
|
||||
/* Alert instance is no longer active */
|
||||
|
||||
if (timeout) {
|
||||
if (ECS_EQZERO(timeout[i].inactive_time)) {
|
||||
/* The alert just became inactive. Add Disabled tag */
|
||||
flecs_alerts_remove_alert_from_src(world, e, parent);
|
||||
ecs_add_id(world, ai, EcsDisabled);
|
||||
}
|
||||
ecs_ftime_t t = timeout[i].inactive_time;
|
||||
timeout[i].inactive_time += it->delta_system_time;
|
||||
if (t < timeout[i].expire_time) {
|
||||
/* Alert instance no longer matches rule, but is still
|
||||
* within the timeout period. Keep it alive. */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Alert instance no longer matches rule, remove it */
|
||||
flecs_alerts_remove_alert_from_src(world, e, parent);
|
||||
ecs_map_remove(&alert->instances, e);
|
||||
ecs_delete(world, ai);
|
||||
}
|
||||
|
||||
ecs_vars_fini(&vars);
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_alert_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_alert_desc_t *desc)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(!desc->filter.entity || desc->entity == desc->filter.entity,
|
||||
ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_entity_t result = desc->entity;
|
||||
if (!result) {
|
||||
result = ecs_new(world, 0);
|
||||
}
|
||||
|
||||
ecs_filter_desc_t private_desc = desc->filter;
|
||||
private_desc.entity = result;
|
||||
|
||||
ecs_rule_t *rule = ecs_rule_init(world, &private_desc);
|
||||
if (!rule) {
|
||||
ecs_err("failed to create alert filter");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ecs_filter_t *filter = ecs_rule_get_filter(rule);
|
||||
if (!(filter->flags & EcsFilterMatchThis)) {
|
||||
ecs_err("alert filter must have at least one '$this' term");
|
||||
ecs_rule_fini(rule);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Initialize Alert component which identifiers entity as alert */
|
||||
EcsAlert *alert = ecs_get_mut(world, result, EcsAlert);
|
||||
ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
alert->message = ecs_os_strdup(desc->message);
|
||||
alert->retain_period = desc->retain_period;
|
||||
|
||||
/* Initialize severity filters */
|
||||
int32_t i;
|
||||
for (i = 0; i < 4; i ++) {
|
||||
if (desc->severity_filters[i].with) {
|
||||
if (!desc->severity_filters[i].severity) {
|
||||
ecs_err("severity filter must have severity");
|
||||
goto error;
|
||||
}
|
||||
ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL,
|
||||
&alert->severity_filters, ecs_alert_severity_filter_t);
|
||||
*sf = desc->severity_filters[i];
|
||||
if (sf->var) {
|
||||
sf->_var_index = ecs_rule_find_var(rule, sf->var);
|
||||
if (sf->_var_index == -1) {
|
||||
ecs_err("unresolved variable '%s' in alert severity filter",
|
||||
sf->var);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fetch data for member monitoring */
|
||||
if (desc->member) {
|
||||
alert->member = desc->member;
|
||||
if (!desc->id) {
|
||||
alert->id = ecs_get_parent(world, desc->member);
|
||||
if (!alert->id) {
|
||||
ecs_err("ecs_alert_desc_t::member is not a member");
|
||||
goto error;
|
||||
}
|
||||
ecs_check(alert->id != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
} else {
|
||||
alert->id = desc->id;
|
||||
}
|
||||
|
||||
ecs_id_record_t *idr = flecs_id_record_ensure(world, alert->id);
|
||||
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
if (!idr->type_info) {
|
||||
ecs_err("ecs_alert_desc_t::id must be a component");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_entity_t type = idr->type_info->component;
|
||||
if (type != ecs_get_parent(world, desc->member)) {
|
||||
char *type_name = ecs_get_fullpath(world, type);
|
||||
ecs_err("member '%s' is not a member of '%s'",
|
||||
ecs_get_name(world, desc->member), type_name);
|
||||
ecs_os_free(type_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
const EcsMember *member = ecs_get(world, alert->member, EcsMember);
|
||||
if (!member) {
|
||||
ecs_err("ecs_alert_desc_t::member is not a member");
|
||||
goto error;
|
||||
}
|
||||
if (!member->type) {
|
||||
ecs_err("ecs_alert_desc_t::member must have a type");
|
||||
goto error;
|
||||
}
|
||||
|
||||
const EcsPrimitive *pr = ecs_get(world, member->type, EcsPrimitive);
|
||||
if (!pr) {
|
||||
ecs_err("ecs_alert_desc_t::member must be of a primitive type");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ecs_has(world, desc->member, EcsMemberRanges)) {
|
||||
ecs_err("ecs_alert_desc_t::member must have warning/error ranges");
|
||||
goto error;
|
||||
}
|
||||
|
||||
int32_t var_id = 0;
|
||||
if (desc->var) {
|
||||
var_id = ecs_rule_find_var(rule, desc->var);
|
||||
if (var_id == -1) {
|
||||
ecs_err("unresolved variable '%s' in alert member", desc->var);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
alert->offset = member->offset;
|
||||
alert->size = idr->type_info->size;
|
||||
alert->kind = pr->kind;
|
||||
alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges);
|
||||
alert->var_id = var_id;
|
||||
}
|
||||
|
||||
ecs_modified(world, result, EcsAlert);
|
||||
|
||||
/* Register alert as metric */
|
||||
ecs_add(world, result, EcsMetric);
|
||||
ecs_add_pair(world, result, EcsMetric, EcsCounter);
|
||||
|
||||
/* Add severity to alert */
|
||||
ecs_entity_t severity = desc->severity;
|
||||
if (!severity) {
|
||||
severity = EcsAlertError;
|
||||
}
|
||||
|
||||
ecs_add_pair(world, result, ecs_id(EcsAlert), severity);
|
||||
|
||||
if (desc->doc_name) {
|
||||
#ifdef FLECS_DOC
|
||||
ecs_doc_set_name(world, result, desc->doc_name);
|
||||
#else
|
||||
ecs_err("cannot set doc_name for alert, requires FLECS_DOC addon");
|
||||
goto error;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (desc->brief) {
|
||||
#ifdef FLECS_DOC
|
||||
ecs_doc_set_brief(world, result, desc->brief);
|
||||
#else
|
||||
ecs_err("cannot set brief for alert, requires FLECS_DOC addon");
|
||||
goto error;
|
||||
#endif
|
||||
}
|
||||
|
||||
return result;
|
||||
error:
|
||||
if (result) {
|
||||
ecs_delete(world, result);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ecs_get_alert_count(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t alert)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(!alert || ecs_has(world, alert, EcsAlert),
|
||||
ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive);
|
||||
if (!active) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (alert) {
|
||||
return ecs_map_get(&active->alerts, alert) != NULL;
|
||||
}
|
||||
|
||||
return ecs_map_count(&active->alerts);
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_get_alert(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t alert)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(alert != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive);
|
||||
if (!active) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert);
|
||||
if (ptr) {
|
||||
return ptr[0];
|
||||
}
|
||||
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FlecsAlertsImport(ecs_world_t *world) {
|
||||
ECS_MODULE_DEFINE(world, FlecsAlerts);
|
||||
|
||||
ECS_IMPORT(world, FlecsPipeline);
|
||||
ECS_IMPORT(world, FlecsTimer);
|
||||
ECS_IMPORT(world, FlecsMetrics);
|
||||
#ifdef FLECS_DOC
|
||||
ECS_IMPORT(world, FlecsDoc);
|
||||
#endif
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
ECS_COMPONENT_DEFINE(world, EcsAlert);
|
||||
ecs_remove_pair(world, ecs_id(EcsAlert), ecs_id(EcsIdentifier), EcsSymbol);
|
||||
ECS_COMPONENT_DEFINE(world, EcsAlertsActive);
|
||||
|
||||
ecs_set_name_prefix(world, "EcsAlert");
|
||||
ECS_COMPONENT_DEFINE(world, EcsAlertInstance);
|
||||
ECS_COMPONENT_DEFINE(world, EcsAlertTimeout);
|
||||
|
||||
ECS_TAG_DEFINE(world, EcsAlertInfo);
|
||||
ECS_TAG_DEFINE(world, EcsAlertWarning);
|
||||
ECS_TAG_DEFINE(world, EcsAlertError);
|
||||
ECS_TAG_DEFINE(world, EcsAlertCritical);
|
||||
|
||||
ecs_add_id(world, ecs_id(EcsAlert), EcsTag);
|
||||
ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive);
|
||||
ecs_add_id(world, ecs_id(EcsAlertsActive), EcsPrivate);
|
||||
|
||||
ecs_struct(world, {
|
||||
.entity = ecs_id(EcsAlertInstance),
|
||||
.members = {
|
||||
{ .name = "message", .type = ecs_id(ecs_string_t) }
|
||||
}
|
||||
});
|
||||
|
||||
ecs_set_hooks(world, EcsAlert, {
|
||||
.ctor = ecs_ctor(EcsAlert),
|
||||
.dtor = ecs_dtor(EcsAlert),
|
||||
.move = ecs_move(EcsAlert)
|
||||
});
|
||||
|
||||
ecs_set_hooks(world, EcsAlertsActive, {
|
||||
.ctor = ecs_ctor(EcsAlertsActive),
|
||||
.dtor = ecs_dtor(EcsAlertsActive),
|
||||
.move = ecs_move(EcsAlertsActive)
|
||||
});
|
||||
|
||||
ecs_set_hooks(world, EcsAlertInstance, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.dtor = ecs_dtor(EcsAlertInstance),
|
||||
.move = ecs_move(EcsAlertInstance),
|
||||
.copy = ecs_copy(EcsAlertInstance)
|
||||
});
|
||||
|
||||
ecs_struct(world, {
|
||||
.entity = ecs_id(EcsAlertsActive),
|
||||
.members = {
|
||||
{ .name = "info_count", .type = ecs_id(ecs_i32_t) },
|
||||
{ .name = "warning_count", .type = ecs_id(ecs_i32_t) },
|
||||
{ .name = "error_count", .type = ecs_id(ecs_i32_t) }
|
||||
}
|
||||
});
|
||||
|
||||
ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query));
|
||||
ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance,
|
||||
flecs.metrics.Source, flecs.metrics.Value, ?EcsAlertTimeout, ?Disabled);
|
||||
|
||||
ecs_system(world, {
|
||||
.entity = ecs_id(MonitorAlerts),
|
||||
.no_readonly = true,
|
||||
.interval = 0.5
|
||||
});
|
||||
|
||||
ecs_system(world, {
|
||||
.entity = ecs_id(MonitorAlertInstances),
|
||||
.interval = 0.5
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
155
engine/libs/flecs/src/addons/app.c
Normal file
155
engine/libs/flecs/src/addons/app.c
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* @file addons/app.c
|
||||
* @brief App addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_APP
|
||||
|
||||
static
|
||||
int flecs_default_run_action(
|
||||
ecs_world_t *world,
|
||||
ecs_app_desc_t *desc)
|
||||
{
|
||||
if (desc->init) {
|
||||
desc->init(world);
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
if (desc->frames) {
|
||||
int32_t i;
|
||||
for (i = 0; i < desc->frames; i ++) {
|
||||
if ((result = ecs_app_run_frame(world, desc)) != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while ((result = ecs_app_run_frame(world, desc)) == 0) { }
|
||||
}
|
||||
|
||||
/* Ensure quit flag is set on world, which can be used to determine if
|
||||
* world needs to be cleaned up. */
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ecs_quit(world);
|
||||
#endif
|
||||
|
||||
if (result == 1) {
|
||||
return 0; /* Normal exit */
|
||||
} else {
|
||||
return result; /* Error code */
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_default_frame_action(
|
||||
ecs_world_t *world,
|
||||
const ecs_app_desc_t *desc)
|
||||
{
|
||||
return !ecs_progress(world, desc->delta_time);
|
||||
}
|
||||
|
||||
static ecs_app_run_action_t run_action = flecs_default_run_action;
|
||||
static ecs_app_frame_action_t frame_action = flecs_default_frame_action;
|
||||
static ecs_app_desc_t ecs_app_desc;
|
||||
|
||||
/* Serve REST API from wasm image when running in emscripten */
|
||||
#ifdef ECS_TARGET_EM
|
||||
#include <emscripten.h>
|
||||
|
||||
ecs_http_server_t *flecs_wasm_rest_server;
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
char* flecs_explorer_request(const char *method, char *request) {
|
||||
ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT;
|
||||
ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply);
|
||||
if (reply.code == 200) {
|
||||
return ecs_strbuf_get(&reply.body);
|
||||
} else {
|
||||
char *body = ecs_strbuf_get(&reply.body);
|
||||
if (body) {
|
||||
return body;
|
||||
} else {
|
||||
return ecs_asprintf(
|
||||
"{\"error\": \"bad request (code %d)\"}", reply.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int ecs_app_run(
|
||||
ecs_world_t *world,
|
||||
ecs_app_desc_t *desc)
|
||||
{
|
||||
ecs_app_desc = *desc;
|
||||
|
||||
/* Don't set FPS & threads if custom run action is set, as the platform on
|
||||
* which the app is running may not support it. */
|
||||
if (run_action == flecs_default_run_action) {
|
||||
if (ECS_NEQZERO(ecs_app_desc.target_fps)) {
|
||||
ecs_set_target_fps(world, ecs_app_desc.target_fps);
|
||||
}
|
||||
if (ecs_app_desc.threads) {
|
||||
ecs_set_threads(world, ecs_app_desc.threads);
|
||||
}
|
||||
}
|
||||
|
||||
/* REST server enables connecting to app with explorer */
|
||||
if (desc->enable_rest) {
|
||||
#ifdef FLECS_REST
|
||||
#ifdef ECS_TARGET_EM
|
||||
flecs_wasm_rest_server = ecs_rest_server_init(world, NULL);
|
||||
#else
|
||||
ecs_set(world, EcsWorld, EcsRest, {.port = desc->port });
|
||||
#endif
|
||||
#else
|
||||
ecs_warn("cannot enable remote API, REST addon not available");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Monitoring periodically collects statistics */
|
||||
if (desc->enable_monitor) {
|
||||
#ifdef FLECS_MONITOR
|
||||
ECS_IMPORT(world, FlecsMonitor);
|
||||
#else
|
||||
ecs_warn("cannot enable monitoring, MONITOR addon not available");
|
||||
#endif
|
||||
}
|
||||
|
||||
return run_action(world, &ecs_app_desc);
|
||||
}
|
||||
|
||||
int ecs_app_run_frame(
|
||||
ecs_world_t *world,
|
||||
const ecs_app_desc_t *desc)
|
||||
{
|
||||
return frame_action(world, desc);
|
||||
}
|
||||
|
||||
int ecs_app_set_run_action(
|
||||
ecs_app_run_action_t callback)
|
||||
{
|
||||
if (run_action != flecs_default_run_action && run_action != callback) {
|
||||
ecs_err("run action already set");
|
||||
return -1;
|
||||
}
|
||||
|
||||
run_action = callback;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecs_app_set_frame_action(
|
||||
ecs_app_frame_action_t callback)
|
||||
{
|
||||
if (frame_action != flecs_default_frame_action && frame_action != callback) {
|
||||
ecs_err("frame action already set");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_action = callback;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
140
engine/libs/flecs/src/addons/coredoc.c
Normal file
140
engine/libs/flecs/src/addons/coredoc.c
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @file addons/coredoc.c
|
||||
* @brief Core doc addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_COREDOC
|
||||
|
||||
#define URL_ROOT "https://www.flecs.dev/flecs/md_docs_Relationships.html/"
|
||||
|
||||
void FlecsCoreDocImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsCoreDoc);
|
||||
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
ECS_IMPORT(world, FlecsDoc);
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
/* Initialize reflection data for core components */
|
||||
|
||||
ecs_struct_init(world, &(ecs_struct_desc_t){
|
||||
.entity = ecs_id(EcsComponent),
|
||||
.members = {
|
||||
{.name = "size", .type = ecs_id(ecs_i32_t)},
|
||||
{.name = "alignment", .type = ecs_id(ecs_i32_t)}
|
||||
}
|
||||
});
|
||||
|
||||
ecs_struct_init(world, &(ecs_struct_desc_t){
|
||||
.entity = ecs_id(EcsDocDescription),
|
||||
.members = {
|
||||
{.name = "value", .type = ecs_id(ecs_string_t)}
|
||||
}
|
||||
});
|
||||
|
||||
/* Initialize documentation data for core components */
|
||||
ecs_doc_set_brief(world, EcsFlecs, "Flecs root module");
|
||||
ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs");
|
||||
|
||||
ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components");
|
||||
|
||||
ecs_doc_set_brief(world, EcsWorld, "Entity associated with world");
|
||||
|
||||
ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all components");
|
||||
ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules");
|
||||
ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs");
|
||||
ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities");
|
||||
|
||||
ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names");
|
||||
ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name");
|
||||
ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol");
|
||||
|
||||
ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property");
|
||||
ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property");
|
||||
ecs_doc_set_brief(world, EcsFinal, "Final relationship property");
|
||||
ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property");
|
||||
ecs_doc_set_brief(world, EcsTag, "Tag relationship property");
|
||||
ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property");
|
||||
ecs_doc_set_brief(world, EcsTraversable, "Traversable relationship property");
|
||||
ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property");
|
||||
ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property");
|
||||
ecs_doc_set_brief(world, EcsWith, "With relationship property");
|
||||
ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property");
|
||||
ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property");
|
||||
ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity");
|
||||
ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property");
|
||||
ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property");
|
||||
ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property");
|
||||
ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship");
|
||||
ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship");
|
||||
ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship");
|
||||
ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event");
|
||||
ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event");
|
||||
ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event");
|
||||
ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event");
|
||||
|
||||
ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property");
|
||||
ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property");
|
||||
ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property");
|
||||
ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property");
|
||||
ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property");
|
||||
ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property");
|
||||
ecs_doc_set_link(world, EcsTraversable, URL_ROOT "#traversable-property");
|
||||
ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property");
|
||||
ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property");
|
||||
ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property");
|
||||
ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties");
|
||||
ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties");
|
||||
ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties");
|
||||
ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties");
|
||||
ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties");
|
||||
ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship");
|
||||
ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship");
|
||||
|
||||
/* Initialize documentation for meta components */
|
||||
ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta");
|
||||
ecs_doc_set_brief(world, meta, "Flecs module with reflection components");
|
||||
|
||||
ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types");
|
||||
ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format");
|
||||
ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types");
|
||||
ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types");
|
||||
ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types");
|
||||
ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members");
|
||||
ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types");
|
||||
ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types");
|
||||
ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types");
|
||||
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component");
|
||||
ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component");
|
||||
|
||||
/* Initialize documentation for doc components */
|
||||
ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc");
|
||||
ecs_doc_set_brief(world, doc, "Flecs module with documentation components");
|
||||
|
||||
ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation");
|
||||
ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description");
|
||||
ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description");
|
||||
ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link");
|
||||
}
|
||||
|
||||
#endif
|
||||
172
engine/libs/flecs/src/addons/doc.c
Normal file
172
engine/libs/flecs/src/addons/doc.c
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* @file addons/doc.c
|
||||
* @brief Doc addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_DOC
|
||||
|
||||
static ECS_COPY(EcsDocDescription, dst, src, {
|
||||
ecs_os_strset((char**)&dst->value, src->value);
|
||||
|
||||
})
|
||||
|
||||
static ECS_MOVE(EcsDocDescription, dst, src, {
|
||||
ecs_os_free((char*)dst->value);
|
||||
dst->value = src->value;
|
||||
src->value = NULL;
|
||||
})
|
||||
|
||||
static ECS_DTOR(EcsDocDescription, ptr, {
|
||||
ecs_os_free((char*)ptr->value);
|
||||
})
|
||||
|
||||
static
|
||||
void flecs_doc_set(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t kind,
|
||||
const char *value)
|
||||
{
|
||||
if (value) {
|
||||
ecs_set_pair(world, entity, EcsDocDescription, kind, {
|
||||
/* Safe, value gets copied by copy hook */
|
||||
.value = ECS_CONST_CAST(char*, value)
|
||||
});
|
||||
} else {
|
||||
ecs_remove_pair(world, entity, ecs_id(EcsDocDescription), kind);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_doc_set_name(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
const char *name)
|
||||
{
|
||||
flecs_doc_set(world, entity, EcsName, name);
|
||||
}
|
||||
|
||||
void ecs_doc_set_brief(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
const char *brief)
|
||||
{
|
||||
flecs_doc_set(world, entity, EcsDocBrief, brief);
|
||||
}
|
||||
|
||||
void ecs_doc_set_detail(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
const char *detail)
|
||||
{
|
||||
flecs_doc_set(world, entity, EcsDocDetail, detail);
|
||||
}
|
||||
|
||||
void ecs_doc_set_link(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
const char *link)
|
||||
{
|
||||
flecs_doc_set(world, entity, EcsDocLink, link);
|
||||
}
|
||||
|
||||
void ecs_doc_set_color(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
const char *color)
|
||||
{
|
||||
flecs_doc_set(world, entity, EcsDocColor, color);
|
||||
}
|
||||
|
||||
const char* ecs_doc_get_name(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
const EcsDocDescription *ptr = ecs_get_pair(
|
||||
world, entity, EcsDocDescription, EcsName);
|
||||
if (ptr) {
|
||||
return ptr->value;
|
||||
} else {
|
||||
return ecs_get_name(world, entity);
|
||||
}
|
||||
}
|
||||
|
||||
const char* ecs_doc_get_brief(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
const EcsDocDescription *ptr = ecs_get_pair(
|
||||
world, entity, EcsDocDescription, EcsDocBrief);
|
||||
if (ptr) {
|
||||
return ptr->value;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char* ecs_doc_get_detail(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
const EcsDocDescription *ptr = ecs_get_pair(
|
||||
world, entity, EcsDocDescription, EcsDocDetail);
|
||||
if (ptr) {
|
||||
return ptr->value;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char* ecs_doc_get_link(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
const EcsDocDescription *ptr = ecs_get_pair(
|
||||
world, entity, EcsDocDescription, EcsDocLink);
|
||||
if (ptr) {
|
||||
return ptr->value;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char* ecs_doc_get_color(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
const EcsDocDescription *ptr = ecs_get_pair(
|
||||
world, entity, EcsDocDescription, EcsDocColor);
|
||||
if (ptr) {
|
||||
return ptr->value;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void FlecsDocImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsDoc);
|
||||
|
||||
ecs_set_name_prefix(world, "EcsDoc");
|
||||
|
||||
flecs_bootstrap_component(world, EcsDocDescription);
|
||||
flecs_bootstrap_tag(world, EcsDocBrief);
|
||||
flecs_bootstrap_tag(world, EcsDocDetail);
|
||||
flecs_bootstrap_tag(world, EcsDocLink);
|
||||
flecs_bootstrap_tag(world, EcsDocColor);
|
||||
|
||||
ecs_set_hooks(world, EcsDocDescription, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.move = ecs_move(EcsDocDescription),
|
||||
.copy = ecs_copy(EcsDocDescription),
|
||||
.dtor = ecs_dtor(EcsDocDescription)
|
||||
});
|
||||
|
||||
ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit);
|
||||
ecs_add_id(world, ecs_id(EcsDocDescription), EcsPrivate);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1413
engine/libs/flecs/src/addons/expr/deserialize.c
Normal file
1413
engine/libs/flecs/src/addons/expr/deserialize.c
Normal file
File diff suppressed because it is too large
Load Diff
574
engine/libs/flecs/src/addons/expr/serialize.c
Normal file
574
engine/libs/flecs/src/addons/expr/serialize.c
Normal file
@@ -0,0 +1,574 @@
|
||||
/**
|
||||
* @file expr/serialize.c
|
||||
* @brief Serialize (component) values to flecs string format.
|
||||
*/
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
#ifdef FLECS_EXPR
|
||||
|
||||
static
|
||||
int flecs_expr_ser_type(
|
||||
const ecs_world_t *world,
|
||||
const ecs_vec_t *ser,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str,
|
||||
bool is_expr);
|
||||
|
||||
static
|
||||
int flecs_expr_ser_type_ops(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *ops,
|
||||
int32_t op_count,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str,
|
||||
int32_t in_array,
|
||||
bool is_expr);
|
||||
|
||||
static
|
||||
int flecs_expr_ser_type_op(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str,
|
||||
bool is_expr);
|
||||
|
||||
static
|
||||
ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) {
|
||||
return kind - EcsOpPrimitive;
|
||||
}
|
||||
|
||||
/* Serialize a primitive value */
|
||||
static
|
||||
int flecs_expr_ser_primitive(
|
||||
const ecs_world_t *world,
|
||||
ecs_primitive_kind_t kind,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str,
|
||||
bool is_expr)
|
||||
{
|
||||
switch(kind) {
|
||||
case EcsBool:
|
||||
if (*(const bool*)base) {
|
||||
ecs_strbuf_appendlit(str, "true");
|
||||
} else {
|
||||
ecs_strbuf_appendlit(str, "false");
|
||||
}
|
||||
break;
|
||||
case EcsChar: {
|
||||
char chbuf[3];
|
||||
char ch = *(const char*)base;
|
||||
if (ch) {
|
||||
ecs_chresc(chbuf, *(const char*)base, '"');
|
||||
if (is_expr) ecs_strbuf_appendch(str, '"');
|
||||
ecs_strbuf_appendstr(str, chbuf);
|
||||
if (is_expr) ecs_strbuf_appendch(str, '"');
|
||||
} else {
|
||||
ecs_strbuf_appendch(str, '0');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EcsByte:
|
||||
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base));
|
||||
break;
|
||||
case EcsU8:
|
||||
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base));
|
||||
break;
|
||||
case EcsU16:
|
||||
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint16_t*)base));
|
||||
break;
|
||||
case EcsU32:
|
||||
ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint32_t*)base));
|
||||
break;
|
||||
case EcsU64:
|
||||
ecs_strbuf_append(str, "%llu", *(const uint64_t*)base);
|
||||
break;
|
||||
case EcsI8:
|
||||
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int8_t*)base));
|
||||
break;
|
||||
case EcsI16:
|
||||
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int16_t*)base));
|
||||
break;
|
||||
case EcsI32:
|
||||
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int32_t*)base));
|
||||
break;
|
||||
case EcsI64:
|
||||
ecs_strbuf_appendint(str, *(const int64_t*)base);
|
||||
break;
|
||||
case EcsF32:
|
||||
ecs_strbuf_appendflt(str, (double)*(const float*)base, 0);
|
||||
break;
|
||||
case EcsF64:
|
||||
ecs_strbuf_appendflt(str, *(const double*)base, 0);
|
||||
break;
|
||||
case EcsIPtr:
|
||||
ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const intptr_t*)base));
|
||||
break;
|
||||
case EcsUPtr:
|
||||
ecs_strbuf_append(str, "%u", *(const uintptr_t*)base);
|
||||
break;
|
||||
case EcsString: {
|
||||
const char *value = *ECS_CONST_CAST(const char**, base);
|
||||
if (value) {
|
||||
if (!is_expr) {
|
||||
ecs_strbuf_appendstr(str, value);
|
||||
} else {
|
||||
ecs_size_t length = ecs_stresc(NULL, 0, '"', value);
|
||||
if (length == ecs_os_strlen(value)) {
|
||||
ecs_strbuf_appendch(str, '"');
|
||||
ecs_strbuf_appendstrn(str, value, length);
|
||||
ecs_strbuf_appendch(str, '"');
|
||||
} else {
|
||||
char *out = ecs_os_malloc(length + 3);
|
||||
ecs_stresc(out + 1, length, '"', value);
|
||||
out[0] = '"';
|
||||
out[length + 1] = '"';
|
||||
out[length + 2] = '\0';
|
||||
ecs_strbuf_appendstr_zerocpy(str, out);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ecs_strbuf_appendlit(str, "null");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EcsEntity: {
|
||||
ecs_entity_t e = *(const ecs_entity_t*)base;
|
||||
if (!e) {
|
||||
ecs_strbuf_appendch(str, '0');
|
||||
} else {
|
||||
ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ecs_err("invalid primitive kind");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Serialize enumeration */
|
||||
static
|
||||
int flecs_expr_ser_enum(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum);
|
||||
ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
int32_t val = *(const int32_t*)base;
|
||||
|
||||
/* Enumeration constants are stored in a map that is keyed on the
|
||||
* enumeration value. */
|
||||
ecs_enum_constant_t *c = ecs_map_get_deref(&enum_type->constants,
|
||||
ecs_enum_constant_t, (ecs_map_key_t)val);
|
||||
if (!c) {
|
||||
char *path = ecs_get_fullpath(world, op->type);
|
||||
ecs_err("value %d is not valid for enum type '%s'", val, path);
|
||||
ecs_os_free(path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_strbuf_appendstr(str, ecs_get_name(world, c->constant));
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Serialize bitmask */
|
||||
static
|
||||
int flecs_expr_ser_bitmask(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *op,
|
||||
const void *ptr,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask);
|
||||
ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
uint32_t value = *(const uint32_t*)ptr;
|
||||
|
||||
ecs_strbuf_list_push(str, "", "|");
|
||||
|
||||
/* Multiple flags can be set at a given time. Iterate through all the flags
|
||||
* and append the ones that are set. */
|
||||
ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants);
|
||||
int count = 0;
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_bitmask_constant_t *c = ecs_map_ptr(&it);
|
||||
ecs_map_key_t key = ecs_map_key(&it);
|
||||
if ((value & key) == key) {
|
||||
ecs_strbuf_list_appendstr(str, ecs_get_name(world, c->constant));
|
||||
count ++;
|
||||
value -= (uint32_t)key;
|
||||
}
|
||||
}
|
||||
|
||||
if (value != 0) {
|
||||
/* All bits must have been matched by a constant */
|
||||
char *path = ecs_get_fullpath(world, op->type);
|
||||
ecs_err(
|
||||
"value for bitmask %s contains bits (%u) that cannot be mapped to constant",
|
||||
path, value);
|
||||
ecs_os_free(path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!count) {
|
||||
ecs_strbuf_list_appendstr(str, "0");
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(str, "");
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Serialize elements of a contiguous array */
|
||||
static
|
||||
int expr_ser_elements(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *ops,
|
||||
int32_t op_count,
|
||||
const void *base,
|
||||
int32_t elem_count,
|
||||
int32_t elem_size,
|
||||
ecs_strbuf_t *str,
|
||||
bool is_array)
|
||||
{
|
||||
ecs_strbuf_list_push(str, "[", ", ");
|
||||
|
||||
const void *ptr = base;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < elem_count; i ++) {
|
||||
ecs_strbuf_list_next(str);
|
||||
if (flecs_expr_ser_type_ops(
|
||||
world, ops, op_count, ptr, str, is_array, true))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr = ECS_OFFSET(ptr, elem_size);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(str, "]");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int expr_ser_type_elements(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
const void *base,
|
||||
int32_t elem_count,
|
||||
ecs_strbuf_t *str,
|
||||
bool is_array)
|
||||
{
|
||||
const EcsMetaTypeSerialized *ser = ecs_get(
|
||||
world, type, EcsMetaTypeSerialized);
|
||||
ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
const EcsComponent *comp = ecs_get(world, type, EcsComponent);
|
||||
ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t);
|
||||
int32_t op_count = ecs_vec_count(&ser->ops);
|
||||
return expr_ser_elements(
|
||||
world, ops, op_count, base, elem_count, comp->size, str, is_array);
|
||||
}
|
||||
|
||||
/* Serialize array */
|
||||
static
|
||||
int expr_ser_array(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *op,
|
||||
const void *ptr,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const EcsArray *a = ecs_get(world, op->type, EcsArray);
|
||||
ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
return expr_ser_type_elements(
|
||||
world, a->type, ptr, a->count, str, true);
|
||||
}
|
||||
|
||||
/* Serialize vector */
|
||||
static
|
||||
int expr_ser_vector(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const ecs_vec_t *value = base;
|
||||
const EcsVector *v = ecs_get(world, op->type, EcsVector);
|
||||
ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
int32_t count = ecs_vec_count(value);
|
||||
void *array = ecs_vec_first(value);
|
||||
|
||||
/* Serialize contiguous buffer of vector */
|
||||
return expr_ser_type_elements(world, v->type, array, count, str, false);
|
||||
}
|
||||
|
||||
/* Forward serialization to the different type kinds */
|
||||
static
|
||||
int flecs_expr_ser_type_op(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *op,
|
||||
const void *ptr,
|
||||
ecs_strbuf_t *str,
|
||||
bool is_expr)
|
||||
{
|
||||
switch(op->kind) {
|
||||
case EcsOpPush:
|
||||
case EcsOpPop:
|
||||
/* Should not be parsed as single op */
|
||||
ecs_throw(ECS_INVALID_PARAMETER, NULL);
|
||||
break;
|
||||
case EcsOpEnum:
|
||||
if (flecs_expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EcsOpBitmask:
|
||||
if (flecs_expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EcsOpArray:
|
||||
if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EcsOpVector:
|
||||
if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EcsOpScope:
|
||||
case EcsOpPrimitive:
|
||||
case EcsOpBool:
|
||||
case EcsOpChar:
|
||||
case EcsOpByte:
|
||||
case EcsOpU8:
|
||||
case EcsOpU16:
|
||||
case EcsOpU32:
|
||||
case EcsOpU64:
|
||||
case EcsOpI8:
|
||||
case EcsOpI16:
|
||||
case EcsOpI32:
|
||||
case EcsOpI64:
|
||||
case EcsOpF32:
|
||||
case EcsOpF64:
|
||||
case EcsOpUPtr:
|
||||
case EcsOpIPtr:
|
||||
case EcsOpEntity:
|
||||
case EcsOpString:
|
||||
case EcsOpOpaque:
|
||||
if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind),
|
||||
ECS_OFFSET(ptr, op->offset), str, is_expr))
|
||||
{
|
||||
/* Unknown operation */
|
||||
ecs_err("unknown serializer operation kind (%d)", op->kind);
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation");
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Iterate over a slice of the type ops array */
|
||||
static
|
||||
int flecs_expr_ser_type_ops(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *ops,
|
||||
int32_t op_count,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str,
|
||||
int32_t in_array,
|
||||
bool is_expr)
|
||||
{
|
||||
for (int i = 0; i < op_count; i ++) {
|
||||
ecs_meta_type_op_t *op = &ops[i];
|
||||
|
||||
if (in_array <= 0) {
|
||||
if (op->name) {
|
||||
ecs_strbuf_list_next(str);
|
||||
ecs_strbuf_append(str, "%s: ", op->name);
|
||||
}
|
||||
|
||||
int32_t elem_count = op->count;
|
||||
if (elem_count > 1) {
|
||||
/* Serialize inline array */
|
||||
if (expr_ser_elements(world, op, op->op_count, base,
|
||||
elem_count, op->size, str, true))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
i += op->op_count - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch(op->kind) {
|
||||
case EcsOpPush:
|
||||
ecs_strbuf_list_push(str, "{", ", ");
|
||||
in_array --;
|
||||
break;
|
||||
case EcsOpPop:
|
||||
ecs_strbuf_list_pop(str, "}");
|
||||
in_array ++;
|
||||
break;
|
||||
case EcsOpArray:
|
||||
case EcsOpVector:
|
||||
case EcsOpEnum:
|
||||
case EcsOpBitmask:
|
||||
case EcsOpScope:
|
||||
case EcsOpPrimitive:
|
||||
case EcsOpBool:
|
||||
case EcsOpChar:
|
||||
case EcsOpByte:
|
||||
case EcsOpU8:
|
||||
case EcsOpU16:
|
||||
case EcsOpU32:
|
||||
case EcsOpU64:
|
||||
case EcsOpI8:
|
||||
case EcsOpI16:
|
||||
case EcsOpI32:
|
||||
case EcsOpI64:
|
||||
case EcsOpF32:
|
||||
case EcsOpF64:
|
||||
case EcsOpUPtr:
|
||||
case EcsOpIPtr:
|
||||
case EcsOpEntity:
|
||||
case EcsOpString:
|
||||
case EcsOpOpaque:
|
||||
if (flecs_expr_ser_type_op(world, op, base, str, is_expr)) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ecs_throw(ECS_INVALID_PARAMETER, "invalid operation");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Iterate over the type ops of a type */
|
||||
static
|
||||
int flecs_expr_ser_type(
|
||||
const ecs_world_t *world,
|
||||
const ecs_vec_t *v_ops,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str,
|
||||
bool is_expr)
|
||||
{
|
||||
ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t);
|
||||
int32_t count = ecs_vec_count(v_ops);
|
||||
return flecs_expr_ser_type_ops(world, ops, count, base, str, 0, is_expr);
|
||||
}
|
||||
|
||||
int ecs_ptr_to_expr_buf(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
const void *ptr,
|
||||
ecs_strbuf_t *buf_out)
|
||||
{
|
||||
const EcsMetaTypeSerialized *ser = ecs_get(
|
||||
world, type, EcsMetaTypeSerialized);
|
||||
if (ser == NULL) {
|
||||
char *path = ecs_get_fullpath(world, type);
|
||||
ecs_err("cannot serialize value for type '%s'", path);
|
||||
ecs_os_free(path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, true)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* ecs_ptr_to_expr(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
const void* ptr)
|
||||
{
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
|
||||
if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) {
|
||||
ecs_strbuf_reset(&str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ecs_strbuf_get(&str);
|
||||
}
|
||||
|
||||
int ecs_ptr_to_str_buf(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
const void *ptr,
|
||||
ecs_strbuf_t *buf_out)
|
||||
{
|
||||
const EcsMetaTypeSerialized *ser = ecs_get(
|
||||
world, type, EcsMetaTypeSerialized);
|
||||
if (ser == NULL) {
|
||||
char *path = ecs_get_fullpath(world, type);
|
||||
ecs_err("cannot serialize value for type '%s'", path);
|
||||
ecs_os_free(path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, false)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* ecs_ptr_to_str(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
const void* ptr)
|
||||
{
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
|
||||
if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) {
|
||||
ecs_strbuf_reset(&str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ecs_strbuf_get(&str);
|
||||
}
|
||||
|
||||
int ecs_primitive_to_expr_buf(
|
||||
const ecs_world_t *world,
|
||||
ecs_primitive_kind_t kind,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
return flecs_expr_ser_primitive(world, kind, base, str, true);
|
||||
}
|
||||
|
||||
#endif
|
||||
433
engine/libs/flecs/src/addons/expr/utils.c
Normal file
433
engine/libs/flecs/src/addons/expr/utils.c
Normal file
@@ -0,0 +1,433 @@
|
||||
/**
|
||||
* @file expr/utils.c
|
||||
* @brief String parsing utilities.
|
||||
*/
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
#ifdef FLECS_EXPR
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
char* ecs_chresc(
|
||||
char *out,
|
||||
char in,
|
||||
char delimiter)
|
||||
{
|
||||
char *bptr = out;
|
||||
switch(in) {
|
||||
case '\a':
|
||||
*bptr++ = '\\';
|
||||
*bptr = 'a';
|
||||
break;
|
||||
case '\b':
|
||||
*bptr++ = '\\';
|
||||
*bptr = 'b';
|
||||
break;
|
||||
case '\f':
|
||||
*bptr++ = '\\';
|
||||
*bptr = 'f';
|
||||
break;
|
||||
case '\n':
|
||||
*bptr++ = '\\';
|
||||
*bptr = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
*bptr++ = '\\';
|
||||
*bptr = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
*bptr++ = '\\';
|
||||
*bptr = 't';
|
||||
break;
|
||||
case '\v':
|
||||
*bptr++ = '\\';
|
||||
*bptr = 'v';
|
||||
break;
|
||||
case '\\':
|
||||
*bptr++ = '\\';
|
||||
*bptr = '\\';
|
||||
break;
|
||||
default:
|
||||
if (in == delimiter) {
|
||||
*bptr++ = '\\';
|
||||
*bptr = delimiter;
|
||||
} else {
|
||||
*bptr = in;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
*(++bptr) = '\0';
|
||||
|
||||
return bptr;
|
||||
}
|
||||
|
||||
const char* ecs_chrparse(
|
||||
const char *in,
|
||||
char *out)
|
||||
{
|
||||
const char *result = in + 1;
|
||||
char ch;
|
||||
|
||||
if (in[0] == '\\') {
|
||||
result ++;
|
||||
|
||||
switch(in[1]) {
|
||||
case 'a':
|
||||
ch = '\a';
|
||||
break;
|
||||
case 'b':
|
||||
ch = '\b';
|
||||
break;
|
||||
case 'f':
|
||||
ch = '\f';
|
||||
break;
|
||||
case 'n':
|
||||
ch = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
ch = '\r';
|
||||
break;
|
||||
case 't':
|
||||
ch = '\t';
|
||||
break;
|
||||
case 'v':
|
||||
ch = '\v';
|
||||
break;
|
||||
case '\\':
|
||||
ch = '\\';
|
||||
break;
|
||||
case '"':
|
||||
ch = '"';
|
||||
break;
|
||||
case '0':
|
||||
ch = '\0';
|
||||
break;
|
||||
case ' ':
|
||||
ch = ' ';
|
||||
break;
|
||||
case '$':
|
||||
ch = '$';
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
ch = in[0];
|
||||
}
|
||||
|
||||
if (out) {
|
||||
*out = ch;
|
||||
}
|
||||
|
||||
return result;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_size_t ecs_stresc(
|
||||
char *out,
|
||||
ecs_size_t n,
|
||||
char delimiter,
|
||||
const char *in)
|
||||
{
|
||||
const char *ptr = in;
|
||||
char ch, *bptr = out, buff[3];
|
||||
ecs_size_t written = 0;
|
||||
while ((ch = *ptr++)) {
|
||||
if ((written += (ecs_size_t)(ecs_chresc(
|
||||
buff, ch, delimiter) - buff)) <= n)
|
||||
{
|
||||
/* If size != 0, an out buffer must be provided. */
|
||||
ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
*bptr++ = buff[0];
|
||||
if ((ch = buff[1])) {
|
||||
*bptr = ch;
|
||||
bptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bptr) {
|
||||
while (written < n) {
|
||||
*bptr = '\0';
|
||||
bptr++;
|
||||
written++;
|
||||
}
|
||||
}
|
||||
return written;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* ecs_astresc(
|
||||
char delimiter,
|
||||
const char *in)
|
||||
{
|
||||
if (!in) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_size_t len = ecs_stresc(NULL, 0, delimiter, in);
|
||||
char *out = ecs_os_malloc_n(char, len + 1);
|
||||
ecs_stresc(out, len, delimiter, in);
|
||||
out[len] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
static
|
||||
const char* flecs_parse_var_name(
|
||||
const char *ptr,
|
||||
char *token_out)
|
||||
{
|
||||
char ch, *bptr = token_out;
|
||||
|
||||
while ((ch = *ptr)) {
|
||||
if (bptr - token_out > ECS_MAX_TOKEN_SIZE) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (isalpha(ch) || isdigit(ch) || ch == '_') {
|
||||
*bptr = ch;
|
||||
bptr ++;
|
||||
ptr ++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bptr == token_out) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
*bptr = '\0';
|
||||
|
||||
return ptr;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
const char* flecs_parse_interpolated_str(
|
||||
const char *ptr,
|
||||
char *token_out)
|
||||
{
|
||||
char ch, *bptr = token_out;
|
||||
|
||||
while ((ch = *ptr)) {
|
||||
if (bptr - token_out > ECS_MAX_TOKEN_SIZE) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ch == '\\') {
|
||||
if (ptr[1] == '}') {
|
||||
*bptr = '}';
|
||||
bptr ++;
|
||||
ptr += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ch != '}') {
|
||||
*bptr = ch;
|
||||
bptr ++;
|
||||
ptr ++;
|
||||
} else {
|
||||
ptr ++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bptr == token_out) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
*bptr = '\0';
|
||||
|
||||
return ptr;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* ecs_interpolate_string(
|
||||
ecs_world_t *world,
|
||||
const char *str,
|
||||
const ecs_vars_t *vars)
|
||||
{
|
||||
char token[ECS_MAX_TOKEN_SIZE];
|
||||
ecs_strbuf_t result = ECS_STRBUF_INIT;
|
||||
const char *ptr;
|
||||
char ch;
|
||||
|
||||
for(ptr = str; (ch = *ptr); ptr++) {
|
||||
if (ch == '\\') {
|
||||
ptr ++;
|
||||
if (ptr[0] == '$') {
|
||||
ecs_strbuf_appendch(&result, '$');
|
||||
continue;
|
||||
}
|
||||
if (ptr[0] == '\\') {
|
||||
ecs_strbuf_appendch(&result, '\\');
|
||||
continue;
|
||||
}
|
||||
if (ptr[0] == '{') {
|
||||
ecs_strbuf_appendch(&result, '{');
|
||||
continue;
|
||||
}
|
||||
if (ptr[0] == '}') {
|
||||
ecs_strbuf_appendch(&result, '}');
|
||||
continue;
|
||||
}
|
||||
ptr --;
|
||||
}
|
||||
|
||||
if (ch == '$') {
|
||||
ptr = flecs_parse_var_name(ptr + 1, token);
|
||||
if (!ptr) {
|
||||
ecs_parser_error(NULL, str, ptr - str,
|
||||
"invalid variable name '%s'", ptr);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_expr_var_t *var = ecs_vars_lookup(vars, token);
|
||||
if (!var) {
|
||||
ecs_parser_error(NULL, str, ptr - str,
|
||||
"unresolved variable '%s'", token);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ecs_ptr_to_str_buf(
|
||||
world, var->value.type, var->value.ptr, &result))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
ptr --;
|
||||
} else if (ch == '{') {
|
||||
ptr = flecs_parse_interpolated_str(ptr + 1, token);
|
||||
if (!ptr) {
|
||||
ecs_parser_error(NULL, str, ptr - str,
|
||||
"invalid interpolated expression");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_parse_expr_desc_t expr_desc = {
|
||||
.vars = ECS_CONST_CAST(ecs_vars_t*, vars)
|
||||
};
|
||||
ecs_value_t expr_result = {0};
|
||||
if (!ecs_parse_expr(world, token, &expr_result, &expr_desc)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ecs_ptr_to_str_buf(
|
||||
world, expr_result.type, expr_result.ptr, &result))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_value_free(world, expr_result.type, expr_result.ptr);
|
||||
|
||||
ptr --;
|
||||
} else {
|
||||
ecs_strbuf_appendch(&result, ch);
|
||||
}
|
||||
}
|
||||
|
||||
return ecs_strbuf_get(&result);
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ecs_iter_to_vars(
|
||||
const ecs_iter_t *it,
|
||||
ecs_vars_t *vars,
|
||||
int offset)
|
||||
{
|
||||
ecs_check(vars != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(!offset || offset < it->count, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
/* Set variable for $this */
|
||||
if (it->count) {
|
||||
ecs_expr_var_t *var = ecs_vars_lookup(vars, "this");
|
||||
if (!var) {
|
||||
ecs_value_t v = {
|
||||
.ptr = &it->entities[offset],
|
||||
.type = ecs_id(ecs_entity_t)
|
||||
};
|
||||
var = ecs_vars_declare_w_value(vars, "this", &v);
|
||||
var->owned = false;
|
||||
} else {
|
||||
var->value.ptr = &it->entities[offset];
|
||||
}
|
||||
}
|
||||
|
||||
/* Set variables for fields */
|
||||
{
|
||||
int32_t i, field_count = it->field_count;
|
||||
for (i = 0; i < field_count; i ++) {
|
||||
ecs_size_t size = it->sizes[i];
|
||||
if (!size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
void *ptr = it->ptrs[i];
|
||||
if (!ptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ptr = ECS_OFFSET(ptr, offset * size);
|
||||
|
||||
char name[16];
|
||||
ecs_os_sprintf(name, "%d", i + 1);
|
||||
ecs_expr_var_t *var = ecs_vars_lookup(vars, name);
|
||||
if (!var) {
|
||||
ecs_value_t v = { .ptr = ptr, .type = it->ids[i] };
|
||||
var = ecs_vars_declare_w_value(vars, name, &v);
|
||||
var->owned = false;
|
||||
} else {
|
||||
ecs_check(var->value.type == it->ids[i],
|
||||
ECS_INVALID_PARAMETER, NULL);
|
||||
var->value.ptr = ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set variables for query variables */
|
||||
{
|
||||
int32_t i, var_count = it->variable_count;
|
||||
for (i = 1 /* skip this variable */ ; i < var_count; i ++) {
|
||||
ecs_entity_t *e_ptr = NULL;
|
||||
ecs_var_t *query_var = &it->variables[i];
|
||||
if (query_var->entity) {
|
||||
e_ptr = &query_var->entity;
|
||||
} else {
|
||||
ecs_table_range_t *range = &query_var->range;
|
||||
if (range->count == 1) {
|
||||
ecs_entity_t *entities = range->table->data.entities.array;
|
||||
e_ptr = &entities[range->offset];
|
||||
}
|
||||
}
|
||||
if (!e_ptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_expr_var_t *var = ecs_vars_lookup(vars, it->variable_names[i]);
|
||||
if (!var) {
|
||||
ecs_value_t v = { .ptr = e_ptr, .type = ecs_id(ecs_entity_t) };
|
||||
var = ecs_vars_declare_w_value(vars, it->variable_names[i], &v);
|
||||
var->owned = false;
|
||||
} else {
|
||||
ecs_check(var->value.type == ecs_id(ecs_entity_t),
|
||||
ECS_INVALID_PARAMETER, NULL);
|
||||
var->value.ptr = e_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
175
engine/libs/flecs/src/addons/expr/vars.c
Normal file
175
engine/libs/flecs/src/addons/expr/vars.c
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* @file expr/vars.c
|
||||
* @brief Utilities for variable substitution in flecs string expressions.
|
||||
*/
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
#ifdef FLECS_EXPR
|
||||
|
||||
static
|
||||
void flecs_expr_var_scope_init(
|
||||
ecs_world_t *world,
|
||||
ecs_expr_var_scope_t *scope,
|
||||
ecs_expr_var_scope_t *parent)
|
||||
{
|
||||
flecs_name_index_init(&scope->var_index, &world->allocator);
|
||||
ecs_vec_init_t(&world->allocator, &scope->vars, ecs_expr_var_t, 0);
|
||||
scope->parent = parent;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_expr_var_scope_fini(
|
||||
ecs_world_t *world,
|
||||
ecs_expr_var_scope_t *scope)
|
||||
{
|
||||
ecs_vec_t *vars = &scope->vars;
|
||||
int32_t i, count = vars->count;
|
||||
for (i = 0; i < count; i++) {
|
||||
ecs_expr_var_t *var = ecs_vec_get_t(vars, ecs_expr_var_t, i);
|
||||
if (var->owned) {
|
||||
ecs_value_free(world, var->value.type, var->value.ptr);
|
||||
}
|
||||
flecs_strfree(&world->allocator, var->name);
|
||||
}
|
||||
|
||||
ecs_vec_fini_t(&world->allocator, &scope->vars, ecs_expr_var_t);
|
||||
flecs_name_index_fini(&scope->var_index);
|
||||
}
|
||||
|
||||
void ecs_vars_init(
|
||||
ecs_world_t *world,
|
||||
ecs_vars_t *vars)
|
||||
{
|
||||
flecs_expr_var_scope_init(world, &vars->root, NULL);
|
||||
vars->world = world;
|
||||
vars->cur = &vars->root;
|
||||
}
|
||||
|
||||
void ecs_vars_fini(
|
||||
ecs_vars_t *vars)
|
||||
{
|
||||
ecs_expr_var_scope_t *cur = vars->cur, *next;
|
||||
do {
|
||||
next = cur->parent;
|
||||
flecs_expr_var_scope_fini(vars->world, cur);
|
||||
if (cur != &vars->root) {
|
||||
flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, cur);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while ((cur = next));
|
||||
}
|
||||
|
||||
void ecs_vars_push(
|
||||
ecs_vars_t *vars)
|
||||
{
|
||||
ecs_expr_var_scope_t *scope = flecs_calloc_t(&vars->world->allocator,
|
||||
ecs_expr_var_scope_t);
|
||||
flecs_expr_var_scope_init(vars->world, scope, vars->cur);
|
||||
vars->cur = scope;
|
||||
}
|
||||
|
||||
int ecs_vars_pop(
|
||||
ecs_vars_t *vars)
|
||||
{
|
||||
ecs_expr_var_scope_t *scope = vars->cur;
|
||||
ecs_check(scope != &vars->root, ECS_INVALID_OPERATION, NULL);
|
||||
vars->cur = scope->parent;
|
||||
flecs_expr_var_scope_fini(vars->world, scope);
|
||||
flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, scope);
|
||||
return 0;
|
||||
error:
|
||||
return 1;
|
||||
}
|
||||
|
||||
ecs_expr_var_t* ecs_vars_declare(
|
||||
ecs_vars_t *vars,
|
||||
const char *name,
|
||||
ecs_entity_t type)
|
||||
{
|
||||
ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_expr_var_scope_t *scope = vars->cur;
|
||||
ecs_hashmap_t *var_index = &scope->var_index;
|
||||
|
||||
if (flecs_name_index_find(var_index, name, 0, 0) != 0) {
|
||||
ecs_err("variable %s redeclared", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator,
|
||||
&scope->vars, ecs_expr_var_t);
|
||||
|
||||
var->value.ptr = ecs_value_new(vars->world, type);
|
||||
if (!var->value.ptr) {
|
||||
goto error;
|
||||
}
|
||||
var->value.type = type;
|
||||
var->name = flecs_strdup(&vars->world->allocator, name);
|
||||
var->owned = true;
|
||||
|
||||
flecs_name_index_ensure(var_index,
|
||||
flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0);
|
||||
return var;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_expr_var_t* ecs_vars_declare_w_value(
|
||||
ecs_vars_t *vars,
|
||||
const char *name,
|
||||
ecs_value_t *value)
|
||||
{
|
||||
ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_expr_var_scope_t *scope = vars->cur;
|
||||
ecs_hashmap_t *var_index = &scope->var_index;
|
||||
|
||||
if (flecs_name_index_find(var_index, name, 0, 0) != 0) {
|
||||
ecs_err("variable %s redeclared", name);
|
||||
ecs_value_free(vars->world, value->type, value->ptr);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator,
|
||||
&scope->vars, ecs_expr_var_t);
|
||||
var->value = *value;
|
||||
var->name = flecs_strdup(&vars->world->allocator, name);
|
||||
var->owned = true;
|
||||
value->ptr = NULL; /* Take ownership, prevent double free */
|
||||
|
||||
flecs_name_index_ensure(var_index,
|
||||
flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0);
|
||||
return var;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_expr_var_t* flecs_vars_scope_lookup(
|
||||
ecs_expr_var_scope_t *scope,
|
||||
const char *name)
|
||||
{
|
||||
uint64_t var_id = flecs_name_index_find(&scope->var_index, name, 0, 0);
|
||||
if (var_id == 0) {
|
||||
if (scope->parent) {
|
||||
return flecs_vars_scope_lookup(scope->parent, name);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ecs_vec_get_t(&scope->vars, ecs_expr_var_t,
|
||||
flecs_uto(int32_t, var_id - 1));
|
||||
}
|
||||
|
||||
ecs_expr_var_t* ecs_vars_lookup(
|
||||
const ecs_vars_t *vars,
|
||||
const char *name)
|
||||
{
|
||||
return flecs_vars_scope_lookup(vars->cur, name);
|
||||
}
|
||||
|
||||
#endif
|
||||
516
engine/libs/flecs/src/addons/flecs_cpp.c
Normal file
516
engine/libs/flecs/src/addons/flecs_cpp.c
Normal file
@@ -0,0 +1,516 @@
|
||||
/**
|
||||
* @file addons/flecs_cpp.c
|
||||
* @brief Utilities for C++ addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
#include <ctype.h>
|
||||
|
||||
/* Utilities for C++ API */
|
||||
|
||||
#ifdef FLECS_CPP
|
||||
|
||||
/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to
|
||||
* a uniform identifier */
|
||||
|
||||
#define ECS_CONST_PREFIX "const "
|
||||
#define ECS_STRUCT_PREFIX "struct "
|
||||
#define ECS_CLASS_PREFIX "class "
|
||||
#define ECS_ENUM_PREFIX "enum "
|
||||
|
||||
#define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX))
|
||||
#define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX))
|
||||
#define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX))
|
||||
#define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX))
|
||||
|
||||
static
|
||||
ecs_size_t ecs_cpp_strip_prefix(
|
||||
char *typeName,
|
||||
ecs_size_t len,
|
||||
const char *prefix,
|
||||
ecs_size_t prefix_len)
|
||||
{
|
||||
if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) {
|
||||
ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len);
|
||||
typeName[len - prefix_len] = '\0';
|
||||
len -= prefix_len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static
|
||||
void ecs_cpp_trim_type_name(
|
||||
char *typeName)
|
||||
{
|
||||
ecs_size_t len = ecs_os_strlen(typeName);
|
||||
|
||||
len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN);
|
||||
len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN);
|
||||
len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN);
|
||||
len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN);
|
||||
|
||||
while (typeName[len - 1] == ' ' ||
|
||||
typeName[len - 1] == '&' ||
|
||||
typeName[len - 1] == '*')
|
||||
{
|
||||
len --;
|
||||
typeName[len] = '\0';
|
||||
}
|
||||
|
||||
/* Remove const at end of string */
|
||||
if (len > ECS_CONST_LEN) {
|
||||
if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) {
|
||||
typeName[len - ECS_CONST_LEN] = '\0';
|
||||
}
|
||||
len -= ECS_CONST_LEN;
|
||||
}
|
||||
|
||||
/* Check if there are any remaining "struct " strings, which can happen
|
||||
* if this is a template type on msvc. */
|
||||
if (len > ECS_STRUCT_LEN) {
|
||||
char *ptr = typeName;
|
||||
while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) {
|
||||
/* Make sure we're not matched with part of a longer identifier
|
||||
* that contains 'struct' */
|
||||
if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) {
|
||||
ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN,
|
||||
ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1);
|
||||
len -= ECS_STRUCT_LEN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char* ecs_cpp_get_type_name(
|
||||
char *type_name,
|
||||
const char *func_name,
|
||||
size_t len,
|
||||
size_t front_len)
|
||||
{
|
||||
memcpy(type_name, func_name + front_len, len);
|
||||
type_name[len] = '\0';
|
||||
ecs_cpp_trim_type_name(type_name);
|
||||
return type_name;
|
||||
}
|
||||
|
||||
char* ecs_cpp_get_symbol_name(
|
||||
char *symbol_name,
|
||||
const char *type_name,
|
||||
size_t len)
|
||||
{
|
||||
// Symbol is same as name, but with '::' replaced with '.'
|
||||
ecs_os_strcpy(symbol_name, type_name);
|
||||
|
||||
char *ptr;
|
||||
size_t i;
|
||||
for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) {
|
||||
if (*ptr == ':') {
|
||||
symbol_name[i] = '.';
|
||||
ptr ++;
|
||||
} else {
|
||||
symbol_name[i] = *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
symbol_name[i] = '\0';
|
||||
|
||||
return symbol_name;
|
||||
}
|
||||
|
||||
static
|
||||
const char* flecs_cpp_func_rchr(
|
||||
const char *func_name,
|
||||
ecs_size_t func_name_len,
|
||||
ecs_size_t func_back_len,
|
||||
char ch)
|
||||
{
|
||||
const char *r = strrchr(func_name, ch);
|
||||
if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) {
|
||||
return NULL;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static
|
||||
const char* flecs_cpp_func_max(
|
||||
const char *a,
|
||||
const char *b)
|
||||
{
|
||||
if (a > b) return a;
|
||||
return b;
|
||||
}
|
||||
|
||||
char* ecs_cpp_get_constant_name(
|
||||
char *constant_name,
|
||||
const char *func_name,
|
||||
size_t func_name_len,
|
||||
size_t func_back_len)
|
||||
{
|
||||
ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len);
|
||||
ecs_size_t fb_len = flecs_uto(ecs_size_t, func_back_len);
|
||||
const char *start = flecs_cpp_func_rchr(func_name, f_len, fb_len, ' ');
|
||||
start = flecs_cpp_func_max(start, flecs_cpp_func_rchr(
|
||||
func_name, f_len, fb_len, ')'));
|
||||
start = flecs_cpp_func_max(start, flecs_cpp_func_rchr(
|
||||
func_name, f_len, fb_len, ':'));
|
||||
start = flecs_cpp_func_max(start, flecs_cpp_func_rchr(
|
||||
func_name, f_len, fb_len, ','));
|
||||
ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name);
|
||||
start ++;
|
||||
|
||||
ecs_size_t len = flecs_uto(ecs_size_t,
|
||||
(f_len - (start - func_name) - fb_len));
|
||||
ecs_os_memcpy_n(constant_name, start, char, len);
|
||||
constant_name[len] = '\0';
|
||||
return constant_name;
|
||||
}
|
||||
|
||||
// Names returned from the name_helper class do not start with ::
|
||||
// but are relative to the root. If the namespace of the type
|
||||
// overlaps with the namespace of the current module, strip it from
|
||||
// the implicit identifier.
|
||||
// This allows for registration of component types that are not in the
|
||||
// module namespace to still be registered under the module scope.
|
||||
const char* ecs_cpp_trim_module(
|
||||
ecs_world_t *world,
|
||||
const char *type_name)
|
||||
{
|
||||
ecs_entity_t scope = ecs_get_scope(world);
|
||||
if (!scope) {
|
||||
return type_name;
|
||||
}
|
||||
|
||||
char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL);
|
||||
if (path) {
|
||||
const char *ptr = strrchr(type_name, ':');
|
||||
ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL);
|
||||
if (ptr) {
|
||||
ptr --;
|
||||
ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name);
|
||||
if (name_path_len <= ecs_os_strlen(path)) {
|
||||
if (!ecs_os_strncmp(type_name, path, name_path_len)) {
|
||||
type_name = &type_name[name_path_len + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ecs_os_free(path);
|
||||
|
||||
return type_name;
|
||||
}
|
||||
|
||||
// Validate registered component
|
||||
void ecs_cpp_component_validate(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t id,
|
||||
const char *name,
|
||||
const char *symbol,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
bool implicit_name)
|
||||
{
|
||||
/* If entity has a name check if it matches */
|
||||
if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) {
|
||||
if (!implicit_name && id >= EcsFirstUserComponentId) {
|
||||
#ifndef FLECS_NDEBUG
|
||||
char *path = ecs_get_path_w_sep(
|
||||
world, 0, id, "::", NULL);
|
||||
if (ecs_os_strcmp(path, name)) {
|
||||
ecs_abort(ECS_INCONSISTENT_NAME,
|
||||
"component '%s' already registered with name '%s'",
|
||||
name, path);
|
||||
}
|
||||
ecs_os_free(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (symbol) {
|
||||
const char *existing_symbol = ecs_get_symbol(world, id);
|
||||
if (existing_symbol) {
|
||||
if (ecs_os_strcmp(symbol, existing_symbol)) {
|
||||
ecs_abort(ECS_INCONSISTENT_NAME,
|
||||
"component '%s' with symbol '%s' already registered with symbol '%s'",
|
||||
name, symbol, existing_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Ensure that the entity id valid */
|
||||
if (!ecs_is_alive(world, id)) {
|
||||
ecs_ensure(world, id);
|
||||
}
|
||||
|
||||
/* Register name with entity, so that when the entity is created the
|
||||
* correct id will be resolved from the name. Only do this when the
|
||||
* entity is empty. */
|
||||
ecs_add_path_w_sep(world, id, 0, name, "::", "::");
|
||||
}
|
||||
|
||||
/* If a component was already registered with this id but with a
|
||||
* different size, the ecs_component_init function will fail. */
|
||||
|
||||
/* We need to explicitly call ecs_component_init here again. Even though
|
||||
* the component was already registered, it may have been registered
|
||||
* with a different world. This ensures that the component is registered
|
||||
* with the same id for the current world.
|
||||
* If the component was registered already, nothing will change. */
|
||||
ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){
|
||||
.entity = id,
|
||||
.type.size = flecs_uto(int32_t, size),
|
||||
.type.alignment = flecs_uto(int32_t, alignment)
|
||||
});
|
||||
(void)ent;
|
||||
ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_cpp_component_register(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t id,
|
||||
const char *name,
|
||||
const char *symbol,
|
||||
ecs_size_t size,
|
||||
ecs_size_t alignment,
|
||||
bool implicit_name,
|
||||
bool *existing_out)
|
||||
{
|
||||
(void)size;
|
||||
(void)alignment;
|
||||
|
||||
/* If the component is not yet registered, ensure no other component
|
||||
* or entity has been registered with this name. Ensure component is
|
||||
* looked up from root. */
|
||||
bool existing = false;
|
||||
ecs_entity_t prev_scope = ecs_set_scope(world, 0);
|
||||
ecs_entity_t ent;
|
||||
if (id) {
|
||||
ent = id;
|
||||
} else {
|
||||
ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false);
|
||||
existing = ent != 0 && ecs_has(world, ent, EcsComponent);
|
||||
}
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* If entity exists, compare symbol name to ensure that the component
|
||||
* we are trying to register under this name is the same */
|
||||
if (ent) {
|
||||
const EcsComponent *component = ecs_get(world, ent, EcsComponent);
|
||||
if (component != NULL) {
|
||||
const char *sym = ecs_get_symbol(world, ent);
|
||||
if (sym && ecs_os_strcmp(sym, symbol)) {
|
||||
/* Application is trying to register a type with an entity that
|
||||
* was already associated with another type. In most cases this
|
||||
* is an error, with the exception of a scenario where the
|
||||
* application is wrapping a C type with a C++ type.
|
||||
*
|
||||
* In this case the C++ type typically inherits from the C type,
|
||||
* and adds convenience methods to the derived class without
|
||||
* changing anything that would change the size or layout.
|
||||
*
|
||||
* To meet this condition, the new type must have the same size
|
||||
* and alignment as the existing type, and the name of the type
|
||||
* type must be equal to the registered name (not symbol).
|
||||
*
|
||||
* The latter ensures that it was the intent of the application
|
||||
* to alias the type, vs. accidentally registering an unrelated
|
||||
* type with the same size/alignment. */
|
||||
char *type_path = ecs_get_fullpath(world, ent);
|
||||
if (ecs_os_strcmp(type_path, symbol) ||
|
||||
component->size != size ||
|
||||
component->alignment != alignment)
|
||||
{
|
||||
ecs_err(
|
||||
"component with name '%s' is already registered for"\
|
||||
" type '%s' (trying to register for type '%s')",
|
||||
name, sym, symbol);
|
||||
ecs_abort(ECS_NAME_IN_USE, NULL);
|
||||
}
|
||||
ecs_os_free(type_path);
|
||||
} else if (!sym) {
|
||||
ecs_set_symbol(world, ent, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
/* If no entity is found, lookup symbol to check if the component was
|
||||
* registered under a different name. */
|
||||
} else if (!implicit_name) {
|
||||
ent = ecs_lookup_symbol(world, symbol, false, false);
|
||||
ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol);
|
||||
}
|
||||
|
||||
if (existing_out) {
|
||||
*existing_out = existing;
|
||||
}
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_cpp_component_register_explicit(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t s_id,
|
||||
ecs_entity_t id,
|
||||
const char *name,
|
||||
const char *type_name,
|
||||
const char *symbol,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
bool is_component,
|
||||
bool *existing_out)
|
||||
{
|
||||
char *existing_name = NULL;
|
||||
if (existing_out) *existing_out = false;
|
||||
|
||||
// If an explicit id is provided, it is possible that the symbol and
|
||||
// name differ from the actual type, as the application may alias
|
||||
// one type to another.
|
||||
if (!id) {
|
||||
if (!name) {
|
||||
// If no name was provided first check if a type with the provided
|
||||
// symbol was already registered.
|
||||
id = ecs_lookup_symbol(world, symbol, false, false);
|
||||
if (id) {
|
||||
existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::");
|
||||
name = existing_name;
|
||||
if (existing_out) *existing_out = true;
|
||||
} else {
|
||||
// If type is not yet known, derive from type name
|
||||
name = ecs_cpp_trim_module(world, type_name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If an explicit id is provided but it has no name, inherit
|
||||
// the name from the type.
|
||||
if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) {
|
||||
name = ecs_cpp_trim_module(world, type_name);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_entity_t entity;
|
||||
if (is_component || size != 0) {
|
||||
entity = ecs_entity(world, {
|
||||
.id = s_id,
|
||||
.name = name,
|
||||
.sep = "::",
|
||||
.root_sep = "::",
|
||||
.symbol = symbol,
|
||||
.use_low_id = true
|
||||
});
|
||||
ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL);
|
||||
|
||||
entity = ecs_component_init(world, &(ecs_component_desc_t){
|
||||
.entity = entity,
|
||||
.type.size = flecs_uto(int32_t, size),
|
||||
.type.alignment = flecs_uto(int32_t, alignment)
|
||||
});
|
||||
ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL);
|
||||
} else {
|
||||
entity = ecs_entity(world, {
|
||||
.id = s_id,
|
||||
.name = name,
|
||||
.sep = "::",
|
||||
.root_sep = "::",
|
||||
.symbol = symbol,
|
||||
.use_low_id = true
|
||||
});
|
||||
}
|
||||
|
||||
ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_os_free(existing_name);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
void ecs_cpp_enum_init(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t id)
|
||||
{
|
||||
(void)world;
|
||||
(void)id;
|
||||
#ifdef FLECS_META
|
||||
ecs_suspend_readonly_state_t readonly_state;
|
||||
world = flecs_suspend_readonly(world, &readonly_state);
|
||||
ecs_set(world, id, EcsEnum, {0});
|
||||
flecs_resume_readonly(world, &readonly_state);
|
||||
#endif
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_cpp_enum_constant_register(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
ecs_entity_t id,
|
||||
const char *name,
|
||||
int value)
|
||||
{
|
||||
ecs_suspend_readonly_state_t readonly_state;
|
||||
world = flecs_suspend_readonly(world, &readonly_state);
|
||||
|
||||
const char *parent_name = ecs_get_name(world, parent);
|
||||
ecs_size_t parent_name_len = ecs_os_strlen(parent_name);
|
||||
if (!ecs_os_strncmp(name, parent_name, parent_name_len)) {
|
||||
name += parent_name_len;
|
||||
if (name[0] == '_') {
|
||||
name ++;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_entity_t prev = ecs_set_scope(world, parent);
|
||||
id = ecs_entity(world, {
|
||||
.id = id,
|
||||
.name = name
|
||||
});
|
||||
ecs_assert(id != 0, ECS_INVALID_OPERATION, name);
|
||||
ecs_set_scope(world, prev);
|
||||
|
||||
#ifdef FLECS_DEBUG
|
||||
const EcsComponent *cptr = ecs_get(world, parent, EcsComponent);
|
||||
ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component");
|
||||
ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED,
|
||||
"enum component must have 32bit size");
|
||||
#endif
|
||||
|
||||
#ifdef FLECS_META
|
||||
ecs_set_id(world, id, ecs_pair(EcsConstant, ecs_id(ecs_i32_t)),
|
||||
sizeof(ecs_i32_t), &value);
|
||||
#endif
|
||||
|
||||
flecs_resume_readonly(world, &readonly_state);
|
||||
|
||||
ecs_trace("#[green]constant#[reset] %s.%s created with value %d",
|
||||
ecs_get_name(world, parent), name, value);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static int32_t flecs_reset_count = 0;
|
||||
|
||||
int32_t ecs_cpp_reset_count_get(void) {
|
||||
return flecs_reset_count;
|
||||
}
|
||||
|
||||
int32_t ecs_cpp_reset_count_inc(void) {
|
||||
return ++flecs_reset_count;
|
||||
}
|
||||
|
||||
#ifdef FLECS_META
|
||||
const ecs_member_t* ecs_cpp_last_member(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type)
|
||||
{
|
||||
const EcsStruct *st = ecs_get(world, type, EcsStruct);
|
||||
if (!st) {
|
||||
char *type_str = ecs_get_fullpath(world, type);
|
||||
ecs_err("entity '%s' is not a struct", type_str);
|
||||
ecs_os_free(type_str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t,
|
||||
ecs_vec_count(&st->members) - 1);
|
||||
ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
return m;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
1670
engine/libs/flecs/src/addons/http.c
Normal file
1670
engine/libs/flecs/src/addons/http.c
Normal file
File diff suppressed because it is too large
Load Diff
136
engine/libs/flecs/src/addons/journal.c
Normal file
136
engine/libs/flecs/src/addons/journal.c
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @file addons/journal.c
|
||||
* @brief Journal addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_JOURNAL
|
||||
|
||||
static
|
||||
char* flecs_journal_entitystr(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
char *path;
|
||||
const char *_path = ecs_get_symbol(world, entity);
|
||||
if (_path && !strchr(_path, '.')) {
|
||||
path = ecs_asprintf("#[blue]%s", _path);
|
||||
} else {
|
||||
uint32_t gen = entity >> 32;
|
||||
if (gen) {
|
||||
path = ecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen);
|
||||
} else {
|
||||
path = ecs_asprintf("#[normal]_%u", (uint32_t)entity);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
static
|
||||
char* flecs_journal_idstr(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t id)
|
||||
{
|
||||
if (ECS_IS_PAIR(id)) {
|
||||
char *first_path = flecs_journal_entitystr(world,
|
||||
ecs_pair_first(world, id));
|
||||
char *second_path = flecs_journal_entitystr(world,
|
||||
ecs_pair_second(world, id));
|
||||
char *result = ecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)",
|
||||
first_path, second_path);
|
||||
ecs_os_free(first_path);
|
||||
ecs_os_free(second_path);
|
||||
return result;
|
||||
} else if (!(id & ECS_ID_FLAGS_MASK)) {
|
||||
return flecs_journal_entitystr(world, id);
|
||||
} else {
|
||||
return ecs_id_str(world, id);
|
||||
}
|
||||
}
|
||||
|
||||
static int flecs_journal_sp = 0;
|
||||
|
||||
void flecs_journal_begin(
|
||||
ecs_world_t *world,
|
||||
ecs_journal_kind_t kind,
|
||||
ecs_entity_t entity,
|
||||
ecs_type_t *add,
|
||||
ecs_type_t *remove)
|
||||
{
|
||||
flecs_journal_sp ++;
|
||||
|
||||
if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
char *path = NULL;
|
||||
char *var_id = NULL;
|
||||
if (entity) {
|
||||
if (kind != EcsJournalDeleteWith && kind != EcsJournalRemoveAll) {
|
||||
path = ecs_get_fullpath(world, entity);
|
||||
var_id = flecs_journal_entitystr(world, entity);
|
||||
} else {
|
||||
path = ecs_id_str(world, entity);
|
||||
var_id = flecs_journal_idstr(world, entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == EcsJournalNew) {
|
||||
ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id);
|
||||
ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id);
|
||||
ecs_print(4, "#[green]ecs_entity_t %s;", var_id);
|
||||
ecs_print(4, "#[magenta]#endif");
|
||||
ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); "
|
||||
"#[grey] // %s = new()", var_id, path);
|
||||
}
|
||||
if (add) {
|
||||
for (int i = 0; i < add->count; i ++) {
|
||||
char *jidstr = flecs_journal_idstr(world, add->array[i]);
|
||||
char *idstr = ecs_id_str(world, add->array[i]);
|
||||
ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); "
|
||||
"#[grey] // add(%s, %s)", var_id, jidstr,
|
||||
path, idstr);
|
||||
ecs_os_free(idstr);
|
||||
ecs_os_free(jidstr);
|
||||
}
|
||||
}
|
||||
if (remove) {
|
||||
for (int i = 0; i < remove->count; i ++) {
|
||||
char *jidstr = flecs_journal_idstr(world, remove->array[i]);
|
||||
char *idstr = ecs_id_str(world, remove->array[i]);
|
||||
ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); "
|
||||
"#[grey] // remove(%s, %s)", var_id, jidstr,
|
||||
path, idstr);
|
||||
ecs_os_free(idstr);
|
||||
ecs_os_free(jidstr);
|
||||
}
|
||||
}
|
||||
if (kind == EcsJournalClear) {
|
||||
ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); "
|
||||
"#[grey] // clear(%s)", var_id, path);
|
||||
} else if (kind == EcsJournalDelete) {
|
||||
ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); "
|
||||
"#[grey] // delete(%s)", var_id, path);
|
||||
} else if (kind == EcsJournalDeleteWith) {
|
||||
ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); "
|
||||
"#[grey] // delete_with(%s)", var_id, path);
|
||||
} else if (kind == EcsJournalRemoveAll) {
|
||||
ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); "
|
||||
"#[grey] // remove_all(%s)", var_id, path);
|
||||
} else if (kind == EcsJournalTableEvents) {
|
||||
ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, "
|
||||
"EcsAperiodicEmptyTables);");
|
||||
}
|
||||
ecs_os_free(var_id);
|
||||
ecs_os_free(path);
|
||||
ecs_log_push();
|
||||
}
|
||||
|
||||
void flecs_journal_end(void) {
|
||||
flecs_journal_sp --;
|
||||
ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_log_pop();
|
||||
}
|
||||
|
||||
#endif
|
||||
1027
engine/libs/flecs/src/addons/json/deserialize.c
Normal file
1027
engine/libs/flecs/src/addons/json/deserialize.c
Normal file
File diff suppressed because it is too large
Load Diff
451
engine/libs/flecs/src/addons/json/json.c
Normal file
451
engine/libs/flecs/src/addons/json/json.c
Normal file
@@ -0,0 +1,451 @@
|
||||
/**
|
||||
* @file json/json.c
|
||||
* @brief JSON serializer utilities.
|
||||
*/
|
||||
|
||||
#include "json.h"
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef FLECS_JSON
|
||||
|
||||
static
|
||||
const char* flecs_json_token_str(
|
||||
ecs_json_token_t token_kind)
|
||||
{
|
||||
switch(token_kind) {
|
||||
case JsonObjectOpen: return "{";
|
||||
case JsonObjectClose: return "}";
|
||||
case JsonArrayOpen: return "[";
|
||||
case JsonArrayClose: return "]";
|
||||
case JsonColon: return ":";
|
||||
case JsonComma: return ",";
|
||||
case JsonNumber: return "number";
|
||||
case JsonLargeInt: return "large integer";
|
||||
case JsonLargeString:
|
||||
case JsonString: return "string";
|
||||
case JsonTrue: return "true";
|
||||
case JsonFalse: return "false";
|
||||
case JsonNull: return "null";
|
||||
case JsonInvalid: return "invalid";
|
||||
default:
|
||||
ecs_throw(ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
error:
|
||||
return "<<invalid token kind>>";
|
||||
}
|
||||
|
||||
const char* flecs_json_parse(
|
||||
const char *json,
|
||||
ecs_json_token_t *token_kind,
|
||||
char *token)
|
||||
{
|
||||
json = ecs_parse_ws_eol(json);
|
||||
|
||||
char ch = json[0];
|
||||
|
||||
if (ch == '{') {
|
||||
token_kind[0] = JsonObjectOpen;
|
||||
return json + 1;
|
||||
} else if (ch == '}') {
|
||||
token_kind[0] = JsonObjectClose;
|
||||
return json + 1;
|
||||
} else if (ch == '[') {
|
||||
token_kind[0] = JsonArrayOpen;
|
||||
return json + 1;
|
||||
} else if (ch == ']') {
|
||||
token_kind[0] = JsonArrayClose;
|
||||
return json + 1;
|
||||
} else if (ch == ':') {
|
||||
token_kind[0] = JsonColon;
|
||||
return json + 1;
|
||||
} else if (ch == ',') {
|
||||
token_kind[0] = JsonComma;
|
||||
return json + 1;
|
||||
} else if (ch == '"') {
|
||||
const char *start = json;
|
||||
char *token_ptr = token;
|
||||
json ++;
|
||||
for (; (ch = json[0]); ) {
|
||||
if (ch == '"') {
|
||||
json ++;
|
||||
token_ptr[0] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
if (token_ptr - token >= ECS_MAX_TOKEN_SIZE) {
|
||||
/* Token doesn't fit in buffer, signal to app to try again with
|
||||
* dynamic buffer. */
|
||||
token_kind[0] = JsonLargeString;
|
||||
return start;
|
||||
}
|
||||
|
||||
json = ecs_chrparse(json, token_ptr ++);
|
||||
}
|
||||
|
||||
if (!ch) {
|
||||
token_kind[0] = JsonInvalid;
|
||||
return NULL;
|
||||
} else {
|
||||
token_kind[0] = JsonString;
|
||||
return json;
|
||||
}
|
||||
} else if (isdigit(ch) || (ch == '-')) {
|
||||
token_kind[0] = JsonNumber;
|
||||
const char *result = ecs_parse_digit(json, token);
|
||||
|
||||
/* Cheap initial check if parsed token could represent large int */
|
||||
if (result - json > 15) {
|
||||
/* Less cheap secondary check to see if number is integer */
|
||||
if (!strchr(token, '.')) {
|
||||
token_kind[0] = JsonLargeInt;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} else if (isalpha(ch)) {
|
||||
if (!ecs_os_strncmp(json, "null", 4)) {
|
||||
token_kind[0] = JsonNull;
|
||||
json += 4;
|
||||
} else
|
||||
if (!ecs_os_strncmp(json, "true", 4)) {
|
||||
token_kind[0] = JsonTrue;
|
||||
json += 4;
|
||||
} else
|
||||
if (!ecs_os_strncmp(json, "false", 5)) {
|
||||
token_kind[0] = JsonFalse;
|
||||
json += 5;
|
||||
}
|
||||
|
||||
if (isalpha(json[0]) || isdigit(json[0])) {
|
||||
token_kind[0] = JsonInvalid;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return json;
|
||||
} else {
|
||||
token_kind[0] = JsonInvalid;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char* flecs_json_parse_large_string(
|
||||
const char *json,
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
if (json[0] != '"') {
|
||||
return NULL; /* can only parse strings */
|
||||
}
|
||||
|
||||
char ch, ch_out;
|
||||
json ++;
|
||||
for (; (ch = json[0]); ) {
|
||||
if (ch == '"') {
|
||||
json ++;
|
||||
break;
|
||||
}
|
||||
|
||||
json = ecs_chrparse(json, &ch_out);
|
||||
ecs_strbuf_appendch(buf, ch_out);
|
||||
}
|
||||
|
||||
if (!ch) {
|
||||
return NULL;
|
||||
} else {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
const char* flecs_json_expect(
|
||||
const char *json,
|
||||
ecs_json_token_t token_kind,
|
||||
char *token,
|
||||
const ecs_from_json_desc_t *desc)
|
||||
{
|
||||
ecs_json_token_t kind = 0;
|
||||
json = flecs_json_parse(json, &kind, token);
|
||||
if (kind == JsonInvalid) {
|
||||
ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid json");
|
||||
return NULL;
|
||||
} else if (kind != token_kind) {
|
||||
ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected %s",
|
||||
flecs_json_token_str(token_kind));
|
||||
return NULL;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
const char* flecs_json_expect_member(
|
||||
const char *json,
|
||||
char *token,
|
||||
const ecs_from_json_desc_t *desc)
|
||||
{
|
||||
json = flecs_json_expect(json, JsonString, token, desc);
|
||||
if (!json) {
|
||||
return NULL;
|
||||
}
|
||||
json = flecs_json_expect(json, JsonColon, token, desc);
|
||||
if (!json) {
|
||||
return NULL;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
const char* flecs_json_expect_member_name(
|
||||
const char *json,
|
||||
char *token,
|
||||
const char *member_name,
|
||||
const ecs_from_json_desc_t *desc)
|
||||
{
|
||||
json = flecs_json_expect_member(json, token, desc);
|
||||
if (!json) {
|
||||
return NULL;
|
||||
}
|
||||
if (ecs_os_strcmp(token, member_name)) {
|
||||
ecs_parser_error(desc->name, desc->expr, json - desc->expr,
|
||||
"expected member '%s'", member_name);
|
||||
return NULL;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
const char* flecs_json_skip_object(
|
||||
const char *json,
|
||||
char *token,
|
||||
const ecs_from_json_desc_t *desc)
|
||||
{
|
||||
ecs_json_token_t token_kind = 0;
|
||||
|
||||
while ((json = flecs_json_parse(json, &token_kind, token))) {
|
||||
if (token_kind == JsonObjectOpen) {
|
||||
json = flecs_json_skip_object(json, token, desc);
|
||||
} else if (token_kind == JsonArrayOpen) {
|
||||
json = flecs_json_skip_array(json, token, desc);
|
||||
} else if (token_kind == JsonObjectClose) {
|
||||
return json;
|
||||
} else if (token_kind == JsonArrayClose) {
|
||||
ecs_parser_error(desc->name, desc->expr, json - desc->expr,
|
||||
"expected }");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* flecs_json_skip_array(
|
||||
const char *json,
|
||||
char *token,
|
||||
const ecs_from_json_desc_t *desc)
|
||||
{
|
||||
ecs_json_token_t token_kind = 0;
|
||||
|
||||
while ((json = flecs_json_parse(json, &token_kind, token))) {
|
||||
if (token_kind == JsonObjectOpen) {
|
||||
json = flecs_json_skip_object(json, token, desc);
|
||||
} else if (token_kind == JsonArrayOpen) {
|
||||
json = flecs_json_skip_array(json, token, desc);
|
||||
} else if (token_kind == JsonObjectClose) {
|
||||
ecs_parser_error(desc->name, desc->expr, json - desc->expr,
|
||||
"expected ]");
|
||||
return NULL;
|
||||
} else if (token_kind == JsonArrayClose) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void flecs_json_next(
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_strbuf_list_next(buf);
|
||||
}
|
||||
|
||||
void flecs_json_number(
|
||||
ecs_strbuf_t *buf,
|
||||
double value)
|
||||
{
|
||||
ecs_strbuf_appendflt(buf, value, '"');
|
||||
}
|
||||
|
||||
void flecs_json_true(
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_strbuf_appendlit(buf, "true");
|
||||
}
|
||||
|
||||
void flecs_json_false(
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_strbuf_appendlit(buf, "false");
|
||||
}
|
||||
|
||||
void flecs_json_bool(
|
||||
ecs_strbuf_t *buf,
|
||||
bool value)
|
||||
{
|
||||
if (value) {
|
||||
flecs_json_true(buf);
|
||||
} else {
|
||||
flecs_json_false(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_json_array_push(
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_strbuf_list_push(buf, "[", ", ");
|
||||
}
|
||||
|
||||
void flecs_json_array_pop(
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_strbuf_list_pop(buf, "]");
|
||||
}
|
||||
|
||||
void flecs_json_object_push(
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_strbuf_list_push(buf, "{", ", ");
|
||||
}
|
||||
|
||||
void flecs_json_object_pop(
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_strbuf_list_pop(buf, "}");
|
||||
}
|
||||
|
||||
void flecs_json_string(
|
||||
ecs_strbuf_t *buf,
|
||||
const char *value)
|
||||
{
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_strbuf_appendstr(buf, value);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
}
|
||||
|
||||
void flecs_json_string_escape(
|
||||
ecs_strbuf_t *buf,
|
||||
const char *value)
|
||||
{
|
||||
ecs_size_t length = ecs_stresc(NULL, 0, '"', value);
|
||||
if (length == ecs_os_strlen(value)) {
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_strbuf_appendstrn(buf, value, length);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
} else {
|
||||
char *out = ecs_os_malloc(length + 3);
|
||||
ecs_stresc(out + 1, length, '"', value);
|
||||
out[0] = '"';
|
||||
out[length + 1] = '"';
|
||||
out[length + 2] = '\0';
|
||||
ecs_strbuf_appendstr_zerocpy(buf, out);
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_json_member(
|
||||
ecs_strbuf_t *buf,
|
||||
const char *name)
|
||||
{
|
||||
flecs_json_membern(buf, name, ecs_os_strlen(name));
|
||||
}
|
||||
|
||||
void flecs_json_membern(
|
||||
ecs_strbuf_t *buf,
|
||||
const char *name,
|
||||
int32_t name_len)
|
||||
{
|
||||
ecs_strbuf_list_appendch(buf, '"');
|
||||
ecs_strbuf_appendstrn(buf, name, name_len);
|
||||
ecs_strbuf_appendlit(buf, "\":");
|
||||
}
|
||||
|
||||
void flecs_json_path(
|
||||
ecs_strbuf_t *buf,
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t e)
|
||||
{
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
}
|
||||
|
||||
void flecs_json_label(
|
||||
ecs_strbuf_t *buf,
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t e)
|
||||
{
|
||||
const char *lbl = NULL;
|
||||
#ifdef FLECS_DOC
|
||||
lbl = ecs_doc_get_name(world, e);
|
||||
#else
|
||||
lbl = ecs_get_name(world, e);
|
||||
#endif
|
||||
|
||||
if (lbl) {
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_strbuf_appendstr(buf, lbl);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
} else {
|
||||
ecs_strbuf_appendch(buf, '0');
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_json_color(
|
||||
ecs_strbuf_t *buf,
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t e)
|
||||
{
|
||||
(void)world;
|
||||
(void)e;
|
||||
|
||||
const char *color = NULL;
|
||||
#ifdef FLECS_DOC
|
||||
color = ecs_doc_get_color(world, e);
|
||||
#endif
|
||||
|
||||
if (color) {
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_strbuf_appendstr(buf, color);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
} else {
|
||||
ecs_strbuf_appendch(buf, '0');
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_json_id(
|
||||
ecs_strbuf_t *buf,
|
||||
const ecs_world_t *world,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_strbuf_appendch(buf, '[');
|
||||
|
||||
if (ECS_IS_PAIR(id)) {
|
||||
ecs_entity_t first = ecs_pair_first(world, id);
|
||||
ecs_entity_t second = ecs_pair_second(world, id);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_strbuf_appendch(buf, ',');
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
} else {
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf);
|
||||
ecs_strbuf_appendch(buf, '"');
|
||||
}
|
||||
|
||||
ecs_strbuf_appendch(buf, ']');
|
||||
}
|
||||
|
||||
ecs_primitive_kind_t flecs_json_op_to_primitive_kind(
|
||||
ecs_meta_type_op_kind_t kind)
|
||||
{
|
||||
return kind - EcsOpPrimitive;
|
||||
}
|
||||
|
||||
#endif
|
||||
137
engine/libs/flecs/src/addons/json/json.h
Normal file
137
engine/libs/flecs/src/addons/json/json.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* @file json/json.h
|
||||
* @brief Internal functions for JSON addon.
|
||||
*/
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
#ifdef FLECS_JSON
|
||||
|
||||
/* Deserialize from JSON */
|
||||
typedef enum ecs_json_token_t {
|
||||
JsonObjectOpen,
|
||||
JsonObjectClose,
|
||||
JsonArrayOpen,
|
||||
JsonArrayClose,
|
||||
JsonColon,
|
||||
JsonComma,
|
||||
JsonNumber,
|
||||
JsonString,
|
||||
JsonTrue,
|
||||
JsonFalse,
|
||||
JsonNull,
|
||||
JsonLargeInt,
|
||||
JsonLargeString,
|
||||
JsonInvalid
|
||||
} ecs_json_token_t;
|
||||
|
||||
const char* flecs_json_parse(
|
||||
const char *json,
|
||||
ecs_json_token_t *token_kind,
|
||||
char *token);
|
||||
|
||||
const char* flecs_json_parse_large_string(
|
||||
const char *json,
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
const char* flecs_json_expect(
|
||||
const char *json,
|
||||
ecs_json_token_t token_kind,
|
||||
char *token,
|
||||
const ecs_from_json_desc_t *desc);
|
||||
|
||||
const char* flecs_json_expect_member(
|
||||
const char *json,
|
||||
char *token,
|
||||
const ecs_from_json_desc_t *desc);
|
||||
|
||||
const char* flecs_json_expect_member_name(
|
||||
const char *json,
|
||||
char *token,
|
||||
const char *member_name,
|
||||
const ecs_from_json_desc_t *desc);
|
||||
|
||||
const char* flecs_json_skip_object(
|
||||
const char *json,
|
||||
char *token,
|
||||
const ecs_from_json_desc_t *desc);
|
||||
|
||||
const char* flecs_json_skip_array(
|
||||
const char *json,
|
||||
char *token,
|
||||
const ecs_from_json_desc_t *desc);
|
||||
|
||||
/* Serialize to JSON */
|
||||
void flecs_json_next(
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
void flecs_json_number(
|
||||
ecs_strbuf_t *buf,
|
||||
double value);
|
||||
|
||||
void flecs_json_true(
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
void flecs_json_false(
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
void flecs_json_bool(
|
||||
ecs_strbuf_t *buf,
|
||||
bool value);
|
||||
|
||||
void flecs_json_array_push(
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
void flecs_json_array_pop(
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
void flecs_json_object_push(
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
void flecs_json_object_pop(
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
void flecs_json_string(
|
||||
ecs_strbuf_t *buf,
|
||||
const char *value);
|
||||
|
||||
void flecs_json_string_escape(
|
||||
ecs_strbuf_t *buf,
|
||||
const char *value);
|
||||
|
||||
void flecs_json_member(
|
||||
ecs_strbuf_t *buf,
|
||||
const char *name);
|
||||
|
||||
void flecs_json_membern(
|
||||
ecs_strbuf_t *buf,
|
||||
const char *name,
|
||||
int32_t name_len);
|
||||
|
||||
#define flecs_json_memberl(buf, name)\
|
||||
flecs_json_membern(buf, name, sizeof(name) - 1)
|
||||
|
||||
void flecs_json_path(
|
||||
ecs_strbuf_t *buf,
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t e);
|
||||
|
||||
void flecs_json_label(
|
||||
ecs_strbuf_t *buf,
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t e);
|
||||
|
||||
void flecs_json_color(
|
||||
ecs_strbuf_t *buf,
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t e);
|
||||
|
||||
void flecs_json_id(
|
||||
ecs_strbuf_t *buf,
|
||||
const ecs_world_t *world,
|
||||
ecs_id_t id);
|
||||
|
||||
ecs_primitive_kind_t flecs_json_op_to_primitive_kind(
|
||||
ecs_meta_type_op_kind_t kind);
|
||||
|
||||
#endif
|
||||
2310
engine/libs/flecs/src/addons/json/serialize.c
Normal file
2310
engine/libs/flecs/src/addons/json/serialize.c
Normal file
File diff suppressed because it is too large
Load Diff
429
engine/libs/flecs/src/addons/json/serialize_type_info.c
Normal file
429
engine/libs/flecs/src/addons/json/serialize_type_info.c
Normal file
@@ -0,0 +1,429 @@
|
||||
/**
|
||||
* @file json/serialize_type_info.c
|
||||
* @brief Serialize type (reflection) information to JSON.
|
||||
*/
|
||||
|
||||
#include "json.h"
|
||||
|
||||
#ifdef FLECS_JSON
|
||||
|
||||
static
|
||||
int json_typeinfo_ser_type(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
static
|
||||
int json_typeinfo_ser_primitive(
|
||||
ecs_primitive_kind_t kind,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
switch(kind) {
|
||||
case EcsBool:
|
||||
flecs_json_string(str, "bool");
|
||||
break;
|
||||
case EcsChar:
|
||||
case EcsString:
|
||||
flecs_json_string(str, "text");
|
||||
break;
|
||||
case EcsByte:
|
||||
flecs_json_string(str, "byte");
|
||||
break;
|
||||
case EcsU8:
|
||||
case EcsU16:
|
||||
case EcsU32:
|
||||
case EcsU64:
|
||||
case EcsI8:
|
||||
case EcsI16:
|
||||
case EcsI32:
|
||||
case EcsI64:
|
||||
case EcsIPtr:
|
||||
case EcsUPtr:
|
||||
flecs_json_string(str, "int");
|
||||
break;
|
||||
case EcsF32:
|
||||
case EcsF64:
|
||||
flecs_json_string(str, "float");
|
||||
break;
|
||||
case EcsEntity:
|
||||
flecs_json_string(str, "entity");
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
void json_typeinfo_ser_constants(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) {
|
||||
.id = ecs_pair(EcsChildOf, type)
|
||||
});
|
||||
|
||||
while (ecs_term_next(&it)) {
|
||||
int32_t i, count = it.count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
flecs_json_next(str);
|
||||
flecs_json_string(str, ecs_get_name(world, it.entities[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void json_typeinfo_ser_enum(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_strbuf_list_appendstr(str, "\"enum\"");
|
||||
json_typeinfo_ser_constants(world, type, str);
|
||||
}
|
||||
|
||||
static
|
||||
void json_typeinfo_ser_bitmask(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_strbuf_list_appendstr(str, "\"bitmask\"");
|
||||
json_typeinfo_ser_constants(world, type, str);
|
||||
}
|
||||
|
||||
static
|
||||
int json_typeinfo_ser_array(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t elem_type,
|
||||
int32_t count,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_strbuf_list_appendstr(str, "\"array\"");
|
||||
|
||||
flecs_json_next(str);
|
||||
if (json_typeinfo_ser_type(world, elem_type, str)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_strbuf_list_append(str, "%u", count);
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
int json_typeinfo_ser_array_type(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const EcsArray *arr = ecs_get(world, type, EcsArray);
|
||||
ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
int json_typeinfo_ser_vector(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const EcsVector *arr = ecs_get(world, type, EcsVector);
|
||||
ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_strbuf_list_appendstr(str, "\"vector\"");
|
||||
|
||||
flecs_json_next(str);
|
||||
if (json_typeinfo_ser_type(world, arr->type, str)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Serialize unit information */
|
||||
static
|
||||
int json_typeinfo_ser_unit(
|
||||
const ecs_world_t *world,
|
||||
ecs_strbuf_t *str,
|
||||
ecs_entity_t unit)
|
||||
{
|
||||
flecs_json_memberl(str, "unit");
|
||||
flecs_json_path(str, world, unit);
|
||||
|
||||
const EcsUnit *uptr = ecs_get(world, unit, EcsUnit);
|
||||
if (uptr) {
|
||||
if (uptr->symbol) {
|
||||
flecs_json_memberl(str, "symbol");
|
||||
flecs_json_string(str, uptr->symbol);
|
||||
}
|
||||
ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0);
|
||||
if (quantity) {
|
||||
flecs_json_memberl(str, "quantity");
|
||||
flecs_json_path(str, world, quantity);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
void json_typeinfo_ser_range(
|
||||
ecs_strbuf_t *str,
|
||||
const char *kind,
|
||||
ecs_member_value_range_t *range)
|
||||
{
|
||||
flecs_json_member(str, kind);
|
||||
flecs_json_array_push(str);
|
||||
flecs_json_next(str);
|
||||
flecs_json_number(str, range->min);
|
||||
flecs_json_next(str);
|
||||
flecs_json_number(str, range->max);
|
||||
flecs_json_array_pop(str);
|
||||
}
|
||||
|
||||
/* Forward serialization to the different type kinds */
|
||||
static
|
||||
int json_typeinfo_ser_type_op(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *op,
|
||||
ecs_strbuf_t *str,
|
||||
const EcsStruct *st)
|
||||
{
|
||||
if (op->kind == EcsOpOpaque) {
|
||||
const EcsOpaque *ct = ecs_get(world, op->type,
|
||||
EcsOpaque);
|
||||
ecs_assert(ct != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return json_typeinfo_ser_type(world, ct->as_type, str);
|
||||
}
|
||||
|
||||
flecs_json_array_push(str);
|
||||
|
||||
switch(op->kind) {
|
||||
case EcsOpPush:
|
||||
case EcsOpPop:
|
||||
/* Should not be parsed as single op */
|
||||
ecs_throw(ECS_INVALID_PARAMETER, NULL);
|
||||
break;
|
||||
case EcsOpEnum:
|
||||
json_typeinfo_ser_enum(world, op->type, str);
|
||||
break;
|
||||
case EcsOpBitmask:
|
||||
json_typeinfo_ser_bitmask(world, op->type, str);
|
||||
break;
|
||||
case EcsOpArray:
|
||||
json_typeinfo_ser_array_type(world, op->type, str);
|
||||
break;
|
||||
case EcsOpVector:
|
||||
json_typeinfo_ser_vector(world, op->type, str);
|
||||
break;
|
||||
case EcsOpOpaque:
|
||||
/* Can't happen, already handled above */
|
||||
ecs_throw(ECS_INTERNAL_ERROR, NULL);
|
||||
break;
|
||||
case EcsOpBool:
|
||||
case EcsOpChar:
|
||||
case EcsOpByte:
|
||||
case EcsOpU8:
|
||||
case EcsOpU16:
|
||||
case EcsOpU32:
|
||||
case EcsOpU64:
|
||||
case EcsOpI8:
|
||||
case EcsOpI16:
|
||||
case EcsOpI32:
|
||||
case EcsOpI64:
|
||||
case EcsOpF32:
|
||||
case EcsOpF64:
|
||||
case EcsOpUPtr:
|
||||
case EcsOpIPtr:
|
||||
case EcsOpEntity:
|
||||
case EcsOpString:
|
||||
if (json_typeinfo_ser_primitive(
|
||||
flecs_json_op_to_primitive_kind(op->kind), str))
|
||||
{
|
||||
ecs_throw(ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
break;
|
||||
case EcsOpScope:
|
||||
case EcsOpPrimitive:
|
||||
default:
|
||||
ecs_throw(ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (st) {
|
||||
ecs_member_t *m = ecs_vec_get_t(
|
||||
&st->members, ecs_member_t, op->member_index);
|
||||
ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
bool value_range = ECS_NEQ(m->range.min, m->range.max);
|
||||
bool error_range = ECS_NEQ(m->error_range.min, m->error_range.max);
|
||||
bool warning_range = ECS_NEQ(m->warning_range.min, m->warning_range.max);
|
||||
|
||||
ecs_entity_t unit = m->unit;
|
||||
if (unit || error_range || warning_range || value_range) {
|
||||
flecs_json_next(str);
|
||||
flecs_json_next(str);
|
||||
flecs_json_object_push(str);
|
||||
|
||||
if (unit) {
|
||||
json_typeinfo_ser_unit(world, str, unit);
|
||||
}
|
||||
if (value_range) {
|
||||
json_typeinfo_ser_range(str, "range", &m->range);
|
||||
}
|
||||
if (error_range) {
|
||||
json_typeinfo_ser_range(str, "error_range", &m->error_range);
|
||||
}
|
||||
if (warning_range) {
|
||||
json_typeinfo_ser_range(str, "warning_range", &m->warning_range);
|
||||
}
|
||||
|
||||
flecs_json_object_pop(str);
|
||||
}
|
||||
}
|
||||
|
||||
flecs_json_array_pop(str);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Iterate over a slice of the type ops array */
|
||||
static
|
||||
int json_typeinfo_ser_type_ops(
|
||||
const ecs_world_t *world,
|
||||
ecs_meta_type_op_t *ops,
|
||||
int32_t op_count,
|
||||
ecs_strbuf_t *str,
|
||||
const EcsStruct *st)
|
||||
{
|
||||
const EcsStruct *stack[64] = {st};
|
||||
int32_t sp = 1;
|
||||
|
||||
for (int i = 0; i < op_count; i ++) {
|
||||
ecs_meta_type_op_t *op = &ops[i];
|
||||
|
||||
if (op != ops) {
|
||||
if (op->name) {
|
||||
flecs_json_member(str, op->name);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t elem_count = op->count;
|
||||
if (elem_count > 1) {
|
||||
flecs_json_array_push(str);
|
||||
json_typeinfo_ser_array(world, op->type, op->count, str);
|
||||
flecs_json_array_pop(str);
|
||||
i += op->op_count - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(op->kind) {
|
||||
case EcsOpPush:
|
||||
flecs_json_object_push(str);
|
||||
ecs_assert(sp < 63, ECS_INVALID_OPERATION, "type nesting too deep");
|
||||
stack[sp ++] = ecs_get(world, op->type, EcsStruct);
|
||||
break;
|
||||
case EcsOpPop:
|
||||
flecs_json_object_pop(str);
|
||||
sp --;
|
||||
break;
|
||||
case EcsOpArray:
|
||||
case EcsOpVector:
|
||||
case EcsOpEnum:
|
||||
case EcsOpBitmask:
|
||||
case EcsOpBool:
|
||||
case EcsOpChar:
|
||||
case EcsOpByte:
|
||||
case EcsOpU8:
|
||||
case EcsOpU16:
|
||||
case EcsOpU32:
|
||||
case EcsOpU64:
|
||||
case EcsOpI8:
|
||||
case EcsOpI16:
|
||||
case EcsOpI32:
|
||||
case EcsOpI64:
|
||||
case EcsOpF32:
|
||||
case EcsOpF64:
|
||||
case EcsOpUPtr:
|
||||
case EcsOpIPtr:
|
||||
case EcsOpEntity:
|
||||
case EcsOpString:
|
||||
case EcsOpOpaque:
|
||||
if (json_typeinfo_ser_type_op(world, op, str, stack[sp - 1])) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EcsOpPrimitive:
|
||||
case EcsOpScope:
|
||||
default:
|
||||
ecs_throw(ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
int json_typeinfo_ser_type(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
const EcsComponent *comp = ecs_get(world, type, EcsComponent);
|
||||
if (!comp) {
|
||||
ecs_strbuf_appendch(buf, '0');
|
||||
return 0;
|
||||
}
|
||||
|
||||
const EcsMetaTypeSerialized *ser = ecs_get(
|
||||
world, type, EcsMetaTypeSerialized);
|
||||
if (!ser) {
|
||||
ecs_strbuf_appendch(buf, '0');
|
||||
return 0;
|
||||
}
|
||||
|
||||
const EcsStruct *st = ecs_get(world, type, EcsStruct);
|
||||
ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t);
|
||||
int32_t count = ecs_vec_count(&ser->ops);
|
||||
|
||||
return json_typeinfo_ser_type_ops(world, ops, count, buf, st);
|
||||
}
|
||||
|
||||
int ecs_type_info_to_json_buf(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
return json_typeinfo_ser_type(world, type, buf);
|
||||
}
|
||||
|
||||
char* ecs_type_info_to_json(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type)
|
||||
{
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
|
||||
if (ecs_type_info_to_json_buf(world, type, &str) != 0) {
|
||||
ecs_strbuf_reset(&str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ecs_strbuf_get(&str);
|
||||
}
|
||||
|
||||
#endif
|
||||
540
engine/libs/flecs/src/addons/log.c
Normal file
540
engine/libs/flecs/src/addons/log.c
Normal file
@@ -0,0 +1,540 @@
|
||||
/**
|
||||
* @file addons/log.c
|
||||
* @brief Log addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_LOG
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
void flecs_colorize_buf(
|
||||
char *msg,
|
||||
bool enable_colors,
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
char *ptr, ch, prev = '\0';
|
||||
bool isNum = false;
|
||||
char isStr = '\0';
|
||||
bool isVar = false;
|
||||
bool overrideColor = false;
|
||||
bool autoColor = true;
|
||||
bool dontAppend = false;
|
||||
|
||||
for (ptr = msg; (ch = *ptr); ptr++) {
|
||||
dontAppend = false;
|
||||
|
||||
if (!overrideColor) {
|
||||
if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL);
|
||||
isNum = false;
|
||||
}
|
||||
if (isStr && (isStr == ch) && prev != '\\') {
|
||||
isStr = '\0';
|
||||
} else if (((ch == '\'') || (ch == '"')) && !isStr &&
|
||||
!isalpha(prev) && (prev != '\\'))
|
||||
{
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN);
|
||||
isStr = ch;
|
||||
}
|
||||
|
||||
if ((isdigit(ch) || (ch == '%' && isdigit(prev)) ||
|
||||
(ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar &&
|
||||
!isalpha(prev) && !isdigit(prev) && (prev != '_') &&
|
||||
(prev != '.'))
|
||||
{
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN);
|
||||
isNum = true;
|
||||
}
|
||||
|
||||
if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL);
|
||||
isVar = false;
|
||||
}
|
||||
|
||||
if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN);
|
||||
isVar = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') {
|
||||
bool isColor = true;
|
||||
overrideColor = true;
|
||||
|
||||
/* Custom colors */
|
||||
if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) {
|
||||
autoColor = false;
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL);
|
||||
} else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) {
|
||||
overrideColor = false;
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL);
|
||||
} else {
|
||||
isColor = false;
|
||||
overrideColor = false;
|
||||
}
|
||||
|
||||
if (isColor) {
|
||||
ptr += 2;
|
||||
while ((ch = *ptr) != ']') ptr ++;
|
||||
dontAppend = true;
|
||||
}
|
||||
if (!autoColor) {
|
||||
overrideColor = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == '\n') {
|
||||
if (isNum || isStr || isVar || overrideColor) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL);
|
||||
overrideColor = false;
|
||||
isNum = false;
|
||||
isStr = false;
|
||||
isVar = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dontAppend) {
|
||||
ecs_strbuf_appendstrn(buf, ptr, 1);
|
||||
}
|
||||
|
||||
if (!overrideColor) {
|
||||
if (((ch == '\'') || (ch == '"')) && !isStr) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
prev = ch;
|
||||
}
|
||||
|
||||
if (isNum || isStr || isVar || overrideColor) {
|
||||
if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_printv_(
|
||||
int level,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
(void)level;
|
||||
(void)line;
|
||||
|
||||
ecs_strbuf_t msg_buf = ECS_STRBUF_INIT;
|
||||
|
||||
/* Apply color. Even if we don't want color, we still need to call the
|
||||
* colorize function to get rid of the color tags (e.g. #[green]) */
|
||||
char *msg_nocolor = ecs_vasprintf(fmt, args);
|
||||
flecs_colorize_buf(msg_nocolor,
|
||||
ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf);
|
||||
ecs_os_free(msg_nocolor);
|
||||
|
||||
char *msg = ecs_strbuf_get(&msg_buf);
|
||||
|
||||
if (msg) {
|
||||
ecs_os_api.log_(level, file, line, msg);
|
||||
ecs_os_free(msg);
|
||||
} else {
|
||||
ecs_os_api.log_(level, file, line, "");
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_print_(
|
||||
int level,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ecs_printv_(level, file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void ecs_logv_(
|
||||
int level,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
if (level > ecs_os_api.log_level_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_printv_(level, file, line, fmt, args);
|
||||
}
|
||||
|
||||
void ecs_log_(
|
||||
int level,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
if (level > ecs_os_api.log_level_) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ecs_printv_(level, file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
||||
void ecs_log_push_(
|
||||
int32_t level)
|
||||
{
|
||||
if (level <= ecs_os_api.log_level_) {
|
||||
ecs_os_api.log_indent_ ++;
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_log_pop_(
|
||||
int32_t level)
|
||||
{
|
||||
if (level <= ecs_os_api.log_level_) {
|
||||
ecs_os_api.log_indent_ --;
|
||||
ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_parser_errorv_(
|
||||
const char *name,
|
||||
const char *expr,
|
||||
int64_t column_arg,
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
if (column_arg > 65536) {
|
||||
/* Limit column size, which prevents the code from throwing up when the
|
||||
* function is called with (expr - ptr), and expr is NULL. */
|
||||
column_arg = 0;
|
||||
}
|
||||
int32_t column = flecs_itoi32(column_arg);
|
||||
|
||||
if (ecs_os_api.log_level_ >= -2) {
|
||||
ecs_strbuf_t msg_buf = ECS_STRBUF_INIT;
|
||||
|
||||
ecs_strbuf_vappend(&msg_buf, fmt, args);
|
||||
|
||||
if (expr) {
|
||||
ecs_strbuf_appendch(&msg_buf, '\n');
|
||||
|
||||
/* Find start of line by taking column and looking for the
|
||||
* last occurring newline */
|
||||
if (column != -1) {
|
||||
const char *ptr = &expr[column];
|
||||
while (ptr[0] != '\n' && ptr > expr) {
|
||||
ptr --;
|
||||
}
|
||||
|
||||
if (ptr == expr) {
|
||||
/* ptr is already at start of line */
|
||||
} else {
|
||||
column -= (int32_t)(ptr - expr + 1);
|
||||
expr = ptr + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Strip newlines from current statement, if any */
|
||||
char *newline_ptr = strchr(expr, '\n');
|
||||
if (newline_ptr) {
|
||||
/* Strip newline from expr */
|
||||
ecs_strbuf_appendstrn(&msg_buf, expr,
|
||||
(int32_t)(newline_ptr - expr));
|
||||
} else {
|
||||
ecs_strbuf_appendstr(&msg_buf, expr);
|
||||
}
|
||||
|
||||
ecs_strbuf_appendch(&msg_buf, '\n');
|
||||
|
||||
if (column != -1) {
|
||||
int32_t c;
|
||||
for (c = 0; c < column; c ++) {
|
||||
ecs_strbuf_appendch(&msg_buf, ' ');
|
||||
}
|
||||
ecs_strbuf_appendch(&msg_buf, '^');
|
||||
}
|
||||
}
|
||||
|
||||
char *msg = ecs_strbuf_get(&msg_buf);
|
||||
ecs_os_err(name, 0, msg);
|
||||
ecs_os_free(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_parser_error_(
|
||||
const char *name,
|
||||
const char *expr,
|
||||
int64_t column,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
if (ecs_os_api.log_level_ >= -2) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ecs_parser_errorv_(name, expr, column, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_abort_(
|
||||
int32_t err,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
if (fmt) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
char *msg = ecs_vasprintf(fmt, args);
|
||||
va_end(args);
|
||||
ecs_fatal_(file, line, "%s (%s)", msg, ecs_strerror(err));
|
||||
ecs_os_free(msg);
|
||||
} else {
|
||||
ecs_fatal_(file, line, "%s", ecs_strerror(err));
|
||||
}
|
||||
ecs_os_api.log_last_error_ = err;
|
||||
}
|
||||
|
||||
void ecs_assert_log_(
|
||||
int32_t err,
|
||||
const char *cond_str,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
if (fmt) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
char *msg = ecs_vasprintf(fmt, args);
|
||||
va_end(args);
|
||||
ecs_fatal_(file, line, "assert: %s %s (%s)",
|
||||
cond_str, msg, ecs_strerror(err));
|
||||
ecs_os_free(msg);
|
||||
} else {
|
||||
ecs_fatal_(file, line, "assert: %s %s",
|
||||
cond_str, ecs_strerror(err));
|
||||
}
|
||||
ecs_os_api.log_last_error_ = err;
|
||||
}
|
||||
|
||||
void ecs_deprecated_(
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *msg)
|
||||
{
|
||||
ecs_err_(file, line, "%s", msg);
|
||||
}
|
||||
|
||||
bool ecs_should_log(int32_t level) {
|
||||
# if !defined(FLECS_LOG_3)
|
||||
if (level == 3) {
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
# if !defined(FLECS_LOG_2)
|
||||
if (level == 2) {
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
# if !defined(FLECS_LOG_1)
|
||||
if (level == 1) {
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
|
||||
return level <= ecs_os_api.log_level_;
|
||||
}
|
||||
|
||||
#define ECS_ERR_STR(code) case code: return &(#code[4])
|
||||
|
||||
const char* ecs_strerror(
|
||||
int32_t error_code)
|
||||
{
|
||||
switch (error_code) {
|
||||
ECS_ERR_STR(ECS_INVALID_PARAMETER);
|
||||
ECS_ERR_STR(ECS_NOT_A_COMPONENT);
|
||||
ECS_ERR_STR(ECS_INTERNAL_ERROR);
|
||||
ECS_ERR_STR(ECS_ALREADY_DEFINED);
|
||||
ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE);
|
||||
ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT);
|
||||
ECS_ERR_STR(ECS_NAME_IN_USE);
|
||||
ECS_ERR_STR(ECS_OUT_OF_MEMORY);
|
||||
ECS_ERR_STR(ECS_DOUBLE_FREE);
|
||||
ECS_ERR_STR(ECS_OPERATION_FAILED);
|
||||
ECS_ERR_STR(ECS_INVALID_CONVERSION);
|
||||
ECS_ERR_STR(ECS_MODULE_UNDEFINED);
|
||||
ECS_ERR_STR(ECS_MISSING_SYMBOL);
|
||||
ECS_ERR_STR(ECS_ALREADY_IN_USE);
|
||||
ECS_ERR_STR(ECS_CYCLE_DETECTED);
|
||||
ECS_ERR_STR(ECS_LEAK_DETECTED);
|
||||
ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE);
|
||||
ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED);
|
||||
ECS_ERR_STR(ECS_COLUMN_IS_SHARED);
|
||||
ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH);
|
||||
ECS_ERR_STR(ECS_INVALID_WHILE_READONLY);
|
||||
ECS_ERR_STR(ECS_INVALID_FROM_WORKER);
|
||||
ECS_ERR_STR(ECS_OUT_OF_RANGE);
|
||||
ECS_ERR_STR(ECS_MISSING_OS_API);
|
||||
ECS_ERR_STR(ECS_UNSUPPORTED);
|
||||
ECS_ERR_STR(ECS_ACCESS_VIOLATION);
|
||||
ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED);
|
||||
ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID);
|
||||
ECS_ERR_STR(ECS_INCONSISTENT_NAME);
|
||||
ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION);
|
||||
ECS_ERR_STR(ECS_INVALID_OPERATION);
|
||||
ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED);
|
||||
ECS_ERR_STR(ECS_LOCKED_STORAGE);
|
||||
ECS_ERR_STR(ECS_ID_IN_USE);
|
||||
}
|
||||
|
||||
return "unknown error code";
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* Empty bodies for when logging is disabled */
|
||||
|
||||
void ecs_log_(
|
||||
int32_t level,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
(void)level;
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
void ecs_parser_error_(
|
||||
const char *name,
|
||||
const char *expr,
|
||||
int64_t column,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
(void)name;
|
||||
(void)expr;
|
||||
(void)column;
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
void ecs_parser_errorv_(
|
||||
const char *name,
|
||||
const char *expr,
|
||||
int64_t column,
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
(void)name;
|
||||
(void)expr;
|
||||
(void)column;
|
||||
(void)fmt;
|
||||
(void)args;
|
||||
}
|
||||
|
||||
void ecs_abort_(
|
||||
int32_t error_code,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
(void)error_code;
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
void ecs_assert_log_(
|
||||
int32_t error_code,
|
||||
const char *condition_str,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
(void)error_code;
|
||||
(void)condition_str;
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int ecs_log_get_level(void) {
|
||||
return ecs_os_api.log_level_;
|
||||
}
|
||||
|
||||
int ecs_log_set_level(
|
||||
int level)
|
||||
{
|
||||
int prev = level;
|
||||
ecs_os_api.log_level_ = level;
|
||||
return prev;
|
||||
}
|
||||
|
||||
bool ecs_log_enable_colors(
|
||||
bool enabled)
|
||||
{
|
||||
bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors;
|
||||
ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled);
|
||||
return prev;
|
||||
}
|
||||
|
||||
bool ecs_log_enable_timestamp(
|
||||
bool enabled)
|
||||
{
|
||||
bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp;
|
||||
ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled);
|
||||
return prev;
|
||||
}
|
||||
|
||||
bool ecs_log_enable_timedelta(
|
||||
bool enabled)
|
||||
{
|
||||
bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta;
|
||||
ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled);
|
||||
return prev;
|
||||
}
|
||||
|
||||
int ecs_log_last_error(void)
|
||||
{
|
||||
int result = ecs_os_api.log_last_error_;
|
||||
ecs_os_api.log_last_error_ = 0;
|
||||
return result;
|
||||
}
|
||||
474
engine/libs/flecs/src/addons/meta/api.c
Normal file
474
engine/libs/flecs/src/addons/meta/api.c
Normal file
@@ -0,0 +1,474 @@
|
||||
/**
|
||||
* @file meta/api.c
|
||||
* @brief API for creating entities with reflection data.
|
||||
*/
|
||||
|
||||
#include "meta.h"
|
||||
|
||||
#ifdef FLECS_META
|
||||
|
||||
static
|
||||
bool flecs_type_is_number(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type)
|
||||
{
|
||||
const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive);
|
||||
if (!p) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(p->kind) {
|
||||
case EcsChar:
|
||||
case EcsU8:
|
||||
case EcsU16:
|
||||
case EcsU32:
|
||||
case EcsU64:
|
||||
case EcsI8:
|
||||
case EcsI16:
|
||||
case EcsI32:
|
||||
case EcsI64:
|
||||
case EcsF32:
|
||||
case EcsF64:
|
||||
return true;
|
||||
|
||||
case EcsBool:
|
||||
case EcsByte:
|
||||
case EcsUPtr:
|
||||
case EcsIPtr:
|
||||
case EcsString:
|
||||
case EcsEntity:
|
||||
return false;
|
||||
default:
|
||||
ecs_abort(ECS_INVALID_PARAMETER, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_primitive_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_primitive_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_set(world, t, EcsPrimitive, { desc->kind });
|
||||
|
||||
flecs_resume_readonly(world, &rs);
|
||||
return t;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_enum_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_enum_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_add(world, t, EcsEnum);
|
||||
|
||||
ecs_entity_t old_scope = ecs_set_scope(world, t);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) {
|
||||
const ecs_enum_constant_t *m_desc = &desc->constants[i];
|
||||
if (!m_desc->name) {
|
||||
break;
|
||||
}
|
||||
|
||||
ecs_entity_t c = ecs_entity(world, {
|
||||
.name = m_desc->name
|
||||
});
|
||||
|
||||
if (!m_desc->value) {
|
||||
ecs_add_id(world, c, EcsConstant);
|
||||
} else {
|
||||
ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t,
|
||||
{m_desc->value});
|
||||
}
|
||||
}
|
||||
|
||||
ecs_set_scope(world, old_scope);
|
||||
flecs_resume_readonly(world, &rs);
|
||||
|
||||
if (i == 0) {
|
||||
ecs_err("enum '%s' has no constants", ecs_get_name(world, t));
|
||||
ecs_delete(world, t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_bitmask_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_bitmask_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_add(world, t, EcsBitmask);
|
||||
|
||||
ecs_entity_t old_scope = ecs_set_scope(world, t);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) {
|
||||
const ecs_bitmask_constant_t *m_desc = &desc->constants[i];
|
||||
if (!m_desc->name) {
|
||||
break;
|
||||
}
|
||||
|
||||
ecs_entity_t c = ecs_entity(world, {
|
||||
.name = m_desc->name
|
||||
});
|
||||
|
||||
if (!m_desc->value) {
|
||||
ecs_add_id(world, c, EcsConstant);
|
||||
} else {
|
||||
ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t,
|
||||
{m_desc->value});
|
||||
}
|
||||
}
|
||||
|
||||
ecs_set_scope(world, old_scope);
|
||||
flecs_resume_readonly(world, &rs);
|
||||
|
||||
if (i == 0) {
|
||||
ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t));
|
||||
ecs_delete(world, t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_array_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_array_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_set(world, t, EcsArray, {
|
||||
.type = desc->type,
|
||||
.count = desc->count
|
||||
});
|
||||
|
||||
flecs_resume_readonly(world, &rs);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_vector_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_vector_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_set(world, t, EcsVector, {
|
||||
.type = desc->type
|
||||
});
|
||||
|
||||
flecs_resume_readonly(world, &rs);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_member_range_overlaps(
|
||||
const ecs_member_value_range_t *range,
|
||||
const ecs_member_value_range_t *with)
|
||||
{
|
||||
if (ECS_EQ(with->min, with->max)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ECS_EQ(range->min, range->max)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (range->min < with->min ||
|
||||
range->max > with->max)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_struct_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_struct_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_entity_t old_scope = ecs_set_scope(world, t);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) {
|
||||
const ecs_member_t *m_desc = &desc->members[i];
|
||||
if (!m_desc->type) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!m_desc->name) {
|
||||
ecs_err("member %d of struct '%s' does not have a name", i,
|
||||
ecs_get_name(world, t));
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_entity_t m = ecs_entity(world, {
|
||||
.name = m_desc->name
|
||||
});
|
||||
|
||||
ecs_set(world, m, EcsMember, {
|
||||
.type = m_desc->type,
|
||||
.count = m_desc->count,
|
||||
.offset = m_desc->offset,
|
||||
.unit = m_desc->unit
|
||||
});
|
||||
|
||||
EcsMemberRanges *ranges = NULL;
|
||||
const ecs_member_value_range_t *range = &m_desc->range;
|
||||
const ecs_member_value_range_t *error = &m_desc->error_range;
|
||||
const ecs_member_value_range_t *warning = &m_desc->warning_range;
|
||||
if (ECS_NEQ(range->min, range->max)) {
|
||||
ranges = ecs_get_mut(world, m, EcsMemberRanges);
|
||||
if (range->min > range->max) {
|
||||
char *member_name = ecs_get_fullpath(world, m);
|
||||
ecs_err("member '%s' has an invalid value range [%d..%d]",
|
||||
member_name, range->min, range->max);
|
||||
ecs_os_free(member_name);
|
||||
goto error;
|
||||
}
|
||||
ranges->value.min = range->min;
|
||||
ranges->value.max = range->max;
|
||||
}
|
||||
if (ECS_NEQ(error->min, error->max)) {
|
||||
if (error->min > error->max) {
|
||||
char *member_name = ecs_get_fullpath(world, m);
|
||||
ecs_err("member '%s' has an invalid error range [%d..%d]",
|
||||
member_name, error->min, error->max);
|
||||
ecs_os_free(member_name);
|
||||
goto error;
|
||||
}
|
||||
if (flecs_member_range_overlaps(error, range)) {
|
||||
char *member_name = ecs_get_fullpath(world, m);
|
||||
ecs_err("error range of member '%s' overlaps with value range",
|
||||
member_name);
|
||||
ecs_os_free(member_name);
|
||||
goto error;
|
||||
}
|
||||
if (!ranges) {
|
||||
ranges = ecs_get_mut(world, m, EcsMemberRanges);
|
||||
}
|
||||
ranges->error.min = error->min;
|
||||
ranges->error.max = error->max;
|
||||
}
|
||||
|
||||
if (ECS_NEQ(warning->min, warning->max)) {
|
||||
if (warning->min > warning->max) {
|
||||
char *member_name = ecs_get_fullpath(world, m);
|
||||
ecs_err("member '%s' has an invalid warning range [%d..%d]",
|
||||
member_name, warning->min, warning->max);
|
||||
ecs_os_free(member_name);
|
||||
goto error;
|
||||
}
|
||||
if (flecs_member_range_overlaps(warning, range)) {
|
||||
char *member_name = ecs_get_fullpath(world, m);
|
||||
ecs_err("warning range of member '%s' overlaps with value "
|
||||
"range", member_name);
|
||||
ecs_os_free(member_name);
|
||||
goto error;
|
||||
}
|
||||
if (flecs_member_range_overlaps(warning, error)) {
|
||||
char *member_name = ecs_get_fullpath(world, m);
|
||||
ecs_err("warning range of member '%s' overlaps with error "
|
||||
"range", member_name);
|
||||
ecs_os_free(member_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ranges) {
|
||||
ranges = ecs_get_mut(world, m, EcsMemberRanges);
|
||||
}
|
||||
ranges->warning.min = warning->min;
|
||||
ranges->warning.max = warning->max;
|
||||
}
|
||||
|
||||
if (ranges && !flecs_type_is_number(world, m_desc->type)) {
|
||||
char *member_name = ecs_get_fullpath(world, m);
|
||||
ecs_err("member '%s' has an value/error/warning range, but is not a "
|
||||
"number", member_name);
|
||||
ecs_os_free(member_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ranges) {
|
||||
ecs_modified(world, m, EcsMemberRanges);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_set_scope(world, old_scope);
|
||||
flecs_resume_readonly(world, &rs);
|
||||
|
||||
if (i == 0) {
|
||||
ecs_err("struct '%s' has no members", ecs_get_name(world, t));
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ecs_has(world, t, EcsStruct)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return t;
|
||||
error:
|
||||
flecs_resume_readonly(world, &rs);
|
||||
if (t) {
|
||||
ecs_delete(world, t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_opaque_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_opaque_desc_t *desc)
|
||||
{
|
||||
ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_set_ptr(world, t, EcsOpaque, &desc->type);
|
||||
|
||||
flecs_resume_readonly(world, &rs);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_unit_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_unit_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_entity_t quantity = desc->quantity;
|
||||
if (quantity) {
|
||||
if (!ecs_has_id(world, quantity, EcsQuantity)) {
|
||||
ecs_err("entity '%s' for unit '%s' is not a quantity",
|
||||
ecs_get_name(world, quantity), ecs_get_name(world, t));
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_add_pair(world, t, EcsQuantity, desc->quantity);
|
||||
} else {
|
||||
ecs_remove_pair(world, t, EcsQuantity, EcsWildcard);
|
||||
}
|
||||
|
||||
EcsUnit *value = ecs_get_mut(world, t, EcsUnit);
|
||||
value->base = desc->base;
|
||||
value->over = desc->over;
|
||||
value->translation = desc->translation;
|
||||
value->prefix = desc->prefix;
|
||||
ecs_os_strset(&value->symbol, desc->symbol);
|
||||
|
||||
if (!flecs_unit_validate(world, t, value)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_modified(world, t, EcsUnit);
|
||||
|
||||
flecs_resume_readonly(world, &rs);
|
||||
return t;
|
||||
error:
|
||||
if (t) {
|
||||
ecs_delete(world, t);
|
||||
}
|
||||
flecs_resume_readonly(world, &rs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_unit_prefix_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_unit_prefix_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = desc->entity;
|
||||
if (!t) {
|
||||
t = ecs_new_low_id(world);
|
||||
}
|
||||
|
||||
ecs_set(world, t, EcsUnitPrefix, {
|
||||
.symbol = ECS_CONST_CAST(char*, desc->symbol),
|
||||
.translation = desc->translation
|
||||
});
|
||||
|
||||
flecs_resume_readonly(world, &rs);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_quantity_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_entity_desc_t *desc)
|
||||
{
|
||||
ecs_suspend_readonly_state_t rs;
|
||||
world = flecs_suspend_readonly(world, &rs);
|
||||
|
||||
ecs_entity_t t = ecs_entity_init(world, desc);
|
||||
if (!t) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_add_id(world, t, EcsQuantity);
|
||||
|
||||
flecs_resume_readonly(world, &rs);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
#endif
|
||||
1895
engine/libs/flecs/src/addons/meta/cursor.c
Normal file
1895
engine/libs/flecs/src/addons/meta/cursor.c
Normal file
File diff suppressed because it is too large
Load Diff
1482
engine/libs/flecs/src/addons/meta/meta.c
Normal file
1482
engine/libs/flecs/src/addons/meta/meta.c
Normal file
File diff suppressed because it is too large
Load Diff
29
engine/libs/flecs/src/addons/meta/meta.h
Normal file
29
engine/libs/flecs/src/addons/meta/meta.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @file meta/meta.h
|
||||
* @brief Private functions for meta addon.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_META_PRIVATE_H
|
||||
#define FLECS_META_PRIVATE_H
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
#ifdef FLECS_META
|
||||
|
||||
void ecs_meta_type_serialized_init(
|
||||
ecs_iter_t *it);
|
||||
|
||||
void ecs_meta_dtor_serialized(
|
||||
EcsMetaTypeSerialized *ptr);
|
||||
|
||||
ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(
|
||||
ecs_primitive_kind_t kind);
|
||||
|
||||
bool flecs_unit_validate(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t t,
|
||||
EcsUnit *data);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
295
engine/libs/flecs/src/addons/meta/serialized.c
Normal file
295
engine/libs/flecs/src/addons/meta/serialized.c
Normal file
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* @file meta/serialized.c
|
||||
* @brief Serialize type into flat operations array to speed up deserialization.
|
||||
*/
|
||||
|
||||
#include "meta.h"
|
||||
|
||||
#ifdef FLECS_META
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_type(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops);
|
||||
|
||||
ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) {
|
||||
return EcsOpPrimitive + kind;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_size_t flecs_meta_type_size(ecs_world_t *world, ecs_entity_t type) {
|
||||
const EcsComponent *comp = ecs_get(world, type, EcsComponent);
|
||||
ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return comp->size;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t kind) {
|
||||
ecs_meta_type_op_t *op = ecs_vec_append_t(NULL, ops, ecs_meta_type_op_t);
|
||||
op->kind = kind;
|
||||
op->offset = 0;
|
||||
op->count = 1;
|
||||
op->op_count = 1;
|
||||
op->size = 0;
|
||||
op->name = NULL;
|
||||
op->members = NULL;
|
||||
op->type = 0;
|
||||
op->member_index = 0;
|
||||
return op;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_meta_type_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) {
|
||||
ecs_meta_type_op_t* op = ecs_vec_get_t(ops, ecs_meta_type_op_t, index);
|
||||
ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return op;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_primitive(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive);
|
||||
if (!ptr) {
|
||||
char *name = ecs_get_fullpath(world, type);
|
||||
ecs_err("entity '%s' is not a primitive type", name);
|
||||
ecs_os_free(name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, flecs_meta_primitive_to_op_kind(ptr->kind));
|
||||
op->offset = offset;
|
||||
op->type = type;
|
||||
op->size = flecs_meta_type_size(world, type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_enum(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
(void)world;
|
||||
|
||||
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum);
|
||||
op->offset = offset;
|
||||
op->type = type;
|
||||
op->size = ECS_SIZEOF(ecs_i32_t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_bitmask(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
(void)world;
|
||||
|
||||
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask);
|
||||
op->offset = offset;
|
||||
op->type = type;
|
||||
op->size = ECS_SIZEOF(ecs_u32_t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_array(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
(void)world;
|
||||
|
||||
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpArray);
|
||||
op->offset = offset;
|
||||
op->type = type;
|
||||
op->size = flecs_meta_type_size(world, type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_array_component(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
const EcsArray *ptr = ecs_get(world, type, EcsArray);
|
||||
if (!ptr) {
|
||||
return -1; /* Should never happen, will trigger internal error */
|
||||
}
|
||||
|
||||
flecs_meta_serialize_type(world, ptr->type, 0, ops);
|
||||
|
||||
ecs_meta_type_op_t *first = ecs_vec_first(ops);
|
||||
first->count = ptr->count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_vector(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
(void)world;
|
||||
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpVector);
|
||||
op->offset = offset;
|
||||
op->type = type;
|
||||
op->size = flecs_meta_type_size(world, type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_custom_type(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
(void)world;
|
||||
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpOpaque);
|
||||
op->offset = offset;
|
||||
op->type = type;
|
||||
op->size = flecs_meta_type_size(world, type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_struct(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
const EcsStruct *ptr = ecs_get(world, type, EcsStruct);
|
||||
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
int32_t cur, first = ecs_vec_count(ops);
|
||||
ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpPush);
|
||||
op->offset = offset;
|
||||
op->type = type;
|
||||
op->size = flecs_meta_type_size(world, type);
|
||||
|
||||
ecs_member_t *members = ecs_vec_first(&ptr->members);
|
||||
int32_t i, count = ecs_vec_count(&ptr->members);
|
||||
|
||||
ecs_hashmap_t *member_index = NULL;
|
||||
if (count) {
|
||||
op->members = member_index = flecs_name_index_new(
|
||||
world, &world->allocator);
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_member_t *member = &members[i];
|
||||
|
||||
cur = ecs_vec_count(ops);
|
||||
flecs_meta_serialize_type(world,
|
||||
member->type, offset + member->offset, ops);
|
||||
|
||||
op = flecs_meta_ops_get(ops, cur);
|
||||
if (!op->type) {
|
||||
op->type = member->type;
|
||||
}
|
||||
|
||||
if (op->count <= 1) {
|
||||
op->count = member->count;
|
||||
}
|
||||
|
||||
const char *member_name = member->name;
|
||||
op->name = member_name;
|
||||
op->op_count = ecs_vec_count(ops) - cur;
|
||||
op->member_index = i;
|
||||
|
||||
flecs_name_index_ensure(
|
||||
member_index, flecs_ito(uint64_t, cur - first - 1),
|
||||
member_name, 0, 0);
|
||||
}
|
||||
|
||||
flecs_meta_ops_add(ops, EcsOpPop);
|
||||
flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_type(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_size_t offset,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType);
|
||||
if (!ptr) {
|
||||
char *path = ecs_get_fullpath(world, type);
|
||||
ecs_err("missing EcsMetaType for type %s'", path);
|
||||
ecs_os_free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch(ptr->kind) {
|
||||
case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops);
|
||||
case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops);
|
||||
case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops);
|
||||
case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops);
|
||||
case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops);
|
||||
case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops);
|
||||
case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_meta_serialize_component(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
ecs_vec_t *ops)
|
||||
{
|
||||
const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType);
|
||||
if (!ptr) {
|
||||
char *path = ecs_get_fullpath(world, type);
|
||||
ecs_err("missing EcsMetaType for type %s'", path);
|
||||
ecs_os_free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ptr->kind == EcsArrayType) {
|
||||
return flecs_meta_serialize_array_component(world, type, ops);
|
||||
} else {
|
||||
return flecs_meta_serialize_type(world, type, 0, ops);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_meta_type_serialized_init(
|
||||
ecs_iter_t *it)
|
||||
{
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
ecs_vec_t ops;
|
||||
ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0);
|
||||
flecs_meta_serialize_component(world, e, &ops);
|
||||
|
||||
EcsMetaTypeSerialized *ptr = ecs_get_mut(
|
||||
world, e, EcsMetaTypeSerialized);
|
||||
if (ptr->ops.array) {
|
||||
ecs_meta_dtor_serialized(ptr);
|
||||
}
|
||||
|
||||
ptr->ops = ops;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
846
engine/libs/flecs/src/addons/meta_c.c
Normal file
846
engine/libs/flecs/src/addons/meta_c.c
Normal file
@@ -0,0 +1,846 @@
|
||||
/**
|
||||
* @file addons/meta_c.c
|
||||
* @brief C utilities for meta addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_META_C
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#define ECS_META_IDENTIFIER_LENGTH (256)
|
||||
|
||||
#define ecs_meta_error(ctx, ptr, ...)\
|
||||
ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__);
|
||||
|
||||
typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH];
|
||||
|
||||
typedef struct meta_parse_ctx_t {
|
||||
const char *name;
|
||||
const char *desc;
|
||||
} meta_parse_ctx_t;
|
||||
|
||||
typedef struct meta_type_t {
|
||||
ecs_meta_token_t type;
|
||||
ecs_meta_token_t params;
|
||||
bool is_const;
|
||||
bool is_ptr;
|
||||
} meta_type_t;
|
||||
|
||||
typedef struct meta_member_t {
|
||||
meta_type_t type;
|
||||
ecs_meta_token_t name;
|
||||
int64_t count;
|
||||
bool is_partial;
|
||||
} meta_member_t;
|
||||
|
||||
typedef struct meta_constant_t {
|
||||
ecs_meta_token_t name;
|
||||
int64_t value;
|
||||
bool is_value_set;
|
||||
} meta_constant_t;
|
||||
|
||||
typedef struct meta_params_t {
|
||||
meta_type_t key_type;
|
||||
meta_type_t type;
|
||||
int64_t count;
|
||||
bool is_key_value;
|
||||
bool is_fixed_size;
|
||||
} meta_params_t;
|
||||
|
||||
static
|
||||
const char* skip_scope(const char *ptr, meta_parse_ctx_t *ctx) {
|
||||
/* Keep track of which characters were used to open the scope */
|
||||
char stack[256];
|
||||
int32_t sp = 0;
|
||||
char ch;
|
||||
|
||||
while ((ch = *ptr)) {
|
||||
if (ch == '(' || ch == '<') {
|
||||
stack[sp] = ch;
|
||||
|
||||
sp ++;
|
||||
if (sp >= 256) {
|
||||
ecs_meta_error(ctx, ptr, "maximum level of nesting reached");
|
||||
goto error;
|
||||
}
|
||||
} else if (ch == ')' || ch == '>') {
|
||||
sp --;
|
||||
if ((sp < 0) || (ch == '>' && stack[sp] != '<') ||
|
||||
(ch == ')' && stack[sp] != '('))
|
||||
{
|
||||
ecs_meta_error(ctx, ptr, "mismatching %c in identifier", ch);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
ptr ++;
|
||||
|
||||
if (!sp) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
const char* parse_c_digit(
|
||||
const char *ptr,
|
||||
int64_t *value_out)
|
||||
{
|
||||
char token[24];
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
ptr = ecs_parse_digit(ptr, token);
|
||||
if (!ptr) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
*value_out = strtol(token, NULL, 0);
|
||||
|
||||
return ecs_parse_ws_eol(ptr);
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
const char* parse_c_identifier(
|
||||
const char *ptr,
|
||||
char *buff,
|
||||
char *params,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
char *bptr = buff, ch;
|
||||
|
||||
if (params) {
|
||||
params[0] = '\0';
|
||||
}
|
||||
|
||||
/* Ignore whitespaces */
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
ch = *ptr;
|
||||
|
||||
if (!isalpha(ch) && (ch != '_')) {
|
||||
ecs_meta_error(ctx, ptr, "invalid identifier (starts with '%c')", ch);
|
||||
goto error;
|
||||
}
|
||||
|
||||
while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' &&
|
||||
ch != '>' && ch != '}' && ch != '*')
|
||||
{
|
||||
/* Type definitions can contain macros or templates */
|
||||
if (ch == '(' || ch == '<') {
|
||||
if (!params) {
|
||||
ecs_meta_error(ctx, ptr, "unexpected %c", *ptr);
|
||||
goto error;
|
||||
}
|
||||
|
||||
const char *end = skip_scope(ptr, ctx);
|
||||
ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr));
|
||||
params[end - ptr] = '\0';
|
||||
|
||||
ptr = end;
|
||||
} else {
|
||||
*bptr = ch;
|
||||
bptr ++;
|
||||
ptr ++;
|
||||
}
|
||||
}
|
||||
|
||||
*bptr = '\0';
|
||||
|
||||
if (!ch) {
|
||||
ecs_meta_error(ctx, ptr, "unexpected end of token");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
const char * meta_open_scope(
|
||||
const char *ptr,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
/* Skip initial whitespaces */
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
|
||||
/* Is this the start of the type definition? */
|
||||
if (ctx->desc == ptr) {
|
||||
if (*ptr != '{') {
|
||||
ecs_meta_error(ctx, ptr, "missing '{' in struct definition");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ptr ++;
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
}
|
||||
|
||||
/* Is this the end of the type definition? */
|
||||
if (!*ptr) {
|
||||
ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Is this the end of the type definition? */
|
||||
if (*ptr == '}') {
|
||||
ptr = ecs_parse_ws_eol(ptr + 1);
|
||||
if (*ptr) {
|
||||
ecs_meta_error(ctx, ptr,
|
||||
"stray characters after struct definition");
|
||||
goto error;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
const char* meta_parse_constant(
|
||||
const char *ptr,
|
||||
meta_constant_t *token,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
ptr = meta_open_scope(ptr, ctx);
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
token->is_value_set = false;
|
||||
|
||||
/* Parse token, constant identifier */
|
||||
ptr = parse_c_identifier(ptr, token->name, NULL, ctx);
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Explicit value assignment */
|
||||
if (*ptr == '=') {
|
||||
int64_t value = 0;
|
||||
ptr = parse_c_digit(ptr + 1, &value);
|
||||
token->value = value;
|
||||
token->is_value_set = true;
|
||||
}
|
||||
|
||||
/* Expect a ',' or '}' */
|
||||
if (*ptr != ',' && *ptr != '}') {
|
||||
ecs_meta_error(ctx, ptr, "missing , after enum constant");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (*ptr == ',') {
|
||||
return ptr + 1;
|
||||
} else {
|
||||
return ptr;
|
||||
}
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
const char* meta_parse_type(
|
||||
const char *ptr,
|
||||
meta_type_t *token,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
token->is_ptr = false;
|
||||
token->is_const = false;
|
||||
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
|
||||
/* Parse token, expect type identifier or ECS_PROPERTY */
|
||||
ptr = parse_c_identifier(ptr, token->type, token->params, ctx);
|
||||
if (!ptr) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!strcmp(token->type, "ECS_PRIVATE")) {
|
||||
/* Members from this point are not stored in metadata */
|
||||
ptr += ecs_os_strlen(ptr);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* If token is const, set const flag and continue parsing type */
|
||||
if (!strcmp(token->type, "const")) {
|
||||
token->is_const = true;
|
||||
|
||||
/* Parse type after const */
|
||||
ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx);
|
||||
}
|
||||
|
||||
/* Check if type is a pointer */
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
if (*ptr == '*') {
|
||||
token->is_ptr = true;
|
||||
ptr ++;
|
||||
}
|
||||
|
||||
done:
|
||||
return ptr;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
const char* meta_parse_member(
|
||||
const char *ptr,
|
||||
meta_member_t *token,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
ptr = meta_open_scope(ptr, ctx);
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
token->count = 1;
|
||||
token->is_partial = false;
|
||||
|
||||
/* Parse member type */
|
||||
ptr = meta_parse_type(ptr, &token->type, ctx);
|
||||
if (!ptr) {
|
||||
token->is_partial = true;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ptr[0]) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* Next token is the identifier */
|
||||
ptr = parse_c_identifier(ptr, token->name, NULL, ctx);
|
||||
if (!ptr) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Skip whitespace between member and [ or ; */
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
|
||||
/* Check if this is an array */
|
||||
char *array_start = strchr(token->name, '[');
|
||||
if (!array_start) {
|
||||
/* If the [ was separated by a space, it will not be parsed as part of
|
||||
* the name */
|
||||
if (*ptr == '[') {
|
||||
/* safe, will not be modified */
|
||||
array_start = ECS_CONST_CAST(char*, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (array_start) {
|
||||
/* Check if the [ matches with a ] */
|
||||
char *array_end = strchr(array_start, ']');
|
||||
if (!array_end) {
|
||||
ecs_meta_error(ctx, ptr, "missing ']'");
|
||||
goto error;
|
||||
|
||||
} else if (array_end - array_start == 0) {
|
||||
ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported");
|
||||
goto error;
|
||||
}
|
||||
|
||||
token->count = atoi(array_start + 1);
|
||||
|
||||
if (array_start == ptr) {
|
||||
/* If [ was found after name, continue parsing after ] */
|
||||
ptr = array_end + 1;
|
||||
} else {
|
||||
/* If [ was fonud in name, replace it with 0 terminator */
|
||||
array_start[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/* Expect a ; */
|
||||
if (*ptr != ';') {
|
||||
ecs_meta_error(ctx, ptr, "missing ; after member declaration");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return ptr + 1;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
int meta_parse_desc(
|
||||
const char *ptr,
|
||||
meta_params_t *token,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
token->is_key_value = false;
|
||||
token->is_fixed_size = false;
|
||||
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
if (*ptr != '(' && *ptr != '<') {
|
||||
ecs_meta_error(ctx, ptr,
|
||||
"expected '(' at start of collection definition");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ptr ++;
|
||||
|
||||
/* Parse type identifier */
|
||||
ptr = meta_parse_type(ptr, &token->type, ctx);
|
||||
if (!ptr) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
|
||||
/* If next token is a ',' the first type was a key type */
|
||||
if (*ptr == ',') {
|
||||
ptr = ecs_parse_ws_eol(ptr + 1);
|
||||
|
||||
if (isdigit(*ptr)) {
|
||||
int64_t value;
|
||||
ptr = parse_c_digit(ptr, &value);
|
||||
if (!ptr) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
token->count = value;
|
||||
token->is_fixed_size = true;
|
||||
} else {
|
||||
token->key_type = token->type;
|
||||
|
||||
/* Parse element type */
|
||||
ptr = meta_parse_type(ptr, &token->type, ctx);
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
|
||||
token->is_key_value = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (*ptr != ')' && *ptr != '>') {
|
||||
ecs_meta_error(ctx, ptr,
|
||||
"expected ')' at end of collection definition");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_entity_t meta_lookup(
|
||||
ecs_world_t *world,
|
||||
meta_type_t *token,
|
||||
const char *ptr,
|
||||
int64_t count,
|
||||
meta_parse_ctx_t *ctx);
|
||||
|
||||
static
|
||||
ecs_entity_t meta_lookup_array(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t e,
|
||||
const char *params_decl,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
meta_parse_ctx_t param_ctx = {
|
||||
.name = ctx->name,
|
||||
.desc = params_decl
|
||||
};
|
||||
|
||||
meta_params_t params;
|
||||
if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) {
|
||||
goto error;
|
||||
}
|
||||
if (!params.is_fixed_size) {
|
||||
ecs_meta_error(ctx, params_decl, "missing size for array");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!params.count) {
|
||||
ecs_meta_error(ctx, params_decl, "invalid array size");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_entity_t element_type = ecs_lookup_symbol(
|
||||
world, params.type.type, true, true);
|
||||
if (!element_type) {
|
||||
ecs_meta_error(ctx, params_decl, "unknown element type '%s'",
|
||||
params.type.type);
|
||||
}
|
||||
|
||||
if (!e) {
|
||||
e = ecs_new_id(world);
|
||||
}
|
||||
|
||||
ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count });
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_entity_t meta_lookup_vector(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t e,
|
||||
const char *params_decl,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
meta_parse_ctx_t param_ctx = {
|
||||
.name = ctx->name,
|
||||
.desc = params_decl
|
||||
};
|
||||
|
||||
meta_params_t params;
|
||||
if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (params.is_key_value) {
|
||||
ecs_meta_error(ctx, params_decl,
|
||||
"unexpected key value parameters for vector");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_entity_t element_type = meta_lookup(
|
||||
world, ¶ms.type, params_decl, 1, ¶m_ctx);
|
||||
|
||||
if (!e) {
|
||||
e = ecs_new_id(world);
|
||||
}
|
||||
|
||||
return ecs_set(world, e, EcsVector, { element_type });
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_entity_t meta_lookup_bitmask(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t e,
|
||||
const char *params_decl,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
(void)e;
|
||||
|
||||
meta_parse_ctx_t param_ctx = {
|
||||
.name = ctx->name,
|
||||
.desc = params_decl
|
||||
};
|
||||
|
||||
meta_params_t params;
|
||||
if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (params.is_key_value) {
|
||||
ecs_meta_error(ctx, params_decl,
|
||||
"unexpected key value parameters for bitmask");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (params.is_fixed_size) {
|
||||
ecs_meta_error(ctx, params_decl,
|
||||
"unexpected size for bitmask");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_entity_t bitmask_type = meta_lookup(
|
||||
world, ¶ms.type, params_decl, 1, ¶m_ctx);
|
||||
ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
#ifndef FLECS_NDEBUG
|
||||
/* Make sure this is a bitmask type */
|
||||
const EcsMetaType *type_ptr = ecs_get(world, bitmask_type, EcsMetaType);
|
||||
ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL);
|
||||
#endif
|
||||
|
||||
return bitmask_type;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_entity_t meta_lookup(
|
||||
ecs_world_t *world,
|
||||
meta_type_t *token,
|
||||
const char *ptr,
|
||||
int64_t count,
|
||||
meta_parse_ctx_t *ctx)
|
||||
{
|
||||
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
const char *typename = token->type;
|
||||
ecs_entity_t type = 0;
|
||||
|
||||
/* Parse vector type */
|
||||
if (!token->is_ptr) {
|
||||
if (!ecs_os_strcmp(typename, "ecs_array")) {
|
||||
type = meta_lookup_array(world, 0, token->params, ctx);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "ecs_vector") ||
|
||||
!ecs_os_strcmp(typename, "flecs::vector"))
|
||||
{
|
||||
type = meta_lookup_vector(world, 0, token->params, ctx);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "flecs::bitmask")) {
|
||||
type = meta_lookup_bitmask(world, 0, token->params, ctx);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "flecs::byte")) {
|
||||
type = ecs_id(ecs_byte_t);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "char")) {
|
||||
type = ecs_id(ecs_char_t);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "bool") ||
|
||||
!ecs_os_strcmp(typename, "_Bool"))
|
||||
{
|
||||
type = ecs_id(ecs_bool_t);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "int8_t")) {
|
||||
type = ecs_id(ecs_i8_t);
|
||||
} else if (!ecs_os_strcmp(typename, "int16_t")) {
|
||||
type = ecs_id(ecs_i16_t);
|
||||
} else if (!ecs_os_strcmp(typename, "int32_t")) {
|
||||
type = ecs_id(ecs_i32_t);
|
||||
} else if (!ecs_os_strcmp(typename, "int64_t")) {
|
||||
type = ecs_id(ecs_i64_t);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "uint8_t")) {
|
||||
type = ecs_id(ecs_u8_t);
|
||||
} else if (!ecs_os_strcmp(typename, "uint16_t")) {
|
||||
type = ecs_id(ecs_u16_t);
|
||||
} else if (!ecs_os_strcmp(typename, "uint32_t")) {
|
||||
type = ecs_id(ecs_u32_t);
|
||||
} else if (!ecs_os_strcmp(typename, "uint64_t")) {
|
||||
type = ecs_id(ecs_u64_t);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "float")) {
|
||||
type = ecs_id(ecs_f32_t);
|
||||
} else if (!ecs_os_strcmp(typename, "double")) {
|
||||
type = ecs_id(ecs_f64_t);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "ecs_entity_t")) {
|
||||
type = ecs_id(ecs_entity_t);
|
||||
|
||||
} else if (!ecs_os_strcmp(typename, "char*")) {
|
||||
type = ecs_id(ecs_string_t);
|
||||
} else {
|
||||
type = ecs_lookup_symbol(world, typename, true, true);
|
||||
}
|
||||
} else {
|
||||
if (!ecs_os_strcmp(typename, "char")) {
|
||||
typename = "flecs.meta.string";
|
||||
} else
|
||||
if (token->is_ptr) {
|
||||
typename = "flecs.meta.uptr";
|
||||
} else
|
||||
if (!ecs_os_strcmp(typename, "char*") ||
|
||||
!ecs_os_strcmp(typename, "flecs::string"))
|
||||
{
|
||||
typename = "flecs.meta.string";
|
||||
}
|
||||
|
||||
type = ecs_lookup_symbol(world, typename, true, true);
|
||||
}
|
||||
|
||||
if (count != 1) {
|
||||
ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
type = ecs_set(world, 0, EcsArray, {type, (int32_t)count});
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
ecs_meta_error(ctx, ptr, "unknown type '%s'", typename);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return type;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int meta_parse_struct(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t t,
|
||||
const char *desc)
|
||||
{
|
||||
const char *ptr = desc;
|
||||
const char *name = ecs_get_name(world, t);
|
||||
|
||||
meta_member_t token;
|
||||
meta_parse_ctx_t ctx = {
|
||||
.name = name,
|
||||
.desc = ptr
|
||||
};
|
||||
|
||||
ecs_entity_t old_scope = ecs_set_scope(world, t);
|
||||
|
||||
while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) {
|
||||
ecs_entity_t m = ecs_entity(world, {
|
||||
.name = token.name
|
||||
});
|
||||
|
||||
ecs_entity_t type = meta_lookup(
|
||||
world, &token.type, ptr, 1, &ctx);
|
||||
if (!type) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_set(world, m, EcsMember, {
|
||||
.type = type,
|
||||
.count = (ecs_size_t)token.count
|
||||
});
|
||||
}
|
||||
|
||||
ecs_set_scope(world, old_scope);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
int meta_parse_constants(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t t,
|
||||
const char *desc,
|
||||
bool is_bitmask)
|
||||
{
|
||||
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
const char *ptr = desc;
|
||||
const char *name = ecs_get_name(world, t);
|
||||
int32_t name_len = ecs_os_strlen(name);
|
||||
const ecs_world_info_t *info = ecs_get_world_info(world);
|
||||
const char *name_prefix = info->name_prefix;
|
||||
int32_t name_prefix_len = name_prefix ? ecs_os_strlen(name_prefix) : 0;
|
||||
|
||||
meta_parse_ctx_t ctx = {
|
||||
.name = name,
|
||||
.desc = ptr
|
||||
};
|
||||
|
||||
meta_constant_t token;
|
||||
int64_t last_value = 0;
|
||||
|
||||
ecs_entity_t old_scope = ecs_set_scope(world, t);
|
||||
|
||||
while ((ptr = meta_parse_constant(ptr, &token, &ctx))) {
|
||||
if (token.is_value_set) {
|
||||
last_value = token.value;
|
||||
} else if (is_bitmask) {
|
||||
ecs_meta_error(&ctx, ptr,
|
||||
"bitmask requires explicit value assignment");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (name_prefix) {
|
||||
if (!ecs_os_strncmp(token.name, name_prefix, name_prefix_len)) {
|
||||
ecs_os_memmove(token.name, token.name + name_prefix_len,
|
||||
ecs_os_strlen(token.name) - name_prefix_len + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ecs_os_strncmp(token.name, name, name_len)) {
|
||||
ecs_os_memmove(token.name, token.name + name_len,
|
||||
ecs_os_strlen(token.name) - name_len + 1);
|
||||
}
|
||||
|
||||
ecs_entity_t c = ecs_entity(world, {
|
||||
.name = token.name
|
||||
});
|
||||
|
||||
if (!is_bitmask) {
|
||||
ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t,
|
||||
{(ecs_i32_t)last_value});
|
||||
} else {
|
||||
ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t,
|
||||
{(ecs_u32_t)last_value});
|
||||
}
|
||||
|
||||
last_value ++;
|
||||
}
|
||||
|
||||
ecs_set_scope(world, old_scope);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
int meta_parse_enum(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t t,
|
||||
const char *desc)
|
||||
{
|
||||
ecs_add(world, t, EcsEnum);
|
||||
return meta_parse_constants(world, t, desc, false);
|
||||
}
|
||||
|
||||
static
|
||||
int meta_parse_bitmask(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t t,
|
||||
const char *desc)
|
||||
{
|
||||
ecs_add(world, t, EcsBitmask);
|
||||
return meta_parse_constants(world, t, desc, true);
|
||||
}
|
||||
|
||||
int ecs_meta_from_desc(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t component,
|
||||
ecs_type_kind_t kind,
|
||||
const char *desc)
|
||||
{
|
||||
switch(kind) {
|
||||
case EcsStructType:
|
||||
if (meta_parse_struct(world, component, desc)) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EcsEnumType:
|
||||
if (meta_parse_enum(world, component, desc)) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EcsBitmaskType:
|
||||
if (meta_parse_bitmask(world, component, desc)) {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EcsPrimitiveType:
|
||||
case EcsArrayType:
|
||||
case EcsVectorType:
|
||||
case EcsOpaqueType:
|
||||
break;
|
||||
default:
|
||||
ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind");
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
951
engine/libs/flecs/src/addons/metrics.c
Normal file
951
engine/libs/flecs/src/addons/metrics.c
Normal file
@@ -0,0 +1,951 @@
|
||||
/**
|
||||
* @file addons/metrics.c
|
||||
* @brief Metrics addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_METRICS
|
||||
|
||||
/* Public components */
|
||||
ECS_COMPONENT_DECLARE(FlecsMetrics);
|
||||
ECS_TAG_DECLARE(EcsMetricInstance);
|
||||
ECS_COMPONENT_DECLARE(EcsMetricValue);
|
||||
ECS_COMPONENT_DECLARE(EcsMetricSource);
|
||||
ECS_TAG_DECLARE(EcsMetric);
|
||||
ECS_TAG_DECLARE(EcsCounter);
|
||||
ECS_TAG_DECLARE(EcsCounterIncrement);
|
||||
ECS_TAG_DECLARE(EcsCounterId);
|
||||
ECS_TAG_DECLARE(EcsGauge);
|
||||
|
||||
/* Internal components */
|
||||
static ECS_COMPONENT_DECLARE(EcsMetricMember);
|
||||
static ECS_COMPONENT_DECLARE(EcsMetricId);
|
||||
static ECS_COMPONENT_DECLARE(EcsMetricOneOf);
|
||||
static ECS_COMPONENT_DECLARE(EcsMetricCountIds);
|
||||
static ECS_COMPONENT_DECLARE(EcsMetricCountTargets);
|
||||
static ECS_COMPONENT_DECLARE(EcsMetricMemberInstance);
|
||||
static ECS_COMPONENT_DECLARE(EcsMetricIdInstance);
|
||||
static ECS_COMPONENT_DECLARE(EcsMetricOneOfInstance);
|
||||
|
||||
/** Context for metric */
|
||||
typedef struct {
|
||||
ecs_entity_t metric; /**< Metric entity */
|
||||
ecs_entity_t kind; /**< Metric kind (gauge, counter) */
|
||||
} ecs_metric_ctx_t;
|
||||
|
||||
/** Context for metric that monitors member */
|
||||
typedef struct {
|
||||
ecs_metric_ctx_t metric;
|
||||
ecs_primitive_kind_t type_kind; /**< Primitive type kind of member */
|
||||
uint16_t offset; /**< Offset of member in component */
|
||||
} ecs_member_metric_ctx_t;
|
||||
|
||||
/** Context for metric that monitors whether entity has id */
|
||||
typedef struct {
|
||||
ecs_metric_ctx_t metric;
|
||||
ecs_id_record_t *idr; /**< Id record for monitored component */
|
||||
} ecs_id_metric_ctx_t;
|
||||
|
||||
/** Context for metric that monitors whether entity has pair target */
|
||||
typedef struct {
|
||||
ecs_metric_ctx_t metric;
|
||||
ecs_id_record_t *idr; /**< Id record for monitored component */
|
||||
ecs_size_t size; /**< Size of metric type */
|
||||
ecs_map_t target_offset; /**< Pair target to metric type offset */
|
||||
} ecs_oneof_metric_ctx_t;
|
||||
|
||||
/** Context for metric that monitors how many entities have a pair target */
|
||||
typedef struct {
|
||||
ecs_metric_ctx_t metric;
|
||||
ecs_id_record_t *idr; /**< Id record for monitored component */
|
||||
ecs_map_t targets; /**< Map of counters for each target */
|
||||
} ecs_count_targets_metric_ctx_t;
|
||||
|
||||
/** Stores context shared for all instances of member metric */
|
||||
typedef struct {
|
||||
ecs_member_metric_ctx_t *ctx;
|
||||
} EcsMetricMember;
|
||||
|
||||
/** Stores context shared for all instances of id metric */
|
||||
typedef struct {
|
||||
ecs_id_metric_ctx_t *ctx;
|
||||
} EcsMetricId;
|
||||
|
||||
/** Stores context shared for all instances of oneof metric */
|
||||
typedef struct {
|
||||
ecs_oneof_metric_ctx_t *ctx;
|
||||
} EcsMetricOneOf;
|
||||
|
||||
/** Stores context shared for all instances of id counter metric */
|
||||
typedef struct {
|
||||
ecs_id_t id;
|
||||
} EcsMetricCountIds;
|
||||
|
||||
/** Stores context shared for all instances of target counter metric */
|
||||
typedef struct {
|
||||
ecs_count_targets_metric_ctx_t *ctx;
|
||||
} EcsMetricCountTargets;
|
||||
|
||||
/** Instance of member metric */
|
||||
typedef struct {
|
||||
ecs_ref_t ref;
|
||||
ecs_member_metric_ctx_t *ctx;
|
||||
} EcsMetricMemberInstance;
|
||||
|
||||
/** Instance of id metric */
|
||||
typedef struct {
|
||||
ecs_record_t *r;
|
||||
ecs_id_metric_ctx_t *ctx;
|
||||
} EcsMetricIdInstance;
|
||||
|
||||
/** Instance of oneof metric */
|
||||
typedef struct {
|
||||
ecs_record_t *r;
|
||||
ecs_oneof_metric_ctx_t *ctx;
|
||||
} EcsMetricOneOfInstance;
|
||||
|
||||
/** Component lifecycle */
|
||||
|
||||
static ECS_DTOR(EcsMetricMember, ptr, {
|
||||
ecs_os_free(ptr->ctx);
|
||||
})
|
||||
|
||||
static ECS_MOVE(EcsMetricMember, dst, src, {
|
||||
*dst = *src;
|
||||
src->ctx = NULL;
|
||||
})
|
||||
|
||||
static ECS_DTOR(EcsMetricId, ptr, {
|
||||
ecs_os_free(ptr->ctx);
|
||||
})
|
||||
|
||||
static ECS_MOVE(EcsMetricId, dst, src, {
|
||||
*dst = *src;
|
||||
src->ctx = NULL;
|
||||
})
|
||||
|
||||
static ECS_DTOR(EcsMetricOneOf, ptr, {
|
||||
if (ptr->ctx) {
|
||||
ecs_map_fini(&ptr->ctx->target_offset);
|
||||
ecs_os_free(ptr->ctx);
|
||||
}
|
||||
})
|
||||
|
||||
static ECS_MOVE(EcsMetricOneOf, dst, src, {
|
||||
*dst = *src;
|
||||
src->ctx = NULL;
|
||||
})
|
||||
|
||||
static ECS_DTOR(EcsMetricCountTargets, ptr, {
|
||||
if (ptr->ctx) {
|
||||
ecs_map_fini(&ptr->ctx->targets);
|
||||
ecs_os_free(ptr->ctx);
|
||||
}
|
||||
})
|
||||
|
||||
static ECS_MOVE(EcsMetricCountTargets, dst, src, {
|
||||
*dst = *src;
|
||||
src->ctx = NULL;
|
||||
})
|
||||
|
||||
/** Observer used for creating new instances of member metric */
|
||||
static void flecs_metrics_on_member_metric(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
ecs_member_metric_ctx_t *ctx = it->ctx;
|
||||
ecs_id_t id = ecs_field_id(it, 1);
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric);
|
||||
|
||||
EcsMetricMemberInstance *src = ecs_emplace(
|
||||
world, m, EcsMetricMemberInstance);
|
||||
src->ref = ecs_ref_init_id(world, e, id);
|
||||
src->ctx = ctx;
|
||||
ecs_modified(world, m, EcsMetricMemberInstance);
|
||||
ecs_set(world, m, EcsMetricValue, { 0 });
|
||||
ecs_set(world, m, EcsMetricSource, { e });
|
||||
ecs_add(world, m, EcsMetricInstance);
|
||||
ecs_add_pair(world, m, EcsMetric, ctx->metric.kind);
|
||||
}
|
||||
}
|
||||
|
||||
/** Observer used for creating new instances of id metric */
|
||||
static void flecs_metrics_on_id_metric(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
ecs_id_metric_ctx_t *ctx = it->ctx;
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric);
|
||||
|
||||
EcsMetricIdInstance *src = ecs_emplace(world, m, EcsMetricIdInstance);
|
||||
src->r = ecs_record_find(world, e);
|
||||
src->ctx = ctx;
|
||||
ecs_modified(world, m, EcsMetricIdInstance);
|
||||
ecs_set(world, m, EcsMetricValue, { 0 });
|
||||
ecs_set(world, m, EcsMetricSource, { e });
|
||||
ecs_add(world, m, EcsMetricInstance);
|
||||
ecs_add_pair(world, m, EcsMetric, ctx->metric.kind);
|
||||
}
|
||||
}
|
||||
|
||||
/** Observer used for creating new instances of oneof metric */
|
||||
static void flecs_metrics_on_oneof_metric(ecs_iter_t *it) {
|
||||
if (it->event == EcsOnRemove) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_world_t *world = it->world;
|
||||
ecs_oneof_metric_ctx_t *ctx = it->ctx;
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric);
|
||||
|
||||
EcsMetricOneOfInstance *src = ecs_emplace(world, m, EcsMetricOneOfInstance);
|
||||
src->r = ecs_record_find(world, e);
|
||||
src->ctx = ctx;
|
||||
ecs_modified(world, m, EcsMetricOneOfInstance);
|
||||
ecs_add_pair(world, m, ctx->metric.metric, ecs_id(EcsMetricValue));
|
||||
ecs_set(world, m, EcsMetricSource, { e });
|
||||
ecs_add(world, m, EcsMetricInstance);
|
||||
ecs_add_pair(world, m, EcsMetric, ctx->metric.kind);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set doc name of metric instance to name of source entity */
|
||||
#ifdef FLECS_DOC
|
||||
static void SetMetricDocName(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1);
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t src_e = src[i].entity;
|
||||
const char *name = ecs_get_name(world, src_e);
|
||||
if (name) {
|
||||
ecs_doc_set_name(world, it->entities[i], name);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Delete metric instances for entities that are no longer alive */
|
||||
static void ClearMetricInstance(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1);
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t src_e = src[i].entity;
|
||||
if (!ecs_is_alive(world, src_e)) {
|
||||
ecs_delete(world, it->entities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Update member metric */
|
||||
static void UpdateMemberInstance(ecs_iter_t *it, bool counter) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
EcsMetricValue *m = ecs_field(it, EcsMetricValue, 1);
|
||||
EcsMetricMemberInstance *mi = ecs_field(it, EcsMetricMemberInstance, 2);
|
||||
ecs_ftime_t dt = it->delta_time;
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_member_metric_ctx_t *ctx = mi[i].ctx;
|
||||
ecs_ref_t *ref = &mi[i].ref;
|
||||
const void *ptr = ecs_ref_get_id(world, ref, ref->id);
|
||||
if (ptr) {
|
||||
ptr = ECS_OFFSET(ptr, ctx->offset);
|
||||
if (!counter) {
|
||||
m[i].value = ecs_meta_ptr_to_float(ctx->type_kind, ptr);
|
||||
} else {
|
||||
m[i].value +=
|
||||
ecs_meta_ptr_to_float(ctx->type_kind, ptr) * (double)dt;
|
||||
}
|
||||
} else {
|
||||
ecs_delete(it->world, it->entities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateGaugeMemberInstance(ecs_iter_t *it) {
|
||||
UpdateMemberInstance(it, false);
|
||||
}
|
||||
|
||||
static void UpdateCounterMemberInstance(ecs_iter_t *it) {
|
||||
UpdateMemberInstance(it, false);
|
||||
}
|
||||
|
||||
static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) {
|
||||
UpdateMemberInstance(it, true);
|
||||
}
|
||||
|
||||
/** Update id metric */
|
||||
static void UpdateIdInstance(ecs_iter_t *it, bool counter) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
EcsMetricValue *m = ecs_field(it, EcsMetricValue, 1);
|
||||
EcsMetricIdInstance *mi = ecs_field(it, EcsMetricIdInstance, 2);
|
||||
ecs_ftime_t dt = it->delta_time;
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_table_t *table = mi[i].r->table;
|
||||
if (!table) {
|
||||
ecs_delete(it->world, it->entities[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_id_metric_ctx_t *ctx = mi[i].ctx;
|
||||
ecs_id_record_t *idr = ctx->idr;
|
||||
if (flecs_search_w_idr(world, table, idr->id, NULL, idr) != -1) {
|
||||
if (!counter) {
|
||||
m[i].value = 1.0;
|
||||
} else {
|
||||
m[i].value += 1.0 * (double)dt;
|
||||
}
|
||||
} else {
|
||||
ecs_delete(it->world, it->entities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateGaugeIdInstance(ecs_iter_t *it) {
|
||||
UpdateIdInstance(it, false);
|
||||
}
|
||||
|
||||
static void UpdateCounterIdInstance(ecs_iter_t *it) {
|
||||
UpdateIdInstance(it, true);
|
||||
}
|
||||
|
||||
/** Update oneof metric */
|
||||
static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
ecs_table_t *table = it->table;
|
||||
void *m = ecs_table_get_column(table,
|
||||
ecs_table_type_to_column_index(table, it->columns[0] - 1), it->offset);
|
||||
EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 2);
|
||||
ecs_ftime_t dt = it->delta_time;
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_oneof_metric_ctx_t *ctx = mi[i].ctx;
|
||||
ecs_table_t *mtable = mi[i].r->table;
|
||||
|
||||
double *value = ECS_ELEM(m, ctx->size, i);
|
||||
if (!counter) {
|
||||
ecs_os_memset(value, 0, ctx->size);
|
||||
}
|
||||
|
||||
if (!mtable) {
|
||||
ecs_delete(it->world, it->entities[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_id_record_t *idr = ctx->idr;
|
||||
ecs_id_t id;
|
||||
if (flecs_search_w_idr(world, mtable, idr->id, &id, idr) == -1) {
|
||||
ecs_delete(it->world, it->entities[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_entity_t tgt = ECS_PAIR_SECOND(id);
|
||||
uint64_t *offset = ecs_map_get(&ctx->target_offset, tgt);
|
||||
if (!offset) {
|
||||
ecs_err("unexpected relationship target for metric");
|
||||
continue;
|
||||
}
|
||||
|
||||
value = ECS_OFFSET(value, *offset);
|
||||
|
||||
if (!counter) {
|
||||
*value = 1.0;
|
||||
} else {
|
||||
*value += 1.0 * (double)dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateGaugeOneOfInstance(ecs_iter_t *it) {
|
||||
UpdateOneOfInstance(it, false);
|
||||
}
|
||||
|
||||
static void UpdateCounterOneOfInstance(ecs_iter_t *it) {
|
||||
UpdateOneOfInstance(it, true);
|
||||
}
|
||||
|
||||
static void UpdateCountTargets(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 1);
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_count_targets_metric_ctx_t *ctx = m[i].ctx;
|
||||
ecs_id_record_t *cur = ctx->idr;
|
||||
while ((cur = cur->first.next)) {
|
||||
ecs_id_t id = cur->id;
|
||||
ecs_entity_t *mi = ecs_map_ensure(&ctx->targets, id);
|
||||
if (!mi[0]) {
|
||||
mi[0] = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric);
|
||||
ecs_entity_t tgt = ecs_pair_second(world, cur->id);
|
||||
const char *name = ecs_get_name(world, tgt);
|
||||
if (name) {
|
||||
ecs_set_name(world, mi[0], name);
|
||||
}
|
||||
|
||||
EcsMetricSource *source = ecs_get_mut(
|
||||
world, mi[0], EcsMetricSource);
|
||||
source->entity = tgt;
|
||||
}
|
||||
|
||||
EcsMetricValue *value = ecs_get_mut(world, mi[0], EcsMetricValue);
|
||||
value->value += (double)ecs_count_id(world, cur->id) *
|
||||
(double)it->delta_system_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateCountIds(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
EcsMetricCountIds *m = ecs_field(it, EcsMetricCountIds, 1);
|
||||
EcsMetricValue *v = ecs_field(it, EcsMetricValue, 2);
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
v[i].value += (double)ecs_count_id(world, m[i].id) *
|
||||
(double)it->delta_system_time;
|
||||
}
|
||||
}
|
||||
|
||||
/** Initialize member metric */
|
||||
static
|
||||
int flecs_member_metric_init(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t metric,
|
||||
const ecs_metric_desc_t *desc)
|
||||
{
|
||||
ecs_entity_t type = 0, member_type = 0, member = 0, id = 0;
|
||||
uintptr_t offset = 0;
|
||||
|
||||
if (desc->dotmember) {
|
||||
if (!desc->id) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
ecs_err("missing id for metric '%s' with member '%s",
|
||||
metric_name, desc->dotmember);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (desc->member) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
ecs_err("cannot set both member and dotmember for metric '%s'",
|
||||
metric_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
type = ecs_get_typeid(world, desc->id);
|
||||
|
||||
ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL);
|
||||
if (ecs_meta_push(&cur)) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
ecs_err("invalid type for metric '%s'", metric_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
if (ecs_meta_dotmember(&cur, desc->dotmember)) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
ecs_err("invalid dotmember '%s' for metric '%s'",
|
||||
desc->dotmember, metric_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
id = desc->id;
|
||||
member_type = ecs_meta_get_type(&cur);
|
||||
offset = (uintptr_t)ecs_meta_get_ptr(&cur);
|
||||
member = ecs_meta_get_member_id(&cur);
|
||||
} else {
|
||||
const EcsMember *m = ecs_get(world, desc->member, EcsMember);
|
||||
if (!m) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
char *member_name = ecs_get_fullpath(world, desc->member);
|
||||
ecs_err("entity '%s' provided for metric '%s' is not a member",
|
||||
member_name, metric_name);
|
||||
ecs_os_free(member_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
type = ecs_get_parent(world, desc->member);
|
||||
if (!type) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
char *member_name = ecs_get_fullpath(world, desc->member);
|
||||
ecs_err("member '%s' provided for metric '%s' is not part of a type",
|
||||
member_name, metric_name);
|
||||
ecs_os_free(member_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
id = type;
|
||||
if (desc->id) {
|
||||
if (type != ecs_get_typeid(world, desc->id)) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
char *member_name = ecs_get_fullpath(world, desc->member);
|
||||
char *id_name = ecs_get_fullpath(world, desc->id);
|
||||
ecs_err("member '%s' for metric '%s' is not of type '%s'",
|
||||
member_name, metric_name, id_name);
|
||||
ecs_os_free(id_name);
|
||||
ecs_os_free(member_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
id = desc->id;
|
||||
}
|
||||
|
||||
member = desc->member;
|
||||
member_type = m->type;
|
||||
offset = flecs_ito(uintptr_t, m->offset);
|
||||
}
|
||||
|
||||
const EcsPrimitive *p = ecs_get(world, member_type, EcsPrimitive);
|
||||
if (!p) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
char *member_name = ecs_get_fullpath(world, desc->member);
|
||||
ecs_err("member '%s' provided for metric '%s' must have primitive type",
|
||||
member_name, metric_name);
|
||||
ecs_os_free(member_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
const EcsMetaType *mt = ecs_get(world, type, EcsMetaType);
|
||||
if (!mt) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
char *member_name = ecs_get_fullpath(world, desc->member);
|
||||
ecs_err("parent of member '%s' for metric '%s' is not a type",
|
||||
member_name, metric_name);
|
||||
ecs_os_free(member_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (mt->kind != EcsStructType) {
|
||||
char *metric_name = ecs_get_fullpath(world, metric);
|
||||
char *member_name = ecs_get_fullpath(world, desc->member);
|
||||
ecs_err("parent of member '%s' for metric '%s' is not a struct",
|
||||
member_name, metric_name);
|
||||
ecs_os_free(member_name);
|
||||
ecs_os_free(metric_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_member_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_member_metric_ctx_t);
|
||||
ctx->metric.metric = metric;
|
||||
ctx->metric.kind = desc->kind;
|
||||
ctx->type_kind = p->kind;
|
||||
ctx->offset = flecs_uto(uint16_t, offset);
|
||||
|
||||
ecs_observer(world, {
|
||||
.entity = metric,
|
||||
.events = { EcsOnAdd },
|
||||
.filter.terms[0] = {
|
||||
.id = id,
|
||||
.src.flags = EcsSelf,
|
||||
.inout = EcsInOutNone
|
||||
},
|
||||
.callback = flecs_metrics_on_member_metric,
|
||||
.yield_existing = true,
|
||||
.ctx = ctx
|
||||
});
|
||||
|
||||
ecs_set_pair(world, metric, EcsMetricMember, member, { .ctx = ctx });
|
||||
ecs_add_pair(world, metric, EcsMetric, desc->kind);
|
||||
ecs_add_id(world, metric, EcsMetric);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Update id metric */
|
||||
static
|
||||
int flecs_id_metric_init(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t metric,
|
||||
const ecs_metric_desc_t *desc)
|
||||
{
|
||||
ecs_id_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_id_metric_ctx_t);
|
||||
ctx->metric.metric = metric;
|
||||
ctx->metric.kind = desc->kind;
|
||||
ctx->idr = flecs_id_record_ensure(world, desc->id);
|
||||
ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_observer(world, {
|
||||
.entity = metric,
|
||||
.events = { EcsOnAdd },
|
||||
.filter.terms[0] = {
|
||||
.id = desc->id,
|
||||
.src.flags = EcsSelf,
|
||||
.inout = EcsInOutNone
|
||||
},
|
||||
.callback = flecs_metrics_on_id_metric,
|
||||
.yield_existing = true,
|
||||
.ctx = ctx
|
||||
});
|
||||
|
||||
ecs_set(world, metric, EcsMetricId, { .ctx = ctx });
|
||||
ecs_add_pair(world, metric, EcsMetric, desc->kind);
|
||||
ecs_add_id(world, metric, EcsMetric);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Update oneof metric */
|
||||
static
|
||||
int flecs_oneof_metric_init(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t metric,
|
||||
ecs_entity_t scope,
|
||||
const ecs_metric_desc_t *desc)
|
||||
{
|
||||
ecs_oneof_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_oneof_metric_ctx_t);
|
||||
ctx->metric.metric = metric;
|
||||
ctx->metric.kind = desc->kind;
|
||||
ctx->idr = flecs_id_record_ensure(world, desc->id);
|
||||
ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_map_init(&ctx->target_offset, NULL);
|
||||
|
||||
/* Add member for each child of oneof to metric, so it can be used as metric
|
||||
* instance type that holds values for all targets */
|
||||
ecs_iter_t it = ecs_children(world, scope);
|
||||
uint64_t offset = 0;
|
||||
while (ecs_children_next(&it)) {
|
||||
int32_t i, count = it.count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t tgt = it.entities[i];
|
||||
const char *name = ecs_get_name(world, tgt);
|
||||
if (!name) {
|
||||
/* Member must have name */
|
||||
continue;
|
||||
}
|
||||
|
||||
char *to_snake_case = flecs_to_snake_case(name);
|
||||
|
||||
ecs_entity_t mbr = ecs_entity(world, {
|
||||
.name = to_snake_case,
|
||||
.add = { ecs_childof(metric) }
|
||||
});
|
||||
|
||||
ecs_os_free(to_snake_case);
|
||||
|
||||
ecs_set(world, mbr, EcsMember, {
|
||||
.type = ecs_id(ecs_f64_t),
|
||||
.unit = EcsSeconds
|
||||
});
|
||||
|
||||
/* Truncate upper 32 bits of target so we can lookup the offset
|
||||
* with the id we get from the pair */
|
||||
ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset;
|
||||
|
||||
offset += sizeof(double);
|
||||
}
|
||||
}
|
||||
|
||||
ctx->size = flecs_uto(ecs_size_t, offset);
|
||||
|
||||
ecs_observer(world, {
|
||||
.entity = metric,
|
||||
.events = { EcsMonitor },
|
||||
.filter.terms[0] = {
|
||||
.id = desc->id,
|
||||
.src.flags = EcsSelf,
|
||||
.inout = EcsInOutNone
|
||||
},
|
||||
.callback = flecs_metrics_on_oneof_metric,
|
||||
.yield_existing = true,
|
||||
.ctx = ctx
|
||||
});
|
||||
|
||||
ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx });
|
||||
ecs_add_pair(world, metric, EcsMetric, desc->kind);
|
||||
ecs_add_id(world, metric, EcsMetric);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_count_id_targets_metric_init(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t metric,
|
||||
const ecs_metric_desc_t *desc)
|
||||
{
|
||||
ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t);
|
||||
ctx->metric.metric = metric;
|
||||
ctx->metric.kind = desc->kind;
|
||||
ctx->idr = flecs_id_record_ensure(world, desc->id);
|
||||
ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_map_init(&ctx->targets, NULL);
|
||||
|
||||
ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx });
|
||||
ecs_add_pair(world, metric, EcsMetric, desc->kind);
|
||||
ecs_add_id(world, metric, EcsMetric);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_count_ids_metric_init(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t metric,
|
||||
const ecs_metric_desc_t *desc)
|
||||
{
|
||||
ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id });
|
||||
ecs_set(world, metric, EcsMetricValue, { .value = 0 });
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_metric_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_metric_desc_t *desc)
|
||||
{
|
||||
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
|
||||
ecs_entity_t result = desc->entity;
|
||||
if (!result) {
|
||||
result = ecs_new_id(world);
|
||||
}
|
||||
|
||||
ecs_entity_t kind = desc->kind;
|
||||
if (!kind) {
|
||||
ecs_err("missing metric kind");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (kind != EcsGauge &&
|
||||
kind != EcsCounter &&
|
||||
kind != EcsCounterId &&
|
||||
kind != EcsCounterIncrement)
|
||||
{
|
||||
ecs_err("invalid metric kind %s", ecs_get_fullpath(world, kind));
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) {
|
||||
ecs_err("CounterIncrement can only be used in combination with member");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (kind == EcsCounterId && (desc->member || desc->dotmember)) {
|
||||
ecs_err("CounterId cannot be used in combination with member");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (desc->brief) {
|
||||
#ifdef FLECS_DOC
|
||||
ecs_doc_set_brief(world, result, desc->brief);
|
||||
#else
|
||||
ecs_warn("FLECS_DOC is not enabled, ignoring metrics brief");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (desc->member || desc->dotmember) {
|
||||
if (flecs_member_metric_init(world, result, desc)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (desc->id) {
|
||||
if (desc->targets) {
|
||||
if (!ecs_id_is_pair(desc->id)) {
|
||||
ecs_err("cannot specify targets for id that is not a pair");
|
||||
goto error;
|
||||
}
|
||||
if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) {
|
||||
ecs_err("first element of pair cannot be wildcard with "
|
||||
" targets enabled");
|
||||
goto error;
|
||||
}
|
||||
if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) {
|
||||
ecs_err("second element of pair must be wildcard with "
|
||||
" targets enabled");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (kind == EcsCounterId) {
|
||||
if (flecs_count_id_targets_metric_init(world, result, desc)) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
ecs_entity_t first = ecs_pair_first(world, desc->id);
|
||||
ecs_entity_t scope = flecs_get_oneof(world, first);
|
||||
if (!scope) {
|
||||
ecs_err("first element of pair must have OneOf with "
|
||||
" targets enabled");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (flecs_oneof_metric_init(world, result, scope, desc)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (kind == EcsCounterId) {
|
||||
if (flecs_count_ids_metric_init(world, result, desc)) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
if (flecs_id_metric_init(world, result, desc)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ecs_err("missing source specified for metric");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return result;
|
||||
error:
|
||||
if (result && result != desc->entity) {
|
||||
ecs_delete(world, result);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FlecsMetricsImport(ecs_world_t *world) {
|
||||
ECS_MODULE_DEFINE(world, FlecsMetrics);
|
||||
|
||||
ECS_IMPORT(world, FlecsPipeline);
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
ECS_IMPORT(world, FlecsUnits);
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
ECS_TAG_DEFINE(world, EcsMetric);
|
||||
ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric);
|
||||
ECS_TAG_DEFINE(world, EcsCounter);
|
||||
ECS_TAG_DEFINE(world, EcsCounterIncrement);
|
||||
ECS_TAG_DEFINE(world, EcsCounterId);
|
||||
ECS_TAG_DEFINE(world, EcsGauge);
|
||||
ecs_set_scope(world, old_scope);
|
||||
|
||||
ecs_set_name_prefix(world, "EcsMetric");
|
||||
ECS_TAG_DEFINE(world, EcsMetricInstance);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricValue);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricSource);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricMember);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricId);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricOneOf);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricCountIds);
|
||||
ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets);
|
||||
|
||||
ecs_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate);
|
||||
ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate);
|
||||
ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate);
|
||||
|
||||
ecs_struct(world, {
|
||||
.entity = ecs_id(EcsMetricValue),
|
||||
.members = {
|
||||
{ .name = "value", .type = ecs_id(ecs_f64_t) }
|
||||
}
|
||||
});
|
||||
|
||||
ecs_struct(world, {
|
||||
.entity = ecs_id(EcsMetricSource),
|
||||
.members = {
|
||||
{ .name = "entity", .type = ecs_id(ecs_entity_t) }
|
||||
}
|
||||
});
|
||||
|
||||
ecs_set_hooks(world, EcsMetricMember, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.dtor = ecs_dtor(EcsMetricMember),
|
||||
.move = ecs_move(EcsMetricMember)
|
||||
});
|
||||
|
||||
ecs_set_hooks(world, EcsMetricId, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.dtor = ecs_dtor(EcsMetricId),
|
||||
.move = ecs_move(EcsMetricId)
|
||||
});
|
||||
|
||||
ecs_set_hooks(world, EcsMetricOneOf, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.dtor = ecs_dtor(EcsMetricOneOf),
|
||||
.move = ecs_move(EcsMetricOneOf)
|
||||
});
|
||||
|
||||
ecs_set_hooks(world, EcsMetricCountTargets, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.dtor = ecs_dtor(EcsMetricCountTargets),
|
||||
.move = ecs_move(EcsMetricCountTargets)
|
||||
});
|
||||
|
||||
ecs_add_id(world, EcsMetric, EcsOneOf);
|
||||
|
||||
#ifdef FLECS_DOC
|
||||
ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, EcsMetricSource);
|
||||
#endif
|
||||
|
||||
ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore,
|
||||
[in] Source);
|
||||
|
||||
ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore,
|
||||
[out] Value,
|
||||
[in] MemberInstance,
|
||||
[none] (Metric, Gauge));
|
||||
|
||||
ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore,
|
||||
[out] Value,
|
||||
[in] MemberInstance,
|
||||
[none] (Metric, Counter));
|
||||
|
||||
ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore,
|
||||
[out] Value,
|
||||
[in] MemberInstance,
|
||||
[none] (Metric, CounterIncrement));
|
||||
|
||||
ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore,
|
||||
[out] Value,
|
||||
[in] IdInstance,
|
||||
[none] (Metric, Gauge));
|
||||
|
||||
ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore,
|
||||
[inout] Value,
|
||||
[in] IdInstance,
|
||||
[none] (Metric, Counter));
|
||||
|
||||
ECS_SYSTEM(world, UpdateGaugeOneOfInstance, EcsPreStore,
|
||||
[none] (_, Value),
|
||||
[in] OneOfInstance,
|
||||
[none] (Metric, Gauge));
|
||||
|
||||
ECS_SYSTEM(world, UpdateCounterOneOfInstance, EcsPreStore,
|
||||
[none] (_, Value),
|
||||
[in] OneOfInstance,
|
||||
[none] (Metric, Counter));
|
||||
|
||||
ECS_SYSTEM(world, UpdateCountIds, EcsPreStore,
|
||||
[inout] CountIds, Value);
|
||||
|
||||
ECS_SYSTEM(world, UpdateCountTargets, EcsPreStore,
|
||||
[inout] CountTargets);
|
||||
}
|
||||
|
||||
#endif
|
||||
235
engine/libs/flecs/src/addons/module.c
Normal file
235
engine/libs/flecs/src/addons/module.c
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* @file addons/module.c
|
||||
* @brief Module addon.
|
||||
*/
|
||||
|
||||
#include "flecs.h"
|
||||
|
||||
#ifdef FLECS_MODULE
|
||||
|
||||
#include "../private_api.h"
|
||||
#include <ctype.h>
|
||||
|
||||
char* ecs_module_path_from_c(
|
||||
const char *c_name)
|
||||
{
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
const char *ptr;
|
||||
char ch;
|
||||
|
||||
for (ptr = c_name; (ch = *ptr); ptr++) {
|
||||
if (isupper(ch)) {
|
||||
ch = flecs_ito(char, tolower(ch));
|
||||
if (ptr != c_name) {
|
||||
ecs_strbuf_appendstrn(&str, ".", 1);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_strbuf_appendstrn(&str, &ch, 1);
|
||||
}
|
||||
|
||||
return ecs_strbuf_get(&str);
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_import(
|
||||
ecs_world_t *world,
|
||||
ecs_module_action_t module,
|
||||
const char *module_name)
|
||||
{
|
||||
ecs_check(!(world->flags & EcsWorldReadonly),
|
||||
ECS_INVALID_WHILE_READONLY, NULL);
|
||||
|
||||
ecs_entity_t old_scope = ecs_set_scope(world, 0);
|
||||
const char *old_name_prefix = world->info.name_prefix;
|
||||
|
||||
char *path = ecs_module_path_from_c(module_name);
|
||||
ecs_entity_t e = ecs_lookup_fullpath(world, path);
|
||||
ecs_os_free(path);
|
||||
|
||||
if (!e) {
|
||||
ecs_trace("#[magenta]import#[reset] %s", module_name);
|
||||
ecs_log_push();
|
||||
|
||||
/* Load module */
|
||||
module(world);
|
||||
|
||||
/* Lookup module entity (must be registered by module) */
|
||||
e = ecs_lookup_fullpath(world, module_name);
|
||||
ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name);
|
||||
|
||||
ecs_log_pop();
|
||||
}
|
||||
|
||||
/* Restore to previous state */
|
||||
ecs_set_scope(world, old_scope);
|
||||
world->info.name_prefix = old_name_prefix;
|
||||
|
||||
return e;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_import_c(
|
||||
ecs_world_t *world,
|
||||
ecs_module_action_t module,
|
||||
const char *c_name)
|
||||
{
|
||||
char *name = ecs_module_path_from_c(c_name);
|
||||
ecs_entity_t e = ecs_import(world, module, name);
|
||||
ecs_os_free(name);
|
||||
return e;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_import_from_library(
|
||||
ecs_world_t *world,
|
||||
const char *library_name,
|
||||
const char *module_name)
|
||||
{
|
||||
ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
char *import_func = ECS_CONST_CAST(char*, module_name);
|
||||
char *module = ECS_CONST_CAST(char*, module_name);
|
||||
|
||||
if (!ecs_os_has_modules() || !ecs_os_has_dl()) {
|
||||
ecs_err(
|
||||
"library loading not supported, set module_to_dl, dlopen, dlclose "
|
||||
"and dlproc os API callbacks first");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If no module name is specified, try default naming convention for loading
|
||||
* the main module from the library */
|
||||
if (!import_func) {
|
||||
import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import"));
|
||||
ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL);
|
||||
|
||||
const char *ptr;
|
||||
char ch, *bptr = import_func;
|
||||
bool capitalize = true;
|
||||
for (ptr = library_name; (ch = *ptr); ptr ++) {
|
||||
if (ch == '.') {
|
||||
capitalize = true;
|
||||
} else {
|
||||
if (capitalize) {
|
||||
*bptr = flecs_ito(char, toupper(ch));
|
||||
bptr ++;
|
||||
capitalize = false;
|
||||
} else {
|
||||
*bptr = flecs_ito(char, tolower(ch));
|
||||
bptr ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*bptr = '\0';
|
||||
|
||||
module = ecs_os_strdup(import_func);
|
||||
ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL);
|
||||
|
||||
ecs_os_strcat(bptr, "Import");
|
||||
}
|
||||
|
||||
char *library_filename = ecs_os_module_to_dl(library_name);
|
||||
if (!library_filename) {
|
||||
ecs_err("failed to find library file for '%s'", library_name);
|
||||
if (module != module_name) {
|
||||
ecs_os_free(module);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
ecs_trace("found file '%s' for library '%s'",
|
||||
library_filename, library_name);
|
||||
}
|
||||
|
||||
ecs_os_dl_t dl = ecs_os_dlopen(library_filename);
|
||||
if (!dl) {
|
||||
ecs_err("failed to load library '%s' ('%s')",
|
||||
library_name, library_filename);
|
||||
|
||||
ecs_os_free(library_filename);
|
||||
|
||||
if (module != module_name) {
|
||||
ecs_os_free(module);
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
ecs_trace("library '%s' ('%s') loaded",
|
||||
library_name, library_filename);
|
||||
}
|
||||
|
||||
ecs_module_action_t action = (ecs_module_action_t)
|
||||
ecs_os_dlproc(dl, import_func);
|
||||
if (!action) {
|
||||
ecs_err("failed to load import function %s from library %s",
|
||||
import_func, library_name);
|
||||
ecs_os_free(library_filename);
|
||||
ecs_os_dlclose(dl);
|
||||
return 0;
|
||||
} else {
|
||||
ecs_trace("found import function '%s' in library '%s' for module '%s'",
|
||||
import_func, library_name, module);
|
||||
}
|
||||
|
||||
/* Do not free id, as it will be stored as the component identifier */
|
||||
ecs_entity_t result = ecs_import(world, action, module);
|
||||
|
||||
if (import_func != module_name) {
|
||||
ecs_os_free(import_func);
|
||||
}
|
||||
|
||||
if (module != module_name) {
|
||||
ecs_os_free(module);
|
||||
}
|
||||
|
||||
ecs_os_free(library_filename);
|
||||
|
||||
return result;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_module_init(
|
||||
ecs_world_t *world,
|
||||
const char *c_name,
|
||||
const ecs_component_desc_t *desc)
|
||||
{
|
||||
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
|
||||
ecs_entity_t old_scope = ecs_set_scope(world, 0);
|
||||
|
||||
ecs_entity_t e = desc->entity;
|
||||
if (!e) {
|
||||
char *module_path = ecs_module_path_from_c(c_name);
|
||||
e = ecs_new_from_fullpath(world, module_path);
|
||||
ecs_set_symbol(world, e, module_path);
|
||||
ecs_os_free(module_path);
|
||||
} else if (!ecs_exists(world, e)) {
|
||||
char *module_path = ecs_module_path_from_c(c_name);
|
||||
ecs_ensure(world, e);
|
||||
ecs_add_fullpath(world, e, module_path);
|
||||
ecs_set_symbol(world, e, module_path);
|
||||
ecs_os_free(module_path);
|
||||
}
|
||||
|
||||
ecs_add_id(world, e, EcsModule);
|
||||
|
||||
ecs_component_desc_t private_desc = *desc;
|
||||
private_desc.entity = e;
|
||||
|
||||
if (desc->type.size) {
|
||||
ecs_entity_t result = ecs_component_init(world, &private_desc);
|
||||
ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL);
|
||||
(void)result;
|
||||
}
|
||||
|
||||
ecs_set_scope(world, old_scope);
|
||||
|
||||
return e;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
367
engine/libs/flecs/src/addons/monitor.c
Normal file
367
engine/libs/flecs/src/addons/monitor.c
Normal file
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* @file addons/monitor.c
|
||||
* @brief Monitor addon.
|
||||
*/
|
||||
|
||||
#include "flecs.h"
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_MONITOR
|
||||
|
||||
ECS_COMPONENT_DECLARE(FlecsMonitor);
|
||||
ECS_COMPONENT_DECLARE(EcsWorldStats);
|
||||
ECS_COMPONENT_DECLARE(EcsWorldSummary);
|
||||
ECS_COMPONENT_DECLARE(EcsPipelineStats);
|
||||
|
||||
ecs_entity_t EcsPeriod1s = 0;
|
||||
ecs_entity_t EcsPeriod1m = 0;
|
||||
ecs_entity_t EcsPeriod1h = 0;
|
||||
ecs_entity_t EcsPeriod1d = 0;
|
||||
ecs_entity_t EcsPeriod1w = 0;
|
||||
|
||||
static int32_t flecs_day_interval_count = 24;
|
||||
static int32_t flecs_week_interval_count = 168;
|
||||
|
||||
static
|
||||
ECS_COPY(EcsPipelineStats, dst, src, {
|
||||
(void)dst;
|
||||
(void)src;
|
||||
ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component");
|
||||
})
|
||||
|
||||
static
|
||||
ECS_MOVE(EcsPipelineStats, dst, src, {
|
||||
ecs_os_memcpy_t(dst, src, EcsPipelineStats);
|
||||
ecs_os_zeromem(src);
|
||||
})
|
||||
|
||||
static
|
||||
ECS_DTOR(EcsPipelineStats, ptr, {
|
||||
ecs_pipeline_stats_fini(&ptr->stats);
|
||||
})
|
||||
|
||||
static
|
||||
void UpdateWorldSummary(ecs_iter_t *it) {
|
||||
EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 1);
|
||||
|
||||
const ecs_world_info_t *info = ecs_get_world_info(it->world);
|
||||
|
||||
int32_t i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
summary[i].target_fps = (double)info->target_fps;
|
||||
|
||||
summary[i].frame_time_last = (double)info->frame_time_total - summary[i].frame_time_total;
|
||||
summary[i].system_time_last = (double)info->system_time_total - summary[i].system_time_total;
|
||||
summary[i].merge_time_last = (double)info->merge_time_total - summary[i].merge_time_total;
|
||||
|
||||
summary[i].frame_time_total = (double)info->frame_time_total;
|
||||
summary[i].system_time_total = (double)info->system_time_total;
|
||||
summary[i].merge_time_total = (double)info->merge_time_total;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void MonitorStats(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
|
||||
EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 1);
|
||||
ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1));
|
||||
void *stats = ECS_OFFSET_T(hdr, EcsStatsHeader);
|
||||
|
||||
ecs_ftime_t elapsed = hdr->elapsed;
|
||||
hdr->elapsed += it->delta_time;
|
||||
|
||||
int32_t t_last = (int32_t)(elapsed * 60);
|
||||
int32_t t_next = (int32_t)(hdr->elapsed * 60);
|
||||
int32_t i, dif = t_last - t_next;
|
||||
|
||||
ecs_world_stats_t last_world = {0};
|
||||
ecs_pipeline_stats_t last_pipeline = {0};
|
||||
void *last = NULL;
|
||||
|
||||
if (!dif) {
|
||||
/* Copy last value so we can pass it to reduce_last */
|
||||
if (kind == ecs_id(EcsWorldStats)) {
|
||||
last = &last_world;
|
||||
ecs_world_stats_copy_last(&last_world, stats);
|
||||
} else if (kind == ecs_id(EcsPipelineStats)) {
|
||||
last = &last_pipeline;
|
||||
ecs_pipeline_stats_copy_last(&last_pipeline, stats);
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == ecs_id(EcsWorldStats)) {
|
||||
ecs_world_stats_get(world, stats);
|
||||
} else if (kind == ecs_id(EcsPipelineStats)) {
|
||||
ecs_pipeline_stats_get(world, ecs_get_pipeline(world), stats);
|
||||
}
|
||||
|
||||
if (!dif) {
|
||||
/* Still in same interval, combine with last measurement */
|
||||
hdr->reduce_count ++;
|
||||
if (kind == ecs_id(EcsWorldStats)) {
|
||||
ecs_world_stats_reduce_last(stats, last, hdr->reduce_count);
|
||||
} else if (kind == ecs_id(EcsPipelineStats)) {
|
||||
ecs_pipeline_stats_reduce_last(stats, last, hdr->reduce_count);
|
||||
}
|
||||
} else if (dif > 1) {
|
||||
/* More than 16ms has passed, backfill */
|
||||
for (i = 1; i < dif; i ++) {
|
||||
if (kind == ecs_id(EcsWorldStats)) {
|
||||
ecs_world_stats_repeat_last(stats);
|
||||
} else if (kind == ecs_id(EcsPipelineStats)) {
|
||||
ecs_world_stats_repeat_last(stats);
|
||||
}
|
||||
}
|
||||
hdr->reduce_count = 0;
|
||||
}
|
||||
|
||||
if (last && kind == ecs_id(EcsPipelineStats)) {
|
||||
ecs_pipeline_stats_fini(last);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void ReduceStats(ecs_iter_t *it) {
|
||||
void *dst = ecs_field_w_size(it, 0, 1);
|
||||
void *src = ecs_field_w_size(it, 0, 2);
|
||||
|
||||
ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1));
|
||||
|
||||
dst = ECS_OFFSET_T(dst, EcsStatsHeader);
|
||||
src = ECS_OFFSET_T(src, EcsStatsHeader);
|
||||
|
||||
if (kind == ecs_id(EcsWorldStats)) {
|
||||
ecs_world_stats_reduce(dst, src);
|
||||
} else if (kind == ecs_id(EcsPipelineStats)) {
|
||||
ecs_pipeline_stats_reduce(dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void AggregateStats(ecs_iter_t *it) {
|
||||
int32_t interval = *(int32_t*)it->ctx;
|
||||
|
||||
EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 1);
|
||||
EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 2);
|
||||
|
||||
void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader);
|
||||
void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader);
|
||||
|
||||
ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1));
|
||||
|
||||
ecs_world_stats_t last_world = {0};
|
||||
ecs_pipeline_stats_t last_pipeline = {0};
|
||||
void *last = NULL;
|
||||
|
||||
if (dst_hdr->reduce_count != 0) {
|
||||
/* Copy last value so we can pass it to reduce_last */
|
||||
if (kind == ecs_id(EcsWorldStats)) {
|
||||
last_world.t = 0;
|
||||
ecs_world_stats_copy_last(&last_world, dst);
|
||||
last = &last_world;
|
||||
} else if (kind == ecs_id(EcsPipelineStats)) {
|
||||
last_pipeline.t = 0;
|
||||
ecs_pipeline_stats_copy_last(&last_pipeline, dst);
|
||||
last = &last_pipeline;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reduce from minutes to the current day */
|
||||
if (kind == ecs_id(EcsWorldStats)) {
|
||||
ecs_world_stats_reduce(dst, src);
|
||||
} else if (kind == ecs_id(EcsPipelineStats)) {
|
||||
ecs_pipeline_stats_reduce(dst, src);
|
||||
}
|
||||
|
||||
if (dst_hdr->reduce_count != 0) {
|
||||
if (kind == ecs_id(EcsWorldStats)) {
|
||||
ecs_world_stats_reduce_last(dst, last, dst_hdr->reduce_count);
|
||||
} else if (kind == ecs_id(EcsPipelineStats)) {
|
||||
ecs_pipeline_stats_reduce_last(dst, last, dst_hdr->reduce_count);
|
||||
}
|
||||
}
|
||||
|
||||
/* A day has 60 24 minute intervals */
|
||||
dst_hdr->reduce_count ++;
|
||||
if (dst_hdr->reduce_count >= interval) {
|
||||
dst_hdr->reduce_count = 0;
|
||||
}
|
||||
|
||||
if (last && kind == ecs_id(EcsPipelineStats)) {
|
||||
ecs_pipeline_stats_fini(last);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_stats_monitor_import(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t kind,
|
||||
size_t size)
|
||||
{
|
||||
ecs_entity_t prev = ecs_set_scope(world, kind);
|
||||
|
||||
// Called each frame, collects 60 measurements per second
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, { .name = "Monitor1s", .add = {ecs_dependson(EcsPreFrame)} }),
|
||||
.query.filter.terms = {{
|
||||
.id = ecs_pair(kind, EcsPeriod1s),
|
||||
.src.id = EcsWorld
|
||||
}},
|
||||
.callback = MonitorStats
|
||||
});
|
||||
|
||||
// Called each second, reduces into 60 measurements per minute
|
||||
ecs_entity_t mw1m = ecs_system(world, {
|
||||
.entity = ecs_entity(world, { .name = "Monitor1m", .add = {ecs_dependson(EcsPreFrame)} }),
|
||||
.query.filter.terms = {{
|
||||
.id = ecs_pair(kind, EcsPeriod1m),
|
||||
.src.id = EcsWorld
|
||||
}, {
|
||||
.id = ecs_pair(kind, EcsPeriod1s),
|
||||
.src.id = EcsWorld
|
||||
}},
|
||||
.callback = ReduceStats,
|
||||
.interval = 1.0
|
||||
});
|
||||
|
||||
// Called each minute, reduces into 60 measurements per hour
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, { .name = "Monitor1h", .add = {ecs_dependson(EcsPreFrame)} }),
|
||||
.query.filter.terms = {{
|
||||
.id = ecs_pair(kind, EcsPeriod1h),
|
||||
.src.id = EcsWorld
|
||||
}, {
|
||||
.id = ecs_pair(kind, EcsPeriod1m),
|
||||
.src.id = EcsWorld
|
||||
}},
|
||||
.callback = ReduceStats,
|
||||
.rate = 60,
|
||||
.tick_source = mw1m
|
||||
});
|
||||
|
||||
// Called each minute, reduces into 60 measurements per day
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, { .name = "Monitor1d", .add = {ecs_dependson(EcsPreFrame)} }),
|
||||
.query.filter.terms = {{
|
||||
.id = ecs_pair(kind, EcsPeriod1d),
|
||||
.src.id = EcsWorld
|
||||
}, {
|
||||
.id = ecs_pair(kind, EcsPeriod1m),
|
||||
.src.id = EcsWorld
|
||||
}},
|
||||
.callback = AggregateStats,
|
||||
.rate = 60,
|
||||
.tick_source = mw1m,
|
||||
.ctx = &flecs_day_interval_count
|
||||
});
|
||||
|
||||
// Called each hour, reduces into 60 measurements per week
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, { .name = "Monitor1w", .add = {ecs_dependson(EcsPreFrame)} }),
|
||||
.query.filter.terms = {{
|
||||
.id = ecs_pair(kind, EcsPeriod1w),
|
||||
.src.id = EcsWorld
|
||||
}, {
|
||||
.id = ecs_pair(kind, EcsPeriod1h),
|
||||
.src.id = EcsWorld
|
||||
}},
|
||||
.callback = AggregateStats,
|
||||
.rate = 60,
|
||||
.tick_source = mw1m,
|
||||
.ctx = &flecs_week_interval_count
|
||||
});
|
||||
|
||||
ecs_set_scope(world, prev);
|
||||
|
||||
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1s), size, NULL);
|
||||
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1m), size, NULL);
|
||||
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1h), size, NULL);
|
||||
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1d), size, NULL);
|
||||
ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1w), size, NULL);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_world_monitor_import(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_COMPONENT_DEFINE(world, EcsWorldStats);
|
||||
|
||||
flecs_stats_monitor_import(world, ecs_id(EcsWorldStats),
|
||||
sizeof(EcsWorldStats));
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_pipeline_monitor_import(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_COMPONENT_DEFINE(world, EcsPipelineStats);
|
||||
|
||||
ecs_set_hooks(world, EcsPipelineStats, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.copy = ecs_copy(EcsPipelineStats),
|
||||
.move = ecs_move(EcsPipelineStats),
|
||||
.dtor = ecs_dtor(EcsPipelineStats)
|
||||
});
|
||||
|
||||
flecs_stats_monitor_import(world, ecs_id(EcsPipelineStats),
|
||||
sizeof(EcsPipelineStats));
|
||||
}
|
||||
|
||||
void FlecsMonitorImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE_DEFINE(world, FlecsMonitor);
|
||||
ECS_IMPORT(world, FlecsPipeline);
|
||||
ECS_IMPORT(world, FlecsTimer);
|
||||
#ifdef FLECS_META
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
#endif
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
EcsPeriod1s = ecs_new_entity(world, "EcsPeriod1s");
|
||||
EcsPeriod1m = ecs_new_entity(world, "EcsPeriod1m");
|
||||
EcsPeriod1h = ecs_new_entity(world, "EcsPeriod1h");
|
||||
EcsPeriod1d = ecs_new_entity(world, "EcsPeriod1d");
|
||||
EcsPeriod1w = ecs_new_entity(world, "EcsPeriod1w");
|
||||
|
||||
ECS_COMPONENT_DEFINE(world, EcsWorldSummary);
|
||||
|
||||
#if defined(FLECS_META) && defined(FLECS_UNITS)
|
||||
ecs_struct(world, {
|
||||
.entity = ecs_id(EcsWorldSummary),
|
||||
.members = {
|
||||
{ .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz },
|
||||
{ .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds },
|
||||
{ .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds },
|
||||
{ .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds },
|
||||
{ .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds },
|
||||
{ .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds },
|
||||
{ .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, {
|
||||
.name = "UpdateWorldSummary",
|
||||
.add = {ecs_dependson(EcsPreFrame)}
|
||||
}),
|
||||
.query.filter.terms[0] = { .id = ecs_id(EcsWorldSummary) },
|
||||
.callback = UpdateWorldSummary
|
||||
});
|
||||
|
||||
ECS_SYSTEM(world, UpdateWorldSummary, EcsPreFrame, WorldSummary);
|
||||
ecs_set(world, EcsWorld, EcsWorldSummary, {0});
|
||||
|
||||
flecs_world_monitor_import(world);
|
||||
flecs_pipeline_monitor_import(world);
|
||||
|
||||
if (ecs_os_has_time()) {
|
||||
ecs_measure_frame_time(world, true);
|
||||
ecs_measure_system_time(world, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
14
engine/libs/flecs/src/addons/os_api_impl/os_api_impl.c
Normal file
14
engine/libs/flecs/src/addons/os_api_impl/os_api_impl.c
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @file addons/os_api_impl/os_api_impl.c
|
||||
* @brief Builtin implementation for OS API.
|
||||
*/
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
#ifdef FLECS_OS_API_IMPL
|
||||
#ifdef ECS_TARGET_WINDOWS
|
||||
#include "windows_impl.inl"
|
||||
#else
|
||||
#include "posix_impl.inl"
|
||||
#endif
|
||||
#endif
|
||||
330
engine/libs/flecs/src/addons/os_api_impl/posix_impl.inl
Normal file
330
engine/libs/flecs/src/addons/os_api_impl/posix_impl.inl
Normal file
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* @file addons/os_api_impl/posix_impl.inl
|
||||
* @brief Builtin POSIX implementation for OS API.
|
||||
*/
|
||||
|
||||
#include "pthread.h"
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#include <mach/mach_time.h>
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
#include <emscripten.h>
|
||||
#else
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
/* This mutex is used to emulate atomic operations when the gnu builtins are
|
||||
* not supported. This is probably not very fast but if the compiler doesn't
|
||||
* support the gnu built-ins, then speed is probably not a priority. */
|
||||
#ifndef __GNUC__
|
||||
static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
#endif
|
||||
|
||||
static
|
||||
ecs_os_thread_t posix_thread_new(
|
||||
ecs_os_thread_callback_t callback,
|
||||
void *arg)
|
||||
{
|
||||
pthread_t *thread = ecs_os_malloc(sizeof(pthread_t));
|
||||
|
||||
if (pthread_create (thread, NULL, callback, arg) != 0) {
|
||||
ecs_os_abort();
|
||||
}
|
||||
|
||||
return (ecs_os_thread_t)(uintptr_t)thread;
|
||||
}
|
||||
|
||||
static
|
||||
void* posix_thread_join(
|
||||
ecs_os_thread_t thread)
|
||||
{
|
||||
void *arg;
|
||||
pthread_t *thr = (pthread_t*)(uintptr_t)thread;
|
||||
pthread_join(*thr, &arg);
|
||||
ecs_os_free(thr);
|
||||
return arg;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_thread_id_t posix_thread_self(void)
|
||||
{
|
||||
return (ecs_os_thread_id_t)pthread_self();
|
||||
}
|
||||
|
||||
static
|
||||
int32_t posix_ainc(
|
||||
int32_t *count)
|
||||
{
|
||||
int value;
|
||||
#ifdef __GNUC__
|
||||
value = __sync_add_and_fetch (count, 1);
|
||||
return value;
|
||||
#else
|
||||
if (pthread_mutex_lock(&atomic_mutex)) {
|
||||
abort();
|
||||
}
|
||||
value = (*count) += 1;
|
||||
if (pthread_mutex_unlock(&atomic_mutex)) {
|
||||
abort();
|
||||
}
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
int32_t posix_adec(
|
||||
int32_t *count)
|
||||
{
|
||||
int32_t value;
|
||||
#ifdef __GNUC__
|
||||
value = __sync_sub_and_fetch (count, 1);
|
||||
return value;
|
||||
#else
|
||||
if (pthread_mutex_lock(&atomic_mutex)) {
|
||||
abort();
|
||||
}
|
||||
value = (*count) -= 1;
|
||||
if (pthread_mutex_unlock(&atomic_mutex)) {
|
||||
abort();
|
||||
}
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
int64_t posix_lainc(
|
||||
int64_t *count)
|
||||
{
|
||||
int64_t value;
|
||||
#ifdef __GNUC__
|
||||
value = __sync_add_and_fetch (count, 1);
|
||||
return value;
|
||||
#else
|
||||
if (pthread_mutex_lock(&atomic_mutex)) {
|
||||
abort();
|
||||
}
|
||||
value = (*count) += 1;
|
||||
if (pthread_mutex_unlock(&atomic_mutex)) {
|
||||
abort();
|
||||
}
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
int64_t posix_ladec(
|
||||
int64_t *count)
|
||||
{
|
||||
int64_t value;
|
||||
#ifdef __GNUC__
|
||||
value = __sync_sub_and_fetch (count, 1);
|
||||
return value;
|
||||
#else
|
||||
if (pthread_mutex_lock(&atomic_mutex)) {
|
||||
abort();
|
||||
}
|
||||
value = (*count) -= 1;
|
||||
if (pthread_mutex_unlock(&atomic_mutex)) {
|
||||
abort();
|
||||
}
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_mutex_t posix_mutex_new(void) {
|
||||
pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t));
|
||||
if (pthread_mutex_init(mutex, NULL)) {
|
||||
abort();
|
||||
}
|
||||
return (ecs_os_mutex_t)(uintptr_t)mutex;
|
||||
}
|
||||
|
||||
static
|
||||
void posix_mutex_free(
|
||||
ecs_os_mutex_t m)
|
||||
{
|
||||
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m;
|
||||
pthread_mutex_destroy(mutex);
|
||||
ecs_os_free(mutex);
|
||||
}
|
||||
|
||||
static
|
||||
void posix_mutex_lock(
|
||||
ecs_os_mutex_t m)
|
||||
{
|
||||
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m;
|
||||
if (pthread_mutex_lock(mutex)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void posix_mutex_unlock(
|
||||
ecs_os_mutex_t m)
|
||||
{
|
||||
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m;
|
||||
if (pthread_mutex_unlock(mutex)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_cond_t posix_cond_new(void) {
|
||||
pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t));
|
||||
if (pthread_cond_init(cond, NULL)) {
|
||||
abort();
|
||||
}
|
||||
return (ecs_os_cond_t)(uintptr_t)cond;
|
||||
}
|
||||
|
||||
static
|
||||
void posix_cond_free(
|
||||
ecs_os_cond_t c)
|
||||
{
|
||||
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c;
|
||||
if (pthread_cond_destroy(cond)) {
|
||||
abort();
|
||||
}
|
||||
ecs_os_free(cond);
|
||||
}
|
||||
|
||||
static
|
||||
void posix_cond_signal(
|
||||
ecs_os_cond_t c)
|
||||
{
|
||||
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c;
|
||||
if (pthread_cond_signal(cond)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void posix_cond_broadcast(
|
||||
ecs_os_cond_t c)
|
||||
{
|
||||
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c;
|
||||
if (pthread_cond_broadcast(cond)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void posix_cond_wait(
|
||||
ecs_os_cond_t c,
|
||||
ecs_os_mutex_t m)
|
||||
{
|
||||
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c;
|
||||
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m;
|
||||
if (pthread_cond_wait(cond, mutex)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static bool posix_time_initialized;
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
static mach_timebase_info_data_t posix_osx_timebase;
|
||||
static uint64_t posix_time_start;
|
||||
#else
|
||||
static uint64_t posix_time_start;
|
||||
#endif
|
||||
|
||||
static
|
||||
void posix_time_setup(void) {
|
||||
if (posix_time_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
posix_time_initialized = true;
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
mach_timebase_info(&posix_osx_timebase);
|
||||
posix_time_start = mach_absolute_time();
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
void posix_sleep(
|
||||
int32_t sec,
|
||||
int32_t nanosec)
|
||||
{
|
||||
struct timespec sleepTime;
|
||||
ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
sleepTime.tv_sec = sec;
|
||||
sleepTime.tv_nsec = nanosec;
|
||||
if (nanosleep(&sleepTime, NULL)) {
|
||||
ecs_err("nanosleep failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* prevent 64-bit overflow when computing relative timestamp
|
||||
see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3
|
||||
*/
|
||||
#if defined(ECS_TARGET_DARWIN)
|
||||
static
|
||||
int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) {
|
||||
int64_t q = value / denom;
|
||||
int64_t r = value % denom;
|
||||
return q * numer + r * numer / denom;
|
||||
}
|
||||
#endif
|
||||
|
||||
static
|
||||
uint64_t posix_time_now(void) {
|
||||
ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
uint64_t now;
|
||||
|
||||
#if defined(ECS_TARGET_DARWIN)
|
||||
now = (uint64_t) posix_int64_muldiv(
|
||||
(int64_t)mach_absolute_time(),
|
||||
(int64_t)posix_osx_timebase.numer,
|
||||
(int64_t)posix_osx_timebase.denom);
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
now = (long long)(emscripten_get_now() * 1000.0 * 1000);
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec);
|
||||
#endif
|
||||
|
||||
return now;
|
||||
}
|
||||
|
||||
void ecs_set_os_api_impl(void) {
|
||||
ecs_os_set_api_defaults();
|
||||
|
||||
ecs_os_api_t api = ecs_os_api;
|
||||
|
||||
api.thread_new_ = posix_thread_new;
|
||||
api.thread_join_ = posix_thread_join;
|
||||
api.thread_self_ = posix_thread_self;
|
||||
api.task_new_ = posix_thread_new;
|
||||
api.task_join_ = posix_thread_join;
|
||||
api.ainc_ = posix_ainc;
|
||||
api.adec_ = posix_adec;
|
||||
api.lainc_ = posix_lainc;
|
||||
api.ladec_ = posix_ladec;
|
||||
api.mutex_new_ = posix_mutex_new;
|
||||
api.mutex_free_ = posix_mutex_free;
|
||||
api.mutex_lock_ = posix_mutex_lock;
|
||||
api.mutex_unlock_ = posix_mutex_unlock;
|
||||
api.cond_new_ = posix_cond_new;
|
||||
api.cond_free_ = posix_cond_free;
|
||||
api.cond_signal_ = posix_cond_signal;
|
||||
api.cond_broadcast_ = posix_cond_broadcast;
|
||||
api.cond_wait_ = posix_cond_wait;
|
||||
api.sleep_ = posix_sleep;
|
||||
api.now_ = posix_time_now;
|
||||
|
||||
posix_time_setup();
|
||||
|
||||
ecs_os_set_api(&api);
|
||||
}
|
||||
290
engine/libs/flecs/src/addons/os_api_impl/windows_impl.inl
Normal file
290
engine/libs/flecs/src/addons/os_api_impl/windows_impl.inl
Normal file
@@ -0,0 +1,290 @@
|
||||
/**
|
||||
* @file addons/os_api_impl/posix_impl.inl
|
||||
* @brief Builtin Windows implementation for OS API.
|
||||
*/
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
|
||||
typedef struct ecs_win_thread_t {
|
||||
HANDLE thread;
|
||||
ecs_os_thread_callback_t callback;
|
||||
void *arg;
|
||||
} ecs_win_thread_t;
|
||||
|
||||
static
|
||||
DWORD flecs_win_thread(void *ptr) {
|
||||
ecs_win_thread_t *thread = ptr;
|
||||
thread->callback(thread->arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_thread_t win_thread_new(
|
||||
ecs_os_thread_callback_t callback,
|
||||
void *arg)
|
||||
{
|
||||
ecs_win_thread_t *thread = ecs_os_malloc_t(ecs_win_thread_t);
|
||||
thread->arg= arg;
|
||||
thread->callback = callback;
|
||||
thread->thread = CreateThread(
|
||||
NULL, 0, (LPTHREAD_START_ROUTINE)flecs_win_thread, thread, 0, NULL);
|
||||
return (ecs_os_thread_t)(uintptr_t)thread;
|
||||
}
|
||||
|
||||
static
|
||||
void* win_thread_join(
|
||||
ecs_os_thread_t thr)
|
||||
{
|
||||
ecs_win_thread_t *thread = (ecs_win_thread_t*)(uintptr_t)thr;
|
||||
DWORD r = WaitForSingleObject(thread->thread, INFINITE);
|
||||
if (r == WAIT_FAILED) {
|
||||
ecs_err("win_thread_join: WaitForSingleObject failed");
|
||||
}
|
||||
ecs_os_free(thread);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_thread_id_t win_thread_self(void)
|
||||
{
|
||||
return (ecs_os_thread_id_t)GetCurrentThreadId();
|
||||
}
|
||||
|
||||
static
|
||||
int32_t win_ainc(
|
||||
int32_t *count)
|
||||
{
|
||||
return InterlockedIncrement((volatile long*)count);
|
||||
}
|
||||
|
||||
static
|
||||
int32_t win_adec(
|
||||
int32_t *count)
|
||||
{
|
||||
return InterlockedDecrement((volatile long*)count);
|
||||
}
|
||||
|
||||
static
|
||||
int64_t win_lainc(
|
||||
int64_t *count)
|
||||
{
|
||||
return InterlockedIncrement64(count);
|
||||
}
|
||||
|
||||
static
|
||||
int64_t win_ladec(
|
||||
int64_t *count)
|
||||
{
|
||||
return InterlockedDecrement64(count);
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_mutex_t win_mutex_new(void) {
|
||||
CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION);
|
||||
InitializeCriticalSection(mutex);
|
||||
return (ecs_os_mutex_t)(uintptr_t)mutex;
|
||||
}
|
||||
|
||||
static
|
||||
void win_mutex_free(
|
||||
ecs_os_mutex_t m)
|
||||
{
|
||||
CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m;
|
||||
DeleteCriticalSection(mutex);
|
||||
ecs_os_free(mutex);
|
||||
}
|
||||
|
||||
static
|
||||
void win_mutex_lock(
|
||||
ecs_os_mutex_t m)
|
||||
{
|
||||
CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m;
|
||||
EnterCriticalSection(mutex);
|
||||
}
|
||||
|
||||
static
|
||||
void win_mutex_unlock(
|
||||
ecs_os_mutex_t m)
|
||||
{
|
||||
CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m;
|
||||
LeaveCriticalSection(mutex);
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_cond_t win_cond_new(void) {
|
||||
CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE);
|
||||
InitializeConditionVariable(cond);
|
||||
return (ecs_os_cond_t)(uintptr_t)cond;
|
||||
}
|
||||
|
||||
static
|
||||
void win_cond_free(
|
||||
ecs_os_cond_t c)
|
||||
{
|
||||
(void)c;
|
||||
}
|
||||
|
||||
static
|
||||
void win_cond_signal(
|
||||
ecs_os_cond_t c)
|
||||
{
|
||||
CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c;
|
||||
WakeConditionVariable(cond);
|
||||
}
|
||||
|
||||
static
|
||||
void win_cond_broadcast(
|
||||
ecs_os_cond_t c)
|
||||
{
|
||||
CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c;
|
||||
WakeAllConditionVariable(cond);
|
||||
}
|
||||
|
||||
static
|
||||
void win_cond_wait(
|
||||
ecs_os_cond_t c,
|
||||
ecs_os_mutex_t m)
|
||||
{
|
||||
CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m;
|
||||
CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c;
|
||||
SleepConditionVariableCS(cond, mutex, INFINITE);
|
||||
}
|
||||
|
||||
static bool win_time_initialized;
|
||||
static double win_time_freq;
|
||||
static LARGE_INTEGER win_time_start;
|
||||
static ULONG win_current_resolution;
|
||||
|
||||
static
|
||||
void win_time_setup(void) {
|
||||
if ( win_time_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
win_time_initialized = true;
|
||||
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
QueryPerformanceCounter(&win_time_start);
|
||||
win_time_freq = (double)freq.QuadPart / 1000000000.0;
|
||||
}
|
||||
|
||||
static
|
||||
void win_sleep(
|
||||
int32_t sec,
|
||||
int32_t nanosec)
|
||||
{
|
||||
HANDLE timer;
|
||||
LARGE_INTEGER ft;
|
||||
|
||||
ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100);
|
||||
|
||||
timer = CreateWaitableTimer(NULL, TRUE, NULL);
|
||||
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
CloseHandle(timer);
|
||||
}
|
||||
|
||||
static
|
||||
void win_enable_high_timer_resolution(bool enable)
|
||||
{
|
||||
HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll"));
|
||||
if (!hntdll) {
|
||||
return;
|
||||
}
|
||||
|
||||
union {
|
||||
LONG (__stdcall *f)(
|
||||
ULONG desired, BOOLEAN set, ULONG * current);
|
||||
FARPROC p;
|
||||
} func;
|
||||
|
||||
func.p = GetProcAddress(hntdll, "NtSetTimerResolution");
|
||||
if(!func.p) {
|
||||
return;
|
||||
}
|
||||
|
||||
ULONG current, resolution = 10000; /* 1 ms */
|
||||
|
||||
if (!enable && win_current_resolution) {
|
||||
func.f(win_current_resolution, 0, ¤t);
|
||||
win_current_resolution = 0;
|
||||
return;
|
||||
} else if (!enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolution == win_current_resolution) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (win_current_resolution) {
|
||||
func.f(win_current_resolution, 0, ¤t);
|
||||
}
|
||||
|
||||
if (func.f(resolution, 1, ¤t)) {
|
||||
/* Try setting a lower resolution */
|
||||
resolution *= 2;
|
||||
if(func.f(resolution, 1, ¤t)) return;
|
||||
}
|
||||
|
||||
win_current_resolution = resolution;
|
||||
}
|
||||
|
||||
static
|
||||
uint64_t win_time_now(void) {
|
||||
uint64_t now;
|
||||
|
||||
LARGE_INTEGER qpc_t;
|
||||
QueryPerformanceCounter(&qpc_t);
|
||||
now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq);
|
||||
|
||||
return now;
|
||||
}
|
||||
|
||||
static
|
||||
void win_fini(void) {
|
||||
if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) {
|
||||
win_enable_high_timer_resolution(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_set_os_api_impl(void) {
|
||||
ecs_os_set_api_defaults();
|
||||
|
||||
ecs_os_api_t api = ecs_os_api;
|
||||
|
||||
api.thread_new_ = win_thread_new;
|
||||
api.thread_join_ = win_thread_join;
|
||||
api.thread_self_ = win_thread_self;
|
||||
api.task_new_ = win_thread_new;
|
||||
api.task_join_ = win_thread_join;
|
||||
api.ainc_ = win_ainc;
|
||||
api.adec_ = win_adec;
|
||||
api.lainc_ = win_lainc;
|
||||
api.ladec_ = win_ladec;
|
||||
api.mutex_new_ = win_mutex_new;
|
||||
api.mutex_free_ = win_mutex_free;
|
||||
api.mutex_lock_ = win_mutex_lock;
|
||||
api.mutex_unlock_ = win_mutex_unlock;
|
||||
api.cond_new_ = win_cond_new;
|
||||
api.cond_free_ = win_cond_free;
|
||||
api.cond_signal_ = win_cond_signal;
|
||||
api.cond_broadcast_ = win_cond_broadcast;
|
||||
api.cond_wait_ = win_cond_wait;
|
||||
api.sleep_ = win_sleep;
|
||||
api.now_ = win_time_now;
|
||||
api.fini_ = win_fini;
|
||||
|
||||
win_time_setup();
|
||||
|
||||
if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) {
|
||||
win_enable_high_timer_resolution(true);
|
||||
}
|
||||
|
||||
ecs_os_set_api(&api);
|
||||
}
|
||||
1126
engine/libs/flecs/src/addons/parser.c
Normal file
1126
engine/libs/flecs/src/addons/parser.c
Normal file
File diff suppressed because it is too large
Load Diff
935
engine/libs/flecs/src/addons/pipeline/pipeline.c
Normal file
935
engine/libs/flecs/src/addons/pipeline/pipeline.c
Normal file
@@ -0,0 +1,935 @@
|
||||
/**
|
||||
* @file addons/ipeline/pipeline.c
|
||||
* @brief Functions for building and running pipelines.
|
||||
*/
|
||||
|
||||
#include "flecs.h"
|
||||
#include "../system/system.h"
|
||||
|
||||
#ifdef FLECS_PIPELINE
|
||||
#include "pipeline.h"
|
||||
|
||||
static void flecs_pipeline_free(
|
||||
ecs_pipeline_state_t *p)
|
||||
{
|
||||
if (p) {
|
||||
ecs_world_t *world = p->query->filter.world;
|
||||
ecs_allocator_t *a = &world->allocator;
|
||||
ecs_vec_fini_t(a, &p->ops, ecs_pipeline_op_t);
|
||||
ecs_vec_fini_t(a, &p->systems, ecs_entity_t);
|
||||
ecs_os_free(p->iters);
|
||||
ecs_query_fini(p->query);
|
||||
ecs_os_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
static ECS_MOVE(EcsPipeline, dst, src, {
|
||||
flecs_pipeline_free(dst->state);
|
||||
dst->state = src->state;
|
||||
src->state = NULL;
|
||||
})
|
||||
|
||||
static ECS_DTOR(EcsPipeline, ptr, {
|
||||
flecs_pipeline_free(ptr->state);
|
||||
})
|
||||
|
||||
typedef enum ecs_write_kind_t {
|
||||
WriteStateNone = 0,
|
||||
WriteStateToStage,
|
||||
} ecs_write_kind_t;
|
||||
|
||||
typedef struct ecs_write_state_t {
|
||||
bool write_barrier;
|
||||
ecs_map_t ids;
|
||||
ecs_map_t wildcard_ids;
|
||||
} ecs_write_state_t;
|
||||
|
||||
static
|
||||
ecs_write_kind_t flecs_pipeline_get_write_state(
|
||||
ecs_write_state_t *write_state,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_write_kind_t result = WriteStateNone;
|
||||
|
||||
if (write_state->write_barrier) {
|
||||
/* Any component could have been written */
|
||||
return WriteStateToStage;
|
||||
}
|
||||
|
||||
if (id == EcsWildcard) {
|
||||
/* Using a wildcard for id indicates read barrier. Return true if any
|
||||
* components could have been staged */
|
||||
if (ecs_map_count(&write_state->ids) ||
|
||||
ecs_map_count(&write_state->wildcard_ids))
|
||||
{
|
||||
return WriteStateToStage;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ecs_id_is_wildcard(id)) {
|
||||
if (ecs_map_get(&write_state->ids, id)) {
|
||||
result = WriteStateToStage;
|
||||
}
|
||||
} else {
|
||||
ecs_map_iter_t it = ecs_map_iter(&write_state->ids);
|
||||
while (ecs_map_next(&it)) {
|
||||
if (ecs_id_match(ecs_map_key(&it), id)) {
|
||||
return WriteStateToStage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ecs_map_count(&write_state->wildcard_ids)) {
|
||||
ecs_map_iter_t it = ecs_map_iter(&write_state->wildcard_ids);
|
||||
while (ecs_map_next(&it)) {
|
||||
if (ecs_id_match(id, ecs_map_key(&it))) {
|
||||
return WriteStateToStage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_pipeline_set_write_state(
|
||||
ecs_write_state_t *write_state,
|
||||
ecs_id_t id)
|
||||
{
|
||||
if (id == EcsWildcard) {
|
||||
/* If writing to wildcard, flag all components as written */
|
||||
write_state->write_barrier = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_map_t *ids;
|
||||
if (ecs_id_is_wildcard(id)) {
|
||||
ids = &write_state->wildcard_ids;
|
||||
} else {
|
||||
ids = &write_state->ids;
|
||||
}
|
||||
|
||||
ecs_map_ensure(ids, id)[0] = true;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_pipeline_reset_write_state(
|
||||
ecs_write_state_t *write_state)
|
||||
{
|
||||
ecs_map_clear(&write_state->ids);
|
||||
ecs_map_clear(&write_state->wildcard_ids);
|
||||
write_state->write_barrier = false;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_pipeline_check_term(
|
||||
ecs_world_t *world,
|
||||
ecs_term_t *term,
|
||||
bool is_active,
|
||||
ecs_write_state_t *write_state)
|
||||
{
|
||||
(void)world;
|
||||
|
||||
ecs_term_id_t *src = &term->src;
|
||||
if (src->flags & EcsInOutNone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_id_t id = term->id;
|
||||
ecs_oper_kind_t oper = term->oper;
|
||||
ecs_inout_kind_t inout = term->inout;
|
||||
bool from_any = ecs_term_match_0(term);
|
||||
bool from_this = ecs_term_match_this(term);
|
||||
bool is_shared = !from_any && (!from_this || !(src->flags & EcsSelf));
|
||||
|
||||
ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id);
|
||||
|
||||
if (from_this && ws >= WriteStateToStage) {
|
||||
/* A staged write could have happened for an id that's matched on the
|
||||
* main storage. Even if the id isn't read, still insert a merge so that
|
||||
* a write to the main storage after the staged write doesn't get
|
||||
* overwritten. */
|
||||
return true;
|
||||
}
|
||||
|
||||
if (inout == EcsInOutDefault) {
|
||||
if (from_any) {
|
||||
/* If no inout kind is specified for terms without a source, this is
|
||||
* not interpreted as a read/write annotation but just a (component)
|
||||
* id that's passed to a system. */
|
||||
return false;
|
||||
} else if (is_shared) {
|
||||
inout = EcsIn;
|
||||
} else {
|
||||
/* Default for owned terms is InOut */
|
||||
inout = EcsInOut;
|
||||
}
|
||||
}
|
||||
|
||||
if (oper == EcsNot && inout == EcsOut) {
|
||||
/* If a Not term is combined with Out, it signals that the system
|
||||
* intends to add a component that the entity doesn't yet have */
|
||||
from_any = true;
|
||||
}
|
||||
|
||||
if (from_any) {
|
||||
switch(inout) {
|
||||
case EcsOut:
|
||||
case EcsInOut:
|
||||
if (is_active) {
|
||||
/* Only flag component as written if system is active */
|
||||
flecs_pipeline_set_write_state(write_state, id);
|
||||
}
|
||||
break;
|
||||
case EcsInOutDefault:
|
||||
case EcsInOutNone:
|
||||
case EcsIn:
|
||||
break;
|
||||
}
|
||||
|
||||
switch(inout) {
|
||||
case EcsIn:
|
||||
case EcsInOut:
|
||||
if (ws == WriteStateToStage) {
|
||||
/* If a system does a get/get_mut, the component is fetched from
|
||||
* the main store so it must be merged first */
|
||||
return true;
|
||||
}
|
||||
/* fall through */
|
||||
case EcsInOutDefault:
|
||||
case EcsInOutNone:
|
||||
case EcsOut:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_pipeline_check_terms(
|
||||
ecs_world_t *world,
|
||||
ecs_filter_t *filter,
|
||||
bool is_active,
|
||||
ecs_write_state_t *ws)
|
||||
{
|
||||
bool needs_merge = false;
|
||||
ecs_term_t *terms = filter->terms;
|
||||
int32_t t, term_count = filter->term_count;
|
||||
|
||||
/* Check This terms first. This way if a term indicating writing to a stage
|
||||
* was added before the term, it won't cause merging. */
|
||||
for (t = 0; t < term_count; t ++) {
|
||||
ecs_term_t *term = &terms[t];
|
||||
if (ecs_term_match_this(term)) {
|
||||
needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now check staged terms */
|
||||
for (t = 0; t < term_count; t ++) {
|
||||
ecs_term_t *term = &terms[t];
|
||||
if (!ecs_term_match_this(term)) {
|
||||
needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws);
|
||||
}
|
||||
}
|
||||
|
||||
return needs_merge;
|
||||
}
|
||||
|
||||
static
|
||||
EcsPoly* flecs_pipeline_term_system(
|
||||
ecs_iter_t *it)
|
||||
{
|
||||
int32_t index = ecs_table_get_column_index(
|
||||
it->real_world, it->table, ecs_poly_id(EcsSystem));
|
||||
ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL);
|
||||
EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset);
|
||||
ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return poly;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_pipeline_build(
|
||||
ecs_world_t *world,
|
||||
ecs_pipeline_state_t *pq)
|
||||
{
|
||||
ecs_iter_t it = ecs_query_iter(world, pq->query);
|
||||
|
||||
if (pq->match_count == pq->query->match_count) {
|
||||
/* No need to rebuild the pipeline */
|
||||
ecs_iter_fini(&it);
|
||||
return false;
|
||||
}
|
||||
|
||||
world->info.pipeline_build_count_total ++;
|
||||
pq->rebuild_count ++;
|
||||
|
||||
ecs_allocator_t *a = &world->allocator;
|
||||
ecs_pipeline_op_t *op = NULL;
|
||||
ecs_write_state_t ws = {0};
|
||||
ecs_map_init(&ws.ids, a);
|
||||
ecs_map_init(&ws.wildcard_ids, a);
|
||||
|
||||
ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t);
|
||||
ecs_vec_reset_t(a, &pq->systems, ecs_entity_t);
|
||||
|
||||
bool multi_threaded = false;
|
||||
bool no_readonly = false;
|
||||
bool first = true;
|
||||
|
||||
/* Iterate systems in pipeline, add ops for running / merging */
|
||||
while (ecs_query_next(&it)) {
|
||||
EcsPoly *poly = flecs_pipeline_term_system(&it);
|
||||
bool is_active = ecs_table_get_type_index(world, it.table, EcsEmpty) == -1;
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < it.count; i ++) {
|
||||
ecs_poly_assert(poly[i].poly, ecs_system_t);
|
||||
ecs_system_t *sys = (ecs_system_t*)poly[i].poly;
|
||||
ecs_query_t *q = sys->query;
|
||||
|
||||
bool needs_merge = false;
|
||||
needs_merge = flecs_pipeline_check_terms(
|
||||
world, &q->filter, is_active, &ws);
|
||||
|
||||
if (is_active) {
|
||||
if (first) {
|
||||
multi_threaded = sys->multi_threaded;
|
||||
no_readonly = sys->no_readonly;
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (sys->multi_threaded != multi_threaded) {
|
||||
needs_merge = true;
|
||||
multi_threaded = sys->multi_threaded;
|
||||
}
|
||||
if (sys->no_readonly != no_readonly) {
|
||||
needs_merge = true;
|
||||
no_readonly = sys->no_readonly;
|
||||
}
|
||||
}
|
||||
|
||||
if (no_readonly) {
|
||||
needs_merge = true;
|
||||
}
|
||||
|
||||
if (needs_merge) {
|
||||
/* After merge all components will be merged, so reset state */
|
||||
flecs_pipeline_reset_write_state(&ws);
|
||||
|
||||
/* An inactive system can insert a merge if one of its
|
||||
* components got written, which could make the system
|
||||
* active. If this is the only system in the pipeline operation,
|
||||
* it results in an empty operation when we get here. If that's
|
||||
* the case, reuse the empty operation for the next op. */
|
||||
if (op && op->count) {
|
||||
op = NULL;
|
||||
}
|
||||
|
||||
/* Re-evaluate columns to set write flags if system is active.
|
||||
* If system is inactive, it can't write anything and so it
|
||||
* should not insert unnecessary merges. */
|
||||
needs_merge = false;
|
||||
if (is_active) {
|
||||
needs_merge = flecs_pipeline_check_terms(
|
||||
world, &q->filter, true, &ws);
|
||||
}
|
||||
|
||||
/* The component states were just reset, so if we conclude that
|
||||
* another merge is needed something is wrong. */
|
||||
ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (!op) {
|
||||
op = ecs_vec_append_t(a, &pq->ops, ecs_pipeline_op_t);
|
||||
op->offset = ecs_vec_count(&pq->systems);
|
||||
op->count = 0;
|
||||
op->multi_threaded = false;
|
||||
op->no_readonly = false;
|
||||
op->time_spent = 0;
|
||||
op->commands_enqueued = 0;
|
||||
}
|
||||
|
||||
/* Don't increase count for inactive systems, as they are ignored by
|
||||
* the query used to run the pipeline. */
|
||||
if (is_active) {
|
||||
ecs_vec_append_t(a, &pq->systems, ecs_entity_t)[0] =
|
||||
it.entities[i];
|
||||
if (!op->count) {
|
||||
op->multi_threaded = multi_threaded;
|
||||
op->no_readonly = no_readonly;
|
||||
}
|
||||
op->count ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (op && !op->count && ecs_vec_count(&pq->ops) > 1) {
|
||||
ecs_vec_remove_last(&pq->ops);
|
||||
}
|
||||
|
||||
ecs_map_fini(&ws.ids);
|
||||
ecs_map_fini(&ws.wildcard_ids);
|
||||
|
||||
op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t);
|
||||
|
||||
if (!op) {
|
||||
ecs_dbg("#[green]pipeline#[reset] is empty");
|
||||
return true;
|
||||
} else {
|
||||
/* Add schedule to debug tracing */
|
||||
ecs_dbg("#[bold]pipeline rebuild");
|
||||
ecs_log_push_1();
|
||||
|
||||
ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:",
|
||||
op->multi_threaded, !op->no_readonly);
|
||||
ecs_log_push_1();
|
||||
|
||||
int32_t i, count = ecs_vec_count(&pq->systems);
|
||||
int32_t op_index = 0, ran_since_merge = 0;
|
||||
ecs_entity_t *systems = ecs_vec_first_t(&pq->systems, ecs_entity_t);
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t system = systems[i];
|
||||
const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem);
|
||||
ecs_poly_assert(poly->poly, ecs_system_t);
|
||||
ecs_system_t *sys = (ecs_system_t*)poly->poly;
|
||||
|
||||
#ifdef FLECS_LOG_1
|
||||
char *path = ecs_get_fullpath(world, system);
|
||||
const char *doc_name = NULL;
|
||||
#ifdef FLECS_DOC
|
||||
const EcsDocDescription *doc_name_id = ecs_get_pair(world, system,
|
||||
EcsDocDescription, EcsName);
|
||||
if (doc_name_id) {
|
||||
doc_name = doc_name_id->value;
|
||||
}
|
||||
#endif
|
||||
if (doc_name) {
|
||||
ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name);
|
||||
} else {
|
||||
ecs_dbg("#[green]system#[reset] %s", path);
|
||||
}
|
||||
ecs_os_free(path);
|
||||
#endif
|
||||
|
||||
ecs_assert(op[op_index].offset + ran_since_merge == i,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ran_since_merge ++;
|
||||
if (ran_since_merge == op[op_index].count) {
|
||||
ecs_dbg("#[magenta]merge#[reset]");
|
||||
ecs_log_pop_1();
|
||||
ran_since_merge = 0;
|
||||
op_index ++;
|
||||
if (op_index < ecs_vec_count(&pq->ops)) {
|
||||
ecs_dbg(
|
||||
"#[green]schedule#[reset]: "
|
||||
"threading: %d, staging: %d:",
|
||||
op[op_index].multi_threaded,
|
||||
!op[op_index].no_readonly);
|
||||
}
|
||||
ecs_log_push_1();
|
||||
}
|
||||
|
||||
if (sys->last_frame == (world->info.frame_count_total + 1)) {
|
||||
if (op_index < ecs_vec_count(&pq->ops)) {
|
||||
pq->cur_op = &op[op_index];
|
||||
pq->cur_i = i;
|
||||
} else {
|
||||
pq->cur_op = NULL;
|
||||
pq->cur_i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ecs_log_pop_1();
|
||||
ecs_log_pop_1();
|
||||
}
|
||||
|
||||
pq->match_count = pq->query->match_count;
|
||||
|
||||
ecs_assert(pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t),
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_pipeline_next_system(
|
||||
ecs_pipeline_state_t *pq)
|
||||
{
|
||||
if (!pq->cur_op) {
|
||||
return;
|
||||
}
|
||||
|
||||
pq->cur_i ++;
|
||||
if (pq->cur_i >= (pq->cur_op->offset + pq->cur_op->count)) {
|
||||
pq->cur_op ++;
|
||||
if (pq->cur_op > ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)) {
|
||||
pq->cur_op = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool flecs_pipeline_update(
|
||||
ecs_world_t *world,
|
||||
ecs_pipeline_state_t *pq,
|
||||
bool start_of_frame)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL);
|
||||
|
||||
/* If any entity mutations happened that could have affected query matching
|
||||
* notify appropriate queries so caches are up to date. This includes the
|
||||
* pipeline query. */
|
||||
if (start_of_frame) {
|
||||
ecs_run_aperiodic(world, 0);
|
||||
}
|
||||
|
||||
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
bool rebuilt = flecs_pipeline_build(world, pq);
|
||||
if (start_of_frame) {
|
||||
/* Initialize iterators */
|
||||
int32_t i, count = pq->iter_count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_world_t *stage = ecs_get_stage(world, i);
|
||||
pq->iters[i] = ecs_query_iter(stage, pq->query);
|
||||
}
|
||||
pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t);
|
||||
pq->cur_i = 0;
|
||||
} else {
|
||||
flecs_pipeline_next_system(pq);
|
||||
}
|
||||
|
||||
return rebuilt;
|
||||
}
|
||||
|
||||
void ecs_run_pipeline(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t pipeline,
|
||||
ecs_ftime_t delta_time)
|
||||
{
|
||||
if (!pipeline) {
|
||||
pipeline = world->pipeline;
|
||||
}
|
||||
|
||||
/* create any worker task threads request */
|
||||
if (ecs_using_task_threads(world)) {
|
||||
flecs_create_worker_threads(world);
|
||||
}
|
||||
|
||||
EcsPipeline *p =
|
||||
ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline));
|
||||
flecs_workers_progress(world, p->state, delta_time);
|
||||
|
||||
if (ecs_using_task_threads(world)) {
|
||||
/* task threads were temporary and may now be joined */
|
||||
flecs_join_worker_threads(world);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t flecs_run_pipeline_ops(
|
||||
ecs_world_t* world,
|
||||
ecs_stage_t* stage,
|
||||
int32_t stage_index,
|
||||
int32_t stage_count,
|
||||
ecs_ftime_t delta_time)
|
||||
{
|
||||
ecs_pipeline_state_t* pq = world->pq;
|
||||
ecs_pipeline_op_t* op = pq->cur_op;
|
||||
int32_t i = pq->cur_i;
|
||||
|
||||
ecs_assert(!stage_index || op->multi_threaded, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
int32_t count = ecs_vec_count(&pq->systems);
|
||||
ecs_entity_t* systems = ecs_vec_first_t(&pq->systems, ecs_entity_t);
|
||||
int32_t ran_since_merge = i - op->offset;
|
||||
|
||||
for (; i < count; i++) {
|
||||
ecs_entity_t system = systems[i];
|
||||
const EcsPoly* poly = ecs_get_pair(world, system, EcsPoly, EcsSystem);
|
||||
ecs_poly_assert(poly->poly, ecs_system_t);
|
||||
ecs_system_t* sys = (ecs_system_t*)poly->poly;
|
||||
|
||||
/* Keep track of the last frame for which the system has ran, so we
|
||||
* know from where to resume the schedule in case the schedule
|
||||
* changes during a merge. */
|
||||
sys->last_frame = world->info.frame_count_total + 1;
|
||||
|
||||
ecs_stage_t* s = NULL;
|
||||
if (!op->no_readonly) {
|
||||
/* If system is no_readonly it operates on the actual world, not
|
||||
* the stage. Only pass stage to system if it's readonly. */
|
||||
s = stage;
|
||||
}
|
||||
|
||||
ecs_run_intern(world, s, system, sys, stage_index,
|
||||
stage_count, delta_time, 0, 0, NULL);
|
||||
|
||||
world->info.systems_ran_frame++;
|
||||
ran_since_merge++;
|
||||
|
||||
if (ran_since_merge == op->count) {
|
||||
/* Merge */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
void flecs_run_pipeline(
|
||||
ecs_world_t *world,
|
||||
ecs_pipeline_state_t *pq,
|
||||
ecs_ftime_t delta_time)
|
||||
{
|
||||
ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL);
|
||||
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_poly_assert(world, ecs_stage_t);
|
||||
|
||||
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
||||
int32_t stage_index = ecs_get_stage_id(stage->thread_ctx);
|
||||
int32_t stage_count = ecs_get_stage_count(world);
|
||||
|
||||
ecs_assert(!stage_index, ECS_INVALID_OPERATION, NULL);
|
||||
|
||||
bool multi_threaded = ecs_get_stage_count(world) > 1;
|
||||
|
||||
// Update the pipeline the workers will execute
|
||||
world->pq = pq;
|
||||
|
||||
// Update the pipeline before waking the workers.
|
||||
flecs_pipeline_update(world, pq, true);
|
||||
|
||||
// If there are no operations to execute in the pipeline bail early,
|
||||
// no need to wake the workers since they have nothing to do.
|
||||
while (pq->cur_op != NULL) {
|
||||
if (pq->cur_i == ecs_vec_count(&pq->systems)) {
|
||||
flecs_pipeline_update(world, pq, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool no_readonly = pq->cur_op->no_readonly;
|
||||
bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded;
|
||||
|
||||
pq->no_readonly = no_readonly;
|
||||
|
||||
if (!no_readonly) {
|
||||
ecs_readonly_begin(world);
|
||||
}
|
||||
|
||||
ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded);
|
||||
ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (op_multi_threaded) {
|
||||
flecs_signal_workers(world);
|
||||
}
|
||||
|
||||
ecs_time_t st = { 0 };
|
||||
bool measure_time = world->flags & EcsWorldMeasureSystemTime;
|
||||
if (measure_time) {
|
||||
ecs_time_measure(&st);
|
||||
}
|
||||
|
||||
const int32_t i = flecs_run_pipeline_ops(
|
||||
world, stage, stage_index, stage_count, delta_time);
|
||||
|
||||
if (measure_time) {
|
||||
/* Don't include merge time in system time */
|
||||
world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st);
|
||||
}
|
||||
|
||||
if (op_multi_threaded) {
|
||||
flecs_wait_for_sync(world);
|
||||
}
|
||||
|
||||
if (!no_readonly) {
|
||||
ecs_time_t mt = { 0 };
|
||||
if (measure_time) {
|
||||
ecs_time_measure(&mt);
|
||||
}
|
||||
|
||||
int32_t si;
|
||||
for (si = 0; si < stage_count; si ++) {
|
||||
ecs_stage_t *s = &world->stages[si];
|
||||
pq->cur_op->commands_enqueued += ecs_vec_count(&s->cmd->queue);
|
||||
}
|
||||
|
||||
ecs_readonly_end(world);
|
||||
if (measure_time) {
|
||||
pq->cur_op->time_spent += ecs_time_measure(&mt);
|
||||
}
|
||||
}
|
||||
|
||||
/* Store the current state of the schedule after we synchronized the
|
||||
* threads, to avoid race conditions. */
|
||||
pq->cur_i = i;
|
||||
|
||||
flecs_pipeline_update(world, pq, false);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_run_startup_systems(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world,
|
||||
ecs_dependson(EcsOnStart));
|
||||
if (!idr || !flecs_table_cache_count(&idr->cache)) {
|
||||
/* Don't bother creating startup pipeline if no systems exist */
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_dbg_2("#[bold]startup#[reset]");
|
||||
ecs_log_push_2();
|
||||
int32_t stage_count = world->stage_count;
|
||||
world->stage_count = 1; /* Prevents running startup systems on workers */
|
||||
|
||||
/* Creating a pipeline is relatively expensive, but this only happens
|
||||
* for the first frame. The startup pipeline is deleted afterwards, which
|
||||
* eliminates the overhead of keeping its query cache in sync. */
|
||||
ecs_dbg_2("#[bold]create startup pipeline#[reset]");
|
||||
ecs_log_push_2();
|
||||
ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){
|
||||
.query = {
|
||||
.filter.terms = {
|
||||
{ .id = EcsSystem },
|
||||
{ .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn },
|
||||
{ .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn },
|
||||
{ .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot },
|
||||
{ .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot }
|
||||
},
|
||||
.order_by = flecs_entity_compare
|
||||
}
|
||||
});
|
||||
ecs_log_pop_2();
|
||||
|
||||
/* Run & delete pipeline */
|
||||
ecs_dbg_2("#[bold]run startup systems#[reset]");
|
||||
ecs_log_push_2();
|
||||
ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline);
|
||||
ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL);
|
||||
flecs_workers_progress(world, p->state, 0);
|
||||
ecs_log_pop_2();
|
||||
|
||||
ecs_dbg_2("#[bold]delete startup pipeline#[reset]");
|
||||
ecs_log_push_2();
|
||||
ecs_delete(world, start_pip);
|
||||
ecs_log_pop_2();
|
||||
|
||||
world->stage_count = stage_count;
|
||||
ecs_log_pop_2();
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
bool ecs_progress(
|
||||
ecs_world_t *world,
|
||||
ecs_ftime_t user_delta_time)
|
||||
{
|
||||
ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time);
|
||||
|
||||
/* If this is the first frame, run startup systems */
|
||||
if (world->info.frame_count_total == 0) {
|
||||
flecs_run_startup_systems(world);
|
||||
}
|
||||
|
||||
/* create any worker task threads request */
|
||||
if (ecs_using_task_threads(world)) {
|
||||
flecs_create_worker_threads(world);
|
||||
}
|
||||
|
||||
ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time);
|
||||
ecs_log_push_3();
|
||||
const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline);
|
||||
ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL);
|
||||
flecs_workers_progress(world, p->state, delta_time);
|
||||
ecs_log_pop_3();
|
||||
|
||||
ecs_frame_end(world);
|
||||
|
||||
if (ecs_using_task_threads(world)) {
|
||||
/* task threads were temporary and may now be joined */
|
||||
flecs_join_worker_threads(world);
|
||||
}
|
||||
|
||||
return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit);
|
||||
error:
|
||||
return false;
|
||||
}
|
||||
|
||||
void ecs_set_time_scale(
|
||||
ecs_world_t *world,
|
||||
ecs_ftime_t scale)
|
||||
{
|
||||
world->info.time_scale = scale;
|
||||
}
|
||||
|
||||
void ecs_reset_clock(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
world->info.world_time_total = 0;
|
||||
world->info.world_time_total_raw = 0;
|
||||
}
|
||||
|
||||
void ecs_set_pipeline(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t pipeline)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL,
|
||||
ECS_INVALID_PARAMETER, "not a pipeline");
|
||||
|
||||
world->pipeline = pipeline;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_get_pipeline(
|
||||
const ecs_world_t *world)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
world = ecs_get_world(world);
|
||||
return world->pipeline;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_pipeline_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_pipeline_desc_t *desc)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_entity_t result = desc->entity;
|
||||
if (!result) {
|
||||
result = ecs_new(world, 0);
|
||||
}
|
||||
|
||||
ecs_query_desc_t qd = desc->query;
|
||||
if (!qd.order_by) {
|
||||
qd.order_by = flecs_entity_compare;
|
||||
}
|
||||
qd.filter.entity = result;
|
||||
|
||||
ecs_query_t *query = ecs_query_init(world, &qd);
|
||||
if (!query) {
|
||||
ecs_delete(world, result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_check(query->filter.terms != NULL, ECS_INVALID_PARAMETER,
|
||||
"pipeline query cannot be empty");
|
||||
ecs_check(query->filter.terms[0].id == EcsSystem,
|
||||
ECS_INVALID_PARAMETER, "pipeline must start with System term");
|
||||
|
||||
ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t);
|
||||
pq->query = query;
|
||||
pq->match_count = -1;
|
||||
pq->idr_inactive = flecs_id_record_ensure(world, EcsEmpty);
|
||||
ecs_set(world, result, EcsPipeline, { pq });
|
||||
|
||||
return result;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -- Module implementation -- */
|
||||
|
||||
static
|
||||
void FlecsPipelineFini(
|
||||
ecs_world_t *world,
|
||||
void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (ecs_get_stage_count(world)) {
|
||||
ecs_set_threads(world, 0);
|
||||
}
|
||||
|
||||
ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
#define flecs_bootstrap_phase(world, phase, depends_on)\
|
||||
flecs_bootstrap_tag(world, phase);\
|
||||
flecs_bootstrap_phase_(world, phase, depends_on)
|
||||
static
|
||||
void flecs_bootstrap_phase_(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t phase,
|
||||
ecs_entity_t depends_on)
|
||||
{
|
||||
ecs_add_id(world, phase, EcsPhase);
|
||||
if (depends_on) {
|
||||
ecs_add_pair(world, phase, EcsDependsOn, depends_on);
|
||||
}
|
||||
}
|
||||
|
||||
void FlecsPipelineImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsPipeline);
|
||||
ECS_IMPORT(world, FlecsSystem);
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
flecs_bootstrap_component(world, EcsPipeline);
|
||||
flecs_bootstrap_tag(world, EcsPhase);
|
||||
|
||||
/* Create anonymous phases to which the builtin phases will have DependsOn
|
||||
* relationships. This ensures that, for example, EcsOnUpdate doesn't have a
|
||||
* direct DependsOn relationship on EcsPreUpdate, which ensures that when
|
||||
* the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */
|
||||
ecs_entity_t phase_0 = ecs_new(world, 0);
|
||||
ecs_entity_t phase_1 = ecs_new_w_pair(world, EcsDependsOn, phase_0);
|
||||
ecs_entity_t phase_2 = ecs_new_w_pair(world, EcsDependsOn, phase_1);
|
||||
ecs_entity_t phase_3 = ecs_new_w_pair(world, EcsDependsOn, phase_2);
|
||||
ecs_entity_t phase_4 = ecs_new_w_pair(world, EcsDependsOn, phase_3);
|
||||
ecs_entity_t phase_5 = ecs_new_w_pair(world, EcsDependsOn, phase_4);
|
||||
ecs_entity_t phase_6 = ecs_new_w_pair(world, EcsDependsOn, phase_5);
|
||||
ecs_entity_t phase_7 = ecs_new_w_pair(world, EcsDependsOn, phase_6);
|
||||
ecs_entity_t phase_8 = ecs_new_w_pair(world, EcsDependsOn, phase_7);
|
||||
|
||||
flecs_bootstrap_phase(world, EcsOnStart, 0);
|
||||
flecs_bootstrap_phase(world, EcsPreFrame, 0);
|
||||
flecs_bootstrap_phase(world, EcsOnLoad, phase_0);
|
||||
flecs_bootstrap_phase(world, EcsPostLoad, phase_1);
|
||||
flecs_bootstrap_phase(world, EcsPreUpdate, phase_2);
|
||||
flecs_bootstrap_phase(world, EcsOnUpdate, phase_3);
|
||||
flecs_bootstrap_phase(world, EcsOnValidate, phase_4);
|
||||
flecs_bootstrap_phase(world, EcsPostUpdate, phase_5);
|
||||
flecs_bootstrap_phase(world, EcsPreStore, phase_6);
|
||||
flecs_bootstrap_phase(world, EcsOnStore, phase_7);
|
||||
flecs_bootstrap_phase(world, EcsPostFrame, phase_8);
|
||||
|
||||
ecs_set_hooks(world, EcsPipeline, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.dtor = ecs_dtor(EcsPipeline),
|
||||
.move = ecs_move(EcsPipeline)
|
||||
});
|
||||
|
||||
world->pipeline = ecs_pipeline(world, {
|
||||
.entity = ecs_entity(world, { .name = "BuiltinPipeline" }),
|
||||
.query = {
|
||||
.filter.terms = {
|
||||
{ .id = EcsSystem },
|
||||
{ .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn },
|
||||
{ .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn, .oper = EcsNot },
|
||||
{ .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot },
|
||||
{ .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot }
|
||||
},
|
||||
.order_by = flecs_entity_compare
|
||||
}
|
||||
});
|
||||
|
||||
/* Cleanup thread administration when world is destroyed */
|
||||
ecs_atfini(world, FlecsPipelineFini, NULL);
|
||||
}
|
||||
|
||||
#endif
|
||||
88
engine/libs/flecs/src/addons/pipeline/pipeline.h
Normal file
88
engine/libs/flecs/src/addons/pipeline/pipeline.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @file addons/pipeline/pipeline.h
|
||||
* @brief Internal functions/types for pipeline addon.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_PIPELINE_PRIVATE_H
|
||||
#define FLECS_PIPELINE_PRIVATE_H
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
/** Instruction data for pipeline.
|
||||
* This type is the element type in the "ops" vector of a pipeline. */
|
||||
typedef struct ecs_pipeline_op_t {
|
||||
int32_t offset; /* Offset in systems vector */
|
||||
int32_t count; /* Number of systems to run before next op */
|
||||
double time_spent; /* Time spent merging commands for sync point */
|
||||
int64_t commands_enqueued; /* Number of commands enqueued for sync point */
|
||||
bool multi_threaded; /* Whether systems can be ran multi threaded */
|
||||
bool no_readonly; /* Whether systems are staged or not */
|
||||
} ecs_pipeline_op_t;
|
||||
|
||||
struct ecs_pipeline_state_t {
|
||||
ecs_query_t *query; /* Pipeline query */
|
||||
ecs_vec_t ops; /* Pipeline schedule */
|
||||
ecs_vec_t systems; /* Vector with system ids */
|
||||
|
||||
ecs_entity_t last_system; /* Last system ran by pipeline */
|
||||
ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */
|
||||
int32_t match_count; /* Used to track of rebuild is necessary */
|
||||
int32_t rebuild_count; /* Number of pipeline rebuilds */
|
||||
ecs_iter_t *iters; /* Iterator for worker(s) */
|
||||
int32_t iter_count;
|
||||
|
||||
/* Members for continuing pipeline iteration after pipeline rebuild */
|
||||
ecs_pipeline_op_t *cur_op; /* Current pipeline op */
|
||||
int32_t cur_i; /* Index in current result */
|
||||
int32_t ran_since_merge; /* Index in current op */
|
||||
bool no_readonly; /* Is pipeline in readonly mode */
|
||||
};
|
||||
|
||||
typedef struct EcsPipeline {
|
||||
/* Stable ptr so threads can safely access while entity/components move */
|
||||
ecs_pipeline_state_t *state;
|
||||
} EcsPipeline;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Pipeline API
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool flecs_pipeline_update(
|
||||
ecs_world_t *world,
|
||||
ecs_pipeline_state_t *pq,
|
||||
bool start_of_frame);
|
||||
|
||||
void flecs_run_pipeline(
|
||||
ecs_world_t *world,
|
||||
ecs_pipeline_state_t *pq,
|
||||
ecs_ftime_t delta_time);
|
||||
|
||||
int32_t flecs_run_pipeline_ops(
|
||||
ecs_world_t* world,
|
||||
ecs_stage_t* stage,
|
||||
int32_t stage_index,
|
||||
int32_t stage_count,
|
||||
ecs_ftime_t delta_time);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Worker API
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void flecs_workers_progress(
|
||||
ecs_world_t *world,
|
||||
ecs_pipeline_state_t *pq,
|
||||
ecs_ftime_t delta_time);
|
||||
|
||||
void flecs_create_worker_threads(
|
||||
ecs_world_t *world);
|
||||
|
||||
void flecs_join_worker_threads(
|
||||
ecs_world_t *world);
|
||||
|
||||
void flecs_signal_workers(
|
||||
ecs_world_t *world);
|
||||
|
||||
void flecs_wait_for_sync(
|
||||
ecs_world_t *world);
|
||||
|
||||
#endif
|
||||
309
engine/libs/flecs/src/addons/pipeline/worker.c
Normal file
309
engine/libs/flecs/src/addons/pipeline/worker.c
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* @file addons/pipeline/worker.c
|
||||
* @brief Functions for running pipelines on one or more threads.
|
||||
*/
|
||||
|
||||
#include "flecs.h"
|
||||
#include "../system/system.h"
|
||||
|
||||
#ifdef FLECS_PIPELINE
|
||||
#include "pipeline.h"
|
||||
|
||||
/* Synchronize workers */
|
||||
static
|
||||
void flecs_sync_worker(
|
||||
ecs_world_t* world)
|
||||
{
|
||||
int32_t stage_count = ecs_get_stage_count(world);
|
||||
if (stage_count <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Signal that thread is waiting */
|
||||
ecs_os_mutex_lock(world->sync_mutex);
|
||||
if (++world->workers_waiting == (stage_count - 1)) {
|
||||
/* Only signal main thread when all threads are waiting */
|
||||
ecs_os_cond_signal(world->sync_cond);
|
||||
}
|
||||
|
||||
/* Wait until main thread signals that thread can continue */
|
||||
ecs_os_cond_wait(world->worker_cond, world->sync_mutex);
|
||||
ecs_os_mutex_unlock(world->sync_mutex);
|
||||
}
|
||||
|
||||
/* Worker thread */
|
||||
static
|
||||
void* flecs_worker(void *arg) {
|
||||
ecs_stage_t *stage = arg;
|
||||
ecs_world_t *world = stage->world;
|
||||
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_poly_assert(stage, ecs_stage_t);
|
||||
|
||||
ecs_dbg_2("worker %d: start", stage->id);
|
||||
|
||||
/* Start worker, increase counter so main thread knows how many
|
||||
* workers are ready */
|
||||
ecs_os_mutex_lock(world->sync_mutex);
|
||||
world->workers_running ++;
|
||||
|
||||
if (!(world->flags & EcsWorldQuitWorkers)) {
|
||||
ecs_os_cond_wait(world->worker_cond, world->sync_mutex);
|
||||
}
|
||||
|
||||
ecs_os_mutex_unlock(world->sync_mutex);
|
||||
|
||||
while (!(world->flags & EcsWorldQuitWorkers)) {
|
||||
ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0);
|
||||
|
||||
ecs_dbg_3("worker %d: run", stage->id);
|
||||
flecs_run_pipeline_ops(world, stage, stage->id, world->stage_count,
|
||||
world->info.delta_time);
|
||||
|
||||
ecs_set_scope((ecs_world_t*)stage, old_scope);
|
||||
|
||||
flecs_sync_worker(world);
|
||||
}
|
||||
|
||||
ecs_dbg_2("worker %d: finalizing", stage->id);
|
||||
|
||||
ecs_os_mutex_lock(world->sync_mutex);
|
||||
world->workers_running --;
|
||||
ecs_os_mutex_unlock(world->sync_mutex);
|
||||
|
||||
ecs_dbg_2("worker %d: stop", stage->id);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Start threads */
|
||||
void flecs_create_worker_threads(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
int32_t stages = ecs_get_stage_count(world);
|
||||
|
||||
for (int32_t i = 1; i < stages; i ++) {
|
||||
ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i);
|
||||
ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_poly_assert(stage, ecs_stage_t);
|
||||
|
||||
ecs_assert(stage->thread == 0, ECS_INTERNAL_ERROR, NULL);
|
||||
if (ecs_using_task_threads(world)) {
|
||||
/* workers are using tasks in an external task manager provided to
|
||||
* the OS API */
|
||||
stage->thread = ecs_os_task_new(flecs_worker, stage);
|
||||
} else {
|
||||
/* workers are using long-running os threads */
|
||||
stage->thread = ecs_os_thread_new(flecs_worker, stage);
|
||||
}
|
||||
ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_start_workers(
|
||||
ecs_world_t *world,
|
||||
int32_t threads)
|
||||
{
|
||||
ecs_set_stage_count(world, threads);
|
||||
|
||||
ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (!ecs_using_task_threads(world)) {
|
||||
flecs_create_worker_threads(world);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait until all workers are running */
|
||||
static
|
||||
void flecs_wait_for_workers(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
|
||||
int32_t stage_count = ecs_get_stage_count(world);
|
||||
if (stage_count <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool wait = true;
|
||||
do {
|
||||
ecs_os_mutex_lock(world->sync_mutex);
|
||||
if (world->workers_running == (stage_count - 1)) {
|
||||
wait = false;
|
||||
}
|
||||
ecs_os_mutex_unlock(world->sync_mutex);
|
||||
} while (wait);
|
||||
}
|
||||
|
||||
/* Wait until all threads are waiting on sync point */
|
||||
void flecs_wait_for_sync(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
int32_t stage_count = ecs_get_stage_count(world);
|
||||
if (stage_count <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_dbg_3("#[bold]pipeline: waiting for worker sync");
|
||||
|
||||
ecs_os_mutex_lock(world->sync_mutex);
|
||||
if (world->workers_waiting != (stage_count - 1)) {
|
||||
ecs_os_cond_wait(world->sync_cond, world->sync_mutex);
|
||||
}
|
||||
|
||||
/* We shouldn't have been signalled unless all workers are waiting on sync */
|
||||
ecs_assert(world->workers_waiting == (stage_count - 1),
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
world->workers_waiting = 0;
|
||||
ecs_os_mutex_unlock(world->sync_mutex);
|
||||
|
||||
ecs_dbg_3("#[bold]pipeline: workers synced");
|
||||
}
|
||||
|
||||
/* Signal workers that they can start/resume work */
|
||||
void flecs_signal_workers(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
int32_t stage_count = ecs_get_stage_count(world);
|
||||
if (stage_count <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_dbg_3("#[bold]pipeline: signal workers");
|
||||
ecs_os_mutex_lock(world->sync_mutex);
|
||||
ecs_os_cond_broadcast(world->worker_cond);
|
||||
ecs_os_mutex_unlock(world->sync_mutex);
|
||||
}
|
||||
|
||||
void flecs_join_worker_threads(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
bool threads_active = false;
|
||||
|
||||
/* Test if threads are created. Cannot use workers_running, since this is
|
||||
* a potential race if threads haven't spun up yet. */
|
||||
ecs_stage_t *stages = world->stages;
|
||||
int i, count = world->stage_count;
|
||||
for (i = 1; i < count; i ++) {
|
||||
ecs_stage_t *stage = &stages[i];
|
||||
if (stage->thread) {
|
||||
threads_active = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* If no threads are active, just return */
|
||||
if (!threads_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure all threads are running, to ensure they catch the signal */
|
||||
flecs_wait_for_workers(world);
|
||||
|
||||
/* Signal threads should quit */
|
||||
world->flags |= EcsWorldQuitWorkers;
|
||||
flecs_signal_workers(world);
|
||||
|
||||
/* Join all threads with main */
|
||||
for (i = 1; i < count; i ++) {
|
||||
if (ecs_using_task_threads(world)) {
|
||||
ecs_os_task_join(stages[i].thread);
|
||||
} else {
|
||||
ecs_os_thread_join(stages[i].thread);
|
||||
}
|
||||
stages[i].thread = 0;
|
||||
}
|
||||
|
||||
world->flags &= ~EcsWorldQuitWorkers;
|
||||
ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
/* -- Private functions -- */
|
||||
void flecs_workers_progress(
|
||||
ecs_world_t *world,
|
||||
ecs_pipeline_state_t *pq,
|
||||
ecs_ftime_t delta_time)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL);
|
||||
|
||||
/* Make sure workers are running and ready */
|
||||
flecs_wait_for_workers(world);
|
||||
|
||||
/* Run pipeline on main thread */
|
||||
ecs_world_t *stage = ecs_get_stage(world, 0);
|
||||
ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0);
|
||||
flecs_run_pipeline(stage, pq, delta_time);
|
||||
ecs_set_scope((ecs_world_t*)stage, old_scope);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_set_threads_internal(
|
||||
ecs_world_t *world,
|
||||
int32_t threads,
|
||||
bool use_task_api)
|
||||
{
|
||||
ecs_assert(threads <= 1 || (use_task_api
|
||||
? ecs_os_has_task_support()
|
||||
: ecs_os_has_threading()),
|
||||
ECS_MISSING_OS_API, NULL);
|
||||
|
||||
int32_t stage_count = ecs_get_stage_count(world);
|
||||
bool worker_method_changed = (use_task_api != world->workers_use_task_api);
|
||||
|
||||
if ((stage_count != threads) || worker_method_changed) {
|
||||
/* Stop existing threads */
|
||||
if (stage_count > 1) {
|
||||
flecs_join_worker_threads(world);
|
||||
ecs_set_stage_count(world, 1);
|
||||
|
||||
if (world->worker_cond) {
|
||||
ecs_os_cond_free(world->worker_cond);
|
||||
}
|
||||
if (world->sync_cond) {
|
||||
ecs_os_cond_free(world->sync_cond);
|
||||
}
|
||||
if (world->sync_mutex) {
|
||||
ecs_os_mutex_free(world->sync_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
world->workers_use_task_api = use_task_api;
|
||||
|
||||
/* Start threads if number of threads > 1 */
|
||||
if (threads > 1) {
|
||||
world->worker_cond = ecs_os_cond_new();
|
||||
world->sync_cond = ecs_os_cond_new();
|
||||
world->sync_mutex = ecs_os_mutex_new();
|
||||
flecs_start_workers(world, threads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -- Public functions -- */
|
||||
|
||||
void ecs_set_threads(
|
||||
ecs_world_t *world,
|
||||
int32_t threads)
|
||||
{
|
||||
flecs_set_threads_internal(world, threads, false /* use thread API */);
|
||||
}
|
||||
|
||||
void ecs_set_task_threads(
|
||||
ecs_world_t *world,
|
||||
int32_t task_threads)
|
||||
{
|
||||
flecs_set_threads_internal(world, task_threads, true /* use task API */);
|
||||
}
|
||||
|
||||
bool ecs_using_task_threads(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
return world->workers_use_task_api;
|
||||
}
|
||||
|
||||
#endif
|
||||
2309
engine/libs/flecs/src/addons/plecs.c
Normal file
2309
engine/libs/flecs/src/addons/plecs.c
Normal file
File diff suppressed because it is too large
Load Diff
1132
engine/libs/flecs/src/addons/rest.c
Normal file
1132
engine/libs/flecs/src/addons/rest.c
Normal file
File diff suppressed because it is too large
Load Diff
409
engine/libs/flecs/src/addons/rules/api.c
Normal file
409
engine/libs/flecs/src/addons/rules/api.c
Normal file
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* @file addons/rules/api.c
|
||||
* @brief User facing API for rules.
|
||||
*/
|
||||
|
||||
#include "rules.h"
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef FLECS_RULES
|
||||
|
||||
static ecs_mixins_t ecs_rule_t_mixins = {
|
||||
.type_name = "ecs_rule_t",
|
||||
.elems = {
|
||||
[EcsMixinWorld] = offsetof(ecs_rule_t, filter.world),
|
||||
[EcsMixinEntity] = offsetof(ecs_rule_t, filter.entity),
|
||||
[EcsMixinIterable] = offsetof(ecs_rule_t, iterable),
|
||||
[EcsMixinDtor] = offsetof(ecs_rule_t, dtor)
|
||||
}
|
||||
};
|
||||
|
||||
static
|
||||
const char* flecs_rule_op_str(
|
||||
uint16_t kind)
|
||||
{
|
||||
switch(kind) {
|
||||
case EcsRuleAnd: return "and ";
|
||||
case EcsRuleAndId: return "and_id ";
|
||||
case EcsRuleAndAny: return "andany ";
|
||||
case EcsRuleWith: return "with ";
|
||||
case EcsRuleTrav: return "trav ";
|
||||
case EcsRuleIdsRight: return "idsr ";
|
||||
case EcsRuleIdsLeft: return "idsl ";
|
||||
case EcsRuleEach: return "each ";
|
||||
case EcsRuleStore: return "store ";
|
||||
case EcsRuleReset: return "reset ";
|
||||
case EcsRuleUnion: return "union ";
|
||||
case EcsRuleEnd: return "end ";
|
||||
case EcsRuleNot: return "not ";
|
||||
case EcsRulePredEq: return "eq ";
|
||||
case EcsRulePredNeq: return "neq ";
|
||||
case EcsRulePredEqName: return "eq_nm ";
|
||||
case EcsRulePredNeqName: return "neq_nm ";
|
||||
case EcsRulePredEqMatch: return "eq_m ";
|
||||
case EcsRulePredNeqMatch: return "neq_m ";
|
||||
case EcsRuleLookup: return "lookup ";
|
||||
case EcsRuleSetVars: return "setvars ";
|
||||
case EcsRuleSetThis: return "setthis ";
|
||||
case EcsRuleSetFixed: return "setfix ";
|
||||
case EcsRuleSetIds: return "setids ";
|
||||
case EcsRuleContain: return "contain ";
|
||||
case EcsRulePairEq: return "pair_eq ";
|
||||
case EcsRuleSetCond: return "setcond ";
|
||||
case EcsRuleJmpCondFalse: return "jfalse ";
|
||||
case EcsRuleJmpNotSet: return "jnotset ";
|
||||
case EcsRuleYield: return "yield ";
|
||||
case EcsRuleNothing: return "nothing ";
|
||||
default: return "!invalid";
|
||||
}
|
||||
}
|
||||
|
||||
/* Implementation for iterable mixin */
|
||||
static
|
||||
void flecs_rule_iter_mixin_init(
|
||||
const ecs_world_t *world,
|
||||
const ecs_poly_t *poly,
|
||||
ecs_iter_t *iter,
|
||||
ecs_term_t *filter)
|
||||
{
|
||||
ecs_poly_assert(poly, ecs_rule_t);
|
||||
|
||||
if (filter) {
|
||||
iter[1] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly));
|
||||
iter[0] = ecs_term_chain_iter(&iter[1], filter);
|
||||
} else {
|
||||
iter[0] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly));
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_rule_fini(
|
||||
ecs_rule_t *rule)
|
||||
{
|
||||
if (rule->vars != &rule->vars_cache.var) {
|
||||
ecs_os_free(rule->vars);
|
||||
}
|
||||
|
||||
ecs_os_free(rule->ops);
|
||||
ecs_os_free(rule->src_vars);
|
||||
flecs_name_index_fini(&rule->tvar_index);
|
||||
flecs_name_index_fini(&rule->evar_index);
|
||||
ecs_filter_fini(&rule->filter);
|
||||
|
||||
ecs_poly_free(rule, ecs_rule_t);
|
||||
}
|
||||
|
||||
void ecs_rule_fini(
|
||||
ecs_rule_t *rule)
|
||||
{
|
||||
if (rule->filter.entity) {
|
||||
/* If filter is associated with entity, use poly dtor path */
|
||||
ecs_delete(rule->filter.world, rule->filter.entity);
|
||||
} else {
|
||||
flecs_rule_fini(rule);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_rule_t* ecs_rule_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_filter_desc_t *const_desc)
|
||||
{
|
||||
ecs_rule_t *result = ecs_poly_new(ecs_rule_t);
|
||||
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
||||
|
||||
/* Initialize the query */
|
||||
ecs_filter_desc_t desc = *const_desc;
|
||||
desc.storage = &result->filter; /* Use storage of rule */
|
||||
result->filter = ECS_FILTER_INIT;
|
||||
if (ecs_filter_init(world, &desc) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
result->iterable.init = flecs_rule_iter_mixin_init;
|
||||
|
||||
/* Compile filter to operations */
|
||||
if (flecs_rule_compile(world, stage, result)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ecs_entity_t entity = const_desc->entity;
|
||||
result->dtor = (ecs_poly_dtor_t)flecs_rule_fini;
|
||||
|
||||
if (entity) {
|
||||
EcsPoly *poly = ecs_poly_bind(world, entity, ecs_rule_t);
|
||||
poly->poly = result;
|
||||
ecs_poly_modified(world, entity, ecs_rule_t);
|
||||
}
|
||||
|
||||
return result;
|
||||
error:
|
||||
ecs_rule_fini(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
int32_t flecs_rule_op_ref_str(
|
||||
const ecs_rule_t *rule,
|
||||
ecs_rule_ref_t *ref,
|
||||
ecs_flags16_t flags,
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
int32_t color_chars = 0;
|
||||
if (flags & EcsRuleIsVar) {
|
||||
ecs_assert(ref->var < rule->var_count, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_rule_var_t *var = &rule->vars[ref->var];
|
||||
ecs_strbuf_appendlit(buf, "#[green]$#[reset]");
|
||||
if (var->kind == EcsVarTable) {
|
||||
ecs_strbuf_appendch(buf, '[');
|
||||
}
|
||||
ecs_strbuf_appendlit(buf, "#[green]");
|
||||
if (var->name) {
|
||||
ecs_strbuf_appendstr(buf, var->name);
|
||||
} else {
|
||||
if (var->id) {
|
||||
#ifdef FLECS_DEBUG
|
||||
if (var->label) {
|
||||
ecs_strbuf_appendstr(buf, var->label);
|
||||
ecs_strbuf_appendch(buf, '\'');
|
||||
}
|
||||
#endif
|
||||
ecs_strbuf_append(buf, "%d", var->id);
|
||||
} else {
|
||||
ecs_strbuf_appendlit(buf, "this");
|
||||
}
|
||||
}
|
||||
ecs_strbuf_appendlit(buf, "#[reset]");
|
||||
if (var->kind == EcsVarTable) {
|
||||
ecs_strbuf_appendch(buf, ']');
|
||||
}
|
||||
color_chars = ecs_os_strlen("#[green]#[reset]#[green]#[reset]");
|
||||
} else if (flags & EcsRuleIsEntity) {
|
||||
char *path = ecs_get_fullpath(rule->filter.world, ref->entity);
|
||||
ecs_strbuf_appendlit(buf, "#[blue]");
|
||||
ecs_strbuf_appendstr(buf, path);
|
||||
ecs_strbuf_appendlit(buf, "#[reset]");
|
||||
ecs_os_free(path);
|
||||
color_chars = ecs_os_strlen("#[blue]#[reset]");
|
||||
}
|
||||
return color_chars;
|
||||
}
|
||||
|
||||
char* ecs_rule_str_w_profile(
|
||||
const ecs_rule_t *rule,
|
||||
const ecs_iter_t *it)
|
||||
{
|
||||
ecs_poly_assert(rule, ecs_rule_t);
|
||||
|
||||
ecs_strbuf_t buf = ECS_STRBUF_INIT;
|
||||
ecs_rule_op_t *ops = rule->ops;
|
||||
int32_t i, count = rule->op_count, indent = 0;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_rule_op_t *op = &ops[i];
|
||||
ecs_flags16_t flags = op->flags;
|
||||
ecs_flags16_t src_flags = flecs_rule_ref_flags(flags, EcsRuleSrc);
|
||||
ecs_flags16_t first_flags = flecs_rule_ref_flags(flags, EcsRuleFirst);
|
||||
ecs_flags16_t second_flags = flecs_rule_ref_flags(flags, EcsRuleSecond);
|
||||
|
||||
if (it) {
|
||||
#ifdef FLECS_DEBUG
|
||||
const ecs_rule_iter_t *rit = &it->priv.iter.rule;
|
||||
ecs_strbuf_append(&buf,
|
||||
"#[green]%4d -> #[red]%4d <- #[grey] | ",
|
||||
rit->profile[i].count[0],
|
||||
rit->profile[i].count[1]);
|
||||
#endif
|
||||
}
|
||||
|
||||
ecs_strbuf_append(&buf,
|
||||
"#[normal]%2d. [#[grey]%2d#[reset], #[green]%2d#[reset]] ",
|
||||
i, op->prev, op->next);
|
||||
int32_t hidden_chars, start = ecs_strbuf_written(&buf);
|
||||
if (op->kind == EcsRuleEnd) {
|
||||
indent --;
|
||||
}
|
||||
|
||||
ecs_strbuf_append(&buf, "%*s", indent, "");
|
||||
ecs_strbuf_appendstr(&buf, flecs_rule_op_str(op->kind));
|
||||
ecs_strbuf_appendstr(&buf, " ");
|
||||
|
||||
int32_t written = ecs_strbuf_written(&buf);
|
||||
for (int32_t j = 0; j < (10 - (written - start)); j ++) {
|
||||
ecs_strbuf_appendch(&buf, ' ');
|
||||
}
|
||||
|
||||
if (op->kind == EcsRuleJmpCondFalse || op->kind == EcsRuleSetCond ||
|
||||
op->kind == EcsRuleJmpNotSet)
|
||||
{
|
||||
ecs_strbuf_appendint(&buf, op->other);
|
||||
ecs_strbuf_appendch(&buf, ' ');
|
||||
}
|
||||
|
||||
hidden_chars = flecs_rule_op_ref_str(rule, &op->src, src_flags, &buf);
|
||||
|
||||
if (op->kind == EcsRuleUnion) {
|
||||
indent ++;
|
||||
}
|
||||
|
||||
if (!first_flags && !second_flags) {
|
||||
ecs_strbuf_appendstr(&buf, "\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
written = ecs_strbuf_written(&buf) - hidden_chars;
|
||||
for (int32_t j = 0; j < (30 - (written - start)); j ++) {
|
||||
ecs_strbuf_appendch(&buf, ' ');
|
||||
}
|
||||
|
||||
ecs_strbuf_appendstr(&buf, "(");
|
||||
flecs_rule_op_ref_str(rule, &op->first, first_flags, &buf);
|
||||
|
||||
if (second_flags) {
|
||||
ecs_strbuf_appendstr(&buf, ", ");
|
||||
flecs_rule_op_ref_str(rule, &op->second, second_flags, &buf);
|
||||
} else {
|
||||
switch (op->kind) {
|
||||
case EcsRulePredEqName:
|
||||
case EcsRulePredNeqName:
|
||||
case EcsRulePredEqMatch:
|
||||
case EcsRulePredNeqMatch: {
|
||||
int8_t term_index = op->term_index;
|
||||
ecs_strbuf_appendstr(&buf, ", #[yellow]\"");
|
||||
ecs_strbuf_appendstr(&buf, rule->filter.terms[term_index].second.name);
|
||||
ecs_strbuf_appendstr(&buf, "\"#[reset]");
|
||||
break;
|
||||
}
|
||||
case EcsRuleLookup: {
|
||||
ecs_var_id_t src_id = op->src.var;
|
||||
ecs_strbuf_appendstr(&buf, ", #[yellow]\"");
|
||||
ecs_strbuf_appendstr(&buf, rule->vars[src_id].lookup);
|
||||
ecs_strbuf_appendstr(&buf, "\"#[reset]");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_strbuf_appendch(&buf, ')');
|
||||
|
||||
ecs_strbuf_appendch(&buf, '\n');
|
||||
}
|
||||
|
||||
#ifdef FLECS_LOG
|
||||
char *str = ecs_strbuf_get(&buf);
|
||||
flecs_colorize_buf(str, true, &buf);
|
||||
ecs_os_free(str);
|
||||
#endif
|
||||
return ecs_strbuf_get(&buf);
|
||||
}
|
||||
|
||||
char* ecs_rule_str(
|
||||
const ecs_rule_t *rule)
|
||||
{
|
||||
return ecs_rule_str_w_profile(rule, NULL);
|
||||
}
|
||||
|
||||
const ecs_filter_t* ecs_rule_get_filter(
|
||||
const ecs_rule_t *rule)
|
||||
{
|
||||
return &rule->filter;
|
||||
}
|
||||
|
||||
const char* ecs_rule_parse_vars(
|
||||
ecs_rule_t *rule,
|
||||
ecs_iter_t *it,
|
||||
const char *expr)
|
||||
{
|
||||
ecs_poly_assert(rule, ecs_rule_t);
|
||||
ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL)
|
||||
char token[ECS_MAX_TOKEN_SIZE];
|
||||
const char *ptr = expr;
|
||||
bool paren = false;
|
||||
|
||||
const char *name = NULL;
|
||||
if (rule->filter.entity) {
|
||||
name = ecs_get_name(rule->filter.world, rule->filter.entity);
|
||||
}
|
||||
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
if (!ptr[0]) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
if (ptr[0] == '(') {
|
||||
paren = true;
|
||||
ptr = ecs_parse_ws_eol(ptr + 1);
|
||||
if (ptr[0] == ')') {
|
||||
return ptr + 1;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
ptr = ecs_parse_identifier(name, expr, ptr, token);
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int var = ecs_rule_find_var(rule, token);
|
||||
if (var == -1) {
|
||||
ecs_parser_error(name, expr, (ptr - expr),
|
||||
"unknown variable '%s'", token);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
if (ptr[0] != ':') {
|
||||
ecs_parser_error(name, expr, (ptr - expr),
|
||||
"missing ':'");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ptr = ecs_parse_ws_eol(ptr + 1);
|
||||
ptr = ecs_parse_identifier(name, expr, ptr, token);
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_entity_t val = ecs_lookup_fullpath(rule->filter.world, token);
|
||||
if (!val) {
|
||||
ecs_parser_error(name, expr, (ptr - expr),
|
||||
"unresolved entity '%s'", token);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_iter_set_var(it, var, val);
|
||||
|
||||
ptr = ecs_parse_ws_eol(ptr);
|
||||
if (ptr[0] == ')') {
|
||||
if (!paren) {
|
||||
ecs_parser_error(name, expr, (ptr - expr),
|
||||
"unexpected closing parenthesis");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ptr ++;
|
||||
break;
|
||||
} else if (ptr[0] == ',') {
|
||||
ptr ++;
|
||||
} else if (!ptr[0]) {
|
||||
if (paren) {
|
||||
ecs_parser_error(name, expr, (ptr - expr),
|
||||
"missing closing parenthesis");
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
ecs_parser_error(name, expr, (ptr - expr),
|
||||
"expected , or end of string");
|
||||
return NULL;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return ptr;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
1850
engine/libs/flecs/src/addons/rules/compile.c
Normal file
1850
engine/libs/flecs/src/addons/rules/compile.c
Normal file
File diff suppressed because it is too large
Load Diff
1966
engine/libs/flecs/src/addons/rules/engine.c
Normal file
1966
engine/libs/flecs/src/addons/rules/engine.c
Normal file
File diff suppressed because it is too large
Load Diff
306
engine/libs/flecs/src/addons/rules/rules.h
Normal file
306
engine/libs/flecs/src/addons/rules/rules.h
Normal file
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* @file addons/rules/rules.h
|
||||
* @brief Internal types and functions for rules addon.
|
||||
*/
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
#ifdef FLECS_RULES
|
||||
|
||||
typedef uint8_t ecs_var_id_t;
|
||||
typedef int16_t ecs_rule_lbl_t;
|
||||
typedef ecs_flags64_t ecs_write_flags_t;
|
||||
|
||||
#define EcsRuleMaxVarCount (64)
|
||||
#define EcsVarNone ((ecs_var_id_t)-1)
|
||||
#define EcsThisName "this"
|
||||
|
||||
/* -- Variable types -- */
|
||||
typedef enum {
|
||||
EcsVarEntity, /* Variable that stores an entity id */
|
||||
EcsVarTable, /* Variable that stores a table */
|
||||
EcsVarAny /* Used when requesting either entity or table var */
|
||||
} ecs_var_kind_t;
|
||||
|
||||
typedef struct ecs_rule_var_t {
|
||||
int8_t kind; /* variable kind (EcsVarEntity or EcsVarTable) */
|
||||
bool anonymous; /* variable is anonymous */
|
||||
ecs_var_id_t id; /* variable id */
|
||||
ecs_var_id_t table_id; /* id to table variable, if any */
|
||||
ecs_var_id_t base_id; /* id to base entity variable, for lookups */
|
||||
const char *name; /* variable name */
|
||||
const char *lookup; /* Lookup string for variable */
|
||||
#ifdef FLECS_DEBUG
|
||||
const char *label; /* for debugging */
|
||||
#endif
|
||||
} ecs_rule_var_t;
|
||||
|
||||
/* -- Instruction kinds -- */
|
||||
typedef enum {
|
||||
EcsRuleAnd, /* And operator: find or match id against variable source */
|
||||
EcsRuleAndId, /* And operator for fixed id (no wildcards/variables) */
|
||||
EcsRuleWith, /* Match id against fixed or variable source */
|
||||
EcsRuleAndAny, /* And operator with support for matching Any src/id */
|
||||
EcsRuleTrav, /* Support for transitive/reflexive queries */
|
||||
EcsRuleIdsRight, /* Find ids in use that match (R, *) wildcard */
|
||||
EcsRuleIdsLeft, /* Find ids in use that match (*, T) wildcard */
|
||||
EcsRuleEach, /* Iterate entities in table, populate entity variable */
|
||||
EcsRuleStore, /* Store table or entity in variable */
|
||||
EcsRuleReset, /* Reset value of variable to wildcard (*) */
|
||||
EcsRuleUnion, /* Combine output of multiple operations */
|
||||
EcsRuleEnd, /* Used to denote end of EcsRuleUnion block */
|
||||
EcsRuleNot, /* Sets iterator state after term was not matched */
|
||||
EcsRulePredEq, /* Test if variable is equal to, or assign to if not set */
|
||||
EcsRulePredNeq, /* Test if variable is not equal to */
|
||||
EcsRulePredEqName, /* Same as EcsRulePredEq but with matching by name */
|
||||
EcsRulePredNeqName, /* Same as EcsRulePredNeq but with matching by name */
|
||||
EcsRulePredEqMatch, /* Same as EcsRulePredEq but with fuzzy matching by name */
|
||||
EcsRulePredNeqMatch, /* Same as EcsRulePredNeq but with fuzzy matching by name */
|
||||
EcsRuleLookup, /* Lookup relative to variable */
|
||||
EcsRuleSetVars, /* Populate it.sources from variables */
|
||||
EcsRuleSetThis, /* Populate This entity variable */
|
||||
EcsRuleSetFixed, /* Set fixed source entity ids */
|
||||
EcsRuleSetIds, /* Set fixed (component) ids */
|
||||
EcsRuleContain, /* Test if table contains entity */
|
||||
EcsRulePairEq, /* Test if both elements of pair are the same */
|
||||
EcsRuleSetCond, /* Set conditional value for EcsRuleJmpCondFalse */
|
||||
EcsRuleJmpCondFalse, /* Jump if condition is false */
|
||||
EcsRuleJmpNotSet, /* Jump if variable(s) is not set */
|
||||
EcsRuleYield, /* Yield result back to application */
|
||||
EcsRuleNothing /* Must be last */
|
||||
} ecs_rule_op_kind_t;
|
||||
|
||||
/* Op flags to indicate if ecs_rule_ref_t is entity or variable */
|
||||
#define EcsRuleIsEntity (1 << 0)
|
||||
#define EcsRuleIsVar (1 << 1)
|
||||
#define EcsRuleIsSelf (1 << 6)
|
||||
|
||||
/* Op flags used to shift EcsRuleIsEntity and EcsRuleIsVar */
|
||||
#define EcsRuleSrc 0
|
||||
#define EcsRuleFirst 2
|
||||
#define EcsRuleSecond 4
|
||||
|
||||
/* References to variable or entity */
|
||||
typedef union {
|
||||
ecs_var_id_t var;
|
||||
ecs_entity_t entity;
|
||||
} ecs_rule_ref_t;
|
||||
|
||||
/* Query instruction */
|
||||
typedef struct ecs_rule_op_t {
|
||||
uint8_t kind; /* Instruction kind */
|
||||
ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */
|
||||
int8_t field_index; /* Query field corresponding with operation */
|
||||
int8_t term_index; /* Query term corresponding with operation */
|
||||
ecs_rule_lbl_t prev; /* Backtracking label (no data) */
|
||||
ecs_rule_lbl_t next; /* Forwarding label. Must come after prev */
|
||||
ecs_rule_lbl_t other; /* Misc register used for control flow */
|
||||
ecs_flags16_t match_flags; /* Flags that modify matching behavior */
|
||||
ecs_rule_ref_t src;
|
||||
ecs_rule_ref_t first;
|
||||
ecs_rule_ref_t second;
|
||||
ecs_flags64_t written; /* Bitset with variables written by op */
|
||||
} ecs_rule_op_t;
|
||||
|
||||
/* And context */
|
||||
typedef struct {
|
||||
ecs_id_record_t *idr;
|
||||
ecs_table_cache_iter_t it;
|
||||
int16_t column;
|
||||
int16_t remaining;
|
||||
} ecs_rule_and_ctx_t;
|
||||
|
||||
/* Cache for storing results of downward traversal */
|
||||
typedef struct {
|
||||
ecs_entity_t entity;
|
||||
ecs_id_record_t *idr;
|
||||
int32_t column;
|
||||
} ecs_trav_elem_t;
|
||||
|
||||
typedef struct {
|
||||
ecs_id_t id;
|
||||
ecs_id_record_t *idr;
|
||||
ecs_vec_t entities;
|
||||
bool up;
|
||||
} ecs_trav_cache_t;
|
||||
|
||||
/* Trav context */
|
||||
typedef struct {
|
||||
ecs_rule_and_ctx_t and;
|
||||
int32_t index;
|
||||
int32_t offset;
|
||||
int32_t count;
|
||||
ecs_trav_cache_t cache;
|
||||
bool yield_reflexive;
|
||||
} ecs_rule_trav_ctx_t;
|
||||
|
||||
/* Eq context */
|
||||
typedef struct {
|
||||
ecs_table_range_t range;
|
||||
int32_t index;
|
||||
int16_t name_col;
|
||||
bool redo;
|
||||
} ecs_rule_eq_ctx_t;
|
||||
|
||||
/* Each context */
|
||||
typedef struct {
|
||||
int32_t row;
|
||||
} ecs_rule_each_ctx_t;
|
||||
|
||||
/* Setthis context */
|
||||
typedef struct {
|
||||
ecs_table_range_t range;
|
||||
} ecs_rule_setthis_ctx_t;
|
||||
|
||||
/* Ids context */
|
||||
typedef struct {
|
||||
ecs_id_record_t *cur;
|
||||
} ecs_rule_ids_ctx_t;
|
||||
|
||||
/* Ctrlflow context (used with Union) */
|
||||
typedef struct {
|
||||
ecs_rule_lbl_t lbl;
|
||||
} ecs_rule_ctrlflow_ctx_t;
|
||||
|
||||
/* Condition context */
|
||||
typedef struct {
|
||||
bool cond;
|
||||
} ecs_rule_cond_ctx_t;
|
||||
|
||||
typedef struct ecs_rule_op_ctx_t {
|
||||
union {
|
||||
ecs_rule_and_ctx_t and;
|
||||
ecs_rule_trav_ctx_t trav;
|
||||
ecs_rule_ids_ctx_t ids;
|
||||
ecs_rule_eq_ctx_t eq;
|
||||
ecs_rule_each_ctx_t each;
|
||||
ecs_rule_setthis_ctx_t setthis;
|
||||
ecs_rule_ctrlflow_ctx_t ctrlflow;
|
||||
ecs_rule_cond_ctx_t cond;
|
||||
} is;
|
||||
} ecs_rule_op_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
/* Labels used for control flow */
|
||||
ecs_rule_lbl_t lbl_union;
|
||||
ecs_rule_lbl_t lbl_not;
|
||||
ecs_rule_lbl_t lbl_option;
|
||||
ecs_rule_lbl_t lbl_cond_eval;
|
||||
ecs_rule_lbl_t lbl_or;
|
||||
ecs_rule_lbl_t lbl_none;
|
||||
ecs_rule_lbl_t lbl_prev; /* If set, use this as default value for prev */
|
||||
ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */
|
||||
bool in_or; /* Whether we're in an or chain */
|
||||
} ecs_rule_compile_ctrlflow_t;
|
||||
|
||||
/* Rule compiler state */
|
||||
typedef struct {
|
||||
ecs_vec_t *ops;
|
||||
ecs_write_flags_t written; /* Bitmask to check which variables have been written */
|
||||
ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */
|
||||
|
||||
/* Maintain control flow per scope */
|
||||
ecs_rule_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX];
|
||||
ecs_rule_compile_ctrlflow_t *cur; /* Current scope */
|
||||
|
||||
int32_t scope; /* Nesting level of query scopes */
|
||||
ecs_flags32_t scope_is_not; /* Whether scope is prefixed with not */
|
||||
} ecs_rule_compile_ctx_t;
|
||||
|
||||
/* Rule run state */
|
||||
typedef struct {
|
||||
uint64_t *written; /* Bitset to check which variables have been written */
|
||||
ecs_rule_lbl_t op_index; /* Currently evaluated operation */
|
||||
ecs_rule_lbl_t prev_index; /* Previously evaluated operation */
|
||||
ecs_rule_lbl_t jump; /* Set by control flow operations to jump to operation */
|
||||
ecs_var_t *vars; /* Variable storage */
|
||||
ecs_iter_t *it; /* Iterator */
|
||||
ecs_rule_op_ctx_t *op_ctx; /* Operation context (stack) */
|
||||
ecs_world_t *world; /* Reference to world */
|
||||
const ecs_rule_t *rule; /* Reference to rule */
|
||||
const ecs_rule_var_t *rule_vars; /* Reference to rule variable array */
|
||||
} ecs_rule_run_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
ecs_rule_var_t var;
|
||||
const char *name;
|
||||
} ecs_rule_var_cache_t;
|
||||
|
||||
struct ecs_rule_t {
|
||||
ecs_header_t hdr; /* Poly header */
|
||||
ecs_filter_t filter; /* Filter */
|
||||
|
||||
/* Variables */
|
||||
ecs_rule_var_t *vars; /* Variables */
|
||||
int32_t var_count; /* Number of variables */
|
||||
int32_t var_pub_count; /* Number of public variables */
|
||||
bool has_table_this; /* Does rule have [$this] */
|
||||
ecs_hashmap_t tvar_index; /* Name index for table variables */
|
||||
ecs_hashmap_t evar_index; /* Name index for entity variables */
|
||||
ecs_rule_var_cache_t vars_cache; /* For trivial rules with only This variables */
|
||||
char **var_names; /* Array with variable names for iterator */
|
||||
ecs_var_id_t *src_vars; /* Array with ids to source variables for fields */
|
||||
|
||||
ecs_rule_op_t *ops; /* Operations */
|
||||
int32_t op_count; /* Number of operations */
|
||||
|
||||
/* Mixins */
|
||||
ecs_iterable_t iterable;
|
||||
ecs_poly_dtor_t dtor;
|
||||
|
||||
#ifdef FLECS_DEBUG
|
||||
int32_t var_size; /* Used for out of bounds check during compilation */
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Convert integer to label */
|
||||
ecs_rule_lbl_t flecs_itolbl(
|
||||
int64_t val);
|
||||
|
||||
/* Get ref flags (IsEntity) or IsVar) for ref (Src, First, Second) */
|
||||
ecs_flags16_t flecs_rule_ref_flags(
|
||||
ecs_flags16_t flags,
|
||||
ecs_flags16_t kind);
|
||||
|
||||
/* Check if variable is written */
|
||||
bool flecs_rule_is_written(
|
||||
ecs_var_id_t var_id,
|
||||
uint64_t written);
|
||||
|
||||
/* Check if ref is written (calls flecs_rule_is_written)*/
|
||||
bool flecs_ref_is_written(
|
||||
const ecs_rule_op_t *op,
|
||||
const ecs_rule_ref_t *ref,
|
||||
ecs_flags16_t kind,
|
||||
uint64_t written);
|
||||
|
||||
/* Compile filter to list of operations */
|
||||
int flecs_rule_compile(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage,
|
||||
ecs_rule_t *rule);
|
||||
|
||||
/* Get allocator from iterator */
|
||||
ecs_allocator_t* flecs_rule_get_allocator(
|
||||
const ecs_iter_t *it);
|
||||
|
||||
/* Find all entities when traversing downwards */
|
||||
void flecs_rule_get_down_cache(
|
||||
const ecs_rule_run_ctx_t *ctx,
|
||||
ecs_trav_cache_t *cache,
|
||||
ecs_entity_t trav,
|
||||
ecs_entity_t entity);
|
||||
|
||||
/* Find all entities when traversing upwards */
|
||||
void flecs_rule_get_up_cache(
|
||||
const ecs_rule_run_ctx_t *ctx,
|
||||
ecs_trav_cache_t *cache,
|
||||
ecs_entity_t trav,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Free traversal cache */
|
||||
void flecs_rule_trav_cache_fini(
|
||||
ecs_allocator_t *a,
|
||||
ecs_trav_cache_t *cache);
|
||||
|
||||
#endif
|
||||
151
engine/libs/flecs/src/addons/rules/trav_cache.c
Normal file
151
engine/libs/flecs/src/addons/rules/trav_cache.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* @file addons/rules/trav_cache.c
|
||||
* @brief Cache that stores the result of graph traversal.
|
||||
*/
|
||||
|
||||
#include "rules.h"
|
||||
|
||||
#ifdef FLECS_RULES
|
||||
|
||||
static
|
||||
void flecs_rule_build_down_cache(
|
||||
ecs_world_t *world,
|
||||
ecs_allocator_t *a,
|
||||
const ecs_rule_run_ctx_t *ctx,
|
||||
ecs_trav_cache_t *cache,
|
||||
ecs_entity_t trav,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity));
|
||||
if (!idr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities,
|
||||
ecs_trav_elem_t);
|
||||
elem->entity = entity;
|
||||
elem->idr = idr;
|
||||
|
||||
ecs_table_cache_iter_t it;
|
||||
if (flecs_table_cache_iter(&idr->cache, &it)) {
|
||||
ecs_table_record_t *tr;
|
||||
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
|
||||
ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_table_t *table = tr->hdr.table;
|
||||
if (!table->_->traversable_count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t i, count = ecs_table_count(table);
|
||||
ecs_entity_t *entities = table->data.entities.array;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_record_t *r = flecs_entities_get(world, entities[i]);
|
||||
if (r->row & EcsEntityIsTraversable) {
|
||||
flecs_rule_build_down_cache(
|
||||
world, a, ctx, cache, trav, entities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_rule_build_up_cache(
|
||||
ecs_world_t *world,
|
||||
ecs_allocator_t *a,
|
||||
const ecs_rule_run_ctx_t *ctx,
|
||||
ecs_trav_cache_t *cache,
|
||||
ecs_entity_t trav,
|
||||
ecs_table_t *table,
|
||||
const ecs_table_record_t *tr,
|
||||
int32_t root_column)
|
||||
{
|
||||
ecs_id_t *ids = table->type.array;
|
||||
int32_t i = tr->index, end = i + tr->count;
|
||||
bool is_root = root_column == -1;
|
||||
|
||||
for (; i < end; i ++) {
|
||||
ecs_entity_t second = ecs_pair_second(world, ids[i]);
|
||||
if (is_root) {
|
||||
root_column = i;
|
||||
}
|
||||
|
||||
ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities,
|
||||
ecs_trav_elem_t);
|
||||
el->entity = second;
|
||||
el->column = root_column;
|
||||
el->idr = NULL;
|
||||
|
||||
ecs_record_t *r = flecs_entities_get_any(world, second);
|
||||
if (r->table) {
|
||||
ecs_table_record_t *r_tr = flecs_id_record_get_table(
|
||||
cache->idr, r->table);
|
||||
if (!r_tr) {
|
||||
return;
|
||||
}
|
||||
flecs_rule_build_up_cache(world, a, ctx, cache, trav, r->table,
|
||||
r_tr, root_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_rule_trav_cache_fini(
|
||||
ecs_allocator_t *a,
|
||||
ecs_trav_cache_t *cache)
|
||||
{
|
||||
ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t);
|
||||
}
|
||||
|
||||
void flecs_rule_get_down_cache(
|
||||
const ecs_rule_run_ctx_t *ctx,
|
||||
ecs_trav_cache_t *cache,
|
||||
ecs_entity_t trav,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
if (cache->id != ecs_pair(trav, entity) || cache->up) {
|
||||
ecs_world_t *world = ctx->it->real_world;
|
||||
ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it);
|
||||
ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t);
|
||||
flecs_rule_build_down_cache(world, a, ctx, cache, trav, entity);
|
||||
cache->id = ecs_pair(trav, entity);
|
||||
cache->up = false;
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_rule_get_up_cache(
|
||||
const ecs_rule_run_ctx_t *ctx,
|
||||
ecs_trav_cache_t *cache,
|
||||
ecs_entity_t trav,
|
||||
ecs_table_t *table)
|
||||
{
|
||||
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_world_t *world = ctx->it->real_world;
|
||||
ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it);
|
||||
|
||||
ecs_id_record_t *idr = cache->idr;
|
||||
if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) {
|
||||
idr = cache->idr = flecs_id_record_get(world,
|
||||
ecs_pair(trav, EcsWildcard));
|
||||
if (!idr) {
|
||||
ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
|
||||
if (!tr) {
|
||||
ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t);
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_id_t id = table->type.array[tr->index];
|
||||
|
||||
if (cache->id != id || !cache->up) {
|
||||
ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t);
|
||||
flecs_rule_build_up_cache(world, a, ctx, cache, trav, table, tr, -1);
|
||||
cache->id = id;
|
||||
cache->up = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
443
engine/libs/flecs/src/addons/snapshot.c
Normal file
443
engine/libs/flecs/src/addons/snapshot.c
Normal file
@@ -0,0 +1,443 @@
|
||||
/**
|
||||
* @file addons/snapshot.c
|
||||
* @brief Snapshot addon.
|
||||
*/
|
||||
|
||||
#include "flecs.h"
|
||||
|
||||
#ifdef FLECS_SNAPSHOT
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
/* World snapshot */
|
||||
struct ecs_snapshot_t {
|
||||
ecs_world_t *world;
|
||||
ecs_entity_index_t entity_index;
|
||||
ecs_vec_t tables;
|
||||
uint64_t last_id;
|
||||
};
|
||||
|
||||
/** Small footprint data structure for storing data associated with a table. */
|
||||
typedef struct ecs_table_leaf_t {
|
||||
ecs_table_t *table;
|
||||
ecs_type_t type;
|
||||
ecs_data_t *data;
|
||||
} ecs_table_leaf_t;
|
||||
|
||||
static
|
||||
ecs_data_t* flecs_duplicate_data(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_data_t *main_data)
|
||||
{
|
||||
if (!ecs_table_count(table)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_data_t *result = ecs_os_calloc_t(ecs_data_t);
|
||||
int32_t i, column_count = table->column_count;
|
||||
result->columns = flecs_wdup_n(world, ecs_column_t, column_count,
|
||||
main_data->columns);
|
||||
|
||||
/* Copy entities */
|
||||
ecs_allocator_t *a = &world->allocator;
|
||||
result->entities = ecs_vec_copy_t(a, &main_data->entities, ecs_entity_t);
|
||||
|
||||
/* Copy each column */
|
||||
for (i = 0; i < column_count; i ++) {
|
||||
ecs_column_t *column = &result->columns[i];
|
||||
ecs_type_info_t *ti = column->ti;
|
||||
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
int32_t size = ti->size;
|
||||
ecs_copy_t copy = ti->hooks.copy;
|
||||
if (copy) {
|
||||
ecs_vec_t dst = ecs_vec_copy(a, &column->data, size);
|
||||
int32_t count = ecs_vec_count(&column->data);
|
||||
void *dst_ptr = ecs_vec_first(&dst);
|
||||
void *src_ptr = ecs_vec_first(&column->data);
|
||||
|
||||
ecs_xtor_t ctor = ti->hooks.ctor;
|
||||
if (ctor) {
|
||||
ctor(dst_ptr, count, ti);
|
||||
}
|
||||
|
||||
copy(dst_ptr, src_ptr, count, ti);
|
||||
column->data = dst;
|
||||
} else {
|
||||
column->data = ecs_vec_copy(a, &column->data, size);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static
|
||||
void snapshot_table(
|
||||
const ecs_world_t *world,
|
||||
ecs_snapshot_t *snapshot,
|
||||
ecs_table_t *table)
|
||||
{
|
||||
if (table->flags & EcsTableHasBuiltins) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_table_leaf_t *l = ecs_vec_get_t(
|
||||
&snapshot->tables, ecs_table_leaf_t, (int32_t)table->id);
|
||||
ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
l->table = table;
|
||||
l->type = flecs_type_copy(
|
||||
ECS_CONST_CAST(ecs_world_t*, world), &table->type);
|
||||
l->data = flecs_duplicate_data(
|
||||
ECS_CONST_CAST(ecs_world_t*, world), table, &table->data);
|
||||
}
|
||||
|
||||
static
|
||||
ecs_snapshot_t* snapshot_create(
|
||||
const ecs_world_t *world,
|
||||
const ecs_entity_index_t *entity_index,
|
||||
ecs_iter_t *iter,
|
||||
ecs_iter_next_action_t next)
|
||||
{
|
||||
ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t);
|
||||
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
|
||||
|
||||
ecs_run_aperiodic(ECS_CONST_CAST(ecs_world_t*, world), 0);
|
||||
|
||||
result->world = ECS_CONST_CAST(ecs_world_t*, world);
|
||||
|
||||
/* If no iterator is provided, the snapshot will be taken of the entire
|
||||
* world, and we can simply copy the entity index as it will be restored
|
||||
* entirely upon snapshote restore. */
|
||||
if (!iter && entity_index) {
|
||||
flecs_entities_copy(&result->entity_index, entity_index);
|
||||
}
|
||||
|
||||
/* Create vector with as many elements as tables, so we can store the
|
||||
* snapshot tables at their element ids. When restoring a snapshot, the code
|
||||
* will run a diff between the tables in the world and the snapshot, to see
|
||||
* which of the world tables still exist, no longer exist, or need to be
|
||||
* deleted. */
|
||||
uint64_t t, table_count = flecs_sparse_last_id(&world->store.tables) + 1;
|
||||
ecs_vec_init_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count);
|
||||
ecs_vec_set_count_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count);
|
||||
ecs_table_leaf_t *arr = ecs_vec_first_t(&result->tables, ecs_table_leaf_t);
|
||||
|
||||
/* Array may have holes, so initialize with 0 */
|
||||
ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count);
|
||||
|
||||
/* Iterate tables in iterator */
|
||||
if (iter) {
|
||||
while (next(iter)) {
|
||||
ecs_table_t *table = iter->table;
|
||||
snapshot_table(world, result, table);
|
||||
}
|
||||
} else {
|
||||
for (t = 1; t < table_count; t ++) {
|
||||
ecs_table_t *table = flecs_sparse_get_t(
|
||||
&world->store.tables, ecs_table_t, t);
|
||||
snapshot_table(world, result, table);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Create a snapshot */
|
||||
ecs_snapshot_t* ecs_snapshot_take(
|
||||
ecs_world_t *stage)
|
||||
{
|
||||
const ecs_world_t *world = ecs_get_world(stage);
|
||||
|
||||
ecs_snapshot_t *result = snapshot_create(
|
||||
world, ecs_eis(world), NULL, NULL);
|
||||
|
||||
result->last_id = flecs_entities_max_id(world);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Create a filtered snapshot */
|
||||
ecs_snapshot_t* ecs_snapshot_take_w_iter(
|
||||
ecs_iter_t *iter)
|
||||
{
|
||||
ecs_world_t *world = iter->world;
|
||||
ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_snapshot_t *result = snapshot_create(
|
||||
world, ecs_eis(world), iter, iter ? iter->next : NULL);
|
||||
|
||||
result->last_id = flecs_entities_max_id(world);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Restoring an unfiltered snapshot restores the world to the exact state it was
|
||||
* when the snapshot was taken. */
|
||||
static
|
||||
void restore_unfiltered(
|
||||
ecs_world_t *world,
|
||||
ecs_snapshot_t *snapshot)
|
||||
{
|
||||
flecs_entity_index_restore(ecs_eis(world), &snapshot->entity_index);
|
||||
flecs_entity_index_fini(&snapshot->entity_index);
|
||||
|
||||
flecs_entities_max_id(world) = snapshot->last_id;
|
||||
|
||||
ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t);
|
||||
int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables);
|
||||
int32_t snapshot_count = ecs_vec_count(&snapshot->tables);
|
||||
|
||||
for (i = 1; i <= count; i ++) {
|
||||
ecs_table_t *world_table = flecs_sparse_get_t(
|
||||
&world->store.tables, ecs_table_t, (uint32_t)i);
|
||||
|
||||
if (world_table && (world_table->flags & EcsTableHasBuiltins)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_table_leaf_t *snapshot_table = NULL;
|
||||
if (i < snapshot_count) {
|
||||
snapshot_table = &leafs[i];
|
||||
if (!snapshot_table->table) {
|
||||
snapshot_table = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the world table no longer exists but the snapshot table does,
|
||||
* reinsert it */
|
||||
if (!world_table && snapshot_table) {
|
||||
ecs_table_t *table = flecs_table_find_or_create(world,
|
||||
&snapshot_table->type);
|
||||
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (snapshot_table->data) {
|
||||
flecs_table_replace_data(world, table, snapshot_table->data);
|
||||
}
|
||||
|
||||
/* If the world table still exists, replace its data */
|
||||
} else if (world_table && snapshot_table) {
|
||||
ecs_assert(snapshot_table->table == world_table,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (snapshot_table->data) {
|
||||
flecs_table_replace_data(
|
||||
world, world_table, snapshot_table->data);
|
||||
} else {
|
||||
flecs_table_clear_data(
|
||||
world, world_table, &world_table->data);
|
||||
flecs_table_init_data(world, world_table);
|
||||
}
|
||||
|
||||
/* If the snapshot table doesn't exist, this table was created after the
|
||||
* snapshot was taken and needs to be deleted */
|
||||
} else if (world_table && !snapshot_table) {
|
||||
/* Deleting a table invokes OnRemove triggers & updates the entity
|
||||
* index. That is not what we want, since entities may no longer be
|
||||
* valid (if they don't exist in the snapshot) or may have been
|
||||
* restored in a different table. Therefore first clear the data
|
||||
* from the table (which doesn't invoke triggers), and then delete
|
||||
* the table. */
|
||||
flecs_table_clear_data(world, world_table, &world_table->data);
|
||||
flecs_delete_table(world, world_table);
|
||||
|
||||
/* If there is no world & snapshot table, nothing needs to be done */
|
||||
} else { }
|
||||
|
||||
if (snapshot_table) {
|
||||
ecs_os_free(snapshot_table->data);
|
||||
flecs_type_free(world, &snapshot_table->type);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now that all tables have been restored and world is in a consistent
|
||||
* state, run OnSet systems */
|
||||
int32_t world_count = flecs_sparse_count(&world->store.tables);
|
||||
for (i = 0; i < world_count; i ++) {
|
||||
ecs_table_t *table = flecs_sparse_get_dense_t(
|
||||
&world->store.tables, ecs_table_t, i);
|
||||
if (table->flags & EcsTableHasBuiltins) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t tcount = ecs_table_count(table);
|
||||
if (tcount) {
|
||||
int32_t j, storage_count = table->column_count;
|
||||
for (j = 0; j < storage_count; j ++) {
|
||||
ecs_type_t type = {
|
||||
.array = &table->data.columns[j].id,
|
||||
.count = 1
|
||||
};
|
||||
flecs_notify_on_set(world, table, 0, tcount, &type, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Restoring a filtered snapshots only restores the entities in the snapshot
|
||||
* to their previous state. */
|
||||
static
|
||||
void restore_filtered(
|
||||
ecs_world_t *world,
|
||||
ecs_snapshot_t *snapshot)
|
||||
{
|
||||
ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t);
|
||||
int32_t l = 0, snapshot_count = ecs_vec_count(&snapshot->tables);
|
||||
|
||||
for (l = 0; l < snapshot_count; l ++) {
|
||||
ecs_table_leaf_t *snapshot_table = &leafs[l];
|
||||
ecs_table_t *table = snapshot_table->table;
|
||||
|
||||
if (!table) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_data_t *data = snapshot_table->data;
|
||||
if (!data) {
|
||||
flecs_type_free(world, &snapshot_table->type);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Delete entity from storage first, so that when we restore it to the
|
||||
* current table we can be sure that there won't be any duplicates */
|
||||
int32_t i, entity_count = ecs_vec_count(&data->entities);
|
||||
ecs_entity_t *entities = ecs_vec_first(
|
||||
&snapshot_table->data->entities);
|
||||
for (i = 0; i < entity_count; i ++) {
|
||||
ecs_entity_t e = entities[i];
|
||||
ecs_record_t *r = flecs_entities_try(world, e);
|
||||
if (r && r->table) {
|
||||
flecs_table_delete(world, r->table,
|
||||
ECS_RECORD_TO_ROW(r->row), true);
|
||||
} else {
|
||||
/* Make sure that the entity has the same generation count */
|
||||
flecs_entities_set_generation(world, e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Merge data from snapshot table with world table */
|
||||
int32_t old_count = ecs_table_count(snapshot_table->table);
|
||||
int32_t new_count = flecs_table_data_count(snapshot_table->data);
|
||||
|
||||
flecs_table_merge(world, table, table, &table->data, snapshot_table->data);
|
||||
|
||||
/* Run OnSet systems for merged entities */
|
||||
if (new_count) {
|
||||
int32_t j, storage_count = table->column_count;
|
||||
for (j = 0; j < storage_count; j ++) {
|
||||
ecs_type_t type = {
|
||||
.array = &table->data.columns[j].id,
|
||||
.count = 1
|
||||
};
|
||||
flecs_notify_on_set(
|
||||
world, table, old_count, new_count, &type, true);
|
||||
}
|
||||
}
|
||||
|
||||
flecs_wfree_n(world, ecs_column_t, table->column_count,
|
||||
snapshot_table->data->columns);
|
||||
ecs_os_free(snapshot_table->data);
|
||||
flecs_type_free(world, &snapshot_table->type);
|
||||
}
|
||||
}
|
||||
|
||||
/** Restore a snapshot */
|
||||
void ecs_snapshot_restore(
|
||||
ecs_world_t *world,
|
||||
ecs_snapshot_t *snapshot)
|
||||
{
|
||||
ecs_run_aperiodic(world, 0);
|
||||
|
||||
if (flecs_entity_index_count(&snapshot->entity_index) > 0) {
|
||||
/* Unfiltered snapshots have a copy of the entity index which is
|
||||
* copied back entirely when the snapshot is restored */
|
||||
restore_unfiltered(world, snapshot);
|
||||
} else {
|
||||
restore_filtered(world, snapshot);
|
||||
}
|
||||
|
||||
ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t);
|
||||
|
||||
ecs_os_free(snapshot);
|
||||
}
|
||||
|
||||
ecs_iter_t ecs_snapshot_iter(
|
||||
ecs_snapshot_t *snapshot)
|
||||
{
|
||||
ecs_snapshot_iter_t iter = {
|
||||
.tables = snapshot->tables,
|
||||
.index = 0
|
||||
};
|
||||
|
||||
return (ecs_iter_t){
|
||||
.world = snapshot->world,
|
||||
.table_count = ecs_vec_count(&snapshot->tables),
|
||||
.priv.iter.snapshot = iter,
|
||||
.next = ecs_snapshot_next
|
||||
};
|
||||
}
|
||||
|
||||
bool ecs_snapshot_next(
|
||||
ecs_iter_t *it)
|
||||
{
|
||||
ecs_snapshot_iter_t *iter = &it->priv.iter.snapshot;
|
||||
ecs_table_leaf_t *tables = ecs_vec_first_t(&iter->tables, ecs_table_leaf_t);
|
||||
int32_t count = ecs_vec_count(&iter->tables);
|
||||
int32_t i;
|
||||
|
||||
for (i = iter->index; i < count; i ++) {
|
||||
ecs_table_t *table = tables[i].table;
|
||||
if (!table) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_data_t *data = tables[i].data;
|
||||
|
||||
it->table = table;
|
||||
it->count = ecs_table_count(table);
|
||||
if (data) {
|
||||
it->entities = ecs_vec_first(&data->entities);
|
||||
} else {
|
||||
it->entities = NULL;
|
||||
}
|
||||
|
||||
ECS_BIT_SET(it->flags, EcsIterIsValid);
|
||||
iter->index = i + 1;
|
||||
|
||||
goto yield;
|
||||
}
|
||||
|
||||
ECS_BIT_CLEAR(it->flags, EcsIterIsValid);
|
||||
return false;
|
||||
|
||||
yield:
|
||||
ECS_BIT_CLEAR(it->flags, EcsIterIsValid);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Cleanup snapshot */
|
||||
void ecs_snapshot_free(
|
||||
ecs_snapshot_t *snapshot)
|
||||
{
|
||||
flecs_entity_index_fini(&snapshot->entity_index);
|
||||
|
||||
ecs_table_leaf_t *tables = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t);
|
||||
int32_t i, count = ecs_vec_count(&snapshot->tables);
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_table_leaf_t *snapshot_table = &tables[i];
|
||||
ecs_table_t *table = snapshot_table->table;
|
||||
if (table) {
|
||||
ecs_data_t *data = snapshot_table->data;
|
||||
if (data) {
|
||||
flecs_table_clear_data(snapshot->world, table, data);
|
||||
ecs_os_free(data);
|
||||
}
|
||||
flecs_type_free(snapshot->world, &snapshot_table->type);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t);
|
||||
ecs_os_free(snapshot);
|
||||
}
|
||||
|
||||
#endif
|
||||
864
engine/libs/flecs/src/addons/stats.c
Normal file
864
engine/libs/flecs/src/addons/stats.c
Normal file
@@ -0,0 +1,864 @@
|
||||
/**
|
||||
* @file addons/stats.c
|
||||
* @brief Stats addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_SYSTEM
|
||||
#include "../addons/system/system.h"
|
||||
#endif
|
||||
|
||||
#ifdef FLECS_PIPELINE
|
||||
#include "../addons/pipeline/pipeline.h"
|
||||
#endif
|
||||
|
||||
#ifdef FLECS_STATS
|
||||
|
||||
#define ECS_GAUGE_RECORD(m, t, value)\
|
||||
flecs_gauge_record(m, t, (ecs_float_t)(value))
|
||||
|
||||
#define ECS_COUNTER_RECORD(m, t, value)\
|
||||
flecs_counter_record(m, t, (double)(value))
|
||||
|
||||
#define ECS_METRIC_FIRST(stats)\
|
||||
ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t)))
|
||||
|
||||
#define ECS_METRIC_LAST(stats)\
|
||||
ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t)))
|
||||
|
||||
static
|
||||
int32_t t_next(
|
||||
int32_t t)
|
||||
{
|
||||
return (t + 1) % ECS_STAT_WINDOW;
|
||||
}
|
||||
|
||||
static
|
||||
int32_t t_prev(
|
||||
int32_t t)
|
||||
{
|
||||
return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_gauge_record(
|
||||
ecs_metric_t *m,
|
||||
int32_t t,
|
||||
ecs_float_t value)
|
||||
{
|
||||
m->gauge.avg[t] = value;
|
||||
m->gauge.min[t] = value;
|
||||
m->gauge.max[t] = value;
|
||||
}
|
||||
|
||||
static
|
||||
double flecs_counter_record(
|
||||
ecs_metric_t *m,
|
||||
int32_t t,
|
||||
double value)
|
||||
{
|
||||
int32_t tp = t_prev(t);
|
||||
double prev = m->counter.value[tp];
|
||||
m->counter.value[t] = value;
|
||||
double gauge_value = value - prev;
|
||||
if (gauge_value < 0) {
|
||||
gauge_value = 0; /* Counters are monotonically increasing */
|
||||
}
|
||||
flecs_gauge_record(m, t, (ecs_float_t)gauge_value);
|
||||
return gauge_value;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_metric_print(
|
||||
const char *name,
|
||||
ecs_float_t value)
|
||||
{
|
||||
ecs_size_t len = ecs_os_strlen(name);
|
||||
ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_gauge_print(
|
||||
const char *name,
|
||||
int32_t t,
|
||||
const ecs_metric_t *m)
|
||||
{
|
||||
flecs_metric_print(name, m->gauge.avg[t]);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_counter_print(
|
||||
const char *name,
|
||||
int32_t t,
|
||||
const ecs_metric_t *m)
|
||||
{
|
||||
flecs_metric_print(name, m->counter.rate.avg[t]);
|
||||
}
|
||||
|
||||
void ecs_metric_reduce(
|
||||
ecs_metric_t *dst,
|
||||
const ecs_metric_t *src,
|
||||
int32_t t_dst,
|
||||
int32_t t_src)
|
||||
{
|
||||
ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
bool min_set = false;
|
||||
dst->gauge.avg[t_dst] = 0;
|
||||
dst->gauge.min[t_dst] = 0;
|
||||
dst->gauge.max[t_dst] = 0;
|
||||
|
||||
ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW;
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < ECS_STAT_WINDOW; i ++) {
|
||||
int32_t t = (t_src + i) % ECS_STAT_WINDOW;
|
||||
dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow;
|
||||
|
||||
if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) {
|
||||
dst->gauge.min[t_dst] = src->gauge.min[t];
|
||||
min_set = true;
|
||||
}
|
||||
if ((src->gauge.max[t] > dst->gauge.max[t_dst])) {
|
||||
dst->gauge.max[t_dst] = src->gauge.max[t];
|
||||
}
|
||||
}
|
||||
|
||||
dst->counter.value[t_dst] = src->counter.value[t_src];
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void ecs_metric_reduce_last(
|
||||
ecs_metric_t *m,
|
||||
int32_t prev,
|
||||
int32_t count)
|
||||
{
|
||||
ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
int32_t t = t_next(prev);
|
||||
|
||||
if (m->gauge.min[t] < m->gauge.min[prev]) {
|
||||
m->gauge.min[prev] = m->gauge.min[t];
|
||||
}
|
||||
|
||||
if (m->gauge.max[t] > m->gauge.max[prev]) {
|
||||
m->gauge.max[prev] = m->gauge.max[t];
|
||||
}
|
||||
|
||||
ecs_float_t fcount = (ecs_float_t)(count + 1);
|
||||
ecs_float_t cur = m->gauge.avg[prev];
|
||||
ecs_float_t next = m->gauge.avg[t];
|
||||
|
||||
cur *= ((fcount - 1) / fcount);
|
||||
next *= 1 / fcount;
|
||||
|
||||
m->gauge.avg[prev] = cur + next;
|
||||
m->counter.value[prev] = m->counter.value[t];
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void ecs_metric_copy(
|
||||
ecs_metric_t *m,
|
||||
int32_t dst,
|
||||
int32_t src)
|
||||
{
|
||||
ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
m->gauge.avg[dst] = m->gauge.avg[src];
|
||||
m->gauge.min[dst] = m->gauge.min[src];
|
||||
m->gauge.max[dst] = m->gauge.max[src];
|
||||
m->counter.value[dst] = m->counter.value[src];
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_stats_reduce(
|
||||
ecs_metric_t *dst_cur,
|
||||
ecs_metric_t *dst_last,
|
||||
ecs_metric_t *src_cur,
|
||||
int32_t t_dst,
|
||||
int32_t t_src)
|
||||
{
|
||||
for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) {
|
||||
ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_stats_reduce_last(
|
||||
ecs_metric_t *dst_cur,
|
||||
ecs_metric_t *dst_last,
|
||||
ecs_metric_t *src_cur,
|
||||
int32_t t_dst,
|
||||
int32_t t_src,
|
||||
int32_t count)
|
||||
{
|
||||
int32_t t_dst_next = t_next(t_dst);
|
||||
for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) {
|
||||
/* Reduce into previous value */
|
||||
ecs_metric_reduce_last(dst_cur, t_dst, count);
|
||||
|
||||
/* Restore old value */
|
||||
dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src];
|
||||
dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src];
|
||||
dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src];
|
||||
dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src];
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_stats_repeat_last(
|
||||
ecs_metric_t *cur,
|
||||
ecs_metric_t *last,
|
||||
int32_t t)
|
||||
{
|
||||
int32_t prev = t_prev(t);
|
||||
for (; cur <= last; cur ++) {
|
||||
ecs_metric_copy(cur, t, prev);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_stats_copy_last(
|
||||
ecs_metric_t *dst_cur,
|
||||
ecs_metric_t *dst_last,
|
||||
ecs_metric_t *src_cur,
|
||||
int32_t t_dst,
|
||||
int32_t t_src)
|
||||
{
|
||||
for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) {
|
||||
dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src];
|
||||
dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src];
|
||||
dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src];
|
||||
dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src];
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_world_stats_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_world_stats_t *s)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
world = ecs_get_world(world);
|
||||
|
||||
int32_t t = s->t = t_next(s->t);
|
||||
|
||||
double delta_frame_count =
|
||||
ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total);
|
||||
ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total);
|
||||
ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total);
|
||||
ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total);
|
||||
ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame);
|
||||
ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame);
|
||||
ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id);
|
||||
|
||||
double delta_world_time =
|
||||
ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw);
|
||||
ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total);
|
||||
ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total);
|
||||
ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total);
|
||||
ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total);
|
||||
ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total);
|
||||
ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total);
|
||||
ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time);
|
||||
if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) {
|
||||
ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count));
|
||||
} else {
|
||||
ECS_GAUGE_RECORD(&s->performance.fps, t, 0);
|
||||
}
|
||||
|
||||
ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world));
|
||||
ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world));
|
||||
|
||||
ECS_GAUGE_RECORD(&s->ids.count, t, world->info.id_count);
|
||||
ECS_GAUGE_RECORD(&s->ids.tag_count, t, world->info.tag_id_count);
|
||||
ECS_GAUGE_RECORD(&s->ids.component_count, t, world->info.component_id_count);
|
||||
ECS_GAUGE_RECORD(&s->ids.pair_count, t, world->info.pair_id_count);
|
||||
ECS_GAUGE_RECORD(&s->ids.wildcard_count, t, world->info.wildcard_id_count);
|
||||
ECS_GAUGE_RECORD(&s->ids.type_count, t, ecs_sparse_count(&world->type_info));
|
||||
ECS_COUNTER_RECORD(&s->ids.create_count, t, world->info.id_create_total);
|
||||
ECS_COUNTER_RECORD(&s->ids.delete_count, t, world->info.id_delete_total);
|
||||
|
||||
ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery));
|
||||
ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver));
|
||||
if (ecs_is_alive(world, EcsSystem)) {
|
||||
ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem));
|
||||
}
|
||||
ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total);
|
||||
ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total);
|
||||
ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count);
|
||||
ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count);
|
||||
ECS_GAUGE_RECORD(&s->tables.tag_only_count, t, world->info.tag_table_count);
|
||||
ECS_GAUGE_RECORD(&s->tables.trivial_only_count, t, world->info.trivial_table_count);
|
||||
ECS_GAUGE_RECORD(&s->tables.storage_count, t, world->info.table_storage_count);
|
||||
ECS_GAUGE_RECORD(&s->tables.record_count, t, world->info.table_record_count);
|
||||
|
||||
ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.get_mut_count, t, world->info.cmd.get_mut_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count);
|
||||
ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count);
|
||||
|
||||
int64_t outstanding_allocs = ecs_os_api_malloc_count +
|
||||
ecs_os_api_calloc_count - ecs_os_api_free_count;
|
||||
ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count);
|
||||
ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count);
|
||||
ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count);
|
||||
ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs);
|
||||
|
||||
outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count;
|
||||
ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count);
|
||||
ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count);
|
||||
ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs);
|
||||
|
||||
outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count;
|
||||
ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count);
|
||||
ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count);
|
||||
ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs);
|
||||
|
||||
#ifdef FLECS_REST
|
||||
ECS_COUNTER_RECORD(&s->rest.request_count, t, ecs_rest_request_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.entity_count, t, ecs_rest_entity_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.entity_error_count, t, ecs_rest_entity_error_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.query_count, t, ecs_rest_query_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.query_error_count, t, ecs_rest_query_error_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.query_name_count, t, ecs_rest_query_name_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.query_name_error_count, t, ecs_rest_query_name_error_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.query_name_from_cache_count, t, ecs_rest_query_name_from_cache_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.enable_count, t, ecs_rest_enable_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.enable_error_count, t, ecs_rest_enable_error_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.world_stats_count, t, ecs_rest_world_stats_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.pipeline_stats_count, t, ecs_rest_pipeline_stats_count);
|
||||
ECS_COUNTER_RECORD(&s->rest.stats_error_count, t, ecs_rest_stats_error_count);
|
||||
#endif
|
||||
|
||||
#ifdef FLECS_HTTP
|
||||
ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count);
|
||||
ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count);
|
||||
ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count);
|
||||
ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count);
|
||||
ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count);
|
||||
ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count);
|
||||
ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count);
|
||||
ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count);
|
||||
ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count);
|
||||
#endif
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void ecs_world_stats_reduce(
|
||||
ecs_world_stats_t *dst,
|
||||
const ecs_world_stats_t *src)
|
||||
{
|
||||
flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t);
|
||||
}
|
||||
|
||||
void ecs_world_stats_reduce_last(
|
||||
ecs_world_stats_t *dst,
|
||||
const ecs_world_stats_t *src,
|
||||
int32_t count)
|
||||
{
|
||||
flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count);
|
||||
}
|
||||
|
||||
void ecs_world_stats_repeat_last(
|
||||
ecs_world_stats_t *stats)
|
||||
{
|
||||
flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats),
|
||||
(stats->t = t_next(stats->t)));
|
||||
}
|
||||
|
||||
void ecs_world_stats_copy_last(
|
||||
ecs_world_stats_t *dst,
|
||||
const ecs_world_stats_t *src)
|
||||
{
|
||||
flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), dst->t, t_next(src->t));
|
||||
}
|
||||
|
||||
void ecs_query_stats_get(
|
||||
const ecs_world_t *world,
|
||||
const ecs_query_t *query,
|
||||
ecs_query_stats_t *s)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)world;
|
||||
|
||||
int32_t t = s->t = t_next(s->t);
|
||||
|
||||
if (query->filter.flags & EcsFilterMatchThis) {
|
||||
ECS_GAUGE_RECORD(&s->matched_entity_count, t,
|
||||
ecs_query_entity_count(query));
|
||||
ECS_GAUGE_RECORD(&s->matched_table_count, t,
|
||||
ecs_query_table_count(query));
|
||||
ECS_GAUGE_RECORD(&s->matched_empty_table_count, t,
|
||||
ecs_query_empty_table_count(query));
|
||||
} else {
|
||||
ECS_GAUGE_RECORD(&s->matched_entity_count, t, 0);
|
||||
ECS_GAUGE_RECORD(&s->matched_table_count, t, 0);
|
||||
ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, 0);
|
||||
}
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void ecs_query_stats_reduce(
|
||||
ecs_query_stats_t *dst,
|
||||
const ecs_query_stats_t *src)
|
||||
{
|
||||
flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t);
|
||||
}
|
||||
|
||||
void ecs_query_stats_reduce_last(
|
||||
ecs_query_stats_t *dst,
|
||||
const ecs_query_stats_t *src,
|
||||
int32_t count)
|
||||
{
|
||||
flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count);
|
||||
}
|
||||
|
||||
void ecs_query_stats_repeat_last(
|
||||
ecs_query_stats_t *stats)
|
||||
{
|
||||
flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats),
|
||||
(stats->t = t_next(stats->t)));
|
||||
}
|
||||
|
||||
void ecs_query_stats_copy_last(
|
||||
ecs_query_stats_t *dst,
|
||||
const ecs_query_stats_t *src)
|
||||
{
|
||||
flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), dst->t, t_next(src->t));
|
||||
}
|
||||
|
||||
#ifdef FLECS_SYSTEM
|
||||
|
||||
bool ecs_system_stats_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t system,
|
||||
ecs_system_stats_t *s)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
world = ecs_get_world(world);
|
||||
|
||||
const ecs_system_t *ptr = ecs_poly_get(world, system, ecs_system_t);
|
||||
if (!ptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_query_stats_get(world, ptr->query, &s->query);
|
||||
int32_t t = s->query.t;
|
||||
|
||||
ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent);
|
||||
ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count);
|
||||
|
||||
s->task = !(ptr->query->filter.flags & EcsFilterMatchThis);
|
||||
|
||||
return true;
|
||||
error:
|
||||
return false;
|
||||
}
|
||||
|
||||
void ecs_system_stats_reduce(
|
||||
ecs_system_stats_t *dst,
|
||||
const ecs_system_stats_t *src)
|
||||
{
|
||||
ecs_query_stats_reduce(&dst->query, &src->query);
|
||||
dst->task = src->task;
|
||||
flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), dst->query.t, src->query.t);
|
||||
}
|
||||
|
||||
void ecs_system_stats_reduce_last(
|
||||
ecs_system_stats_t *dst,
|
||||
const ecs_system_stats_t *src,
|
||||
int32_t count)
|
||||
{
|
||||
ecs_query_stats_reduce_last(&dst->query, &src->query, count);
|
||||
dst->task = src->task;
|
||||
flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count);
|
||||
}
|
||||
|
||||
void ecs_system_stats_repeat_last(
|
||||
ecs_system_stats_t *stats)
|
||||
{
|
||||
ecs_query_stats_repeat_last(&stats->query);
|
||||
flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats),
|
||||
(stats->query.t));
|
||||
}
|
||||
|
||||
void ecs_system_stats_copy_last(
|
||||
ecs_system_stats_t *dst,
|
||||
const ecs_system_stats_t *src)
|
||||
{
|
||||
ecs_query_stats_copy_last(&dst->query, &src->query);
|
||||
dst->task = src->task;
|
||||
flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst),
|
||||
ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef FLECS_PIPELINE
|
||||
|
||||
bool ecs_pipeline_stats_get(
|
||||
ecs_world_t *stage,
|
||||
ecs_entity_t pipeline,
|
||||
ecs_pipeline_stats_t *s)
|
||||
{
|
||||
ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
const ecs_world_t *world = ecs_get_world(stage);
|
||||
const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline);
|
||||
if (!pqc) {
|
||||
return false;
|
||||
}
|
||||
ecs_pipeline_state_t *pq = pqc->state;
|
||||
ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
int32_t sys_count = 0, active_sys_count = 0;
|
||||
|
||||
/* Count number of active systems */
|
||||
ecs_iter_t it = ecs_query_iter(stage, pq->query);
|
||||
while (ecs_query_next(&it)) {
|
||||
if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) {
|
||||
continue;
|
||||
}
|
||||
active_sys_count += it.count;
|
||||
}
|
||||
|
||||
/* Count total number of systems in pipeline */
|
||||
it = ecs_query_iter(stage, pq->query);
|
||||
while (ecs_query_next(&it)) {
|
||||
sys_count += it.count;
|
||||
}
|
||||
|
||||
/* Also count synchronization points */
|
||||
ecs_vec_t *ops = &pq->ops;
|
||||
ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t);
|
||||
ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t);
|
||||
int32_t pip_count = active_sys_count + ecs_vec_count(ops);
|
||||
|
||||
if (!sys_count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ecs_map_is_init(&s->system_stats) && !sys_count) {
|
||||
ecs_map_fini(&s->system_stats);
|
||||
}
|
||||
ecs_map_init_if(&s->system_stats, NULL);
|
||||
|
||||
if (op) {
|
||||
ecs_entity_t *systems = NULL;
|
||||
if (pip_count) {
|
||||
ecs_vec_init_if_t(&s->systems, ecs_entity_t);
|
||||
ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count);
|
||||
systems = ecs_vec_first_t(&s->systems, ecs_entity_t);
|
||||
|
||||
/* Populate systems vector, keep track of sync points */
|
||||
it = ecs_query_iter(stage, pq->query);
|
||||
|
||||
int32_t i, i_system = 0, ran_since_merge = 0;
|
||||
while (ecs_query_next(&it)) {
|
||||
if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < it.count; i ++) {
|
||||
systems[i_system ++] = it.entities[i];
|
||||
ran_since_merge ++;
|
||||
if (op != op_last && ran_since_merge == op->count) {
|
||||
ran_since_merge = 0;
|
||||
op++;
|
||||
systems[i_system ++] = 0; /* 0 indicates a merge point */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
systems[i_system ++] = 0; /* Last merge */
|
||||
ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL);
|
||||
} else {
|
||||
ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t);
|
||||
}
|
||||
|
||||
/* Get sync point statistics */
|
||||
int32_t i, count = ecs_vec_count(ops);
|
||||
if (count) {
|
||||
ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t);
|
||||
ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count);
|
||||
op = ecs_vec_first_t(ops, ecs_pipeline_op_t);
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_pipeline_op_t *cur = &op[i];
|
||||
ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points,
|
||||
ecs_sync_stats_t, i);
|
||||
|
||||
ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent);
|
||||
ECS_COUNTER_RECORD(&el->commands_enqueued, s->t,
|
||||
cur->commands_enqueued);
|
||||
|
||||
el->system_count = cur->count;
|
||||
el->multi_threaded = cur->multi_threaded;
|
||||
el->no_readonly = cur->no_readonly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Separately populate system stats map from build query, which includes
|
||||
* systems that aren't currently active */
|
||||
it = ecs_query_iter(stage, pq->query);
|
||||
while (ecs_query_next(&it)) {
|
||||
int32_t i;
|
||||
for (i = 0; i < it.count; i ++) {
|
||||
ecs_system_stats_t *stats = ecs_map_ensure_alloc_t(&s->system_stats,
|
||||
ecs_system_stats_t, it.entities[i]);
|
||||
stats->query.t = s->t;
|
||||
ecs_system_stats_get(world, it.entities[i], stats);
|
||||
}
|
||||
}
|
||||
|
||||
s->t = t_next(s->t);
|
||||
|
||||
return true;
|
||||
error:
|
||||
return false;
|
||||
}
|
||||
|
||||
void ecs_pipeline_stats_fini(
|
||||
ecs_pipeline_stats_t *stats)
|
||||
{
|
||||
ecs_map_iter_t it = ecs_map_iter(&stats->system_stats);
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_system_stats_t *elem = ecs_map_ptr(&it);
|
||||
ecs_os_free(elem);
|
||||
}
|
||||
ecs_map_fini(&stats->system_stats);
|
||||
ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t);
|
||||
ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t);
|
||||
}
|
||||
|
||||
void ecs_pipeline_stats_reduce(
|
||||
ecs_pipeline_stats_t *dst,
|
||||
const ecs_pipeline_stats_t *src)
|
||||
{
|
||||
int32_t system_count = ecs_vec_count(&src->systems);
|
||||
ecs_vec_init_if_t(&dst->systems, ecs_entity_t);
|
||||
ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count);
|
||||
ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t);
|
||||
ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t);
|
||||
ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count);
|
||||
|
||||
int32_t i, sync_count = ecs_vec_count(&src->sync_points);
|
||||
ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t);
|
||||
ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count);
|
||||
ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t);
|
||||
ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t);
|
||||
for (i = 0; i < sync_count; i ++) {
|
||||
ecs_sync_stats_t *dst_el = &dst_syncs[i];
|
||||
ecs_sync_stats_t *src_el = &src_syncs[i];
|
||||
flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el),
|
||||
ECS_METRIC_FIRST(src_el), dst->t, src->t);
|
||||
dst_el->system_count = src_el->system_count;
|
||||
dst_el->multi_threaded = src_el->multi_threaded;
|
||||
dst_el->no_readonly = src_el->no_readonly;
|
||||
}
|
||||
|
||||
ecs_map_init_if(&dst->system_stats, NULL);
|
||||
ecs_map_iter_t it = ecs_map_iter(&src->system_stats);
|
||||
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_system_stats_t *sys_src = ecs_map_ptr(&it);
|
||||
ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats,
|
||||
ecs_system_stats_t, ecs_map_key(&it));
|
||||
sys_dst->query.t = dst->t;
|
||||
ecs_system_stats_reduce(sys_dst, sys_src);
|
||||
}
|
||||
dst->t = t_next(dst->t);
|
||||
}
|
||||
|
||||
void ecs_pipeline_stats_reduce_last(
|
||||
ecs_pipeline_stats_t *dst,
|
||||
const ecs_pipeline_stats_t *src,
|
||||
int32_t count)
|
||||
{
|
||||
int32_t i, sync_count = ecs_vec_count(&src->sync_points);
|
||||
ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t);
|
||||
ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t);
|
||||
|
||||
for (i = 0; i < sync_count; i ++) {
|
||||
ecs_sync_stats_t *dst_el = &dst_syncs[i];
|
||||
ecs_sync_stats_t *src_el = &src_syncs[i];
|
||||
flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el),
|
||||
ECS_METRIC_FIRST(src_el), dst->t, src->t, count);
|
||||
dst_el->system_count = src_el->system_count;
|
||||
dst_el->multi_threaded = src_el->multi_threaded;
|
||||
dst_el->no_readonly = src_el->no_readonly;
|
||||
}
|
||||
|
||||
ecs_map_init_if(&dst->system_stats, NULL);
|
||||
ecs_map_iter_t it = ecs_map_iter(&src->system_stats);
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_system_stats_t *sys_src = ecs_map_ptr(&it);
|
||||
ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats,
|
||||
ecs_system_stats_t, ecs_map_key(&it));
|
||||
sys_dst->query.t = dst->t;
|
||||
ecs_system_stats_reduce_last(sys_dst, sys_src, count);
|
||||
}
|
||||
dst->t = t_prev(dst->t);
|
||||
}
|
||||
|
||||
void ecs_pipeline_stats_repeat_last(
|
||||
ecs_pipeline_stats_t *stats)
|
||||
{
|
||||
int32_t i, sync_count = ecs_vec_count(&stats->sync_points);
|
||||
ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t);
|
||||
|
||||
for (i = 0; i < sync_count; i ++) {
|
||||
ecs_sync_stats_t *el = &syncs[i];
|
||||
flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el),
|
||||
(stats->t));
|
||||
}
|
||||
|
||||
ecs_map_iter_t it = ecs_map_iter(&stats->system_stats);
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_system_stats_t *sys = ecs_map_ptr(&it);
|
||||
sys->query.t = stats->t;
|
||||
ecs_system_stats_repeat_last(sys);
|
||||
}
|
||||
stats->t = t_next(stats->t);
|
||||
}
|
||||
|
||||
void ecs_pipeline_stats_copy_last(
|
||||
ecs_pipeline_stats_t *dst,
|
||||
const ecs_pipeline_stats_t *src)
|
||||
{
|
||||
int32_t i, sync_count = ecs_vec_count(&src->sync_points);
|
||||
ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t);
|
||||
ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count);
|
||||
ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t);
|
||||
ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t);
|
||||
|
||||
for (i = 0; i < sync_count; i ++) {
|
||||
ecs_sync_stats_t *dst_el = &dst_syncs[i];
|
||||
ecs_sync_stats_t *src_el = &src_syncs[i];
|
||||
flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el),
|
||||
ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t));
|
||||
dst_el->system_count = src_el->system_count;
|
||||
dst_el->multi_threaded = src_el->multi_threaded;
|
||||
dst_el->no_readonly = src_el->no_readonly;
|
||||
}
|
||||
|
||||
ecs_map_init_if(&dst->system_stats, NULL);
|
||||
|
||||
ecs_map_iter_t it = ecs_map_iter(&src->system_stats);
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_system_stats_t *sys_src = ecs_map_ptr(&it);
|
||||
ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats,
|
||||
ecs_system_stats_t, ecs_map_key(&it));
|
||||
sys_dst->query.t = dst->t;
|
||||
ecs_system_stats_copy_last(sys_dst, sys_src);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void ecs_world_stats_log(
|
||||
const ecs_world_t *world,
|
||||
const ecs_world_stats_t *s)
|
||||
{
|
||||
int32_t t = s->t;
|
||||
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
world = ecs_get_world(world);
|
||||
|
||||
flecs_counter_print("Frame", t, &s->frame.frame_count);
|
||||
ecs_trace("-------------------------------------");
|
||||
flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count);
|
||||
flecs_counter_print("systems ran", t, &s->frame.systems_ran);
|
||||
ecs_trace("");
|
||||
flecs_metric_print("target FPS", world->info.target_fps);
|
||||
flecs_metric_print("time scale", world->info.time_scale);
|
||||
ecs_trace("");
|
||||
flecs_gauge_print("actual FPS", t, &s->performance.fps);
|
||||
flecs_counter_print("frame time", t, &s->performance.frame_time);
|
||||
flecs_counter_print("system time", t, &s->performance.system_time);
|
||||
flecs_counter_print("merge time", t, &s->performance.merge_time);
|
||||
flecs_counter_print("simulation time elapsed", t, &s->performance.world_time);
|
||||
ecs_trace("");
|
||||
flecs_gauge_print("id count", t, &s->ids.count);
|
||||
flecs_gauge_print("tag id count", t, &s->ids.tag_count);
|
||||
flecs_gauge_print("component id count", t, &s->ids.component_count);
|
||||
flecs_gauge_print("pair id count", t, &s->ids.pair_count);
|
||||
flecs_gauge_print("wildcard id count", t, &s->ids.wildcard_count);
|
||||
flecs_gauge_print("type count", t, &s->ids.type_count);
|
||||
flecs_counter_print("id create count", t, &s->ids.create_count);
|
||||
flecs_counter_print("id delete count", t, &s->ids.delete_count);
|
||||
ecs_trace("");
|
||||
flecs_gauge_print("alive entity count", t, &s->entities.count);
|
||||
flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count);
|
||||
ecs_trace("");
|
||||
flecs_gauge_print("query count", t, &s->queries.query_count);
|
||||
flecs_gauge_print("observer count", t, &s->queries.observer_count);
|
||||
flecs_gauge_print("system count", t, &s->queries.system_count);
|
||||
ecs_trace("");
|
||||
flecs_gauge_print("table count", t, &s->tables.count);
|
||||
flecs_gauge_print("empty table count", t, &s->tables.empty_count);
|
||||
flecs_gauge_print("tag table count", t, &s->tables.tag_only_count);
|
||||
flecs_gauge_print("trivial table count", t, &s->tables.trivial_only_count);
|
||||
flecs_gauge_print("table storage count", t, &s->tables.storage_count);
|
||||
flecs_gauge_print("table cache record count", t, &s->tables.record_count);
|
||||
flecs_counter_print("table create count", t, &s->tables.create_count);
|
||||
flecs_counter_print("table delete count", t, &s->tables.delete_count);
|
||||
ecs_trace("");
|
||||
flecs_counter_print("add commands", t, &s->commands.add_count);
|
||||
flecs_counter_print("remove commands", t, &s->commands.remove_count);
|
||||
flecs_counter_print("delete commands", t, &s->commands.delete_count);
|
||||
flecs_counter_print("clear commands", t, &s->commands.clear_count);
|
||||
flecs_counter_print("set commands", t, &s->commands.set_count);
|
||||
flecs_counter_print("get_mut commands", t, &s->commands.get_mut_count);
|
||||
flecs_counter_print("modified commands", t, &s->commands.modified_count);
|
||||
flecs_counter_print("other commands", t, &s->commands.other_count);
|
||||
flecs_counter_print("discarded commands", t, &s->commands.discard_count);
|
||||
flecs_counter_print("batched entities", t, &s->commands.batched_entity_count);
|
||||
flecs_counter_print("batched commands", t, &s->commands.batched_count);
|
||||
ecs_trace("");
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
394
engine/libs/flecs/src/addons/system/system.c
Normal file
394
engine/libs/flecs/src/addons/system/system.c
Normal file
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* @file addons/system/system.c
|
||||
* @brief System addon.
|
||||
*/
|
||||
|
||||
#include "flecs.h"
|
||||
|
||||
#ifdef FLECS_SYSTEM
|
||||
|
||||
#include "../../private_api.h"
|
||||
#include "system.h"
|
||||
|
||||
ecs_mixins_t ecs_system_t_mixins = {
|
||||
.type_name = "ecs_system_t",
|
||||
.elems = {
|
||||
[EcsMixinWorld] = offsetof(ecs_system_t, world),
|
||||
[EcsMixinEntity] = offsetof(ecs_system_t, entity),
|
||||
[EcsMixinDtor] = offsetof(ecs_system_t, dtor)
|
||||
}
|
||||
};
|
||||
|
||||
/* -- Public API -- */
|
||||
|
||||
ecs_entity_t ecs_run_intern(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t system,
|
||||
ecs_system_t *system_data,
|
||||
int32_t stage_index,
|
||||
int32_t stage_count,
|
||||
ecs_ftime_t delta_time,
|
||||
int32_t offset,
|
||||
int32_t limit,
|
||||
void *param)
|
||||
{
|
||||
ecs_ftime_t time_elapsed = delta_time;
|
||||
ecs_entity_t tick_source = system_data->tick_source;
|
||||
|
||||
/* Support legacy behavior */
|
||||
if (!param) {
|
||||
param = system_data->ctx;
|
||||
}
|
||||
|
||||
if (tick_source) {
|
||||
const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource);
|
||||
|
||||
if (tick) {
|
||||
time_elapsed = tick->time_elapsed;
|
||||
|
||||
/* If timer hasn't fired we shouldn't run the system */
|
||||
if (!tick->tick) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
/* If a timer has been set but the timer entity does not have the
|
||||
* EcsTimer component, don't run the system. This can be the result
|
||||
* of a single-shot timer that has fired already. Not resetting the
|
||||
* timer field of the system will ensure that the system won't be
|
||||
* ran after the timer has fired. */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (ecs_should_log_3()) {
|
||||
char *path = ecs_get_fullpath(world, system);
|
||||
ecs_dbg_3("worker %d: %s", stage_index, path);
|
||||
ecs_os_free(path);
|
||||
}
|
||||
|
||||
ecs_time_t time_start;
|
||||
bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime);
|
||||
if (measure_time) {
|
||||
ecs_os_get_time(&time_start);
|
||||
}
|
||||
|
||||
ecs_world_t *thread_ctx = world;
|
||||
if (stage) {
|
||||
thread_ctx = stage->thread_ctx;
|
||||
} else {
|
||||
stage = &world->stages[0];
|
||||
}
|
||||
|
||||
/* Prepare the query iterator */
|
||||
ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query);
|
||||
ecs_iter_t *it = &qit;
|
||||
|
||||
qit.system = system;
|
||||
qit.delta_time = delta_time;
|
||||
qit.delta_system_time = time_elapsed;
|
||||
qit.frame_offset = offset;
|
||||
qit.param = param;
|
||||
qit.ctx = system_data->ctx;
|
||||
qit.binding_ctx = system_data->binding_ctx;
|
||||
|
||||
flecs_defer_begin(world, stage);
|
||||
|
||||
if (offset || limit) {
|
||||
pit = ecs_page_iter(it, offset, limit);
|
||||
it = &pit;
|
||||
}
|
||||
|
||||
if (stage_count > 1 && system_data->multi_threaded) {
|
||||
wit = ecs_worker_iter(it, stage_index, stage_count);
|
||||
it = &wit;
|
||||
}
|
||||
|
||||
ecs_iter_action_t action = system_data->action;
|
||||
it->callback = action;
|
||||
|
||||
ecs_run_action_t run = system_data->run;
|
||||
if (run) {
|
||||
run(it);
|
||||
} else if (system_data->query->filter.term_count) {
|
||||
if (it == &qit) {
|
||||
while (ecs_query_next(&qit)) {
|
||||
action(&qit);
|
||||
}
|
||||
} else {
|
||||
while (ecs_iter_next(it)) {
|
||||
action(it);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
action(&qit);
|
||||
ecs_iter_fini(&qit);
|
||||
}
|
||||
|
||||
if (measure_time) {
|
||||
system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start);
|
||||
}
|
||||
|
||||
system_data->invoke_count ++;
|
||||
|
||||
flecs_defer_end(world, stage);
|
||||
|
||||
return it->interrupted_by;
|
||||
}
|
||||
|
||||
/* -- Public API -- */
|
||||
|
||||
ecs_entity_t ecs_run_w_filter(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t system,
|
||||
ecs_ftime_t delta_time,
|
||||
int32_t offset,
|
||||
int32_t limit,
|
||||
void *param)
|
||||
{
|
||||
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
||||
ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t);
|
||||
ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time,
|
||||
offset, limit, param);
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_run_worker(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t system,
|
||||
int32_t stage_index,
|
||||
int32_t stage_count,
|
||||
ecs_ftime_t delta_time,
|
||||
void *param)
|
||||
{
|
||||
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
||||
ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t);
|
||||
ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
return ecs_run_intern(
|
||||
world, stage, system, system_data, stage_index, stage_count,
|
||||
delta_time, 0, 0, param);
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_run(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t system,
|
||||
ecs_ftime_t delta_time,
|
||||
void *param)
|
||||
{
|
||||
return ecs_run_w_filter(world, system, delta_time, 0, 0, param);
|
||||
}
|
||||
|
||||
ecs_query_t* ecs_system_get_query(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t system)
|
||||
{
|
||||
const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t);
|
||||
if (s) {
|
||||
return s->query;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void* ecs_system_get_ctx(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t system)
|
||||
{
|
||||
const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t);
|
||||
if (s) {
|
||||
return s->ctx;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void* ecs_system_get_binding_ctx(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t system)
|
||||
{
|
||||
const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t);
|
||||
if (s) {
|
||||
return s->binding_ctx;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* System deinitialization */
|
||||
static
|
||||
void flecs_system_fini(ecs_system_t *sys) {
|
||||
if (sys->ctx_free) {
|
||||
sys->ctx_free(sys->ctx);
|
||||
}
|
||||
|
||||
if (sys->binding_ctx_free) {
|
||||
sys->binding_ctx_free(sys->binding_ctx);
|
||||
}
|
||||
|
||||
ecs_poly_free(sys, ecs_system_t);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_system_init_timer(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
const ecs_system_desc_t *desc)
|
||||
{
|
||||
if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) ||
|
||||
ECS_NEQZERO(desc->tick_source))
|
||||
{
|
||||
#ifdef FLECS_TIMER
|
||||
if (ECS_NEQZERO(desc->interval)) {
|
||||
ecs_set_interval(world, entity, desc->interval);
|
||||
}
|
||||
|
||||
if (desc->rate) {
|
||||
ecs_entity_t tick_source = desc->tick_source;
|
||||
if (!tick_source) {
|
||||
tick_source = entity;
|
||||
}
|
||||
ecs_set_rate(world, entity, desc->rate, tick_source);
|
||||
} else if (desc->tick_source) {
|
||||
ecs_set_tick_source(world, entity, desc->tick_source);
|
||||
}
|
||||
#else
|
||||
(void)world;
|
||||
(void)entity;
|
||||
ecs_abort(ECS_UNSUPPORTED, "timer module not available");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_system_init(
|
||||
ecs_world_t *world,
|
||||
const ecs_system_desc_t *desc)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!(world->flags & EcsWorldReadonly),
|
||||
ECS_INVALID_WHILE_READONLY, NULL);
|
||||
|
||||
ecs_entity_t entity = desc->entity;
|
||||
if (!entity) {
|
||||
entity = ecs_new(world, 0);
|
||||
}
|
||||
EcsPoly *poly = ecs_poly_bind(world, entity, ecs_system_t);
|
||||
if (!poly->poly) {
|
||||
ecs_system_t *system = ecs_poly_new(ecs_system_t);
|
||||
ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
poly->poly = system;
|
||||
system->world = world;
|
||||
system->dtor = (ecs_poly_dtor_t)flecs_system_fini;
|
||||
system->entity = entity;
|
||||
|
||||
ecs_query_desc_t query_desc = desc->query;
|
||||
query_desc.filter.entity = entity;
|
||||
|
||||
ecs_query_t *query = ecs_query_init(world, &query_desc);
|
||||
if (!query) {
|
||||
ecs_delete(world, entity);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Prevent the system from moving while we're initializing */
|
||||
flecs_defer_begin(world, &world->stages[0]);
|
||||
|
||||
system->query = query;
|
||||
system->query_entity = query->filter.entity;
|
||||
|
||||
system->run = desc->run;
|
||||
system->action = desc->callback;
|
||||
|
||||
system->ctx = desc->ctx;
|
||||
system->binding_ctx = desc->binding_ctx;
|
||||
|
||||
system->ctx_free = desc->ctx_free;
|
||||
system->binding_ctx_free = desc->binding_ctx_free;
|
||||
|
||||
system->tick_source = desc->tick_source;
|
||||
|
||||
system->multi_threaded = desc->multi_threaded;
|
||||
system->no_readonly = desc->no_readonly;
|
||||
|
||||
flecs_system_init_timer(world, entity, desc);
|
||||
|
||||
if (ecs_get_name(world, entity)) {
|
||||
ecs_trace("#[green]system#[reset] %s created",
|
||||
ecs_get_name(world, entity));
|
||||
}
|
||||
|
||||
ecs_defer_end(world);
|
||||
} else {
|
||||
ecs_poly_assert(poly->poly, ecs_system_t);
|
||||
ecs_system_t *system = (ecs_system_t*)poly->poly;
|
||||
|
||||
if (desc->run) {
|
||||
system->run = desc->run;
|
||||
}
|
||||
if (desc->callback) {
|
||||
system->action = desc->callback;
|
||||
}
|
||||
|
||||
if (system->ctx_free) {
|
||||
if (system->ctx && system->ctx != desc->ctx) {
|
||||
system->ctx_free(system->ctx);
|
||||
}
|
||||
}
|
||||
if (system->binding_ctx_free) {
|
||||
if (system->binding_ctx && system->binding_ctx != desc->binding_ctx) {
|
||||
system->binding_ctx_free(system->binding_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (desc->ctx) {
|
||||
system->ctx = desc->ctx;
|
||||
}
|
||||
if (desc->binding_ctx) {
|
||||
system->binding_ctx = desc->binding_ctx;
|
||||
}
|
||||
if (desc->ctx_free) {
|
||||
system->ctx_free = desc->ctx_free;
|
||||
}
|
||||
if (desc->binding_ctx_free) {
|
||||
system->binding_ctx_free = desc->binding_ctx_free;
|
||||
}
|
||||
if (desc->query.filter.instanced) {
|
||||
ECS_BIT_SET(system->query->filter.flags, EcsFilterIsInstanced);
|
||||
}
|
||||
if (desc->multi_threaded) {
|
||||
system->multi_threaded = desc->multi_threaded;
|
||||
}
|
||||
if (desc->no_readonly) {
|
||||
system->no_readonly = desc->no_readonly;
|
||||
}
|
||||
|
||||
flecs_system_init_timer(world, entity, desc);
|
||||
}
|
||||
|
||||
ecs_poly_modified(world, entity, ecs_system_t);
|
||||
|
||||
return entity;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FlecsSystemImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsSystem);
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
flecs_bootstrap_tag(world, EcsSystem);
|
||||
flecs_bootstrap_component(world, EcsTickSource);
|
||||
|
||||
/* Make sure to never inherit system component. This makes sure that any
|
||||
* term created for the System component will default to 'self' traversal,
|
||||
* which improves efficiency of the query. */
|
||||
ecs_add_id(world, EcsSystem, EcsDontInherit);
|
||||
}
|
||||
|
||||
#endif
|
||||
71
engine/libs/flecs/src/addons/system/system.h
Normal file
71
engine/libs/flecs/src/addons/system/system.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @file addons/system/system.h
|
||||
* @brief Internal types and functions for system addon.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_SYSTEM_PRIVATE_H
|
||||
#define FLECS_SYSTEM_PRIVATE_H
|
||||
|
||||
#ifdef FLECS_SYSTEM
|
||||
|
||||
#include "../../private_api.h"
|
||||
|
||||
#define ecs_system_t_magic (0x65637383)
|
||||
#define ecs_system_t_tag EcsSystem
|
||||
|
||||
extern ecs_mixins_t ecs_system_t_mixins;
|
||||
|
||||
typedef struct ecs_system_t {
|
||||
ecs_header_t hdr;
|
||||
|
||||
ecs_run_action_t run; /* See ecs_system_desc_t */
|
||||
ecs_iter_action_t action; /* See ecs_system_desc_t */
|
||||
|
||||
ecs_query_t *query; /* System query */
|
||||
ecs_entity_t query_entity; /* Entity associated with query */
|
||||
ecs_entity_t tick_source; /* Tick source associated with system */
|
||||
|
||||
/* Schedule parameters */
|
||||
bool multi_threaded;
|
||||
bool no_readonly;
|
||||
|
||||
int64_t invoke_count; /* Number of times system is invoked */
|
||||
ecs_ftime_t time_spent; /* Time spent on running system */
|
||||
ecs_ftime_t time_passed; /* Time passed since last invocation */
|
||||
int64_t last_frame; /* Last frame for which the system was considered */
|
||||
|
||||
void *ctx; /* Userdata for system */
|
||||
void *binding_ctx; /* Optional language binding context */
|
||||
|
||||
ecs_ctx_free_t ctx_free;
|
||||
ecs_ctx_free_t binding_ctx_free;
|
||||
|
||||
/* Mixins */
|
||||
ecs_world_t *world;
|
||||
ecs_entity_t entity;
|
||||
ecs_poly_dtor_t dtor;
|
||||
} ecs_system_t;
|
||||
|
||||
/* Invoked when system becomes active / inactive */
|
||||
void ecs_system_activate(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t system,
|
||||
bool activate,
|
||||
const ecs_system_t *system_data);
|
||||
|
||||
/* Internal function to run a system */
|
||||
ecs_entity_t ecs_run_intern(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t system,
|
||||
ecs_system_t *system_data,
|
||||
int32_t stage_current,
|
||||
int32_t stage_count,
|
||||
ecs_ftime_t delta_time,
|
||||
int32_t offset,
|
||||
int32_t limit,
|
||||
void *param);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
346
engine/libs/flecs/src/addons/timer.c
Normal file
346
engine/libs/flecs/src/addons/timer.c
Normal file
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* @file addons/timer.c
|
||||
* @brief Timer addon.
|
||||
*/
|
||||
|
||||
#include "flecs.h"
|
||||
#include "system/system.h"
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_TIMER
|
||||
|
||||
static
|
||||
void AddTickSource(ecs_iter_t *it) {
|
||||
int32_t i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
ecs_set(it->world, it->entities[i], EcsTickSource, {0});
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void ProgressTimers(ecs_iter_t *it) {
|
||||
EcsTimer *timer = ecs_field(it, EcsTimer, 1);
|
||||
EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 2);
|
||||
|
||||
ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
tick_source[i].tick = false;
|
||||
|
||||
if (!timer[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ecs_world_info_t *info = ecs_get_world_info(it->world);
|
||||
ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw;
|
||||
ecs_ftime_t timeout = timer[i].timeout;
|
||||
|
||||
if (time_elapsed >= timeout) {
|
||||
ecs_ftime_t t = time_elapsed - timeout;
|
||||
if (t > timeout) {
|
||||
t = 0;
|
||||
}
|
||||
|
||||
timer[i].time = t; /* Initialize with remainder */
|
||||
tick_source[i].tick = true;
|
||||
tick_source[i].time_elapsed = time_elapsed - timer[i].overshoot;
|
||||
timer[i].overshoot = t;
|
||||
|
||||
if (timer[i].single_shot) {
|
||||
timer[i].active = false;
|
||||
}
|
||||
} else {
|
||||
timer[i].time = time_elapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void ProgressRateFilters(ecs_iter_t *it) {
|
||||
EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 1);
|
||||
EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 2);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
ecs_entity_t src = filter[i].src;
|
||||
bool inc = false;
|
||||
|
||||
filter[i].time_elapsed += it->delta_time;
|
||||
|
||||
if (src) {
|
||||
const EcsTickSource *tick_src = ecs_get(
|
||||
it->world, src, EcsTickSource);
|
||||
if (tick_src) {
|
||||
inc = tick_src->tick;
|
||||
} else {
|
||||
inc = true;
|
||||
}
|
||||
} else {
|
||||
inc = true;
|
||||
}
|
||||
|
||||
if (inc) {
|
||||
filter[i].tick_count ++;
|
||||
bool triggered = !(filter[i].tick_count % filter[i].rate);
|
||||
tick_dst[i].tick = triggered;
|
||||
tick_dst[i].time_elapsed = filter[i].time_elapsed;
|
||||
|
||||
if (triggered) {
|
||||
filter[i].time_elapsed = 0;
|
||||
}
|
||||
} else {
|
||||
tick_dst[i].tick = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void ProgressTickSource(ecs_iter_t *it) {
|
||||
EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 1);
|
||||
|
||||
/* If tick source has no filters, tick unconditionally */
|
||||
int i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
tick_src[i].tick = true;
|
||||
tick_src[i].time_elapsed = it->delta_time;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_set_timeout(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t timer,
|
||||
ecs_ftime_t timeout)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
timer = ecs_set(world, timer, EcsTimer, {
|
||||
.timeout = timeout,
|
||||
.single_shot = true,
|
||||
.active = true
|
||||
});
|
||||
|
||||
ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t);
|
||||
if (system_data) {
|
||||
system_data->tick_source = timer;
|
||||
}
|
||||
|
||||
error:
|
||||
return timer;
|
||||
}
|
||||
|
||||
ecs_ftime_t ecs_get_timeout(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t timer)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
const EcsTimer *value = ecs_get(world, timer, EcsTimer);
|
||||
if (value) {
|
||||
return value->timeout;
|
||||
}
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_set_interval(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t timer,
|
||||
ecs_ftime_t interval)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
if (!timer) {
|
||||
timer = ecs_new(world, EcsTimer);
|
||||
}
|
||||
|
||||
EcsTimer *t = ecs_get_mut(world, timer, EcsTimer);
|
||||
ecs_check(t != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
t->timeout = interval;
|
||||
t->active = true;
|
||||
ecs_modified(world, timer, EcsTimer);
|
||||
|
||||
ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t);
|
||||
if (system_data) {
|
||||
system_data->tick_source = timer;
|
||||
}
|
||||
error:
|
||||
return timer;
|
||||
}
|
||||
|
||||
ecs_ftime_t ecs_get_interval(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t timer)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
if (!timer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const EcsTimer *value = ecs_get(world, timer, EcsTimer);
|
||||
if (value) {
|
||||
return value->timeout;
|
||||
}
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ecs_start_timer(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t timer)
|
||||
{
|
||||
EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer);
|
||||
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ptr->active = true;
|
||||
ptr->time = 0;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void ecs_stop_timer(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t timer)
|
||||
{
|
||||
EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer);
|
||||
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ptr->active = false;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void ecs_reset_timer(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t timer)
|
||||
{
|
||||
EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer);
|
||||
ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ptr->time = 0;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_set_rate(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t filter,
|
||||
int32_t rate,
|
||||
ecs_entity_t source)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
filter = ecs_set(world, filter, EcsRateFilter, {
|
||||
.rate = rate,
|
||||
.src = source
|
||||
});
|
||||
|
||||
ecs_system_t *system_data = ecs_poly_get(world, filter, ecs_system_t);
|
||||
if (system_data) {
|
||||
system_data->tick_source = filter;
|
||||
}
|
||||
|
||||
error:
|
||||
return filter;
|
||||
}
|
||||
|
||||
void ecs_set_tick_source(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t system,
|
||||
ecs_entity_t tick_source)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t);
|
||||
ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
system_data->tick_source = tick_source;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
static
|
||||
void RandomizeTimers(ecs_iter_t *it) {
|
||||
EcsTimer *timer = ecs_field(it, EcsTimer, 1);
|
||||
int32_t i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
timer[i].time =
|
||||
((ecs_ftime_t)rand() / (ecs_ftime_t)RAND_MAX) * timer[i].timeout;
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_randomize_timers(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_observer(world, {
|
||||
.entity = ecs_entity(world, { .name = "flecs.timer.RandomizeTimers" }),
|
||||
.filter.terms = {{
|
||||
.id = ecs_id(EcsTimer)
|
||||
}},
|
||||
.events = {EcsOnSet},
|
||||
.yield_existing = true,
|
||||
.callback = RandomizeTimers
|
||||
});
|
||||
}
|
||||
|
||||
void FlecsTimerImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsTimer);
|
||||
|
||||
ECS_IMPORT(world, FlecsPipeline);
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
flecs_bootstrap_component(world, EcsTimer);
|
||||
flecs_bootstrap_component(world, EcsRateFilter);
|
||||
|
||||
ecs_set_hooks(world, EcsTimer, {
|
||||
.ctor = ecs_default_ctor
|
||||
});
|
||||
|
||||
/* Add EcsTickSource to timers and rate filters */
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, {.name = "AddTickSource", .add = { ecs_dependson(EcsPreFrame) }}),
|
||||
.query.filter.terms = {
|
||||
{ .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn },
|
||||
{ .id = ecs_id(EcsRateFilter), .oper = EcsAnd, .inout = EcsIn },
|
||||
{ .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut}
|
||||
},
|
||||
.callback = AddTickSource
|
||||
});
|
||||
|
||||
/* Timer handling */
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, {.name = "ProgressTimers", .add = { ecs_dependson(EcsPreFrame)}}),
|
||||
.query.filter.terms = {
|
||||
{ .id = ecs_id(EcsTimer) },
|
||||
{ .id = ecs_id(EcsTickSource) }
|
||||
},
|
||||
.callback = ProgressTimers
|
||||
});
|
||||
|
||||
/* Rate filter handling */
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = { ecs_dependson(EcsPreFrame)}}),
|
||||
.query.filter.terms = {
|
||||
{ .id = ecs_id(EcsRateFilter), .inout = EcsIn },
|
||||
{ .id = ecs_id(EcsTickSource), .inout = EcsOut }
|
||||
},
|
||||
.callback = ProgressRateFilters
|
||||
});
|
||||
|
||||
/* TickSource without a timer or rate filter just increases each frame */
|
||||
ecs_system(world, {
|
||||
.entity = ecs_entity(world, { .name = "ProgressTickSource", .add = { ecs_dependson(EcsPreFrame)}}),
|
||||
.query.filter.terms = {
|
||||
{ .id = ecs_id(EcsTickSource), .inout = EcsOut },
|
||||
{ .id = ecs_id(EcsRateFilter), .oper = EcsNot },
|
||||
{ .id = ecs_id(EcsTimer), .oper = EcsNot }
|
||||
},
|
||||
.callback = ProgressTickSource
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
961
engine/libs/flecs/src/addons/units.c
Normal file
961
engine/libs/flecs/src/addons/units.c
Normal file
@@ -0,0 +1,961 @@
|
||||
/**
|
||||
* @file addons/units.c
|
||||
* @brief Units addon.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_UNITS
|
||||
|
||||
void FlecsUnitsImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsUnits);
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
EcsUnitPrefixes = ecs_entity(world, {
|
||||
.name = "prefixes",
|
||||
.add = { EcsModule }
|
||||
});
|
||||
|
||||
/* Initialize unit prefixes */
|
||||
|
||||
ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes);
|
||||
|
||||
EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Yocto" }),
|
||||
.symbol = "y",
|
||||
.translation = { .factor = 10, .power = -24 }
|
||||
});
|
||||
EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Zepto" }),
|
||||
.symbol = "z",
|
||||
.translation = { .factor = 10, .power = -21 }
|
||||
});
|
||||
EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Atto" }),
|
||||
.symbol = "a",
|
||||
.translation = { .factor = 10, .power = -18 }
|
||||
});
|
||||
EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Femto" }),
|
||||
.symbol = "a",
|
||||
.translation = { .factor = 10, .power = -15 }
|
||||
});
|
||||
EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Pico" }),
|
||||
.symbol = "p",
|
||||
.translation = { .factor = 10, .power = -12 }
|
||||
});
|
||||
EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Nano" }),
|
||||
.symbol = "n",
|
||||
.translation = { .factor = 10, .power = -9 }
|
||||
});
|
||||
EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Micro" }),
|
||||
.symbol = "μ",
|
||||
.translation = { .factor = 10, .power = -6 }
|
||||
});
|
||||
EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Milli" }),
|
||||
.symbol = "m",
|
||||
.translation = { .factor = 10, .power = -3 }
|
||||
});
|
||||
EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Centi" }),
|
||||
.symbol = "c",
|
||||
.translation = { .factor = 10, .power = -2 }
|
||||
});
|
||||
EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Deci" }),
|
||||
.symbol = "d",
|
||||
.translation = { .factor = 10, .power = -1 }
|
||||
});
|
||||
EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Deca" }),
|
||||
.symbol = "da",
|
||||
.translation = { .factor = 10, .power = 1 }
|
||||
});
|
||||
EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Hecto" }),
|
||||
.symbol = "h",
|
||||
.translation = { .factor = 10, .power = 2 }
|
||||
});
|
||||
EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Kilo" }),
|
||||
.symbol = "k",
|
||||
.translation = { .factor = 10, .power = 3 }
|
||||
});
|
||||
EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Mega" }),
|
||||
.symbol = "M",
|
||||
.translation = { .factor = 10, .power = 6 }
|
||||
});
|
||||
EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Giga" }),
|
||||
.symbol = "G",
|
||||
.translation = { .factor = 10, .power = 9 }
|
||||
});
|
||||
EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Tera" }),
|
||||
.symbol = "T",
|
||||
.translation = { .factor = 10, .power = 12 }
|
||||
});
|
||||
EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Peta" }),
|
||||
.symbol = "P",
|
||||
.translation = { .factor = 10, .power = 15 }
|
||||
});
|
||||
EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Exa" }),
|
||||
.symbol = "E",
|
||||
.translation = { .factor = 10, .power = 18 }
|
||||
});
|
||||
EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Zetta" }),
|
||||
.symbol = "Z",
|
||||
.translation = { .factor = 10, .power = 21 }
|
||||
});
|
||||
EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Yotta" }),
|
||||
.symbol = "Y",
|
||||
.translation = { .factor = 10, .power = 24 }
|
||||
});
|
||||
|
||||
EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Kibi" }),
|
||||
.symbol = "Ki",
|
||||
.translation = { .factor = 1024, .power = 1 }
|
||||
});
|
||||
EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Mebi" }),
|
||||
.symbol = "Mi",
|
||||
.translation = { .factor = 1024, .power = 2 }
|
||||
});
|
||||
EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Gibi" }),
|
||||
.symbol = "Gi",
|
||||
.translation = { .factor = 1024, .power = 3 }
|
||||
});
|
||||
EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Tebi" }),
|
||||
.symbol = "Ti",
|
||||
.translation = { .factor = 1024, .power = 4 }
|
||||
});
|
||||
EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Pebi" }),
|
||||
.symbol = "Pi",
|
||||
.translation = { .factor = 1024, .power = 5 }
|
||||
});
|
||||
EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Exbi" }),
|
||||
.symbol = "Ei",
|
||||
.translation = { .factor = 1024, .power = 6 }
|
||||
});
|
||||
EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Zebi" }),
|
||||
.symbol = "Zi",
|
||||
.translation = { .factor = 1024, .power = 7 }
|
||||
});
|
||||
EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Yobi" }),
|
||||
.symbol = "Yi",
|
||||
.translation = { .factor = 1024, .power = 8 }
|
||||
});
|
||||
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Duration units */
|
||||
|
||||
EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Duration" });
|
||||
prev_scope = ecs_set_scope(world, EcsDuration);
|
||||
|
||||
EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Seconds" }),
|
||||
.quantity = EcsDuration,
|
||||
.symbol = "s" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsSeconds,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "PicoSeconds" }),
|
||||
.quantity = EcsDuration,
|
||||
.base = EcsSeconds,
|
||||
.prefix = EcsPico });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsPicoSeconds,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
|
||||
EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "NanoSeconds" }),
|
||||
.quantity = EcsDuration,
|
||||
.base = EcsSeconds,
|
||||
.prefix = EcsNano });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsNanoSeconds,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MicroSeconds" }),
|
||||
.quantity = EcsDuration,
|
||||
.base = EcsSeconds,
|
||||
.prefix = EcsMicro });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMicroSeconds,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MilliSeconds" }),
|
||||
.quantity = EcsDuration,
|
||||
.base = EcsSeconds,
|
||||
.prefix = EcsMilli });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMilliSeconds,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Minutes" }),
|
||||
.quantity = EcsDuration,
|
||||
.base = EcsSeconds,
|
||||
.symbol = "min",
|
||||
.translation = { .factor = 60, .power = 1 } });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMinutes,
|
||||
.kind = EcsU32
|
||||
});
|
||||
|
||||
EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Hours" }),
|
||||
.quantity = EcsDuration,
|
||||
.base = EcsMinutes,
|
||||
.symbol = "h",
|
||||
.translation = { .factor = 60, .power = 1 } });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsHours,
|
||||
.kind = EcsU32
|
||||
});
|
||||
|
||||
EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Days" }),
|
||||
.quantity = EcsDuration,
|
||||
.base = EcsHours,
|
||||
.symbol = "d",
|
||||
.translation = { .factor = 24, .power = 1 } });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsDays,
|
||||
.kind = EcsU32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Time units */
|
||||
|
||||
EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Time" });
|
||||
prev_scope = ecs_set_scope(world, EcsTime);
|
||||
|
||||
EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Date" }),
|
||||
.quantity = EcsTime });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsDate,
|
||||
.kind = EcsU32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Mass units */
|
||||
|
||||
EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Mass" });
|
||||
prev_scope = ecs_set_scope(world, EcsMass);
|
||||
EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Grams" }),
|
||||
.quantity = EcsMass,
|
||||
.symbol = "g" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsGrams,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloGrams" }),
|
||||
.quantity = EcsMass,
|
||||
.prefix = EcsKilo,
|
||||
.base = EcsGrams });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloGrams,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Electric current units */
|
||||
|
||||
EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "ElectricCurrent" });
|
||||
prev_scope = ecs_set_scope(world, EcsElectricCurrent);
|
||||
EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Ampere" }),
|
||||
.quantity = EcsElectricCurrent,
|
||||
.symbol = "A" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsAmpere,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Amount of substance units */
|
||||
|
||||
EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Amount" });
|
||||
prev_scope = ecs_set_scope(world, EcsAmount);
|
||||
EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Mole" }),
|
||||
.quantity = EcsAmount,
|
||||
.symbol = "mol" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMole,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Luminous intensity units */
|
||||
|
||||
EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "LuminousIntensity" });
|
||||
prev_scope = ecs_set_scope(world, EcsLuminousIntensity);
|
||||
EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Candela" }),
|
||||
.quantity = EcsLuminousIntensity,
|
||||
.symbol = "cd" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsCandela,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Force units */
|
||||
|
||||
EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Force" });
|
||||
prev_scope = ecs_set_scope(world, EcsForce);
|
||||
EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Newton" }),
|
||||
.quantity = EcsForce,
|
||||
.symbol = "N" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsNewton,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Length units */
|
||||
|
||||
EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Length" });
|
||||
prev_scope = ecs_set_scope(world, EcsLength);
|
||||
EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Meters" }),
|
||||
.quantity = EcsLength,
|
||||
.symbol = "m" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMeters,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "PicoMeters" }),
|
||||
.quantity = EcsLength,
|
||||
.base = EcsMeters,
|
||||
.prefix = EcsPico });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsPicoMeters,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "NanoMeters" }),
|
||||
.quantity = EcsLength,
|
||||
.base = EcsMeters,
|
||||
.prefix = EcsNano });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsNanoMeters,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MicroMeters" }),
|
||||
.quantity = EcsLength,
|
||||
.base = EcsMeters,
|
||||
.prefix = EcsMicro });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMicroMeters,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MilliMeters" }),
|
||||
.quantity = EcsLength,
|
||||
.base = EcsMeters,
|
||||
.prefix = EcsMilli });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMilliMeters,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "CentiMeters" }),
|
||||
.quantity = EcsLength,
|
||||
.base = EcsMeters,
|
||||
.prefix = EcsCenti });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsCentiMeters,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloMeters" }),
|
||||
.quantity = EcsLength,
|
||||
.base = EcsMeters,
|
||||
.prefix = EcsKilo });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloMeters,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Miles" }),
|
||||
.quantity = EcsLength,
|
||||
.symbol = "mi"
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMiles,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsPixels = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Pixels" }),
|
||||
.quantity = EcsLength,
|
||||
.symbol = "px"
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsPixels,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Pressure units */
|
||||
|
||||
EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Pressure" });
|
||||
prev_scope = ecs_set_scope(world, EcsPressure);
|
||||
EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Pascal" }),
|
||||
.quantity = EcsPressure,
|
||||
.symbol = "Pa" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsPascal,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Bar" }),
|
||||
.quantity = EcsPressure,
|
||||
.symbol = "bar" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsBar,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Speed units */
|
||||
|
||||
EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Speed" });
|
||||
prev_scope = ecs_set_scope(world, EcsSpeed);
|
||||
EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MetersPerSecond" }),
|
||||
.quantity = EcsSpeed,
|
||||
.base = EcsMeters,
|
||||
.over = EcsSeconds });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMetersPerSecond,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloMetersPerSecond" }),
|
||||
.quantity = EcsSpeed,
|
||||
.base = EcsKiloMeters,
|
||||
.over = EcsSeconds });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloMetersPerSecond,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloMetersPerHour" }),
|
||||
.quantity = EcsSpeed,
|
||||
.base = EcsKiloMeters,
|
||||
.over = EcsHours });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloMetersPerHour,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MilesPerHour" }),
|
||||
.quantity = EcsSpeed,
|
||||
.base = EcsMiles,
|
||||
.over = EcsHours });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMilesPerHour,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Acceleration */
|
||||
|
||||
EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Acceleration" }),
|
||||
.base = EcsMetersPerSecond,
|
||||
.over = EcsSeconds });
|
||||
ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.id = EcsAcceleration
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsAcceleration,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
/* Temperature units */
|
||||
|
||||
EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Temperature" });
|
||||
prev_scope = ecs_set_scope(world, EcsTemperature);
|
||||
EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Kelvin" }),
|
||||
.quantity = EcsTemperature,
|
||||
.symbol = "K" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKelvin,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Celsius" }),
|
||||
.quantity = EcsTemperature,
|
||||
.symbol = "°C" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsCelsius,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Fahrenheit" }),
|
||||
.quantity = EcsTemperature,
|
||||
.symbol = "F" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsFahrenheit,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Data units */
|
||||
|
||||
EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Data" });
|
||||
prev_scope = ecs_set_scope(world, EcsData);
|
||||
|
||||
EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Bits" }),
|
||||
.quantity = EcsData,
|
||||
.symbol = "bit" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsBits,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloBits" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBits,
|
||||
.prefix = EcsKilo });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloBits,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MegaBits" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBits,
|
||||
.prefix = EcsMega });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMegaBits,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "GigaBits" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBits,
|
||||
.prefix = EcsGiga });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsGigaBits,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Bytes" }),
|
||||
.quantity = EcsData,
|
||||
.symbol = "B",
|
||||
.base = EcsBits,
|
||||
.translation = { .factor = 8, .power = 1 } });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsBytes,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloBytes" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBytes,
|
||||
.prefix = EcsKilo });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloBytes,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MegaBytes" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBytes,
|
||||
.prefix = EcsMega });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMegaBytes,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "GigaBytes" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBytes,
|
||||
.prefix = EcsGiga });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsGigaBytes,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KibiBytes" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBytes,
|
||||
.prefix = EcsKibi });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKibiBytes,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MebiBytes" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBytes,
|
||||
.prefix = EcsMebi });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMebiBytes,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "GibiBytes" }),
|
||||
.quantity = EcsData,
|
||||
.base = EcsBytes,
|
||||
.prefix = EcsGibi });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsGibiBytes,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* DataRate units */
|
||||
|
||||
EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "DataRate" });
|
||||
prev_scope = ecs_set_scope(world, EcsDataRate);
|
||||
|
||||
EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "BitsPerSecond" }),
|
||||
.quantity = EcsDataRate,
|
||||
.base = EcsBits,
|
||||
.over = EcsSeconds });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsBitsPerSecond,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloBitsPerSecond" }),
|
||||
.quantity = EcsDataRate,
|
||||
.base = EcsKiloBits,
|
||||
.over = EcsSeconds
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloBitsPerSecond,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MegaBitsPerSecond" }),
|
||||
.quantity = EcsDataRate,
|
||||
.base = EcsMegaBits,
|
||||
.over = EcsSeconds
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMegaBitsPerSecond,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "GigaBitsPerSecond" }),
|
||||
.quantity = EcsDataRate,
|
||||
.base = EcsGigaBits,
|
||||
.over = EcsSeconds
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsGigaBitsPerSecond,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "BytesPerSecond" }),
|
||||
.quantity = EcsDataRate,
|
||||
.base = EcsBytes,
|
||||
.over = EcsSeconds });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsBytesPerSecond,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloBytesPerSecond" }),
|
||||
.quantity = EcsDataRate,
|
||||
.base = EcsKiloBytes,
|
||||
.over = EcsSeconds
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloBytesPerSecond,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MegaBytesPerSecond" }),
|
||||
.quantity = EcsDataRate,
|
||||
.base = EcsMegaBytes,
|
||||
.over = EcsSeconds
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMegaBytesPerSecond,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "GigaBytesPerSecond" }),
|
||||
.quantity = EcsDataRate,
|
||||
.base = EcsGigaBytes,
|
||||
.over = EcsSeconds
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsGigaBytesPerSecond,
|
||||
.kind = EcsU64
|
||||
});
|
||||
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Percentage */
|
||||
|
||||
EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Percentage" });
|
||||
ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = EcsPercentage,
|
||||
.symbol = "%"
|
||||
});
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsPercentage,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
/* Angles */
|
||||
|
||||
EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Angle" });
|
||||
prev_scope = ecs_set_scope(world, EcsAngle);
|
||||
EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Radians" }),
|
||||
.quantity = EcsAngle,
|
||||
.symbol = "rad" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsRadians,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Degrees" }),
|
||||
.quantity = EcsAngle,
|
||||
.symbol = "°" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsDegrees,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* DeciBel */
|
||||
|
||||
EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Bel" }),
|
||||
.symbol = "B" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsBel,
|
||||
.kind = EcsF32
|
||||
});
|
||||
EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "DeciBel" }),
|
||||
.prefix = EcsDeci,
|
||||
.base = EcsBel });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsDeciBel,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Frequency" });
|
||||
prev_scope = ecs_set_scope(world, EcsFrequency);
|
||||
|
||||
EcsHertz = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Hertz" }),
|
||||
.quantity = EcsFrequency,
|
||||
.symbol = "Hz" });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsHertz,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsKiloHertz = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "KiloHertz" }),
|
||||
.prefix = EcsKilo,
|
||||
.base = EcsHertz });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsKiloHertz,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsMegaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "MegaHertz" }),
|
||||
.prefix = EcsMega,
|
||||
.base = EcsHertz });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsMegaHertz,
|
||||
.kind = EcsF32
|
||||
});
|
||||
|
||||
EcsGigaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "GigaHertz" }),
|
||||
.prefix = EcsGiga,
|
||||
.base = EcsHertz });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsGigaHertz,
|
||||
.kind = EcsF32
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){
|
||||
.name = "Uri" });
|
||||
prev_scope = ecs_set_scope(world, EcsUri);
|
||||
|
||||
EcsUriHyperlink = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Hyperlink" }),
|
||||
.quantity = EcsUri });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsUriHyperlink,
|
||||
.kind = EcsString
|
||||
});
|
||||
|
||||
EcsUriImage = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "Image" }),
|
||||
.quantity = EcsUri });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsUriImage,
|
||||
.kind = EcsString
|
||||
});
|
||||
|
||||
EcsUriFile = ecs_unit_init(world, &(ecs_unit_desc_t){
|
||||
.entity = ecs_entity(world, { .name = "File" }),
|
||||
.quantity = EcsUri });
|
||||
ecs_primitive_init(world, &(ecs_primitive_desc_t){
|
||||
.entity = EcsUriFile,
|
||||
.kind = EcsString
|
||||
});
|
||||
ecs_set_scope(world, prev_scope);
|
||||
|
||||
/* Documentation */
|
||||
#ifdef FLECS_DOC
|
||||
ECS_IMPORT(world, FlecsDoc);
|
||||
|
||||
ecs_doc_set_brief(world, EcsDuration,
|
||||
"Time amount (e.g. \"20 seconds\", \"2 hours\")");
|
||||
ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds");
|
||||
ecs_doc_set_brief(world, EcsMinutes, "60 seconds");
|
||||
ecs_doc_set_brief(world, EcsHours, "60 minutes");
|
||||
ecs_doc_set_brief(world, EcsDays, "24 hours");
|
||||
|
||||
ecs_doc_set_brief(world, EcsTime,
|
||||
"Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")");
|
||||
ecs_doc_set_brief(world, EcsDate,
|
||||
"Seconds passed since January 1st 1970");
|
||||
|
||||
ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsElectricCurrent,
|
||||
"Units of electrical current (e.g. \"2 ampere\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsAmount,
|
||||
"Units of amount of substance (e.g. \"2 mole\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsLuminousIntensity,
|
||||
"Units of luminous intensity (e.g. \"1 candela\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsLength,
|
||||
"Units of length (e.g. \"5 meters\", \"20 miles\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsPressure,
|
||||
"Units of pressure (e.g. \"1 bar\", \"1000 pascal\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsSpeed,
|
||||
"Units of movement (e.g. \"5 meters/second\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsAcceleration,
|
||||
"Unit of speed increase (e.g. \"5 meters/second/second\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsTemperature,
|
||||
"Units of temperature (e.g. \"5 degrees Celsius\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsData,
|
||||
"Units of information (e.g. \"8 bits\", \"100 megabytes\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsDataRate,
|
||||
"Units of data transmission (e.g. \"100 megabits/second\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsAngle,
|
||||
"Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")");
|
||||
|
||||
ecs_doc_set_brief(world, EcsFrequency,
|
||||
"The number of occurrences of a repeating event per unit of time.");
|
||||
|
||||
ecs_doc_set_brief(world, EcsUri, "Universal resource identifier.");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
945
engine/libs/flecs/src/bootstrap.c
Normal file
945
engine/libs/flecs/src/bootstrap.c
Normal file
@@ -0,0 +1,945 @@
|
||||
/**
|
||||
* @file bootstrap.c
|
||||
* @brief Bootstrap entities in the flecs.core namespace.
|
||||
*
|
||||
* Before the ECS storage can be used, core entities such first need to be
|
||||
* initialized. For example, components in Flecs are stored as entities in the
|
||||
* ECS storage itself with an EcsComponent component, but before this component
|
||||
* can be stored, the component itself needs to be initialized.
|
||||
*
|
||||
* The bootstrap code uses lower-level APIs to initialize the data structures.
|
||||
* After bootstrap is completed, regular ECS operations can be used to create
|
||||
* entities and components.
|
||||
*
|
||||
* The bootstrap file also includes several lifecycle hooks and observers for
|
||||
* builtin features, such as relationship properties and hooks for keeping the
|
||||
* entity name administration in sync with the (Identifier, Name) component.
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
|
||||
/* -- Identifier Component -- */
|
||||
static ECS_DTOR(EcsIdentifier, ptr, {
|
||||
ecs_os_strset(&ptr->value, NULL);
|
||||
})
|
||||
|
||||
static ECS_COPY(EcsIdentifier, dst, src, {
|
||||
ecs_os_strset(&dst->value, src->value);
|
||||
dst->hash = src->hash;
|
||||
dst->length = src->length;
|
||||
dst->index_hash = src->index_hash;
|
||||
dst->index = src->index;
|
||||
})
|
||||
|
||||
static ECS_MOVE(EcsIdentifier, dst, src, {
|
||||
ecs_os_strset(&dst->value, NULL);
|
||||
dst->value = src->value;
|
||||
dst->hash = src->hash;
|
||||
dst->length = src->length;
|
||||
dst->index_hash = src->index_hash;
|
||||
dst->index = src->index;
|
||||
|
||||
src->value = NULL;
|
||||
src->hash = 0;
|
||||
src->index_hash = 0;
|
||||
src->index = 0;
|
||||
src->length = 0;
|
||||
})
|
||||
|
||||
static
|
||||
void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) {
|
||||
EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 1);
|
||||
|
||||
ecs_world_t *world = it->real_world;
|
||||
ecs_entity_t evt = it->event;
|
||||
ecs_id_t evt_id = it->event_id;
|
||||
ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */
|
||||
ecs_id_t pair = ecs_childof(0);
|
||||
ecs_hashmap_t *index = NULL;
|
||||
|
||||
if (kind == EcsSymbol) {
|
||||
index = &world->symbols;
|
||||
} else if (kind == EcsAlias) {
|
||||
index = &world->aliases;
|
||||
} else if (kind == EcsName) {
|
||||
ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair);
|
||||
ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (evt == EcsOnSet) {
|
||||
index = flecs_id_name_index_ensure(world, pair);
|
||||
} else {
|
||||
index = flecs_id_name_index_get(world, pair);
|
||||
}
|
||||
}
|
||||
|
||||
int i, count = it->count;
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
EcsIdentifier *cur = &ptr[i];
|
||||
uint64_t hash;
|
||||
ecs_size_t len;
|
||||
const char *name = cur->value;
|
||||
|
||||
if (cur->index && cur->index != index) {
|
||||
/* If index doesn't match up, the value must have been copied from
|
||||
* another entity, so reset index & cached index hash */
|
||||
cur->index = NULL;
|
||||
cur->index_hash = 0;
|
||||
}
|
||||
|
||||
if (cur->value && (evt == EcsOnSet)) {
|
||||
len = cur->length = ecs_os_strlen(name);
|
||||
hash = cur->hash = flecs_hash(name, len);
|
||||
} else {
|
||||
len = cur->length = 0;
|
||||
hash = cur->hash = 0;
|
||||
cur->index = NULL;
|
||||
}
|
||||
|
||||
if (index) {
|
||||
uint64_t index_hash = cur->index_hash;
|
||||
ecs_entity_t e = it->entities[i];
|
||||
|
||||
if (hash != index_hash) {
|
||||
if (index_hash) {
|
||||
flecs_name_index_remove(index, e, index_hash);
|
||||
}
|
||||
if (hash) {
|
||||
flecs_name_index_ensure(index, e, name, len, hash);
|
||||
cur->index_hash = hash;
|
||||
cur->index = index;
|
||||
}
|
||||
} else {
|
||||
/* Name didn't change, but the string could have been
|
||||
* reallocated. Make sure name index points to correct string */
|
||||
flecs_name_index_update_name(index, e, hash, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -- Poly component -- */
|
||||
|
||||
static ECS_COPY(EcsPoly, dst, src, {
|
||||
(void)dst;
|
||||
(void)src;
|
||||
ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied");
|
||||
})
|
||||
|
||||
static ECS_MOVE(EcsPoly, dst, src, {
|
||||
if (dst->poly && (dst->poly != src->poly)) {
|
||||
ecs_poly_dtor_t *dtor = ecs_get_dtor(dst->poly);
|
||||
ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
dtor[0](dst->poly);
|
||||
}
|
||||
|
||||
dst->poly = src->poly;
|
||||
src->poly = NULL;
|
||||
})
|
||||
|
||||
static ECS_DTOR(EcsPoly, ptr, {
|
||||
if (ptr->poly) {
|
||||
ecs_poly_dtor_t *dtor = ecs_get_dtor(ptr->poly);
|
||||
ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
dtor[0](ptr->poly);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
/* -- Builtin triggers -- */
|
||||
|
||||
static
|
||||
void flecs_assert_relation_unused(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t rel,
|
||||
ecs_entity_t property)
|
||||
{
|
||||
if (world->flags & (EcsWorldInit|EcsWorldFini)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_vec_t *marked_ids = &world->store.marked_ids;
|
||||
int32_t i, count = ecs_vec_count(marked_ids);
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_marked_id_t *mid = ecs_vec_get_t(marked_ids, ecs_marked_id_t, i);
|
||||
if (mid->id == ecs_pair(rel, EcsWildcard)) {
|
||||
/* If id is being cleaned up, no need to throw error as tables will
|
||||
* be cleaned up */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard));
|
||||
if (property != EcsUnion) {
|
||||
in_use |= ecs_id_in_use(world, rel);
|
||||
}
|
||||
if (in_use) {
|
||||
char *r_str = ecs_get_fullpath(world, rel);
|
||||
char *p_str = ecs_get_fullpath(world, property);
|
||||
|
||||
ecs_throw(ECS_ID_IN_USE,
|
||||
"cannot change property '%s' for relationship '%s': already in use",
|
||||
p_str, r_str);
|
||||
|
||||
ecs_os_free(r_str);
|
||||
ecs_os_free(p_str);
|
||||
}
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_set_id_flag(
|
||||
ecs_id_record_t *idr,
|
||||
ecs_flags32_t flag)
|
||||
{
|
||||
if (!(idr->flags & flag)) {
|
||||
idr->flags |= flag;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_unset_id_flag(
|
||||
ecs_id_record_t *idr,
|
||||
ecs_flags32_t flag)
|
||||
{
|
||||
if ((idr->flags & flag)) {
|
||||
idr->flags &= ~flag;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_id_flag_for_relation(
|
||||
ecs_iter_t *it,
|
||||
ecs_entity_t prop,
|
||||
ecs_flags32_t flag,
|
||||
ecs_flags32_t not_flag,
|
||||
ecs_flags32_t entity_flag)
|
||||
{
|
||||
ecs_world_t *world = it->world;
|
||||
ecs_entity_t event = it->event;
|
||||
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
bool changed = false;
|
||||
|
||||
if (event == EcsOnAdd) {
|
||||
ecs_id_record_t *idr = flecs_id_record_ensure(world, e);
|
||||
changed |= flecs_set_id_flag(idr, flag);
|
||||
idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard));
|
||||
do {
|
||||
changed |= flecs_set_id_flag(idr, flag);
|
||||
} while ((idr = idr->first.next));
|
||||
if (entity_flag) flecs_add_flag(world, e, entity_flag);
|
||||
} else if (event == EcsOnRemove) {
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, e);
|
||||
if (idr) changed |= flecs_unset_id_flag(idr, not_flag);
|
||||
idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard));
|
||||
if (idr) {
|
||||
do {
|
||||
changed |= flecs_unset_id_flag(idr, not_flag);
|
||||
} while ((idr = idr->first.next));
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
flecs_assert_relation_unused(world, e, prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_final(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
if (flecs_id_record_get(world, ecs_pair(EcsIsA, e)) != NULL) {
|
||||
char *e_str = ecs_get_fullpath(world, e);
|
||||
ecs_throw(ECS_ID_IN_USE,
|
||||
"cannot change property 'Final' for '%s': already inherited from",
|
||||
e_str);
|
||||
ecs_os_free(e_str);
|
||||
error:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_on_delete(ecs_iter_t *it) {
|
||||
ecs_id_t id = ecs_field_id(it, 1);
|
||||
flecs_register_id_flag_for_relation(it, EcsOnDelete,
|
||||
ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)),
|
||||
EcsIdOnDeleteMask,
|
||||
EcsEntityIsId);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_on_delete_object(ecs_iter_t *it) {
|
||||
ecs_id_t id = ecs_field_id(it, 1);
|
||||
flecs_register_id_flag_for_relation(it, EcsOnDeleteTarget,
|
||||
ECS_ID_ON_DELETE_TARGET_FLAG(ECS_PAIR_SECOND(id)),
|
||||
EcsIdOnDeleteObjectMask,
|
||||
EcsEntityIsId);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_traversable(ecs_iter_t *it) {
|
||||
flecs_register_id_flag_for_relation(it, EcsAcyclic, EcsIdTraversable,
|
||||
EcsIdTraversable, 0);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_tag(ecs_iter_t *it) {
|
||||
flecs_register_id_flag_for_relation(it, EcsTag, EcsIdTag, ~EcsIdTag, 0);
|
||||
|
||||
/* Ensure that all id records for tag have type info set to NULL */
|
||||
ecs_world_t *world = it->real_world;
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
|
||||
if (it->event == EcsOnAdd) {
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world,
|
||||
ecs_pair(e, EcsWildcard));
|
||||
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
do {
|
||||
if (idr->type_info != NULL) {
|
||||
flecs_assert_relation_unused(world, e, EcsTag);
|
||||
}
|
||||
idr->type_info = NULL;
|
||||
} while ((idr = idr->first.next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_exclusive(ecs_iter_t *it) {
|
||||
flecs_register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive,
|
||||
EcsIdExclusive, 0);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_dont_inherit(ecs_iter_t *it) {
|
||||
flecs_register_id_flag_for_relation(it, EcsDontInherit,
|
||||
EcsIdDontInherit, EcsIdDontInherit, 0);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_always_override(ecs_iter_t *it) {
|
||||
flecs_register_id_flag_for_relation(it, EcsAlwaysOverride,
|
||||
EcsIdAlwaysOverride, EcsIdAlwaysOverride, 0);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_with(ecs_iter_t *it) {
|
||||
flecs_register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_union(ecs_iter_t *it) {
|
||||
flecs_register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_slot_of(ecs_iter_t *it) {
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_add_id(it->world, it->entities[i], EcsUnion);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_on_symmetric_add_remove(ecs_iter_t *it) {
|
||||
ecs_entity_t pair = ecs_field_id(it, 1);
|
||||
|
||||
if (!ECS_HAS_ID_FLAG(pair, PAIR)) {
|
||||
/* If relationship was not added as a pair, there's nothing to do */
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_world_t *world = it->world;
|
||||
ecs_entity_t rel = ECS_PAIR_FIRST(pair);
|
||||
ecs_entity_t obj = ecs_pair_second(world, pair);
|
||||
ecs_entity_t event = it->event;
|
||||
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t subj = it->entities[i];
|
||||
if (event == EcsOnAdd) {
|
||||
if (!ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) {
|
||||
ecs_add_pair(it->world, obj, rel, subj);
|
||||
}
|
||||
} else {
|
||||
if (ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) {
|
||||
ecs_remove_pair(it->world, obj, rel, subj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_register_symmetric(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->real_world;
|
||||
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t r = it->entities[i];
|
||||
flecs_assert_relation_unused(world, r, EcsSymmetric);
|
||||
|
||||
/* Create observer that adds the reverse relationship when R(X, Y) is
|
||||
* added, or remove the reverse relationship when R(X, Y) is removed. */
|
||||
ecs_observer(world, {
|
||||
.entity = ecs_entity(world, {.add = {ecs_childof(r)}}),
|
||||
.filter.terms[0] = { .id = ecs_pair(r, EcsWildcard) },
|
||||
.callback = flecs_on_symmetric_add_remove,
|
||||
.events = {EcsOnAdd, EcsOnRemove}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_on_component(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
EcsComponent *c = ecs_field(it, EcsComponent, 1);
|
||||
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
|
||||
uint32_t component_id = (uint32_t)e; /* Strip generation */
|
||||
ecs_assert(component_id < ECS_MAX_COMPONENT_ID, ECS_OUT_OF_RANGE,
|
||||
"component id must be smaller than %u", ECS_MAX_COMPONENT_ID);
|
||||
(void)component_id;
|
||||
|
||||
if (it->event == EcsOnSet) {
|
||||
if (flecs_type_info_init_id(
|
||||
world, e, c[i].size, c[i].alignment, NULL))
|
||||
{
|
||||
flecs_assert_relation_unused(world, e, ecs_id(EcsComponent));
|
||||
}
|
||||
} else if (it->event == EcsOnRemove) {
|
||||
flecs_type_info_free(world, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_ensure_module_tag(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
int i, count = it->count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0);
|
||||
if (parent) {
|
||||
ecs_add_id(world, parent, EcsModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -- Iterable mixins -- */
|
||||
|
||||
static
|
||||
void flecs_on_event_iterable_init(
|
||||
const ecs_world_t *world,
|
||||
const ecs_poly_t *poly, /* Observable */
|
||||
ecs_iter_t *it,
|
||||
ecs_term_t *filter)
|
||||
{
|
||||
ecs_iter_poly(world, poly, it, filter);
|
||||
it->event_id = filter->id;
|
||||
}
|
||||
|
||||
/* -- Bootstrapping -- */
|
||||
|
||||
#define flecs_bootstrap_builtin_t(world, table, name)\
|
||||
flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\
|
||||
ECS_ALIGNOF(name))
|
||||
|
||||
static
|
||||
void flecs_bootstrap_builtin(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_entity_t entity,
|
||||
const char *symbol,
|
||||
ecs_size_t size,
|
||||
ecs_size_t alignment)
|
||||
{
|
||||
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_column_t *columns = table->data.columns;
|
||||
ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_record_t *record = flecs_entities_ensure(world, entity);
|
||||
record->table = table;
|
||||
|
||||
int32_t index = flecs_table_append(world, table, entity, false, false);
|
||||
record->row = ECS_ROW_TO_RECORD(index, 0);
|
||||
|
||||
EcsComponent *component = ecs_vec_first(&columns[0].data);
|
||||
component[index].size = size;
|
||||
component[index].alignment = alignment;
|
||||
|
||||
const char *name = &symbol[3]; /* Strip 'Ecs' */
|
||||
ecs_size_t symbol_length = ecs_os_strlen(symbol);
|
||||
ecs_size_t name_length = symbol_length - 3;
|
||||
|
||||
EcsIdentifier *name_col = ecs_vec_first(&columns[1].data);
|
||||
uint64_t name_hash = flecs_hash(name, name_length);
|
||||
name_col[index].value = ecs_os_strdup(name);
|
||||
name_col[index].length = name_length;
|
||||
name_col[index].hash = name_hash;
|
||||
name_col[index].index_hash = 0;
|
||||
name_col[index].index = table->_->name_index;
|
||||
flecs_name_index_ensure(
|
||||
table->_->name_index, entity, name, name_length, name_hash);
|
||||
|
||||
EcsIdentifier *symbol_col = ecs_vec_first(&columns[2].data);
|
||||
symbol_col[index].value = ecs_os_strdup(symbol);
|
||||
symbol_col[index].length = symbol_length;
|
||||
symbol_col[index].hash = flecs_hash(symbol, symbol_length);
|
||||
symbol_col[index].index_hash = 0;
|
||||
symbol_col[index].index = NULL;
|
||||
}
|
||||
|
||||
/** Initialize component table. This table is manually constructed to bootstrap
|
||||
* flecs. After this function has been called, the builtin components can be
|
||||
* created.
|
||||
* The reason this table is constructed manually is because it requires the size
|
||||
* and alignment of the EcsComponent and EcsIdentifier components, which haven't
|
||||
* been created yet */
|
||||
static
|
||||
ecs_table_t* flecs_bootstrap_component_table(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
/* Before creating table, manually set flags for ChildOf/Identifier, as this
|
||||
* can no longer be done after they are in use. */
|
||||
ecs_id_record_t *idr = flecs_id_record_ensure(world, EcsChildOf);
|
||||
idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit |
|
||||
EcsIdTraversable | EcsIdTag;
|
||||
|
||||
/* Initialize id records cached on world */
|
||||
world->idr_childof_wildcard = flecs_id_record_ensure(world,
|
||||
ecs_pair(EcsChildOf, EcsWildcard));
|
||||
world->idr_childof_wildcard->flags |= EcsIdOnDeleteObjectDelete |
|
||||
EcsIdDontInherit | EcsIdTraversable | EcsIdTag | EcsIdExclusive;
|
||||
idr = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsWildcard));
|
||||
idr->flags |= EcsIdDontInherit;
|
||||
world->idr_identifier_name =
|
||||
flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsName));
|
||||
world->idr_childof_0 = flecs_id_record_ensure(world,
|
||||
ecs_pair(EcsChildOf, 0));
|
||||
|
||||
ecs_id_t ids[] = {
|
||||
ecs_id(EcsComponent),
|
||||
EcsFinal,
|
||||
ecs_pair_t(EcsIdentifier, EcsName),
|
||||
ecs_pair_t(EcsIdentifier, EcsSymbol),
|
||||
ecs_pair(EcsChildOf, EcsFlecsCore),
|
||||
ecs_pair(EcsOnDelete, EcsPanic)
|
||||
};
|
||||
|
||||
ecs_type_t array = {
|
||||
.array = ids,
|
||||
.count = 6
|
||||
};
|
||||
|
||||
ecs_table_t *result = flecs_table_find_or_create(world, &array);
|
||||
ecs_data_t *data = &result->data;
|
||||
|
||||
/* Preallocate enough memory for initial components */
|
||||
ecs_allocator_t *a = &world->allocator;
|
||||
ecs_vec_init_t(a, &data->entities, ecs_entity_t, EcsFirstUserComponentId);
|
||||
ecs_vec_init_t(a, &data->columns[0].data, EcsComponent, EcsFirstUserComponentId);
|
||||
ecs_vec_init_t(a, &data->columns[1].data, EcsIdentifier, EcsFirstUserComponentId);
|
||||
ecs_vec_init_t(a, &data->columns[2].data, EcsIdentifier, EcsFirstUserComponentId);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_bootstrap_entity(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t id,
|
||||
const char *name,
|
||||
ecs_entity_t parent)
|
||||
{
|
||||
char symbol[256];
|
||||
ecs_os_strcpy(symbol, "flecs.core.");
|
||||
ecs_os_strcat(symbol, name);
|
||||
|
||||
ecs_ensure(world, id);
|
||||
ecs_add_pair(world, id, EcsChildOf, parent);
|
||||
ecs_set_name(world, id, name);
|
||||
ecs_set_symbol(world, id, symbol);
|
||||
|
||||
ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (!parent || parent == EcsFlecsCore) {
|
||||
ecs_assert(ecs_lookup_fullpath(world, name) == id,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_bootstrap(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_log_push();
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
/* Ensure builtin ids are alive */
|
||||
ecs_ensure(world, ecs_id(EcsComponent));
|
||||
ecs_ensure(world, EcsFinal);
|
||||
ecs_ensure(world, ecs_id(EcsIdentifier));
|
||||
ecs_ensure(world, EcsName);
|
||||
ecs_ensure(world, EcsSymbol);
|
||||
ecs_ensure(world, EcsAlias);
|
||||
ecs_ensure(world, EcsChildOf);
|
||||
ecs_ensure(world, EcsFlecs);
|
||||
ecs_ensure(world, EcsFlecsCore);
|
||||
ecs_ensure(world, EcsOnAdd);
|
||||
ecs_ensure(world, EcsOnRemove);
|
||||
ecs_ensure(world, EcsOnSet);
|
||||
ecs_ensure(world, EcsUnSet);
|
||||
ecs_ensure(world, EcsOnDelete);
|
||||
ecs_ensure(world, EcsPanic);
|
||||
ecs_ensure(world, EcsFlag);
|
||||
ecs_ensure(world, EcsIsA);
|
||||
ecs_ensure(world, EcsWildcard);
|
||||
ecs_ensure(world, EcsAny);
|
||||
ecs_ensure(world, EcsTag);
|
||||
|
||||
/* Register type information for builtin components */
|
||||
flecs_type_info_init(world, EcsComponent, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.on_set = flecs_on_component,
|
||||
.on_remove = flecs_on_component
|
||||
});
|
||||
|
||||
flecs_type_info_init(world, EcsIdentifier, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.dtor = ecs_dtor(EcsIdentifier),
|
||||
.copy = ecs_copy(EcsIdentifier),
|
||||
.move = ecs_move(EcsIdentifier),
|
||||
.on_set = ecs_on_set(EcsIdentifier),
|
||||
.on_remove = ecs_on_set(EcsIdentifier)
|
||||
});
|
||||
|
||||
flecs_type_info_init(world, EcsPoly, {
|
||||
.ctor = ecs_default_ctor,
|
||||
.copy = ecs_copy(EcsPoly),
|
||||
.move = ecs_move(EcsPoly),
|
||||
.dtor = ecs_dtor(EcsPoly)
|
||||
});
|
||||
|
||||
flecs_type_info_init(world, EcsIterable, { 0 });
|
||||
flecs_type_info_init(world, EcsTarget, { 0 });
|
||||
|
||||
/* Create and cache often used id records on world */
|
||||
flecs_init_id_records(world);
|
||||
|
||||
/* Create table for builtin components. This table temporarily stores the
|
||||
* entities associated with builtin components, until they get moved to
|
||||
* other tables once properties are added (see below) */
|
||||
ecs_table_t *table = flecs_bootstrap_component_table(world);
|
||||
assert(table != NULL);
|
||||
|
||||
/* Bootstrap builtin components */
|
||||
flecs_bootstrap_builtin_t(world, table, EcsIdentifier);
|
||||
flecs_bootstrap_builtin_t(world, table, EcsComponent);
|
||||
flecs_bootstrap_builtin_t(world, table, EcsIterable);
|
||||
flecs_bootstrap_builtin_t(world, table, EcsPoly);
|
||||
flecs_bootstrap_builtin_t(world, table, EcsTarget);
|
||||
|
||||
/* Initialize default entity id range */
|
||||
world->info.last_component_id = EcsFirstUserComponentId;
|
||||
flecs_entities_max_id(world) = EcsFirstUserEntityId;
|
||||
world->info.min_id = 0;
|
||||
world->info.max_id = 0;
|
||||
|
||||
/* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */
|
||||
ecs_set(world, EcsOnAdd, EcsIterable, { .init = flecs_on_event_iterable_init });
|
||||
ecs_set(world, EcsOnSet, EcsIterable, { .init = flecs_on_event_iterable_init });
|
||||
|
||||
/* Register observer for tag property before adding EcsTag */
|
||||
ecs_observer(world, {
|
||||
.entity = ecs_entity(world, {.add = { ecs_childof(EcsFlecsInternals)}}),
|
||||
.filter.terms[0] = { .id = EcsTag, .src.flags = EcsSelf },
|
||||
.events = {EcsOnAdd, EcsOnRemove},
|
||||
.callback = flecs_register_tag,
|
||||
.yield_existing = true
|
||||
});
|
||||
|
||||
/* Populate core module */
|
||||
ecs_set_scope(world, EcsFlecsCore);
|
||||
|
||||
flecs_bootstrap_tag(world, EcsName);
|
||||
flecs_bootstrap_tag(world, EcsSymbol);
|
||||
flecs_bootstrap_tag(world, EcsAlias);
|
||||
|
||||
flecs_bootstrap_tag(world, EcsQuery);
|
||||
flecs_bootstrap_tag(world, EcsObserver);
|
||||
|
||||
flecs_bootstrap_tag(world, EcsModule);
|
||||
flecs_bootstrap_tag(world, EcsPrivate);
|
||||
flecs_bootstrap_tag(world, EcsPrefab);
|
||||
flecs_bootstrap_tag(world, EcsSlotOf);
|
||||
flecs_bootstrap_tag(world, EcsDisabled);
|
||||
flecs_bootstrap_tag(world, EcsEmpty);
|
||||
|
||||
/* Initialize builtin modules */
|
||||
ecs_set_name(world, EcsFlecs, "flecs");
|
||||
ecs_add_id(world, EcsFlecs, EcsModule);
|
||||
ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic);
|
||||
|
||||
ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs);
|
||||
ecs_set_name(world, EcsFlecsCore, "core");
|
||||
ecs_add_id(world, EcsFlecsCore, EcsModule);
|
||||
|
||||
ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore);
|
||||
ecs_set_name(world, EcsFlecsInternals, "internals");
|
||||
ecs_add_id(world, EcsFlecsInternals, EcsModule);
|
||||
|
||||
/* Self check */
|
||||
ecs_record_t *r = flecs_entities_get(world, EcsFlecs);
|
||||
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(r->row & EcsEntityIsTraversable, ECS_INTERNAL_ERROR, NULL);
|
||||
(void)r;
|
||||
|
||||
/* Initialize builtin entities */
|
||||
flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsAny, "_", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsThis, "this", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore);
|
||||
|
||||
/* Component/relationship properties */
|
||||
flecs_bootstrap_tag(world, EcsTransitive);
|
||||
flecs_bootstrap_tag(world, EcsReflexive);
|
||||
flecs_bootstrap_tag(world, EcsSymmetric);
|
||||
flecs_bootstrap_tag(world, EcsFinal);
|
||||
flecs_bootstrap_tag(world, EcsDontInherit);
|
||||
flecs_bootstrap_tag(world, EcsAlwaysOverride);
|
||||
flecs_bootstrap_tag(world, EcsTag);
|
||||
flecs_bootstrap_tag(world, EcsUnion);
|
||||
flecs_bootstrap_tag(world, EcsExclusive);
|
||||
flecs_bootstrap_tag(world, EcsAcyclic);
|
||||
flecs_bootstrap_tag(world, EcsTraversable);
|
||||
flecs_bootstrap_tag(world, EcsWith);
|
||||
flecs_bootstrap_tag(world, EcsOneOf);
|
||||
|
||||
flecs_bootstrap_tag(world, EcsOnDelete);
|
||||
flecs_bootstrap_tag(world, EcsOnDeleteTarget);
|
||||
flecs_bootstrap_tag(world, EcsRemove);
|
||||
flecs_bootstrap_tag(world, EcsDelete);
|
||||
flecs_bootstrap_tag(world, EcsPanic);
|
||||
|
||||
flecs_bootstrap_tag(world, EcsFlatten);
|
||||
flecs_bootstrap_tag(world, EcsDefaultChildComponent);
|
||||
|
||||
/* Builtin predicates */
|
||||
flecs_bootstrap_tag(world, EcsPredEq);
|
||||
flecs_bootstrap_tag(world, EcsPredMatch);
|
||||
flecs_bootstrap_tag(world, EcsPredLookup);
|
||||
flecs_bootstrap_tag(world, EcsScopeOpen);
|
||||
flecs_bootstrap_tag(world, EcsScopeClose);
|
||||
|
||||
/* Builtin relationships */
|
||||
flecs_bootstrap_tag(world, EcsIsA);
|
||||
flecs_bootstrap_tag(world, EcsChildOf);
|
||||
flecs_bootstrap_tag(world, EcsDependsOn);
|
||||
|
||||
/* Builtin events */
|
||||
flecs_bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore);
|
||||
flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore);
|
||||
|
||||
/* Tag relationships (relationships that should never have data) */
|
||||
ecs_add_id(world, EcsIsA, EcsTag);
|
||||
ecs_add_id(world, EcsChildOf, EcsTag);
|
||||
ecs_add_id(world, EcsSlotOf, EcsTag);
|
||||
ecs_add_id(world, EcsDependsOn, EcsTag);
|
||||
ecs_add_id(world, EcsFlatten, EcsTag);
|
||||
ecs_add_id(world, EcsDefaultChildComponent, EcsTag);
|
||||
ecs_add_id(world, EcsUnion, EcsTag);
|
||||
ecs_add_id(world, EcsFlag, EcsTag);
|
||||
ecs_add_id(world, EcsWith, EcsTag);
|
||||
|
||||
/* Exclusive properties */
|
||||
ecs_add_id(world, EcsChildOf, EcsExclusive);
|
||||
ecs_add_id(world, EcsOnDelete, EcsExclusive);
|
||||
ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive);
|
||||
ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive);
|
||||
|
||||
/* Sync properties of ChildOf and Identifier with bootstrapped flags */
|
||||
ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete);
|
||||
ecs_add_id(world, EcsChildOf, EcsAcyclic);
|
||||
ecs_add_id(world, EcsChildOf, EcsTraversable);
|
||||
ecs_add_id(world, EcsChildOf, EcsDontInherit);
|
||||
ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit);
|
||||
|
||||
/* Create triggers in internals scope */
|
||||
ecs_set_scope(world, EcsFlecsInternals);
|
||||
|
||||
/* Term used to also match prefabs */
|
||||
ecs_term_t match_prefab = {
|
||||
.id = EcsPrefab,
|
||||
.oper = EcsOptional,
|
||||
.src.flags = EcsSelf
|
||||
};
|
||||
|
||||
/* Register observers for components/relationship properties. Most observers
|
||||
* set flags on an id record when a property is added to a component, which
|
||||
* allows for quick property testing in various operations. */
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab },
|
||||
.events = {EcsOnAdd},
|
||||
.callback = flecs_register_final
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {
|
||||
{ .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf },
|
||||
match_prefab
|
||||
},
|
||||
.events = {EcsOnAdd, EcsOnRemove},
|
||||
.callback = flecs_register_on_delete
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {
|
||||
{ .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf },
|
||||
match_prefab
|
||||
},
|
||||
.events = {EcsOnAdd, EcsOnRemove},
|
||||
.callback = flecs_register_on_delete_object
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {
|
||||
{ .id = EcsTraversable, .src.flags = EcsSelf },
|
||||
match_prefab
|
||||
},
|
||||
.events = {EcsOnAdd, EcsOnRemove},
|
||||
.callback = flecs_register_traversable
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab },
|
||||
.events = {EcsOnAdd, EcsOnRemove},
|
||||
.callback = flecs_register_exclusive
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab },
|
||||
.events = {EcsOnAdd},
|
||||
.callback = flecs_register_symmetric
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab },
|
||||
.events = {EcsOnAdd},
|
||||
.callback = flecs_register_dont_inherit
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {{ .id = EcsAlwaysOverride, .src.flags = EcsSelf } },
|
||||
.events = {EcsOnAdd},
|
||||
.callback = flecs_register_always_override
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {
|
||||
{ .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf },
|
||||
match_prefab
|
||||
},
|
||||
.events = {EcsOnAdd},
|
||||
.callback = flecs_register_with
|
||||
});
|
||||
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab },
|
||||
.events = {EcsOnAdd},
|
||||
.callback = flecs_register_union
|
||||
});
|
||||
|
||||
/* Entities used as slot are marked as exclusive to ensure a slot can always
|
||||
* only point to a single entity. */
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {
|
||||
{ .id = ecs_pair(EcsSlotOf, EcsWildcard), .src.flags = EcsSelf },
|
||||
match_prefab
|
||||
},
|
||||
.events = {EcsOnAdd},
|
||||
.callback = flecs_register_slot_of
|
||||
});
|
||||
|
||||
/* Define observer to make sure that adding a module to a child entity also
|
||||
* adds it to the parent. */
|
||||
ecs_observer(world, {
|
||||
.filter.terms = {{ .id = EcsModule, .src.flags = EcsSelf }, match_prefab},
|
||||
.events = {EcsOnAdd},
|
||||
.callback = flecs_ensure_module_tag
|
||||
});
|
||||
|
||||
/* Set scope back to flecs core */
|
||||
ecs_set_scope(world, EcsFlecsCore);
|
||||
|
||||
/* Traversable relationships are always acyclic */
|
||||
ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic);
|
||||
|
||||
/* Transitive relationships are always Traversable */
|
||||
ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable);
|
||||
|
||||
/* DontInherit components */
|
||||
ecs_add_id(world, EcsPrefab, EcsDontInherit);
|
||||
|
||||
/* Acyclic/Traversable components */
|
||||
ecs_add_id(world, EcsIsA, EcsTraversable);
|
||||
ecs_add_id(world, EcsDependsOn, EcsTraversable);
|
||||
ecs_add_id(world, EcsWith, EcsAcyclic);
|
||||
|
||||
/* Transitive relationships */
|
||||
ecs_add_id(world, EcsIsA, EcsTransitive);
|
||||
ecs_add_id(world, EcsIsA, EcsReflexive);
|
||||
|
||||
/* Exclusive properties */
|
||||
ecs_add_id(world, EcsSlotOf, EcsExclusive);
|
||||
ecs_add_id(world, EcsOneOf, EcsExclusive);
|
||||
ecs_add_id(world, EcsFlatten, EcsExclusive);
|
||||
|
||||
/* Private properties */
|
||||
ecs_add_id(world, ecs_id(EcsPoly), EcsPrivate);
|
||||
ecs_add_id(world, ecs_id(EcsIdentifier), EcsPrivate);
|
||||
ecs_add_id(world, EcsChildOf, EcsPrivate);
|
||||
ecs_add_id(world, EcsIsA, EcsPrivate);
|
||||
|
||||
/* Run bootstrap functions for other parts of the code */
|
||||
flecs_bootstrap_hierarchy(world);
|
||||
|
||||
ecs_set_scope(world, 0);
|
||||
|
||||
ecs_set_name_prefix(world, NULL);
|
||||
|
||||
ecs_log_pop();
|
||||
}
|
||||
103
engine/libs/flecs/src/datastructures/allocator.c
Normal file
103
engine/libs/flecs/src/datastructures/allocator.c
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @file datastructures/allocator.c
|
||||
* @brief Allocator for any size.
|
||||
*
|
||||
* Allocators create a block allocator for each requested size.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
static
|
||||
ecs_size_t flecs_allocator_size(
|
||||
ecs_size_t size)
|
||||
{
|
||||
return ECS_ALIGN(size, 16);
|
||||
}
|
||||
|
||||
static
|
||||
ecs_size_t flecs_allocator_size_hash(
|
||||
ecs_size_t size)
|
||||
{
|
||||
return size >> 4;
|
||||
}
|
||||
|
||||
void flecs_allocator_init(
|
||||
ecs_allocator_t *a)
|
||||
{
|
||||
flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t,
|
||||
FLECS_SPARSE_PAGE_SIZE);
|
||||
flecs_sparse_init_t(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t);
|
||||
}
|
||||
|
||||
void flecs_allocator_fini(
|
||||
ecs_allocator_t *a)
|
||||
{
|
||||
int32_t i = 0, count = flecs_sparse_count(&a->sizes);
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_block_allocator_t *ba = flecs_sparse_get_dense_t(
|
||||
&a->sizes, ecs_block_allocator_t, i);
|
||||
flecs_ballocator_fini(ba);
|
||||
}
|
||||
flecs_sparse_fini(&a->sizes);
|
||||
flecs_ballocator_fini(&a->chunks);
|
||||
}
|
||||
|
||||
ecs_block_allocator_t* flecs_allocator_get(
|
||||
ecs_allocator_t *a,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
if (!size) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(size <= flecs_allocator_size(size), ECS_INTERNAL_ERROR, NULL);
|
||||
size = flecs_allocator_size(size);
|
||||
ecs_size_t hash = flecs_allocator_size_hash(size);
|
||||
ecs_block_allocator_t *result = flecs_sparse_get_any_t(&a->sizes,
|
||||
ecs_block_allocator_t, (uint32_t)hash);
|
||||
|
||||
if (!result) {
|
||||
result = flecs_sparse_ensure_fast_t(&a->sizes,
|
||||
ecs_block_allocator_t, (uint32_t)hash);
|
||||
flecs_ballocator_init(result, size);
|
||||
}
|
||||
|
||||
ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char* flecs_strdup(
|
||||
ecs_allocator_t *a,
|
||||
const char* str)
|
||||
{
|
||||
ecs_size_t len = ecs_os_strlen(str);
|
||||
char *result = flecs_alloc_n(a, char, len + 1);
|
||||
ecs_os_memcpy(result, str, len + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
void flecs_strfree(
|
||||
ecs_allocator_t *a,
|
||||
char* str)
|
||||
{
|
||||
ecs_size_t len = ecs_os_strlen(str);
|
||||
flecs_free_n(a, char, len + 1, str);
|
||||
}
|
||||
|
||||
void* flecs_dup(
|
||||
ecs_allocator_t *a,
|
||||
ecs_size_t size,
|
||||
const void *src)
|
||||
{
|
||||
ecs_block_allocator_t *ba = flecs_allocator_get(a, size);
|
||||
if (ba) {
|
||||
void *dst = flecs_balloc(ba);
|
||||
ecs_os_memcpy(dst, src, size);
|
||||
return dst;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
121
engine/libs/flecs/src/datastructures/bitset.c
Normal file
121
engine/libs/flecs/src/datastructures/bitset.c
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @file datastructures/bitset.c
|
||||
* @brief Bitset data structure.
|
||||
*
|
||||
* Simple bitset implementation. The bitset allows for storage of arbitrary
|
||||
* numbers of bits.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
static
|
||||
void ensure(
|
||||
ecs_bitset_t *bs,
|
||||
ecs_size_t size)
|
||||
{
|
||||
if (!bs->size) {
|
||||
int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t);
|
||||
bs->size = ((size - 1) / 64 + 1) * 64;
|
||||
bs->data = ecs_os_calloc(new_size);
|
||||
} else if (size > bs->size) {
|
||||
int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t);
|
||||
bs->size = ((size - 1) / 64 + 1) * 64;
|
||||
int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t);
|
||||
bs->data = ecs_os_realloc(bs->data, new_size);
|
||||
ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size);
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_bitset_init(
|
||||
ecs_bitset_t* bs)
|
||||
{
|
||||
bs->size = 0;
|
||||
bs->count = 0;
|
||||
bs->data = NULL;
|
||||
}
|
||||
|
||||
void flecs_bitset_ensure(
|
||||
ecs_bitset_t *bs,
|
||||
int32_t count)
|
||||
{
|
||||
if (count > bs->count) {
|
||||
bs->count = count;
|
||||
ensure(bs, count);
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_bitset_fini(
|
||||
ecs_bitset_t *bs)
|
||||
{
|
||||
ecs_os_free(bs->data);
|
||||
bs->data = NULL;
|
||||
bs->count = 0;
|
||||
}
|
||||
|
||||
void flecs_bitset_addn(
|
||||
ecs_bitset_t *bs,
|
||||
int32_t count)
|
||||
{
|
||||
int32_t elem = bs->count += count;
|
||||
ensure(bs, elem);
|
||||
}
|
||||
|
||||
void flecs_bitset_set(
|
||||
ecs_bitset_t *bs,
|
||||
int32_t elem,
|
||||
bool value)
|
||||
{
|
||||
ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL);
|
||||
uint32_t hi = ((uint32_t)elem) >> 6;
|
||||
uint32_t lo = ((uint32_t)elem) & 0x3F;
|
||||
uint64_t v = bs->data[hi];
|
||||
bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo);
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
bool flecs_bitset_get(
|
||||
const ecs_bitset_t *bs,
|
||||
int32_t elem)
|
||||
{
|
||||
ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL);
|
||||
return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F)));
|
||||
error:
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t flecs_bitset_count(
|
||||
const ecs_bitset_t *bs)
|
||||
{
|
||||
return bs->count;
|
||||
}
|
||||
|
||||
void flecs_bitset_remove(
|
||||
ecs_bitset_t *bs,
|
||||
int32_t elem)
|
||||
{
|
||||
ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL);
|
||||
int32_t last = bs->count - 1;
|
||||
bool last_value = flecs_bitset_get(bs, last);
|
||||
flecs_bitset_set(bs, elem, last_value);
|
||||
flecs_bitset_set(bs, last, 0);
|
||||
bs->count --;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void flecs_bitset_swap(
|
||||
ecs_bitset_t *bs,
|
||||
int32_t elem_a,
|
||||
int32_t elem_b)
|
||||
{
|
||||
ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
bool a = flecs_bitset_get(bs, elem_a);
|
||||
bool b = flecs_bitset_get(bs, elem_b);
|
||||
flecs_bitset_set(bs, elem_a, b);
|
||||
flecs_bitset_set(bs, elem_b, a);
|
||||
error:
|
||||
return;
|
||||
}
|
||||
242
engine/libs/flecs/src/datastructures/block_allocator.c
Normal file
242
engine/libs/flecs/src/datastructures/block_allocator.c
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* @file datastructures/block_allocator.c
|
||||
* @brief Block allocator.
|
||||
*
|
||||
* A block allocator is an allocator for a fixed size that allocates blocks of
|
||||
* memory with N elements of the requested size.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
// #ifdef FLECS_SANITIZE
|
||||
// #define FLECS_MEMSET_UNINITIALIZED
|
||||
// #endif
|
||||
|
||||
int64_t ecs_block_allocator_alloc_count = 0;
|
||||
int64_t ecs_block_allocator_free_count = 0;
|
||||
|
||||
static
|
||||
ecs_block_allocator_chunk_header_t* flecs_balloc_block(
|
||||
ecs_block_allocator_t *allocator)
|
||||
{
|
||||
if (!allocator->chunk_size) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_block_allocator_block_t *block =
|
||||
ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) +
|
||||
allocator->block_size);
|
||||
ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block,
|
||||
ECS_SIZEOF(ecs_block_allocator_block_t));
|
||||
|
||||
block->memory = first_chunk;
|
||||
if (!allocator->block_tail) {
|
||||
ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0);
|
||||
block->next = NULL;
|
||||
allocator->block_head = block;
|
||||
allocator->block_tail = block;
|
||||
} else {
|
||||
block->next = NULL;
|
||||
allocator->block_tail->next = block;
|
||||
allocator->block_tail = block;
|
||||
}
|
||||
|
||||
ecs_block_allocator_chunk_header_t *chunk = first_chunk;
|
||||
int32_t i, end;
|
||||
for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) {
|
||||
chunk->next = ECS_OFFSET(chunk, allocator->chunk_size);
|
||||
chunk = chunk->next;
|
||||
}
|
||||
|
||||
ecs_os_linc(&ecs_block_allocator_alloc_count);
|
||||
|
||||
chunk->next = NULL;
|
||||
return first_chunk;
|
||||
}
|
||||
|
||||
void flecs_ballocator_init(
|
||||
ecs_block_allocator_t *ba,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ba->data_size = size;
|
||||
#ifdef FLECS_SANITIZE
|
||||
size += ECS_SIZEOF(int64_t);
|
||||
#endif
|
||||
ba->chunk_size = ECS_ALIGN(size, 16);
|
||||
ba->chunks_per_block = ECS_MAX(4096 / ba->chunk_size, 1);
|
||||
ba->block_size = ba->chunks_per_block * ba->chunk_size;
|
||||
ba->head = NULL;
|
||||
ba->block_head = NULL;
|
||||
ba->block_tail = NULL;
|
||||
}
|
||||
|
||||
ecs_block_allocator_t* flecs_ballocator_new(
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t);
|
||||
flecs_ballocator_init(result, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
void flecs_ballocator_fini(
|
||||
ecs_block_allocator_t *ba)
|
||||
{
|
||||
ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
#ifdef FLECS_SANITIZE
|
||||
ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED,
|
||||
"(size = %u)", (uint32_t)ba->data_size);
|
||||
#endif
|
||||
|
||||
ecs_block_allocator_block_t *block;
|
||||
for (block = ba->block_head; block;) {
|
||||
ecs_block_allocator_block_t *next = block->next;
|
||||
ecs_os_free(block);
|
||||
ecs_os_linc(&ecs_block_allocator_free_count);
|
||||
block = next;
|
||||
}
|
||||
ba->block_head = NULL;
|
||||
}
|
||||
|
||||
void flecs_ballocator_free(
|
||||
ecs_block_allocator_t *ba)
|
||||
{
|
||||
flecs_ballocator_fini(ba);
|
||||
ecs_os_free(ba);
|
||||
}
|
||||
|
||||
void* flecs_balloc(
|
||||
ecs_block_allocator_t *ba)
|
||||
{
|
||||
void *result;
|
||||
#ifdef FLECS_USE_OS_ALLOC
|
||||
result = ecs_os_malloc(ba->data_size);
|
||||
#else
|
||||
|
||||
if (!ba) return NULL;
|
||||
|
||||
if (!ba->head) {
|
||||
ba->head = flecs_balloc_block(ba);
|
||||
}
|
||||
|
||||
result = ba->head;
|
||||
ba->head = ba->head->next;
|
||||
|
||||
#ifdef FLECS_SANITIZE
|
||||
ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator");
|
||||
ba->alloc_count ++;
|
||||
*(int64_t*)result = ba->chunk_size;
|
||||
result = ECS_OFFSET(result, ECS_SIZEOF(int64_t));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef FLECS_MEMSET_UNINITIALIZED
|
||||
ecs_os_memset(result, 0xAA, ba->data_size);
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void* flecs_bcalloc(
|
||||
ecs_block_allocator_t *ba)
|
||||
{
|
||||
#ifdef FLECS_USE_OS_ALLOC
|
||||
return ecs_os_calloc(ba->data_size);
|
||||
#endif
|
||||
|
||||
if (!ba) return NULL;
|
||||
void *result = flecs_balloc(ba);
|
||||
ecs_os_memset(result, 0, ba->data_size);
|
||||
return result;
|
||||
}
|
||||
|
||||
void flecs_bfree(
|
||||
ecs_block_allocator_t *ba,
|
||||
void *memory)
|
||||
{
|
||||
#ifdef FLECS_USE_OS_ALLOC
|
||||
ecs_os_free(memory);
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (!ba) {
|
||||
ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return;
|
||||
}
|
||||
if (memory == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef FLECS_SANITIZE
|
||||
memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t));
|
||||
if (*(int64_t*)memory != ba->chunk_size) {
|
||||
ecs_err("chunk %p returned to wrong allocator "
|
||||
"(chunk = %ub, allocator = %ub)",
|
||||
memory, *(int64_t*)memory, ba->chunk_size);
|
||||
ecs_abort(ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
ba->alloc_count --;
|
||||
#endif
|
||||
|
||||
ecs_block_allocator_chunk_header_t *chunk = memory;
|
||||
chunk->next = ba->head;
|
||||
ba->head = chunk;
|
||||
ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator");
|
||||
}
|
||||
|
||||
void* flecs_brealloc(
|
||||
ecs_block_allocator_t *dst,
|
||||
ecs_block_allocator_t *src,
|
||||
void *memory)
|
||||
{
|
||||
void *result;
|
||||
#ifdef FLECS_USE_OS_ALLOC
|
||||
result = ecs_os_realloc(memory, dst->data_size);
|
||||
#else
|
||||
if (dst == src) {
|
||||
return memory;
|
||||
}
|
||||
|
||||
result = flecs_balloc(dst);
|
||||
if (result && src) {
|
||||
ecs_size_t size = src->data_size;
|
||||
if (dst->data_size < size) {
|
||||
size = dst->data_size;
|
||||
}
|
||||
ecs_os_memcpy(result, memory, size);
|
||||
}
|
||||
flecs_bfree(src, memory);
|
||||
#endif
|
||||
#ifdef FLECS_MEMSET_UNINITIALIZED
|
||||
if (dst && src && (dst->data_size > src->data_size)) {
|
||||
ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA,
|
||||
dst->data_size - src->data_size);
|
||||
} else if (dst && !src) {
|
||||
ecs_os_memset(result, 0xAA, dst->data_size);
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void* flecs_bdup(
|
||||
ecs_block_allocator_t *ba,
|
||||
void *memory)
|
||||
{
|
||||
#ifdef FLECS_USE_OS_ALLOC
|
||||
if (memory && ba->chunk_size) {
|
||||
return ecs_os_memdup(memory, ba->data_size);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
void *result = flecs_balloc(ba);
|
||||
if (result) {
|
||||
ecs_os_memcpy(result, memory, ba->data_size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
154
engine/libs/flecs/src/datastructures/hash.c
Normal file
154
engine/libs/flecs/src/datastructures/hash.c
Normal file
@@ -0,0 +1,154 @@
|
||||
// This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/)
|
||||
// main repo: https://github.com/wangyi-fudan/wyhash
|
||||
// author: 王一 Wang Yi
|
||||
// contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger,
|
||||
// Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev,
|
||||
// Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms,
|
||||
// hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric
|
||||
|
||||
/* quick example:
|
||||
string s="fjsakfdsjkf";
|
||||
uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_);
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifndef WYHASH_CONDOM
|
||||
//protections that produce different results:
|
||||
//1: normal valid behavior
|
||||
//2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication"
|
||||
#define WYHASH_CONDOM 1
|
||||
#endif
|
||||
|
||||
#ifndef WYHASH_32BIT_MUM
|
||||
//0: normal version, slow on 32 bit systems
|
||||
//1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function
|
||||
#define WYHASH_32BIT_MUM 0
|
||||
#endif
|
||||
|
||||
//includes
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#if defined(_MSC_VER) && defined(_M_X64)
|
||||
#include <intrin.h>
|
||||
#pragma intrinsic(_umul128)
|
||||
#endif
|
||||
|
||||
//likely and unlikely macros
|
||||
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
|
||||
#define likely_(x) __builtin_expect(x,1)
|
||||
#define unlikely_(x) __builtin_expect(x,0)
|
||||
#else
|
||||
#define likely_(x) (x)
|
||||
#define unlikely_(x) (x)
|
||||
#endif
|
||||
|
||||
//128bit multiply function
|
||||
static inline void wymum_(uint64_t *A, uint64_t *B){
|
||||
#if(WYHASH_32BIT_MUM)
|
||||
uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B;
|
||||
#if(WYHASH_CONDOM>1)
|
||||
*A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll;
|
||||
#else
|
||||
*A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll;
|
||||
#endif
|
||||
#elif defined(__SIZEOF_INT128__)
|
||||
__uint128_t r=*A; r*=*B;
|
||||
#if(WYHASH_CONDOM>1)
|
||||
*A^=(uint64_t)r; *B^=(uint64_t)(r>>64);
|
||||
#else
|
||||
*A=(uint64_t)r; *B=(uint64_t)(r>>64);
|
||||
#endif
|
||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
||||
#if(WYHASH_CONDOM>1)
|
||||
uint64_t a, b;
|
||||
a=_umul128(*A,*B,&b);
|
||||
*A^=a; *B^=b;
|
||||
#else
|
||||
*A=_umul128(*A,*B,B);
|
||||
#endif
|
||||
#else
|
||||
uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo;
|
||||
uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t<rl;
|
||||
lo=t+(rm1<<32); c+=lo<t; hi=rh+(rm0>>32)+(rm1>>32)+c;
|
||||
#if(WYHASH_CONDOM>1)
|
||||
*A^=lo; *B^=hi;
|
||||
#else
|
||||
*A=lo; *B=hi;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
//multiply and xor mix function, aka MUM
|
||||
static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; }
|
||||
|
||||
//endian macros
|
||||
#ifndef WYHASH_LITTLE_ENDIAN
|
||||
#if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
#define WYHASH_LITTLE_ENDIAN 1
|
||||
#elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||
#define WYHASH_LITTLE_ENDIAN 0
|
||||
#else
|
||||
#warning could not determine endianness! Falling back to little endian.
|
||||
#define WYHASH_LITTLE_ENDIAN 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//read functions
|
||||
#if (WYHASH_LITTLE_ENDIAN)
|
||||
static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;}
|
||||
static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;}
|
||||
#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
|
||||
static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);}
|
||||
static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);}
|
||||
#elif defined(_MSC_VER)
|
||||
static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);}
|
||||
static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);}
|
||||
#else
|
||||
static inline uint64_t wyr8_(const uint8_t *p) {
|
||||
uint64_t v; memcpy(&v, p, 8);
|
||||
return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000));
|
||||
}
|
||||
static inline uint64_t wyr4_(const uint8_t *p) {
|
||||
uint32_t v; memcpy(&v, p, 4);
|
||||
return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000));
|
||||
}
|
||||
#endif
|
||||
static inline uint64_t wyr3_(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];}
|
||||
|
||||
//wyhash main function
|
||||
static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){
|
||||
const uint8_t *p=(const uint8_t *)key; seed^=wymix_(seed^secret[0],secret[1]); uint64_t a, b;
|
||||
if(likely_(len<=16)){
|
||||
if(likely_(len>=4)){ a=(wyr4_(p)<<32)|wyr4_(p+((len>>3)<<2)); b=(wyr4_(p+len-4)<<32)|wyr4_(p+len-4-((len>>3)<<2)); }
|
||||
else if(likely_(len>0)){ a=wyr3_(p,len); b=0;}
|
||||
else a=b=0;
|
||||
}
|
||||
else{
|
||||
size_t i=len;
|
||||
if(unlikely_(i>48)){
|
||||
uint64_t see1=seed, see2=seed;
|
||||
do{
|
||||
seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed);
|
||||
see1=wymix_(wyr8_(p+16)^secret[2],wyr8_(p+24)^see1);
|
||||
see2=wymix_(wyr8_(p+32)^secret[3],wyr8_(p+40)^see2);
|
||||
p+=48; i-=48;
|
||||
}while(likely_(i>48));
|
||||
seed^=see1^see2;
|
||||
}
|
||||
while(unlikely_(i>16)){ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); i-=16; p+=16; }
|
||||
a=wyr8_(p+i-16); b=wyr8_(p+i-8);
|
||||
}
|
||||
a^=secret[1]; b^=seed; wymum_(&a,&b);
|
||||
return wymix_(a^secret[0]^len,b^secret[1]);
|
||||
}
|
||||
|
||||
//the default secret parameters
|
||||
static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull};
|
||||
|
||||
uint64_t flecs_hash(
|
||||
const void *data,
|
||||
ecs_size_t length)
|
||||
{
|
||||
return wyhash(data, flecs_ito(size_t, length), 0, wyp_);
|
||||
}
|
||||
263
engine/libs/flecs/src/datastructures/hashmap.c
Normal file
263
engine/libs/flecs/src/datastructures/hashmap.c
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* @file datastructures/hashmap.c
|
||||
* @brief Hashmap data structure.
|
||||
*
|
||||
* The hashmap data structure is built on top of the map data structure. Where
|
||||
* the map data structure can only work with 64bit key values, the hashmap can
|
||||
* hash keys of any size, and handles collisions between hashes.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
static
|
||||
int32_t flecs_hashmap_find_key(
|
||||
const ecs_hashmap_t *map,
|
||||
ecs_vec_t *keys,
|
||||
ecs_size_t key_size,
|
||||
const void *key)
|
||||
{
|
||||
int32_t i, count = ecs_vec_count(keys);
|
||||
void *key_array = ecs_vec_first(keys);
|
||||
for (i = 0; i < count; i ++) {
|
||||
void *key_ptr = ECS_OFFSET(key_array, key_size * i);
|
||||
if (map->compare(key_ptr, key) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void flecs_hashmap_init_(
|
||||
ecs_hashmap_t *map,
|
||||
ecs_size_t key_size,
|
||||
ecs_size_t value_size,
|
||||
ecs_hash_value_action_t hash,
|
||||
ecs_compare_action_t compare,
|
||||
ecs_allocator_t *allocator)
|
||||
{
|
||||
map->key_size = key_size;
|
||||
map->value_size = value_size;
|
||||
map->hash = hash;
|
||||
map->compare = compare;
|
||||
flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t);
|
||||
ecs_map_init(&map->impl, allocator);
|
||||
}
|
||||
|
||||
void flecs_hashmap_fini(
|
||||
ecs_hashmap_t *map)
|
||||
{
|
||||
ecs_allocator_t *a = map->impl.allocator;
|
||||
ecs_map_iter_t it = ecs_map_iter(&map->impl);
|
||||
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_hm_bucket_t *bucket = ecs_map_ptr(&it);
|
||||
ecs_vec_fini(a, &bucket->keys, map->key_size);
|
||||
ecs_vec_fini(a, &bucket->values, map->value_size);
|
||||
#ifdef FLECS_SANITIZE
|
||||
flecs_bfree(&map->bucket_allocator, bucket);
|
||||
#endif
|
||||
}
|
||||
|
||||
flecs_ballocator_fini(&map->bucket_allocator);
|
||||
ecs_map_fini(&map->impl);
|
||||
}
|
||||
|
||||
void flecs_hashmap_copy(
|
||||
ecs_hashmap_t *dst,
|
||||
const ecs_hashmap_t *src)
|
||||
{
|
||||
ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash,
|
||||
src->compare, src->impl.allocator);
|
||||
ecs_map_copy(&dst->impl, &src->impl);
|
||||
|
||||
ecs_allocator_t *a = dst->impl.allocator;
|
||||
ecs_map_iter_t it = ecs_map_iter(&dst->impl);
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t);
|
||||
ecs_hm_bucket_t *src_bucket = bucket_ptr[0];
|
||||
ecs_hm_bucket_t *dst_bucket = flecs_balloc(&dst->bucket_allocator);
|
||||
bucket_ptr[0] = dst_bucket;
|
||||
dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size);
|
||||
dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size);
|
||||
}
|
||||
}
|
||||
|
||||
void* flecs_hashmap_get_(
|
||||
const ecs_hashmap_t *map,
|
||||
ecs_size_t key_size,
|
||||
const void *key,
|
||||
ecs_size_t value_size)
|
||||
{
|
||||
ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
uint64_t hash = map->hash(key);
|
||||
ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl,
|
||||
ecs_hm_bucket_t, hash);
|
||||
if (!bucket) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key);
|
||||
if (index == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ecs_vec_get(&bucket->values, value_size, index);
|
||||
}
|
||||
|
||||
flecs_hashmap_result_t flecs_hashmap_ensure_(
|
||||
ecs_hashmap_t *map,
|
||||
ecs_size_t key_size,
|
||||
const void *key,
|
||||
ecs_size_t value_size)
|
||||
{
|
||||
ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
uint64_t hash = map->hash(key);
|
||||
ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash);
|
||||
ecs_hm_bucket_t *bucket = r[0];
|
||||
if (!bucket) {
|
||||
bucket = r[0] = flecs_bcalloc(&map->bucket_allocator);
|
||||
}
|
||||
|
||||
ecs_allocator_t *a = map->impl.allocator;
|
||||
void *value_ptr, *key_ptr;
|
||||
ecs_vec_t *keys = &bucket->keys;
|
||||
ecs_vec_t *values = &bucket->values;
|
||||
if (!keys->array) {
|
||||
keys = ecs_vec_init(a, &bucket->keys, key_size, 1);
|
||||
values = ecs_vec_init(a, &bucket->values, value_size, 1);
|
||||
key_ptr = ecs_vec_append(a, keys, key_size);
|
||||
value_ptr = ecs_vec_append(a, values, value_size);
|
||||
ecs_os_memcpy(key_ptr, key, key_size);
|
||||
ecs_os_memset(value_ptr, 0, value_size);
|
||||
} else {
|
||||
int32_t index = flecs_hashmap_find_key(map, keys, key_size, key);
|
||||
if (index == -1) {
|
||||
key_ptr = ecs_vec_append(a, keys, key_size);
|
||||
value_ptr = ecs_vec_append(a, values, value_size);
|
||||
ecs_os_memcpy(key_ptr, key, key_size);
|
||||
ecs_os_memset(value_ptr, 0, value_size);
|
||||
} else {
|
||||
key_ptr = ecs_vec_get(keys, key_size, index);
|
||||
value_ptr = ecs_vec_get(values, value_size, index);
|
||||
}
|
||||
}
|
||||
|
||||
return (flecs_hashmap_result_t){
|
||||
.key = key_ptr, .value = value_ptr, .hash = hash
|
||||
};
|
||||
}
|
||||
|
||||
void flecs_hashmap_set_(
|
||||
ecs_hashmap_t *map,
|
||||
ecs_size_t key_size,
|
||||
void *key,
|
||||
ecs_size_t value_size,
|
||||
const void *value)
|
||||
{
|
||||
void *value_ptr = flecs_hashmap_ensure_(map, key_size, key, value_size).value;
|
||||
ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_os_memcpy(value_ptr, value, value_size);
|
||||
}
|
||||
|
||||
ecs_hm_bucket_t* flecs_hashmap_get_bucket(
|
||||
const ecs_hashmap_t *map,
|
||||
uint64_t hash)
|
||||
{
|
||||
ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash);
|
||||
}
|
||||
|
||||
void flecs_hm_bucket_remove(
|
||||
ecs_hashmap_t *map,
|
||||
ecs_hm_bucket_t *bucket,
|
||||
uint64_t hash,
|
||||
int32_t index)
|
||||
{
|
||||
ecs_vec_remove(&bucket->keys, map->key_size, index);
|
||||
ecs_vec_remove(&bucket->values, map->value_size, index);
|
||||
|
||||
if (!ecs_vec_count(&bucket->keys)) {
|
||||
ecs_allocator_t *a = map->impl.allocator;
|
||||
ecs_vec_fini(a, &bucket->keys, map->key_size);
|
||||
ecs_vec_fini(a, &bucket->values, map->value_size);
|
||||
ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash);
|
||||
ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b;
|
||||
flecs_bfree(&map->bucket_allocator, bucket);
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_hashmap_remove_w_hash_(
|
||||
ecs_hashmap_t *map,
|
||||
ecs_size_t key_size,
|
||||
const void *key,
|
||||
ecs_size_t value_size,
|
||||
uint64_t hash)
|
||||
{
|
||||
ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)value_size;
|
||||
|
||||
ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl,
|
||||
ecs_hm_bucket_t, hash);
|
||||
if (!bucket) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
flecs_hm_bucket_remove(map, bucket, hash, index);
|
||||
}
|
||||
|
||||
void flecs_hashmap_remove_(
|
||||
ecs_hashmap_t *map,
|
||||
ecs_size_t key_size,
|
||||
const void *key,
|
||||
ecs_size_t value_size)
|
||||
{
|
||||
ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
uint64_t hash = map->hash(key);
|
||||
flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash);
|
||||
}
|
||||
|
||||
flecs_hashmap_iter_t flecs_hashmap_iter(
|
||||
ecs_hashmap_t *map)
|
||||
{
|
||||
return (flecs_hashmap_iter_t){
|
||||
.it = ecs_map_iter(&map->impl)
|
||||
};
|
||||
}
|
||||
|
||||
void* flecs_hashmap_next_(
|
||||
flecs_hashmap_iter_t *it,
|
||||
ecs_size_t key_size,
|
||||
void *key_out,
|
||||
ecs_size_t value_size)
|
||||
{
|
||||
int32_t index = ++ it->index;
|
||||
ecs_hm_bucket_t *bucket = it->bucket;
|
||||
while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) {
|
||||
ecs_map_next(&it->it);
|
||||
bucket = it->bucket = ecs_map_ptr(&it->it);
|
||||
if (!bucket) {
|
||||
return NULL;
|
||||
}
|
||||
index = it->index = 0;
|
||||
}
|
||||
|
||||
if (key_out) {
|
||||
*(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index);
|
||||
}
|
||||
|
||||
return ecs_vec_get(&bucket->values, value_size, index);
|
||||
}
|
||||
468
engine/libs/flecs/src/datastructures/map.c
Normal file
468
engine/libs/flecs/src/datastructures/map.c
Normal file
@@ -0,0 +1,468 @@
|
||||
/**
|
||||
* @file datastructures/map.c
|
||||
* @brief Map data structure.
|
||||
*
|
||||
* Map data structure for 64bit keys and dynamic payload size.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
/* The ratio used to determine whether the map should flecs_map_rehash. If
|
||||
* (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */
|
||||
#define ECS_LOAD_FACTOR (12)
|
||||
#define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c)
|
||||
|
||||
static
|
||||
uint8_t flecs_log2(uint32_t v) {
|
||||
static const uint8_t log2table[32] =
|
||||
{0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
|
||||
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31};
|
||||
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27];
|
||||
}
|
||||
|
||||
/* Get bucket count for number of elements */
|
||||
static
|
||||
int32_t flecs_map_get_bucket_count(
|
||||
int32_t count)
|
||||
{
|
||||
return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1));
|
||||
}
|
||||
|
||||
/* Get bucket shift amount for a given bucket count */
|
||||
static
|
||||
uint8_t flecs_map_get_bucket_shift (
|
||||
int32_t bucket_count)
|
||||
{
|
||||
return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count));
|
||||
}
|
||||
|
||||
/* Get bucket index for provided map key */
|
||||
static
|
||||
int32_t flecs_map_get_bucket_index(
|
||||
uint16_t bucket_shift,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
return (int32_t)((11400714819323198485ull * key) >> bucket_shift);
|
||||
}
|
||||
|
||||
/* Get bucket for key */
|
||||
static
|
||||
ecs_bucket_t* flecs_map_get_bucket(
|
||||
const ecs_map_t *map,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
int32_t bucket_id = flecs_map_get_bucket_index(map->bucket_shift, key);
|
||||
ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL);
|
||||
return &map->buckets[bucket_id];
|
||||
}
|
||||
|
||||
/* Add element to bucket */
|
||||
static
|
||||
ecs_map_val_t* flecs_map_bucket_add(
|
||||
ecs_block_allocator_t *allocator,
|
||||
ecs_bucket_t *bucket,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_bucket_entry_t *new_entry = flecs_balloc(allocator);
|
||||
new_entry->key = key;
|
||||
new_entry->next = bucket->first;
|
||||
bucket->first = new_entry;
|
||||
return &new_entry->value;
|
||||
}
|
||||
|
||||
/* Remove element from bucket */
|
||||
static
|
||||
ecs_map_val_t flecs_map_bucket_remove(
|
||||
ecs_map_t *map,
|
||||
ecs_bucket_t *bucket,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_bucket_entry_t *entry;
|
||||
for (entry = bucket->first; entry; entry = entry->next) {
|
||||
if (entry->key == key) {
|
||||
ecs_map_val_t value = entry->value;
|
||||
ecs_bucket_entry_t **next_holder = &bucket->first;
|
||||
while(*next_holder != entry) {
|
||||
next_holder = &(*next_holder)->next;
|
||||
}
|
||||
*next_holder = entry->next;
|
||||
flecs_bfree(map->entry_allocator, entry);
|
||||
map->count --;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Free contents of bucket */
|
||||
static
|
||||
void flecs_map_bucket_clear(
|
||||
ecs_block_allocator_t *allocator,
|
||||
ecs_bucket_t *bucket)
|
||||
{
|
||||
ecs_bucket_entry_t *entry = bucket->first;
|
||||
while(entry) {
|
||||
ecs_bucket_entry_t *next = entry->next;
|
||||
flecs_bfree(allocator, entry);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get payload pointer for key from bucket */
|
||||
static
|
||||
ecs_map_val_t* flecs_map_bucket_get(
|
||||
ecs_bucket_t *bucket,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_bucket_entry_t *entry;
|
||||
for (entry = bucket->first; entry; entry = entry->next) {
|
||||
if (entry->key == key) {
|
||||
return &entry->value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Grow number of buckets */
|
||||
static
|
||||
void flecs_map_rehash(
|
||||
ecs_map_t *map,
|
||||
int32_t count)
|
||||
{
|
||||
count = flecs_next_pow_of_2(count);
|
||||
if (count < 2) {
|
||||
count = 2;
|
||||
}
|
||||
ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
int32_t old_count = map->bucket_count;
|
||||
ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count);
|
||||
|
||||
if (map->allocator) {
|
||||
map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, count);
|
||||
} else {
|
||||
map->buckets = ecs_os_calloc_n(ecs_bucket_t, count);
|
||||
}
|
||||
map->bucket_count = count;
|
||||
map->bucket_shift = flecs_map_get_bucket_shift(count);
|
||||
|
||||
/* Remap old bucket entries to new buckets */
|
||||
for (b = buckets; b < end; b++) {
|
||||
ecs_bucket_entry_t* entry;
|
||||
for (entry = b->first; entry;) {
|
||||
ecs_bucket_entry_t* next = entry->next;
|
||||
int32_t bucket_index = flecs_map_get_bucket_index(
|
||||
map->bucket_shift, entry->key);
|
||||
ecs_bucket_t *bucket = &map->buckets[bucket_index];
|
||||
entry->next = bucket->first;
|
||||
bucket->first = entry;
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
if (map->allocator) {
|
||||
flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets);
|
||||
} else {
|
||||
ecs_os_free(buckets);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_map_params_init(
|
||||
ecs_map_params_t *params,
|
||||
ecs_allocator_t *allocator)
|
||||
{
|
||||
params->allocator = allocator;
|
||||
flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t);
|
||||
}
|
||||
|
||||
void ecs_map_params_fini(
|
||||
ecs_map_params_t *params)
|
||||
{
|
||||
flecs_ballocator_fini(¶ms->entry_allocator);
|
||||
}
|
||||
|
||||
void ecs_map_init_w_params(
|
||||
ecs_map_t *result,
|
||||
ecs_map_params_t *params)
|
||||
{
|
||||
ecs_os_zeromem(result);
|
||||
|
||||
result->allocator = params->allocator;
|
||||
|
||||
if (params->entry_allocator.chunk_size) {
|
||||
result->entry_allocator = ¶ms->entry_allocator;
|
||||
result->shared_allocator = true;
|
||||
} else {
|
||||
result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t);
|
||||
}
|
||||
|
||||
flecs_map_rehash(result, 0);
|
||||
}
|
||||
|
||||
void ecs_map_init_w_params_if(
|
||||
ecs_map_t *result,
|
||||
ecs_map_params_t *params)
|
||||
{
|
||||
if (!ecs_map_is_init(result)) {
|
||||
ecs_map_init_w_params(result, params);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_map_init(
|
||||
ecs_map_t *result,
|
||||
ecs_allocator_t *allocator)
|
||||
{
|
||||
ecs_map_init_w_params(result, &(ecs_map_params_t) {
|
||||
.allocator = allocator
|
||||
});
|
||||
}
|
||||
|
||||
void ecs_map_init_if(
|
||||
ecs_map_t *result,
|
||||
ecs_allocator_t *allocator)
|
||||
{
|
||||
if (!ecs_map_is_init(result)) {
|
||||
ecs_map_init(result, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_map_fini(
|
||||
ecs_map_t *map)
|
||||
{
|
||||
if (!ecs_map_is_init(map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool sanitize = false;
|
||||
#ifdef FLECS_SANITIZE
|
||||
sanitize = true;
|
||||
#endif
|
||||
|
||||
/* Free buckets in sanitized mode, so we can replace the allocator with
|
||||
* regular malloc/free and use asan/valgrind to find memory errors. */
|
||||
ecs_allocator_t *a = map->allocator;
|
||||
ecs_block_allocator_t *ea = map->entry_allocator;
|
||||
if (map->shared_allocator || sanitize) {
|
||||
ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count];
|
||||
while (bucket != end) {
|
||||
flecs_map_bucket_clear(ea, bucket);
|
||||
bucket ++;
|
||||
}
|
||||
}
|
||||
|
||||
if (ea && !map->shared_allocator) {
|
||||
flecs_ballocator_free(ea);
|
||||
map->entry_allocator = NULL;
|
||||
}
|
||||
if (a) {
|
||||
flecs_free_n(a, ecs_bucket_t, map->bucket_count, map->buckets);
|
||||
} else {
|
||||
ecs_os_free(map->buckets);
|
||||
}
|
||||
|
||||
map->bucket_shift = 0;
|
||||
}
|
||||
|
||||
ecs_map_val_t* ecs_map_get(
|
||||
const ecs_map_t *map,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key);
|
||||
}
|
||||
|
||||
void* ecs_map_get_deref_(
|
||||
const ecs_map_t *map,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_map_val_t* ptr = flecs_map_bucket_get(
|
||||
flecs_map_get_bucket(map, key), key);
|
||||
if (ptr) {
|
||||
return (void*)(uintptr_t)ptr[0];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ecs_map_insert(
|
||||
ecs_map_t *map,
|
||||
ecs_map_key_t key,
|
||||
ecs_map_val_t value)
|
||||
{
|
||||
ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
int32_t map_count = ++map->count;
|
||||
int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count);
|
||||
int32_t bucket_count = map->bucket_count;
|
||||
if (tgt_bucket_count > bucket_count) {
|
||||
flecs_map_rehash(map, tgt_bucket_count);
|
||||
}
|
||||
|
||||
ecs_bucket_t *bucket = flecs_map_get_bucket(map, key);
|
||||
flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value;
|
||||
}
|
||||
|
||||
void* ecs_map_insert_alloc(
|
||||
ecs_map_t *map,
|
||||
ecs_size_t elem_size,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
void *elem = ecs_os_calloc(elem_size);
|
||||
ecs_map_insert_ptr(map, key, (uintptr_t)elem);
|
||||
return elem;
|
||||
}
|
||||
|
||||
ecs_map_val_t* ecs_map_ensure(
|
||||
ecs_map_t *map,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_bucket_t *bucket = flecs_map_get_bucket(map, key);
|
||||
ecs_map_val_t *result = flecs_map_bucket_get(bucket, key);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t map_count = ++map->count;
|
||||
int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count);
|
||||
int32_t bucket_count = map->bucket_count;
|
||||
if (tgt_bucket_count > bucket_count) {
|
||||
flecs_map_rehash(map, tgt_bucket_count);
|
||||
bucket = flecs_map_get_bucket(map, key);
|
||||
}
|
||||
|
||||
ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key);
|
||||
*v = 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
void* ecs_map_ensure_alloc(
|
||||
ecs_map_t *map,
|
||||
ecs_size_t elem_size,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_map_val_t *val = ecs_map_ensure(map, key);
|
||||
if (!*val) {
|
||||
void *elem = ecs_os_calloc(elem_size);
|
||||
*val = (ecs_map_val_t)(uintptr_t)elem;
|
||||
return elem;
|
||||
} else {
|
||||
return (void*)(uintptr_t)*val;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_map_val_t ecs_map_remove(
|
||||
ecs_map_t *map,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key);
|
||||
}
|
||||
|
||||
void ecs_map_remove_free(
|
||||
ecs_map_t *map,
|
||||
ecs_map_key_t key)
|
||||
{
|
||||
ecs_map_val_t val = ecs_map_remove(map, key);
|
||||
if (val) {
|
||||
ecs_os_free((void*)(uintptr_t)val);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_map_clear(
|
||||
ecs_map_t *map)
|
||||
{
|
||||
ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
int32_t i, count = map->bucket_count;
|
||||
for (i = 0; i < count; i ++) {
|
||||
flecs_map_bucket_clear(map->entry_allocator, &map->buckets[i]);
|
||||
}
|
||||
if (map->allocator) {
|
||||
flecs_free_n(map->allocator, ecs_bucket_t, count, map->buckets);
|
||||
} else {
|
||||
ecs_os_free(map->buckets);
|
||||
}
|
||||
map->buckets = NULL;
|
||||
map->bucket_count = 0;
|
||||
map->count = 0;
|
||||
flecs_map_rehash(map, 2);
|
||||
}
|
||||
|
||||
ecs_map_iter_t ecs_map_iter(
|
||||
const ecs_map_t *map)
|
||||
{
|
||||
if (ecs_map_is_init(map)) {
|
||||
return (ecs_map_iter_t){
|
||||
.map = map,
|
||||
.bucket = NULL,
|
||||
.entry = NULL
|
||||
};
|
||||
} else {
|
||||
return (ecs_map_iter_t){ 0 };
|
||||
}
|
||||
}
|
||||
|
||||
bool ecs_map_next(
|
||||
ecs_map_iter_t *iter)
|
||||
{
|
||||
const ecs_map_t *map = iter->map;
|
||||
ecs_bucket_t *end;
|
||||
if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_bucket_entry_t *entry = NULL;
|
||||
if (!iter->bucket) {
|
||||
for (iter->bucket = map->buckets;
|
||||
iter->bucket != end;
|
||||
++iter->bucket)
|
||||
{
|
||||
if (iter->bucket->first) {
|
||||
entry = iter->bucket->first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iter->bucket == end) {
|
||||
return false;
|
||||
}
|
||||
} else if ((entry = iter->entry) == NULL) {
|
||||
do {
|
||||
++iter->bucket;
|
||||
if (iter->bucket == end) {
|
||||
return false;
|
||||
}
|
||||
} while(!iter->bucket->first);
|
||||
entry = iter->bucket->first;
|
||||
}
|
||||
|
||||
ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
iter->entry = entry->next;
|
||||
iter->res = &entry->key;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ecs_map_copy(
|
||||
ecs_map_t *dst,
|
||||
const ecs_map_t *src)
|
||||
{
|
||||
if (ecs_map_is_init(dst)) {
|
||||
ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_map_fini(dst);
|
||||
}
|
||||
|
||||
if (!ecs_map_is_init(src)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_map_init(dst, src->allocator);
|
||||
|
||||
ecs_map_iter_t it = ecs_map_iter(src);
|
||||
while (ecs_map_next(&it)) {
|
||||
ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it));
|
||||
}
|
||||
}
|
||||
238
engine/libs/flecs/src/datastructures/name_index.c
Normal file
238
engine/libs/flecs/src/datastructures/name_index.c
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* @file datastructures/name_index.c
|
||||
* @brief Data structure for resolving 64bit keys by string (name).
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
static
|
||||
uint64_t flecs_name_index_hash(
|
||||
const void *ptr)
|
||||
{
|
||||
const ecs_hashed_string_t *str = ptr;
|
||||
ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
return str->hash;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_name_index_compare(
|
||||
const void *ptr1,
|
||||
const void *ptr2)
|
||||
{
|
||||
const ecs_hashed_string_t *str1 = ptr1;
|
||||
const ecs_hashed_string_t *str2 = ptr2;
|
||||
ecs_size_t len1 = str1->length;
|
||||
ecs_size_t len2 = str2->length;
|
||||
if (len1 != len2) {
|
||||
return (len1 > len2) - (len1 < len2);
|
||||
}
|
||||
|
||||
return ecs_os_memcmp(str1->value, str2->value, len1);
|
||||
}
|
||||
|
||||
void flecs_name_index_init(
|
||||
ecs_hashmap_t *hm,
|
||||
ecs_allocator_t *allocator)
|
||||
{
|
||||
flecs_hashmap_init_(hm,
|
||||
ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t),
|
||||
flecs_name_index_hash,
|
||||
flecs_name_index_compare,
|
||||
allocator);
|
||||
}
|
||||
|
||||
void flecs_name_index_init_if(
|
||||
ecs_hashmap_t *hm,
|
||||
ecs_allocator_t *allocator)
|
||||
{
|
||||
if (!hm->compare) {
|
||||
flecs_name_index_init(hm, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
bool flecs_name_index_is_init(
|
||||
const ecs_hashmap_t *hm)
|
||||
{
|
||||
return hm->compare != NULL;
|
||||
}
|
||||
|
||||
ecs_hashmap_t* flecs_name_index_new(
|
||||
ecs_world_t *world,
|
||||
ecs_allocator_t *allocator)
|
||||
{
|
||||
ecs_hashmap_t *result = flecs_bcalloc(&world->allocators.hashmap);
|
||||
flecs_name_index_init(result, allocator);
|
||||
result->hashmap_allocator = &world->allocators.hashmap;
|
||||
return result;
|
||||
}
|
||||
|
||||
void flecs_name_index_fini(
|
||||
ecs_hashmap_t *map)
|
||||
{
|
||||
flecs_hashmap_fini(map);
|
||||
}
|
||||
|
||||
void flecs_name_index_free(
|
||||
ecs_hashmap_t *map)
|
||||
{
|
||||
if (map) {
|
||||
flecs_name_index_fini(map);
|
||||
flecs_bfree(map->hashmap_allocator, map);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_hashmap_t* flecs_name_index_copy(
|
||||
ecs_hashmap_t *map)
|
||||
{
|
||||
ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator);
|
||||
result->hashmap_allocator = map->hashmap_allocator;
|
||||
flecs_hashmap_copy(result, map);
|
||||
return result;
|
||||
}
|
||||
|
||||
ecs_hashed_string_t flecs_get_hashed_string(
|
||||
const char *name,
|
||||
ecs_size_t length,
|
||||
uint64_t hash)
|
||||
{
|
||||
if (!length) {
|
||||
length = ecs_os_strlen(name);
|
||||
} else {
|
||||
ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (!hash) {
|
||||
hash = flecs_hash(name, length);
|
||||
} else {
|
||||
ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
return (ecs_hashed_string_t) {
|
||||
.value = ECS_CONST_CAST(char*, name),
|
||||
.length = length,
|
||||
.hash = hash
|
||||
};
|
||||
}
|
||||
|
||||
const uint64_t* flecs_name_index_find_ptr(
|
||||
const ecs_hashmap_t *map,
|
||||
const char *name,
|
||||
ecs_size_t length,
|
||||
uint64_t hash)
|
||||
{
|
||||
ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash);
|
||||
ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash);
|
||||
if (!b) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_hashed_string_t *keys = ecs_vec_first(&b->keys);
|
||||
int32_t i, count = ecs_vec_count(&b->keys);
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_hashed_string_t *key = &keys[i];
|
||||
ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (hs.length != key->length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ecs_os_strcmp(name, key->value)) {
|
||||
uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i);
|
||||
ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t flecs_name_index_find(
|
||||
const ecs_hashmap_t *map,
|
||||
const char *name,
|
||||
ecs_size_t length,
|
||||
uint64_t hash)
|
||||
{
|
||||
const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash);
|
||||
if (id) {
|
||||
return id[0];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void flecs_name_index_remove(
|
||||
ecs_hashmap_t *map,
|
||||
uint64_t e,
|
||||
uint64_t hash)
|
||||
{
|
||||
ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash);
|
||||
if (!b) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t *ids = ecs_vec_first(&b->values);
|
||||
int32_t i, count = ecs_vec_count(&b->values);
|
||||
for (i = 0; i < count; i ++) {
|
||||
if (ids[i] == e) {
|
||||
flecs_hm_bucket_remove(map, b, hash, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_name_index_update_name(
|
||||
ecs_hashmap_t *map,
|
||||
uint64_t e,
|
||||
uint64_t hash,
|
||||
const char *name)
|
||||
{
|
||||
ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash);
|
||||
if (!b) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t *ids = ecs_vec_first(&b->values);
|
||||
int32_t i, count = ecs_vec_count(&b->values);
|
||||
for (i = 0; i < count; i ++) {
|
||||
if (ids[i] == e) {
|
||||
ecs_hashed_string_t *key = ecs_vec_get_t(
|
||||
&b->keys, ecs_hashed_string_t, i);
|
||||
key->value = ECS_CONST_CAST(char*, name);
|
||||
ecs_assert(ecs_os_strlen(name) == key->length,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(flecs_hash(name, key->length) == key->hash,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Record must already have been in the index */
|
||||
ecs_abort(ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
void flecs_name_index_ensure(
|
||||
ecs_hashmap_t *map,
|
||||
uint64_t id,
|
||||
const char *name,
|
||||
ecs_size_t length,
|
||||
uint64_t hash)
|
||||
{
|
||||
ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash);
|
||||
|
||||
uint64_t existing = flecs_name_index_find(
|
||||
map, name, key.length, key.hash);
|
||||
if (existing) {
|
||||
if (existing != id) {
|
||||
ecs_abort(ECS_ALREADY_DEFINED,
|
||||
"conflicting id registered with name '%s'", name);
|
||||
}
|
||||
}
|
||||
|
||||
flecs_hashmap_result_t hmr = flecs_hashmap_ensure(
|
||||
map, &key, uint64_t);
|
||||
*((uint64_t*)hmr.value) = id;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
68
engine/libs/flecs/src/datastructures/name_index.h
Normal file
68
engine/libs/flecs/src/datastructures/name_index.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file datastructures/name_index.h
|
||||
* @brief Data structure for resolving 64bit keys by string (name).
|
||||
*/
|
||||
|
||||
#ifndef FLECS_NAME_INDEX_H
|
||||
#define FLECS_NAME_INDEX_H
|
||||
|
||||
void flecs_name_index_init(
|
||||
ecs_hashmap_t *hm,
|
||||
ecs_allocator_t *allocator);
|
||||
|
||||
void flecs_name_index_init_if(
|
||||
ecs_hashmap_t *hm,
|
||||
ecs_allocator_t *allocator);
|
||||
|
||||
bool flecs_name_index_is_init(
|
||||
const ecs_hashmap_t *hm);
|
||||
|
||||
ecs_hashmap_t* flecs_name_index_new(
|
||||
ecs_world_t *world,
|
||||
ecs_allocator_t *allocator);
|
||||
|
||||
void flecs_name_index_fini(
|
||||
ecs_hashmap_t *map);
|
||||
|
||||
void flecs_name_index_free(
|
||||
ecs_hashmap_t *map);
|
||||
|
||||
ecs_hashmap_t* flecs_name_index_copy(
|
||||
ecs_hashmap_t *dst);
|
||||
|
||||
ecs_hashed_string_t flecs_get_hashed_string(
|
||||
const char *name,
|
||||
ecs_size_t length,
|
||||
uint64_t hash);
|
||||
|
||||
const uint64_t* flecs_name_index_find_ptr(
|
||||
const ecs_hashmap_t *map,
|
||||
const char *name,
|
||||
ecs_size_t length,
|
||||
uint64_t hash);
|
||||
|
||||
uint64_t flecs_name_index_find(
|
||||
const ecs_hashmap_t *map,
|
||||
const char *name,
|
||||
ecs_size_t length,
|
||||
uint64_t hash);
|
||||
|
||||
void flecs_name_index_ensure(
|
||||
ecs_hashmap_t *map,
|
||||
uint64_t id,
|
||||
const char *name,
|
||||
ecs_size_t length,
|
||||
uint64_t hash);
|
||||
|
||||
void flecs_name_index_remove(
|
||||
ecs_hashmap_t *map,
|
||||
uint64_t id,
|
||||
uint64_t hash);
|
||||
|
||||
void flecs_name_index_update_name(
|
||||
ecs_hashmap_t *map,
|
||||
uint64_t e,
|
||||
uint64_t hash,
|
||||
const char *name);
|
||||
|
||||
#endif
|
||||
697
engine/libs/flecs/src/datastructures/sparse.c
Normal file
697
engine/libs/flecs/src/datastructures/sparse.c
Normal file
@@ -0,0 +1,697 @@
|
||||
/**
|
||||
* @file datastructures/sparse.c
|
||||
* @brief Sparse set data structure.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
/** Compute the page index from an id by stripping the first 12 bits */
|
||||
#define PAGE(index) ((int32_t)((uint32_t)index >> FLECS_SPARSE_PAGE_BITS))
|
||||
|
||||
/** This computes the offset of an index inside a page */
|
||||
#define OFFSET(index) ((int32_t)index & (FLECS_SPARSE_PAGE_SIZE - 1))
|
||||
|
||||
/* Utility to get a pointer to the payload */
|
||||
#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset))
|
||||
|
||||
typedef struct ecs_page_t {
|
||||
int32_t *sparse; /* Sparse array with indices to dense array */
|
||||
void *data; /* Store data in sparse array to reduce
|
||||
* indirection and provide stable pointers. */
|
||||
} ecs_page_t;
|
||||
|
||||
static
|
||||
ecs_page_t* flecs_sparse_page_new(
|
||||
ecs_sparse_t *sparse,
|
||||
int32_t page_index)
|
||||
{
|
||||
ecs_allocator_t *a = sparse->allocator;
|
||||
ecs_block_allocator_t *ca = sparse->page_allocator;
|
||||
int32_t count = ecs_vec_count(&sparse->pages);
|
||||
ecs_page_t *pages;
|
||||
|
||||
if (count <= page_index) {
|
||||
ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1);
|
||||
pages = ecs_vec_first_t(&sparse->pages, ecs_page_t);
|
||||
ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count));
|
||||
} else {
|
||||
pages = ecs_vec_first_t(&sparse->pages, ecs_page_t);
|
||||
}
|
||||
|
||||
ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_page_t *result = &pages[page_index];
|
||||
ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* Initialize sparse array with zero's, as zero is used to indicate that the
|
||||
* sparse element has not been paired with a dense element. Use zero
|
||||
* as this means we can take advantage of calloc having a possibly better
|
||||
* performance than malloc + memset. */
|
||||
result->sparse = ca ? flecs_bcalloc(ca)
|
||||
: ecs_os_calloc_n(int32_t, FLECS_SPARSE_PAGE_SIZE);
|
||||
|
||||
/* Initialize the data array with zero's to guarantee that data is
|
||||
* always initialized. When an entry is removed, data is reset back to
|
||||
* zero. Initialize now, as this can take advantage of calloc. */
|
||||
result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE)
|
||||
: ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE);
|
||||
|
||||
ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_sparse_page_free(
|
||||
ecs_sparse_t *sparse,
|
||||
ecs_page_t *page)
|
||||
{
|
||||
ecs_allocator_t *a = sparse->allocator;
|
||||
ecs_block_allocator_t *ca = sparse->page_allocator;
|
||||
|
||||
if (ca) {
|
||||
flecs_bfree(ca, page->sparse);
|
||||
} else {
|
||||
ecs_os_free(page->sparse);
|
||||
}
|
||||
if (a) {
|
||||
flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data);
|
||||
} else {
|
||||
ecs_os_free(page->data);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
ecs_page_t* flecs_sparse_get_page(
|
||||
const ecs_sparse_t *sparse,
|
||||
int32_t page_index)
|
||||
{
|
||||
ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL);
|
||||
if (page_index >= ecs_vec_count(&sparse->pages)) {
|
||||
return NULL;
|
||||
}
|
||||
return ecs_vec_get_t(&sparse->pages, ecs_page_t, page_index);
|
||||
}
|
||||
|
||||
static
|
||||
ecs_page_t* flecs_sparse_get_or_create_page(
|
||||
ecs_sparse_t *sparse,
|
||||
int32_t page_index)
|
||||
{
|
||||
ecs_page_t *page = flecs_sparse_get_page(sparse, page_index);
|
||||
if (page && page->sparse) {
|
||||
return page;
|
||||
}
|
||||
|
||||
return flecs_sparse_page_new(sparse, page_index);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_sparse_grow_dense(
|
||||
ecs_sparse_t *sparse)
|
||||
{
|
||||
ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t);
|
||||
}
|
||||
|
||||
static
|
||||
uint64_t flecs_sparse_strip_generation(
|
||||
uint64_t *index_out)
|
||||
{
|
||||
uint64_t index = *index_out;
|
||||
uint64_t gen = index & ECS_GENERATION_MASK;
|
||||
/* Make sure there's no junk in the id */
|
||||
ecs_assert(gen == (index & (0xFFFFFFFFull << 32)),
|
||||
ECS_INVALID_PARAMETER, NULL);
|
||||
*index_out -= gen;
|
||||
return gen;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_sparse_assign_index(
|
||||
ecs_page_t * page,
|
||||
uint64_t * dense_array,
|
||||
uint64_t index,
|
||||
int32_t dense)
|
||||
{
|
||||
/* Initialize sparse-dense pair. This assigns the dense index to the sparse
|
||||
* array, and the sparse index to the dense array .*/
|
||||
page->sparse[OFFSET(index)] = dense;
|
||||
dense_array[dense] = index;
|
||||
}
|
||||
|
||||
static
|
||||
uint64_t flecs_sparse_inc_gen(
|
||||
uint64_t index)
|
||||
{
|
||||
/* When an index is deleted, its generation is increased so that we can do
|
||||
* liveliness checking while recycling ids */
|
||||
return ECS_GENERATION_INC(index);
|
||||
}
|
||||
|
||||
static
|
||||
uint64_t flecs_sparse_inc_id(
|
||||
ecs_sparse_t *sparse)
|
||||
{
|
||||
/* Generate a new id. The last issued id could be stored in an external
|
||||
* variable, such as is the case with the last issued entity id, which is
|
||||
* stored on the world. */
|
||||
return ++ sparse->max_id;
|
||||
}
|
||||
|
||||
static
|
||||
uint64_t flecs_sparse_get_id(
|
||||
const ecs_sparse_t *sparse)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return sparse->max_id;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_sparse_set_id(
|
||||
ecs_sparse_t *sparse,
|
||||
uint64_t value)
|
||||
{
|
||||
/* Sometimes the max id needs to be assigned directly, which typically
|
||||
* happens when the API calls get_or_create for an id that hasn't been
|
||||
* issued before. */
|
||||
sparse->max_id = value;
|
||||
}
|
||||
|
||||
/* Pair dense id with new sparse id */
|
||||
static
|
||||
uint64_t flecs_sparse_create_id(
|
||||
ecs_sparse_t *sparse,
|
||||
int32_t dense)
|
||||
{
|
||||
uint64_t index = flecs_sparse_inc_id(sparse);
|
||||
flecs_sparse_grow_dense(sparse);
|
||||
|
||||
ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index));
|
||||
ecs_assert(page->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
flecs_sparse_assign_index(page, dense_array, index, dense);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/* Create new id */
|
||||
static
|
||||
uint64_t flecs_sparse_new_index(
|
||||
ecs_sparse_t *sparse)
|
||||
{
|
||||
int32_t dense_count = ecs_vec_count(&sparse->dense);
|
||||
int32_t count = sparse->count ++;
|
||||
|
||||
ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL);
|
||||
if (count < dense_count) {
|
||||
/* If there are unused elements in the dense array, return first */
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
return dense_array[count];
|
||||
} else {
|
||||
return flecs_sparse_create_id(sparse, count);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get value from sparse set when it is guaranteed that the value exists. This
|
||||
* function is used when values are obtained using a dense index */
|
||||
static
|
||||
void* flecs_sparse_get_sparse(
|
||||
const ecs_sparse_t *sparse,
|
||||
int32_t dense,
|
||||
uint64_t index)
|
||||
{
|
||||
flecs_sparse_strip_generation(&index);
|
||||
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index));
|
||||
if (!page || !page->sparse) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t offset = OFFSET(index);
|
||||
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL);
|
||||
(void)dense;
|
||||
|
||||
return DATA(page->data, sparse->size, offset);
|
||||
}
|
||||
|
||||
/* Swap dense elements. A swap occurs when an element is removed, or when a
|
||||
* removed element is recycled. */
|
||||
static
|
||||
void flecs_sparse_swap_dense(
|
||||
ecs_sparse_t * sparse,
|
||||
ecs_page_t * page_a,
|
||||
int32_t a,
|
||||
int32_t b)
|
||||
{
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
uint64_t index_a = dense_array[a];
|
||||
uint64_t index_b = dense_array[b];
|
||||
|
||||
ecs_page_t *page_b = flecs_sparse_get_or_create_page(sparse, PAGE(index_b));
|
||||
flecs_sparse_assign_index(page_a, dense_array, index_a, b);
|
||||
flecs_sparse_assign_index(page_b, dense_array, index_b, a);
|
||||
}
|
||||
|
||||
void flecs_sparse_init(
|
||||
ecs_sparse_t *result,
|
||||
struct ecs_allocator_t *allocator,
|
||||
ecs_block_allocator_t *page_allocator,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
|
||||
result->size = size;
|
||||
result->max_id = UINT64_MAX;
|
||||
result->allocator = allocator;
|
||||
result->page_allocator = page_allocator;
|
||||
|
||||
ecs_vec_init_t(allocator, &result->pages, ecs_page_t, 0);
|
||||
ecs_vec_init_t(allocator, &result->dense, uint64_t, 1);
|
||||
result->dense.count = 1;
|
||||
|
||||
/* Consume first value in dense array as 0 is used in the sparse array to
|
||||
* indicate that a sparse element hasn't been paired yet. */
|
||||
ecs_vec_first_t(&result->dense, uint64_t)[0] = 0;
|
||||
|
||||
result->count = 1;
|
||||
}
|
||||
|
||||
void flecs_sparse_clear(
|
||||
ecs_sparse_t *sparse)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
int32_t i, count = ecs_vec_count(&sparse->pages);
|
||||
ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t);
|
||||
for (i = 0; i < count; i ++) {
|
||||
int32_t *indices = pages[i].sparse;
|
||||
if (indices) {
|
||||
ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1);
|
||||
|
||||
sparse->count = 1;
|
||||
sparse->max_id = 0;
|
||||
}
|
||||
|
||||
void flecs_sparse_fini(
|
||||
ecs_sparse_t *sparse)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
int32_t i, count = ecs_vec_count(&sparse->pages);
|
||||
ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t);
|
||||
for (i = 0; i < count; i ++) {
|
||||
flecs_sparse_page_free(sparse, &pages[i]);
|
||||
}
|
||||
|
||||
ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t);
|
||||
ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t);
|
||||
}
|
||||
|
||||
uint64_t flecs_sparse_new_id(
|
||||
ecs_sparse_t *sparse)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
return flecs_sparse_new_index(sparse);
|
||||
}
|
||||
|
||||
void* flecs_sparse_add(
|
||||
ecs_sparse_t *sparse,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
||||
uint64_t index = flecs_sparse_new_index(sparse);
|
||||
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index));
|
||||
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return DATA(page->data, size, OFFSET(index));
|
||||
}
|
||||
|
||||
uint64_t flecs_sparse_last_id(
|
||||
const ecs_sparse_t *sparse)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
return dense_array[sparse->count - 1];
|
||||
}
|
||||
|
||||
void* flecs_sparse_ensure(
|
||||
ecs_sparse_t *sparse,
|
||||
ecs_size_t size,
|
||||
uint64_t index)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL);
|
||||
(void)size;
|
||||
|
||||
uint64_t gen = flecs_sparse_strip_generation(&index);
|
||||
ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index));
|
||||
int32_t offset = OFFSET(index);
|
||||
int32_t dense = page->sparse[offset];
|
||||
|
||||
if (dense) {
|
||||
/* Check if element is alive. If element is not alive, update indices so
|
||||
* that the first unused dense element points to the sparse element. */
|
||||
int32_t count = sparse->count;
|
||||
if (dense >= count) {
|
||||
/* If dense is not alive, swap it with the first unused element. */
|
||||
flecs_sparse_swap_dense(sparse, page, dense, count);
|
||||
dense = count;
|
||||
|
||||
/* First unused element is now last used element */
|
||||
sparse->count ++;
|
||||
} else {
|
||||
/* Dense is already alive, nothing to be done */
|
||||
}
|
||||
|
||||
/* Ensure provided generation matches current. Only allow mismatching
|
||||
* generations if the provided generation count is 0. This allows for
|
||||
* using the ensure function in combination with ids that have their
|
||||
* generation stripped. */
|
||||
#ifdef FLECS_DEBUG
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL);
|
||||
#endif
|
||||
} else {
|
||||
/* Element is not paired yet. Must add a new element to dense array */
|
||||
flecs_sparse_grow_dense(sparse);
|
||||
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
int32_t dense_count = ecs_vec_count(&sparse->dense) - 1;
|
||||
int32_t count = sparse->count ++;
|
||||
|
||||
/* If index is larger than max id, update max id */
|
||||
if (index >= flecs_sparse_get_id(sparse)) {
|
||||
flecs_sparse_set_id(sparse, index);
|
||||
}
|
||||
|
||||
if (count < dense_count) {
|
||||
/* If there are unused elements in the list, move the first unused
|
||||
* element to the end of the list */
|
||||
uint64_t unused = dense_array[count];
|
||||
ecs_page_t *unused_page = flecs_sparse_get_or_create_page(sparse, PAGE(unused));
|
||||
flecs_sparse_assign_index(unused_page, dense_array, unused, dense_count);
|
||||
}
|
||||
|
||||
flecs_sparse_assign_index(page, dense_array, index, count);
|
||||
dense_array[count] |= gen;
|
||||
}
|
||||
|
||||
return DATA(page->data, sparse->size, offset);
|
||||
}
|
||||
|
||||
void* flecs_sparse_ensure_fast(
|
||||
ecs_sparse_t *sparse,
|
||||
ecs_size_t size,
|
||||
uint64_t index_long)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL);
|
||||
(void)size;
|
||||
|
||||
uint32_t index = (uint32_t)index_long;
|
||||
ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index));
|
||||
int32_t offset = OFFSET(index);
|
||||
int32_t dense = page->sparse[offset];
|
||||
int32_t count = sparse->count;
|
||||
|
||||
if (!dense) {
|
||||
/* Element is not paired yet. Must add a new element to dense array */
|
||||
sparse->count = count + 1;
|
||||
if (count == ecs_vec_count(&sparse->dense)) {
|
||||
flecs_sparse_grow_dense(sparse);
|
||||
}
|
||||
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
flecs_sparse_assign_index(page, dense_array, index, count);
|
||||
}
|
||||
|
||||
return DATA(page->data, sparse->size, offset);
|
||||
}
|
||||
|
||||
void flecs_sparse_remove(
|
||||
ecs_sparse_t *sparse,
|
||||
ecs_size_t size,
|
||||
uint64_t index)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)size;
|
||||
|
||||
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index));
|
||||
if (!page || !page->sparse) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t gen = flecs_sparse_strip_generation(&index);
|
||||
int32_t offset = OFFSET(index);
|
||||
int32_t dense = page->sparse[offset];
|
||||
|
||||
if (dense) {
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK;
|
||||
if (gen != cur_gen) {
|
||||
/* Generation doesn't match which means that the provided entity is
|
||||
* already not alive. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Increase generation */
|
||||
dense_array[dense] = index | flecs_sparse_inc_gen(cur_gen);
|
||||
|
||||
int32_t count = sparse->count;
|
||||
|
||||
if (dense == (count - 1)) {
|
||||
/* If dense is the last used element, simply decrease count */
|
||||
sparse->count --;
|
||||
} else if (dense < count) {
|
||||
/* If element is alive, move it to unused elements */
|
||||
flecs_sparse_swap_dense(sparse, page, dense, count - 1);
|
||||
sparse->count --;
|
||||
} else {
|
||||
/* Element is not alive, nothing to be done */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reset memory to zero on remove */
|
||||
void *ptr = DATA(page->data, sparse->size, offset);
|
||||
ecs_os_memset(ptr, 0, size);
|
||||
} else {
|
||||
/* Element is not paired and thus not alive, nothing to be done */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_sparse_set_generation(
|
||||
ecs_sparse_t *sparse,
|
||||
uint64_t index)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index));
|
||||
|
||||
uint64_t index_w_gen = index;
|
||||
flecs_sparse_strip_generation(&index);
|
||||
int32_t offset = OFFSET(index);
|
||||
int32_t dense = page->sparse[offset];
|
||||
|
||||
if (dense) {
|
||||
/* Increase generation */
|
||||
ecs_vec_get_t(&sparse->dense, uint64_t, dense)[0] = index_w_gen;
|
||||
} else {
|
||||
/* Element is not paired and thus not alive, nothing to be done */
|
||||
}
|
||||
}
|
||||
|
||||
void* flecs_sparse_get_dense(
|
||||
const ecs_sparse_t *sparse,
|
||||
ecs_size_t size,
|
||||
int32_t dense_index)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)size;
|
||||
|
||||
dense_index ++;
|
||||
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
return flecs_sparse_get_sparse(sparse, dense_index, dense_array[dense_index]);
|
||||
}
|
||||
|
||||
bool flecs_sparse_is_alive(
|
||||
const ecs_sparse_t *sparse,
|
||||
uint64_t index)
|
||||
{
|
||||
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index));
|
||||
if (!page || !page->sparse) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t offset = OFFSET(index);
|
||||
int32_t dense = page->sparse[offset];
|
||||
if (!dense || (dense >= sparse->count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t gen = flecs_sparse_strip_generation(&index);
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK;
|
||||
|
||||
if (cur_gen != gen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
void* flecs_sparse_try(
|
||||
const ecs_sparse_t *sparse,
|
||||
ecs_size_t size,
|
||||
uint64_t index)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)size;
|
||||
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index));
|
||||
if (!page || !page->sparse) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t offset = OFFSET(index);
|
||||
int32_t dense = page->sparse[offset];
|
||||
if (!dense || (dense >= sparse->count)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t gen = flecs_sparse_strip_generation(&index);
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK;
|
||||
if (cur_gen != gen) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL);
|
||||
return DATA(page->data, sparse->size, offset);
|
||||
}
|
||||
|
||||
void* flecs_sparse_get(
|
||||
const ecs_sparse_t *sparse,
|
||||
ecs_size_t size,
|
||||
uint64_t index)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)size;
|
||||
|
||||
ecs_page_t *page = ecs_vec_get_t(&sparse->pages, ecs_page_t, PAGE(index));
|
||||
int32_t offset = OFFSET(index);
|
||||
int32_t dense = page->sparse[offset];
|
||||
ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
uint64_t gen = flecs_sparse_strip_generation(&index);
|
||||
uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t);
|
||||
uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK;
|
||||
(void)cur_gen; (void)gen;
|
||||
|
||||
ecs_assert(cur_gen == gen, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(dense < sparse->count, ECS_INTERNAL_ERROR, NULL);
|
||||
return DATA(page->data, sparse->size, offset);
|
||||
}
|
||||
|
||||
void* flecs_sparse_get_any(
|
||||
const ecs_sparse_t *sparse,
|
||||
ecs_size_t size,
|
||||
uint64_t index)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)size;
|
||||
|
||||
flecs_sparse_strip_generation(&index);
|
||||
ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index));
|
||||
if (!page || !page->sparse) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t offset = OFFSET(index);
|
||||
int32_t dense = page->sparse[offset];
|
||||
bool in_use = dense && (dense < sparse->count);
|
||||
if (!in_use) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL);
|
||||
return DATA(page->data, sparse->size, offset);
|
||||
}
|
||||
|
||||
int32_t flecs_sparse_count(
|
||||
const ecs_sparse_t *sparse)
|
||||
{
|
||||
if (!sparse || !sparse->count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sparse->count - 1;
|
||||
}
|
||||
|
||||
const uint64_t* flecs_sparse_ids(
|
||||
const ecs_sparse_t *sparse)
|
||||
{
|
||||
ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
if (sparse->dense.array) {
|
||||
return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_sparse_init(
|
||||
ecs_sparse_t *sparse,
|
||||
ecs_size_t elem_size)
|
||||
{
|
||||
flecs_sparse_init(sparse, NULL, NULL, elem_size);
|
||||
}
|
||||
|
||||
void* ecs_sparse_add(
|
||||
ecs_sparse_t *sparse,
|
||||
ecs_size_t elem_size)
|
||||
{
|
||||
return flecs_sparse_add(sparse, elem_size);
|
||||
}
|
||||
|
||||
uint64_t ecs_sparse_last_id(
|
||||
const ecs_sparse_t *sparse)
|
||||
{
|
||||
return flecs_sparse_last_id(sparse);
|
||||
}
|
||||
|
||||
int32_t ecs_sparse_count(
|
||||
const ecs_sparse_t *sparse)
|
||||
{
|
||||
return flecs_sparse_count(sparse);
|
||||
}
|
||||
|
||||
void* ecs_sparse_get_dense(
|
||||
const ecs_sparse_t *sparse,
|
||||
ecs_size_t elem_size,
|
||||
int32_t index)
|
||||
{
|
||||
return flecs_sparse_get_dense(sparse, elem_size, index);
|
||||
}
|
||||
|
||||
void* ecs_sparse_get(
|
||||
const ecs_sparse_t *sparse,
|
||||
ecs_size_t elem_size,
|
||||
uint64_t id)
|
||||
{
|
||||
return flecs_sparse_get(sparse, elem_size, id);
|
||||
}
|
||||
191
engine/libs/flecs/src/datastructures/stack_allocator.c
Normal file
191
engine/libs/flecs/src/datastructures/stack_allocator.c
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* @file datastructures/stack_allocator.c
|
||||
* @brief Stack allocator.
|
||||
*
|
||||
* The stack allocator enables pushing and popping values to a stack, and has
|
||||
* a lower overhead when compared to block allocators. A stack allocator is a
|
||||
* good fit for small temporary allocations.
|
||||
*
|
||||
* The stack allocator allocates memory in pages. If the requested size of an
|
||||
* allocation exceeds the page size, a regular allocator is used instead.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16)
|
||||
|
||||
int64_t ecs_stack_allocator_alloc_count = 0;
|
||||
int64_t ecs_stack_allocator_free_count = 0;
|
||||
|
||||
static
|
||||
ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) {
|
||||
ecs_stack_page_t *result = ecs_os_malloc(
|
||||
FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE);
|
||||
result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET);
|
||||
result->next = NULL;
|
||||
result->id = page_id + 1;
|
||||
ecs_os_linc(&ecs_stack_allocator_alloc_count);
|
||||
return result;
|
||||
}
|
||||
|
||||
void* flecs_stack_alloc(
|
||||
ecs_stack_t *stack,
|
||||
ecs_size_t size,
|
||||
ecs_size_t align)
|
||||
{
|
||||
ecs_stack_page_t *page = stack->tail_page;
|
||||
if (page == &stack->first && !page->data) {
|
||||
page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE);
|
||||
ecs_os_linc(&ecs_stack_allocator_alloc_count);
|
||||
}
|
||||
|
||||
int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align));
|
||||
int16_t next_sp = flecs_ito(int16_t, sp + size);
|
||||
void *result = NULL;
|
||||
|
||||
if (next_sp > ECS_STACK_PAGE_SIZE) {
|
||||
if (size > ECS_STACK_PAGE_SIZE) {
|
||||
result = ecs_os_malloc(size); /* Too large for page */
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (page->next) {
|
||||
page = page->next;
|
||||
} else {
|
||||
page = page->next = flecs_stack_page_new(page->id);
|
||||
}
|
||||
sp = 0;
|
||||
next_sp = flecs_ito(int16_t, size);
|
||||
stack->tail_page = page;
|
||||
}
|
||||
|
||||
page->sp = next_sp;
|
||||
result = ECS_OFFSET(page->data, sp);
|
||||
|
||||
done:
|
||||
#ifdef FLECS_SANITIZE
|
||||
ecs_os_memset(result, 0xAA, size);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void* flecs_stack_calloc(
|
||||
ecs_stack_t *stack,
|
||||
ecs_size_t size,
|
||||
ecs_size_t align)
|
||||
{
|
||||
void *ptr = flecs_stack_alloc(stack, size, align);
|
||||
ecs_os_memset(ptr, 0, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void flecs_stack_free(
|
||||
void *ptr,
|
||||
ecs_size_t size)
|
||||
{
|
||||
if (size > ECS_STACK_PAGE_SIZE) {
|
||||
ecs_os_free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_stack_cursor_t* flecs_stack_get_cursor(
|
||||
ecs_stack_t *stack)
|
||||
{
|
||||
ecs_stack_page_t *page = stack->tail_page;
|
||||
int16_t sp = stack->tail_page->sp;
|
||||
ecs_stack_cursor_t *result = flecs_stack_alloc_t(stack, ecs_stack_cursor_t);
|
||||
result->page = page;
|
||||
result->sp = sp;
|
||||
result->is_free = false;
|
||||
|
||||
#ifdef FLECS_DEBUG
|
||||
++ stack->cursor_count;
|
||||
result->owner = stack;
|
||||
#endif
|
||||
|
||||
result->prev = stack->tail_cursor;
|
||||
stack->tail_cursor = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void flecs_stack_restore_cursor(
|
||||
ecs_stack_t *stack,
|
||||
ecs_stack_cursor_t *cursor)
|
||||
{
|
||||
if (!cursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, NULL);
|
||||
ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, NULL);
|
||||
ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, NULL);
|
||||
|
||||
cursor->is_free = true;
|
||||
|
||||
#ifdef FLECS_DEBUG
|
||||
-- stack->cursor_count;
|
||||
#endif
|
||||
|
||||
/* If cursor is not the last on the stack no memory should be freed */
|
||||
if (cursor != stack->tail_cursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Iterate freed cursors to know how much memory we can free */
|
||||
do {
|
||||
ecs_stack_cursor_t* prev = cursor->prev;
|
||||
if (!prev || !prev->is_free) {
|
||||
break; /* Found active cursor, free up until this point */
|
||||
}
|
||||
cursor = prev;
|
||||
} while (cursor);
|
||||
|
||||
stack->tail_cursor = cursor->prev;
|
||||
stack->tail_page = cursor->page;
|
||||
stack->tail_page->sp = cursor->sp;
|
||||
|
||||
/* If the cursor count is zero, stack should be empty
|
||||
* if the cursor count is non-zero, stack should not be empty */
|
||||
ecs_dbg_assert((stack->cursor_count == 0) ==
|
||||
(stack->tail_page == &stack->first && stack->tail_page->sp == 0),
|
||||
ECS_LEAK_DETECTED, NULL);
|
||||
}
|
||||
|
||||
void flecs_stack_reset(
|
||||
ecs_stack_t *stack)
|
||||
{
|
||||
ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL);
|
||||
stack->tail_page = &stack->first;
|
||||
stack->first.sp = 0;
|
||||
stack->tail_cursor = NULL;
|
||||
}
|
||||
|
||||
void flecs_stack_init(
|
||||
ecs_stack_t *stack)
|
||||
{
|
||||
ecs_os_zeromem(stack);
|
||||
stack->tail_page = &stack->first;
|
||||
stack->first.data = NULL;
|
||||
}
|
||||
|
||||
void flecs_stack_fini(
|
||||
ecs_stack_t *stack)
|
||||
{
|
||||
ecs_stack_page_t *next, *cur = &stack->first;
|
||||
ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL);
|
||||
ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, NULL);
|
||||
ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, NULL);
|
||||
|
||||
do {
|
||||
next = cur->next;
|
||||
if (cur == &stack->first) {
|
||||
if (cur->data) {
|
||||
ecs_os_linc(&ecs_stack_allocator_free_count);
|
||||
}
|
||||
ecs_os_free(cur->data);
|
||||
} else {
|
||||
ecs_os_linc(&ecs_stack_allocator_free_count);
|
||||
ecs_os_free(cur);
|
||||
}
|
||||
} while ((cur = next));
|
||||
}
|
||||
83
engine/libs/flecs/src/datastructures/stack_allocator.h
Normal file
83
engine/libs/flecs/src/datastructures/stack_allocator.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @file datastructures/stack_allocator.h
|
||||
* @brief Stack allocator.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_STACK_ALLOCATOR_H
|
||||
#define FLECS_STACK_ALLOCATOR_H
|
||||
|
||||
/** Stack allocator for quick allocation of small temporary values */
|
||||
#define ECS_STACK_PAGE_SIZE (4096)
|
||||
|
||||
typedef struct ecs_stack_page_t {
|
||||
void *data;
|
||||
struct ecs_stack_page_t *next;
|
||||
int16_t sp;
|
||||
uint32_t id;
|
||||
} ecs_stack_page_t;
|
||||
|
||||
typedef struct ecs_stack_t {
|
||||
ecs_stack_page_t first;
|
||||
ecs_stack_page_t *tail_page;
|
||||
ecs_stack_cursor_t *tail_cursor;
|
||||
#ifdef FLECS_DEBUG
|
||||
int32_t cursor_count;
|
||||
#endif
|
||||
} ecs_stack_t;
|
||||
|
||||
FLECS_DBG_API
|
||||
void flecs_stack_init(
|
||||
ecs_stack_t *stack);
|
||||
|
||||
FLECS_DBG_API
|
||||
void flecs_stack_fini(
|
||||
ecs_stack_t *stack);
|
||||
|
||||
FLECS_DBG_API
|
||||
void* flecs_stack_alloc(
|
||||
ecs_stack_t *stack,
|
||||
ecs_size_t size,
|
||||
ecs_size_t align);
|
||||
|
||||
#define flecs_stack_alloc_t(stack, T)\
|
||||
flecs_stack_alloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T))
|
||||
|
||||
#define flecs_stack_alloc_n(stack, T, count)\
|
||||
flecs_stack_alloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T))
|
||||
|
||||
FLECS_DBG_API
|
||||
void* flecs_stack_calloc(
|
||||
ecs_stack_t *stack,
|
||||
ecs_size_t size,
|
||||
ecs_size_t align);
|
||||
|
||||
#define flecs_stack_calloc_t(stack, T)\
|
||||
flecs_stack_calloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T))
|
||||
|
||||
#define flecs_stack_calloc_n(stack, T, count)\
|
||||
flecs_stack_calloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T))
|
||||
|
||||
FLECS_DBG_API
|
||||
void flecs_stack_free(
|
||||
void *ptr,
|
||||
ecs_size_t size);
|
||||
|
||||
#define flecs_stack_free_t(ptr, T)\
|
||||
flecs_stack_free(ptr, ECS_SIZEOF(T))
|
||||
|
||||
#define flecs_stack_free_n(ptr, T, count)\
|
||||
flecs_stack_free(ptr, ECS_SIZEOF(T) * count)
|
||||
|
||||
void flecs_stack_reset(
|
||||
ecs_stack_t *stack);
|
||||
|
||||
FLECS_DBG_API
|
||||
ecs_stack_cursor_t* flecs_stack_get_cursor(
|
||||
ecs_stack_t *stack);
|
||||
|
||||
FLECS_DBG_API
|
||||
void flecs_stack_restore_cursor(
|
||||
ecs_stack_t *stack,
|
||||
ecs_stack_cursor_t *cursor);
|
||||
|
||||
#endif
|
||||
836
engine/libs/flecs/src/datastructures/strbuf.c
Normal file
836
engine/libs/flecs/src/datastructures/strbuf.c
Normal file
@@ -0,0 +1,836 @@
|
||||
/**
|
||||
* @file datastructures/strbuf.c
|
||||
* @brief Utility for constructing strings.
|
||||
*
|
||||
* A buffer builds up a list of elements which individually can be up to N bytes
|
||||
* large. While appending, data is added to these elements. More elements are
|
||||
* added on the fly when needed. When an application calls ecs_strbuf_get, all
|
||||
* elements are combined in one string and the element administration is freed.
|
||||
*
|
||||
* This approach prevents reallocs of large blocks of memory, and therefore
|
||||
* copying large blocks of memory when appending to a large buffer. A buffer
|
||||
* preallocates some memory for the element overhead so that for small strings
|
||||
* there is hardly any overhead, while for large strings the overhead is offset
|
||||
* by the reduced time spent on copying memory.
|
||||
*
|
||||
* The functionality provided by strbuf is similar to std::stringstream.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
#include <math.h>
|
||||
|
||||
/**
|
||||
* stm32tpl -- STM32 C++ Template Peripheral Library
|
||||
* Visit https://github.com/antongus/stm32tpl for new versions
|
||||
*
|
||||
* Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA
|
||||
*/
|
||||
|
||||
#define MAX_PRECISION (10)
|
||||
#define EXP_THRESHOLD (3)
|
||||
#define INT64_MAX_F ((double)INT64_MAX)
|
||||
|
||||
static const double rounders[MAX_PRECISION + 1] =
|
||||
{
|
||||
0.5, // 0
|
||||
0.05, // 1
|
||||
0.005, // 2
|
||||
0.0005, // 3
|
||||
0.00005, // 4
|
||||
0.000005, // 5
|
||||
0.0000005, // 6
|
||||
0.00000005, // 7
|
||||
0.000000005, // 8
|
||||
0.0000000005, // 9
|
||||
0.00000000005 // 10
|
||||
};
|
||||
|
||||
static
|
||||
char* flecs_strbuf_itoa(
|
||||
char *buf,
|
||||
int64_t v)
|
||||
{
|
||||
char *ptr = buf;
|
||||
char * p1;
|
||||
char c;
|
||||
|
||||
if (!v) {
|
||||
*ptr++ = '0';
|
||||
} else {
|
||||
if (v < 0) {
|
||||
ptr[0] = '-';
|
||||
ptr ++;
|
||||
v *= -1;
|
||||
}
|
||||
|
||||
char *p = ptr;
|
||||
while (v) {
|
||||
int64_t vdiv = v / 10;
|
||||
int64_t vmod = v - (vdiv * 10);
|
||||
p[0] = (char)('0' + vmod);
|
||||
p ++;
|
||||
v = vdiv;
|
||||
}
|
||||
|
||||
p1 = p;
|
||||
|
||||
while (p > ptr) {
|
||||
c = *--p;
|
||||
*p = *ptr;
|
||||
*ptr++ = c;
|
||||
}
|
||||
ptr = p1;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_strbuf_ftoa(
|
||||
ecs_strbuf_t *out,
|
||||
double f,
|
||||
int precision,
|
||||
char nan_delim)
|
||||
{
|
||||
char buf[64];
|
||||
char * ptr = buf;
|
||||
char c;
|
||||
int64_t intPart;
|
||||
int64_t exp = 0;
|
||||
|
||||
if (ecs_os_isnan(f)) {
|
||||
if (nan_delim) {
|
||||
ecs_strbuf_appendch(out, nan_delim);
|
||||
ecs_strbuf_appendlit(out, "NaN");
|
||||
return ecs_strbuf_appendch(out, nan_delim);
|
||||
} else {
|
||||
return ecs_strbuf_appendlit(out, "NaN");
|
||||
}
|
||||
}
|
||||
if (ecs_os_isinf(f)) {
|
||||
if (nan_delim) {
|
||||
ecs_strbuf_appendch(out, nan_delim);
|
||||
ecs_strbuf_appendlit(out, "Inf");
|
||||
return ecs_strbuf_appendch(out, nan_delim);
|
||||
} else {
|
||||
return ecs_strbuf_appendlit(out, "Inf");
|
||||
}
|
||||
}
|
||||
|
||||
if (precision > MAX_PRECISION) {
|
||||
precision = MAX_PRECISION;
|
||||
}
|
||||
|
||||
if (f < 0) {
|
||||
f = -f;
|
||||
*ptr++ = '-';
|
||||
}
|
||||
|
||||
if (precision < 0) {
|
||||
if (f < 1.0) precision = 6;
|
||||
else if (f < 10.0) precision = 5;
|
||||
else if (f < 100.0) precision = 4;
|
||||
else if (f < 1000.0) precision = 3;
|
||||
else if (f < 10000.0) precision = 2;
|
||||
else if (f < 100000.0) precision = 1;
|
||||
else precision = 0;
|
||||
}
|
||||
|
||||
if (precision) {
|
||||
f += rounders[precision];
|
||||
}
|
||||
|
||||
/* Make sure that number can be represented as 64bit int, increase exp */
|
||||
while (f > INT64_MAX_F) {
|
||||
f /= 1000 * 1000 * 1000;
|
||||
exp += 9;
|
||||
}
|
||||
|
||||
intPart = (int64_t)f;
|
||||
f -= (double)intPart;
|
||||
|
||||
ptr = flecs_strbuf_itoa(ptr, intPart);
|
||||
|
||||
if (precision) {
|
||||
*ptr++ = '.';
|
||||
while (precision--) {
|
||||
f *= 10.0;
|
||||
c = (char)f;
|
||||
*ptr++ = (char)('0' + c);
|
||||
f -= c;
|
||||
}
|
||||
}
|
||||
*ptr = 0;
|
||||
|
||||
/* Remove trailing 0s */
|
||||
while ((&ptr[-1] != buf) && (ptr[-1] == '0')) {
|
||||
ptr[-1] = '\0';
|
||||
ptr --;
|
||||
}
|
||||
if (ptr != buf && ptr[-1] == '.') {
|
||||
ptr[-1] = '\0';
|
||||
ptr --;
|
||||
}
|
||||
|
||||
/* If 0s before . exceed threshold, convert to exponent to save space
|
||||
* without losing precision. */
|
||||
char *cur = ptr;
|
||||
while ((&cur[-1] != buf) && (cur[-1] == '0')) {
|
||||
cur --;
|
||||
}
|
||||
|
||||
if (exp || ((ptr - cur) > EXP_THRESHOLD)) {
|
||||
cur[0] = '\0';
|
||||
exp += (ptr - cur);
|
||||
ptr = cur;
|
||||
}
|
||||
|
||||
if (exp) {
|
||||
char *p1 = &buf[1];
|
||||
if (nan_delim) {
|
||||
ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf));
|
||||
buf[0] = nan_delim;
|
||||
p1 ++;
|
||||
}
|
||||
|
||||
/* Make sure that exp starts after first character */
|
||||
c = p1[0];
|
||||
|
||||
if (c) {
|
||||
p1[0] = '.';
|
||||
do {
|
||||
char t = (++p1)[0];
|
||||
if (t == '.') {
|
||||
exp ++;
|
||||
p1 --;
|
||||
break;
|
||||
}
|
||||
p1[0] = c;
|
||||
c = t;
|
||||
exp ++;
|
||||
} while (c);
|
||||
ptr = p1 + 1;
|
||||
} else {
|
||||
ptr = p1;
|
||||
}
|
||||
|
||||
|
||||
ptr[0] = 'e';
|
||||
ptr = flecs_strbuf_itoa(ptr + 1, exp);
|
||||
|
||||
if (nan_delim) {
|
||||
ptr[0] = nan_delim;
|
||||
ptr ++;
|
||||
}
|
||||
|
||||
ptr[0] = '\0';
|
||||
}
|
||||
|
||||
return ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf));
|
||||
}
|
||||
|
||||
/* Add an extra element to the buffer */
|
||||
static
|
||||
void flecs_strbuf_grow(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
/* Allocate new element */
|
||||
ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded);
|
||||
b->size += b->current->pos;
|
||||
b->current->next = (ecs_strbuf_element*)e;
|
||||
b->current = (ecs_strbuf_element*)e;
|
||||
b->elementCount ++;
|
||||
e->super.buffer_embedded = true;
|
||||
e->super.buf = e->buf;
|
||||
e->super.pos = 0;
|
||||
e->super.next = NULL;
|
||||
}
|
||||
|
||||
/* Add an extra dynamic element */
|
||||
static
|
||||
void flecs_strbuf_grow_str(
|
||||
ecs_strbuf_t *b,
|
||||
const char *str,
|
||||
char *alloc_str,
|
||||
int32_t size)
|
||||
{
|
||||
/* Allocate new element */
|
||||
ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str);
|
||||
b->size += b->current->pos;
|
||||
b->current->next = (ecs_strbuf_element*)e;
|
||||
b->current = (ecs_strbuf_element*)e;
|
||||
b->elementCount ++;
|
||||
e->super.buffer_embedded = false;
|
||||
e->super.pos = size ? size : (int32_t)ecs_os_strlen(str);
|
||||
e->super.next = NULL;
|
||||
e->super.buf = ECS_CONST_CAST(char*, str);
|
||||
e->alloc_str = alloc_str;
|
||||
}
|
||||
|
||||
static
|
||||
char* flecs_strbuf_ptr(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
if (b->buf) {
|
||||
return &b->buf[b->current->pos];
|
||||
} else {
|
||||
return &b->current->buf[b->current->pos];
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute the amount of space left in the current element */
|
||||
static
|
||||
int32_t flecs_strbuf_memLeftInCurrentElement(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
if (b->current->buffer_embedded) {
|
||||
return ECS_STRBUF_ELEMENT_SIZE - b->current->pos;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute the amount of space left */
|
||||
static
|
||||
int32_t flecs_strbuf_memLeft(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
if (b->max) {
|
||||
return b->max - b->size - b->current->pos;
|
||||
} else {
|
||||
return INT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_strbuf_init(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
/* Initialize buffer structure only once */
|
||||
if (!b->elementCount) {
|
||||
b->size = 0;
|
||||
b->firstElement.super.next = NULL;
|
||||
b->firstElement.super.pos = 0;
|
||||
b->firstElement.super.buffer_embedded = true;
|
||||
b->firstElement.super.buf = b->firstElement.buf;
|
||||
b->elementCount ++;
|
||||
b->current = (ecs_strbuf_element*)&b->firstElement;
|
||||
}
|
||||
}
|
||||
|
||||
/* Append a format string to a buffer */
|
||||
static
|
||||
bool flecs_strbuf_vappend(
|
||||
ecs_strbuf_t *b,
|
||||
const char* str,
|
||||
va_list args)
|
||||
{
|
||||
bool result = true;
|
||||
va_list arg_cpy;
|
||||
|
||||
if (!str) {
|
||||
return result;
|
||||
}
|
||||
|
||||
flecs_strbuf_init(b);
|
||||
|
||||
int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b);
|
||||
int32_t memLeft = flecs_strbuf_memLeft(b);
|
||||
|
||||
if (!memLeft) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Compute the memory required to add the string to the buffer. If user
|
||||
* provided buffer, use space left in buffer, otherwise use space left in
|
||||
* current element. */
|
||||
int32_t max_copy = b->buf ? memLeft : memLeftInElement;
|
||||
int32_t memRequired;
|
||||
|
||||
va_copy(arg_cpy, args);
|
||||
memRequired = vsnprintf(
|
||||
flecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args);
|
||||
|
||||
ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (memRequired <= memLeftInElement) {
|
||||
/* Element was large enough to fit string */
|
||||
b->current->pos += memRequired;
|
||||
} else if ((memRequired - memLeftInElement) < memLeft) {
|
||||
/* If string is a format string, a new buffer of size memRequired is
|
||||
* needed to re-evaluate the format string and only use the part that
|
||||
* wasn't already copied to the previous element */
|
||||
if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) {
|
||||
/* Resulting string fits in standard-size buffer. Note that the
|
||||
* entire string needs to fit, not just the remainder, as the
|
||||
* format string cannot be partially evaluated */
|
||||
flecs_strbuf_grow(b);
|
||||
|
||||
/* Copy entire string to new buffer */
|
||||
ecs_os_vsprintf(flecs_strbuf_ptr(b), str, arg_cpy);
|
||||
|
||||
/* Ignore the part of the string that was copied into the
|
||||
* previous buffer. The string copied into the new buffer could
|
||||
* be memmoved so that only the remainder is left, but that is
|
||||
* most likely more expensive than just keeping the entire
|
||||
* string. */
|
||||
|
||||
/* Update position in buffer */
|
||||
b->current->pos += memRequired;
|
||||
} else {
|
||||
/* Resulting string does not fit in standard-size buffer.
|
||||
* Allocate a new buffer that can hold the entire string. */
|
||||
char *dst = ecs_os_malloc(memRequired + 1);
|
||||
ecs_os_vsprintf(dst, str, arg_cpy);
|
||||
flecs_strbuf_grow_str(b, dst, dst, memRequired);
|
||||
}
|
||||
}
|
||||
|
||||
va_end(arg_cpy);
|
||||
|
||||
return flecs_strbuf_memLeft(b) > 0;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_strbuf_appendstr(
|
||||
ecs_strbuf_t *b,
|
||||
const char* str,
|
||||
int n)
|
||||
{
|
||||
flecs_strbuf_init(b);
|
||||
|
||||
int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b);
|
||||
int32_t memLeft = flecs_strbuf_memLeft(b);
|
||||
if (memLeft <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Never write more than what the buffer can store */
|
||||
if (n > memLeft) {
|
||||
n = memLeft;
|
||||
}
|
||||
|
||||
if (n <= memLeftInElement) {
|
||||
/* Element was large enough to fit string */
|
||||
ecs_os_strncpy(flecs_strbuf_ptr(b), str, n);
|
||||
b->current->pos += n;
|
||||
} else if ((n - memLeftInElement) < memLeft) {
|
||||
ecs_os_strncpy(flecs_strbuf_ptr(b), str, memLeftInElement);
|
||||
|
||||
/* Element was not large enough, but buffer still has space */
|
||||
b->current->pos += memLeftInElement;
|
||||
n -= memLeftInElement;
|
||||
|
||||
/* Current element was too small, copy remainder into new element */
|
||||
if (n < ECS_STRBUF_ELEMENT_SIZE) {
|
||||
/* A standard-size buffer is large enough for the new string */
|
||||
flecs_strbuf_grow(b);
|
||||
|
||||
/* Copy the remainder to the new buffer */
|
||||
if (n) {
|
||||
/* If a max number of characters to write is set, only a
|
||||
* subset of the string should be copied to the buffer */
|
||||
ecs_os_strncpy(
|
||||
flecs_strbuf_ptr(b),
|
||||
str + memLeftInElement,
|
||||
(size_t)n);
|
||||
} else {
|
||||
ecs_os_strcpy(flecs_strbuf_ptr(b), str + memLeftInElement);
|
||||
}
|
||||
|
||||
/* Update to number of characters copied to new buffer */
|
||||
b->current->pos += n;
|
||||
} else {
|
||||
/* String doesn't fit in a single element, strdup */
|
||||
char *remainder = ecs_os_strdup(str + memLeftInElement);
|
||||
flecs_strbuf_grow_str(b, remainder, remainder, n);
|
||||
}
|
||||
} else {
|
||||
/* Buffer max has been reached */
|
||||
return false;
|
||||
}
|
||||
|
||||
return flecs_strbuf_memLeft(b) > 0;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_strbuf_appendch(
|
||||
ecs_strbuf_t *b,
|
||||
char ch)
|
||||
{
|
||||
flecs_strbuf_init(b);
|
||||
|
||||
int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b);
|
||||
int32_t memLeft = flecs_strbuf_memLeft(b);
|
||||
if (memLeft <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memLeftInElement) {
|
||||
/* Element was large enough to fit string */
|
||||
flecs_strbuf_ptr(b)[0] = ch;
|
||||
b->current->pos ++;
|
||||
} else {
|
||||
flecs_strbuf_grow(b);
|
||||
flecs_strbuf_ptr(b)[0] = ch;
|
||||
b->current->pos ++;
|
||||
}
|
||||
|
||||
return flecs_strbuf_memLeft(b) > 0;
|
||||
}
|
||||
|
||||
bool ecs_strbuf_vappend(
|
||||
ecs_strbuf_t *b,
|
||||
const char* fmt,
|
||||
va_list args)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
return flecs_strbuf_vappend(b, fmt, args);
|
||||
}
|
||||
|
||||
bool ecs_strbuf_append(
|
||||
ecs_strbuf_t *b,
|
||||
const char* fmt,
|
||||
...)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
bool result = flecs_strbuf_vappend(b, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendstrn(
|
||||
ecs_strbuf_t *b,
|
||||
const char* str,
|
||||
int32_t len)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
return flecs_strbuf_appendstr(b, str, len);
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendch(
|
||||
ecs_strbuf_t *b,
|
||||
char ch)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
return flecs_strbuf_appendch(b, ch);
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendint(
|
||||
ecs_strbuf_t *b,
|
||||
int64_t v)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
char numbuf[32];
|
||||
char *ptr = flecs_strbuf_itoa(numbuf, v);
|
||||
return ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf));
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendflt(
|
||||
ecs_strbuf_t *b,
|
||||
double flt,
|
||||
char nan_delim)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
return flecs_strbuf_ftoa(b, flt, 10, nan_delim);
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendbool(
|
||||
ecs_strbuf_t *buffer,
|
||||
bool v)
|
||||
{
|
||||
ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
if (v) {
|
||||
return ecs_strbuf_appendlit(buffer, "true");
|
||||
} else {
|
||||
return ecs_strbuf_appendlit(buffer, "false");
|
||||
}
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendstr_zerocpy(
|
||||
ecs_strbuf_t *b,
|
||||
char* str)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
flecs_strbuf_init(b);
|
||||
flecs_strbuf_grow_str(b, str, str, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendstr_zerocpyn(
|
||||
ecs_strbuf_t *b,
|
||||
char *str,
|
||||
int32_t n)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
flecs_strbuf_init(b);
|
||||
flecs_strbuf_grow_str(b, str, str, n);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendstr_zerocpy_const(
|
||||
ecs_strbuf_t *b,
|
||||
const char* str)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
/* Removes const modifier, but logic prevents changing / delete string */
|
||||
flecs_strbuf_init(b);
|
||||
flecs_strbuf_grow_str(b, str, NULL, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendstr_zerocpyn_const(
|
||||
ecs_strbuf_t *b,
|
||||
const char *str,
|
||||
int32_t n)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
/* Removes const modifier, but logic prevents changing / delete string */
|
||||
flecs_strbuf_init(b);
|
||||
flecs_strbuf_grow_str(b, str, NULL, n);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ecs_strbuf_appendstr(
|
||||
ecs_strbuf_t *b,
|
||||
const char* str)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
return flecs_strbuf_appendstr(b, str, ecs_os_strlen(str));
|
||||
}
|
||||
|
||||
bool ecs_strbuf_mergebuff(
|
||||
ecs_strbuf_t *dst_buffer,
|
||||
ecs_strbuf_t *src_buffer)
|
||||
{
|
||||
if (src_buffer->elementCount) {
|
||||
if (src_buffer->buf) {
|
||||
return ecs_strbuf_appendstrn(
|
||||
dst_buffer, src_buffer->buf, src_buffer->length);
|
||||
} else {
|
||||
ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement;
|
||||
|
||||
/* Copy first element as it is inlined in the src buffer */
|
||||
ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos);
|
||||
|
||||
while ((e = e->next)) {
|
||||
dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element));
|
||||
*dst_buffer->current->next = *e;
|
||||
}
|
||||
}
|
||||
|
||||
*src_buffer = ECS_STRBUF_INIT;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char* ecs_strbuf_get(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
char* result = NULL;
|
||||
if (b->elementCount) {
|
||||
if (b->buf) {
|
||||
b->buf[b->current->pos] = '\0';
|
||||
result = ecs_os_strdup(b->buf);
|
||||
} else {
|
||||
void *next = NULL;
|
||||
int32_t len = b->size + b->current->pos + 1;
|
||||
ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement;
|
||||
|
||||
result = ecs_os_malloc(len);
|
||||
char* ptr = result;
|
||||
|
||||
do {
|
||||
ecs_os_memcpy(ptr, e->buf, e->pos);
|
||||
ptr += e->pos;
|
||||
next = e->next;
|
||||
if (e != &b->firstElement.super) {
|
||||
if (!e->buffer_embedded) {
|
||||
ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str);
|
||||
}
|
||||
ecs_os_free(e);
|
||||
}
|
||||
} while ((e = next));
|
||||
|
||||
result[len - 1] = '\0';
|
||||
b->length = len;
|
||||
}
|
||||
} else {
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
b->elementCount = 0;
|
||||
|
||||
b->content = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char *ecs_strbuf_get_small(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
int32_t written = ecs_strbuf_written(b);
|
||||
ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL);
|
||||
char *buf = b->firstElement.buf;
|
||||
buf[written] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
void ecs_strbuf_reset(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
if (b->elementCount && !b->buf) {
|
||||
void *next = NULL;
|
||||
ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement;
|
||||
do {
|
||||
next = e->next;
|
||||
if (e != (ecs_strbuf_element*)&b->firstElement) {
|
||||
ecs_os_free(e);
|
||||
}
|
||||
} while ((e = next));
|
||||
}
|
||||
|
||||
*b = ECS_STRBUF_INIT;
|
||||
}
|
||||
|
||||
void ecs_strbuf_list_push(
|
||||
ecs_strbuf_t *b,
|
||||
const char *list_open,
|
||||
const char *separator)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, NULL);
|
||||
b->list_sp ++;
|
||||
ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH,
|
||||
ECS_INVALID_OPERATION, NULL);
|
||||
|
||||
b->list_stack[b->list_sp].count = 0;
|
||||
b->list_stack[b->list_sp].separator = separator;
|
||||
|
||||
if (list_open) {
|
||||
char ch = list_open[0];
|
||||
if (ch && !list_open[1]) {
|
||||
ecs_strbuf_appendch(b, ch);
|
||||
} else {
|
||||
ecs_strbuf_appendstr(b, list_open);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_strbuf_list_pop(
|
||||
ecs_strbuf_t *b,
|
||||
const char *list_close)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(b->list_sp > 0, ECS_INVALID_OPERATION, NULL);
|
||||
|
||||
b->list_sp --;
|
||||
|
||||
if (list_close) {
|
||||
char ch = list_close[0];
|
||||
if (ch && !list_close[1]) {
|
||||
ecs_strbuf_appendch(b, list_close[0]);
|
||||
} else {
|
||||
ecs_strbuf_appendstr(b, list_close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_strbuf_list_next(
|
||||
ecs_strbuf_t *b)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
int32_t list_sp = b->list_sp;
|
||||
if (b->list_stack[list_sp].count != 0) {
|
||||
const char *sep = b->list_stack[list_sp].separator;
|
||||
if (sep && !sep[1]) {
|
||||
ecs_strbuf_appendch(b, sep[0]);
|
||||
} else {
|
||||
ecs_strbuf_appendstr(b, sep);
|
||||
}
|
||||
}
|
||||
b->list_stack[list_sp].count ++;
|
||||
}
|
||||
|
||||
bool ecs_strbuf_list_appendch(
|
||||
ecs_strbuf_t *b,
|
||||
char ch)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_strbuf_list_next(b);
|
||||
return flecs_strbuf_appendch(b, ch);
|
||||
}
|
||||
|
||||
bool ecs_strbuf_list_append(
|
||||
ecs_strbuf_t *b,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_strbuf_list_next(b);
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
bool result = flecs_strbuf_vappend(b, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ecs_strbuf_list_appendstr(
|
||||
ecs_strbuf_t *b,
|
||||
const char *str)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_strbuf_list_next(b);
|
||||
return ecs_strbuf_appendstr(b, str);
|
||||
}
|
||||
|
||||
bool ecs_strbuf_list_appendstrn(
|
||||
ecs_strbuf_t *b,
|
||||
const char *str,
|
||||
int32_t n)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_strbuf_list_next(b);
|
||||
return ecs_strbuf_appendstrn(b, str, n);
|
||||
}
|
||||
|
||||
int32_t ecs_strbuf_written(
|
||||
const ecs_strbuf_t *b)
|
||||
{
|
||||
ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
if (b->current) {
|
||||
return b->size + b->current->pos;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
383
engine/libs/flecs/src/datastructures/switch_list.c
Normal file
383
engine/libs/flecs/src/datastructures/switch_list.c
Normal file
@@ -0,0 +1,383 @@
|
||||
/**
|
||||
* @file datastructures/switch_list.c
|
||||
* @brief Interleaved linked list for storing mutually exclusive values.
|
||||
*
|
||||
* Datastructure that stores N interleaved linked lists in an array.
|
||||
* This allows for efficient storage of elements with mutually exclusive values.
|
||||
* Each linked list has a header element which points to the index in the array
|
||||
* that stores the first node of the list. Each list node points to the next
|
||||
* array element.
|
||||
*
|
||||
* The datastructure allows for efficient storage and retrieval for values with
|
||||
* mutually exclusive values, such as enumeration values. The linked list allows
|
||||
* an application to obtain all elements for a given (enumeration) value without
|
||||
* having to search.
|
||||
*
|
||||
* While the list accepts 64 bit values, it only uses the lower 32bits of the
|
||||
* value for selecting the correct linked list.
|
||||
*
|
||||
* The switch list is used to store union relationships.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
#ifdef FLECS_SANITIZE
|
||||
static
|
||||
void flecs_switch_verify_nodes(
|
||||
ecs_switch_header_t *hdr,
|
||||
ecs_switch_node_t *nodes)
|
||||
{
|
||||
if (!hdr) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t prev = -1, elem = hdr->element, count = 0;
|
||||
while (elem != -1) {
|
||||
ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL);
|
||||
prev = elem;
|
||||
elem = nodes[elem].next;
|
||||
count ++;
|
||||
}
|
||||
|
||||
ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
#else
|
||||
#define flecs_switch_verify_nodes(hdr, nodes)
|
||||
#endif
|
||||
|
||||
static
|
||||
ecs_switch_header_t* flecs_switch_get_header(
|
||||
const ecs_switch_t *sw,
|
||||
uint64_t value)
|
||||
{
|
||||
if (value == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return (ecs_switch_header_t*)ecs_map_get(&sw->hdrs, value);
|
||||
}
|
||||
|
||||
static
|
||||
ecs_switch_header_t *flecs_switch_ensure_header(
|
||||
ecs_switch_t *sw,
|
||||
uint64_t value)
|
||||
{
|
||||
ecs_switch_header_t *node = flecs_switch_get_header(sw, value);
|
||||
if (!node && (value != 0)) {
|
||||
node = (ecs_switch_header_t*)ecs_map_ensure(&sw->hdrs, value);
|
||||
node->count = 0;
|
||||
node->element = -1;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_switch_remove_node(
|
||||
ecs_switch_header_t *hdr,
|
||||
ecs_switch_node_t *nodes,
|
||||
ecs_switch_node_t *node,
|
||||
int32_t element)
|
||||
{
|
||||
ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* Update previous node/header */
|
||||
if (hdr->element == element) {
|
||||
ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL);
|
||||
/* If this is the first node, update the header */
|
||||
hdr->element = node->next;
|
||||
} else {
|
||||
/* If this is not the first node, update the previous node to the
|
||||
* removed node's next ptr */
|
||||
ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_switch_node_t *prev_node = &nodes[node->prev];
|
||||
prev_node->next = node->next;
|
||||
}
|
||||
|
||||
/* Update next node */
|
||||
int32_t next = node->next;
|
||||
if (next != -1) {
|
||||
ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL);
|
||||
/* If this is not the last node, update the next node to point to the
|
||||
* removed node's prev ptr */
|
||||
ecs_switch_node_t *next_node = &nodes[next];
|
||||
next_node->prev = node->prev;
|
||||
}
|
||||
|
||||
/* Decrease count of current header */
|
||||
hdr->count --;
|
||||
ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
void flecs_switch_init(
|
||||
ecs_switch_t *sw,
|
||||
ecs_allocator_t *allocator,
|
||||
int32_t elements)
|
||||
{
|
||||
ecs_map_init(&sw->hdrs, allocator);
|
||||
ecs_vec_init_t(allocator, &sw->nodes, ecs_switch_node_t, elements);
|
||||
ecs_vec_init_t(allocator, &sw->values, uint64_t, elements);
|
||||
|
||||
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes);
|
||||
uint64_t *values = ecs_vec_first(&sw->values);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < elements; i ++) {
|
||||
nodes[i].prev = -1;
|
||||
nodes[i].next = -1;
|
||||
values[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_switch_clear(
|
||||
ecs_switch_t *sw)
|
||||
{
|
||||
ecs_map_clear(&sw->hdrs);
|
||||
ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t);
|
||||
ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t);
|
||||
}
|
||||
|
||||
void flecs_switch_fini(
|
||||
ecs_switch_t *sw)
|
||||
{
|
||||
ecs_map_fini(&sw->hdrs);
|
||||
ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t);
|
||||
ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t);
|
||||
}
|
||||
|
||||
void flecs_switch_add(
|
||||
ecs_switch_t *sw)
|
||||
{
|
||||
ecs_switch_node_t *node = ecs_vec_append_t(sw->hdrs.allocator,
|
||||
&sw->nodes, ecs_switch_node_t);
|
||||
uint64_t *value = ecs_vec_append_t(sw->hdrs.allocator,
|
||||
&sw->values, uint64_t);
|
||||
node->prev = -1;
|
||||
node->next = -1;
|
||||
*value = 0;
|
||||
}
|
||||
|
||||
void flecs_switch_set_count(
|
||||
ecs_switch_t *sw,
|
||||
int32_t count)
|
||||
{
|
||||
int32_t old_count = ecs_vec_count(&sw->nodes);
|
||||
if (old_count == count) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_vec_set_count_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t, count);
|
||||
ecs_vec_set_count_t(sw->hdrs.allocator, &sw->values, uint64_t, count);
|
||||
|
||||
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes);
|
||||
uint64_t *values = ecs_vec_first(&sw->values);
|
||||
|
||||
int32_t i;
|
||||
for (i = old_count; i < count; i ++) {
|
||||
ecs_switch_node_t *node = &nodes[i];
|
||||
node->prev = -1;
|
||||
node->next = -1;
|
||||
values[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t flecs_switch_count(
|
||||
ecs_switch_t *sw)
|
||||
{
|
||||
ecs_assert(ecs_vec_count(&sw->values) == ecs_vec_count(&sw->nodes),
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
return ecs_vec_count(&sw->values);
|
||||
}
|
||||
|
||||
void flecs_switch_ensure(
|
||||
ecs_switch_t *sw,
|
||||
int32_t count)
|
||||
{
|
||||
int32_t old_count = ecs_vec_count(&sw->nodes);
|
||||
if (old_count >= count) {
|
||||
return;
|
||||
}
|
||||
|
||||
flecs_switch_set_count(sw, count);
|
||||
}
|
||||
|
||||
void flecs_switch_addn(
|
||||
ecs_switch_t *sw,
|
||||
int32_t count)
|
||||
{
|
||||
int32_t old_count = ecs_vec_count(&sw->nodes);
|
||||
flecs_switch_set_count(sw, old_count + count);
|
||||
}
|
||||
|
||||
void flecs_switch_set(
|
||||
ecs_switch_t *sw,
|
||||
int32_t element,
|
||||
uint64_t value)
|
||||
{
|
||||
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
uint64_t *values = ecs_vec_first(&sw->values);
|
||||
uint64_t cur_value = values[element];
|
||||
|
||||
/* If the node is already assigned to the value, nothing to be done */
|
||||
if (cur_value == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes);
|
||||
ecs_switch_node_t *node = &nodes[element];
|
||||
|
||||
ecs_switch_header_t *dst_hdr = flecs_switch_ensure_header(sw, value);
|
||||
ecs_switch_header_t *cur_hdr = flecs_switch_get_header(sw, cur_value);
|
||||
|
||||
flecs_switch_verify_nodes(cur_hdr, nodes);
|
||||
flecs_switch_verify_nodes(dst_hdr, nodes);
|
||||
|
||||
/* If value is not 0, and dst_hdr is NULL, then this is not a valid value
|
||||
* for this switch */
|
||||
ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
if (cur_hdr) {
|
||||
flecs_switch_remove_node(cur_hdr, nodes, node, element);
|
||||
}
|
||||
|
||||
/* Now update the node itself by adding it as the first node of dst */
|
||||
node->prev = -1;
|
||||
values[element] = value;
|
||||
|
||||
if (dst_hdr) {
|
||||
node->next = dst_hdr->element;
|
||||
|
||||
/* Also update the dst header */
|
||||
int32_t first = dst_hdr->element;
|
||||
if (first != -1) {
|
||||
ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_switch_node_t *first_node = &nodes[first];
|
||||
first_node->prev = element;
|
||||
}
|
||||
|
||||
dst_hdr->element = element;
|
||||
dst_hdr->count ++;
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_switch_remove(
|
||||
ecs_switch_t *sw,
|
||||
int32_t elem)
|
||||
{
|
||||
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(elem < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(elem >= 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
uint64_t *values = ecs_vec_first(&sw->values);
|
||||
uint64_t value = values[elem];
|
||||
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes);
|
||||
ecs_switch_node_t *node = &nodes[elem];
|
||||
|
||||
/* If node is currently assigned to a case, remove it from the list */
|
||||
if (value != 0) {
|
||||
ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value);
|
||||
ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
flecs_switch_verify_nodes(hdr, nodes);
|
||||
flecs_switch_remove_node(hdr, nodes, node, elem);
|
||||
}
|
||||
|
||||
int32_t last_elem = ecs_vec_count(&sw->nodes) - 1;
|
||||
if (last_elem != elem) {
|
||||
ecs_switch_node_t *last = ecs_vec_last_t(&sw->nodes, ecs_switch_node_t);
|
||||
int32_t next = last->next, prev = last->prev;
|
||||
if (next != -1) {
|
||||
ecs_switch_node_t *n = &nodes[next];
|
||||
n->prev = elem;
|
||||
}
|
||||
|
||||
if (prev != -1) {
|
||||
ecs_switch_node_t *n = &nodes[prev];
|
||||
n->next = elem;
|
||||
} else {
|
||||
ecs_switch_header_t *hdr = flecs_switch_get_header(sw, values[last_elem]);
|
||||
if (hdr && hdr->element != -1) {
|
||||
ecs_assert(hdr->element == last_elem,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
hdr->element = elem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove element from arrays */
|
||||
ecs_vec_remove_t(&sw->nodes, ecs_switch_node_t, elem);
|
||||
ecs_vec_remove_t(&sw->values, uint64_t, elem);
|
||||
}
|
||||
|
||||
uint64_t flecs_switch_get(
|
||||
const ecs_switch_t *sw,
|
||||
int32_t element)
|
||||
{
|
||||
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
uint64_t *values = ecs_vec_first(&sw->values);
|
||||
return values[element];
|
||||
}
|
||||
|
||||
ecs_vec_t* flecs_switch_values(
|
||||
const ecs_switch_t *sw)
|
||||
{
|
||||
return ECS_CONST_CAST(ecs_vec_t*, &sw->values);
|
||||
}
|
||||
|
||||
int32_t flecs_switch_case_count(
|
||||
const ecs_switch_t *sw,
|
||||
uint64_t value)
|
||||
{
|
||||
ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value);
|
||||
if (!hdr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return hdr->count;
|
||||
}
|
||||
|
||||
void flecs_switch_swap(
|
||||
ecs_switch_t *sw,
|
||||
int32_t elem_1,
|
||||
int32_t elem_2)
|
||||
{
|
||||
uint64_t v1 = flecs_switch_get(sw, elem_1);
|
||||
uint64_t v2 = flecs_switch_get(sw, elem_2);
|
||||
|
||||
flecs_switch_set(sw, elem_2, v1);
|
||||
flecs_switch_set(sw, elem_1, v2);
|
||||
}
|
||||
|
||||
int32_t flecs_switch_first(
|
||||
const ecs_switch_t *sw,
|
||||
uint64_t value)
|
||||
{
|
||||
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value);
|
||||
if (!hdr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return hdr->element;
|
||||
}
|
||||
|
||||
int32_t flecs_switch_next(
|
||||
const ecs_switch_t *sw,
|
||||
int32_t element)
|
||||
{
|
||||
ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes);
|
||||
|
||||
return nodes[element].next;
|
||||
}
|
||||
296
engine/libs/flecs/src/datastructures/vec.c
Normal file
296
engine/libs/flecs/src/datastructures/vec.c
Normal file
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* @file datastructures/vec.c
|
||||
* @brief Vector with allocator support.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
ecs_vec_t* ecs_vec_init(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size,
|
||||
int32_t elem_count)
|
||||
{
|
||||
ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
v->array = NULL;
|
||||
v->count = 0;
|
||||
if (elem_count) {
|
||||
if (allocator) {
|
||||
v->array = flecs_alloc(allocator, size * elem_count);
|
||||
} else {
|
||||
v->array = ecs_os_malloc(size * elem_count);
|
||||
}
|
||||
}
|
||||
v->size = elem_count;
|
||||
#ifdef FLECS_SANITIZE
|
||||
v->elem_size = size;
|
||||
#endif
|
||||
return v;
|
||||
}
|
||||
|
||||
void ecs_vec_init_if(
|
||||
ecs_vec_t *vec,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_san_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)vec;
|
||||
(void)size;
|
||||
#ifdef FLECS_SANITIZE
|
||||
if (!vec->elem_size) {
|
||||
ecs_assert(vec->count == 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(vec->size == 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(vec->array == NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
vec->elem_size = size;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ecs_vec_fini(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size)
|
||||
{
|
||||
if (v->array) {
|
||||
ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
if (allocator) {
|
||||
flecs_free(allocator, size * v->size, v->array);
|
||||
} else {
|
||||
ecs_os_free(v->array);
|
||||
}
|
||||
v->array = NULL;
|
||||
v->count = 0;
|
||||
v->size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_vec_t* ecs_vec_reset(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size)
|
||||
{
|
||||
if (!v->size) {
|
||||
ecs_vec_init(allocator, v, size, 0);
|
||||
} else {
|
||||
ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_vec_clear(v);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void ecs_vec_clear(
|
||||
ecs_vec_t *vec)
|
||||
{
|
||||
vec->count = 0;
|
||||
}
|
||||
|
||||
ecs_vec_t ecs_vec_copy(
|
||||
ecs_allocator_t *allocator,
|
||||
const ecs_vec_t *v,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
void *array;
|
||||
if (allocator) {
|
||||
array = flecs_dup(allocator, size * v->size, v->array);
|
||||
} else {
|
||||
array = ecs_os_memdup(v->array, size * v->size);
|
||||
}
|
||||
return (ecs_vec_t) {
|
||||
.count = v->count,
|
||||
.size = v->size,
|
||||
.array = array
|
||||
#ifdef FLECS_SANITIZE
|
||||
, .elem_size = size
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
void ecs_vec_reclaim(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
int32_t count = v->count;
|
||||
if (count < v->size) {
|
||||
if (count) {
|
||||
if (allocator) {
|
||||
v->array = flecs_realloc(
|
||||
allocator, size * count, size * v->size, v->array);
|
||||
} else {
|
||||
v->array = ecs_os_realloc(v->array, size * count);
|
||||
}
|
||||
v->size = count;
|
||||
} else {
|
||||
ecs_vec_fini(allocator, v, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_vec_set_size(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size,
|
||||
int32_t elem_count)
|
||||
{
|
||||
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
if (v->size != elem_count) {
|
||||
if (elem_count < v->count) {
|
||||
elem_count = v->count;
|
||||
}
|
||||
|
||||
elem_count = flecs_next_pow_of_2(elem_count);
|
||||
if (elem_count < 2) {
|
||||
elem_count = 2;
|
||||
}
|
||||
if (elem_count != v->size) {
|
||||
if (allocator) {
|
||||
v->array = flecs_realloc(
|
||||
allocator, size * elem_count, size * v->size, v->array);
|
||||
} else {
|
||||
v->array = ecs_os_realloc(v->array, size * elem_count);
|
||||
}
|
||||
v->size = elem_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_vec_set_min_size(
|
||||
struct ecs_allocator_t *allocator,
|
||||
ecs_vec_t *vec,
|
||||
ecs_size_t size,
|
||||
int32_t elem_count)
|
||||
{
|
||||
if (elem_count > vec->size) {
|
||||
ecs_vec_set_size(allocator, vec, size, elem_count);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_vec_set_min_count(
|
||||
struct ecs_allocator_t *allocator,
|
||||
ecs_vec_t *vec,
|
||||
ecs_size_t size,
|
||||
int32_t elem_count)
|
||||
{
|
||||
ecs_vec_set_min_size(allocator, vec, size, elem_count);
|
||||
if (vec->count < elem_count) {
|
||||
vec->count = elem_count;
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_vec_set_min_count_zeromem(
|
||||
struct ecs_allocator_t *allocator,
|
||||
ecs_vec_t *vec,
|
||||
ecs_size_t size,
|
||||
int32_t elem_count)
|
||||
{
|
||||
int32_t count = vec->count;
|
||||
if (count < elem_count) {
|
||||
ecs_vec_set_min_count(allocator, vec, size, elem_count);
|
||||
ecs_os_memset(ECS_ELEM(vec->array, size, count), 0,
|
||||
size * (elem_count - count));
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_vec_set_count(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size,
|
||||
int32_t elem_count)
|
||||
{
|
||||
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
if (v->count != elem_count) {
|
||||
if (v->size < elem_count) {
|
||||
ecs_vec_set_size(allocator, v, size, elem_count);
|
||||
}
|
||||
|
||||
v->count = elem_count;
|
||||
}
|
||||
}
|
||||
|
||||
void* ecs_vec_grow(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size,
|
||||
int32_t elem_count)
|
||||
{
|
||||
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL);
|
||||
int32_t count = v->count;
|
||||
ecs_vec_set_count(allocator, v, size, count + elem_count);
|
||||
return ECS_ELEM(v->array, size, count);
|
||||
}
|
||||
|
||||
void* ecs_vec_append(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
int32_t count = v->count;
|
||||
if (v->size == count) {
|
||||
ecs_vec_set_size(allocator, v, size, count + 1);
|
||||
}
|
||||
v->count = count + 1;
|
||||
return ECS_ELEM(v->array, size, count);
|
||||
}
|
||||
|
||||
void ecs_vec_remove(
|
||||
ecs_vec_t *v,
|
||||
ecs_size_t size,
|
||||
int32_t index)
|
||||
{
|
||||
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL);
|
||||
if (index == --v->count) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_os_memcpy(
|
||||
ECS_ELEM(v->array, size, index),
|
||||
ECS_ELEM(v->array, size, v->count),
|
||||
size);
|
||||
}
|
||||
|
||||
void ecs_vec_remove_last(
|
||||
ecs_vec_t *v)
|
||||
{
|
||||
v->count --;
|
||||
}
|
||||
|
||||
int32_t ecs_vec_count(
|
||||
const ecs_vec_t *v)
|
||||
{
|
||||
return v->count;
|
||||
}
|
||||
|
||||
int32_t ecs_vec_size(
|
||||
const ecs_vec_t *v)
|
||||
{
|
||||
return v->size;
|
||||
}
|
||||
|
||||
void* ecs_vec_get(
|
||||
const ecs_vec_t *v,
|
||||
ecs_size_t size,
|
||||
int32_t index)
|
||||
{
|
||||
ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL);
|
||||
return ECS_ELEM(v->array, size, index);
|
||||
}
|
||||
|
||||
void* ecs_vec_last(
|
||||
const ecs_vec_t *v,
|
||||
ecs_size_t size)
|
||||
{
|
||||
ecs_san_assert(!v->elem_size || size == v->elem_size,
|
||||
ECS_INVALID_PARAMETER, NULL);
|
||||
return ECS_ELEM(v->array, size, v->count - 1);
|
||||
}
|
||||
|
||||
void* ecs_vec_first(
|
||||
const ecs_vec_t *v)
|
||||
{
|
||||
return v->array;
|
||||
}
|
||||
4927
engine/libs/flecs/src/entity.c
Normal file
4927
engine/libs/flecs/src/entity.c
Normal file
File diff suppressed because it is too large
Load Diff
650
engine/libs/flecs/src/entity_filter.c
Normal file
650
engine/libs/flecs/src/entity_filter.c
Normal file
@@ -0,0 +1,650 @@
|
||||
/**
|
||||
* @file entity_filter.c
|
||||
* @brief Filters that are applied to entities in a table.
|
||||
*
|
||||
* After a table has been matched by a query, additional filters may have to
|
||||
* be applied before returning entities to the application. The two scenarios
|
||||
* under which this happens are queries for union relationship pairs (entities
|
||||
* for multiple targets are stored in the same table) and toggles (components
|
||||
* that are enabled/disabled with a bitset).
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
|
||||
static
|
||||
int flecs_entity_filter_find_smallest_term(
|
||||
ecs_table_t *table,
|
||||
ecs_entity_filter_iter_t *iter)
|
||||
{
|
||||
ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
flecs_switch_term_t *sw_terms = ecs_vec_first(&iter->entity_filter->sw_terms);
|
||||
int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms);
|
||||
int32_t min = INT_MAX, index = 0;
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
/* The array with sparse queries for the matched table */
|
||||
flecs_switch_term_t *sparse_column = &sw_terms[i];
|
||||
|
||||
/* Pointer to the switch column struct of the table */
|
||||
ecs_switch_t *sw = sparse_column->sw_column;
|
||||
|
||||
/* If the sparse column pointer hadn't been retrieved yet, do it now */
|
||||
if (!sw) {
|
||||
/* Get the table column index from the signature column index */
|
||||
int32_t table_column_index = iter->columns[
|
||||
sparse_column->signature_column_index];
|
||||
|
||||
/* Translate the table column index to switch column index */
|
||||
table_column_index -= table->_->sw_offset;
|
||||
ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* Get the sparse column */
|
||||
sw = sparse_column->sw_column =
|
||||
&table->_->sw_columns[table_column_index - 1];
|
||||
}
|
||||
|
||||
/* Find the smallest column */
|
||||
int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case);
|
||||
if (case_count < min) {
|
||||
min = case_count;
|
||||
index = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static
|
||||
int flecs_entity_filter_switch_next(
|
||||
ecs_table_t *table,
|
||||
ecs_entity_filter_iter_t *iter,
|
||||
bool filter)
|
||||
{
|
||||
bool first_iteration = false;
|
||||
int32_t switch_smallest;
|
||||
|
||||
if (!(switch_smallest = iter->sw_smallest)) {
|
||||
switch_smallest = iter->sw_smallest =
|
||||
flecs_entity_filter_find_smallest_term(table, iter);
|
||||
first_iteration = true;
|
||||
}
|
||||
|
||||
switch_smallest -= 1;
|
||||
|
||||
flecs_switch_term_t *columns = ecs_vec_first(&iter->entity_filter->sw_terms);
|
||||
flecs_switch_term_t *column = &columns[switch_smallest];
|
||||
ecs_switch_t *sw, *sw_smallest = column->sw_column;
|
||||
ecs_entity_t case_smallest = column->sw_case;
|
||||
|
||||
/* Find next entity to iterate in sparse column */
|
||||
int32_t first, sparse_first = iter->sw_offset;
|
||||
|
||||
if (!filter) {
|
||||
if (first_iteration) {
|
||||
first = flecs_switch_first(sw_smallest, case_smallest);
|
||||
} else {
|
||||
first = flecs_switch_next(sw_smallest, sparse_first);
|
||||
}
|
||||
} else {
|
||||
int32_t cur_first = iter->range.offset, cur_count = iter->range.count;
|
||||
first = cur_first;
|
||||
while (flecs_switch_get(sw_smallest, first) != case_smallest) {
|
||||
first ++;
|
||||
if (first >= (cur_first + cur_count)) {
|
||||
first = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first == -1) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Check if entity matches with other sparse columns, if any */
|
||||
int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms);
|
||||
do {
|
||||
for (i = 0; i < count; i ++) {
|
||||
if (i == switch_smallest) {
|
||||
/* Already validated this one */
|
||||
continue;
|
||||
}
|
||||
|
||||
column = &columns[i];
|
||||
sw = column->sw_column;
|
||||
|
||||
if (flecs_switch_get(sw, first) != column->sw_case) {
|
||||
first = flecs_switch_next(sw_smallest, first);
|
||||
if (first == -1) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (i != count);
|
||||
|
||||
iter->range.offset = iter->sw_offset = first;
|
||||
iter->range.count = 1;
|
||||
|
||||
return 0;
|
||||
done:
|
||||
/* Iterated all elements in the sparse list, we should move to the
|
||||
* next matched table. */
|
||||
iter->sw_smallest = 0;
|
||||
iter->sw_offset = 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF)
|
||||
|
||||
static
|
||||
int flecs_entity_filter_bitset_next(
|
||||
ecs_table_t *table,
|
||||
ecs_entity_filter_iter_t *iter)
|
||||
{
|
||||
/* Precomputed single-bit test */
|
||||
static const uint64_t bitmask[64] = {
|
||||
(uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3,
|
||||
(uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7,
|
||||
(uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11,
|
||||
(uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15,
|
||||
(uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19,
|
||||
(uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23,
|
||||
(uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27,
|
||||
(uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31,
|
||||
(uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35,
|
||||
(uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39,
|
||||
(uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43,
|
||||
(uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47,
|
||||
(uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51,
|
||||
(uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55,
|
||||
(uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59,
|
||||
(uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63
|
||||
};
|
||||
|
||||
/* Precomputed test to verify if remainder of block is set (or not) */
|
||||
static const uint64_t bitmask_remain[64] = {
|
||||
BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62),
|
||||
BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59),
|
||||
BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56),
|
||||
BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53),
|
||||
BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50),
|
||||
BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47),
|
||||
BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44),
|
||||
BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41),
|
||||
BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38),
|
||||
BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35),
|
||||
BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32),
|
||||
BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29),
|
||||
BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26),
|
||||
BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23),
|
||||
BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20),
|
||||
BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17),
|
||||
BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14),
|
||||
BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11),
|
||||
BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8),
|
||||
BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5),
|
||||
BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2),
|
||||
BS_MAX - (BS_MAX >> 1)
|
||||
};
|
||||
|
||||
int32_t i, count = ecs_vec_count(&iter->entity_filter->bs_terms);
|
||||
flecs_bitset_term_t *terms = ecs_vec_first(&iter->entity_filter->bs_terms);
|
||||
int32_t bs_offset = table->_->bs_offset;
|
||||
int32_t first = iter->bs_offset;
|
||||
int32_t last = 0;
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
flecs_bitset_term_t *column = &terms[i];
|
||||
ecs_bitset_t *bs = terms[i].bs_column;
|
||||
|
||||
if (!bs) {
|
||||
int32_t index = column->column_index;
|
||||
ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL);
|
||||
bs = &table->_->bs_columns[index - bs_offset];
|
||||
terms[i].bs_column = bs;
|
||||
}
|
||||
|
||||
int32_t bs_elem_count = bs->count;
|
||||
int32_t bs_block = first >> 6;
|
||||
int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1;
|
||||
|
||||
if (bs_block >= bs_block_count) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
uint64_t *data = bs->data;
|
||||
int32_t bs_start = first & 0x3F;
|
||||
|
||||
/* Step 1: find the first non-empty block */
|
||||
uint64_t v = data[bs_block];
|
||||
uint64_t remain = bitmask_remain[bs_start];
|
||||
while (!(v & remain)) {
|
||||
/* If no elements are remaining, move to next block */
|
||||
if ((++bs_block) >= bs_block_count) {
|
||||
/* No non-empty blocks left */
|
||||
goto done;
|
||||
}
|
||||
|
||||
bs_start = 0;
|
||||
remain = BS_MAX; /* Test the full block */
|
||||
v = data[bs_block];
|
||||
}
|
||||
|
||||
/* Step 2: find the first non-empty element in the block */
|
||||
while (!(v & bitmask[bs_start])) {
|
||||
bs_start ++;
|
||||
|
||||
/* Block was not empty, so bs_start must be smaller than 64 */
|
||||
ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
/* Step 3: Find number of contiguous enabled elements after start */
|
||||
int32_t bs_end = bs_start, bs_block_end = bs_block;
|
||||
|
||||
remain = bitmask_remain[bs_end];
|
||||
while ((v & remain) == remain) {
|
||||
bs_end = 0;
|
||||
bs_block_end ++;
|
||||
|
||||
if (bs_block_end == bs_block_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
v = data[bs_block_end];
|
||||
remain = BS_MAX; /* Test the full block */
|
||||
}
|
||||
|
||||
/* Step 4: find remainder of enabled elements in current block */
|
||||
if (bs_block_end != bs_block_count) {
|
||||
while ((v & bitmask[bs_end])) {
|
||||
bs_end ++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Block was not 100% occupied, so bs_start must be smaller than 64 */
|
||||
ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* Step 5: translate to element start/end and make sure that each column
|
||||
* range is a subset of the previous one. */
|
||||
first = bs_block * 64 + bs_start;
|
||||
int32_t cur_last = bs_block_end * 64 + bs_end;
|
||||
|
||||
/* No enabled elements found in table */
|
||||
if (first == cur_last) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* If multiple bitsets are evaluated, make sure each subsequent range
|
||||
* is equal or a subset of the previous range */
|
||||
if (i) {
|
||||
/* If the first element of a subsequent bitset is larger than the
|
||||
* previous last value, start over. */
|
||||
if (first >= last) {
|
||||
i = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Make sure the last element of the range doesn't exceed the last
|
||||
* element of the previous range. */
|
||||
if (cur_last > last) {
|
||||
cur_last = last;
|
||||
}
|
||||
}
|
||||
|
||||
last = cur_last;
|
||||
int32_t elem_count = last - first;
|
||||
|
||||
/* Make sure last element doesn't exceed total number of elements in
|
||||
* the table */
|
||||
if (elem_count > (bs_elem_count - first)) {
|
||||
elem_count = (bs_elem_count - first);
|
||||
if (!elem_count) {
|
||||
iter->bs_offset = 0;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
iter->range.offset = first;
|
||||
iter->range.count = elem_count;
|
||||
iter->bs_offset = first;
|
||||
}
|
||||
|
||||
/* Keep track of last processed element for iteration */
|
||||
iter->bs_offset = last;
|
||||
|
||||
return 0;
|
||||
done:
|
||||
iter->sw_smallest = 0;
|
||||
iter->sw_offset = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef BS_MAX
|
||||
|
||||
static
|
||||
int32_t flecs_get_flattened_target(
|
||||
ecs_world_t *world,
|
||||
EcsTarget *cur,
|
||||
ecs_entity_t rel,
|
||||
ecs_id_t id,
|
||||
ecs_entity_t *src_out,
|
||||
ecs_table_record_t **tr_out)
|
||||
{
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ecs_record_t *r = cur->target;
|
||||
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_table_t *table = r->table;
|
||||
if (!table) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
|
||||
if (tr) {
|
||||
*src_out = ecs_record_get_entity(r);
|
||||
*tr_out = tr;
|
||||
return tr->index;
|
||||
}
|
||||
|
||||
if (table->flags & EcsTableHasTarget) {
|
||||
int32_t col = table->column_map[table->_->ft_offset];
|
||||
ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL);
|
||||
EcsTarget *next = table->data.columns[col].data.array;
|
||||
next = ECS_ELEM_T(next, EcsTarget, ECS_RECORD_TO_ROW(r->row));
|
||||
return flecs_get_flattened_target(
|
||||
world, next, rel, id, src_out, tr_out);
|
||||
}
|
||||
|
||||
return ecs_search_relation(
|
||||
world, table, 0, id, rel, EcsSelf|EcsUp, src_out, NULL, tr_out);
|
||||
}
|
||||
|
||||
void flecs_entity_filter_init(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_filter_t **entity_filter,
|
||||
const ecs_filter_t *filter,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t *ids,
|
||||
int32_t *columns)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(entity_filter != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_allocator_t *a = &world->allocator;
|
||||
ecs_entity_filter_t ef;
|
||||
ecs_os_zeromem(&ef);
|
||||
ecs_vec_t *sw_terms = &ef.sw_terms;
|
||||
ecs_vec_t *bs_terms = &ef.bs_terms;
|
||||
ecs_vec_t *ft_terms = &ef.ft_terms;
|
||||
if (*entity_filter) {
|
||||
ef.sw_terms = (*entity_filter)->sw_terms;
|
||||
ef.bs_terms = (*entity_filter)->bs_terms;
|
||||
ef.ft_terms = (*entity_filter)->ft_terms;
|
||||
}
|
||||
ecs_vec_reset_t(a, sw_terms, flecs_switch_term_t);
|
||||
ecs_vec_reset_t(a, bs_terms, flecs_bitset_term_t);
|
||||
ecs_vec_reset_t(a, ft_terms, flecs_flat_table_term_t);
|
||||
ecs_term_t *terms = filter->terms;
|
||||
int32_t i, term_count = filter->term_count;
|
||||
bool has_filter = false;
|
||||
ef.flat_tree_column = -1;
|
||||
|
||||
/* Look for union fields */
|
||||
if (table->flags & EcsTableHasUnion) {
|
||||
for (i = 0; i < term_count; i ++) {
|
||||
if (ecs_term_match_0(&terms[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_id_t id = terms[i].id;
|
||||
if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) == EcsWildcard) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t field = terms[i].field_index;
|
||||
int32_t column = columns[field];
|
||||
if (column <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_id_t table_id = table->type.array[column - 1];
|
||||
if (ECS_PAIR_FIRST(table_id) != EcsUnion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
flecs_switch_term_t *el = ecs_vec_append_t(a, sw_terms,
|
||||
flecs_switch_term_t);
|
||||
el->signature_column_index = field;
|
||||
el->sw_case = ecs_pair_second(world, id);
|
||||
el->sw_column = NULL;
|
||||
ids[field] = id;
|
||||
has_filter = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Look for disabled fields */
|
||||
if (table->flags & EcsTableHasToggle) {
|
||||
for (i = 0; i < term_count; i ++) {
|
||||
if (ecs_term_match_0(&terms[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t field = terms[i].field_index;
|
||||
ecs_id_t id = ids[field];
|
||||
ecs_id_t bs_id = ECS_TOGGLE | id;
|
||||
int32_t bs_index = ecs_table_get_type_index(world, table, bs_id);
|
||||
|
||||
if (bs_index != -1) {
|
||||
flecs_bitset_term_t *bc = ecs_vec_append_t(a, bs_terms,
|
||||
flecs_bitset_term_t);
|
||||
bc->column_index = bs_index;
|
||||
bc->bs_column = NULL;
|
||||
has_filter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Look for flattened fields */
|
||||
if (table->flags & EcsTableHasTarget) {
|
||||
const ecs_table_record_t *tr = flecs_table_record_get(world, table,
|
||||
ecs_pair_t(EcsTarget, EcsWildcard));
|
||||
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
int32_t column = tr->index;
|
||||
ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_entity_t rel = ecs_pair_second(world, table->type.array[column]);
|
||||
|
||||
for (i = 0; i < term_count; i ++) {
|
||||
if (ecs_term_match_0(&terms[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (terms[i].src.trav == rel) {
|
||||
ef.flat_tree_column = table->column_map[column];
|
||||
ecs_assert(ef.flat_tree_column != -1,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
has_filter = true;
|
||||
|
||||
flecs_flat_table_term_t *term = ecs_vec_append_t(
|
||||
a, ft_terms, flecs_flat_table_term_t);
|
||||
term->field_index = terms[i].field_index;
|
||||
term->term = &terms[i];
|
||||
ecs_os_zeromem(&term->monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_filter) {
|
||||
if (!*entity_filter) {
|
||||
*entity_filter = ecs_os_malloc_t(ecs_entity_filter_t);
|
||||
}
|
||||
ecs_assert(*entity_filter != NULL, ECS_OUT_OF_MEMORY, NULL);
|
||||
**entity_filter = ef;
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_entity_filter_fini(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_filter_t *ef)
|
||||
{
|
||||
if (!ef) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_allocator_t *a = &world->allocator;
|
||||
|
||||
flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms);
|
||||
int32_t i, term_count = ecs_vec_count(&ef->ft_terms);
|
||||
for (i = 0; i < term_count; i ++) {
|
||||
ecs_vec_fini_t(NULL, &fields[i].monitor, flecs_flat_monitor_t);
|
||||
}
|
||||
|
||||
ecs_vec_fini_t(a, &ef->sw_terms, flecs_switch_term_t);
|
||||
ecs_vec_fini_t(a, &ef->bs_terms, flecs_bitset_term_t);
|
||||
ecs_vec_fini_t(a, &ef->ft_terms, flecs_flat_table_term_t);
|
||||
ecs_os_free(ef);
|
||||
}
|
||||
|
||||
int flecs_entity_filter_next(
|
||||
ecs_entity_filter_iter_t *it)
|
||||
{
|
||||
ecs_table_t *table = it->range.table;
|
||||
flecs_switch_term_t *sw_terms = ecs_vec_first(&it->entity_filter->sw_terms);
|
||||
flecs_bitset_term_t *bs_terms = ecs_vec_first(&it->entity_filter->bs_terms);
|
||||
ecs_entity_filter_t *ef = it->entity_filter;
|
||||
int32_t flat_tree_column = ef->flat_tree_column;
|
||||
ecs_table_range_t *range = &it->range;
|
||||
int32_t range_end = range->offset + range->count;
|
||||
int result = EcsIterNext;
|
||||
bool found = false;
|
||||
|
||||
do {
|
||||
found = false;
|
||||
|
||||
if (bs_terms) {
|
||||
if (flecs_entity_filter_bitset_next(table, it) == -1) {
|
||||
/* No more enabled components for table */
|
||||
it->bs_offset = 0;
|
||||
break;
|
||||
} else {
|
||||
result = EcsIterYield;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sw_terms) {
|
||||
if (flecs_entity_filter_switch_next(table, it, found) == -1) {
|
||||
/* No more elements in sparse column */
|
||||
if (found) {
|
||||
/* Try again */
|
||||
result = EcsIterNext;
|
||||
found = false;
|
||||
} else {
|
||||
/* Nothing found */
|
||||
it->bs_offset = 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
result = EcsIterYield;
|
||||
found = true;
|
||||
it->bs_offset = range->offset + range->count;
|
||||
}
|
||||
}
|
||||
|
||||
if (flat_tree_column != -1) {
|
||||
bool first_for_table = it->prev != table;
|
||||
ecs_iter_t *iter = it->it;
|
||||
ecs_world_t *world = iter->real_world;
|
||||
EcsTarget *ft = table->data.columns[flat_tree_column].data.array;
|
||||
int32_t ft_offset;
|
||||
int32_t ft_count;
|
||||
|
||||
if (first_for_table) {
|
||||
ft_offset = it->flat_tree_offset = range->offset;
|
||||
it->target_count = 1;
|
||||
} else {
|
||||
it->flat_tree_offset += ft[it->flat_tree_offset].count;
|
||||
ft_offset = it->flat_tree_offset;
|
||||
it->target_count ++;
|
||||
}
|
||||
|
||||
ecs_assert(ft_offset < ecs_table_count(table),
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
EcsTarget *cur = &ft[ft_offset];
|
||||
ft_count = cur->count;
|
||||
bool is_last = (ft_offset + ft_count) >= range_end;
|
||||
|
||||
int32_t i, field_count = ecs_vec_count(&ef->ft_terms);
|
||||
flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms);
|
||||
for (i = 0; i < field_count; i ++) {
|
||||
flecs_flat_table_term_t *field = &fields[i];
|
||||
ecs_vec_init_if_t(&field->monitor, flecs_flat_monitor_t);
|
||||
int32_t field_index = field->field_index;
|
||||
ecs_id_t id = it->it->ids[field_index];
|
||||
ecs_id_t flat_pair = table->type.array[flat_tree_column];
|
||||
ecs_entity_t rel = ECS_PAIR_FIRST(flat_pair);
|
||||
ecs_entity_t tgt;
|
||||
ecs_table_record_t *tr;
|
||||
int32_t tgt_col = flecs_get_flattened_target(
|
||||
world, cur, rel, id, &tgt, &tr);
|
||||
if (tgt_col != -1) {
|
||||
iter->sources[field_index] = tgt;
|
||||
iter->columns[field_index] = /* encode flattened field */
|
||||
-(iter->field_count + tgt_col + 1);
|
||||
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* Keep track of maximum value encountered in target table
|
||||
* dirty state so this doesn't have to be recomputed when
|
||||
* synchronizing the query monitor. */
|
||||
ecs_vec_set_min_count_zeromem_t(NULL, &field->monitor,
|
||||
flecs_flat_monitor_t, it->target_count);
|
||||
ecs_table_t *tgt_table = tr->hdr.table;
|
||||
int32_t *ds = flecs_table_get_dirty_state(world, tgt_table);
|
||||
ecs_assert(ds != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_vec_get_t(&field->monitor, flecs_flat_monitor_t,
|
||||
it->target_count - 1)->table_state = ds[tgt_col + 1];
|
||||
} else {
|
||||
if (field->term->oper == EcsOptional) {
|
||||
iter->columns[field_index] = 0;
|
||||
iter->ptrs[field_index] = NULL;
|
||||
} else {
|
||||
it->prev = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i != field_count) {
|
||||
if (is_last) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
found = true;
|
||||
if ((ft_offset + ft_count) == range_end) {
|
||||
result = EcsIterNextYield;
|
||||
} else {
|
||||
result = EcsIterYield;
|
||||
}
|
||||
}
|
||||
|
||||
range->offset = ft_offset;
|
||||
range->count = ft_count;
|
||||
it->prev = table;
|
||||
}
|
||||
} while (!found);
|
||||
|
||||
it->prev = table;
|
||||
|
||||
if (!found) {
|
||||
return EcsIterNext;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
691
engine/libs/flecs/src/entity_name.c
Normal file
691
engine/libs/flecs/src/entity_name.c
Normal file
@@ -0,0 +1,691 @@
|
||||
/**
|
||||
* @file entity_name.c
|
||||
* @brief Functions for working with named entities.
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
#include <ctype.h>
|
||||
|
||||
#define ECS_NAME_BUFFER_LENGTH (64)
|
||||
|
||||
static
|
||||
bool flecs_path_append(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
ecs_entity_t child,
|
||||
const char *sep,
|
||||
const char *prefix,
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_entity_t cur = 0;
|
||||
const char *name = NULL;
|
||||
ecs_size_t name_len = 0;
|
||||
|
||||
if (child && ecs_is_alive(world, child)) {
|
||||
cur = ecs_get_target(world, child, EcsChildOf, 0);
|
||||
if (cur) {
|
||||
ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL);
|
||||
if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) {
|
||||
flecs_path_append(world, parent, cur, sep, prefix, buf);
|
||||
if (!sep[1]) {
|
||||
ecs_strbuf_appendch(buf, sep[0]);
|
||||
} else {
|
||||
ecs_strbuf_appendstr(buf, sep);
|
||||
}
|
||||
}
|
||||
} else if (prefix && prefix[0]) {
|
||||
if (!prefix[1]) {
|
||||
ecs_strbuf_appendch(buf, prefix[0]);
|
||||
} else {
|
||||
ecs_strbuf_appendstr(buf, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
const EcsIdentifier *id = ecs_get_pair(
|
||||
world, child, EcsIdentifier, EcsName);
|
||||
if (id) {
|
||||
name = id->value;
|
||||
name_len = id->length;
|
||||
}
|
||||
}
|
||||
|
||||
if (name) {
|
||||
ecs_strbuf_appendstrn(buf, name, name_len);
|
||||
} else {
|
||||
ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child));
|
||||
}
|
||||
|
||||
return cur != 0;
|
||||
}
|
||||
|
||||
bool flecs_name_is_id(
|
||||
const char *name)
|
||||
{
|
||||
ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (!isdigit(name[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_size_t i, length = ecs_os_strlen(name);
|
||||
for (i = 1; i < length; i ++) {
|
||||
char ch = name[i];
|
||||
|
||||
if (!isdigit(ch)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i >= length;
|
||||
}
|
||||
|
||||
ecs_entity_t flecs_name_to_id(
|
||||
const ecs_world_t *world,
|
||||
const char *name)
|
||||
{
|
||||
int64_t result = atoll(name);
|
||||
ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_entity_t alive = ecs_get_alive(world, (ecs_entity_t)result);
|
||||
if (alive) {
|
||||
return alive;
|
||||
} else {
|
||||
if ((uint32_t)result == (uint64_t)result) {
|
||||
return (ecs_entity_t)result;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
ecs_entity_t flecs_get_builtin(
|
||||
const char *name)
|
||||
{
|
||||
if (name[0] == '.' && name[1] == '\0') {
|
||||
return EcsThis;
|
||||
} else if (name[0] == '*' && name[1] == '\0') {
|
||||
return EcsWildcard;
|
||||
} else if (name[0] == '_' && name[1] == '\0') {
|
||||
return EcsAny;
|
||||
} else if (name[0] == '$' && name[1] == '\0') {
|
||||
return EcsVariable;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_is_sep(
|
||||
const char **ptr,
|
||||
const char *sep)
|
||||
{
|
||||
ecs_size_t len = ecs_os_strlen(sep);
|
||||
|
||||
if (!ecs_os_strncmp(*ptr, sep, len)) {
|
||||
*ptr += len;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
const char* flecs_path_elem(
|
||||
const char *path,
|
||||
const char *sep,
|
||||
int32_t *len)
|
||||
{
|
||||
const char *ptr;
|
||||
char ch;
|
||||
int32_t template_nesting = 0;
|
||||
int32_t count = 0;
|
||||
|
||||
for (ptr = path; (ch = *ptr); ptr ++) {
|
||||
if (ch == '<') {
|
||||
template_nesting ++;
|
||||
} else if (ch == '>') {
|
||||
template_nesting --;
|
||||
}
|
||||
|
||||
ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path);
|
||||
|
||||
if (!template_nesting && flecs_is_sep(&ptr, sep)) {
|
||||
break;
|
||||
}
|
||||
|
||||
count ++;
|
||||
}
|
||||
|
||||
if (len) {
|
||||
*len = count;
|
||||
}
|
||||
|
||||
if (count) {
|
||||
return ptr;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_is_root_path(
|
||||
const char *path,
|
||||
const char *prefix)
|
||||
{
|
||||
if (prefix) {
|
||||
return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
ecs_entity_t flecs_get_parent_from_path(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
const char **path_ptr,
|
||||
const char *prefix,
|
||||
bool new_entity)
|
||||
{
|
||||
bool start_from_root = false;
|
||||
const char *path = *path_ptr;
|
||||
|
||||
if (flecs_is_root_path(path, prefix)) {
|
||||
path += ecs_os_strlen(prefix);
|
||||
parent = 0;
|
||||
start_from_root = true;
|
||||
}
|
||||
|
||||
if (!start_from_root && !parent && new_entity) {
|
||||
parent = ecs_get_scope(world);
|
||||
}
|
||||
|
||||
*path_ptr = path;
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_on_set_symbol(ecs_iter_t *it) {
|
||||
EcsIdentifier *n = ecs_field(it, EcsIdentifier, 1);
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
flecs_name_index_ensure(
|
||||
&world->symbols, e, n[i].value, n[i].length, n[i].hash);
|
||||
}
|
||||
}
|
||||
|
||||
void flecs_bootstrap_hierarchy(ecs_world_t *world) {
|
||||
ecs_observer(world, {
|
||||
.entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}),
|
||||
.filter.terms[0] = {
|
||||
.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol),
|
||||
.src.flags = EcsSelf
|
||||
},
|
||||
.callback = flecs_on_set_symbol,
|
||||
.events = {EcsOnSet},
|
||||
.yield_existing = true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* Public functions */
|
||||
|
||||
void ecs_get_path_w_sep_buf(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
ecs_entity_t child,
|
||||
const char *sep,
|
||||
const char *prefix,
|
||||
ecs_strbuf_t *buf)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
world = ecs_get_world(world);
|
||||
|
||||
if (child == EcsWildcard) {
|
||||
ecs_strbuf_appendch(buf, '*');
|
||||
return;
|
||||
}
|
||||
if (child == EcsAny) {
|
||||
ecs_strbuf_appendch(buf, '_');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sep) {
|
||||
sep = ".";
|
||||
}
|
||||
|
||||
if (!child || parent != child) {
|
||||
flecs_path_append(world, parent, child, sep, prefix, buf);
|
||||
} else {
|
||||
ecs_strbuf_appendstrn(buf, "", 0);
|
||||
}
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
char* ecs_get_path_w_sep(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
ecs_entity_t child,
|
||||
const char *sep,
|
||||
const char *prefix)
|
||||
{
|
||||
ecs_strbuf_t buf = ECS_STRBUF_INIT;
|
||||
ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf);
|
||||
return ecs_strbuf_get(&buf);
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_lookup_child(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
const char *name)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
world = ecs_get_world(world);
|
||||
|
||||
if (flecs_name_is_id(name)) {
|
||||
ecs_entity_t result = flecs_name_to_id(world, name);
|
||||
if (result && ecs_is_alive(world, result)) {
|
||||
if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) {
|
||||
return 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_id_t pair = ecs_childof(parent);
|
||||
ecs_hashmap_t *index = flecs_id_name_index_get(world, pair);
|
||||
if (index) {
|
||||
return flecs_name_index_find(index, name, 0, 0);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_lookup(
|
||||
const ecs_world_t *world,
|
||||
const char *name)
|
||||
{
|
||||
if (!name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
world = ecs_get_world(world);
|
||||
|
||||
ecs_entity_t e = flecs_get_builtin(name);
|
||||
if (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
if (flecs_name_is_id(name)) {
|
||||
return flecs_name_to_id(world, name);
|
||||
}
|
||||
|
||||
e = flecs_name_index_find(&world->aliases, name, 0, 0);
|
||||
if (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
return ecs_lookup_child(world, 0, name);
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_lookup_symbol(
|
||||
const ecs_world_t *world,
|
||||
const char *name,
|
||||
bool lookup_as_path,
|
||||
bool recursive)
|
||||
{
|
||||
if (!name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
world = ecs_get_world(world);
|
||||
|
||||
ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0);
|
||||
if (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
if (lookup_as_path) {
|
||||
return ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive);
|
||||
}
|
||||
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_lookup_path_w_sep(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
const char *path,
|
||||
const char *sep,
|
||||
const char *prefix,
|
||||
bool recursive)
|
||||
{
|
||||
if (!path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
const ecs_world_t *stage = world;
|
||||
world = ecs_get_world(world);
|
||||
|
||||
ecs_entity_t e = flecs_get_builtin(path);
|
||||
if (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
e = flecs_name_index_find(&world->aliases, path, 0, 0);
|
||||
if (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
char buff[ECS_NAME_BUFFER_LENGTH];
|
||||
const char *ptr, *ptr_start;
|
||||
char *elem = buff;
|
||||
int32_t len, size = ECS_NAME_BUFFER_LENGTH;
|
||||
ecs_entity_t cur;
|
||||
bool lookup_path_search = false;
|
||||
|
||||
const ecs_entity_t *lookup_path = ecs_get_lookup_path(stage);
|
||||
const ecs_entity_t *lookup_path_cur = lookup_path;
|
||||
while (lookup_path_cur && *lookup_path_cur) {
|
||||
lookup_path_cur ++;
|
||||
}
|
||||
|
||||
if (!sep) {
|
||||
sep = ".";
|
||||
}
|
||||
|
||||
parent = flecs_get_parent_from_path(stage, parent, &path, prefix, true);
|
||||
|
||||
if (!sep[0]) {
|
||||
return ecs_lookup_child(world, parent, path);
|
||||
}
|
||||
|
||||
retry:
|
||||
cur = parent;
|
||||
ptr_start = ptr = path;
|
||||
|
||||
while ((ptr = flecs_path_elem(ptr, sep, &len))) {
|
||||
if (len < size) {
|
||||
ecs_os_memcpy(elem, ptr_start, len);
|
||||
} else {
|
||||
if (size == ECS_NAME_BUFFER_LENGTH) {
|
||||
elem = NULL;
|
||||
}
|
||||
|
||||
elem = ecs_os_realloc(elem, len + 1);
|
||||
ecs_os_memcpy(elem, ptr_start, len);
|
||||
size = len + 1;
|
||||
}
|
||||
|
||||
elem[len] = '\0';
|
||||
ptr_start = ptr;
|
||||
|
||||
cur = ecs_lookup_child(world, cur, elem);
|
||||
if (!cur) {
|
||||
goto tail;
|
||||
}
|
||||
}
|
||||
|
||||
tail:
|
||||
if (!cur && recursive) {
|
||||
if (!lookup_path_search) {
|
||||
if (parent) {
|
||||
parent = ecs_get_target(world, parent, EcsChildOf, 0);
|
||||
goto retry;
|
||||
} else {
|
||||
lookup_path_search = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (lookup_path_search) {
|
||||
if (lookup_path_cur != lookup_path) {
|
||||
lookup_path_cur --;
|
||||
parent = lookup_path_cur[0];
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (elem != buff) {
|
||||
ecs_os_free(elem);
|
||||
}
|
||||
|
||||
return cur;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_set_scope(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t scope)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
||||
|
||||
ecs_entity_t cur = stage->scope;
|
||||
stage->scope = scope;
|
||||
|
||||
return cur;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_get_scope(
|
||||
const ecs_world_t *world)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
const ecs_stage_t *stage = flecs_stage_from_readonly_world(world);
|
||||
return stage->scope;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t* ecs_set_lookup_path(
|
||||
ecs_world_t *world,
|
||||
const ecs_entity_t *lookup_path)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
||||
|
||||
/* Safe: application owns lookup path */
|
||||
ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path);
|
||||
stage->lookup_path = lookup_path;
|
||||
|
||||
return cur;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_entity_t* ecs_get_lookup_path(
|
||||
const ecs_world_t *world)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
const ecs_stage_t *stage = flecs_stage_from_readonly_world(world);
|
||||
/* Safe: application owns lookup path */
|
||||
return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path);
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* ecs_set_name_prefix(
|
||||
ecs_world_t *world,
|
||||
const char *prefix)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
const char *old_prefix = world->info.name_prefix;
|
||||
world->info.name_prefix = prefix;
|
||||
return old_prefix;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_add_path(
|
||||
ecs_world_t *world,
|
||||
bool defer_suspend,
|
||||
ecs_entity_t parent,
|
||||
ecs_entity_t entity,
|
||||
const char *name)
|
||||
{
|
||||
ecs_suspend_readonly_state_t srs;
|
||||
ecs_world_t *real_world = NULL;
|
||||
if (defer_suspend) {
|
||||
real_world = flecs_suspend_readonly(world, &srs);
|
||||
ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
ecs_add_pair(world, entity, EcsChildOf, parent);
|
||||
}
|
||||
|
||||
ecs_set_name(world, entity, name);
|
||||
|
||||
if (defer_suspend) {
|
||||
flecs_resume_readonly(real_world, &srs);
|
||||
flecs_defer_path((ecs_stage_t*)world, parent, entity, name);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_add_path_w_sep(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t parent,
|
||||
const char *path,
|
||||
const char *sep,
|
||||
const char *prefix)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
if (!sep) {
|
||||
sep = ".";
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
if (!entity) {
|
||||
entity = ecs_new_id(world);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
ecs_add_pair(world, entity, EcsChildOf, entity);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
bool root_path = flecs_is_root_path(path, prefix);
|
||||
parent = flecs_get_parent_from_path(world, parent, &path, prefix, !entity);
|
||||
|
||||
char buff[ECS_NAME_BUFFER_LENGTH];
|
||||
const char *ptr = path;
|
||||
const char *ptr_start = path;
|
||||
char *elem = buff;
|
||||
int32_t len, size = ECS_NAME_BUFFER_LENGTH;
|
||||
|
||||
/* If we're in deferred/readonly mode suspend it, so that the name index is
|
||||
* immediately updated. Without this, we could create multiple entities for
|
||||
* the same name in a single command queue. */
|
||||
bool suspend_defer = ecs_poly_is(world, ecs_stage_t) &&
|
||||
(ecs_get_stage_count(world) <= 1);
|
||||
ecs_entity_t cur = parent;
|
||||
char *name = NULL;
|
||||
|
||||
if (sep[0]) {
|
||||
while ((ptr = flecs_path_elem(ptr, sep, &len))) {
|
||||
if (len < size) {
|
||||
ecs_os_memcpy(elem, ptr_start, len);
|
||||
} else {
|
||||
if (size == ECS_NAME_BUFFER_LENGTH) {
|
||||
elem = NULL;
|
||||
}
|
||||
|
||||
elem = ecs_os_realloc(elem, len + 1);
|
||||
ecs_os_memcpy(elem, ptr_start, len);
|
||||
size = len + 1;
|
||||
}
|
||||
|
||||
elem[len] = '\0';
|
||||
ptr_start = ptr;
|
||||
|
||||
ecs_entity_t e = ecs_lookup_child(world, cur, elem);
|
||||
if (!e) {
|
||||
if (name) {
|
||||
ecs_os_free(name);
|
||||
}
|
||||
|
||||
name = ecs_os_strdup(elem);
|
||||
|
||||
/* If this is the last entity in the path, use the provided id */
|
||||
bool last_elem = false;
|
||||
if (!flecs_path_elem(ptr, sep, NULL)) {
|
||||
e = entity;
|
||||
last_elem = true;
|
||||
}
|
||||
|
||||
if (!e) {
|
||||
if (last_elem) {
|
||||
ecs_entity_t prev = ecs_set_scope(world, 0);
|
||||
e = ecs_new(world, 0);
|
||||
ecs_set_scope(world, prev);
|
||||
} else {
|
||||
e = ecs_new_id(world);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cur && last_elem && root_path) {
|
||||
ecs_remove_pair(world, e, EcsChildOf, EcsWildcard);
|
||||
}
|
||||
|
||||
flecs_add_path(world, suspend_defer, cur, e, name);
|
||||
}
|
||||
|
||||
cur = e;
|
||||
}
|
||||
|
||||
if (entity && (cur != entity)) {
|
||||
ecs_throw(ECS_ALREADY_DEFINED, name);
|
||||
}
|
||||
|
||||
if (name) {
|
||||
ecs_os_free(name);
|
||||
}
|
||||
|
||||
if (elem != buff) {
|
||||
ecs_os_free(elem);
|
||||
}
|
||||
} else {
|
||||
flecs_add_path(world, suspend_defer, parent, entity, path);
|
||||
}
|
||||
|
||||
return cur;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_new_from_path_w_sep(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
const char *path,
|
||||
const char *sep,
|
||||
const char *prefix)
|
||||
{
|
||||
return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix);
|
||||
}
|
||||
3014
engine/libs/flecs/src/filter.c
Normal file
3014
engine/libs/flecs/src/filter.c
Normal file
File diff suppressed because it is too large
Load Diff
1106
engine/libs/flecs/src/iter.c
Normal file
1106
engine/libs/flecs/src/iter.c
Normal file
File diff suppressed because it is too large
Load Diff
53
engine/libs/flecs/src/iter.h
Normal file
53
engine/libs/flecs/src/iter.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file iter.h
|
||||
* @brief Iterator utilities.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_ITER_H
|
||||
#define FLECS_ITER_H
|
||||
|
||||
void flecs_iter_init(
|
||||
const ecs_world_t *world,
|
||||
ecs_iter_t *it,
|
||||
ecs_flags8_t fields);
|
||||
|
||||
void flecs_iter_validate(
|
||||
ecs_iter_t *it);
|
||||
|
||||
void flecs_iter_populate_data(
|
||||
ecs_world_t *world,
|
||||
ecs_iter_t *it,
|
||||
ecs_table_t *table,
|
||||
int32_t offset,
|
||||
int32_t count,
|
||||
void **ptrs);
|
||||
|
||||
bool flecs_iter_next_row(
|
||||
ecs_iter_t *it);
|
||||
|
||||
bool flecs_iter_next_instanced(
|
||||
ecs_iter_t *it,
|
||||
bool result);
|
||||
|
||||
void* flecs_iter_calloc(
|
||||
ecs_iter_t *it,
|
||||
ecs_size_t size,
|
||||
ecs_size_t align);
|
||||
|
||||
#define flecs_iter_calloc_t(it, T)\
|
||||
flecs_iter_calloc(it, ECS_SIZEOF(T), ECS_ALIGNOF(T))
|
||||
|
||||
#define flecs_iter_calloc_n(it, T, count)\
|
||||
flecs_iter_calloc(it, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T))
|
||||
|
||||
void flecs_iter_free(
|
||||
void *ptr,
|
||||
ecs_size_t size);
|
||||
|
||||
#define flecs_iter_free_t(ptr, T)\
|
||||
flecs_iter_free(ptr, ECS_SIZEOF(T))
|
||||
|
||||
#define flecs_iter_free_n(ptr, T, count)\
|
||||
flecs_iter_free(ptr, ECS_SIZEOF(T) * count)
|
||||
|
||||
#endif
|
||||
208
engine/libs/flecs/src/misc.c
Normal file
208
engine/libs/flecs/src/misc.c
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* @file misc.c
|
||||
* @brief Miscallaneous functions.
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifndef FLECS_NDEBUG
|
||||
static int64_t flecs_s_min[] = {
|
||||
[1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN };
|
||||
static int64_t flecs_s_max[] = {
|
||||
[1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX };
|
||||
static uint64_t flecs_u_max[] = {
|
||||
[1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX };
|
||||
|
||||
uint64_t flecs_ito_(
|
||||
size_t size,
|
||||
bool is_signed,
|
||||
bool lt_zero,
|
||||
uint64_t u,
|
||||
const char *err)
|
||||
{
|
||||
union {
|
||||
uint64_t u;
|
||||
int64_t s;
|
||||
} v;
|
||||
|
||||
v.u = u;
|
||||
|
||||
if (is_signed) {
|
||||
ecs_assert(v.s >= flecs_s_min[size], ECS_INVALID_CONVERSION, err);
|
||||
ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, err);
|
||||
} else {
|
||||
ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err);
|
||||
ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err);
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
#endif
|
||||
|
||||
int32_t flecs_next_pow_of_2(
|
||||
int32_t n)
|
||||
{
|
||||
n --;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
n ++;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Convert time to double */
|
||||
double ecs_time_to_double(
|
||||
ecs_time_t t)
|
||||
{
|
||||
double result;
|
||||
result = t.sec;
|
||||
return result + (double)t.nanosec / (double)1000000000;
|
||||
}
|
||||
|
||||
ecs_time_t ecs_time_sub(
|
||||
ecs_time_t t1,
|
||||
ecs_time_t t2)
|
||||
{
|
||||
ecs_time_t result;
|
||||
|
||||
if (t1.nanosec >= t2.nanosec) {
|
||||
result.nanosec = t1.nanosec - t2.nanosec;
|
||||
result.sec = t1.sec - t2.sec;
|
||||
} else {
|
||||
result.nanosec = t1.nanosec - t2.nanosec + 1000000000;
|
||||
result.sec = t1.sec - t2.sec - 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ecs_sleepf(
|
||||
double t)
|
||||
{
|
||||
if (t > 0) {
|
||||
int sec = (int)t;
|
||||
int nsec = (int)((t - sec) * 1000000000);
|
||||
ecs_os_sleep(sec, nsec);
|
||||
}
|
||||
}
|
||||
|
||||
double ecs_time_measure(
|
||||
ecs_time_t *start)
|
||||
{
|
||||
ecs_time_t stop, temp;
|
||||
ecs_os_get_time(&stop);
|
||||
temp = stop;
|
||||
stop = ecs_time_sub(stop, *start);
|
||||
*start = temp;
|
||||
return ecs_time_to_double(stop);
|
||||
}
|
||||
|
||||
void* ecs_os_memdup(
|
||||
const void *src,
|
||||
ecs_size_t size)
|
||||
{
|
||||
if (!src) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *dst = ecs_os_malloc(size);
|
||||
ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL);
|
||||
ecs_os_memcpy(dst, src, size);
|
||||
return dst;
|
||||
}
|
||||
|
||||
int flecs_entity_compare(
|
||||
ecs_entity_t e1,
|
||||
const void *ptr1,
|
||||
ecs_entity_t e2,
|
||||
const void *ptr2)
|
||||
{
|
||||
(void)ptr1;
|
||||
(void)ptr2;
|
||||
return (e1 > e2) - (e1 < e2);
|
||||
}
|
||||
|
||||
uint64_t flecs_string_hash(
|
||||
const void *ptr)
|
||||
{
|
||||
const ecs_hashed_string_t *str = ptr;
|
||||
ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
return str->hash;
|
||||
}
|
||||
|
||||
char* ecs_vasprintf(
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
ecs_size_t size = 0;
|
||||
char *result = NULL;
|
||||
va_list tmpa;
|
||||
|
||||
va_copy(tmpa, args);
|
||||
|
||||
size = vsnprintf(result, 0, fmt, tmpa);
|
||||
|
||||
va_end(tmpa);
|
||||
|
||||
if ((int32_t)size < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = (char *) ecs_os_malloc(size + 1);
|
||||
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_os_vsprintf(result, fmt, args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char* ecs_asprintf(
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
char *result = ecs_vasprintf(fmt, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
char* flecs_to_snake_case(const char *str) {
|
||||
int32_t upper_count = 0, len = 1;
|
||||
const char *ptr = str;
|
||||
char ch, *out, *out_ptr;
|
||||
|
||||
for (ptr = &str[1]; (ch = *ptr); ptr ++) {
|
||||
if (isupper(ch)) {
|
||||
upper_count ++;
|
||||
}
|
||||
len ++;
|
||||
}
|
||||
|
||||
out = out_ptr = ecs_os_malloc_n(char, len + upper_count + 1);
|
||||
for (ptr = str; (ch = *ptr); ptr ++) {
|
||||
if (isupper(ch)) {
|
||||
if ((ptr != str) && (out_ptr[-1] != '_')) {
|
||||
out_ptr[0] = '_';
|
||||
out_ptr ++;
|
||||
}
|
||||
out_ptr[0] = (char)tolower(ch);
|
||||
out_ptr ++;
|
||||
} else {
|
||||
out_ptr[0] = ch;
|
||||
out_ptr ++;
|
||||
}
|
||||
}
|
||||
|
||||
out_ptr[0] = '\0';
|
||||
|
||||
return out;
|
||||
}
|
||||
1417
engine/libs/flecs/src/observable.c
Normal file
1417
engine/libs/flecs/src/observable.c
Normal file
File diff suppressed because it is too large
Load Diff
66
engine/libs/flecs/src/observable.h
Normal file
66
engine/libs/flecs/src/observable.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @file observable.h
|
||||
* @brief Functions for sending events.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_OBSERVABLE_H
|
||||
#define FLECS_OBSERVABLE_H
|
||||
|
||||
ecs_event_record_t* flecs_event_record_get(
|
||||
const ecs_observable_t *o,
|
||||
ecs_entity_t event);
|
||||
|
||||
ecs_event_record_t* flecs_event_record_ensure(
|
||||
ecs_observable_t *o,
|
||||
ecs_entity_t event);
|
||||
|
||||
ecs_event_id_record_t* flecs_event_id_record_get(
|
||||
const ecs_event_record_t *er,
|
||||
ecs_id_t id);
|
||||
|
||||
ecs_event_id_record_t* flecs_event_id_record_ensure(
|
||||
ecs_world_t *world,
|
||||
ecs_event_record_t *er,
|
||||
ecs_id_t id);
|
||||
|
||||
void flecs_event_id_record_remove(
|
||||
ecs_event_record_t *er,
|
||||
ecs_id_t id);
|
||||
|
||||
void flecs_observable_init(
|
||||
ecs_observable_t *observable);
|
||||
|
||||
void flecs_observable_fini(
|
||||
ecs_observable_t *observable);
|
||||
|
||||
bool flecs_observers_exist(
|
||||
ecs_observable_t *observable,
|
||||
ecs_id_t id,
|
||||
ecs_entity_t event);
|
||||
|
||||
void flecs_observer_fini(
|
||||
ecs_observer_t *observer);
|
||||
|
||||
void flecs_emit(
|
||||
ecs_world_t *world,
|
||||
ecs_world_t *stage,
|
||||
ecs_event_desc_t *desc);
|
||||
|
||||
bool flecs_default_observer_next_callback(
|
||||
ecs_iter_t *it);
|
||||
|
||||
void flecs_observers_invoke(
|
||||
ecs_world_t *world,
|
||||
ecs_map_t *observers,
|
||||
ecs_iter_t *it,
|
||||
ecs_table_t *table,
|
||||
ecs_entity_t trav,
|
||||
int32_t evtx);
|
||||
|
||||
void flecs_emit_propagate_invalidate(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
int32_t offset,
|
||||
int32_t count);
|
||||
|
||||
#endif
|
||||
1027
engine/libs/flecs/src/observer.c
Normal file
1027
engine/libs/flecs/src/observer.c
Normal file
File diff suppressed because it is too large
Load Diff
532
engine/libs/flecs/src/os_api.c
Normal file
532
engine/libs/flecs/src/os_api.c
Normal file
@@ -0,0 +1,532 @@
|
||||
/**
|
||||
* @file os_api.c
|
||||
* @brief Operating system abstraction API.
|
||||
*
|
||||
* The OS API implements an overridable interface for implementing functions
|
||||
* that are operating system specific, in addition to a number of hooks which
|
||||
* allow for customization by the user, like logging.
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
void ecs_os_api_impl(ecs_os_api_t *api);
|
||||
|
||||
static bool ecs_os_api_initialized = false;
|
||||
static bool ecs_os_api_initializing = false;
|
||||
static int ecs_os_api_init_count = 0;
|
||||
|
||||
ecs_os_api_t ecs_os_api = {
|
||||
.flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors,
|
||||
.log_level_ = -1 /* Disable tracing by default, but log warnings/errors */
|
||||
};
|
||||
|
||||
int64_t ecs_os_api_malloc_count = 0;
|
||||
int64_t ecs_os_api_realloc_count = 0;
|
||||
int64_t ecs_os_api_calloc_count = 0;
|
||||
int64_t ecs_os_api_free_count = 0;
|
||||
|
||||
void ecs_os_set_api(
|
||||
ecs_os_api_t *os_api)
|
||||
{
|
||||
if (!ecs_os_api_initialized) {
|
||||
ecs_os_api = *os_api;
|
||||
ecs_os_api_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_os_api_t ecs_os_get_api(void) {
|
||||
return ecs_os_api;
|
||||
}
|
||||
|
||||
void ecs_os_init(void)
|
||||
{
|
||||
if (!ecs_os_api_initialized) {
|
||||
ecs_os_set_api_defaults();
|
||||
}
|
||||
|
||||
if (!(ecs_os_api_init_count ++)) {
|
||||
if (ecs_os_api.init_) {
|
||||
ecs_os_api.init_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_os_fini(void) {
|
||||
if (!--ecs_os_api_init_count) {
|
||||
if (ecs_os_api.fini_) {
|
||||
ecs_os_api.fini_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Assume every non-glibc Linux target has no execinfo.
|
||||
This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */
|
||||
#if defined(ECS_TARGET_LINUX) && !defined(__GLIBC__)
|
||||
#define HAVE_EXECINFO 0
|
||||
#elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID)
|
||||
#define HAVE_EXECINFO 1
|
||||
#else
|
||||
#define HAVE_EXECINFO 0
|
||||
#endif
|
||||
|
||||
#if HAVE_EXECINFO
|
||||
#include <execinfo.h>
|
||||
#define ECS_BT_BUF_SIZE 100
|
||||
|
||||
void flecs_dump_backtrace(
|
||||
void *stream)
|
||||
{
|
||||
int nptrs;
|
||||
void *buffer[ECS_BT_BUF_SIZE];
|
||||
char **strings;
|
||||
|
||||
nptrs = backtrace(buffer, ECS_BT_BUF_SIZE);
|
||||
|
||||
strings = backtrace_symbols(buffer, nptrs);
|
||||
if (strings == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int j = 1; j < nptrs; j++) {
|
||||
fprintf(stream, "%s\n", strings[j]);
|
||||
}
|
||||
|
||||
free(strings);
|
||||
}
|
||||
#else
|
||||
void flecs_dump_backtrace(
|
||||
void *stream)
|
||||
{
|
||||
(void)stream;
|
||||
}
|
||||
#endif
|
||||
#undef HAVE_EXECINFO_H
|
||||
|
||||
static
|
||||
void flecs_log_msg(
|
||||
int32_t level,
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *msg)
|
||||
{
|
||||
FILE *stream;
|
||||
if (level >= 0) {
|
||||
stream = stdout;
|
||||
} else {
|
||||
stream = stderr;
|
||||
}
|
||||
|
||||
bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors;
|
||||
bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp;
|
||||
bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta;
|
||||
|
||||
time_t now = 0;
|
||||
|
||||
if (deltatime) {
|
||||
now = time(NULL);
|
||||
int64_t delta = 0;
|
||||
if (ecs_os_api.log_last_timestamp_) {
|
||||
delta = now - ecs_os_api.log_last_timestamp_;
|
||||
}
|
||||
ecs_os_api.log_last_timestamp_ = (int64_t)now;
|
||||
|
||||
if (delta) {
|
||||
if (delta < 10) {
|
||||
fputs(" ", stream);
|
||||
}
|
||||
if (delta < 100) {
|
||||
fputs(" ", stream);
|
||||
}
|
||||
char time_buf[20];
|
||||
ecs_os_sprintf(time_buf, "%u", (uint32_t)delta);
|
||||
fputs("+", stream);
|
||||
fputs(time_buf, stream);
|
||||
fputs(" ", stream);
|
||||
} else {
|
||||
fputs(" ", stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp) {
|
||||
if (!now) {
|
||||
now = time(NULL);
|
||||
}
|
||||
char time_buf[20];
|
||||
ecs_os_sprintf(time_buf, "%u", (uint32_t)now);
|
||||
fputs(time_buf, stream);
|
||||
fputs(" ", stream);
|
||||
}
|
||||
|
||||
if (level >= 4) {
|
||||
if (use_colors) fputs(ECS_NORMAL, stream);
|
||||
fputs("jrnl", stream);
|
||||
} else if (level >= 0) {
|
||||
if (level == 0) {
|
||||
if (use_colors) fputs(ECS_MAGENTA, stream);
|
||||
} else {
|
||||
if (use_colors) fputs(ECS_GREY, stream);
|
||||
}
|
||||
fputs("info", stream);
|
||||
} else if (level == -2) {
|
||||
if (use_colors) fputs(ECS_YELLOW, stream);
|
||||
fputs("warning", stream);
|
||||
} else if (level == -3) {
|
||||
if (use_colors) fputs(ECS_RED, stream);
|
||||
fputs("error", stream);
|
||||
} else if (level == -4) {
|
||||
if (use_colors) fputs(ECS_RED, stream);
|
||||
fputs("fatal", stream);
|
||||
}
|
||||
|
||||
if (use_colors) fputs(ECS_NORMAL, stream);
|
||||
fputs(": ", stream);
|
||||
|
||||
if (level >= 0) {
|
||||
if (ecs_os_api.log_indent_) {
|
||||
char indent[32];
|
||||
int i, indent_count = ecs_os_api.log_indent_;
|
||||
if (indent_count > 15) indent_count = 15;
|
||||
|
||||
for (i = 0; i < indent_count; i ++) {
|
||||
indent[i * 2] = '|';
|
||||
indent[i * 2 + 1] = ' ';
|
||||
}
|
||||
|
||||
if (ecs_os_api.log_indent_ != indent_count) {
|
||||
indent[i * 2 - 2] = '+';
|
||||
}
|
||||
|
||||
indent[i * 2] = '\0';
|
||||
|
||||
fputs(indent, stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (level < 0) {
|
||||
if (file) {
|
||||
const char *file_ptr = strrchr(file, '/');
|
||||
if (!file_ptr) {
|
||||
file_ptr = strrchr(file, '\\');
|
||||
}
|
||||
|
||||
if (file_ptr) {
|
||||
file = file_ptr + 1;
|
||||
}
|
||||
|
||||
fputs(file, stream);
|
||||
fputs(": ", stream);
|
||||
}
|
||||
|
||||
if (line) {
|
||||
fprintf(stream, "%d: ", line);
|
||||
}
|
||||
}
|
||||
|
||||
fputs(msg, stream);
|
||||
|
||||
fputs("\n", stream);
|
||||
|
||||
if (level == -4) {
|
||||
flecs_dump_backtrace(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_os_dbg(
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *msg)
|
||||
{
|
||||
if (ecs_os_api.log_) {
|
||||
ecs_os_api.log_(1, file, line, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_os_trace(
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *msg)
|
||||
{
|
||||
if (ecs_os_api.log_) {
|
||||
ecs_os_api.log_(0, file, line, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_os_warn(
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *msg)
|
||||
{
|
||||
if (ecs_os_api.log_) {
|
||||
ecs_os_api.log_(-2, file, line, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_os_err(
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *msg)
|
||||
{
|
||||
if (ecs_os_api.log_) {
|
||||
ecs_os_api.log_(-3, file, line, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_os_fatal(
|
||||
const char *file,
|
||||
int32_t line,
|
||||
const char *msg)
|
||||
{
|
||||
if (ecs_os_api.log_) {
|
||||
ecs_os_api.log_(-4, file, line, msg);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void ecs_os_gettime(ecs_time_t *time) {
|
||||
ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL);
|
||||
|
||||
uint64_t now = ecs_os_now();
|
||||
uint64_t sec = now / 1000000000;
|
||||
|
||||
assert(sec < UINT32_MAX);
|
||||
assert((now - sec * 1000000000) < UINT32_MAX);
|
||||
|
||||
time->sec = (uint32_t)sec;
|
||||
time->nanosec = (uint32_t)(now - sec * 1000000000);
|
||||
}
|
||||
|
||||
static
|
||||
void* ecs_os_api_malloc(ecs_size_t size) {
|
||||
ecs_os_linc(&ecs_os_api_malloc_count);
|
||||
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL);
|
||||
return malloc((size_t)size);
|
||||
}
|
||||
|
||||
static
|
||||
void* ecs_os_api_calloc(ecs_size_t size) {
|
||||
ecs_os_linc(&ecs_os_api_calloc_count);
|
||||
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL);
|
||||
return calloc(1, (size_t)size);
|
||||
}
|
||||
|
||||
static
|
||||
void* ecs_os_api_realloc(void *ptr, ecs_size_t size) {
|
||||
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
if (ptr) {
|
||||
ecs_os_linc(&ecs_os_api_realloc_count);
|
||||
} else {
|
||||
/* If not actually reallocing, treat as malloc */
|
||||
ecs_os_linc(&ecs_os_api_malloc_count);
|
||||
}
|
||||
|
||||
return realloc(ptr, (size_t)size);
|
||||
}
|
||||
|
||||
static
|
||||
void ecs_os_api_free(void *ptr) {
|
||||
if (ptr) {
|
||||
ecs_os_linc(&ecs_os_api_free_count);
|
||||
}
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
static
|
||||
char* ecs_os_api_strdup(const char *str) {
|
||||
if (str) {
|
||||
int len = ecs_os_strlen(str);
|
||||
char *result = ecs_os_malloc(len + 1);
|
||||
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
|
||||
ecs_os_strcpy(result, str);
|
||||
return result;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_os_strset(char **str, const char *value) {
|
||||
char *old = str[0];
|
||||
str[0] = ecs_os_strdup(value);
|
||||
ecs_os_free(old);
|
||||
}
|
||||
|
||||
/* Replace dots with underscores */
|
||||
static
|
||||
char *module_file_base(const char *module, char sep) {
|
||||
char *base = ecs_os_strdup(module);
|
||||
ecs_size_t i, len = ecs_os_strlen(base);
|
||||
for (i = 0; i < len; i ++) {
|
||||
if (base[i] == '.') {
|
||||
base[i] = sep;
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
static
|
||||
char* ecs_os_api_module_to_dl(const char *module) {
|
||||
ecs_strbuf_t lib = ECS_STRBUF_INIT;
|
||||
|
||||
/* Best guess, use module name with underscores + OS library extension */
|
||||
char *file_base = module_file_base(module, '_');
|
||||
|
||||
# if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD)
|
||||
ecs_strbuf_appendlit(&lib, "lib");
|
||||
ecs_strbuf_appendstr(&lib, file_base);
|
||||
ecs_strbuf_appendlit(&lib, ".so");
|
||||
# elif defined(ECS_TARGET_DARWIN)
|
||||
ecs_strbuf_appendlit(&lib, "lib");
|
||||
ecs_strbuf_appendstr(&lib, file_base);
|
||||
ecs_strbuf_appendlit(&lib, ".dylib");
|
||||
# elif defined(ECS_TARGET_WINDOWS)
|
||||
ecs_strbuf_appendstr(&lib, file_base);
|
||||
ecs_strbuf_appendlit(&lib, ".dll");
|
||||
# endif
|
||||
|
||||
ecs_os_free(file_base);
|
||||
|
||||
return ecs_strbuf_get(&lib);
|
||||
}
|
||||
|
||||
static
|
||||
char* ecs_os_api_module_to_etc(const char *module) {
|
||||
ecs_strbuf_t lib = ECS_STRBUF_INIT;
|
||||
|
||||
/* Best guess, use module name with dashes + /etc */
|
||||
char *file_base = module_file_base(module, '-');
|
||||
|
||||
ecs_strbuf_appendstr(&lib, file_base);
|
||||
ecs_strbuf_appendlit(&lib, "/etc");
|
||||
|
||||
ecs_os_free(file_base);
|
||||
|
||||
return ecs_strbuf_get(&lib);
|
||||
}
|
||||
|
||||
void ecs_os_set_api_defaults(void)
|
||||
{
|
||||
/* Don't overwrite if already initialized */
|
||||
if (ecs_os_api_initialized != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ecs_os_api_initializing != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_os_api_initializing = true;
|
||||
|
||||
/* Memory management */
|
||||
ecs_os_api.malloc_ = ecs_os_api_malloc;
|
||||
ecs_os_api.free_ = ecs_os_api_free;
|
||||
ecs_os_api.realloc_ = ecs_os_api_realloc;
|
||||
ecs_os_api.calloc_ = ecs_os_api_calloc;
|
||||
|
||||
/* Strings */
|
||||
ecs_os_api.strdup_ = ecs_os_api_strdup;
|
||||
|
||||
/* Time */
|
||||
ecs_os_api.get_time_ = ecs_os_gettime;
|
||||
|
||||
/* Logging */
|
||||
ecs_os_api.log_ = flecs_log_msg;
|
||||
|
||||
/* Modules */
|
||||
if (!ecs_os_api.module_to_dl_) {
|
||||
ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl;
|
||||
}
|
||||
|
||||
if (!ecs_os_api.module_to_etc_) {
|
||||
ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc;
|
||||
}
|
||||
|
||||
ecs_os_api.abort_ = abort;
|
||||
|
||||
# ifdef FLECS_OS_API_IMPL
|
||||
/* Initialize defaults to OS API IMPL addon, but still allow for overriding
|
||||
* by the application */
|
||||
ecs_set_os_api_impl();
|
||||
ecs_os_api_initialized = false;
|
||||
# endif
|
||||
|
||||
ecs_os_api_initializing = false;
|
||||
}
|
||||
|
||||
bool ecs_os_has_heap(void) {
|
||||
return
|
||||
(ecs_os_api.malloc_ != NULL) &&
|
||||
(ecs_os_api.calloc_ != NULL) &&
|
||||
(ecs_os_api.realloc_ != NULL) &&
|
||||
(ecs_os_api.free_ != NULL);
|
||||
}
|
||||
|
||||
bool ecs_os_has_threading(void) {
|
||||
return
|
||||
(ecs_os_api.mutex_new_ != NULL) &&
|
||||
(ecs_os_api.mutex_free_ != NULL) &&
|
||||
(ecs_os_api.mutex_lock_ != NULL) &&
|
||||
(ecs_os_api.mutex_unlock_ != NULL) &&
|
||||
(ecs_os_api.cond_new_ != NULL) &&
|
||||
(ecs_os_api.cond_free_ != NULL) &&
|
||||
(ecs_os_api.cond_wait_ != NULL) &&
|
||||
(ecs_os_api.cond_signal_ != NULL) &&
|
||||
(ecs_os_api.cond_broadcast_ != NULL) &&
|
||||
(ecs_os_api.thread_new_ != NULL) &&
|
||||
(ecs_os_api.thread_join_ != NULL) &&
|
||||
(ecs_os_api.thread_self_ != NULL);
|
||||
}
|
||||
|
||||
bool ecs_os_has_task_support(void) {
|
||||
return
|
||||
(ecs_os_api.mutex_new_ != NULL) &&
|
||||
(ecs_os_api.mutex_free_ != NULL) &&
|
||||
(ecs_os_api.mutex_lock_ != NULL) &&
|
||||
(ecs_os_api.mutex_unlock_ != NULL) &&
|
||||
(ecs_os_api.cond_new_ != NULL) &&
|
||||
(ecs_os_api.cond_free_ != NULL) &&
|
||||
(ecs_os_api.cond_wait_ != NULL) &&
|
||||
(ecs_os_api.cond_signal_ != NULL) &&
|
||||
(ecs_os_api.cond_broadcast_ != NULL) &&
|
||||
(ecs_os_api.task_new_ != NULL) &&
|
||||
(ecs_os_api.task_join_ != NULL);
|
||||
}
|
||||
|
||||
bool ecs_os_has_time(void) {
|
||||
return
|
||||
(ecs_os_api.get_time_ != NULL) &&
|
||||
(ecs_os_api.sleep_ != NULL) &&
|
||||
(ecs_os_api.now_ != NULL);
|
||||
}
|
||||
|
||||
bool ecs_os_has_logging(void) {
|
||||
return (ecs_os_api.log_ != NULL);
|
||||
}
|
||||
|
||||
bool ecs_os_has_dl(void) {
|
||||
return
|
||||
(ecs_os_api.dlopen_ != NULL) &&
|
||||
(ecs_os_api.dlproc_ != NULL) &&
|
||||
(ecs_os_api.dlclose_ != NULL);
|
||||
}
|
||||
|
||||
bool ecs_os_has_modules(void) {
|
||||
return
|
||||
(ecs_os_api.module_to_dl_ != NULL) &&
|
||||
(ecs_os_api.module_to_etc_ != NULL);
|
||||
}
|
||||
|
||||
#if defined(ECS_TARGET_WINDOWS)
|
||||
static char error_str[255];
|
||||
#endif
|
||||
|
||||
const char* ecs_os_strerror(int err) {
|
||||
# if defined(ECS_TARGET_WINDOWS)
|
||||
strerror_s(error_str, 255, err);
|
||||
return error_str;
|
||||
# else
|
||||
return strerror(err);
|
||||
# endif
|
||||
}
|
||||
232
engine/libs/flecs/src/poly.c
Normal file
232
engine/libs/flecs/src/poly.c
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* @file poly.c
|
||||
* @brief Functions for managing poly objects.
|
||||
*
|
||||
* The poly framework makes it possible to generalize common functionality for
|
||||
* different kinds of API objects, as well as improved type safety checks. Poly
|
||||
* objects have a header that identifiers what kind of object it is. This can
|
||||
* then be used to discover a set of "mixins" implemented by the type.
|
||||
*
|
||||
* Mixins are like a vtable, but for members. Each type populates the table with
|
||||
* offsets to the members that correspond with the mixin. If an entry in the
|
||||
* mixin table is not set, the type does not support the mixin.
|
||||
*
|
||||
* An example is the Iterable mixin, which makes it possible to create an
|
||||
* iterator for any poly object (like filters, queries, the world) that
|
||||
* implements the Iterable mixin.
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
|
||||
static const char* mixin_kind_str[] = {
|
||||
[EcsMixinWorld] = "world",
|
||||
[EcsMixinEntity] = "entity",
|
||||
[EcsMixinObservable] = "observable",
|
||||
[EcsMixinIterable] = "iterable",
|
||||
[EcsMixinDtor] = "dtor",
|
||||
[EcsMixinMax] = "max (should never be requested by application)"
|
||||
};
|
||||
|
||||
ecs_mixins_t ecs_world_t_mixins = {
|
||||
.type_name = "ecs_world_t",
|
||||
.elems = {
|
||||
[EcsMixinWorld] = offsetof(ecs_world_t, self),
|
||||
[EcsMixinObservable] = offsetof(ecs_world_t, observable),
|
||||
[EcsMixinIterable] = offsetof(ecs_world_t, iterable)
|
||||
}
|
||||
};
|
||||
|
||||
ecs_mixins_t ecs_stage_t_mixins = {
|
||||
.type_name = "ecs_stage_t",
|
||||
.elems = {
|
||||
[EcsMixinWorld] = offsetof(ecs_stage_t, world)
|
||||
}
|
||||
};
|
||||
|
||||
ecs_mixins_t ecs_query_t_mixins = {
|
||||
.type_name = "ecs_query_t",
|
||||
.elems = {
|
||||
[EcsMixinWorld] = offsetof(ecs_query_t, filter.world),
|
||||
[EcsMixinEntity] = offsetof(ecs_query_t, filter.entity),
|
||||
[EcsMixinIterable] = offsetof(ecs_query_t, iterable),
|
||||
[EcsMixinDtor] = offsetof(ecs_query_t, dtor)
|
||||
}
|
||||
};
|
||||
|
||||
ecs_mixins_t ecs_observer_t_mixins = {
|
||||
.type_name = "ecs_observer_t",
|
||||
.elems = {
|
||||
[EcsMixinWorld] = offsetof(ecs_observer_t, filter.world),
|
||||
[EcsMixinEntity] = offsetof(ecs_observer_t, filter.entity),
|
||||
[EcsMixinDtor] = offsetof(ecs_observer_t, dtor)
|
||||
}
|
||||
};
|
||||
|
||||
ecs_mixins_t ecs_filter_t_mixins = {
|
||||
.type_name = "ecs_filter_t",
|
||||
.elems = {
|
||||
[EcsMixinWorld] = offsetof(ecs_filter_t, world),
|
||||
[EcsMixinEntity] = offsetof(ecs_filter_t, entity),
|
||||
[EcsMixinIterable] = offsetof(ecs_filter_t, iterable),
|
||||
[EcsMixinDtor] = offsetof(ecs_filter_t, dtor)
|
||||
}
|
||||
};
|
||||
|
||||
static
|
||||
void* assert_mixin(
|
||||
const ecs_poly_t *poly,
|
||||
ecs_mixin_kind_t kind)
|
||||
{
|
||||
ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
const ecs_header_t *hdr = poly;
|
||||
ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
const ecs_mixins_t *mixins = hdr->mixins;
|
||||
ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_size_t offset = mixins->elems[kind];
|
||||
ecs_assert(offset != 0, ECS_INVALID_PARAMETER,
|
||||
"mixin %s not available for type %s",
|
||||
mixin_kind_str[kind], mixins ? mixins->type_name : "unknown");
|
||||
(void)mixin_kind_str;
|
||||
|
||||
/* Object has mixin, return its address */
|
||||
return ECS_OFFSET(hdr, offset);
|
||||
}
|
||||
|
||||
void* ecs_poly_init_(
|
||||
ecs_poly_t *poly,
|
||||
int32_t type,
|
||||
ecs_size_t size,
|
||||
ecs_mixins_t *mixins)
|
||||
{
|
||||
ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_header_t *hdr = poly;
|
||||
ecs_os_memset(poly, 0, size);
|
||||
|
||||
hdr->magic = ECS_OBJECT_MAGIC;
|
||||
hdr->type = type;
|
||||
hdr->mixins = mixins;
|
||||
|
||||
return poly;
|
||||
}
|
||||
|
||||
void ecs_poly_fini_(
|
||||
ecs_poly_t *poly,
|
||||
int32_t type)
|
||||
{
|
||||
ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)type;
|
||||
|
||||
ecs_header_t *hdr = poly;
|
||||
|
||||
/* Don't deinit poly that wasn't initialized */
|
||||
ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL);
|
||||
hdr->magic = 0;
|
||||
}
|
||||
|
||||
EcsPoly* ecs_poly_bind_(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t tag)
|
||||
{
|
||||
/* Add tag to the entity for easy querying. This will make it possible to
|
||||
* query for `Query` instead of `(Poly, Query) */
|
||||
if (!ecs_has_id(world, entity, tag)) {
|
||||
ecs_add_id(world, entity, tag);
|
||||
}
|
||||
|
||||
/* Never defer creation of a poly object */
|
||||
bool deferred = false;
|
||||
if (ecs_is_deferred(world)) {
|
||||
deferred = true;
|
||||
ecs_defer_suspend(world);
|
||||
}
|
||||
|
||||
/* If this is a new poly, leave the actual creation up to the caller so they
|
||||
* call tell the difference between a create or an update */
|
||||
EcsPoly *result = ecs_get_mut_pair(world, entity, EcsPoly, tag);
|
||||
|
||||
if (deferred) {
|
||||
ecs_defer_resume(world);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ecs_poly_modified_(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t tag)
|
||||
{
|
||||
ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag);
|
||||
}
|
||||
|
||||
const EcsPoly* ecs_poly_bind_get_(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t tag)
|
||||
{
|
||||
return ecs_get_pair(world, entity, EcsPoly, tag);
|
||||
}
|
||||
|
||||
ecs_poly_t* ecs_poly_get_(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t tag)
|
||||
{
|
||||
const EcsPoly *p = ecs_poly_bind_get_(world, entity, tag);
|
||||
if (p) {
|
||||
return p->poly;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ecs_poly_is_(
|
||||
const ecs_poly_t *poly,
|
||||
int32_t type)
|
||||
{
|
||||
ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
const ecs_header_t *hdr = poly;
|
||||
ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL);
|
||||
return hdr->type == type;
|
||||
}
|
||||
|
||||
ecs_iterable_t* ecs_get_iterable(
|
||||
const ecs_poly_t *poly)
|
||||
{
|
||||
return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable);
|
||||
}
|
||||
|
||||
ecs_observable_t* ecs_get_observable(
|
||||
const ecs_poly_t *poly)
|
||||
{
|
||||
return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable);
|
||||
}
|
||||
|
||||
const ecs_world_t* ecs_get_world(
|
||||
const ecs_poly_t *poly)
|
||||
{
|
||||
if (((const ecs_header_t*)poly)->type == ecs_world_t_magic) {
|
||||
return poly;
|
||||
}
|
||||
return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld);
|
||||
}
|
||||
|
||||
ecs_entity_t ecs_get_entity(
|
||||
const ecs_poly_t *poly)
|
||||
{
|
||||
return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity);
|
||||
}
|
||||
|
||||
ecs_poly_dtor_t* ecs_get_dtor(
|
||||
const ecs_poly_t *poly)
|
||||
{
|
||||
return (ecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor);
|
||||
}
|
||||
95
engine/libs/flecs/src/poly.h
Normal file
95
engine/libs/flecs/src/poly.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @file poly.h
|
||||
* @brief Functions for managing poly objects.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_POLY_H
|
||||
#define FLECS_POLY_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* Initialize poly */
|
||||
void* ecs_poly_init_(
|
||||
ecs_poly_t *object,
|
||||
int32_t kind,
|
||||
ecs_size_t size,
|
||||
ecs_mixins_t *mixins);
|
||||
|
||||
#define ecs_poly_init(object, type)\
|
||||
ecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins)
|
||||
|
||||
/* Deinitialize object for specified type */
|
||||
void ecs_poly_fini_(
|
||||
ecs_poly_t *object,
|
||||
int32_t kind);
|
||||
|
||||
#define ecs_poly_fini(object, type)\
|
||||
ecs_poly_fini_(object, type##_magic)
|
||||
|
||||
/* Utility functions for creating an object on the heap */
|
||||
#define ecs_poly_new(type)\
|
||||
(type*)ecs_poly_init(ecs_os_calloc_t(type), type)
|
||||
|
||||
#define ecs_poly_free(obj, type)\
|
||||
ecs_poly_fini(obj, type);\
|
||||
ecs_os_free(obj)
|
||||
|
||||
/* Get or create poly component for an entity */
|
||||
EcsPoly* ecs_poly_bind_(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t tag);
|
||||
|
||||
#define ecs_poly_bind(world, entity, T) \
|
||||
ecs_poly_bind_(world, entity, T##_tag)
|
||||
|
||||
void ecs_poly_modified_(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t tag);
|
||||
|
||||
#define ecs_poly_modified(world, entity, T) \
|
||||
ecs_poly_modified_(world, entity, T##_tag)
|
||||
|
||||
/* Get poly component for an entity */
|
||||
const EcsPoly* ecs_poly_bind_get_(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t tag);
|
||||
|
||||
#define ecs_poly_bind_get(world, entity, T) \
|
||||
ecs_poly_bind_get_(world, entity, T##_tag)
|
||||
|
||||
ecs_poly_t* ecs_poly_get_(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t tag);
|
||||
|
||||
#define ecs_poly_get(world, entity, T) \
|
||||
((T*)ecs_poly_get_(world, entity, T##_tag))
|
||||
|
||||
/* Utilities for testing/asserting an object type */
|
||||
#ifndef FLECS_NDEBUG
|
||||
#define ecs_poly_assert(object, ty)\
|
||||
do {\
|
||||
ecs_assert(object != NULL, ECS_INVALID_PARAMETER, NULL);\
|
||||
const ecs_header_t *hdr = (const ecs_header_t *)object;\
|
||||
const char *type_name = hdr->mixins->type_name;\
|
||||
ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, type_name);\
|
||||
ecs_assert(hdr->type == ty##_magic, ECS_INVALID_PARAMETER, type_name);\
|
||||
} while (0)
|
||||
#else
|
||||
#define ecs_poly_assert(object, ty)
|
||||
#endif
|
||||
|
||||
/* Utility functions for getting a mixin from an object */
|
||||
ecs_iterable_t* ecs_get_iterable(
|
||||
const ecs_poly_t *poly);
|
||||
|
||||
ecs_observable_t* ecs_get_observable(
|
||||
const ecs_poly_t *object);
|
||||
|
||||
ecs_poly_dtor_t* ecs_get_dtor(
|
||||
const ecs_poly_t *poly);
|
||||
|
||||
#endif
|
||||
325
engine/libs/flecs/src/private_api.h
Normal file
325
engine/libs/flecs/src/private_api.h
Normal file
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* @file private_api.h
|
||||
* @brief Private functions.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_PRIVATE_H
|
||||
#define FLECS_PRIVATE_H
|
||||
|
||||
#include "private_types.h"
|
||||
#include "storage/table_cache.h"
|
||||
#include "storage/id_index.h"
|
||||
#include "observable.h"
|
||||
#include "iter.h"
|
||||
#include "poly.h"
|
||||
#include "stage.h"
|
||||
#include "world.h"
|
||||
#include "datastructures/name_index.h"
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Bootstrap API
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* Bootstrap world */
|
||||
void flecs_bootstrap(
|
||||
ecs_world_t *world);
|
||||
|
||||
#define flecs_bootstrap_component(world, id_)\
|
||||
ecs_component_init(world, &(ecs_component_desc_t){\
|
||||
.entity = ecs_entity(world, { .id = ecs_id(id_), .name = #id_, .symbol = #id_ }),\
|
||||
.type.size = sizeof(id_),\
|
||||
.type.alignment = ECS_ALIGNOF(id_)\
|
||||
});
|
||||
|
||||
#define flecs_bootstrap_tag(world, name)\
|
||||
ecs_ensure(world, name);\
|
||||
ecs_add_id(world, name, EcsFinal);\
|
||||
ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\
|
||||
ecs_set(world, name, EcsComponent, {.size = 0});\
|
||||
ecs_set_name(world, name, (const char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\
|
||||
ecs_set_symbol(world, name, #name)
|
||||
|
||||
/* Bootstrap functions for other parts in the code */
|
||||
void flecs_bootstrap_hierarchy(ecs_world_t *world);
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Entity API
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* Mark an entity as being watched. This is used to trigger automatic rematching
|
||||
* when entities used in system expressions change their components. */
|
||||
void flecs_add_flag(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
uint32_t flag);
|
||||
|
||||
void flecs_record_add_flag(
|
||||
ecs_record_t *record,
|
||||
uint32_t flag);
|
||||
|
||||
ecs_entity_t flecs_get_oneof(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t e);
|
||||
|
||||
void flecs_notify_on_remove(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_table_t *other_table,
|
||||
int32_t row,
|
||||
int32_t count,
|
||||
const ecs_type_t *diff);
|
||||
|
||||
void flecs_notify_on_set(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
int32_t row,
|
||||
int32_t count,
|
||||
ecs_type_t *type,
|
||||
bool owned);
|
||||
|
||||
int32_t flecs_relation_depth(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t r,
|
||||
const ecs_table_t *table);
|
||||
|
||||
void flecs_instantiate(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t base,
|
||||
ecs_table_t *table,
|
||||
int32_t row,
|
||||
int32_t count);
|
||||
|
||||
void* flecs_get_base_component(
|
||||
const ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_id_t id,
|
||||
ecs_id_record_t *table_index,
|
||||
int32_t recur_depth);
|
||||
|
||||
void flecs_invoke_hook(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
int32_t count,
|
||||
int32_t row,
|
||||
ecs_entity_t *entities,
|
||||
void *ptr,
|
||||
ecs_id_t id,
|
||||
const ecs_type_info_t *ti,
|
||||
ecs_entity_t event,
|
||||
ecs_iter_action_t hook);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Query API
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* Match table with term */
|
||||
bool flecs_term_match_table(
|
||||
ecs_world_t *world,
|
||||
const ecs_term_t *term,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t *id_out,
|
||||
int32_t *column_out,
|
||||
ecs_entity_t *subject_out,
|
||||
int32_t *match_indices,
|
||||
bool first,
|
||||
ecs_flags32_t iter_flags);
|
||||
|
||||
/* Match table with filter */
|
||||
bool flecs_filter_match_table(
|
||||
ecs_world_t *world,
|
||||
const ecs_filter_t *filter,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t *ids,
|
||||
int32_t *columns,
|
||||
ecs_entity_t *sources,
|
||||
int32_t *match_indices,
|
||||
int32_t *matches_left,
|
||||
bool first,
|
||||
int32_t skip_term,
|
||||
ecs_flags32_t iter_flags);
|
||||
|
||||
ecs_iter_t flecs_filter_iter_w_flags(
|
||||
const ecs_world_t *stage,
|
||||
const ecs_filter_t *filter,
|
||||
ecs_flags32_t flags);
|
||||
|
||||
void flecs_query_notify(
|
||||
ecs_world_t *world,
|
||||
ecs_query_t *query,
|
||||
ecs_query_event_t *event);
|
||||
|
||||
ecs_id_t flecs_to_public_id(
|
||||
ecs_id_t id);
|
||||
|
||||
ecs_id_t flecs_from_public_id(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t id);
|
||||
|
||||
void flecs_filter_apply_iter_flags(
|
||||
ecs_iter_t *it,
|
||||
const ecs_filter_t *filter);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Safe(r) integer casting
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define FLECS_CONVERSION_ERR(T, value)\
|
||||
"illegal conversion from value " #value " to type " #T
|
||||
|
||||
#define flecs_signed_char__ (CHAR_MIN < 0)
|
||||
#define flecs_signed_short__ true
|
||||
#define flecs_signed_int__ true
|
||||
#define flecs_signed_long__ true
|
||||
#define flecs_signed_size_t__ false
|
||||
#define flecs_signed_int8_t__ true
|
||||
#define flecs_signed_int16_t__ true
|
||||
#define flecs_signed_int32_t__ true
|
||||
#define flecs_signed_int64_t__ true
|
||||
#define flecs_signed_intptr_t__ true
|
||||
#define flecs_signed_uint8_t__ false
|
||||
#define flecs_signed_uint16_t__ false
|
||||
#define flecs_signed_uint32_t__ false
|
||||
#define flecs_signed_uint64_t__ false
|
||||
#define flecs_signed_uintptr_t__ false
|
||||
#define flecs_signed_ecs_size_t__ true
|
||||
#define flecs_signed_ecs_entity_t__ false
|
||||
|
||||
uint64_t flecs_ito_(
|
||||
size_t dst_size,
|
||||
bool dst_signed,
|
||||
bool lt_zero,
|
||||
uint64_t value,
|
||||
const char *err);
|
||||
|
||||
#ifndef FLECS_NDEBUG
|
||||
#define flecs_ito(T, value)\
|
||||
(T)flecs_ito_(\
|
||||
sizeof(T),\
|
||||
flecs_signed_##T##__,\
|
||||
(value) < 0,\
|
||||
(uint64_t)(value),\
|
||||
FLECS_CONVERSION_ERR(T, (value)))
|
||||
|
||||
#define flecs_uto(T, value)\
|
||||
(T)flecs_ito_(\
|
||||
sizeof(T),\
|
||||
flecs_signed_##T##__,\
|
||||
false,\
|
||||
(uint64_t)(value),\
|
||||
FLECS_CONVERSION_ERR(T, (value)))
|
||||
#else
|
||||
#define flecs_ito(T, value) (T)(value)
|
||||
#define flecs_uto(T, value) (T)(value)
|
||||
#endif
|
||||
|
||||
#define flecs_itosize(value) flecs_ito(size_t, (value))
|
||||
#define flecs_utosize(value) flecs_uto(ecs_size_t, (value))
|
||||
#define flecs_itoi16(value) flecs_ito(int16_t, (value))
|
||||
#define flecs_itoi32(value) flecs_ito(int32_t, (value))
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Entity filter
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void flecs_entity_filter_init(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_filter_t **entity_filter,
|
||||
const ecs_filter_t *filter,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t *ids,
|
||||
int32_t *columns);
|
||||
|
||||
void flecs_entity_filter_fini(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_filter_t *entity_filter);
|
||||
|
||||
int flecs_entity_filter_next(
|
||||
ecs_entity_filter_iter_t *it);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Utilities
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
uint64_t flecs_hash(
|
||||
const void *data,
|
||||
ecs_size_t length);
|
||||
|
||||
uint64_t flecs_wyhash(
|
||||
const void *data,
|
||||
ecs_size_t length);
|
||||
|
||||
/* Get next power of 2 */
|
||||
int32_t flecs_next_pow_of_2(
|
||||
int32_t n);
|
||||
|
||||
/* Convert 64bit value to ecs_record_t type. ecs_record_t is stored as 64bit int in the
|
||||
* entity index */
|
||||
ecs_record_t flecs_to_row(
|
||||
uint64_t value);
|
||||
|
||||
/* Get 64bit integer from ecs_record_t */
|
||||
uint64_t flecs_from_row(
|
||||
ecs_record_t record);
|
||||
|
||||
/* Convert a symbol name to an entity name by removing the prefix */
|
||||
const char* flecs_name_from_symbol(
|
||||
ecs_world_t *world,
|
||||
const char *type_name);
|
||||
|
||||
/* Compare function for entity ids */
|
||||
int flecs_entity_compare(
|
||||
ecs_entity_t e1,
|
||||
const void *ptr1,
|
||||
ecs_entity_t e2,
|
||||
const void *ptr2);
|
||||
|
||||
bool flecs_name_is_id(
|
||||
const char *name);
|
||||
|
||||
ecs_entity_t flecs_name_to_id(
|
||||
const ecs_world_t *world,
|
||||
const char *name);
|
||||
|
||||
/* Convert floating point to string */
|
||||
char * ecs_ftoa(
|
||||
double f,
|
||||
char * buf,
|
||||
int precision);
|
||||
|
||||
uint64_t flecs_string_hash(
|
||||
const void *ptr);
|
||||
|
||||
void flecs_table_hashmap_init(
|
||||
ecs_world_t *world,
|
||||
ecs_hashmap_t *hm);
|
||||
|
||||
void flecs_colorize_buf(
|
||||
char *msg,
|
||||
bool enable_colors,
|
||||
ecs_strbuf_t *buf);
|
||||
|
||||
bool flecs_isident(
|
||||
char ch);
|
||||
|
||||
int32_t flecs_search_w_idr(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t id,
|
||||
ecs_id_t *id_out,
|
||||
ecs_id_record_t *idr);
|
||||
|
||||
int32_t flecs_search_relation_w_idr(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
int32_t offset,
|
||||
ecs_id_t id,
|
||||
ecs_entity_t rel,
|
||||
ecs_flags32_t flags,
|
||||
ecs_entity_t *subject_out,
|
||||
ecs_id_t *id_out,
|
||||
struct ecs_table_record_t **tr_out,
|
||||
ecs_id_record_t *idr);
|
||||
|
||||
#endif
|
||||
561
engine/libs/flecs/src/private_types.h
Normal file
561
engine/libs/flecs/src/private_types.h
Normal file
@@ -0,0 +1,561 @@
|
||||
/**
|
||||
* @file private_types.h
|
||||
* @brief Private types.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_PRIVATE_TYPES_H
|
||||
#define FLECS_PRIVATE_TYPES_H
|
||||
|
||||
#ifndef __MACH__
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "flecs.h"
|
||||
#include "storage/entity_index.h"
|
||||
#include "datastructures/stack_allocator.h"
|
||||
#include "flecs/private/bitset.h"
|
||||
#include "flecs/private/switch_list.h"
|
||||
#include "storage/table.h"
|
||||
|
||||
/* Used in id records to keep track of entities used with id flags */
|
||||
extern const ecs_entity_t EcsFlag;
|
||||
|
||||
#define ECS_MAX_JOBS_PER_WORKER (16)
|
||||
#define ECS_MAX_DEFER_STACK (8)
|
||||
|
||||
/* Magic number for a flecs object */
|
||||
#define ECS_OBJECT_MAGIC (0x6563736f)
|
||||
|
||||
/* Tags associated with poly for (Poly, tag) components */
|
||||
#define ecs_world_t_tag invalid
|
||||
#define ecs_stage_t_tag invalid
|
||||
#define ecs_query_t_tag EcsQuery
|
||||
#define ecs_rule_t_tag EcsQuery
|
||||
#define ecs_table_t_tag invalid
|
||||
#define ecs_filter_t_tag EcsQuery
|
||||
#define ecs_observer_t_tag EcsObserver
|
||||
|
||||
/* Mixin kinds */
|
||||
typedef enum ecs_mixin_kind_t {
|
||||
EcsMixinWorld,
|
||||
EcsMixinEntity,
|
||||
EcsMixinObservable,
|
||||
EcsMixinIterable,
|
||||
EcsMixinDtor,
|
||||
EcsMixinMax
|
||||
} ecs_mixin_kind_t;
|
||||
|
||||
/* The mixin array contains pointers to mixin members for different kinds of
|
||||
* flecs objects. This allows the API to retrieve data from an object regardless
|
||||
* of its type. Each mixin array is only stored once per type */
|
||||
struct ecs_mixins_t {
|
||||
const char *type_name; /* Include name of mixin type so debug code doesn't
|
||||
* need to know about every object */
|
||||
ecs_size_t elems[EcsMixinMax];
|
||||
};
|
||||
|
||||
/* Mixin tables */
|
||||
extern ecs_mixins_t ecs_world_t_mixins;
|
||||
extern ecs_mixins_t ecs_stage_t_mixins;
|
||||
extern ecs_mixins_t ecs_filter_t_mixins;
|
||||
extern ecs_mixins_t ecs_query_t_mixins;
|
||||
extern ecs_mixins_t ecs_trigger_t_mixins;
|
||||
extern ecs_mixins_t ecs_observer_t_mixins;
|
||||
|
||||
/* Types that have no mixins */
|
||||
#define ecs_table_t_mixins (&(ecs_mixins_t){ NULL })
|
||||
|
||||
/* Scope for flecs internals, like observers used for builtin features */
|
||||
extern const ecs_entity_t EcsFlecsInternals;
|
||||
|
||||
/** Type used for internal string hashmap */
|
||||
typedef struct ecs_hashed_string_t {
|
||||
char *value;
|
||||
ecs_size_t length;
|
||||
uint64_t hash;
|
||||
} ecs_hashed_string_t;
|
||||
|
||||
/** Must appear as first member in payload of table cache */
|
||||
typedef struct ecs_table_cache_hdr_t {
|
||||
struct ecs_table_cache_t *cache;
|
||||
ecs_table_t *table;
|
||||
struct ecs_table_cache_hdr_t *prev, *next;
|
||||
bool empty;
|
||||
} ecs_table_cache_hdr_t;
|
||||
|
||||
/** Linked list of tables in table cache */
|
||||
typedef struct ecs_table_cache_list_t {
|
||||
ecs_table_cache_hdr_t *first;
|
||||
ecs_table_cache_hdr_t *last;
|
||||
int32_t count;
|
||||
} ecs_table_cache_list_t;
|
||||
|
||||
/** Table cache */
|
||||
typedef struct ecs_table_cache_t {
|
||||
ecs_map_t index; /* <table_id, T*> */
|
||||
ecs_table_cache_list_t tables;
|
||||
ecs_table_cache_list_t empty_tables;
|
||||
} ecs_table_cache_t;
|
||||
|
||||
/* Sparse query term */
|
||||
typedef struct flecs_switch_term_t {
|
||||
ecs_switch_t *sw_column;
|
||||
ecs_entity_t sw_case;
|
||||
int32_t signature_column_index;
|
||||
} flecs_switch_term_t;
|
||||
|
||||
/* Bitset query term */
|
||||
typedef struct flecs_bitset_term_t {
|
||||
ecs_bitset_t *bs_column;
|
||||
int32_t column_index;
|
||||
} flecs_bitset_term_t;
|
||||
|
||||
typedef struct flecs_flat_monitor_t {
|
||||
int32_t table_state;
|
||||
int32_t monitor;
|
||||
} flecs_flat_monitor_t;
|
||||
|
||||
/* Flat table term */
|
||||
typedef struct flecs_flat_table_term_t {
|
||||
int32_t field_index; /* Iterator field index */
|
||||
ecs_term_t *term;
|
||||
ecs_vec_t monitor;
|
||||
} flecs_flat_table_term_t;
|
||||
|
||||
/* Entity filter. This filters the entities of a matched table, for example when
|
||||
* it has disabled components or union relationships (switch). */
|
||||
typedef struct ecs_entity_filter_t {
|
||||
ecs_vec_t sw_terms; /* Terms with switch (union) entity filter */
|
||||
ecs_vec_t bs_terms; /* Terms with bitset (toggle) entity filter */
|
||||
ecs_vec_t ft_terms; /* Terms with components from flattened tree */
|
||||
int32_t flat_tree_column;
|
||||
} ecs_entity_filter_t;
|
||||
|
||||
typedef struct ecs_entity_filter_iter_t {
|
||||
ecs_entity_filter_t *entity_filter;
|
||||
ecs_iter_t *it;
|
||||
int32_t *columns;
|
||||
ecs_table_t *prev;
|
||||
ecs_table_range_t range;
|
||||
int32_t bs_offset;
|
||||
int32_t sw_offset;
|
||||
int32_t sw_smallest;
|
||||
int32_t flat_tree_offset;
|
||||
int32_t target_count;
|
||||
} ecs_entity_filter_iter_t;
|
||||
|
||||
/** Table match data.
|
||||
* Each table matched by the query is represented by a ecs_query_table_match_t
|
||||
* instance, which are linked together in a list. A table may match a query
|
||||
* multiple times (due to wildcard queries) with different columns being matched
|
||||
* by the query. */
|
||||
struct ecs_query_table_match_t {
|
||||
ecs_query_table_match_t *next, *prev;
|
||||
ecs_table_t *table; /* The current table. */
|
||||
int32_t offset; /* Starting point in table */
|
||||
int32_t count; /* Number of entities to iterate in table */
|
||||
int32_t *columns; /* Mapping from query fields to table columns */
|
||||
int32_t *storage_columns; /* Mapping from query fields to storage columns */
|
||||
ecs_id_t *ids; /* Resolved (component) ids for current table */
|
||||
ecs_entity_t *sources; /* Subjects (sources) of ids */
|
||||
ecs_vec_t refs; /* Cached components for non-this terms */
|
||||
uint64_t group_id; /* Value used to organize tables in groups */
|
||||
int32_t *monitor; /* Used to monitor table for changes */
|
||||
ecs_entity_filter_t *entity_filter; /* Entity specific filters */
|
||||
|
||||
/* Next match in cache for same table (includes empty tables) */
|
||||
ecs_query_table_match_t *next_match;
|
||||
};
|
||||
|
||||
/** Table record type for query table cache. A query only has one per table. */
|
||||
typedef struct ecs_query_table_t {
|
||||
ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */
|
||||
ecs_query_table_match_t *first; /* List with matches for table */
|
||||
ecs_query_table_match_t *last; /* Last discovered match for table */
|
||||
uint64_t table_id;
|
||||
int32_t rematch_count; /* Track whether table was rematched */
|
||||
} ecs_query_table_t;
|
||||
|
||||
/** Points to the beginning & ending of a query group */
|
||||
typedef struct ecs_query_table_list_t {
|
||||
ecs_query_table_match_t *first;
|
||||
ecs_query_table_match_t *last;
|
||||
ecs_query_group_info_t info;
|
||||
} ecs_query_table_list_t;
|
||||
|
||||
/* Query event type for notifying queries of world events */
|
||||
typedef enum ecs_query_eventkind_t {
|
||||
EcsQueryTableMatch,
|
||||
EcsQueryTableRematch,
|
||||
EcsQueryTableUnmatch,
|
||||
EcsQueryOrphan
|
||||
} ecs_query_eventkind_t;
|
||||
|
||||
typedef struct ecs_query_event_t {
|
||||
ecs_query_eventkind_t kind;
|
||||
ecs_table_t *table;
|
||||
ecs_query_t *parent_query;
|
||||
} ecs_query_event_t;
|
||||
|
||||
/* Query level block allocators have sizes that depend on query field count */
|
||||
typedef struct ecs_query_allocators_t {
|
||||
ecs_block_allocator_t columns;
|
||||
ecs_block_allocator_t ids;
|
||||
ecs_block_allocator_t sources;
|
||||
ecs_block_allocator_t monitors;
|
||||
} ecs_query_allocators_t;
|
||||
|
||||
/** Query that is automatically matched against tables */
|
||||
struct ecs_query_t {
|
||||
ecs_header_t hdr;
|
||||
|
||||
/* Query filter */
|
||||
ecs_filter_t filter;
|
||||
|
||||
/* Tables matched with query */
|
||||
ecs_table_cache_t cache;
|
||||
|
||||
/* Linked list with all matched non-empty tables, in iteration order */
|
||||
ecs_query_table_list_t list;
|
||||
|
||||
/* Contains head/tail to nodes of query groups (if group_by is used) */
|
||||
ecs_map_t groups;
|
||||
|
||||
/* Table sorting */
|
||||
ecs_entity_t order_by_component;
|
||||
ecs_order_by_action_t order_by;
|
||||
ecs_sort_table_action_t sort_table;
|
||||
ecs_vec_t table_slices;
|
||||
int32_t order_by_term;
|
||||
|
||||
/* Table grouping */
|
||||
ecs_entity_t group_by_id;
|
||||
ecs_group_by_action_t group_by;
|
||||
ecs_group_create_action_t on_group_create;
|
||||
ecs_group_delete_action_t on_group_delete;
|
||||
void *group_by_ctx;
|
||||
ecs_ctx_free_t group_by_ctx_free;
|
||||
|
||||
/* Subqueries */
|
||||
ecs_query_t *parent;
|
||||
ecs_vec_t subqueries;
|
||||
|
||||
/* Flags for query properties */
|
||||
ecs_flags32_t flags;
|
||||
|
||||
/* Monitor generation */
|
||||
int32_t monitor_generation;
|
||||
|
||||
int32_t cascade_by; /* Identify cascade term */
|
||||
int32_t match_count; /* How often have tables been (un)matched */
|
||||
int32_t prev_match_count; /* Track if sorting is needed */
|
||||
int32_t rematch_count; /* Track which tables were added during rematch */
|
||||
|
||||
/* User context */
|
||||
void *ctx; /* User context to pass to callback */
|
||||
void *binding_ctx; /* Context to be used for language bindings */
|
||||
|
||||
ecs_ctx_free_t ctx_free; /** Callback to free ctx */
|
||||
ecs_ctx_free_t binding_ctx_free; /** Callback to free binding_ctx */
|
||||
|
||||
/* Mixins */
|
||||
ecs_iterable_t iterable;
|
||||
ecs_poly_dtor_t dtor;
|
||||
|
||||
/* Query-level allocators */
|
||||
ecs_query_allocators_t allocators;
|
||||
};
|
||||
|
||||
/** All observers for a specific (component) id */
|
||||
typedef struct ecs_event_id_record_t {
|
||||
/* Triggers for Self */
|
||||
ecs_map_t self; /* map<trigger_id, trigger_t> */
|
||||
ecs_map_t self_up; /* map<trigger_id, trigger_t> */
|
||||
ecs_map_t up; /* map<trigger_id, trigger_t> */
|
||||
|
||||
ecs_map_t observers; /* map<trigger_id, trigger_t> */
|
||||
|
||||
/* Triggers for SuperSet, SubSet */
|
||||
ecs_map_t set_observers; /* map<trigger_id, trigger_t> */
|
||||
|
||||
/* Triggers for Self with non-This subject */
|
||||
ecs_map_t entity_observers; /* map<trigger_id, trigger_t> */
|
||||
|
||||
/* Number of active observers for (component) id */
|
||||
int32_t observer_count;
|
||||
} ecs_event_id_record_t;
|
||||
|
||||
/* World level allocators are for operations that are not multithreaded */
|
||||
typedef struct ecs_world_allocators_t {
|
||||
ecs_map_params_t ptr;
|
||||
ecs_map_params_t query_table_list;
|
||||
ecs_block_allocator_t query_table;
|
||||
ecs_block_allocator_t query_table_match;
|
||||
ecs_block_allocator_t graph_edge_lo;
|
||||
ecs_block_allocator_t graph_edge;
|
||||
ecs_block_allocator_t id_record;
|
||||
ecs_block_allocator_t id_record_chunk;
|
||||
ecs_block_allocator_t table_diff;
|
||||
ecs_block_allocator_t sparse_chunk;
|
||||
ecs_block_allocator_t hashmap;
|
||||
|
||||
/* Temporary vectors used for creating table diff id sequences */
|
||||
ecs_table_diff_builder_t diff_builder;
|
||||
} ecs_world_allocators_t;
|
||||
|
||||
/* Stage level allocators are for operations that can be multithreaded */
|
||||
typedef struct ecs_stage_allocators_t {
|
||||
ecs_stack_t iter_stack;
|
||||
ecs_stack_t deser_stack;
|
||||
ecs_block_allocator_t cmd_entry_chunk;
|
||||
} ecs_stage_allocators_t;
|
||||
|
||||
/** Types for deferred operations */
|
||||
typedef enum ecs_cmd_kind_t {
|
||||
EcsOpClone,
|
||||
EcsOpBulkNew,
|
||||
EcsOpAdd,
|
||||
EcsOpRemove,
|
||||
EcsOpSet,
|
||||
EcsOpEmplace,
|
||||
EcsOpMut,
|
||||
EcsOpModified,
|
||||
EcsOpAddModified,
|
||||
EcsOpPath,
|
||||
EcsOpDelete,
|
||||
EcsOpClear,
|
||||
EcsOpOnDeleteAction,
|
||||
EcsOpEnable,
|
||||
EcsOpDisable,
|
||||
EcsOpSkip
|
||||
} ecs_cmd_kind_t;
|
||||
|
||||
/* Entity specific metadata for command in queue */
|
||||
typedef struct ecs_cmd_entry_t {
|
||||
int32_t first;
|
||||
int32_t last; /* If -1, a delete command was inserted */
|
||||
} ecs_cmd_entry_t;
|
||||
|
||||
typedef struct ecs_cmd_1_t {
|
||||
void *value; /* Component value (used by set / get_mut) */
|
||||
ecs_size_t size; /* Size of value */
|
||||
bool clone_value; /* Clone entity with value (used for clone) */
|
||||
} ecs_cmd_1_t;
|
||||
|
||||
typedef struct ecs_cmd_n_t {
|
||||
ecs_entity_t *entities;
|
||||
int32_t count;
|
||||
} ecs_cmd_n_t;
|
||||
|
||||
typedef struct ecs_cmd_t {
|
||||
ecs_cmd_kind_t kind; /* Command kind */
|
||||
int32_t next_for_entity; /* Next operation for entity */
|
||||
ecs_id_t id; /* (Component) id */
|
||||
ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */
|
||||
ecs_cmd_entry_t *entry;
|
||||
ecs_entity_t entity; /* Entity id */
|
||||
|
||||
union {
|
||||
ecs_cmd_1_t _1; /* Data for single entity operation */
|
||||
ecs_cmd_n_t _n; /* Data for multi entity operation */
|
||||
} is;
|
||||
} ecs_cmd_t;
|
||||
|
||||
/* Data structures that store the command queue */
|
||||
typedef struct ecs_commands_t {
|
||||
ecs_vec_t queue;
|
||||
ecs_stack_t stack; /* Temp memory used by deferred commands */
|
||||
ecs_sparse_t entries; /* <entity, op_entry_t> - command batching */
|
||||
} ecs_commands_t;
|
||||
|
||||
/** A stage is a context that allows for safely using the API from multiple
|
||||
* threads. Stage pointers can be passed to the world argument of API
|
||||
* operations, which causes the operation to be ran on the stage instead of the
|
||||
* world. */
|
||||
struct ecs_stage_t {
|
||||
ecs_header_t hdr;
|
||||
|
||||
/* Unique id that identifies the stage */
|
||||
int32_t id;
|
||||
|
||||
/* Zero if not deferred, positive if deferred, negative if suspended */
|
||||
int32_t defer;
|
||||
|
||||
/* Command queue stack, for nested execution */
|
||||
ecs_commands_t *cmd;
|
||||
ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK];
|
||||
int32_t cmd_sp;
|
||||
|
||||
/* Thread context */
|
||||
ecs_world_t *thread_ctx; /* Points to stage when a thread stage */
|
||||
ecs_world_t *world; /* Reference to world */
|
||||
ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */
|
||||
|
||||
/* One-shot actions to be executed after the merge */
|
||||
ecs_vec_t post_frame_actions;
|
||||
|
||||
/* Namespacing */
|
||||
ecs_entity_t scope; /* Entity of current scope */
|
||||
ecs_entity_t with; /* Id to add by default to new entities */
|
||||
ecs_entity_t base; /* Currently instantiated top-level base */
|
||||
const ecs_entity_t *lookup_path; /* Search path used by lookup operations */
|
||||
|
||||
/* Properties */
|
||||
bool auto_merge; /* Should this stage automatically merge? */
|
||||
bool async; /* Is stage asynchronous? (write only) */
|
||||
|
||||
/* Thread specific allocators */
|
||||
ecs_stage_allocators_t allocators;
|
||||
ecs_allocator_t allocator;
|
||||
|
||||
/* Caches for rule creation */
|
||||
ecs_vec_t variables;
|
||||
ecs_vec_t operations;
|
||||
};
|
||||
|
||||
/* Component monitor */
|
||||
typedef struct ecs_monitor_t {
|
||||
ecs_vec_t queries; /* vector<ecs_query_t*> */
|
||||
bool is_dirty; /* Should queries be rematched? */
|
||||
} ecs_monitor_t;
|
||||
|
||||
/* Component monitors */
|
||||
typedef struct ecs_monitor_set_t {
|
||||
ecs_map_t monitors; /* map<id, ecs_monitor_t> */
|
||||
bool is_dirty; /* Should monitors be evaluated? */
|
||||
} ecs_monitor_set_t;
|
||||
|
||||
/* Data stored for id marked for deletion */
|
||||
typedef struct ecs_marked_id_t {
|
||||
ecs_id_record_t *idr;
|
||||
ecs_id_t id;
|
||||
ecs_entity_t action; /* Set explicitly for delete_with, remove_all */
|
||||
bool delete_id;
|
||||
} ecs_marked_id_t;
|
||||
|
||||
typedef struct ecs_store_t {
|
||||
/* Entity lookup */
|
||||
ecs_entity_index_t entity_index;
|
||||
|
||||
/* Table lookup by id */
|
||||
ecs_sparse_t tables; /* sparse<table_id, ecs_table_t> */
|
||||
|
||||
/* Table lookup by hash */
|
||||
ecs_hashmap_t table_map; /* hashmap<ecs_type_t, ecs_table_t*> */
|
||||
|
||||
/* Root table */
|
||||
ecs_table_t root;
|
||||
|
||||
/* Records cache */
|
||||
ecs_vec_t records;
|
||||
|
||||
/* Stack of ids being deleted. */
|
||||
ecs_vec_t marked_ids; /* vector<ecs_marked_ids_t> */
|
||||
|
||||
/* Entity ids associated with depth (for flat hierarchies) */
|
||||
ecs_vec_t depth_ids;
|
||||
ecs_map_t entity_to_depth; /* What it says */
|
||||
} ecs_store_t;
|
||||
|
||||
/* fini actions */
|
||||
typedef struct ecs_action_elem_t {
|
||||
ecs_fini_action_t action;
|
||||
void *ctx;
|
||||
} ecs_action_elem_t;
|
||||
|
||||
typedef struct ecs_pipeline_state_t ecs_pipeline_state_t;
|
||||
|
||||
/** The world stores and manages all ECS data. An application can have more than
|
||||
* one world, but data is not shared between worlds. */
|
||||
struct ecs_world_t {
|
||||
ecs_header_t hdr;
|
||||
|
||||
/* -- Type metadata -- */
|
||||
ecs_id_record_t *id_index_lo;
|
||||
ecs_map_t id_index_hi; /* map<id, ecs_id_record_t*> */
|
||||
ecs_sparse_t type_info; /* sparse<type_id, type_info_t> */
|
||||
|
||||
/* -- Cached handle to id records -- */
|
||||
ecs_id_record_t *idr_wildcard;
|
||||
ecs_id_record_t *idr_wildcard_wildcard;
|
||||
ecs_id_record_t *idr_any;
|
||||
ecs_id_record_t *idr_isa_wildcard;
|
||||
ecs_id_record_t *idr_childof_0;
|
||||
ecs_id_record_t *idr_childof_wildcard;
|
||||
ecs_id_record_t *idr_identifier_name;
|
||||
|
||||
/* -- Mixins -- */
|
||||
ecs_world_t *self;
|
||||
ecs_observable_t observable;
|
||||
ecs_iterable_t iterable;
|
||||
|
||||
/* Unique id per generated event used to prevent duplicate notifications */
|
||||
int32_t event_id;
|
||||
|
||||
/* Is entity range checking enabled? */
|
||||
bool range_check_enabled;
|
||||
|
||||
/* -- Data storage -- */
|
||||
ecs_store_t store;
|
||||
|
||||
/* -- Pending table event buffers -- */
|
||||
ecs_sparse_t *pending_buffer; /* sparse<table_id, ecs_table_t*> */
|
||||
ecs_sparse_t *pending_tables; /* sparse<table_id, ecs_table_t*> */
|
||||
|
||||
/* Used to track when cache needs to be updated */
|
||||
ecs_monitor_set_t monitors; /* map<id, ecs_monitor_t> */
|
||||
|
||||
/* -- Systems -- */
|
||||
ecs_entity_t pipeline; /* Current pipeline */
|
||||
|
||||
/* -- Identifiers -- */
|
||||
ecs_hashmap_t aliases;
|
||||
ecs_hashmap_t symbols;
|
||||
|
||||
/* -- Staging -- */
|
||||
ecs_stage_t *stages; /* Stages */
|
||||
int32_t stage_count; /* Number of stages */
|
||||
|
||||
/* -- Multithreading -- */
|
||||
ecs_os_cond_t worker_cond; /* Signal that worker threads can start */
|
||||
ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */
|
||||
ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */
|
||||
int32_t workers_running; /* Number of threads running */
|
||||
int32_t workers_waiting; /* Number of workers waiting on sync */
|
||||
ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */
|
||||
bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */
|
||||
|
||||
/* -- Time management -- */
|
||||
ecs_time_t world_start_time; /* Timestamp of simulation start */
|
||||
ecs_time_t frame_start_time; /* Timestamp of frame start */
|
||||
ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */
|
||||
|
||||
/* -- Metrics -- */
|
||||
ecs_world_info_t info;
|
||||
|
||||
/* -- World flags -- */
|
||||
ecs_flags32_t flags;
|
||||
|
||||
/* Count that increases when component monitors change */
|
||||
int32_t monitor_generation;
|
||||
|
||||
/* -- Allocators -- */
|
||||
ecs_world_allocators_t allocators; /* Static allocation sizes */
|
||||
ecs_allocator_t allocator; /* Dynamic allocation sizes */
|
||||
|
||||
void *ctx; /* Application context */
|
||||
void *binding_ctx; /* Binding-specific context */
|
||||
|
||||
ecs_ctx_free_t ctx_free; /**< Callback to free ctx */
|
||||
ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */
|
||||
|
||||
ecs_vec_t fini_actions; /* Callbacks to execute when world exits */
|
||||
};
|
||||
|
||||
#endif
|
||||
2816
engine/libs/flecs/src/query.c
Normal file
2816
engine/libs/flecs/src/query.c
Normal file
File diff suppressed because it is too large
Load Diff
391
engine/libs/flecs/src/search.c
Normal file
391
engine/libs/flecs/src/search.c
Normal file
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* @file search.c
|
||||
* @brief Search functions to find (component) ids in table types.
|
||||
*
|
||||
* Search functions are used to find the column index of a (component) id in a
|
||||
* table. Additionally, search functions implement the logic for finding a
|
||||
* component id by following a relationship upwards.
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
|
||||
static
|
||||
int32_t flecs_type_search(
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t search_id,
|
||||
ecs_id_record_t *idr,
|
||||
ecs_id_t *ids,
|
||||
ecs_id_t *id_out,
|
||||
ecs_table_record_t **tr_out)
|
||||
{
|
||||
ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table);
|
||||
if (tr) {
|
||||
int32_t r = tr->index;
|
||||
if (tr_out) tr_out[0] = tr;
|
||||
if (id_out) {
|
||||
if (ECS_PAIR_FIRST(search_id) == EcsUnion) {
|
||||
id_out[0] = ids[r];
|
||||
} else {
|
||||
id_out[0] = flecs_to_public_id(ids[r]);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
int32_t flecs_type_offset_search(
|
||||
int32_t offset,
|
||||
ecs_id_t id,
|
||||
ecs_id_t *ids,
|
||||
int32_t count,
|
||||
ecs_id_t *id_out)
|
||||
{
|
||||
ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
while (offset < count) {
|
||||
ecs_id_t type_id = ids[offset ++];
|
||||
if (ecs_id_match(type_id, id)) {
|
||||
if (id_out) {
|
||||
id_out[0] = flecs_to_public_id(type_id);
|
||||
}
|
||||
return offset - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static
|
||||
bool flecs_type_can_inherit_id(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
const ecs_id_record_t *idr,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
if (idr->flags & EcsIdDontInherit) {
|
||||
return false;
|
||||
}
|
||||
if (idr->flags & EcsIdExclusive) {
|
||||
if (ECS_HAS_ID_FLAG(id, PAIR)) {
|
||||
ecs_entity_t er = ECS_PAIR_FIRST(id);
|
||||
if (flecs_table_record_get(
|
||||
world, table, ecs_pair(er, EcsWildcard)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
int32_t flecs_type_search_relation(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
int32_t offset,
|
||||
ecs_id_t id,
|
||||
ecs_id_record_t *idr,
|
||||
ecs_id_t rel,
|
||||
ecs_id_record_t *idr_r,
|
||||
bool self,
|
||||
ecs_entity_t *subject_out,
|
||||
ecs_id_t *id_out,
|
||||
ecs_table_record_t **tr_out)
|
||||
{
|
||||
ecs_type_t type = table->type;
|
||||
ecs_id_t *ids = type.array;
|
||||
int32_t count = type.count;
|
||||
|
||||
if (self) {
|
||||
if (offset) {
|
||||
int32_t r = flecs_type_offset_search(offset, id, ids, count, id_out);
|
||||
if (r != -1) {
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
int32_t r = flecs_type_search(table, id, idr, ids, id_out, tr_out);
|
||||
if (r != -1) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ecs_flags32_t flags = table->flags;
|
||||
if ((flags & EcsTableHasPairs) && rel) {
|
||||
bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard);
|
||||
if (is_a) {
|
||||
if (!(flags & EcsTableHasIsA)) {
|
||||
return -1;
|
||||
}
|
||||
idr_r = world->idr_isa_wildcard;
|
||||
|
||||
if (!flecs_type_can_inherit_id(world, table, idr, id)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!idr_r) {
|
||||
idr_r = flecs_id_record_get(world, rel);
|
||||
if (!idr_r) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_id_t id_r;
|
||||
int32_t r, r_column;
|
||||
if (offset) {
|
||||
r_column = flecs_type_offset_search(offset, rel, ids, count, &id_r);
|
||||
} else {
|
||||
r_column = flecs_type_search(table, id, idr_r, ids, &id_r, 0);
|
||||
}
|
||||
while (r_column != -1) {
|
||||
ecs_entity_t obj = ECS_PAIR_SECOND(id_r);
|
||||
ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_record_t *rec = flecs_entities_get_any(world, obj);
|
||||
ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_table_t *obj_table = rec->table;
|
||||
if (obj_table) {
|
||||
ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL);
|
||||
|
||||
r = flecs_type_search_relation(world, obj_table, 0, id, idr,
|
||||
rel, idr_r, true, subject_out, id_out, tr_out);
|
||||
if (r != -1) {
|
||||
if (subject_out && !subject_out[0]) {
|
||||
subject_out[0] = ecs_get_alive(world, obj);
|
||||
}
|
||||
return r_column;
|
||||
}
|
||||
|
||||
if (!is_a) {
|
||||
r = flecs_type_search_relation(world, obj_table, 0, id, idr,
|
||||
ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard,
|
||||
true, subject_out, id_out, tr_out);
|
||||
if (r != -1) {
|
||||
if (subject_out && !subject_out[0]) {
|
||||
subject_out[0] = ecs_get_alive(world, obj);
|
||||
}
|
||||
return r_column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r_column = flecs_type_offset_search(
|
||||
r_column + 1, rel, ids, count, &id_r);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t flecs_search_relation_w_idr(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
int32_t offset,
|
||||
ecs_id_t id,
|
||||
ecs_entity_t rel,
|
||||
ecs_flags32_t flags,
|
||||
ecs_entity_t *subject_out,
|
||||
ecs_id_t *id_out,
|
||||
struct ecs_table_record_t **tr_out,
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
if (!table) return -1;
|
||||
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
flags = flags ? flags : (EcsSelf|EcsUp);
|
||||
|
||||
if (!idr) {
|
||||
idr = flecs_query_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (subject_out) subject_out[0] = 0;
|
||||
if (!(flags & EcsUp)) {
|
||||
if (offset) {
|
||||
return ecs_search_offset(world, table, offset, id, id_out);
|
||||
} else {
|
||||
return flecs_type_search(
|
||||
table, id, idr, table->type.array, id_out, tr_out);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t result = flecs_type_search_relation(world, table, offset, id, idr,
|
||||
ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out,
|
||||
id_out, tr_out);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t ecs_search_relation(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
int32_t offset,
|
||||
ecs_id_t id,
|
||||
ecs_entity_t rel,
|
||||
ecs_flags32_t flags,
|
||||
ecs_entity_t *subject_out,
|
||||
ecs_id_t *id_out,
|
||||
struct ecs_table_record_t **tr_out)
|
||||
{
|
||||
if (!table) return -1;
|
||||
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
flags = flags ? flags : (EcsSelf|EcsUp);
|
||||
|
||||
if (subject_out) subject_out[0] = 0;
|
||||
if (!(flags & EcsUp)) {
|
||||
return ecs_search_offset(world, table, offset, id, id_out);
|
||||
}
|
||||
|
||||
ecs_id_record_t *idr = flecs_query_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t result = flecs_type_search_relation(world, table, offset, id, idr,
|
||||
ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out,
|
||||
id_out, tr_out);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t flecs_search_w_idr(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t id,
|
||||
ecs_id_t *id_out,
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
if (!table) return -1;
|
||||
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)world;
|
||||
|
||||
ecs_type_t type = table->type;
|
||||
ecs_id_t *ids = type.array;
|
||||
return flecs_type_search(table, id, idr, ids, id_out, 0);
|
||||
}
|
||||
|
||||
int32_t ecs_search(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t id,
|
||||
ecs_id_t *id_out)
|
||||
{
|
||||
if (!table) return -1;
|
||||
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_id_record_t *idr = flecs_query_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ecs_type_t type = table->type;
|
||||
ecs_id_t *ids = type.array;
|
||||
return flecs_type_search(table, id, idr, ids, id_out, 0);
|
||||
}
|
||||
|
||||
int32_t ecs_search_offset(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
int32_t offset,
|
||||
ecs_id_t id,
|
||||
ecs_id_t *id_out)
|
||||
{
|
||||
if (!offset) {
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
return ecs_search(world, table, id, id_out);
|
||||
}
|
||||
|
||||
if (!table) return -1;
|
||||
|
||||
ecs_type_t type = table->type;
|
||||
ecs_id_t *ids = type.array;
|
||||
int32_t count = type.count;
|
||||
return flecs_type_offset_search(offset, id, ids, count, id_out);
|
||||
}
|
||||
|
||||
static
|
||||
int32_t flecs_relation_depth_walk(
|
||||
const ecs_world_t *world,
|
||||
const ecs_id_record_t *idr,
|
||||
const ecs_table_t *first,
|
||||
const ecs_table_t *table)
|
||||
{
|
||||
int32_t result = 0;
|
||||
|
||||
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
|
||||
if (!tr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t i = tr->index, end = i + tr->count;
|
||||
for (; i != end; i ++) {
|
||||
ecs_entity_t o = ecs_pair_second(world, table->type.array[i]);
|
||||
ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_table_t *ot = ecs_get_table(world, o);
|
||||
if (!ot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL);
|
||||
int32_t cur = flecs_relation_depth_walk(world, idr, first, ot);
|
||||
if (cur > result) {
|
||||
result = cur;
|
||||
}
|
||||
}
|
||||
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
int32_t flecs_relation_depth(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t r,
|
||||
const ecs_table_t *table)
|
||||
{
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard));
|
||||
if (!idr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t depth_offset = 0;
|
||||
if (table->flags & EcsTableHasTarget) {
|
||||
if (ecs_table_get_type_index(world, table,
|
||||
ecs_pair_t(EcsTarget, r)) != -1)
|
||||
{
|
||||
ecs_id_t id;
|
||||
int32_t col = ecs_search(world, table,
|
||||
ecs_pair(EcsFlatten, EcsWildcard), &id);
|
||||
if (col == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_entity_t did = ecs_pair_second(world, id);
|
||||
ecs_assert(did != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
uint64_t *val = ecs_map_get(&world->store.entity_to_depth, did);
|
||||
ecs_assert(val != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
depth_offset = flecs_uto(int32_t, val[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return flecs_relation_depth_walk(world, idr, table, table) + depth_offset;
|
||||
}
|
||||
929
engine/libs/flecs/src/stage.c
Normal file
929
engine/libs/flecs/src/stage.c
Normal file
@@ -0,0 +1,929 @@
|
||||
/**
|
||||
* @file stage.c
|
||||
* @brief Staging implementation.
|
||||
*
|
||||
* A stage is an object that can be used to temporarily store mutations to a
|
||||
* world while a world is in readonly mode. ECS operations that are invoked on
|
||||
* a stage are stored in a command buffer, which is flushed during sync points,
|
||||
* or manually by the user.
|
||||
*
|
||||
* Stages contain additional state to enable other API functionality without
|
||||
* having to mutate the world, such as setting the current scope, and allocators
|
||||
* that are local to a stage.
|
||||
*
|
||||
* In a multi threaded application, each thread has its own stage which allows
|
||||
* threads to insert mutations without having to lock administration.
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
|
||||
static
|
||||
ecs_cmd_t* flecs_cmd_alloc(
|
||||
ecs_stage_t *stage)
|
||||
{
|
||||
ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue,
|
||||
ecs_cmd_t);
|
||||
ecs_os_zeromem(cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_cmd_t* flecs_cmd_new(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t e,
|
||||
bool is_delete,
|
||||
bool can_batch)
|
||||
{
|
||||
if (e) {
|
||||
ecs_vec_t *cmds = &stage->cmd->queue;
|
||||
ecs_cmd_entry_t *first_entry = NULL;
|
||||
ecs_cmd_entry_t *entry = flecs_sparse_try_t(
|
||||
&stage->cmd->entries, ecs_cmd_entry_t, e);
|
||||
|
||||
int32_t cur = ecs_vec_count(cmds);
|
||||
if (entry) {
|
||||
if (entry->first == -1) {
|
||||
/* Existing but invalidated entry */
|
||||
entry->first = cur;
|
||||
first_entry = entry;
|
||||
} else {
|
||||
int32_t last = entry->last;
|
||||
if (entry->last == -1) {
|
||||
/* Entity was deleted, don't insert command */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (can_batch) {
|
||||
ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t);
|
||||
ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_cmd_t *last_op = &arr[last];
|
||||
last_op->next_for_entity = cur;
|
||||
if (last == entry->first) {
|
||||
/* Flip sign bit so flush logic can tell which command
|
||||
* is the first for an entity */
|
||||
last_op->next_for_entity *= -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (can_batch || is_delete) {
|
||||
first_entry = entry = flecs_sparse_ensure_fast_t(
|
||||
&stage->cmd->entries, ecs_cmd_entry_t, e);
|
||||
entry->first = cur;
|
||||
}
|
||||
if (can_batch) {
|
||||
entry->last = cur;
|
||||
}
|
||||
if (is_delete) {
|
||||
/* Prevent insertion of more commands for entity */
|
||||
entry->last = -1;
|
||||
}
|
||||
|
||||
ecs_cmd_t *cmd = flecs_cmd_alloc(stage);
|
||||
cmd->entry = first_entry;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
return flecs_cmd_alloc(stage);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_stages_merge(
|
||||
ecs_world_t *world,
|
||||
bool force_merge)
|
||||
{
|
||||
bool is_stage = ecs_poly_is(world, ecs_stage_t);
|
||||
ecs_stage_t *stage = flecs_stage_from_world(&world);
|
||||
|
||||
bool measure_frame_time = ECS_BIT_IS_SET(world->flags,
|
||||
EcsWorldMeasureFrameTime);
|
||||
|
||||
ecs_time_t t_start = {0};
|
||||
if (measure_frame_time) {
|
||||
ecs_os_get_time(&t_start);
|
||||
}
|
||||
|
||||
ecs_dbg_3("#[magenta]merge");
|
||||
ecs_log_push_3();
|
||||
|
||||
if (is_stage) {
|
||||
/* Check for consistency if force_merge is enabled. In practice this
|
||||
* function will never get called with force_merge disabled for just
|
||||
* a single stage. */
|
||||
if (force_merge || stage->auto_merge) {
|
||||
ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION,
|
||||
"mismatching defer_begin/defer_end detected");
|
||||
flecs_defer_end(world, stage);
|
||||
}
|
||||
} else {
|
||||
/* Merge stages. Only merge if the stage has auto_merging turned on, or
|
||||
* if this is a forced merge (like when ecs_merge is called) */
|
||||
int32_t i, count = ecs_get_stage_count(world);
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i);
|
||||
ecs_poly_assert(s, ecs_stage_t);
|
||||
if (force_merge || s->auto_merge) {
|
||||
flecs_defer_end(world, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flecs_eval_component_monitors(world);
|
||||
|
||||
if (measure_frame_time) {
|
||||
world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start);
|
||||
}
|
||||
|
||||
world->info.merge_count_total ++;
|
||||
|
||||
/* If stage is asynchronous, deferring is always enabled */
|
||||
if (stage->async) {
|
||||
flecs_defer_begin(world, stage);
|
||||
}
|
||||
|
||||
ecs_log_pop_3();
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_stage_auto_merge(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
flecs_stages_merge(world, false);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_stage_manual_merge(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
flecs_stages_merge(world, true);
|
||||
}
|
||||
|
||||
bool flecs_defer_begin(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_poly_assert(stage, ecs_stage_t);
|
||||
(void)world;
|
||||
if (stage->defer < 0) return false;
|
||||
return (++ stage->defer) == 1;
|
||||
}
|
||||
|
||||
bool flecs_defer_cmd(
|
||||
ecs_stage_t *stage)
|
||||
{
|
||||
if (stage->defer) {
|
||||
return (stage->defer > 0);
|
||||
}
|
||||
|
||||
stage->defer ++;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_modified(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_id_t id)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
||||
if (cmd) {
|
||||
cmd->kind = EcsOpModified;
|
||||
cmd->id = id;
|
||||
cmd->entity = entity;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_clone(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t src,
|
||||
bool clone_value)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false);
|
||||
if (cmd) {
|
||||
cmd->kind = EcsOpClone;
|
||||
cmd->id = src;
|
||||
cmd->entity = entity;
|
||||
cmd->is._1.clone_value = clone_value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_path(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t parent,
|
||||
ecs_entity_t entity,
|
||||
const char *name)
|
||||
{
|
||||
if (stage->defer > 0) {
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false);
|
||||
if (cmd) {
|
||||
cmd->kind = EcsOpPath;
|
||||
cmd->entity = entity;
|
||||
cmd->id = parent;
|
||||
cmd->is._1.value = ecs_os_strdup(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_delete(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, true, false);
|
||||
if (cmd) {
|
||||
cmd->kind = EcsOpDelete;
|
||||
cmd->entity = entity;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_clear(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
||||
if (cmd) {
|
||||
cmd->kind = EcsOpClear;
|
||||
cmd->entity = entity;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_on_delete_action(
|
||||
ecs_stage_t *stage,
|
||||
ecs_id_t id,
|
||||
ecs_entity_t action)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_cmd_t *cmd = flecs_cmd_alloc(stage);
|
||||
cmd->kind = EcsOpOnDeleteAction;
|
||||
cmd->id = id;
|
||||
cmd->entity = action;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_enable(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_id_t id,
|
||||
bool enable)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false);
|
||||
if (cmd) {
|
||||
cmd->kind = enable ? EcsOpEnable : EcsOpDisable;
|
||||
cmd->entity = entity;
|
||||
cmd->id = id;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_bulk_new(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage,
|
||||
int32_t count,
|
||||
ecs_id_t id,
|
||||
const ecs_entity_t **ids_out)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t));
|
||||
|
||||
/* Use ecs_new_id as this is thread safe */
|
||||
int i;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ids[i] = ecs_new_id(world);
|
||||
}
|
||||
|
||||
*ids_out = ids;
|
||||
|
||||
/* Store data in op */
|
||||
ecs_cmd_t *cmd = flecs_cmd_alloc(stage);
|
||||
if (cmd) {
|
||||
cmd->kind = EcsOpBulkNew;
|
||||
cmd->id = id;
|
||||
cmd->is._n.entities = ids;
|
||||
cmd->is._n.count = count;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_add(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_id_t id)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
||||
if (cmd) {
|
||||
cmd->kind = EcsOpAdd;
|
||||
cmd->id = id;
|
||||
cmd->entity = entity;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flecs_defer_remove(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_id_t id)
|
||||
{
|
||||
if (flecs_defer_cmd(stage)) {
|
||||
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
||||
if (cmd) {
|
||||
cmd->kind = EcsOpRemove;
|
||||
cmd->id = id;
|
||||
cmd->entity = entity;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void* flecs_defer_set(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage,
|
||||
ecs_cmd_kind_t cmd_kind,
|
||||
ecs_entity_t entity,
|
||||
ecs_id_t id,
|
||||
ecs_size_t size,
|
||||
void *value,
|
||||
bool need_value)
|
||||
{
|
||||
ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true);
|
||||
if (!cmd) {
|
||||
if (need_value) {
|
||||
/* Entity is deleted by a previous command, but we still need to
|
||||
* return a temporary storage to the application. */
|
||||
cmd_kind = EcsOpSkip;
|
||||
} else {
|
||||
/* No value needs to be returned, we can drop the command */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find type info for id */
|
||||
const ecs_type_info_t *ti = NULL;
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
/* If idr doesn't exist yet, create it but only if the
|
||||
* application is not multithreaded. */
|
||||
if (stage->async || (world->flags & EcsWorldMultiThreaded)) {
|
||||
ti = ecs_get_type_info(world, id);
|
||||
ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
} else {
|
||||
/* When not in multi threaded mode, it's safe to find or
|
||||
* create the id record. */
|
||||
idr = flecs_id_record_ensure(world, id);
|
||||
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* Get type_info from id record. We could have called
|
||||
* ecs_get_type_info directly, but since this function can be
|
||||
* expensive for pairs, creating the id record ensures we can
|
||||
* find the type_info quickly for subsequent operations. */
|
||||
ti = idr->type_info;
|
||||
}
|
||||
} else {
|
||||
ti = idr->type_info;
|
||||
}
|
||||
|
||||
/* If the id isn't associated with a type, we can't set anything */
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
/* Make sure the size of the value equals the type size */
|
||||
ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL);
|
||||
size = ti->size;
|
||||
|
||||
/* Find existing component. Make sure it's owned, so that we won't use the
|
||||
* component of a prefab. */
|
||||
void *existing = NULL;
|
||||
ecs_table_t *table = NULL;
|
||||
if (idr) {
|
||||
/* Entity can only have existing component if id record exists */
|
||||
ecs_record_t *r = flecs_entities_get(world, entity);
|
||||
table = r->table;
|
||||
if (r && table) {
|
||||
const ecs_table_record_t *tr = flecs_id_record_get_table(
|
||||
idr, table);
|
||||
if (tr) {
|
||||
ecs_assert(tr->column != -1, ECS_NOT_A_COMPONENT, NULL);
|
||||
/* Entity has the component */
|
||||
ecs_vec_t *column = &table->data.columns[tr->column].data;
|
||||
existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Get existing value from storage */
|
||||
void *cmd_value = existing;
|
||||
bool emplace = cmd_kind == EcsOpEmplace;
|
||||
|
||||
/* If the component does not yet exist, create a temporary value. This is
|
||||
* necessary so we can store a component value in the deferred command,
|
||||
* without adding the component to the entity which is not allowed in
|
||||
* deferred mode. */
|
||||
if (!existing) {
|
||||
ecs_stack_t *stack = &stage->cmd->stack;
|
||||
cmd_value = flecs_stack_alloc(stack, size, ti->alignment);
|
||||
|
||||
/* If the component doesn't yet exist, construct it and move the
|
||||
* provided value into the component, if provided. Don't construct if
|
||||
* this is an emplace operation, in which case the application is
|
||||
* responsible for constructing. */
|
||||
if (value) {
|
||||
if (emplace) {
|
||||
ecs_move_t move = ti->hooks.move_ctor;
|
||||
if (move) {
|
||||
move(cmd_value, value, 1, ti);
|
||||
} else {
|
||||
ecs_os_memcpy(cmd_value, value, size);
|
||||
}
|
||||
} else {
|
||||
ecs_copy_t copy = ti->hooks.copy_ctor;
|
||||
if (copy) {
|
||||
copy(cmd_value, value, 1, ti);
|
||||
} else {
|
||||
ecs_os_memcpy(cmd_value, value, size);
|
||||
}
|
||||
}
|
||||
} else if (!emplace) {
|
||||
/* If the command is not an emplace, construct the temp storage */
|
||||
|
||||
/* Check if entity inherits component */
|
||||
void *base = NULL;
|
||||
if (table && (table->flags & EcsTableHasIsA)) {
|
||||
base = flecs_get_base_component(world, table, id, idr, 0);
|
||||
}
|
||||
|
||||
if (!base) {
|
||||
/* Normal ctor */
|
||||
ecs_xtor_t ctor = ti->hooks.ctor;
|
||||
if (ctor) {
|
||||
ctor(cmd_value, 1, ti);
|
||||
}
|
||||
} else {
|
||||
/* Override */
|
||||
ecs_copy_t copy = ti->hooks.copy_ctor;
|
||||
if (copy) {
|
||||
copy(cmd_value, base, 1, ti);
|
||||
} else {
|
||||
ecs_os_memcpy(cmd_value, base, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (value) {
|
||||
/* If component exists and value is provided, copy */
|
||||
ecs_copy_t copy = ti->hooks.copy;
|
||||
if (copy) {
|
||||
copy(existing, value, 1, ti);
|
||||
} else {
|
||||
ecs_os_memcpy(existing, value, size);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cmd) {
|
||||
/* If cmd is NULL, entity was already deleted. Check if we need to
|
||||
* insert a command into the queue. */
|
||||
if (!ti->hooks.dtor) {
|
||||
/* If temporary memory does not need to be destructed, it'll get
|
||||
* freed when the stack allocator is reset. This prevents us
|
||||
* from having to insert a command when the entity was
|
||||
* already deleted. */
|
||||
return cmd_value;
|
||||
}
|
||||
cmd = flecs_cmd_alloc(stage);
|
||||
}
|
||||
|
||||
if (!existing) {
|
||||
/* If component didn't exist yet, insert command that will create it */
|
||||
cmd->kind = cmd_kind;
|
||||
cmd->id = id;
|
||||
cmd->idr = idr;
|
||||
cmd->entity = entity;
|
||||
cmd->is._1.size = size;
|
||||
cmd->is._1.value = cmd_value;
|
||||
} else {
|
||||
/* If component already exists, still insert an Add command to ensure
|
||||
* that any preceding remove commands won't remove the component. If the
|
||||
* operation is a set, also insert a Modified command. */
|
||||
if (cmd_kind == EcsOpSet) {
|
||||
cmd->kind = EcsOpAddModified;
|
||||
} else {
|
||||
cmd->kind = EcsOpAdd;
|
||||
}
|
||||
cmd->id = id;
|
||||
cmd->entity = entity;
|
||||
}
|
||||
|
||||
return cmd_value;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void flecs_stage_merge_post_frame(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage)
|
||||
{
|
||||
/* Execute post frame actions */
|
||||
int32_t i, count = ecs_vec_count(&stage->post_frame_actions);
|
||||
ecs_action_elem_t *elems = ecs_vec_first(&stage->post_frame_actions);
|
||||
for (i = 0; i < count; i ++) {
|
||||
elems[i].action(world, elems[i].ctx);
|
||||
}
|
||||
|
||||
ecs_vec_clear(&stage->post_frame_actions);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_commands_init(
|
||||
ecs_stage_t *stage,
|
||||
ecs_commands_t *cmd)
|
||||
{
|
||||
flecs_stack_init(&cmd->stack);
|
||||
ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0);
|
||||
flecs_sparse_init_t(&cmd->entries, &stage->allocator,
|
||||
&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_commands_fini(
|
||||
ecs_stage_t *stage,
|
||||
ecs_commands_t *cmd)
|
||||
{
|
||||
/* Make sure stage has no unmerged data */
|
||||
ecs_assert(ecs_vec_count(&stage->cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
flecs_stack_fini(&cmd->stack);
|
||||
ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t);
|
||||
flecs_sparse_fini(&cmd->entries);
|
||||
}
|
||||
|
||||
void flecs_commands_push(
|
||||
ecs_stage_t *stage)
|
||||
{
|
||||
int32_t sp = ++ stage->cmd_sp;
|
||||
ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL);
|
||||
stage->cmd = &stage->cmd_stack[sp];
|
||||
}
|
||||
|
||||
void flecs_commands_pop(
|
||||
ecs_stage_t *stage)
|
||||
{
|
||||
int32_t sp = -- stage->cmd_sp;
|
||||
ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
stage->cmd = &stage->cmd_stack[sp];
|
||||
}
|
||||
|
||||
void flecs_stage_init(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_poly_init(stage, ecs_stage_t);
|
||||
|
||||
stage->world = world;
|
||||
stage->thread_ctx = world;
|
||||
stage->auto_merge = true;
|
||||
stage->async = false;
|
||||
|
||||
flecs_stack_init(&stage->allocators.iter_stack);
|
||||
flecs_stack_init(&stage->allocators.deser_stack);
|
||||
flecs_allocator_init(&stage->allocator);
|
||||
flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t,
|
||||
FLECS_SPARSE_PAGE_SIZE);
|
||||
|
||||
ecs_allocator_t *a = &stage->allocator;
|
||||
ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0);
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) {
|
||||
flecs_commands_init(stage, &stage->cmd_stack[i]);
|
||||
}
|
||||
|
||||
stage->cmd = &stage->cmd_stack[0];
|
||||
}
|
||||
|
||||
void flecs_stage_fini(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage)
|
||||
{
|
||||
(void)world;
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_poly_assert(stage, ecs_stage_t);
|
||||
|
||||
ecs_poly_fini(stage, ecs_stage_t);
|
||||
|
||||
ecs_allocator_t *a = &stage->allocator;
|
||||
|
||||
ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t);
|
||||
ecs_vec_fini(NULL, &stage->variables, 0);
|
||||
ecs_vec_fini(NULL, &stage->operations, 0);
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) {
|
||||
flecs_commands_fini(stage, &stage->cmd_stack[i]);
|
||||
}
|
||||
|
||||
flecs_stack_fini(&stage->allocators.iter_stack);
|
||||
flecs_stack_fini(&stage->allocators.deser_stack);
|
||||
flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk);
|
||||
flecs_allocator_fini(&stage->allocator);
|
||||
}
|
||||
|
||||
void ecs_set_stage_count(
|
||||
ecs_world_t *world,
|
||||
int32_t stage_count)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
|
||||
/* World must have at least one default stage */
|
||||
ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini),
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
bool auto_merge = true;
|
||||
const ecs_entity_t *lookup_path = NULL;
|
||||
ecs_entity_t scope = 0;
|
||||
ecs_entity_t with = 0;
|
||||
if (world->stage_count >= 1) {
|
||||
auto_merge = world->stages[0].auto_merge;
|
||||
lookup_path = world->stages[0].lookup_path;
|
||||
scope = world->stages[0].scope;
|
||||
with = world->stages[0].with;
|
||||
}
|
||||
|
||||
int32_t i, count = world->stage_count;
|
||||
if (count && count != stage_count) {
|
||||
ecs_stage_t *stages = world->stages;
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
/* If stage contains a thread handle, ecs_set_threads was used to
|
||||
* create the stages. ecs_set_threads and ecs_set_stage_count should not
|
||||
* be mixed. */
|
||||
ecs_poly_assert(&stages[i], ecs_stage_t);
|
||||
ecs_check(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL);
|
||||
flecs_stage_fini(world, &stages[i]);
|
||||
}
|
||||
|
||||
ecs_os_free(world->stages);
|
||||
}
|
||||
|
||||
if (stage_count) {
|
||||
world->stages = ecs_os_malloc_n(ecs_stage_t, stage_count);
|
||||
|
||||
for (i = 0; i < stage_count; i ++) {
|
||||
ecs_stage_t *stage = &world->stages[i];
|
||||
flecs_stage_init(world, stage);
|
||||
stage->id = i;
|
||||
|
||||
/* Set thread_ctx to stage, as this stage might be used in a
|
||||
* multithreaded context */
|
||||
stage->thread_ctx = (ecs_world_t*)stage;
|
||||
stage->thread = 0;
|
||||
}
|
||||
} else {
|
||||
/* Set to NULL to prevent double frees */
|
||||
world->stages = NULL;
|
||||
}
|
||||
|
||||
/* Regardless of whether the stage was just initialized or not, when the
|
||||
* ecs_set_stage_count function is called, all stages inherit the auto_merge
|
||||
* property from the world */
|
||||
for (i = 0; i < stage_count; i ++) {
|
||||
world->stages[i].auto_merge = auto_merge;
|
||||
world->stages[i].lookup_path = lookup_path;
|
||||
world->stages[0].scope = scope;
|
||||
world->stages[0].with = with;
|
||||
}
|
||||
|
||||
world->stage_count = stage_count;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t ecs_get_stage_count(
|
||||
const ecs_world_t *world)
|
||||
{
|
||||
world = ecs_get_world(world);
|
||||
return world->stage_count;
|
||||
}
|
||||
|
||||
int32_t ecs_get_stage_id(
|
||||
const ecs_world_t *world)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
if (ecs_poly_is(world, ecs_stage_t)) {
|
||||
ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world);
|
||||
|
||||
/* Index 0 is reserved for main stage */
|
||||
return stage->id;
|
||||
} else if (ecs_poly_is(world, ecs_world_t)) {
|
||||
return 0;
|
||||
} else {
|
||||
ecs_throw(ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecs_world_t* ecs_get_stage(
|
||||
const ecs_world_t *world,
|
||||
int32_t stage_id)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL);
|
||||
return (ecs_world_t*)&world->stages[stage_id];
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ecs_readonly_begin(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
|
||||
flecs_process_pending_tables(world);
|
||||
|
||||
ecs_dbg_3("#[bold]readonly");
|
||||
ecs_log_push_3();
|
||||
|
||||
int32_t i, count = ecs_get_stage_count(world);
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_stage_t *stage = &world->stages[i];
|
||||
stage->lookup_path = world->stages[0].lookup_path;
|
||||
ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION,
|
||||
"deferred mode cannot be enabled when entering readonly mode");
|
||||
flecs_defer_begin(world, stage);
|
||||
}
|
||||
|
||||
bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly);
|
||||
|
||||
/* From this point on, the world is "locked" for mutations, and it is only
|
||||
* allowed to enqueue commands from stages */
|
||||
ECS_BIT_SET(world->flags, EcsWorldReadonly);
|
||||
|
||||
/* If world has more than one stage, signal we might be running on multiple
|
||||
* threads. This is a stricter version of readonly mode: while some
|
||||
* mutations like implicit component registration are still allowed in plain
|
||||
* readonly mode, no mutations are allowed when multithreaded. */
|
||||
if (world->worker_cond) {
|
||||
ECS_BIT_SET(world->flags, EcsWorldMultiThreaded);
|
||||
}
|
||||
|
||||
return is_readonly;
|
||||
}
|
||||
|
||||
void ecs_readonly_end(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL);
|
||||
|
||||
/* After this it is safe again to mutate the world directly */
|
||||
ECS_BIT_CLEAR(world->flags, EcsWorldReadonly);
|
||||
ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded);
|
||||
|
||||
ecs_log_pop_3();
|
||||
|
||||
flecs_stage_auto_merge(world);
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void ecs_merge(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_check(ecs_poly_is(world, ecs_world_t) ||
|
||||
ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL);
|
||||
flecs_stage_manual_merge(world);
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void ecs_set_automerge(
|
||||
ecs_world_t *world,
|
||||
bool auto_merge)
|
||||
{
|
||||
/* If a world is provided, set auto_merge globally for the world. This
|
||||
* doesn't actually do anything (the main stage never merges) but it serves
|
||||
* as the default for when stages are created. */
|
||||
if (ecs_poly_is(world, ecs_world_t)) {
|
||||
world->stages[0].auto_merge = auto_merge;
|
||||
|
||||
/* Propagate change to all stages */
|
||||
int i, stage_count = ecs_get_stage_count(world);
|
||||
for (i = 0; i < stage_count; i ++) {
|
||||
ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i);
|
||||
stage->auto_merge = auto_merge;
|
||||
}
|
||||
|
||||
/* If a stage is provided, override the auto_merge value for the individual
|
||||
* stage. This allows an application to control per-stage which stage should
|
||||
* be automatically merged and which one shouldn't */
|
||||
} else {
|
||||
ecs_poly_assert(world, ecs_stage_t);
|
||||
ecs_stage_t *stage = (ecs_stage_t*)world;
|
||||
stage->auto_merge = auto_merge;
|
||||
}
|
||||
}
|
||||
|
||||
bool ecs_stage_is_readonly(
|
||||
const ecs_world_t *stage)
|
||||
{
|
||||
const ecs_world_t *world = ecs_get_world(stage);
|
||||
|
||||
if (ecs_poly_is(stage, ecs_stage_t)) {
|
||||
if (((const ecs_stage_t*)stage)->async) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (world->flags & EcsWorldReadonly) {
|
||||
if (ecs_poly_is(stage, ecs_world_t)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (ecs_poly_is(stage, ecs_stage_t)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_world_t* ecs_async_stage_new(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t));
|
||||
flecs_stage_init(world, stage);
|
||||
|
||||
stage->id = -1;
|
||||
stage->auto_merge = false;
|
||||
stage->async = true;
|
||||
|
||||
flecs_defer_begin(world, stage);
|
||||
|
||||
return (ecs_world_t*)stage;
|
||||
}
|
||||
|
||||
void ecs_async_stage_free(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_stage_t);
|
||||
ecs_stage_t *stage = (ecs_stage_t*)world;
|
||||
ecs_check(stage->async == true, ECS_INVALID_PARAMETER, NULL);
|
||||
flecs_stage_fini(stage->world, stage);
|
||||
ecs_os_free(stage);
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
bool ecs_stage_is_async(
|
||||
ecs_world_t *stage)
|
||||
{
|
||||
if (!stage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ecs_poly_is(stage, ecs_stage_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((ecs_stage_t*)stage)->async;
|
||||
}
|
||||
|
||||
bool ecs_is_deferred(
|
||||
const ecs_world_t *world)
|
||||
{
|
||||
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
const ecs_stage_t *stage = flecs_stage_from_readonly_world(world);
|
||||
return stage->defer > 0;
|
||||
error:
|
||||
return false;
|
||||
}
|
||||
108
engine/libs/flecs/src/stage.h
Normal file
108
engine/libs/flecs/src/stage.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @file stage.h
|
||||
* @brief Stage functions.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_STAGE_H
|
||||
#define FLECS_STAGE_H
|
||||
|
||||
/* Initialize stage data structures */
|
||||
void flecs_stage_init(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage);
|
||||
|
||||
/* Deinitialize stage */
|
||||
void flecs_stage_fini(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage);
|
||||
|
||||
/* Post-frame merge actions */
|
||||
void flecs_stage_merge_post_frame(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage);
|
||||
|
||||
bool flecs_defer_cmd(
|
||||
ecs_stage_t *stage);
|
||||
|
||||
bool flecs_defer_begin(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage);
|
||||
|
||||
bool flecs_defer_modified(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t component);
|
||||
|
||||
bool flecs_defer_clone(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t src,
|
||||
bool clone_value);
|
||||
|
||||
bool flecs_defer_bulk_new(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage,
|
||||
int32_t count,
|
||||
ecs_id_t id,
|
||||
const ecs_entity_t **ids_out);
|
||||
|
||||
bool flecs_defer_path(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t parent,
|
||||
ecs_entity_t entity,
|
||||
const char *name);
|
||||
|
||||
bool flecs_defer_delete(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity);
|
||||
|
||||
bool flecs_defer_clear(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity);
|
||||
|
||||
bool flecs_defer_on_delete_action(
|
||||
ecs_stage_t *stage,
|
||||
ecs_id_t id,
|
||||
ecs_entity_t action);
|
||||
|
||||
bool flecs_defer_enable(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t component,
|
||||
bool enable);
|
||||
|
||||
bool flecs_defer_add(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_id_t id);
|
||||
|
||||
bool flecs_defer_remove(
|
||||
ecs_stage_t *stage,
|
||||
ecs_entity_t entity,
|
||||
ecs_id_t id);
|
||||
|
||||
void* flecs_defer_set(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage,
|
||||
ecs_cmd_kind_t op_kind,
|
||||
ecs_entity_t entity,
|
||||
ecs_entity_t component,
|
||||
ecs_size_t size,
|
||||
void *value,
|
||||
bool need_value);
|
||||
|
||||
bool flecs_defer_end(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage);
|
||||
|
||||
bool flecs_defer_purge(
|
||||
ecs_world_t *world,
|
||||
ecs_stage_t *stage);
|
||||
|
||||
void flecs_commands_push(
|
||||
ecs_stage_t *stage);
|
||||
|
||||
void flecs_commands_pop(
|
||||
ecs_stage_t *stage);
|
||||
|
||||
#endif
|
||||
388
engine/libs/flecs/src/storage/entity_index.c
Normal file
388
engine/libs/flecs/src/storage/entity_index.c
Normal file
@@ -0,0 +1,388 @@
|
||||
#include "../private_api.h"
|
||||
|
||||
static
|
||||
ecs_entity_index_page_t* flecs_entity_index_ensure_page(
|
||||
ecs_entity_index_t *index,
|
||||
uint32_t id)
|
||||
{
|
||||
int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS);
|
||||
if (page_index >= ecs_vec_count(&index->pages)) {
|
||||
ecs_vec_set_min_count_zeromem_t(index->allocator, &index->pages,
|
||||
ecs_entity_index_page_t*, page_index + 1);
|
||||
}
|
||||
|
||||
ecs_entity_index_page_t **page_ptr = ecs_vec_get_t(&index->pages,
|
||||
ecs_entity_index_page_t*, page_index);
|
||||
ecs_entity_index_page_t *page = *page_ptr;
|
||||
if (!page) {
|
||||
page = *page_ptr = flecs_bcalloc(&index->page_allocator);
|
||||
ecs_assert(page != NULL, ECS_OUT_OF_MEMORY, NULL);
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
void flecs_entity_index_init(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_entity_index_t *index)
|
||||
{
|
||||
index->allocator = allocator;
|
||||
index->alive_count = 1;
|
||||
ecs_vec_init_t(allocator, &index->dense, uint64_t, 1);
|
||||
ecs_vec_set_count_t(allocator, &index->dense, uint64_t, 1);
|
||||
ecs_vec_init_t(allocator, &index->pages, ecs_entity_index_page_t*, 0);
|
||||
flecs_ballocator_init(&index->page_allocator,
|
||||
ECS_SIZEOF(ecs_entity_index_page_t));
|
||||
}
|
||||
|
||||
void flecs_entity_index_fini(
|
||||
ecs_entity_index_t *index)
|
||||
{
|
||||
ecs_vec_fini_t(index->allocator, &index->dense, uint64_t);
|
||||
#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC)
|
||||
int32_t i, count = ecs_vec_count(&index->pages);
|
||||
ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages);
|
||||
for (i = 0; i < count; i ++) {
|
||||
flecs_bfree(&index->page_allocator, pages[i]);
|
||||
}
|
||||
#endif
|
||||
ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*);
|
||||
flecs_ballocator_fini(&index->page_allocator);
|
||||
}
|
||||
|
||||
ecs_record_t* flecs_entity_index_get_any(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
uint32_t id = (uint32_t)entity;
|
||||
int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS);
|
||||
ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages,
|
||||
ecs_entity_index_page_t*, page_index)[0];
|
||||
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK];
|
||||
ecs_assert(r->dense != 0, ECS_INVALID_PARAMETER, NULL);
|
||||
return r;
|
||||
}
|
||||
|
||||
ecs_record_t* flecs_entity_index_get(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
ecs_record_t *r = flecs_entity_index_get_any(index, entity);
|
||||
ecs_assert(r->dense < index->alive_count, ECS_INVALID_PARAMETER, NULL);
|
||||
ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity,
|
||||
ECS_INVALID_PARAMETER, NULL);
|
||||
return r;
|
||||
}
|
||||
|
||||
ecs_record_t* flecs_entity_index_try_get_any(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
uint32_t id = (uint32_t)entity;
|
||||
int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS);
|
||||
if (page_index >= ecs_vec_count(&index->pages)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages,
|
||||
ecs_entity_index_page_t*, page_index)[0];
|
||||
if (!page) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK];
|
||||
if (!r->dense) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
ecs_record_t* flecs_entity_index_try_get(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
ecs_record_t *r = flecs_entity_index_try_get_any(index, entity);
|
||||
if (r) {
|
||||
if (r->dense >= index->alive_count) {
|
||||
return NULL;
|
||||
}
|
||||
if (ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] != entity) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
ecs_record_t* flecs_entity_index_ensure(
|
||||
ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
uint32_t id = (uint32_t)entity;
|
||||
ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id);
|
||||
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK];
|
||||
|
||||
int32_t dense = r->dense;
|
||||
if (dense) {
|
||||
/* Entity is already alive, nothing to be done */
|
||||
if (dense < index->alive_count) {
|
||||
ecs_assert(
|
||||
ecs_vec_get_t(&index->dense, uint64_t, dense)[0] == entity,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
/* Entity doesn't have a dense index yet */
|
||||
ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = entity;
|
||||
r->dense = dense = ecs_vec_count(&index->dense) - 1;
|
||||
index->max_id = id > index->max_id ? id : index->max_id;
|
||||
}
|
||||
|
||||
ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* Entity is not alive, swap with first not alive element */
|
||||
uint64_t *ids = ecs_vec_first(&index->dense);
|
||||
uint64_t e_swap = ids[index->alive_count];
|
||||
ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap);
|
||||
ecs_assert(r_swap->dense == index->alive_count,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
r_swap->dense = dense;
|
||||
r->dense = index->alive_count;
|
||||
ids[dense] = e_swap;
|
||||
ids[index->alive_count ++] = entity;
|
||||
|
||||
ecs_assert(flecs_entity_index_is_alive(index, entity),
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void flecs_entity_index_remove(
|
||||
ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
ecs_record_t *r = flecs_entity_index_try_get(index, entity);
|
||||
if (!r) {
|
||||
/* Entity is not alive or doesn't exist, nothing to be done */
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t dense = r->dense;
|
||||
int32_t i_swap = -- index->alive_count;
|
||||
uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap);
|
||||
uint64_t e_swap = e_swap_ptr[0];
|
||||
ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap);
|
||||
ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
r_swap->dense = dense;
|
||||
r->table = NULL;
|
||||
r->idr = NULL;
|
||||
r->row = 0;
|
||||
r->dense = i_swap;
|
||||
ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = e_swap;
|
||||
e_swap_ptr[0] = ECS_GENERATION_INC(entity);
|
||||
ecs_assert(!flecs_entity_index_is_alive(index, entity),
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
void flecs_entity_index_set_generation(
|
||||
ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
ecs_record_t *r = flecs_entity_index_try_get_any(index, entity);
|
||||
if (r) {
|
||||
ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] = entity;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t flecs_entity_index_get_generation(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
ecs_record_t *r = flecs_entity_index_try_get_any(index, entity);
|
||||
if (r) {
|
||||
return ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool flecs_entity_index_is_alive(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
return flecs_entity_index_try_get(index, entity) != NULL;
|
||||
}
|
||||
|
||||
bool flecs_entity_index_is_valid(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
uint32_t id = (uint32_t)entity;
|
||||
ecs_record_t *r = flecs_entity_index_try_get_any(index, id);
|
||||
if (!r || !r->dense) {
|
||||
/* Doesn't exist yet, so is valid */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the id exists, it must be alive */
|
||||
return r->dense < index->alive_count;
|
||||
}
|
||||
|
||||
bool flecs_entity_index_exists(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity)
|
||||
{
|
||||
return flecs_entity_index_try_get_any(index, entity) != NULL;
|
||||
}
|
||||
|
||||
uint64_t flecs_entity_index_new_id(
|
||||
ecs_entity_index_t *index)
|
||||
{
|
||||
if (index->alive_count != ecs_vec_count(&index->dense)) {
|
||||
/* Recycle id */
|
||||
return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0];
|
||||
}
|
||||
|
||||
/* Create new id */
|
||||
uint32_t id = (uint32_t)++ index->max_id;
|
||||
ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id;
|
||||
|
||||
ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id);
|
||||
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK];
|
||||
r->dense = index->alive_count ++;
|
||||
ecs_assert(index->alive_count == ecs_vec_count(&index->dense),
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
uint64_t* flecs_entity_index_new_ids(
|
||||
ecs_entity_index_t *index,
|
||||
int32_t count)
|
||||
{
|
||||
int32_t alive_count = index->alive_count;
|
||||
int32_t new_count = alive_count + count;
|
||||
int32_t dense_count = ecs_vec_count(&index->dense);
|
||||
|
||||
if (new_count < dense_count) {
|
||||
/* Recycle ids */
|
||||
index->alive_count = new_count;
|
||||
return ecs_vec_get_t(&index->dense, uint64_t, alive_count);
|
||||
}
|
||||
|
||||
/* Allocate new ids */
|
||||
ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, new_count);
|
||||
int32_t i, to_add = new_count - dense_count;
|
||||
for (i = 0; i < to_add; i ++) {
|
||||
uint32_t id = (uint32_t)++ index->max_id;
|
||||
int32_t dense = dense_count + i;
|
||||
ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = id;
|
||||
ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id);
|
||||
ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK];
|
||||
r->dense = dense;
|
||||
}
|
||||
|
||||
index->alive_count = new_count;
|
||||
return ecs_vec_get_t(&index->dense, uint64_t, alive_count);
|
||||
}
|
||||
|
||||
void flecs_entity_index_set_size(
|
||||
ecs_entity_index_t *index,
|
||||
int32_t size)
|
||||
{
|
||||
ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size);
|
||||
}
|
||||
|
||||
int32_t flecs_entity_index_count(
|
||||
const ecs_entity_index_t *index)
|
||||
{
|
||||
return index->alive_count - 1;
|
||||
}
|
||||
|
||||
int32_t flecs_entity_index_size(
|
||||
const ecs_entity_index_t *index)
|
||||
{
|
||||
return ecs_vec_count(&index->dense) - 1;
|
||||
}
|
||||
|
||||
int32_t flecs_entity_index_not_alive_count(
|
||||
const ecs_entity_index_t *index)
|
||||
{
|
||||
return ecs_vec_count(&index->dense) - index->alive_count;
|
||||
}
|
||||
|
||||
void flecs_entity_index_clear(
|
||||
ecs_entity_index_t *index)
|
||||
{
|
||||
int32_t i, count = ecs_vec_count(&index->pages);
|
||||
ecs_entity_index_page_t **pages = ecs_vec_first_t(&index->pages,
|
||||
ecs_entity_index_page_t*);
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_index_page_t *page = pages[i];
|
||||
if (page) {
|
||||
ecs_os_zeromem(page);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1);
|
||||
|
||||
index->alive_count = 1;
|
||||
index->max_id = 0;
|
||||
}
|
||||
|
||||
const uint64_t* flecs_entity_index_ids(
|
||||
const ecs_entity_index_t *index)
|
||||
{
|
||||
return ecs_vec_get_t(&index->dense, uint64_t, 1);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_entity_index_copy_intern(
|
||||
ecs_entity_index_t * dst,
|
||||
const ecs_entity_index_t * src)
|
||||
{
|
||||
flecs_entity_index_set_size(dst, flecs_entity_index_size(src));
|
||||
const uint64_t *ids = flecs_entity_index_ids(src);
|
||||
|
||||
int32_t i, count = src->alive_count;
|
||||
for (i = 0; i < count - 1; i ++) {
|
||||
uint64_t id = ids[i];
|
||||
ecs_record_t *src_ptr = flecs_entity_index_get(src, id);
|
||||
ecs_record_t *dst_ptr = flecs_entity_index_ensure(dst, id);
|
||||
flecs_entity_index_set_generation(dst, id);
|
||||
ecs_os_memcpy_t(dst_ptr, src_ptr, ecs_record_t);
|
||||
}
|
||||
|
||||
dst->max_id = src->max_id;
|
||||
|
||||
ecs_assert(src->alive_count == dst->alive_count, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
void flecs_entity_index_copy(
|
||||
ecs_entity_index_t *dst,
|
||||
const ecs_entity_index_t *src)
|
||||
{
|
||||
if (!src) {
|
||||
return;
|
||||
}
|
||||
|
||||
flecs_entity_index_init(src->allocator, dst);
|
||||
flecs_entity_index_copy_intern(dst, src);
|
||||
}
|
||||
|
||||
void flecs_entity_index_restore(
|
||||
ecs_entity_index_t *dst,
|
||||
const ecs_entity_index_t *src)
|
||||
{
|
||||
if (!src) {
|
||||
return;
|
||||
}
|
||||
|
||||
flecs_entity_index_clear(dst);
|
||||
flecs_entity_index_copy_intern(dst, src);
|
||||
}
|
||||
158
engine/libs/flecs/src/storage/entity_index.h
Normal file
158
engine/libs/flecs/src/storage/entity_index.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* @file datastructures/entity_index.h
|
||||
* @brief Entity index data structure.
|
||||
*
|
||||
* The entity index stores the table, row for an entity id.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_ENTITY_INDEX_H
|
||||
#define FLECS_ENTITY_INDEX_H
|
||||
|
||||
#define FLECS_ENTITY_PAGE_SIZE (1 << FLECS_ENTITY_PAGE_BITS)
|
||||
#define FLECS_ENTITY_PAGE_MASK (FLECS_ENTITY_PAGE_SIZE - 1)
|
||||
|
||||
typedef struct ecs_entity_index_page_t {
|
||||
ecs_record_t records[FLECS_ENTITY_PAGE_SIZE];
|
||||
} ecs_entity_index_page_t;
|
||||
|
||||
typedef struct ecs_entity_index_t {
|
||||
ecs_vec_t dense;
|
||||
ecs_vec_t pages;
|
||||
int32_t alive_count;
|
||||
uint64_t max_id;
|
||||
ecs_block_allocator_t page_allocator;
|
||||
ecs_allocator_t *allocator;
|
||||
} ecs_entity_index_t;
|
||||
|
||||
/** Initialize entity index. */
|
||||
void flecs_entity_index_init(
|
||||
ecs_allocator_t *allocator,
|
||||
ecs_entity_index_t *index);
|
||||
|
||||
/** Deinitialize entity index. */
|
||||
void flecs_entity_index_fini(
|
||||
ecs_entity_index_t *index);
|
||||
|
||||
/* Get entity (must exist/must be alive) */
|
||||
ecs_record_t* flecs_entity_index_get(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Get entity (must exist/may not be alive) */
|
||||
ecs_record_t* flecs_entity_index_get_any(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Get entity (may not exist/must be alive) */
|
||||
ecs_record_t* flecs_entity_index_try_get(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Get entity (may not exist/may not be alive) */
|
||||
ecs_record_t* flecs_entity_index_try_get_any(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/** Ensure entity exists. */
|
||||
ecs_record_t* flecs_entity_index_ensure(
|
||||
ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Remove entity */
|
||||
void flecs_entity_index_remove(
|
||||
ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Set generation of entity */
|
||||
void flecs_entity_index_set_generation(
|
||||
ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Get current generation of entity */
|
||||
uint64_t flecs_entity_index_get_generation(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Return whether entity is alive */
|
||||
bool flecs_entity_index_is_alive(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Return whether entity is valid */
|
||||
bool flecs_entity_index_is_valid(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Return whether entity exists */
|
||||
bool flecs_entity_index_exists(
|
||||
const ecs_entity_index_t *index,
|
||||
uint64_t entity);
|
||||
|
||||
/* Create or recycle entity id */
|
||||
uint64_t flecs_entity_index_new_id(
|
||||
ecs_entity_index_t *index);
|
||||
|
||||
/* Bulk create or recycle new entity ids */
|
||||
uint64_t* flecs_entity_index_new_ids(
|
||||
ecs_entity_index_t *index,
|
||||
int32_t count);
|
||||
|
||||
/* Set size of index */
|
||||
void flecs_entity_index_set_size(
|
||||
ecs_entity_index_t *index,
|
||||
int32_t size);
|
||||
|
||||
/* Return number of entities in index */
|
||||
int32_t flecs_entity_index_count(
|
||||
const ecs_entity_index_t *index);
|
||||
|
||||
/* Return number of allocated entities in index */
|
||||
int32_t flecs_entity_index_size(
|
||||
const ecs_entity_index_t *index);
|
||||
|
||||
/* Return number of not alive entities in index */
|
||||
int32_t flecs_entity_index_not_alive_count(
|
||||
const ecs_entity_index_t *index);
|
||||
|
||||
/* Clear entity index */
|
||||
void flecs_entity_index_clear(
|
||||
ecs_entity_index_t *index);
|
||||
|
||||
/* Return number of alive entities in index */
|
||||
const uint64_t* flecs_entity_index_ids(
|
||||
const ecs_entity_index_t *index);
|
||||
|
||||
void flecs_entity_index_copy(
|
||||
ecs_entity_index_t *dst,
|
||||
const ecs_entity_index_t *src);
|
||||
|
||||
void flecs_entity_index_restore(
|
||||
ecs_entity_index_t *dst,
|
||||
const ecs_entity_index_t *src);
|
||||
|
||||
#define ecs_eis(world) (&((world)->store.entity_index))
|
||||
#define flecs_entities_init(world) flecs_entity_index_init(&world->allocator, ecs_eis(world))
|
||||
#define flecs_entities_fini(world) flecs_entity_index_fini(ecs_eis(world))
|
||||
#define flecs_entities_get(world, entity) flecs_entity_index_get(ecs_eis(world), entity)
|
||||
#define flecs_entities_try(world, entity) flecs_entity_index_try_get(ecs_eis(world), entity)
|
||||
#define flecs_entities_get_any(world, entity) flecs_entity_index_get_any(ecs_eis(world), entity)
|
||||
#define flecs_entities_ensure(world, entity) flecs_entity_index_ensure(ecs_eis(world), entity)
|
||||
#define flecs_entities_remove(world, entity) flecs_entity_index_remove(ecs_eis(world), entity)
|
||||
#define flecs_entities_set_generation(world, entity) flecs_entity_index_set_generation(ecs_eis(world), entity)
|
||||
#define flecs_entities_get_generation(world, entity) flecs_entity_index_get_generation(ecs_eis(world), entity)
|
||||
#define flecs_entities_is_alive(world, entity) flecs_entity_index_is_alive(ecs_eis(world), entity)
|
||||
#define flecs_entities_is_valid(world, entity) flecs_entity_index_is_valid(ecs_eis(world), entity)
|
||||
#define flecs_entities_exists(world, entity) flecs_entity_index_exists(ecs_eis(world), entity)
|
||||
#define flecs_entities_new_id(world) flecs_entity_index_new_id(ecs_eis(world))
|
||||
#define flecs_entities_new_ids(world, count) flecs_entity_index_new_ids(ecs_eis(world), count)
|
||||
#define flecs_entities_max_id(world) (ecs_eis(world)->max_id)
|
||||
#define flecs_entities_set_size(world, size) flecs_entity_index_set_size(ecs_eis(world), size)
|
||||
#define flecs_entities_count(world) flecs_entity_index_count(ecs_eis(world))
|
||||
#define flecs_entities_size(world) flecs_entity_index_size(ecs_eis(world))
|
||||
#define flecs_entities_not_alive_count(world) flecs_entity_index_not_alive_count(ecs_eis(world))
|
||||
#define flecs_entities_clear(world) flecs_entity_index_clear(ecs_eis(world))
|
||||
#define flecs_entities_ids(world) flecs_entity_index_ids(ecs_eis(world))
|
||||
#define flecs_entities_copy(dst, src) flecs_entity_index_copy(dst, src)
|
||||
#define flecs_entities_restore(dst, src) flecs_entity_index_restore(dst, src)
|
||||
|
||||
#endif
|
||||
614
engine/libs/flecs/src/storage/id_index.c
Normal file
614
engine/libs/flecs/src/storage/id_index.c
Normal file
@@ -0,0 +1,614 @@
|
||||
/**
|
||||
* @file id_index.c
|
||||
* @brief Index for looking up tables by (component) id.
|
||||
*
|
||||
* An id record stores the administration for an in use (component) id, that is
|
||||
* an id that has been used in tables.
|
||||
*
|
||||
* An id record contains a table cache, which stores the list of tables that
|
||||
* have the id. Each entry in the cache (a table record) stores the first
|
||||
* occurrence of the id in the table and the number of occurrences of the id in
|
||||
* the table (in the case of wildcard ids).
|
||||
*
|
||||
* Id records are used in lots of scenarios, like uncached queries, or for
|
||||
* getting a component array/component for an entity.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
static
|
||||
ecs_id_record_elem_t* flecs_id_record_elem(
|
||||
ecs_id_record_t *head,
|
||||
ecs_id_record_elem_t *list,
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_id_record_elem_insert(
|
||||
ecs_id_record_t *head,
|
||||
ecs_id_record_t *idr,
|
||||
ecs_id_record_elem_t *elem)
|
||||
{
|
||||
ecs_id_record_elem_t *head_elem = flecs_id_record_elem(idr, elem, head);
|
||||
ecs_id_record_t *cur = head_elem->next;
|
||||
elem->next = cur;
|
||||
elem->prev = head;
|
||||
if (cur) {
|
||||
ecs_id_record_elem_t *cur_elem = flecs_id_record_elem(idr, elem, cur);
|
||||
cur_elem->prev = idr;
|
||||
}
|
||||
head_elem->next = idr;
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_id_record_elem_remove(
|
||||
ecs_id_record_t *idr,
|
||||
ecs_id_record_elem_t *elem)
|
||||
{
|
||||
ecs_id_record_t *prev = elem->prev;
|
||||
ecs_id_record_t *next = elem->next;
|
||||
ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_id_record_elem_t *prev_elem = flecs_id_record_elem(idr, elem, prev);
|
||||
prev_elem->next = next;
|
||||
if (next) {
|
||||
ecs_id_record_elem_t *next_elem = flecs_id_record_elem(idr, elem, next);
|
||||
next_elem->prev = prev;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_insert_id_elem(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr,
|
||||
ecs_id_t wildcard,
|
||||
ecs_id_record_t *widr)
|
||||
{
|
||||
ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL);
|
||||
if (!widr) {
|
||||
widr = flecs_id_record_ensure(world, wildcard);
|
||||
}
|
||||
ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) {
|
||||
ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
flecs_id_record_elem_insert(widr, idr, &idr->first);
|
||||
} else {
|
||||
ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
flecs_id_record_elem_insert(widr, idr, &idr->second);
|
||||
|
||||
if (idr->flags & EcsIdTraversable) {
|
||||
flecs_id_record_elem_insert(widr, idr, &idr->trav);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_remove_id_elem(
|
||||
ecs_id_record_t *idr,
|
||||
ecs_id_t wildcard)
|
||||
{
|
||||
ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) {
|
||||
ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
flecs_id_record_elem_remove(idr, &idr->first);
|
||||
} else {
|
||||
ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
flecs_id_record_elem_remove(idr, &idr->second);
|
||||
|
||||
if (idr->flags & EcsIdTraversable) {
|
||||
flecs_id_record_elem_remove(idr, &idr->trav);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
ecs_id_t flecs_id_record_hash(
|
||||
ecs_id_t id)
|
||||
{
|
||||
id = ecs_strip_generation(id);
|
||||
if (ECS_IS_PAIR(id)) {
|
||||
ecs_entity_t r = ECS_PAIR_FIRST(id);
|
||||
ecs_entity_t o = ECS_PAIR_SECOND(id);
|
||||
if (r == EcsAny) {
|
||||
r = EcsWildcard;
|
||||
}
|
||||
if (o == EcsAny) {
|
||||
o = EcsWildcard;
|
||||
}
|
||||
id = ecs_pair(r, o);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
static
|
||||
ecs_id_record_t* flecs_id_record_new(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_id_record_t *idr, *idr_t = NULL;
|
||||
ecs_id_t hash = flecs_id_record_hash(id);
|
||||
if (hash >= FLECS_HI_ID_RECORD_ID) {
|
||||
idr = flecs_bcalloc(&world->allocators.id_record);
|
||||
ecs_map_insert_ptr(&world->id_index_hi, hash, idr);
|
||||
} else {
|
||||
idr = &world->id_index_lo[hash];
|
||||
ecs_os_zeromem(idr);
|
||||
}
|
||||
|
||||
ecs_table_cache_init(world, &idr->cache);
|
||||
|
||||
idr->id = id;
|
||||
idr->refcount = 1;
|
||||
idr->reachable.current = -1;
|
||||
|
||||
bool is_wildcard = ecs_id_is_wildcard(id);
|
||||
bool is_pair = ECS_IS_PAIR(id);
|
||||
|
||||
ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK;
|
||||
if (is_pair) {
|
||||
// rel = ecs_pair_first(world, id);
|
||||
rel = ECS_PAIR_FIRST(id);
|
||||
ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* Relationship object can be 0, as tables without a ChildOf
|
||||
* relationship are added to the (ChildOf, 0) id record */
|
||||
tgt = ECS_PAIR_SECOND(id);
|
||||
|
||||
#ifdef FLECS_DEBUG
|
||||
/* Check constraints */
|
||||
if (tgt) {
|
||||
tgt = ecs_get_alive(world, tgt);
|
||||
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
if (tgt && !ecs_id_is_wildcard(tgt)) {
|
||||
/* Check if target of relationship satisfies OneOf property */
|
||||
ecs_entity_t oneof = flecs_get_oneof(world, rel);
|
||||
ecs_check( !oneof || ecs_has_pair(world, tgt, EcsChildOf, oneof),
|
||||
ECS_CONSTRAINT_VIOLATED, NULL);
|
||||
(void)oneof;
|
||||
|
||||
/* Check if we're not trying to inherit from a final target */
|
||||
if (rel == EcsIsA) {
|
||||
bool is_final = ecs_has_id(world, tgt, EcsFinal);
|
||||
ecs_check(!is_final, ECS_CONSTRAINT_VIOLATED,
|
||||
"cannot inherit from final entity");
|
||||
(void)is_final;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!is_wildcard && (rel != EcsFlag)) {
|
||||
/* Inherit flags from (relationship, *) record */
|
||||
ecs_id_record_t *idr_r = flecs_id_record_ensure(
|
||||
world, ecs_pair(rel, EcsWildcard));
|
||||
idr->parent = idr_r;
|
||||
idr->flags = idr_r->flags;
|
||||
|
||||
/* If pair is not a wildcard, append it to wildcard lists. These
|
||||
* allow for quickly enumerating all relationships for an object,
|
||||
* or all objecs for a relationship. */
|
||||
flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r);
|
||||
|
||||
idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt));
|
||||
flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t);
|
||||
|
||||
if (rel == EcsUnion) {
|
||||
idr->flags |= EcsIdUnion;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rel = id & ECS_COMPONENT_MASK;
|
||||
ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
/* Initialize type info if id is not a tag */
|
||||
if (!is_wildcard && (!role || is_pair)) {
|
||||
if (!(idr->flags & EcsIdTag)) {
|
||||
const ecs_type_info_t *ti = flecs_type_info_get(world, rel);
|
||||
if (!ti && tgt) {
|
||||
ti = flecs_type_info_get(world, tgt);
|
||||
}
|
||||
idr->type_info = ti;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark entities that are used as component/pair ids. When a tracked
|
||||
* entity is deleted, cleanup policies are applied so that the store
|
||||
* won't contain any tables with deleted ids. */
|
||||
|
||||
/* Flag for OnDelete policies */
|
||||
flecs_add_flag(world, rel, EcsEntityIsId);
|
||||
if (tgt) {
|
||||
/* Flag for OnDeleteTarget policies */
|
||||
ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt);
|
||||
ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
flecs_record_add_flag(tgt_r, EcsEntityIsTarget);
|
||||
if (idr->flags & EcsIdTraversable) {
|
||||
/* Flag used to determine if object should be traversed when
|
||||
* propagating events or with super/subset queries */
|
||||
flecs_record_add_flag(tgt_r, EcsEntityIsTraversable);
|
||||
|
||||
/* Add reference to (*, tgt) id record to entity record */
|
||||
tgt_r->idr = idr_t;
|
||||
}
|
||||
}
|
||||
|
||||
ecs_observable_t *o = &world->observable;
|
||||
idr->flags |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd;
|
||||
idr->flags |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove;
|
||||
idr->flags |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet;
|
||||
idr->flags |= flecs_observers_exist(o, id, EcsUnSet) * EcsIdHasUnSet;
|
||||
idr->flags |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill;
|
||||
idr->flags |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty;
|
||||
idr->flags |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate;
|
||||
idr->flags |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete;
|
||||
|
||||
if (ecs_should_log_1()) {
|
||||
char *id_str = ecs_id_str(world, id);
|
||||
ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str);
|
||||
ecs_os_free(id_str);
|
||||
}
|
||||
|
||||
/* Update counters */
|
||||
world->info.id_create_total ++;
|
||||
|
||||
if (!is_wildcard) {
|
||||
world->info.id_count ++;
|
||||
|
||||
if (idr->type_info) {
|
||||
world->info.component_id_count ++;
|
||||
} else {
|
||||
world->info.tag_id_count ++;
|
||||
}
|
||||
|
||||
if (is_pair) {
|
||||
world->info.pair_id_count ++;
|
||||
}
|
||||
} else {
|
||||
world->info.wildcard_id_count ++;
|
||||
}
|
||||
|
||||
return idr;
|
||||
#ifdef FLECS_DEBUG
|
||||
error:
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_id_record_assert_empty(
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
(void)idr;
|
||||
ecs_assert(flecs_table_cache_count(&idr->cache) == 0,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_id_record_free(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_id_t id = idr->id;
|
||||
|
||||
flecs_id_record_assert_empty(idr);
|
||||
|
||||
/* Id is still in use by a filter, query, rule or observer */
|
||||
ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0),
|
||||
ECS_ID_IN_USE, "cannot delete id that is queried for");
|
||||
|
||||
if (ECS_IS_PAIR(id)) {
|
||||
ecs_entity_t rel = ECS_PAIR_FIRST(id);
|
||||
ecs_entity_t tgt = ECS_PAIR_SECOND(id);
|
||||
if (!ecs_id_is_wildcard(id)) {
|
||||
if (ECS_PAIR_FIRST(id) != EcsFlag) {
|
||||
/* If id is not a wildcard, remove it from the wildcard lists */
|
||||
flecs_remove_id_elem(idr, ecs_pair(rel, EcsWildcard));
|
||||
flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, tgt));
|
||||
}
|
||||
} else {
|
||||
ecs_log_push_2();
|
||||
|
||||
/* If id is a wildcard, it means that all id records that match the
|
||||
* wildcard are also empty, so release them */
|
||||
if (ECS_PAIR_FIRST(id) == EcsWildcard) {
|
||||
/* Iterate (*, Target) list */
|
||||
ecs_id_record_t *cur, *next = idr->second.next;
|
||||
while ((cur = next)) {
|
||||
flecs_id_record_assert_empty(cur);
|
||||
next = cur->second.next;
|
||||
flecs_id_record_release(world, cur);
|
||||
}
|
||||
} else {
|
||||
/* Iterate (Relationship, *) list */
|
||||
ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_id_record_t *cur, *next = idr->first.next;
|
||||
while ((cur = next)) {
|
||||
flecs_id_record_assert_empty(cur);
|
||||
next = cur->first.next;
|
||||
flecs_id_record_release(world, cur);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_log_pop_2();
|
||||
}
|
||||
}
|
||||
|
||||
/* Update counters */
|
||||
world->info.id_delete_total ++;
|
||||
|
||||
if (!ecs_id_is_wildcard(id)) {
|
||||
world->info.id_count --;
|
||||
|
||||
if (ECS_IS_PAIR(id)) {
|
||||
world->info.pair_id_count --;
|
||||
}
|
||||
|
||||
if (idr->type_info) {
|
||||
world->info.component_id_count --;
|
||||
} else {
|
||||
world->info.tag_id_count --;
|
||||
}
|
||||
} else {
|
||||
world->info.wildcard_id_count --;
|
||||
}
|
||||
|
||||
/* Unregister the id record from the world & free resources */
|
||||
ecs_table_cache_fini(&idr->cache);
|
||||
flecs_name_index_free(idr->name_index);
|
||||
ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t);
|
||||
|
||||
ecs_id_t hash = flecs_id_record_hash(id);
|
||||
if (hash >= FLECS_HI_ID_RECORD_ID) {
|
||||
ecs_map_remove(&world->id_index_hi, hash);
|
||||
flecs_bfree(&world->allocators.id_record, idr);
|
||||
} else {
|
||||
idr->id = 0; /* Tombstone */
|
||||
}
|
||||
|
||||
if (ecs_should_log_1()) {
|
||||
char *id_str = ecs_id_str(world, id);
|
||||
ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str);
|
||||
ecs_os_free(id_str);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_id_record_t* flecs_id_record_ensure(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
idr = flecs_id_record_new(world, id);
|
||||
}
|
||||
return idr;
|
||||
}
|
||||
|
||||
ecs_id_record_t* flecs_id_record_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
if (id == ecs_pair(EcsIsA, EcsWildcard)) {
|
||||
return world->idr_isa_wildcard;
|
||||
} else if (id == ecs_pair(EcsChildOf, EcsWildcard)) {
|
||||
return world->idr_childof_wildcard;
|
||||
} else if (id == ecs_pair_t(EcsIdentifier, EcsName)) {
|
||||
return world->idr_identifier_name;
|
||||
}
|
||||
|
||||
ecs_id_t hash = flecs_id_record_hash(id);
|
||||
ecs_id_record_t *idr = NULL;
|
||||
if (hash >= FLECS_HI_ID_RECORD_ID) {
|
||||
idr = ecs_map_get_deref(&world->id_index_hi, ecs_id_record_t, hash);
|
||||
} else {
|
||||
idr = &world->id_index_lo[hash];
|
||||
if (!idr->id) {
|
||||
idr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return idr;
|
||||
}
|
||||
|
||||
ecs_id_record_t* flecs_query_id_record_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
ecs_entity_t first = ECS_PAIR_FIRST(id);
|
||||
if (ECS_IS_PAIR(id) && (first != EcsWildcard)) {
|
||||
idr = flecs_id_record_get(world, ecs_pair(EcsUnion, first));
|
||||
}
|
||||
return idr;
|
||||
}
|
||||
if (ECS_IS_PAIR(id) &&
|
||||
ECS_PAIR_SECOND(id) == EcsWildcard &&
|
||||
(idr->flags & EcsIdUnion))
|
||||
{
|
||||
idr = flecs_id_record_get(world,
|
||||
ecs_pair(EcsUnion, ECS_PAIR_FIRST(id)));
|
||||
}
|
||||
|
||||
return idr;
|
||||
}
|
||||
|
||||
void flecs_id_record_claim(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
(void)world;
|
||||
idr->refcount ++;
|
||||
}
|
||||
|
||||
int32_t flecs_id_record_release(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
int32_t rc = -- idr->refcount;
|
||||
ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (!rc) {
|
||||
flecs_id_record_free(world, idr);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void flecs_id_record_release_tables(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
/* Cache should not contain tables that aren't empty */
|
||||
ecs_assert(flecs_table_cache_count(&idr->cache) == 0,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_table_cache_iter_t it;
|
||||
if (flecs_table_cache_empty_iter(&idr->cache, &it)) {
|
||||
ecs_table_record_t *tr;
|
||||
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
|
||||
/* Release current table */
|
||||
flecs_table_free(world, tr->hdr.table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool flecs_id_record_set_type_info(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr,
|
||||
const ecs_type_info_t *ti)
|
||||
{
|
||||
bool is_wildcard = ecs_id_is_wildcard(idr->id);
|
||||
if (!is_wildcard) {
|
||||
if (ti) {
|
||||
if (!idr->type_info) {
|
||||
world->info.tag_id_count --;
|
||||
world->info.component_id_count ++;
|
||||
}
|
||||
} else {
|
||||
if (idr->type_info) {
|
||||
world->info.tag_id_count ++;
|
||||
world->info.component_id_count --;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = idr->type_info != ti;
|
||||
idr->type_info = ti;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
ecs_hashmap_t* flecs_id_record_name_index_ensure(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr)
|
||||
{
|
||||
ecs_hashmap_t *map = idr->name_index;
|
||||
if (!map) {
|
||||
map = idr->name_index = flecs_name_index_new(world, &world->allocator);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
ecs_hashmap_t* flecs_id_name_index_ensure(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
||||
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
return flecs_id_record_name_index_ensure(world, idr);
|
||||
}
|
||||
|
||||
ecs_hashmap_t* flecs_id_name_index_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
|
||||
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return idr->name_index;
|
||||
}
|
||||
|
||||
ecs_table_record_t* flecs_table_record_get(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t id)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
|
||||
ecs_id_record_t* idr = flecs_id_record_get(world, id);
|
||||
if (!idr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table);
|
||||
}
|
||||
|
||||
ecs_table_record_t* flecs_id_record_get_table(
|
||||
const ecs_id_record_t *idr,
|
||||
const ecs_table_t *table)
|
||||
{
|
||||
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table);
|
||||
}
|
||||
|
||||
void flecs_init_id_records(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
/* Cache often used id records on world */
|
||||
world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard);
|
||||
world->idr_wildcard_wildcard = flecs_id_record_ensure(world,
|
||||
ecs_pair(EcsWildcard, EcsWildcard));
|
||||
world->idr_any = flecs_id_record_ensure(world, EcsAny);
|
||||
world->idr_isa_wildcard = flecs_id_record_ensure(world,
|
||||
ecs_pair(EcsIsA, EcsWildcard));
|
||||
}
|
||||
|
||||
void flecs_fini_id_records(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
/* Loop & delete first element until there are no elements left. Id records
|
||||
* can recursively delete each other, this ensures we always have a
|
||||
* valid iterator. */
|
||||
while (ecs_map_count(&world->id_index_hi) > 0) {
|
||||
ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi);
|
||||
ecs_map_next(&it);
|
||||
flecs_id_record_release(world, ecs_map_ptr(&it));
|
||||
}
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) {
|
||||
ecs_id_record_t *idr = &world->id_index_lo[i];
|
||||
if (idr->id) {
|
||||
flecs_id_record_release(world, idr);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_assert(ecs_map_count(&world->id_index_hi) == 0,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_map_fini(&world->id_index_hi);
|
||||
ecs_os_free(world->id_index_lo);
|
||||
}
|
||||
147
engine/libs/flecs/src/storage/id_index.h
Normal file
147
engine/libs/flecs/src/storage/id_index.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @file id_index.h
|
||||
* @brief Index for looking up tables by (component) id.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_ID_INDEX_H
|
||||
#define FLECS_ID_INDEX_H
|
||||
|
||||
/* Payload for id cache */
|
||||
struct ecs_table_record_t {
|
||||
ecs_table_cache_hdr_t hdr; /* Table cache header */
|
||||
int16_t index; /* First type index where id occurs in table */
|
||||
int16_t count; /* Number of times id occurs in table */
|
||||
int16_t column; /* First column index where id occurs */
|
||||
};
|
||||
|
||||
/* Linked list of id records */
|
||||
typedef struct ecs_id_record_elem_t {
|
||||
struct ecs_id_record_t *prev, *next;
|
||||
} ecs_id_record_elem_t;
|
||||
|
||||
typedef struct ecs_reachable_elem_t {
|
||||
const ecs_table_record_t *tr;
|
||||
ecs_record_t *record;
|
||||
ecs_entity_t src;
|
||||
ecs_id_t id;
|
||||
#ifndef NDEBUG
|
||||
ecs_table_t *table;
|
||||
#endif
|
||||
} ecs_reachable_elem_t;
|
||||
|
||||
typedef struct ecs_reachable_cache_t {
|
||||
int32_t generation;
|
||||
int32_t current;
|
||||
ecs_vec_t ids; /* vec<reachable_elem_t> */
|
||||
} ecs_reachable_cache_t;
|
||||
|
||||
/* Payload for id index which contains all datastructures for an id. */
|
||||
struct ecs_id_record_t {
|
||||
/* Cache with all tables that contain the id. Must be first member. */
|
||||
ecs_table_cache_t cache; /* table_cache<ecs_table_record_t> */
|
||||
|
||||
/* Id of record */
|
||||
ecs_id_t id;
|
||||
|
||||
/* Flags for id */
|
||||
ecs_flags32_t flags;
|
||||
|
||||
/* Cached pointer to type info for id, if id contains data. */
|
||||
const ecs_type_info_t *type_info;
|
||||
|
||||
/* Name lookup index (currently only used for ChildOf pairs) */
|
||||
ecs_hashmap_t *name_index;
|
||||
|
||||
/* Lists for all id records that match a pair wildcard. The wildcard id
|
||||
* record is at the head of the list. */
|
||||
ecs_id_record_elem_t first; /* (R, *) */
|
||||
ecs_id_record_elem_t second; /* (*, O) */
|
||||
ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */
|
||||
|
||||
/* Parent id record. For pair records the parent is the (R, *) record. */
|
||||
ecs_id_record_t *parent;
|
||||
|
||||
/* Refcount */
|
||||
int32_t refcount;
|
||||
|
||||
/* Keep alive count. This count must be 0 when the id record is deleted. If
|
||||
* it is not 0, an application attempted to delete an id that was still
|
||||
* queried for. */
|
||||
int32_t keep_alive;
|
||||
|
||||
/* Cache invalidation counter */
|
||||
ecs_reachable_cache_t reachable;
|
||||
};
|
||||
|
||||
/* Get id record for id */
|
||||
ecs_id_record_t* flecs_id_record_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_id_t id);
|
||||
|
||||
/* Get id record for id for searching.
|
||||
* Same as flecs_id_record_get, but replaces (R, *) with (Union, R) if R is a
|
||||
* union relationship. */
|
||||
ecs_id_record_t* flecs_query_id_record_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_id_t id);
|
||||
|
||||
/* Ensure id record for id */
|
||||
ecs_id_record_t* flecs_id_record_ensure(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t id);
|
||||
|
||||
/* Increase refcount of id record */
|
||||
void flecs_id_record_claim(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr);
|
||||
|
||||
/* Decrease refcount of id record, delete if 0 */
|
||||
int32_t flecs_id_record_release(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr);
|
||||
|
||||
/* Release all empty tables in id record */
|
||||
void flecs_id_record_release_tables(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr);
|
||||
|
||||
/* Set (component) type info for id record */
|
||||
bool flecs_id_record_set_type_info(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr,
|
||||
const ecs_type_info_t *ti);
|
||||
|
||||
/* Ensure id record has name index */
|
||||
ecs_hashmap_t* flecs_id_name_index_ensure(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t id);
|
||||
|
||||
ecs_hashmap_t* flecs_id_record_name_index_ensure(
|
||||
ecs_world_t *world,
|
||||
ecs_id_record_t *idr);
|
||||
|
||||
/* Get name index for id record */
|
||||
ecs_hashmap_t* flecs_id_name_index_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_id_t id);
|
||||
|
||||
/* Find table record for id */
|
||||
ecs_table_record_t* flecs_table_record_get(
|
||||
const ecs_world_t *world,
|
||||
const ecs_table_t *table,
|
||||
ecs_id_t id);
|
||||
|
||||
/* Find table record for id record */
|
||||
ecs_table_record_t* flecs_id_record_get_table(
|
||||
const ecs_id_record_t *idr,
|
||||
const ecs_table_t *table);
|
||||
|
||||
/* Bootstrap cached id records */
|
||||
void flecs_init_id_records(
|
||||
ecs_world_t *world);
|
||||
|
||||
/* Cleanup all id records in world */
|
||||
void flecs_fini_id_records(
|
||||
ecs_world_t *world);
|
||||
|
||||
#endif
|
||||
2583
engine/libs/flecs/src/storage/table.c
Normal file
2583
engine/libs/flecs/src/storage/table.c
Normal file
File diff suppressed because it is too large
Load Diff
259
engine/libs/flecs/src/storage/table.h
Normal file
259
engine/libs/flecs/src/storage/table.h
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* @file table.h
|
||||
* @brief Table storage implementation.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_TABLE_H
|
||||
#define FLECS_TABLE_H
|
||||
|
||||
#include "table_graph.h"
|
||||
|
||||
/* Table event type for notifying tables of world events */
|
||||
typedef enum ecs_table_eventkind_t {
|
||||
EcsTableTriggersForId,
|
||||
EcsTableNoTriggersForId,
|
||||
} ecs_table_eventkind_t;
|
||||
|
||||
typedef struct ecs_table_event_t {
|
||||
ecs_table_eventkind_t kind;
|
||||
|
||||
/* Query event */
|
||||
ecs_query_t *query;
|
||||
|
||||
/* Component info event */
|
||||
ecs_entity_t component;
|
||||
|
||||
/* Event match */
|
||||
ecs_entity_t event;
|
||||
|
||||
/* If the nubmer of fields gets out of hand, this can be turned into a union
|
||||
* but since events are very temporary objects, this works for now and makes
|
||||
* initializing an event a bit simpler. */
|
||||
} ecs_table_event_t;
|
||||
|
||||
/** Infrequently accessed data not stored inline in ecs_table_t */
|
||||
typedef struct ecs_table__t {
|
||||
uint64_t hash; /* Type hash */
|
||||
int32_t lock; /* Prevents modifications */
|
||||
int32_t traversable_count; /* Traversable relationship targets in table */
|
||||
uint16_t generation; /* Used for table cleanup */
|
||||
int16_t record_count; /* Table record count including wildcards */
|
||||
|
||||
struct ecs_table_record_t *records; /* Array with table records */
|
||||
ecs_hashmap_t *name_index; /* Cached pointer to name index */
|
||||
|
||||
ecs_switch_t *sw_columns; /* Switch columns */
|
||||
ecs_bitset_t *bs_columns; /* Bitset columns */
|
||||
int16_t sw_count;
|
||||
int16_t sw_offset;
|
||||
int16_t bs_count;
|
||||
int16_t bs_offset;
|
||||
int16_t ft_offset;
|
||||
} ecs_table__t;
|
||||
|
||||
/** Table column */
|
||||
typedef struct ecs_column_t {
|
||||
ecs_vec_t data; /* Vector with component data */
|
||||
ecs_id_t id; /* Component id */
|
||||
ecs_type_info_t *ti; /* Component type info */
|
||||
ecs_size_t size; /* Component size */
|
||||
} ecs_column_t;
|
||||
|
||||
/** Table data */
|
||||
struct ecs_data_t {
|
||||
ecs_vec_t entities; /* Entity ids */
|
||||
ecs_column_t *columns; /* Component data */
|
||||
};
|
||||
|
||||
/** A table is the Flecs equivalent of an archetype. Tables store all entities
|
||||
* with a specific set of components. Tables are automatically created when an
|
||||
* entity has a set of components not previously observed before. When a new
|
||||
* table is created, it is automatically matched with existing queries */
|
||||
struct ecs_table_t {
|
||||
uint64_t id; /* Table id in sparse set */
|
||||
ecs_flags32_t flags; /* Flags for testing table properties */
|
||||
int16_t column_count; /* Number of components (excluding tags) */
|
||||
ecs_type_t type; /* Vector with component ids */
|
||||
|
||||
ecs_data_t data; /* Component storage */
|
||||
ecs_graph_node_t node; /* Graph node */
|
||||
|
||||
int32_t *dirty_state; /* Keep track of changes in columns */
|
||||
int32_t *column_map; /* Map type index <-> column
|
||||
* - 0..count(T): type index -> column
|
||||
* - count(T)..count(C): column -> type index
|
||||
*/
|
||||
|
||||
ecs_table__t *_; /* Infrequently accessed table metadata */
|
||||
};
|
||||
|
||||
/* Init table */
|
||||
void flecs_table_init(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_table_t *from);
|
||||
|
||||
/** Copy type. */
|
||||
ecs_type_t flecs_type_copy(
|
||||
ecs_world_t *world,
|
||||
const ecs_type_t *src);
|
||||
|
||||
/** Free type. */
|
||||
void flecs_type_free(
|
||||
ecs_world_t *world,
|
||||
ecs_type_t *type);
|
||||
|
||||
/** Find or create table for a set of components */
|
||||
ecs_table_t* flecs_table_find_or_create(
|
||||
ecs_world_t *world,
|
||||
ecs_type_t *type);
|
||||
|
||||
/* Initialize columns for data */
|
||||
void flecs_table_init_data(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Clear all entities from a table. */
|
||||
void flecs_table_clear_entities(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Reset a table to its initial state */
|
||||
void flecs_table_reset(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Clear all entities from the table. Do not invoke OnRemove systems */
|
||||
void flecs_table_clear_entities_silent(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Clear table data. Don't call OnRemove handlers. */
|
||||
void flecs_table_clear_data(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_data_t *data);
|
||||
|
||||
/* Return number of entities in data */
|
||||
int32_t flecs_table_data_count(
|
||||
const ecs_data_t *data);
|
||||
|
||||
/* Add a new entry to the table for the specified entity */
|
||||
int32_t flecs_table_append(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_entity_t entity,
|
||||
bool construct,
|
||||
bool on_add);
|
||||
|
||||
/* Delete an entity from the table. */
|
||||
void flecs_table_delete(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
int32_t index,
|
||||
bool destruct);
|
||||
|
||||
/* Make sure table records are in correct table cache list */
|
||||
bool flecs_table_records_update_empty(
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Move a row from one table to another */
|
||||
void flecs_table_move(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t dst_entity,
|
||||
ecs_entity_t src_entity,
|
||||
ecs_table_t *new_table,
|
||||
int32_t new_index,
|
||||
ecs_table_t *old_table,
|
||||
int32_t old_index,
|
||||
bool construct);
|
||||
|
||||
/* Grow table with specified number of records. Populate table with entities,
|
||||
* starting from specified entity id. */
|
||||
int32_t flecs_table_appendn(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_data_t *data,
|
||||
int32_t count,
|
||||
const ecs_entity_t *ids);
|
||||
|
||||
/* Set table to a fixed size. Useful for preallocating memory in advance. */
|
||||
void flecs_table_set_size(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_data_t *data,
|
||||
int32_t count);
|
||||
|
||||
/* Shrink table to contents */
|
||||
bool flecs_table_shrink(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Get dirty state for table columns */
|
||||
int32_t* flecs_table_get_dirty_state(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Initialize root table */
|
||||
void flecs_init_root_table(
|
||||
ecs_world_t *world);
|
||||
|
||||
/* Unset components in table */
|
||||
void flecs_table_remove_actions(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Free table */
|
||||
void flecs_table_free(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Free table */
|
||||
void flecs_table_free_type(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Replace data */
|
||||
void flecs_table_replace_data(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_data_t *data);
|
||||
|
||||
/* Merge data of one table into another table */
|
||||
void flecs_table_merge(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *new_table,
|
||||
ecs_table_t *old_table,
|
||||
ecs_data_t *new_data,
|
||||
ecs_data_t *old_data);
|
||||
|
||||
void flecs_table_swap(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
int32_t row_1,
|
||||
int32_t row_2);
|
||||
|
||||
void flecs_table_mark_dirty(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_entity_t component);
|
||||
|
||||
void flecs_table_notify(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_table_event_t *event);
|
||||
|
||||
void flecs_table_delete_entities(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
int32_t flecs_table_column_to_union_index(
|
||||
const ecs_table_t *table,
|
||||
int32_t column);
|
||||
|
||||
/* Increase observer count of table */
|
||||
void flecs_table_traversable_add(
|
||||
ecs_table_t *table,
|
||||
int32_t value);
|
||||
|
||||
#endif
|
||||
282
engine/libs/flecs/src/storage/table_cache.c
Normal file
282
engine/libs/flecs/src/storage/table_cache.c
Normal file
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* @file table_cache.c
|
||||
* @brief Data structure for fast table iteration/lookups.
|
||||
*
|
||||
* A table cache is a data structure that provides constant time operations for
|
||||
* insertion and removal of tables, and to testing whether a table is registered
|
||||
* with the cache. A table cache also provides functions to iterate the tables
|
||||
* in a cache.
|
||||
*
|
||||
* The world stores a table cache per (component) id inside the id record
|
||||
* administration. Cached queries store a table cache with matched tables.
|
||||
*
|
||||
* A table cache has separate lists for non-empty tables and empty tables. This
|
||||
* improves performance as applications don't waste time iterating empty tables.
|
||||
*/
|
||||
|
||||
#include "../private_api.h"
|
||||
|
||||
static
|
||||
void flecs_table_cache_list_remove(
|
||||
ecs_table_cache_t *cache,
|
||||
ecs_table_cache_hdr_t *elem)
|
||||
{
|
||||
ecs_table_cache_hdr_t *next = elem->next;
|
||||
ecs_table_cache_hdr_t *prev = elem->prev;
|
||||
|
||||
if (next) {
|
||||
next->prev = prev;
|
||||
}
|
||||
if (prev) {
|
||||
prev->next = next;
|
||||
}
|
||||
|
||||
cache->empty_tables.count -= !!elem->empty;
|
||||
cache->tables.count -= !elem->empty;
|
||||
|
||||
if (cache->empty_tables.first == elem) {
|
||||
cache->empty_tables.first = next;
|
||||
} else if (cache->tables.first == elem) {
|
||||
cache->tables.first = next;
|
||||
}
|
||||
if (cache->empty_tables.last == elem) {
|
||||
cache->empty_tables.last = prev;
|
||||
}
|
||||
if (cache->tables.last == elem) {
|
||||
cache->tables.last = prev;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void flecs_table_cache_list_insert(
|
||||
ecs_table_cache_t *cache,
|
||||
ecs_table_cache_hdr_t *elem)
|
||||
{
|
||||
ecs_table_cache_hdr_t *last;
|
||||
if (elem->empty) {
|
||||
last = cache->empty_tables.last;
|
||||
cache->empty_tables.last = elem;
|
||||
if ((++ cache->empty_tables.count) == 1) {
|
||||
cache->empty_tables.first = elem;
|
||||
}
|
||||
} else {
|
||||
last = cache->tables.last;
|
||||
cache->tables.last = elem;
|
||||
if ((++ cache->tables.count) == 1) {
|
||||
cache->tables.first = elem;
|
||||
}
|
||||
}
|
||||
|
||||
elem->next = NULL;
|
||||
elem->prev = last;
|
||||
|
||||
if (last) {
|
||||
last->next = elem;
|
||||
}
|
||||
}
|
||||
|
||||
void ecs_table_cache_init(
|
||||
ecs_world_t *world,
|
||||
ecs_table_cache_t *cache)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_map_init_w_params(&cache->index, &world->allocators.ptr);
|
||||
}
|
||||
|
||||
void ecs_table_cache_fini(
|
||||
ecs_table_cache_t *cache)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_map_fini(&cache->index);
|
||||
}
|
||||
|
||||
bool ecs_table_cache_is_empty(
|
||||
const ecs_table_cache_t *cache)
|
||||
{
|
||||
return ecs_map_count(&cache->index) == 0;
|
||||
}
|
||||
|
||||
void ecs_table_cache_insert(
|
||||
ecs_table_cache_t *cache,
|
||||
const ecs_table_t *table,
|
||||
ecs_table_cache_hdr_t *result)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(ecs_table_cache_get(cache, table) == NULL,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
bool empty;
|
||||
if (!table) {
|
||||
empty = false;
|
||||
} else {
|
||||
empty = ecs_table_count(table) == 0;
|
||||
}
|
||||
|
||||
result->cache = cache;
|
||||
result->table = ECS_CONST_CAST(ecs_table_t*, table);
|
||||
result->empty = empty;
|
||||
|
||||
flecs_table_cache_list_insert(cache, result);
|
||||
|
||||
if (table) {
|
||||
ecs_map_insert_ptr(&cache->index, table->id, result);
|
||||
}
|
||||
|
||||
ecs_assert(empty || cache->tables.first != NULL,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(!empty || cache->empty_tables.first != NULL,
|
||||
ECS_INTERNAL_ERROR, NULL);
|
||||
}
|
||||
|
||||
void ecs_table_cache_replace(
|
||||
ecs_table_cache_t *cache,
|
||||
const ecs_table_t *table,
|
||||
ecs_table_cache_hdr_t *elem)
|
||||
{
|
||||
ecs_table_cache_hdr_t **r = ecs_map_get_ref(
|
||||
&cache->index, ecs_table_cache_hdr_t, table->id);
|
||||
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_table_cache_hdr_t *old = *r;
|
||||
ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_table_cache_hdr_t *prev = old->prev, *next = old->next;
|
||||
if (prev) {
|
||||
ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL);
|
||||
prev->next = elem;
|
||||
}
|
||||
if (next) {
|
||||
ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL);
|
||||
next->prev = elem;
|
||||
}
|
||||
|
||||
if (cache->empty_tables.first == old) {
|
||||
cache->empty_tables.first = elem;
|
||||
}
|
||||
if (cache->empty_tables.last == old) {
|
||||
cache->empty_tables.last = elem;
|
||||
}
|
||||
if (cache->tables.first == old) {
|
||||
cache->tables.first = elem;
|
||||
}
|
||||
if (cache->tables.last == old) {
|
||||
cache->tables.last = elem;
|
||||
}
|
||||
|
||||
*r = elem;
|
||||
elem->prev = prev;
|
||||
elem->next = next;
|
||||
}
|
||||
|
||||
void* ecs_table_cache_get(
|
||||
const ecs_table_cache_t *cache,
|
||||
const ecs_table_t *table)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
if (table) {
|
||||
if (ecs_map_is_init(&cache->index)) {
|
||||
return ecs_map_get_deref(&cache->index, void**, table->id);
|
||||
}
|
||||
return NULL;
|
||||
} else {
|
||||
ecs_table_cache_hdr_t *elem = cache->tables.first;
|
||||
ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
|
||||
void* ecs_table_cache_remove(
|
||||
ecs_table_cache_t *cache,
|
||||
uint64_t table_id,
|
||||
ecs_table_cache_hdr_t *elem)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(table_id != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
flecs_table_cache_list_remove(cache, elem);
|
||||
ecs_map_remove(&cache->index, table_id);
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
bool ecs_table_cache_set_empty(
|
||||
ecs_table_cache_t *cache,
|
||||
const ecs_table_t *table,
|
||||
bool empty)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index,
|
||||
ecs_table_cache_hdr_t, table->id);
|
||||
if (!elem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elem->empty == empty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
flecs_table_cache_list_remove(cache, elem);
|
||||
elem->empty = empty;
|
||||
flecs_table_cache_list_insert(cache, elem);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flecs_table_cache_iter(
|
||||
ecs_table_cache_t *cache,
|
||||
ecs_table_cache_iter_t *out)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
out->next = cache->tables.first;
|
||||
out->next_list = NULL;
|
||||
out->cur = NULL;
|
||||
return out->next != NULL;
|
||||
}
|
||||
|
||||
bool flecs_table_cache_empty_iter(
|
||||
ecs_table_cache_t *cache,
|
||||
ecs_table_cache_iter_t *out)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
out->next = cache->empty_tables.first;
|
||||
out->next_list = NULL;
|
||||
out->cur = NULL;
|
||||
return out->next != NULL;
|
||||
}
|
||||
|
||||
bool flecs_table_cache_all_iter(
|
||||
ecs_table_cache_t *cache,
|
||||
ecs_table_cache_iter_t *out)
|
||||
{
|
||||
ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
out->next = cache->empty_tables.first;
|
||||
out->next_list = cache->tables.first;
|
||||
out->cur = NULL;
|
||||
return out->next != NULL || out->next_list != NULL;
|
||||
}
|
||||
|
||||
ecs_table_cache_hdr_t* flecs_table_cache_next_(
|
||||
ecs_table_cache_iter_t *it)
|
||||
{
|
||||
ecs_table_cache_hdr_t *next = it->next;
|
||||
if (!next) {
|
||||
next = it->next_list;
|
||||
it->next_list = NULL;
|
||||
if (!next) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
it->cur = next;
|
||||
it->next = next->next;
|
||||
return next;
|
||||
}
|
||||
64
engine/libs/flecs/src/storage/table_cache.h
Normal file
64
engine/libs/flecs/src/storage/table_cache.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @file table_cache.h
|
||||
* @brief Data structure for fast table iteration/lookups.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_TABLE_CACHE_H_
|
||||
#define FLECS_TABLE_CACHE_H_
|
||||
|
||||
void ecs_table_cache_init(
|
||||
ecs_world_t *world,
|
||||
ecs_table_cache_t *cache);
|
||||
|
||||
void ecs_table_cache_fini(
|
||||
ecs_table_cache_t *cache);
|
||||
|
||||
void ecs_table_cache_insert(
|
||||
ecs_table_cache_t *cache,
|
||||
const ecs_table_t *table,
|
||||
ecs_table_cache_hdr_t *result);
|
||||
|
||||
void ecs_table_cache_replace(
|
||||
ecs_table_cache_t *cache,
|
||||
const ecs_table_t *table,
|
||||
ecs_table_cache_hdr_t *elem);
|
||||
|
||||
void* ecs_table_cache_remove(
|
||||
ecs_table_cache_t *cache,
|
||||
uint64_t table_id,
|
||||
ecs_table_cache_hdr_t *elem);
|
||||
|
||||
void* ecs_table_cache_get(
|
||||
const ecs_table_cache_t *cache,
|
||||
const ecs_table_t *table);
|
||||
|
||||
bool ecs_table_cache_set_empty(
|
||||
ecs_table_cache_t *cache,
|
||||
const ecs_table_t *table,
|
||||
bool empty);
|
||||
|
||||
bool ecs_table_cache_is_empty(
|
||||
const ecs_table_cache_t *cache);
|
||||
|
||||
#define flecs_table_cache_count(cache) (cache)->tables.count
|
||||
#define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count
|
||||
|
||||
bool flecs_table_cache_iter(
|
||||
ecs_table_cache_t *cache,
|
||||
ecs_table_cache_iter_t *out);
|
||||
|
||||
bool flecs_table_cache_empty_iter(
|
||||
ecs_table_cache_t *cache,
|
||||
ecs_table_cache_iter_t *out);
|
||||
|
||||
bool flecs_table_cache_all_iter(
|
||||
ecs_table_cache_t *cache,
|
||||
ecs_table_cache_iter_t *out);
|
||||
|
||||
ecs_table_cache_hdr_t* flecs_table_cache_next_(
|
||||
ecs_table_cache_iter_t *it);
|
||||
|
||||
#define flecs_table_cache_next(it, T)\
|
||||
(ECS_CAST(T*, flecs_table_cache_next_(it)))
|
||||
|
||||
#endif
|
||||
1240
engine/libs/flecs/src/storage/table_graph.c
Normal file
1240
engine/libs/flecs/src/storage/table_graph.c
Normal file
File diff suppressed because it is too large
Load Diff
104
engine/libs/flecs/src/storage/table_graph.h
Normal file
104
engine/libs/flecs/src/storage/table_graph.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* @file table_graph.h
|
||||
* @brief Table graph types and functions.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_TABLE_GRAPH_H
|
||||
#define FLECS_TABLE_GRAPH_H
|
||||
|
||||
/** Cache of added/removed components for non-trivial edges between tables */
|
||||
#define ECS_TABLE_DIFF_INIT { .added = {0}}
|
||||
|
||||
/** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to
|
||||
* conserve memory on table edges (a type doesn't have the size field), whereas
|
||||
* a vec for the builder is more convenient to use & has allocator support. */
|
||||
typedef struct ecs_table_diff_builder_t {
|
||||
ecs_vec_t added;
|
||||
ecs_vec_t removed;
|
||||
} ecs_table_diff_builder_t;
|
||||
|
||||
typedef struct ecs_table_diff_t {
|
||||
ecs_type_t added; /* Components added between tables */
|
||||
ecs_type_t removed; /* Components removed between tables */
|
||||
} ecs_table_diff_t;
|
||||
|
||||
/** Edge linked list (used to keep track of incoming edges) */
|
||||
typedef struct ecs_graph_edge_hdr_t {
|
||||
struct ecs_graph_edge_hdr_t *prev;
|
||||
struct ecs_graph_edge_hdr_t *next;
|
||||
} ecs_graph_edge_hdr_t;
|
||||
|
||||
/** Single edge. */
|
||||
typedef struct ecs_graph_edge_t {
|
||||
ecs_graph_edge_hdr_t hdr;
|
||||
ecs_table_t *from; /* Edge source table */
|
||||
ecs_table_t *to; /* Edge destination table */
|
||||
ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */
|
||||
ecs_id_t id; /* Id associated with edge */
|
||||
} ecs_graph_edge_t;
|
||||
|
||||
/* Edges to other tables. */
|
||||
typedef struct ecs_graph_edges_t {
|
||||
ecs_graph_edge_t *lo; /* Small array optimized for low edges */
|
||||
ecs_map_t *hi; /* Map for hi edges (map<id, edge_t>) */
|
||||
} ecs_graph_edges_t;
|
||||
|
||||
/* Table graph node */
|
||||
typedef struct ecs_graph_node_t {
|
||||
/* Outgoing edges */
|
||||
ecs_graph_edges_t add;
|
||||
ecs_graph_edges_t remove;
|
||||
|
||||
/* Incoming edges (next = add edges, prev = remove edges) */
|
||||
ecs_graph_edge_hdr_t refs;
|
||||
} ecs_graph_node_t;
|
||||
|
||||
/* Find table by adding id to current table */
|
||||
ecs_table_t *flecs_table_traverse_add(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_id_t *id_ptr,
|
||||
ecs_table_diff_t *diff);
|
||||
|
||||
/* Find table by removing id from current table */
|
||||
ecs_table_t *flecs_table_traverse_remove(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table,
|
||||
ecs_id_t *id_ptr,
|
||||
ecs_table_diff_t *diff);
|
||||
|
||||
/* Cleanup incoming and outgoing edges for table */
|
||||
void flecs_table_clear_edges(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
/* Table diff builder, used to build id lists that indicate the difference in
|
||||
* ids between two tables. */
|
||||
void flecs_table_diff_builder_init(
|
||||
ecs_world_t *world,
|
||||
ecs_table_diff_builder_t *builder);
|
||||
|
||||
void flecs_table_diff_builder_fini(
|
||||
ecs_world_t *world,
|
||||
ecs_table_diff_builder_t *builder);
|
||||
|
||||
void flecs_table_diff_builder_clear(
|
||||
ecs_table_diff_builder_t *builder);
|
||||
|
||||
void flecs_table_diff_build_append_table(
|
||||
ecs_world_t *world,
|
||||
ecs_table_diff_builder_t *dst,
|
||||
ecs_table_diff_t *src);
|
||||
|
||||
void flecs_table_diff_build(
|
||||
ecs_world_t *world,
|
||||
ecs_table_diff_builder_t *builder,
|
||||
ecs_table_diff_t *diff,
|
||||
int32_t added_offset,
|
||||
int32_t removed_offset);
|
||||
|
||||
void flecs_table_diff_build_noalloc(
|
||||
ecs_table_diff_builder_t *builder,
|
||||
ecs_table_diff_t *diff);
|
||||
|
||||
#endif
|
||||
232
engine/libs/flecs/src/value.c
Normal file
232
engine/libs/flecs/src/value.c
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* @file value.c
|
||||
* @brief Utility functions to work with non-trivial pointers of user types.
|
||||
*/
|
||||
|
||||
#include "private_api.h"
|
||||
|
||||
int ecs_value_init_w_type_info(
|
||||
const ecs_world_t *world,
|
||||
const ecs_type_info_t *ti,
|
||||
void *ptr)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)world;
|
||||
|
||||
ecs_xtor_t ctor;
|
||||
if ((ctor = ti->hooks.ctor)) {
|
||||
ctor(ptr, 1, ti);
|
||||
} else {
|
||||
ecs_os_memset(ptr, 0, ti->size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_init(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
void *ptr)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
const ecs_type_info_t *ti = ecs_get_type_info(world, type);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type");
|
||||
return ecs_value_init_w_type_info(world, ti, ptr);
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
void* ecs_value_new_w_type_info(
|
||||
ecs_world_t *world,
|
||||
const ecs_type_info_t *ti)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)world;
|
||||
|
||||
void *result = flecs_alloc(&world->allocator, ti->size);
|
||||
if (ecs_value_init_w_type_info(world, ti, result) != 0) {
|
||||
flecs_free(&world->allocator, ti->size, result);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return result;
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* ecs_value_new(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
const ecs_type_info_t *ti = ecs_get_type_info(world, type);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type");
|
||||
|
||||
return ecs_value_new_w_type_info(world, ti);
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ecs_value_fini_w_type_info(
|
||||
const ecs_world_t *world,
|
||||
const ecs_type_info_t *ti,
|
||||
void *ptr)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)world;
|
||||
|
||||
ecs_xtor_t dtor;
|
||||
if ((dtor = ti->hooks.dtor)) {
|
||||
dtor(ptr, 1, ti);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_fini(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
void* ptr)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
(void)world;
|
||||
const ecs_type_info_t *ti = ecs_get_type_info(world, type);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type");
|
||||
return ecs_value_fini_w_type_info(world, ti, ptr);
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_free(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
void* ptr)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
const ecs_type_info_t *ti = ecs_get_type_info(world, type);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type");
|
||||
if (ecs_value_fini_w_type_info(world, ti, ptr) != 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
flecs_free(&world->allocator, ti->size, ptr);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_copy_w_type_info(
|
||||
const ecs_world_t *world,
|
||||
const ecs_type_info_t *ti,
|
||||
void* dst,
|
||||
const void *src)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)world;
|
||||
|
||||
ecs_copy_t copy;
|
||||
if ((copy = ti->hooks.copy)) {
|
||||
copy(dst, src, 1, ti);
|
||||
} else {
|
||||
ecs_os_memcpy(dst, src, ti->size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_copy(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
void* dst,
|
||||
const void *src)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
const ecs_type_info_t *ti = ecs_get_type_info(world, type);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type");
|
||||
return ecs_value_copy_w_type_info(world, ti, dst, src);
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_move_w_type_info(
|
||||
const ecs_world_t *world,
|
||||
const ecs_type_info_t *ti,
|
||||
void* dst,
|
||||
void *src)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)world;
|
||||
|
||||
ecs_move_t move;
|
||||
if ((move = ti->hooks.move)) {
|
||||
move(dst, src, 1, ti);
|
||||
} else {
|
||||
ecs_os_memcpy(dst, src, ti->size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_move(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
void* dst,
|
||||
void *src)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
const ecs_type_info_t *ti = ecs_get_type_info(world, type);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type");
|
||||
return ecs_value_move_w_type_info(world, ti, dst, src);
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_move_ctor_w_type_info(
|
||||
const ecs_world_t *world,
|
||||
const ecs_type_info_t *ti,
|
||||
void* dst,
|
||||
void *src)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
(void)world;
|
||||
|
||||
ecs_move_t move;
|
||||
if ((move = ti->hooks.move_ctor)) {
|
||||
move(dst, src, 1, ti);
|
||||
} else {
|
||||
ecs_os_memcpy(dst, src, ti->size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ecs_value_move_ctor(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
void* dst,
|
||||
void *src)
|
||||
{
|
||||
ecs_poly_assert(world, ecs_world_t);
|
||||
const ecs_type_info_t *ti = ecs_get_type_info(world, type);
|
||||
ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type");
|
||||
return ecs_value_move_w_type_info(world, ti, dst, src);
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
2169
engine/libs/flecs/src/world.c
Normal file
2169
engine/libs/flecs/src/world.c
Normal file
File diff suppressed because it is too large
Load Diff
139
engine/libs/flecs/src/world.h
Normal file
139
engine/libs/flecs/src/world.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @file world.h
|
||||
* @brief World-level API.
|
||||
*/
|
||||
|
||||
#ifndef FLECS_WORLD_H
|
||||
#define FLECS_WORLD_H
|
||||
|
||||
/* Get current stage */
|
||||
ecs_stage_t* flecs_stage_from_world(
|
||||
ecs_world_t **world_ptr);
|
||||
|
||||
/* Get current thread-specific stage from readonly world */
|
||||
const ecs_stage_t* flecs_stage_from_readonly_world(
|
||||
const ecs_world_t *world);
|
||||
|
||||
/* Get component callbacks */
|
||||
const ecs_type_info_t *flecs_type_info_get(
|
||||
const ecs_world_t *world,
|
||||
ecs_entity_t component);
|
||||
|
||||
/* Get or create component callbacks */
|
||||
ecs_type_info_t* flecs_type_info_ensure(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t component);
|
||||
|
||||
bool flecs_type_info_init_id(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t component,
|
||||
ecs_size_t size,
|
||||
ecs_size_t alignment,
|
||||
const ecs_type_hooks_t *li);
|
||||
|
||||
#define flecs_type_info_init(world, T, ...)\
|
||||
flecs_type_info_init_id(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T),\
|
||||
&(ecs_type_hooks_t)__VA_ARGS__)
|
||||
|
||||
void flecs_type_info_fini(
|
||||
ecs_type_info_t *ti);
|
||||
|
||||
void flecs_type_info_free(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t component);
|
||||
|
||||
void flecs_eval_component_monitors(
|
||||
ecs_world_t *world);
|
||||
|
||||
void flecs_monitor_mark_dirty(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t id);
|
||||
|
||||
void flecs_monitor_register(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t id,
|
||||
ecs_query_t *query);
|
||||
|
||||
void flecs_monitor_unregister(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t id,
|
||||
ecs_query_t *query);
|
||||
|
||||
void flecs_notify_tables(
|
||||
ecs_world_t *world,
|
||||
ecs_id_t id,
|
||||
ecs_table_event_t *event);
|
||||
|
||||
void flecs_register_table(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
void flecs_unregister_table(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
void flecs_table_set_empty(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
void flecs_delete_table(
|
||||
ecs_world_t *world,
|
||||
ecs_table_t *table);
|
||||
|
||||
void flecs_process_pending_tables(
|
||||
const ecs_world_t *world);
|
||||
|
||||
/* Suspend/resume readonly state. To fully support implicit registration of
|
||||
* components, it should be possible to register components while the world is
|
||||
* in readonly mode. It is not uncommon that a component is used first from
|
||||
* within a system, which are often ran while in readonly mode.
|
||||
*
|
||||
* Suspending readonly mode is only allowed when the world is not multithreaded.
|
||||
* When a world is multithreaded, it is not safe to (even temporarily) leave
|
||||
* readonly mode, so a multithreaded application should always explicitly
|
||||
* register components in advance.
|
||||
*
|
||||
* These operations also suspend deferred mode.
|
||||
*/
|
||||
typedef struct ecs_suspend_readonly_state_t {
|
||||
bool is_readonly;
|
||||
bool is_deferred;
|
||||
int32_t defer_count;
|
||||
ecs_entity_t scope;
|
||||
ecs_entity_t with;
|
||||
ecs_vec_t commands;
|
||||
ecs_stack_t defer_stack;
|
||||
ecs_stage_t *stage;
|
||||
} ecs_suspend_readonly_state_t;
|
||||
|
||||
ecs_world_t* flecs_suspend_readonly(
|
||||
const ecs_world_t *world,
|
||||
ecs_suspend_readonly_state_t *state);
|
||||
|
||||
void flecs_resume_readonly(
|
||||
ecs_world_t *world,
|
||||
ecs_suspend_readonly_state_t *state);
|
||||
|
||||
/* Convenience macro's for world allocator */
|
||||
#define flecs_walloc(world, size)\
|
||||
flecs_alloc(&world->allocator, size)
|
||||
#define flecs_walloc_n(world, T, count)\
|
||||
flecs_alloc_n(&world->allocator, T, count)
|
||||
#define flecs_wcalloc(world, size)\
|
||||
flecs_calloc(&world->allocator, size)
|
||||
#define flecs_wcalloc_n(world, T, count)\
|
||||
flecs_calloc_n(&world->allocator, T, count)
|
||||
#define flecs_wfree(world, size, ptr)\
|
||||
flecs_free(&world->allocator, size, ptr)
|
||||
#define flecs_wfree_n(world, T, count, ptr)\
|
||||
flecs_free_n(&world->allocator, T, count, ptr)
|
||||
#define flecs_wrealloc(world, size_dst, size_src, ptr)\
|
||||
flecs_realloc(&world->allocator, size_dst, size_src, ptr)
|
||||
#define flecs_wrealloc_n(world, T, count_dst, count_src, ptr)\
|
||||
flecs_realloc_n(&world->allocator, T, count_dst, count_src, ptr)
|
||||
#define flecs_wdup(world, size, ptr)\
|
||||
flecs_dup(&world->allocator, size, ptr)
|
||||
#define flecs_wdup_n(world, T, count, ptr)\
|
||||
flecs_dup_n(&world->allocator, T, count, ptr)
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user