From 07e1c0f35d9fc4d9a27ec765a21ea9d95117b76b Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 27 May 2022 19:50:07 -0700 Subject: [PATCH] Add monitor addon & extend statistics API --- README.md | 1 + flecs.c | 952 +++++++++++++----- flecs.h | 289 ++++-- include/flecs.h | 3 +- include/flecs/addons/cpp/flecs.hpp | 6 + .../flecs/addons/cpp/mixins/monitor/decl.hpp | 9 + .../flecs/addons/cpp/mixins/monitor/impl.hpp | 11 + include/flecs/addons/module.h | 35 +- include/flecs/addons/monitor.h | 52 + include/flecs/addons/stats.h | 166 +-- include/flecs/private/addons.h | 9 + meson.build | 1 + src/addons/module.c | 15 +- src/addons/monitor.c | 170 ++++ src/addons/rest.c | 370 +++++-- src/addons/stats.c | 382 +++---- src/id_record.c | 20 +- src/observer.c | 2 +- src/world.c | 3 + test/addons/src/Modules.c | 13 +- test/addons/src/Pipeline.c | 8 +- test/addons/src/Stats.c | 72 +- test/api/project.json | 4 +- test/api/src/Filter.c | 34 + test/api/src/OnDelete.c | 32 +- test/api/src/Query.c | 29 + test/api/src/main.c | 14 +- 27 files changed, 1930 insertions(+), 772 deletions(-) create mode 100644 include/flecs/addons/cpp/mixins/monitor/decl.hpp create mode 100644 include/flecs/addons/cpp/mixins/monitor/impl.hpp create mode 100644 include/flecs/addons/monitor.h create mode 100644 src/addons/monitor.c diff --git a/README.md b/README.md index 465a4a9a3b..8b0c1f6cc2 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,7 @@ Addon | Description | Define [Rules](https://flecs.docsforge.com/master/api-rules/) | Powerful prolog-like query language | FLECS_RULES | [Snapshot](https://flecs.docsforge.com/master/api-snapshot/) | Take snapshots of the world & restore them | FLECS_SNAPSHOT | [Stats](https://flecs.docsforge.com/master/api-stats/) | See what's happening in a world with statistics | FLECS_STATS | +[Monitor](https://flecs.docsforge.com/master/api-monitor/) | Periodically collect & store statistics | FLECS_MONITOR | [Log](https://flecs.docsforge.com/master/api-log/) | Extended tracing and error logging | FLECS_LOG | [App](https://flecs.docsforge.com/master/api-app/) | Flecs application framework | FLECS_APP | [OS API Impl](https://flecs.docsforge.com/master/api-os-api-impl/) | Default OS API implementation for Posix/Win32 | FLECS_OS_API_IMPL | diff --git a/flecs.c b/flecs.c index 2486fcc96b..eca521424d 100644 --- a/flecs.c +++ b/flecs.c @@ -15667,6 +15667,175 @@ void FlecsPipelineImport( #endif +#ifdef FLECS_MONITOR + +ECS_COMPONENT_DECLARE(FlecsMonitor); +ECS_COMPONENT_DECLARE(EcsWorldStats); +ECS_DECLARE(EcsPeriod1s); +ECS_DECLARE(EcsPeriod1m); +ECS_DECLARE(EcsPeriod1h); +ECS_DECLARE(EcsPeriod1d); +ECS_DECLARE(EcsPeriod1w); + +void MonitorWorldStats(ecs_iter_t *it) { + EcsWorldStats *stats = ecs_term(it, EcsWorldStats, 1); + + FLECS_FLOAT elapsed = stats->elapsed; + stats->elapsed += it->delta_time; + + ecs_world_stats_get(it->real_world, &stats->stats); + + int32_t t_last = (int32_t)(elapsed * 60); + int32_t t_next = (int32_t)(stats->elapsed * 60); + int32_t i, dif = t_last - t_next; + if (!dif) { + /* Still in same interval, combine with last measurement */ + ecs_world_stats_reduce_last(&stats->stats); + } else if (dif > 1) { + /* More than 16ms has passed, backfill */ + for (i = 1; i < dif; i ++) { + ecs_world_stats_copy_last(&stats->stats); + } + } +} + +void ReduceWorldStats(ecs_iter_t *it) { + EcsWorldStats *dst = ecs_term(it, EcsWorldStats, 1); + EcsWorldStats *src = ecs_term(it, EcsWorldStats, 2); + + ecs_world_stats_reduce(&dst->stats, &src->stats); +} + +void ReduceWorldStats1Day(ecs_iter_t *it) { + EcsWorldStats *dst = ecs_term(it, EcsWorldStats, 1); + EcsWorldStats *src = ecs_term(it, EcsWorldStats, 2); + + /* Reduce from minutes to the current day */ + ecs_world_stats_reduce(&dst->stats, &src->stats); + + dst->elapsed ++; + if (dst->elapsed < 1440) { + /* 1440 minutes in a day */ + ecs_world_stats_reduce_last(&dst->stats); + } else { + dst->elapsed = 0; + } +} + +void ReduceWorldStats1Week(ecs_iter_t *it) { + EcsWorldStats *dst = ecs_term(it, EcsWorldStats, 1); + EcsWorldStats *src = ecs_term(it, EcsWorldStats, 2); + + /* Reduce from minutes to the current day */ + ecs_world_stats_reduce(&dst->stats, &src->stats); + + dst->elapsed ++; + if (dst->elapsed < 168) { + /* 1440 minutes in a day */ + ecs_world_stats_reduce_last(&dst->stats); + } else { + dst->elapsed = 0; + } +} + +void FlecsMonitorImport( + ecs_world_t *world) +{ + ECS_MODULE_DEFINE(world, FlecsMonitor); + + ecs_set_name_prefix(world, "Ecs"); + + ECS_COMPONENT_DEFINE(world, EcsWorldStats); + ECS_TAG_DEFINE(world, EcsPeriod1s); + ECS_TAG_DEFINE(world, EcsPeriod1m); + ECS_TAG_DEFINE(world, EcsPeriod1h); + ECS_TAG_DEFINE(world, EcsPeriod1d); + ECS_TAG_DEFINE(world, EcsPeriod1w); + + // Called each frame, collects 60 measurements per second + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1s", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1s), + .subj.entity = EcsWorld + }}, + .callback = MonitorWorldStats + }); + + // Called each second, reduces into 60 measurements per minute + ecs_entity_t mw1m = ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1m", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1m), + .subj.entity = EcsWorld + }, { + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1s), + .subj.entity = EcsWorld + }}, + .callback = ReduceWorldStats, + .interval = 1 + }); + + // Called each minute, reduces into 60 measurements per hour + ecs_entity_t mw1h = ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1h", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1h), + .subj.entity = EcsWorld + }, { + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1m), + .subj.entity = EcsWorld + }}, + .callback = ReduceWorldStats, + .rate = 60, + .tick_source = mw1m + }); + + // Called each minute, reduces into 60 measurements per day + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1d", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1d), + .subj.entity = EcsWorld + }, { + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1m), + .subj.entity = EcsWorld + }}, + .callback = ReduceWorldStats1Day, + .rate = 60, + .tick_source = mw1m + }); + + // Called each hour, reduces into 60 measurements per week + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1w", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1w), + .subj.entity = EcsWorld + }, { + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1h), + .subj.entity = EcsWorld + }}, + .callback = ReduceWorldStats1Week, + .rate = 60, + .tick_source = mw1h + }); + + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1s, {0}); + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1m, {0}); + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1h, {0}); + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1d, {0}); + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1w, {0}); + + if (ecs_os_has_time()) { + ecs_measure_frame_time(world, true); + ecs_measure_system_time(world, true); + } +} + +#endif + + #ifdef FLECS_TIMER static @@ -22509,7 +22678,7 @@ char* ecs_module_path_from_c( ecs_entity_t ecs_import( ecs_world_t *world, - ecs_module_action_t init_action, + ecs_module_action_t module, const char *module_name) { ecs_check(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); @@ -22526,7 +22695,7 @@ ecs_entity_t ecs_import( ecs_log_push(); /* Load module */ - init_action(world); + module(world); /* Lookup module entity (must be registered by module) */ e = ecs_lookup_fullpath(world, module_name); @@ -22544,6 +22713,17 @@ ecs_entity_t ecs_import( 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, @@ -26401,6 +26581,18 @@ const char* ecs_parse_expr( #include +#define ECS_GAUGE_RECORD(m, t, value)\ + flecs_gauge_record(m, t, (float)value) + +#define ECS_COUNTER_RECORD(m, t, value)\ + flecs_counter_record(m, t, (float)value) + +#define ECS_METRIC_FIRST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int32_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) @@ -26416,38 +26608,31 @@ int32_t t_prev( } static -void _record_gauge( - ecs_gauge_t *m, +void flecs_gauge_record( + ecs_metric_t *m, int32_t t, float value) { - m->avg[t] = value; - m->min[t] = value; - m->max[t] = value; + m->gauge.avg[t] = value; + m->gauge.min[t] = value; + m->gauge.max[t] = value; } static -float _record_counter( - ecs_counter_t *m, +float flecs_counter_record( + ecs_metric_t *m, int32_t t, float value) { int32_t tp = t_prev(t); - float prev = m->value[tp]; - m->value[t] = value; - _record_gauge((ecs_gauge_t*)m, t, value - prev); + float prev = m->counter.value[tp]; + m->counter.value[t] = value; + flecs_gauge_record(m, t, value - prev); return value - prev; } -/* Macro's to silence conversion warnings without adding casts everywhere */ -#define record_gauge(m, t, value)\ - _record_gauge(m, t, (float)value) - -#define record_counter(m, t, value)\ - _record_counter(m, t, (float)value) - static -void print_value( +void flecs_metric_print( const char *name, float value) { @@ -26456,54 +26641,85 @@ void print_value( } static -void print_gauge( +void flecs_gauge_print( const char *name, int32_t t, - const ecs_gauge_t *m) + const ecs_metric_t *m) { - print_value(name, m->avg[t]); + flecs_metric_print(name, m->gauge.avg[t]); } static -void print_counter( +void flecs_counter_print( const char *name, int32_t t, - const ecs_counter_t *m) + const ecs_metric_t *m) { - print_value(name, m->rate.avg[t]); + flecs_metric_print(name, m->counter.rate.avg[t]); } -void ecs_gauge_reduce( - ecs_gauge_t *dst, +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, int32_t t_dst, - ecs_gauge_t *src, 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->min[t_dst] = 0; - dst->avg[t_dst] = 0; - dst->max[t_dst] = 0; + dst->gauge.min[t_dst] = 0; + dst->gauge.avg[t_dst] = 0; + dst->gauge.max[t_dst] = 0; int32_t i; for (i = 0; i < ECS_STAT_WINDOW; i ++) { int32_t t = (t_src + i) % ECS_STAT_WINDOW; - dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW; - if (!min_set || (src->min[t] < dst->min[t_dst])) { - dst->min[t_dst] = src->min[t]; + dst->gauge.avg[t_dst] += src->gauge.avg[t] / (float)ECS_STAT_WINDOW; + + 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->max[t] > dst->max[t_dst])) { - dst->max[t_dst] = src->max[t]; + 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 t) +{ + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_metric_reduce(m, m, t, t_next(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; } -void ecs_get_world_stats( +void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *s) { @@ -26514,106 +26730,111 @@ void ecs_get_world_stats( int32_t t = s->t = t_next(s->t); - float delta_world_time = record_counter(&s->world_time_total_raw, t, world->info.world_time_total_raw); - record_counter(&s->world_time_total, t, world->info.world_time_total); - record_counter(&s->frame_time_total, t, world->info.frame_time_total); - record_counter(&s->system_time_total, t, world->info.system_time_total); - record_counter(&s->merge_time_total, t, world->info.merge_time_total); + float delta_world_time = ECS_COUNTER_RECORD(&s->world_time_total_raw, t, world->info.world_time_total_raw); + ECS_COUNTER_RECORD(&s->world_time_total, t, world->info.world_time_total); + ECS_COUNTER_RECORD(&s->frame_time_total, t, world->info.frame_time_total); + ECS_COUNTER_RECORD(&s->system_time_total, t, world->info.system_time_total); + ECS_COUNTER_RECORD(&s->merge_time_total, t, world->info.merge_time_total); - float delta_frame_count = record_counter(&s->frame_count_total, t, world->info.frame_count_total); - record_counter(&s->merge_count_total, t, world->info.merge_count_total); - record_counter(&s->pipeline_build_count_total, t, world->info.pipeline_build_count_total); - record_counter(&s->systems_ran_frame, t, world->info.systems_ran_frame); + float delta_frame_count = ECS_COUNTER_RECORD(&s->frame_count_total, t, world->info.frame_count_total); + ECS_COUNTER_RECORD(&s->merge_count_total, t, world->info.merge_count_total); + ECS_COUNTER_RECORD(&s->pipeline_build_count_total, t, world->info.pipeline_build_count_total); + ECS_COUNTER_RECORD(&s->systems_ran_frame, t, world->info.systems_ran_frame); if (delta_world_time != 0.0f && delta_frame_count != 0.0f) { - record_gauge( + ECS_GAUGE_RECORD( &s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count)); } else { - record_gauge(&s->fps, t, 0); + ECS_GAUGE_RECORD(&s->fps, t, 0); } - record_gauge(&s->entity_count, t, flecs_sparse_count(ecs_eis(world))); - record_gauge(&s->entity_not_alive_count, t, + ECS_GAUGE_RECORD(&s->delta_time, t, delta_world_time); + + ECS_GAUGE_RECORD(&s->entity_count, t, flecs_sparse_count(ecs_eis(world))); + ECS_GAUGE_RECORD(&s->entity_not_alive_count, t, flecs_sparse_not_alive_count(ecs_eis(world))); - record_gauge(&s->id_count, t, world->info.id_count); - record_gauge(&s->tag_id_count, t, world->info.tag_id_count); - record_gauge(&s->component_id_count, t, world->info.component_id_count); - record_gauge(&s->pair_id_count, t, world->info.pair_id_count); - record_gauge(&s->wildcard_id_count, t, world->info.wildcard_id_count); - record_gauge(&s->component_count, t, ecs_sparse_count(world->type_info)); - - record_gauge(&s->query_count, t, flecs_sparse_count(world->queries)); - record_gauge(&s->trigger_count, t, ecs_count(world, EcsTrigger)); - record_gauge(&s->observer_count, t, ecs_count(world, EcsObserver)); - record_gauge(&s->system_count, t, ecs_count(world, EcsSystem)); - - record_counter(&s->id_create_count, t, world->info.id_create_total); - record_counter(&s->id_delete_count, t, world->info.id_delete_total); - record_counter(&s->table_create_count, t, world->info.table_create_total); - record_counter(&s->table_delete_count, t, world->info.table_delete_total); - - record_counter(&s->new_count, t, world->new_count); - record_counter(&s->bulk_new_count, t, world->bulk_new_count); - record_counter(&s->delete_count, t, world->delete_count); - record_counter(&s->clear_count, t, world->clear_count); - record_counter(&s->add_count, t, world->add_count); - record_counter(&s->remove_count, t, world->remove_count); - record_counter(&s->set_count, t, world->set_count); - record_counter(&s->discard_count, t, world->discard_count); - - /* Compute table statistics */ - int32_t empty_table_count = 0; - int32_t singleton_table_count = 0; + ECS_GAUGE_RECORD(&s->id_count, t, world->info.id_count); + ECS_GAUGE_RECORD(&s->tag_id_count, t, world->info.tag_id_count); + ECS_GAUGE_RECORD(&s->component_id_count, t, world->info.component_id_count); + ECS_GAUGE_RECORD(&s->pair_id_count, t, world->info.pair_id_count); + ECS_GAUGE_RECORD(&s->wildcard_id_count, t, world->info.wildcard_id_count); + ECS_GAUGE_RECORD(&s->component_count, t, ecs_sparse_count(world->type_info)); + + ECS_GAUGE_RECORD(&s->query_count, t, flecs_sparse_count(world->queries)); + ECS_GAUGE_RECORD(&s->trigger_count, t, ecs_count(world, EcsTrigger)); + ECS_GAUGE_RECORD(&s->observer_count, t, ecs_count(world, EcsObserver)); + ECS_GAUGE_RECORD(&s->system_count, t, ecs_count(world, EcsSystem)); + + ECS_COUNTER_RECORD(&s->id_create_count, t, world->info.id_create_total); + ECS_COUNTER_RECORD(&s->id_delete_count, t, world->info.id_delete_total); + ECS_COUNTER_RECORD(&s->table_create_count, t, world->info.table_create_total); + ECS_COUNTER_RECORD(&s->table_delete_count, t, world->info.table_delete_total); + + ECS_COUNTER_RECORD(&s->new_count, t, world->new_count); + ECS_COUNTER_RECORD(&s->bulk_new_count, t, world->bulk_new_count); + ECS_COUNTER_RECORD(&s->delete_count, t, world->delete_count); + ECS_COUNTER_RECORD(&s->clear_count, t, world->clear_count); + ECS_COUNTER_RECORD(&s->add_count, t, world->add_count); + ECS_COUNTER_RECORD(&s->remove_count, t, world->remove_count); + ECS_COUNTER_RECORD(&s->set_count, t, world->set_count); + ECS_COUNTER_RECORD(&s->discard_count, t, world->discard_count); + + ECS_GAUGE_RECORD(&s->table_count, t, world->info.table_count); + ECS_GAUGE_RECORD(&s->empty_table_count, t, world->info.empty_table_count); + ECS_GAUGE_RECORD(&s->tag_table_count, t, world->info.tag_table_count); + ECS_GAUGE_RECORD(&s->trivial_table_count, t, world->info.trivial_table_count); + ECS_GAUGE_RECORD(&s->table_storage_count, t, world->info.table_storage_count); + ECS_GAUGE_RECORD(&s->table_record_count, t, world->info.table_record_count); - int32_t i, count = flecs_sparse_count(&world->store.tables); - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, i); - int32_t entity_count = ecs_table_count(table); +error: + return; +} - if (!entity_count) { - empty_table_count ++; - } +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + int32_t t_dst = dst->t = t_next(dst->t); + int32_t t_src = src->t; - /* Singleton tables are tables that have just one entity that also has - * itself in the table type. */ - if (entity_count == 1) { - ecs_entity_t *entities = ecs_storage_first_t( - &table->data.entities, ecs_entity_t); - if (ecs_search(world, table, entities[0], 0)) { - singleton_table_count ++; - } - } + ecs_metric_t *cur = ECS_METRIC_FIRST(dst), *last = ECS_METRIC_LAST(dst); + ecs_metric_t *src_cur = ECS_METRIC_FIRST(src); + + for (; cur != last; cur ++, src_cur ++) { + ecs_metric_reduce(cur, src_cur, t_dst, t_src); } +} - /* Correct for root table */ - count --; - empty_table_count --; +void ecs_world_stats_reduce_last( + ecs_world_stats_t *stats) +{ + int32_t t = stats->t; - if (count != world->info.table_count) { - ecs_warn("world::table_count (%d) is not equal to computed number (%d)", - world->info.table_count, count); - } - if (empty_table_count != world->info.empty_table_count) { - ecs_warn("world::empty_table_count (%d) is not equal to computed" - " number (%d)", - world->info.empty_table_count, empty_table_count); + ecs_metric_t *cur = ECS_METRIC_FIRST(stats), *last = ECS_METRIC_LAST(stats); + + for (; cur != last; cur ++) { + ecs_metric_reduce_last(cur, t); } - record_gauge(&s->table_count, t, count); - record_gauge(&s->empty_table_count, t, empty_table_count); - record_gauge(&s->singleton_table_count, t, singleton_table_count); - record_gauge(&s->tag_table_count, t, world->info.tag_table_count); - record_gauge(&s->trivial_table_count, t, world->info.trivial_table_count); - record_gauge(&s->table_storage_count, t, world->info.table_storage_count); - record_gauge(&s->table_record_count, t, world->info.table_record_count); + stats->t = t_prev(t); +} -error: - return; +void ecs_world_stats_copy_last( + ecs_world_stats_t *stats) +{ + int32_t t_last = stats->t, t = t_next(t_last); + + ecs_metric_t *cur = ECS_METRIC_FIRST(stats), *last = ECS_METRIC_LAST(stats); + + for (; cur != last; cur ++) { + ecs_metric_copy(cur, t_last, t); + } + + stats->t = t; } -void ecs_get_query_stats( +void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *s) @@ -26626,16 +26847,16 @@ void ecs_get_query_stats( int32_t t = s->t = t_next(s->t); ecs_iter_t it = ecs_query_iter(world, (ecs_query_t*)query); - record_gauge(&s->matched_entity_count, t, ecs_iter_count(&it)); - record_gauge(&s->matched_table_count, t, ecs_query_table_count(query)); - record_gauge(&s->matched_empty_table_count, t, + ECS_GAUGE_RECORD(&s->matched_entity_count, t, ecs_iter_count(&it)); + 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)); error: return; } #ifdef FLECS_SYSTEM -bool ecs_get_system_stats( +bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *s) @@ -26651,13 +26872,13 @@ bool ecs_get_system_stats( return false; } - ecs_get_query_stats(world, ptr->query, &s->query_stats); + ecs_query_stats_get(world, ptr->query, &s->query_stats); int32_t t = s->query_stats.t; - record_counter(&s->time_spent, t, ptr->time_spent); - record_counter(&s->invoke_count, t, ptr->invoke_count); - record_gauge(&s->active, t, !ecs_has_id(world, system, EcsInactive)); - record_gauge(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); + ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); + ECS_GAUGE_RECORD(&s->active, t, !ecs_has_id(world, system, EcsInactive)); + ECS_GAUGE_RECORD(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); return true; error: @@ -26686,7 +26907,7 @@ ecs_system_stats_t* get_system_stats( return NULL; } -bool ecs_get_pipeline_stats( +bool ecs_pipeline_stats_get( ecs_world_t *stage, ecs_entity_t pipeline, ecs_pipeline_stats_t *s) @@ -26773,7 +26994,7 @@ bool ecs_get_pipeline_stats( for (i = 0; i < it.count; i ++) { ecs_system_stats_t *sys_stats = get_system_stats( s->system_stats, it.entities[i]); - ecs_get_system_stats(world, it.entities[i], sys_stats); + ecs_system_stats_get(world, it.entities[i], sys_stats); } } @@ -26791,7 +27012,7 @@ void ecs_pipeline_stats_fini( #endif -void ecs_dump_world_stats( +void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *s) { @@ -26802,56 +27023,55 @@ void ecs_dump_world_stats( world = ecs_get_world(world); - print_counter("Frame", t, &s->frame_count_total); + flecs_counter_print("Frame", t, &s->frame_count_total); ecs_trace("-------------------------------------"); - print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total); - print_counter("systems invocations", t, &s->systems_ran_frame); + flecs_counter_print("pipeline rebuilds", t, &s->pipeline_build_count_total); + flecs_counter_print("systems invocations", t, &s->systems_ran_frame); ecs_trace(""); - print_value("target FPS", world->info.target_fps); - print_value("time scale", world->info.time_scale); + flecs_metric_print("target FPS", world->info.target_fps); + flecs_metric_print("time scale", world->info.time_scale); ecs_trace(""); - print_gauge("actual FPS", t, &s->fps); - print_counter("frame time", t, &s->frame_time_total); - print_counter("system time", t, &s->system_time_total); - print_counter("merge time", t, &s->merge_time_total); - print_counter("simulation time elapsed", t, &s->world_time_total); + flecs_gauge_print("actual FPS", t, &s->fps); + flecs_counter_print("frame time", t, &s->frame_time_total); + flecs_counter_print("system time", t, &s->system_time_total); + flecs_counter_print("merge time", t, &s->merge_time_total); + flecs_counter_print("simulation time elapsed", t, &s->world_time_total); ecs_trace(""); - print_gauge("id count", t, &s->id_count); - print_gauge("tag id count", t, &s->tag_id_count); - print_gauge("component id count", t, &s->component_id_count); - print_gauge("pair id count", t, &s->pair_id_count); - print_gauge("wildcard id count", t, &s->wildcard_id_count); - print_gauge("component count", t, &s->component_count); + flecs_gauge_print("id count", t, &s->id_count); + flecs_gauge_print("tag id count", t, &s->tag_id_count); + flecs_gauge_print("component id count", t, &s->component_id_count); + flecs_gauge_print("pair id count", t, &s->pair_id_count); + flecs_gauge_print("wildcard id count", t, &s->wildcard_id_count); + flecs_gauge_print("component count", t, &s->component_count); ecs_trace(""); - print_gauge("alive entity count", t, &s->entity_count); - print_gauge("not alive entity count", t, &s->entity_not_alive_count); + flecs_gauge_print("alive entity count", t, &s->entity_count); + flecs_gauge_print("not alive entity count", t, &s->entity_not_alive_count); ecs_trace(""); - print_gauge("query count", t, &s->query_count); - print_gauge("trigger count", t, &s->trigger_count); - print_gauge("observer count", t, &s->observer_count); - print_gauge("system count", t, &s->system_count); + flecs_gauge_print("query count", t, &s->query_count); + flecs_gauge_print("trigger count", t, &s->trigger_count); + flecs_gauge_print("observer count", t, &s->observer_count); + flecs_gauge_print("system count", t, &s->system_count); ecs_trace(""); - print_gauge("table count", t, &s->table_count); - print_gauge("empty table count", t, &s->empty_table_count); - print_gauge("tag table count", t, &s->tag_table_count); - print_gauge("trivial table count", t, &s->trivial_table_count); - print_gauge("table storage count", t, &s->table_storage_count); - print_gauge("table cache record count", t, &s->table_record_count); - print_gauge("singleton table count", t, &s->singleton_table_count); + flecs_gauge_print("table count", t, &s->table_count); + flecs_gauge_print("empty table count", t, &s->empty_table_count); + flecs_gauge_print("tag table count", t, &s->tag_table_count); + flecs_gauge_print("trivial table count", t, &s->trivial_table_count); + flecs_gauge_print("table storage count", t, &s->table_storage_count); + flecs_gauge_print("table cache record count", t, &s->table_record_count); ecs_trace(""); - print_counter("table create count", t, &s->table_create_count); - print_counter("table delete count", t, &s->table_delete_count); - print_counter("id create count", t, &s->id_create_count); - print_counter("id delete count", t, &s->id_delete_count); + flecs_counter_print("table create count", t, &s->table_create_count); + flecs_counter_print("table delete count", t, &s->table_delete_count); + flecs_counter_print("id create count", t, &s->id_create_count); + flecs_counter_print("id delete count", t, &s->id_delete_count); ecs_trace(""); - print_counter("deferred new operations", t, &s->new_count); - print_counter("deferred bulk_new operations", t, &s->bulk_new_count); - print_counter("deferred delete operations", t, &s->delete_count); - print_counter("deferred clear operations", t, &s->clear_count); - print_counter("deferred add operations", t, &s->add_count); - print_counter("deferred remove operations", t, &s->remove_count); - print_counter("deferred set operations", t, &s->set_count); - print_counter("discarded operations", t, &s->discard_count); + flecs_counter_print("deferred new operations", t, &s->new_count); + flecs_counter_print("deferred bulk_new operations", t, &s->bulk_new_count); + flecs_counter_print("deferred delete operations", t, &s->delete_count); + flecs_counter_print("deferred clear operations", t, &s->clear_count); + flecs_counter_print("deferred add operations", t, &s->add_count); + flecs_counter_print("deferred remove operations", t, &s->remove_count); + flecs_counter_print("deferred set operations", t, &s->set_count); + flecs_counter_print("discarded operations", t, &s->discard_count); ecs_trace(""); error: @@ -30870,7 +31090,7 @@ static ECS_DTOR(EcsRest, ptr, { static char *rest_last_err; static -void rest_capture_log( +void flecs_rest_capture_log( int32_t level, const char *file, int32_t line, @@ -30884,14 +31104,14 @@ void rest_capture_log( } static -char* rest_get_captured_log(void) { +char* flecs_rest_get_captured_log(void) { char *result = rest_last_err; rest_last_err = NULL; return result; } static -void reply_verror( +void flecs_reply_verror( ecs_http_reply_t *reply, const char *fmt, va_list args) @@ -30902,19 +31122,19 @@ void reply_verror( } static -void reply_error( +void flecs_reply_error( ecs_http_reply_t *reply, const char *fmt, ...) { va_list args; va_start(args, fmt); - reply_verror(reply, fmt, args); + flecs_reply_verror(reply, fmt, args); va_end(args); } static -void rest_bool_param( +void flecs_rest_bool_param( const ecs_http_request_t *req, const char *name, bool *value_out) @@ -30930,7 +31150,7 @@ void rest_bool_param( } static -void rest_int_param( +void flecs_rest_int_param( const ecs_http_request_t *req, const char *name, int32_t *value_out) @@ -30942,41 +31162,271 @@ void rest_int_param( } static -void rest_parse_json_ser_entity_params( +void flecs_rest_string_param( + const ecs_http_request_t *req, + const char *name, + char **value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + *value_out = (char*)value; + } +} + +static +void flecs_rest_parse_json_ser_entity_params( ecs_entity_to_json_desc_t *desc, const ecs_http_request_t *req) { - rest_bool_param(req, "path", &desc->serialize_path); - rest_bool_param(req, "label", &desc->serialize_label); - rest_bool_param(req, "brief", &desc->serialize_brief); - rest_bool_param(req, "link", &desc->serialize_link); - rest_bool_param(req, "id_labels", &desc->serialize_id_labels); - rest_bool_param(req, "base", &desc->serialize_base); - rest_bool_param(req, "values", &desc->serialize_values); - rest_bool_param(req, "private", &desc->serialize_private); - rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "path", &desc->serialize_path); + flecs_rest_bool_param(req, "label", &desc->serialize_label); + flecs_rest_bool_param(req, "brief", &desc->serialize_brief); + flecs_rest_bool_param(req, "link", &desc->serialize_link); + flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); + flecs_rest_bool_param(req, "base", &desc->serialize_base); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "private", &desc->serialize_private); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); } static -void rest_parse_json_ser_iter_params( +void flecs_rest_parse_json_ser_iter_params( ecs_iter_to_json_desc_t *desc, const ecs_http_request_t *req) { - rest_bool_param(req, "term_ids", &desc->serialize_term_ids); - rest_bool_param(req, "ids", &desc->serialize_ids); - rest_bool_param(req, "subjects", &desc->serialize_subjects); - rest_bool_param(req, "variables", &desc->serialize_variables); - rest_bool_param(req, "is_set", &desc->serialize_is_set); - rest_bool_param(req, "values", &desc->serialize_values); - rest_bool_param(req, "entities", &desc->serialize_entities); - rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); - rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); - rest_bool_param(req, "duration", &desc->measure_eval_duration); - rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); + flecs_rest_bool_param(req, "ids", &desc->serialize_ids); + flecs_rest_bool_param(req, "subjects", &desc->serialize_subjects); + flecs_rest_bool_param(req, "variables", &desc->serialize_variables); + flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "entities", &desc->serialize_entities); + flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); + flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); + flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); +} + +static +bool flecs_rest_reply_entity( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + char *path = &req->path[7]; + ecs_dbg_2("rest: request entity '%s'", path); + + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); + if (!e) { + ecs_dbg_2("rest: entity '%s' not found", path); + flecs_reply_error(reply, "entity '%s' not found", path); + reply->code = 404; + return true; + } + + ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; + flecs_rest_parse_json_ser_entity_params(&desc, req); + + ecs_entity_to_json_buf(world, e, &reply->body, &desc); + return true; } static -bool rest_reply( +bool flecs_rest_reply_query( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + const char *q = ecs_http_get_param(req, "q"); + if (!q) { + ecs_strbuf_appendstr(&reply->body, "Missing parameter 'q'"); + reply->code = 400; /* bad request */ + return true; + } + + ecs_dbg_2("rest: request query '%s'", q); + bool prev_color = ecs_log_enable_colors(false); + ecs_os_api_log_t prev_log_ = ecs_os_api.log_; + ecs_os_api.log_ = flecs_rest_capture_log; + + ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) { + .expr = q + }); + if (!r) { + char *err = flecs_rest_get_captured_log(); + char *escaped_err = ecs_astresc('"', err); + flecs_reply_error(reply, escaped_err); + reply->code = 400; /* bad request */ + ecs_os_free(escaped_err); + ecs_os_free(err); + } else { + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + flecs_rest_parse_json_ser_iter_params(&desc, req); + + int32_t offset = 0; + int32_t limit = 100; + + flecs_rest_int_param(req, "offset", &offset); + flecs_rest_int_param(req, "limit", &limit); + + ecs_iter_t it = ecs_rule_iter(world, r); + ecs_iter_t pit = ecs_page_iter(&it, offset, limit); + ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); + ecs_rule_fini(r); + } + + ecs_os_api.log_ = prev_log_; + ecs_log_enable_colors(prev_color); + + return true; +} + +void flecs_rest_array_append( + ecs_strbuf_t *reply, + const char *field, + const FLECS_FLOAT *values, + int32_t t) +{ + ecs_strbuf_list_append(reply, "\"%s\"", field); + ecs_strbuf_appendch(reply, ':'); + ecs_strbuf_list_push(reply, "[", ","); + + int32_t i; + for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { + int32_t index = i % ECS_STAT_WINDOW; + ecs_strbuf_list_next(reply); + ecs_strbuf_appendflt(reply, (double)values[index], '"'); + } + + ecs_strbuf_list_pop(reply, "]"); +} + +void flecs_rest_gauge_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t t) +{ + ecs_strbuf_list_append(reply, "\"%s\"", field); + ecs_strbuf_appendch(reply, ':'); + ecs_strbuf_list_push(reply, "{", ","); + + flecs_rest_array_append(reply, "avg", m->gauge.avg, t); + flecs_rest_array_append(reply, "min", m->gauge.min, t); + flecs_rest_array_append(reply, "max", m->gauge.max, t); + + ecs_strbuf_list_pop(reply, "}"); +} + +void flecs_rest_counter_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t t) +{ + flecs_rest_gauge_append(reply, m, field, t); +} + +#define ECS_GAUGE_APPEND(reply, s, field)\ + flecs_rest_gauge_append(reply, &s->stats.field, #field, s->stats.t) + +#define ECS_COUNTER_APPEND(reply, s, field)\ + flecs_rest_counter_append(reply, &s->stats.field, #field, s->stats.t) + +static +void flecs_world_stats_to_json( + ecs_strbuf_t *reply, + const EcsWorldStats *stats) +{ + ecs_strbuf_list_push(reply, "{", ","); + ECS_GAUGE_APPEND(reply, stats, entity_count); + ECS_GAUGE_APPEND(reply, stats, entity_not_alive_count); + ECS_GAUGE_APPEND(reply, stats, id_count); + ECS_GAUGE_APPEND(reply, stats, tag_id_count); + ECS_GAUGE_APPEND(reply, stats, component_id_count); + ECS_GAUGE_APPEND(reply, stats, pair_id_count); + ECS_GAUGE_APPEND(reply, stats, wildcard_id_count); + ECS_GAUGE_APPEND(reply, stats, component_count); + ECS_COUNTER_APPEND(reply, stats, id_create_count); + ECS_COUNTER_APPEND(reply, stats, id_delete_count); + ECS_GAUGE_APPEND(reply, stats, table_count); + ECS_GAUGE_APPEND(reply, stats, empty_table_count); + ECS_GAUGE_APPEND(reply, stats, tag_table_count); + ECS_GAUGE_APPEND(reply, stats, trivial_table_count); + ECS_GAUGE_APPEND(reply, stats, table_record_count); + ECS_GAUGE_APPEND(reply, stats, table_storage_count); + ECS_COUNTER_APPEND(reply, stats, table_create_count); + ECS_COUNTER_APPEND(reply, stats, table_delete_count); + ECS_GAUGE_APPEND(reply, stats, query_count); + ECS_GAUGE_APPEND(reply, stats, trigger_count); + ECS_GAUGE_APPEND(reply, stats, observer_count); + ECS_GAUGE_APPEND(reply, stats, system_count); + ECS_COUNTER_APPEND(reply, stats, new_count); + ECS_COUNTER_APPEND(reply, stats, bulk_new_count); + ECS_COUNTER_APPEND(reply, stats, delete_count); + ECS_COUNTER_APPEND(reply, stats, clear_count); + ECS_COUNTER_APPEND(reply, stats, add_count); + ECS_COUNTER_APPEND(reply, stats, remove_count); + ECS_COUNTER_APPEND(reply, stats, set_count); + ECS_COUNTER_APPEND(reply, stats, discard_count); + ECS_COUNTER_APPEND(reply, stats, world_time_total_raw); + ECS_COUNTER_APPEND(reply, stats, world_time_total); + ECS_COUNTER_APPEND(reply, stats, frame_time_total); + ECS_COUNTER_APPEND(reply, stats, system_time_total); + ECS_COUNTER_APPEND(reply, stats, merge_time_total); + ECS_GAUGE_APPEND(reply, stats, fps); + ECS_GAUGE_APPEND(reply, stats, delta_time); + ECS_COUNTER_APPEND(reply, stats, frame_count_total); + ECS_COUNTER_APPEND(reply, stats, merge_count_total); + ECS_COUNTER_APPEND(reply, stats, pipeline_build_count_total); + ECS_COUNTER_APPEND(reply, stats, systems_ran_frame); + ecs_strbuf_list_pop(reply, "}"); +} + +static +bool flecs_rest_reply_stats( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ +#ifdef FLECS_MONITOR + char *period_str = NULL; + flecs_rest_string_param(req, "period", &period_str); + char *category = &req->path[6]; + + ecs_entity_t period = EcsPeriod1s; + if (period_str) { + char *period_name = ecs_asprintf("Period%s", period_str); + period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); + ecs_os_free(period_name); + if (!period) { + flecs_reply_error(reply, "bad request (invalid period string)"); + reply->code = 400; + return false; + } + } + + if (!ecs_os_strcmp(category, "world")) { + const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, + EcsWorldStats, period); + flecs_world_stats_to_json(&reply->body, stats); + return true; + + } else { + flecs_reply_error(reply, "bad request (unsupported category)"); + reply->code = 400; + return false; + } + + return true; +#else + return false; +#endif +} + +static +bool flecs_rest_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, void *ctx) @@ -30986,7 +31436,7 @@ bool rest_reply( if (req->path == NULL) { ecs_dbg("rest: bad request (missing path)"); - reply_error(reply, "bad request (missing path)"); + flecs_reply_error(reply, "bad request (missing path)"); reply->code = 400; return false; } @@ -30996,71 +31446,18 @@ bool rest_reply( if (req->method == EcsHttpGet) { /* Entity endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { - char *path = &req->path[7]; - ecs_dbg_2("rest: request entity '%s'", path); - - ecs_entity_t e = ecs_lookup_path_w_sep( - world, 0, path, "/", NULL, false); - if (!e) { - ecs_dbg_2("rest: entity '%s' not found", path); - reply_error(reply, "entity '%s' not found", path); - reply->code = 404; - return true; - } - - ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; - rest_parse_json_ser_entity_params(&desc, req); - - ecs_entity_to_json_buf(world, e, &reply->body, &desc); - return true; + return flecs_rest_reply_entity(world, req, reply); /* Query endpoint */ } else if (!ecs_os_strcmp(req->path, "query")) { - const char *q = ecs_http_get_param(req, "q"); - if (!q) { - ecs_strbuf_appendstr(&reply->body, "Missing parameter 'q'"); - reply->code = 400; /* bad request */ - return true; - } + return flecs_rest_reply_query(world, req, reply); - ecs_dbg_2("rest: request query '%s'", q); - bool prev_color = ecs_log_enable_colors(false); - ecs_os_api_log_t prev_log_ = ecs_os_api.log_; - ecs_os_api.log_ = rest_capture_log; - - ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) { - .expr = q - }); - if (!r) { - char *err = rest_get_captured_log(); - char *escaped_err = ecs_astresc('"', err); - reply_error(reply, escaped_err); - reply->code = 400; /* bad request */ - ecs_os_free(escaped_err); - ecs_os_free(err); - } else { - ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; - rest_parse_json_ser_iter_params(&desc, req); - - int32_t offset = 0; - int32_t limit = 100; - - rest_int_param(req, "offset", &offset); - rest_int_param(req, "limit", &limit); - - ecs_iter_t it = ecs_rule_iter(world, r); - ecs_iter_t pit = ecs_page_iter(&it, offset, limit); - ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); - ecs_rule_fini(r); - } - - ecs_os_api.log_ = prev_log_; - ecs_log_enable_colors(prev_color); - - return true; + /* Stats endpoint */ + } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { + return flecs_rest_reply_stats(world, req, reply); } - } - if (req->method == EcsHttpOptions) { + + } else if (req->method == EcsHttpOptions) { return true; } @@ -31068,7 +31465,7 @@ bool rest_reply( } static -void on_set_rest(ecs_iter_t *it) +void flecs_on_set_rest(ecs_iter_t *it) { EcsRest *rest = it->ptrs[0]; @@ -31082,7 +31479,7 @@ void on_set_rest(ecs_iter_t *it) ecs_http_server_t *srv = ecs_http_server_init(&(ecs_http_server_desc_t){ .ipaddr = rest[i].ipaddr, .port = rest[i].port, - .callback = rest_reply, + .callback = flecs_rest_reply, .ctx = srv_ctx }); @@ -31138,7 +31535,7 @@ void FlecsRestImport( .move = ecs_move(EcsRest), .copy = ecs_copy(EcsRest), .dtor = ecs_dtor(EcsRest), - .on_set = on_set_rest + .on_set = flecs_on_set_rest }); ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); @@ -35003,6 +35400,9 @@ void log_addons(void) { #ifdef FLECS_STATS ecs_trace("FLECS_STATS"); #endif + #ifdef FLECS_MONITOR + ecs_trace("FLECS_MONITOR"); + #endif #ifdef FLECS_SYSTEM ecs_trace("FLECS_SYSTEM"); #endif @@ -39613,7 +40013,7 @@ ecs_entity_t ecs_observer_init( .last_event_id = &observer->last_event_id }; - bool optional_only = true; + bool optional_only = filter->flags & EcsFilterMatchThis; for (i = 0; i < filter->term_count; i ++) { if (filter->terms[i].oper != EcsOptional) { if (filter->terms[i].subj.entity == EcsThis) { @@ -48698,15 +49098,17 @@ bool flecs_set_type_info_for_id_record( ecs_id_record_t *idr, const ecs_type_info_t *ti) { - if (ti) { - if (!idr->type_info) { - world->info.tag_id_count --; - world->info.component_id_count ++; - } - } else { - if (idr->type_info) { - world->info.tag_id_count ++; - world->info.component_id_count --; + if (!ecs_id_is_wildcard(idr->id)) { + if (ti) { + if (!idr->type_info) { + world->info.tag_id_count --; + world->info.component_id_count ++; + } + } else { + if (idr->type_info) { + world->info.tag_id_count ++; + world->info.component_id_count --; + } } } diff --git a/flecs.h b/flecs.h index ed034734e3..baea44f327 100644 --- a/flecs.h +++ b/flecs.h @@ -96,7 +96,8 @@ #define FLECS_PLECS /* ECS data definition format */ #define FLECS_RULES /* Constraint solver for advanced queries */ #define FLECS_SNAPSHOT /* Snapshot & restore ECS data */ -#define FLECS_STATS /* Keep track of runtime statistics */ +#define FLECS_STATS /* Access runtime statistics */ +#define FLECS_MONITOR /* Track runtime statistics periodically */ #define FLECS_SYSTEM /* System support */ #define FLECS_PIPELINE /* Pipeline support */ #define FLECS_TIMER /* Timer support */ @@ -8035,6 +8036,9 @@ void* ecs_record_get_column( #ifdef FLECS_NO_SNAPSHOT #undef FLECS_SNAPSHOT #endif +#ifdef FLECS_NO_MONITOR +#undef FLECS_MONITOR +#endif #ifdef FLECS_NO_STATS #undef FLECS_STATS #endif @@ -11618,90 +11622,96 @@ typedef struct ecs_counter_t { float value[ECS_STAT_WINDOW]; } ecs_counter_t; +/* Make all metrics the same size, so we can iterate over fields */ +typedef union ecs_metric_t { + ecs_gauge_t gauge; + ecs_counter_t counter; +} ecs_metric_t; + typedef struct ecs_world_stats_t { - /* Allows struct to be initialized with {0} */ - int32_t dummy_; + int32_t first_; - ecs_gauge_t entity_count; /* Number of entities */ - ecs_gauge_t entity_not_alive_count; /* Number of not alive (recyclable) entity ids */ + ecs_metric_t entity_count; /* Number of entities */ + ecs_metric_t entity_not_alive_count; /* Number of not alive (recyclable) entity ids */ /* Components and ids */ - ecs_gauge_t id_count; /* Number of ids (excluding wildcards) */ - ecs_gauge_t tag_id_count; /* Number of tag ids (ids without data) */ - ecs_gauge_t component_id_count; /* Number of components ids (ids with data) */ - ecs_gauge_t pair_id_count; /* Number of pair ids */ - ecs_gauge_t wildcard_id_count; /* Number of wildcard ids */ - ecs_gauge_t component_count; /* Number of components (non-zero sized types) */ - ecs_counter_t id_create_count; /* Number of times id has been created */ - ecs_counter_t id_delete_count; /* Number of times id has been deleted */ + ecs_metric_t id_count; /* Number of ids (excluding wildcards) */ + ecs_metric_t tag_id_count; /* Number of tag ids (ids without data) */ + ecs_metric_t component_id_count; /* Number of components ids (ids with data) */ + ecs_metric_t pair_id_count; /* Number of pair ids */ + ecs_metric_t wildcard_id_count; /* Number of wildcard ids */ + ecs_metric_t component_count; /* Number of components (non-zero sized types) */ + ecs_metric_t id_create_count; /* Number of times id has been created */ + ecs_metric_t id_delete_count; /* Number of times id has been deleted */ /* Tables */ - ecs_gauge_t table_count; /* Number of tables */ - ecs_gauge_t empty_table_count; /* Number of empty tables */ - ecs_gauge_t singleton_table_count; /* Number of singleton tables. Singleton tables are tables with just a single entity that contains itself */ - ecs_gauge_t tag_table_count; /* Number of tables with only tags */ - ecs_gauge_t trivial_table_count; /* Number of tables with only trivial components */ - ecs_gauge_t table_record_count; /* Number of table cache records */ - ecs_gauge_t table_storage_count; /* Number of table storages */ - ecs_counter_t table_create_count; /* Number of times table has been created */ - ecs_counter_t table_delete_count; /* Number of times table has been deleted */ + ecs_metric_t table_count; /* Number of tables */ + ecs_metric_t empty_table_count; /* Number of empty tables */ + ecs_metric_t tag_table_count; /* Number of tables with only tags */ + ecs_metric_t trivial_table_count; /* Number of tables with only trivial components */ + ecs_metric_t table_record_count; /* Number of table cache records */ + ecs_metric_t table_storage_count; /* Number of table storages */ + ecs_metric_t table_create_count; /* Number of times table has been created */ + ecs_metric_t table_delete_count; /* Number of times table has been deleted */ /* Queries & events */ - ecs_gauge_t query_count; /* Number of queries */ - ecs_gauge_t trigger_count; /* Number of triggers */ - ecs_gauge_t observer_count; /* Number of observers */ - ecs_gauge_t system_count; /* Number of systems */ + ecs_metric_t query_count; /* Number of queries */ + ecs_metric_t trigger_count; /* Number of triggers */ + ecs_metric_t observer_count; /* Number of observers */ + ecs_metric_t system_count; /* Number of systems */ /* Deferred operations */ - ecs_counter_t new_count; - ecs_counter_t bulk_new_count; - ecs_counter_t delete_count; - ecs_counter_t clear_count; - ecs_counter_t add_count; - ecs_counter_t remove_count; - ecs_counter_t set_count; - ecs_counter_t discard_count; + ecs_metric_t new_count; + ecs_metric_t bulk_new_count; + ecs_metric_t delete_count; + ecs_metric_t clear_count; + ecs_metric_t add_count; + ecs_metric_t remove_count; + ecs_metric_t set_count; + ecs_metric_t discard_count; /* Timing */ - ecs_counter_t world_time_total_raw; /* Actual time passed since simulation start (first time progress() is called) */ - ecs_counter_t world_time_total; /* Simulation time passed since simulation start. Takes into account time scaling */ - ecs_counter_t frame_time_total; /* Time spent processing a frame. Smaller than world_time_total when load is not 100% */ - ecs_counter_t system_time_total; /* Time spent on processing systems. */ - ecs_counter_t merge_time_total; /* Time spent on merging deferred actions. */ - ecs_gauge_t fps; /* Frames per second. */ - ecs_gauge_t delta_time; /* Delta_time. */ + ecs_metric_t world_time_total_raw; /* Actual time passed since simulation start (first time progress() is called) */ + ecs_metric_t world_time_total; /* Simulation time passed since simulation start. Takes into account time scaling */ + ecs_metric_t frame_time_total; /* Time spent processing a frame. Smaller than world_time_total when load is not 100% */ + ecs_metric_t system_time_total; /* Time spent on processing systems. */ + ecs_metric_t merge_time_total; /* Time spent on merging deferred actions. */ + ecs_metric_t fps; /* Frames per second. */ + ecs_metric_t delta_time; /* Delta_time. */ /* Frame data */ - ecs_counter_t frame_count_total; /* Number of frames processed. */ - ecs_counter_t merge_count_total; /* Number of merges executed. */ - ecs_counter_t pipeline_build_count_total; /* Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ - ecs_counter_t systems_ran_frame; /* Number of systems ran in the last frame. */ + ecs_metric_t frame_count_total; /* Number of frames processed. */ + ecs_metric_t merge_count_total; /* Number of merges executed. */ + ecs_metric_t pipeline_build_count_total; /* Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ + ecs_metric_t systems_ran_frame; /* Number of systems ran in the last frame. */ + + int32_t last_; /** Current position in ringbuffer */ int32_t t; } ecs_world_stats_t; -/* Statistics for a single query (use ecs_get_query_stats) */ +/* Statistics for a single query (use ecs_query_stats_get) */ typedef struct ecs_query_stats_t { - ecs_gauge_t matched_table_count; /* Number of matched non-empty tables. This is the number of tables + ecs_metric_t matched_table_count; /* Number of matched non-empty tables. This is the number of tables * iterated over when evaluating a query. */ - ecs_gauge_t matched_empty_table_count; /* Number of matched empty tables. Empty tables are not iterated over when + ecs_metric_t matched_empty_table_count; /* Number of matched empty tables. Empty tables are not iterated over when * evaluating a query. */ - ecs_gauge_t matched_entity_count; /* Number of matched entities across all tables */ + ecs_metric_t matched_entity_count; /* Number of matched entities across all tables */ /** Current position in ringbuffer */ int32_t t; } ecs_query_stats_t; -/** Statistics for a single system (use ecs_get_system_stats) */ +/** Statistics for a single system (use ecs_system_stats_get) */ typedef struct ecs_system_stats_t { ecs_query_stats_t query_stats; - ecs_counter_t time_spent; /* Time spent processing a system */ - ecs_counter_t invoke_count; /* Number of times system is invoked */ - ecs_gauge_t active; /* Whether system is active (is matched with >0 entities) */ - ecs_gauge_t enabled; /* Whether system is enabled */ + ecs_metric_t time_spent; /* Time spent processing a system */ + ecs_metric_t invoke_count; /* Number of times system is invoked */ + ecs_metric_t active; /* Whether system is active (is matched with >0 entities) */ + ecs_metric_t enabled; /* Whether system is enabled */ } ecs_system_stats_t; /** Statistics for all systems in a pipeline. */ @@ -11727,10 +11737,37 @@ typedef struct ecs_pipeline_stats_t { * @param stats Out parameter for statistics. */ FLECS_API -void ecs_get_world_stats( +void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *stats); +/** Reduce world statistics. + * This operation reduces values from the last window into a single metric. + * + * @param dst The metrics to reduce to. + * @param src The metrics to reduce. + */ +FLECS_API +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src); + +/** Reduce last measurement into previous measurement. + * + * @param stats The metrics. + */ +FLECS_API +void ecs_world_stats_reduce_last( + ecs_world_stats_t *stats); + +/** Copy last measurement to next measurement. + * + * @param stats The metrics. + */ +FLECS_API +void ecs_world_stats_copy_last( + ecs_world_stats_t *stats); + /** Print world statistics. * Print statistics obtained by ecs_get_world_statistics and in the * ecs_world_info_t struct. @@ -11739,7 +11776,7 @@ void ecs_get_world_stats( * @param stats The statistics to print. */ FLECS_API -void ecs_dump_world_stats( +void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *stats); @@ -11751,7 +11788,7 @@ void ecs_dump_world_stats( * @param stats Out parameter for statistics. */ FLECS_API -void ecs_get_query_stats( +void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *stats); @@ -11766,7 +11803,7 @@ void ecs_get_query_stats( * @return true if success, false if not a system. */ FLECS_API -bool ecs_get_system_stats( +bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *stats); @@ -11782,7 +11819,7 @@ bool ecs_get_system_stats( * @return true if success, false if not a pipeline. */ FLECS_API -bool ecs_get_pipeline_stats( +bool ecs_pipeline_stats_get( ecs_world_t *world, ecs_entity_t pipeline, ecs_pipeline_stats_t *stats); @@ -11798,12 +11835,81 @@ void ecs_pipeline_stats_fini( #endif FLECS_API -void ecs_gauge_reduce( - ecs_gauge_t *dst, +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, int32_t t_dst, - ecs_gauge_t *src, int32_t t_src); +FLECS_API +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t t); + +FLECS_API +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +#endif +#ifdef FLECS_MONITOR +#ifdef FLECS_NO_MONITOR +#error "FLECS_NO_MONITOR failed: MONITOR is required by other addons" +#endif +/** + * @file doc.h + * @brief Doc module. + * + * The monitor module automatically tracks statistics from the stats addon and + * stores them in components. + */ + +#ifdef FLECS_MONITOR + +#ifndef FLECS_MONITOR_H +#define FLECS_MONITOR_H + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_STATS +#define FLECS_STATS +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsMonitor); + +FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldStats); + +FLECS_API extern ECS_DECLARE(EcsPeriod1s); +FLECS_API extern ECS_DECLARE(EcsPeriod1m); +FLECS_API extern ECS_DECLARE(EcsPeriod1h); +FLECS_API extern ECS_DECLARE(EcsPeriod1d); +FLECS_API extern ECS_DECLARE(EcsPeriod1w); + +typedef struct { + ecs_world_stats_t stats; + FLECS_FLOAT elapsed; +} EcsWorldStats; + +/* Module import */ +FLECS_API +void FlecsMonitorImport( + ecs_world_t *world); + #ifdef __cplusplus } #endif @@ -12209,6 +12315,20 @@ ecs_entity_t ecs_import( ecs_module_action_t module, const char *module_name); +/** Same as ecs_import, but with name to scope conversion. + * PascalCase names are automatically converted to scoped names. + * + * @param world The world. + * @param module The module import function. + * @param module_name_c The name of the module. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import_c( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name_c); + /* Import a module from a library. * Similar to ecs_import, except that this operation will attempt to load the * module from a dynamic library. @@ -12242,8 +12362,8 @@ ecs_entity_t ecs_module_init( /** Define module */ -#define ECS_MODULE(world, id)\ - ecs_entity_t ecs_id(id) = ecs_module_init(world, &(ecs_component_desc_t){\ +#define ECS_MODULE_DEFINE(world, id)\ + ecs_id(id) = ecs_module_init(world, &(ecs_component_desc_t){\ .entity = {\ .name = #id,\ .add = {EcsModule}\ @@ -12252,23 +12372,16 @@ ecs_entity_t ecs_module_init( ecs_set_scope(world, ecs_id(id));\ (void)ecs_id(id); +#define ECS_MODULE(world, id)\ + ecs_entity_t ECS_MODULE_DEFINE(world, id) + /** Wrapper around ecs_import. * This macro provides a convenient way to load a module with the world. It can * be used like this: * - * ECS_IMPORT(world, FlecsSystemsPhysics, 0); - * - * This macro will define entity and type handles for the component associated - * with the module. The module component will be created as a singleton. - * - * The contents of a module component are module specific, although they - * typically contain handles to the content of the module. + * ECS_IMPORT(world, FlecsSystemsPhysics); */ -#define ECS_IMPORT(world, id) \ - char *FLECS__##id##_name = ecs_module_path_from_c(#id);\ - ecs_id_t ecs_id(id) = ecs_import(world, id##Import, FLECS__##id##_name);\ - ecs_os_free(FLECS__##id##_name);\ - (void)ecs_id(id) +#define ECS_IMPORT(world, id) ecs_import_c(world, id##Import, #id); #ifdef __cplusplus } @@ -14183,6 +14296,17 @@ units(flecs::world& world); }; } +#endif +#ifdef FLECS_MONITOR +#pragma once + +namespace flecs { + +struct monitor { + monitor(flecs::world& world); +}; + +} #endif #ifdef FLECS_JSON #pragma once @@ -22981,6 +23105,19 @@ inline units::units(flecs::world& world) { } #endif +#ifdef FLECS_MONITOR +#pragma once + +namespace flecs { + +inline monitor::monitor(flecs::world& world) { + /* Import C module */ + FlecsMonitorImport(world); + +} + +} +#endif diff --git a/include/flecs.h b/include/flecs.h index d5f68c61ff..25dc06f400 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -94,7 +94,8 @@ #define FLECS_PLECS /* ECS data definition format */ #define FLECS_RULES /* Constraint solver for advanced queries */ #define FLECS_SNAPSHOT /* Snapshot & restore ECS data */ -#define FLECS_STATS /* Keep track of runtime statistics */ +#define FLECS_STATS /* Access runtime statistics */ +#define FLECS_MONITOR /* Track runtime statistics periodically */ #define FLECS_SYSTEM /* System support */ #define FLECS_PIPELINE /* Pipeline support */ #define FLECS_TIMER /* Timer support */ diff --git a/include/flecs/addons/cpp/flecs.hpp b/include/flecs/addons/cpp/flecs.hpp index f94bc0a647..a0cd966d60 100644 --- a/include/flecs/addons/cpp/flecs.hpp +++ b/include/flecs/addons/cpp/flecs.hpp @@ -77,6 +77,9 @@ struct each_invoker; #ifdef FLECS_UNITS #include "mixins/units/decl.hpp" #endif +#ifdef FLECS_MONITOR +#include "mixins/monitor/decl.hpp" +#endif #ifdef FLECS_JSON #include "mixins/json/decl.hpp" #endif @@ -140,5 +143,8 @@ struct each_invoker; #ifdef FLECS_UNITS #include "mixins/units/impl.hpp" #endif +#ifdef FLECS_MONITOR +#include "mixins/monitor/impl.hpp" +#endif #include "impl.hpp" diff --git a/include/flecs/addons/cpp/mixins/monitor/decl.hpp b/include/flecs/addons/cpp/mixins/monitor/decl.hpp new file mode 100644 index 0000000000..a989e994f9 --- /dev/null +++ b/include/flecs/addons/cpp/mixins/monitor/decl.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace flecs { + +struct monitor { + monitor(flecs::world& world); +}; + +} \ No newline at end of file diff --git a/include/flecs/addons/cpp/mixins/monitor/impl.hpp b/include/flecs/addons/cpp/mixins/monitor/impl.hpp new file mode 100644 index 0000000000..aaaf50d0c2 --- /dev/null +++ b/include/flecs/addons/cpp/mixins/monitor/impl.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace flecs { + +inline monitor::monitor(flecs::world& world) { + /* Import C module */ + FlecsMonitorImport(world); + +} + +} \ No newline at end of file diff --git a/include/flecs/addons/module.h b/include/flecs/addons/module.h index cb9db8875c..64435f503c 100644 --- a/include/flecs/addons/module.h +++ b/include/flecs/addons/module.h @@ -41,6 +41,20 @@ ecs_entity_t ecs_import( ecs_module_action_t module, const char *module_name); +/** Same as ecs_import, but with name to scope conversion. + * PascalCase names are automatically converted to scoped names. + * + * @param world The world. + * @param module The module import function. + * @param module_name_c The name of the module. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import_c( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name_c); + /* Import a module from a library. * Similar to ecs_import, except that this operation will attempt to load the * module from a dynamic library. @@ -74,8 +88,8 @@ ecs_entity_t ecs_module_init( /** Define module */ -#define ECS_MODULE(world, id)\ - ecs_entity_t ecs_id(id) = ecs_module_init(world, &(ecs_component_desc_t){\ +#define ECS_MODULE_DEFINE(world, id)\ + ecs_id(id) = ecs_module_init(world, &(ecs_component_desc_t){\ .entity = {\ .name = #id,\ .add = {EcsModule}\ @@ -84,23 +98,16 @@ ecs_entity_t ecs_module_init( ecs_set_scope(world, ecs_id(id));\ (void)ecs_id(id); +#define ECS_MODULE(world, id)\ + ecs_entity_t ECS_MODULE_DEFINE(world, id) + /** Wrapper around ecs_import. * This macro provides a convenient way to load a module with the world. It can * be used like this: * - * ECS_IMPORT(world, FlecsSystemsPhysics, 0); - * - * This macro will define entity and type handles for the component associated - * with the module. The module component will be created as a singleton. - * - * The contents of a module component are module specific, although they - * typically contain handles to the content of the module. + * ECS_IMPORT(world, FlecsSystemsPhysics); */ -#define ECS_IMPORT(world, id) \ - char *FLECS__##id##_name = ecs_module_path_from_c(#id);\ - ecs_id_t ecs_id(id) = ecs_import(world, id##Import, FLECS__##id##_name);\ - ecs_os_free(FLECS__##id##_name);\ - (void)ecs_id(id) +#define ECS_IMPORT(world, id) ecs_import_c(world, id##Import, #id); #ifdef __cplusplus } diff --git a/include/flecs/addons/monitor.h b/include/flecs/addons/monitor.h new file mode 100644 index 0000000000..ae2e0a4585 --- /dev/null +++ b/include/flecs/addons/monitor.h @@ -0,0 +1,52 @@ +/** + * @file doc.h + * @brief Doc module. + * + * The monitor module automatically tracks statistics from the stats addon and + * stores them in components. + */ + +#ifdef FLECS_MONITOR + +#ifndef FLECS_MONITOR_H +#define FLECS_MONITOR_H + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_STATS +#define FLECS_STATS +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsMonitor); + +FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldStats); + +FLECS_API extern ECS_DECLARE(EcsPeriod1s); +FLECS_API extern ECS_DECLARE(EcsPeriod1m); +FLECS_API extern ECS_DECLARE(EcsPeriod1h); +FLECS_API extern ECS_DECLARE(EcsPeriod1d); +FLECS_API extern ECS_DECLARE(EcsPeriod1w); + +typedef struct { + ecs_world_stats_t stats; + FLECS_FLOAT elapsed; +} EcsWorldStats; + +/* Module import */ +FLECS_API +void FlecsMonitorImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/include/flecs/addons/stats.h b/include/flecs/addons/stats.h index 8878f0ced6..4510de171d 100644 --- a/include/flecs/addons/stats.h +++ b/include/flecs/addons/stats.h @@ -30,90 +30,96 @@ typedef struct ecs_counter_t { float value[ECS_STAT_WINDOW]; } ecs_counter_t; +/* Make all metrics the same size, so we can iterate over fields */ +typedef union ecs_metric_t { + ecs_gauge_t gauge; + ecs_counter_t counter; +} ecs_metric_t; + typedef struct ecs_world_stats_t { - /* Allows struct to be initialized with {0} */ - int32_t dummy_; + int32_t first_; - ecs_gauge_t entity_count; /* Number of entities */ - ecs_gauge_t entity_not_alive_count; /* Number of not alive (recyclable) entity ids */ + ecs_metric_t entity_count; /* Number of entities */ + ecs_metric_t entity_not_alive_count; /* Number of not alive (recyclable) entity ids */ /* Components and ids */ - ecs_gauge_t id_count; /* Number of ids (excluding wildcards) */ - ecs_gauge_t tag_id_count; /* Number of tag ids (ids without data) */ - ecs_gauge_t component_id_count; /* Number of components ids (ids with data) */ - ecs_gauge_t pair_id_count; /* Number of pair ids */ - ecs_gauge_t wildcard_id_count; /* Number of wildcard ids */ - ecs_gauge_t component_count; /* Number of components (non-zero sized types) */ - ecs_counter_t id_create_count; /* Number of times id has been created */ - ecs_counter_t id_delete_count; /* Number of times id has been deleted */ + ecs_metric_t id_count; /* Number of ids (excluding wildcards) */ + ecs_metric_t tag_id_count; /* Number of tag ids (ids without data) */ + ecs_metric_t component_id_count; /* Number of components ids (ids with data) */ + ecs_metric_t pair_id_count; /* Number of pair ids */ + ecs_metric_t wildcard_id_count; /* Number of wildcard ids */ + ecs_metric_t component_count; /* Number of components (non-zero sized types) */ + ecs_metric_t id_create_count; /* Number of times id has been created */ + ecs_metric_t id_delete_count; /* Number of times id has been deleted */ /* Tables */ - ecs_gauge_t table_count; /* Number of tables */ - ecs_gauge_t empty_table_count; /* Number of empty tables */ - ecs_gauge_t singleton_table_count; /* Number of singleton tables. Singleton tables are tables with just a single entity that contains itself */ - ecs_gauge_t tag_table_count; /* Number of tables with only tags */ - ecs_gauge_t trivial_table_count; /* Number of tables with only trivial components */ - ecs_gauge_t table_record_count; /* Number of table cache records */ - ecs_gauge_t table_storage_count; /* Number of table storages */ - ecs_counter_t table_create_count; /* Number of times table has been created */ - ecs_counter_t table_delete_count; /* Number of times table has been deleted */ + ecs_metric_t table_count; /* Number of tables */ + ecs_metric_t empty_table_count; /* Number of empty tables */ + ecs_metric_t tag_table_count; /* Number of tables with only tags */ + ecs_metric_t trivial_table_count; /* Number of tables with only trivial components */ + ecs_metric_t table_record_count; /* Number of table cache records */ + ecs_metric_t table_storage_count; /* Number of table storages */ + ecs_metric_t table_create_count; /* Number of times table has been created */ + ecs_metric_t table_delete_count; /* Number of times table has been deleted */ /* Queries & events */ - ecs_gauge_t query_count; /* Number of queries */ - ecs_gauge_t trigger_count; /* Number of triggers */ - ecs_gauge_t observer_count; /* Number of observers */ - ecs_gauge_t system_count; /* Number of systems */ + ecs_metric_t query_count; /* Number of queries */ + ecs_metric_t trigger_count; /* Number of triggers */ + ecs_metric_t observer_count; /* Number of observers */ + ecs_metric_t system_count; /* Number of systems */ /* Deferred operations */ - ecs_counter_t new_count; - ecs_counter_t bulk_new_count; - ecs_counter_t delete_count; - ecs_counter_t clear_count; - ecs_counter_t add_count; - ecs_counter_t remove_count; - ecs_counter_t set_count; - ecs_counter_t discard_count; + ecs_metric_t new_count; + ecs_metric_t bulk_new_count; + ecs_metric_t delete_count; + ecs_metric_t clear_count; + ecs_metric_t add_count; + ecs_metric_t remove_count; + ecs_metric_t set_count; + ecs_metric_t discard_count; /* Timing */ - ecs_counter_t world_time_total_raw; /* Actual time passed since simulation start (first time progress() is called) */ - ecs_counter_t world_time_total; /* Simulation time passed since simulation start. Takes into account time scaling */ - ecs_counter_t frame_time_total; /* Time spent processing a frame. Smaller than world_time_total when load is not 100% */ - ecs_counter_t system_time_total; /* Time spent on processing systems. */ - ecs_counter_t merge_time_total; /* Time spent on merging deferred actions. */ - ecs_gauge_t fps; /* Frames per second. */ - ecs_gauge_t delta_time; /* Delta_time. */ + ecs_metric_t world_time_total_raw; /* Actual time passed since simulation start (first time progress() is called) */ + ecs_metric_t world_time_total; /* Simulation time passed since simulation start. Takes into account time scaling */ + ecs_metric_t frame_time_total; /* Time spent processing a frame. Smaller than world_time_total when load is not 100% */ + ecs_metric_t system_time_total; /* Time spent on processing systems. */ + ecs_metric_t merge_time_total; /* Time spent on merging deferred actions. */ + ecs_metric_t fps; /* Frames per second. */ + ecs_metric_t delta_time; /* Delta_time. */ /* Frame data */ - ecs_counter_t frame_count_total; /* Number of frames processed. */ - ecs_counter_t merge_count_total; /* Number of merges executed. */ - ecs_counter_t pipeline_build_count_total; /* Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ - ecs_counter_t systems_ran_frame; /* Number of systems ran in the last frame. */ + ecs_metric_t frame_count_total; /* Number of frames processed. */ + ecs_metric_t merge_count_total; /* Number of merges executed. */ + ecs_metric_t pipeline_build_count_total; /* Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ + ecs_metric_t systems_ran_frame; /* Number of systems ran in the last frame. */ + + int32_t last_; /** Current position in ringbuffer */ int32_t t; } ecs_world_stats_t; -/* Statistics for a single query (use ecs_get_query_stats) */ +/* Statistics for a single query (use ecs_query_stats_get) */ typedef struct ecs_query_stats_t { - ecs_gauge_t matched_table_count; /* Number of matched non-empty tables. This is the number of tables + ecs_metric_t matched_table_count; /* Number of matched non-empty tables. This is the number of tables * iterated over when evaluating a query. */ - ecs_gauge_t matched_empty_table_count; /* Number of matched empty tables. Empty tables are not iterated over when + ecs_metric_t matched_empty_table_count; /* Number of matched empty tables. Empty tables are not iterated over when * evaluating a query. */ - ecs_gauge_t matched_entity_count; /* Number of matched entities across all tables */ + ecs_metric_t matched_entity_count; /* Number of matched entities across all tables */ /** Current position in ringbuffer */ int32_t t; } ecs_query_stats_t; -/** Statistics for a single system (use ecs_get_system_stats) */ +/** Statistics for a single system (use ecs_system_stats_get) */ typedef struct ecs_system_stats_t { ecs_query_stats_t query_stats; - ecs_counter_t time_spent; /* Time spent processing a system */ - ecs_counter_t invoke_count; /* Number of times system is invoked */ - ecs_gauge_t active; /* Whether system is active (is matched with >0 entities) */ - ecs_gauge_t enabled; /* Whether system is enabled */ + ecs_metric_t time_spent; /* Time spent processing a system */ + ecs_metric_t invoke_count; /* Number of times system is invoked */ + ecs_metric_t active; /* Whether system is active (is matched with >0 entities) */ + ecs_metric_t enabled; /* Whether system is enabled */ } ecs_system_stats_t; /** Statistics for all systems in a pipeline. */ @@ -139,10 +145,37 @@ typedef struct ecs_pipeline_stats_t { * @param stats Out parameter for statistics. */ FLECS_API -void ecs_get_world_stats( +void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *stats); +/** Reduce world statistics. + * This operation reduces values from the last window into a single metric. + * + * @param dst The metrics to reduce to. + * @param src The metrics to reduce. + */ +FLECS_API +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src); + +/** Reduce last measurement into previous measurement. + * + * @param stats The metrics. + */ +FLECS_API +void ecs_world_stats_reduce_last( + ecs_world_stats_t *stats); + +/** Copy last measurement to next measurement. + * + * @param stats The metrics. + */ +FLECS_API +void ecs_world_stats_copy_last( + ecs_world_stats_t *stats); + /** Print world statistics. * Print statistics obtained by ecs_get_world_statistics and in the * ecs_world_info_t struct. @@ -151,7 +184,7 @@ void ecs_get_world_stats( * @param stats The statistics to print. */ FLECS_API -void ecs_dump_world_stats( +void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *stats); @@ -163,7 +196,7 @@ void ecs_dump_world_stats( * @param stats Out parameter for statistics. */ FLECS_API -void ecs_get_query_stats( +void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *stats); @@ -178,7 +211,7 @@ void ecs_get_query_stats( * @return true if success, false if not a system. */ FLECS_API -bool ecs_get_system_stats( +bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *stats); @@ -194,7 +227,7 @@ bool ecs_get_system_stats( * @return true if success, false if not a pipeline. */ FLECS_API -bool ecs_get_pipeline_stats( +bool ecs_pipeline_stats_get( ecs_world_t *world, ecs_entity_t pipeline, ecs_pipeline_stats_t *stats); @@ -210,12 +243,23 @@ void ecs_pipeline_stats_fini( #endif FLECS_API -void ecs_gauge_reduce( - ecs_gauge_t *dst, +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, int32_t t_dst, - ecs_gauge_t *src, int32_t t_src); +FLECS_API +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t t); + +FLECS_API +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src); + #ifdef __cplusplus } #endif diff --git a/include/flecs/private/addons.h b/include/flecs/private/addons.h index df3bf2aca6..62f8bbcd7e 100644 --- a/include/flecs/private/addons.h +++ b/include/flecs/private/addons.h @@ -38,6 +38,9 @@ #ifdef FLECS_NO_SNAPSHOT #undef FLECS_SNAPSHOT #endif +#ifdef FLECS_NO_MONITOR +#undef FLECS_MONITOR +#endif #ifdef FLECS_NO_STATS #undef FLECS_STATS #endif @@ -189,6 +192,12 @@ #endif #include "../addons/stats.h" #endif +#ifdef FLECS_MONITOR +#ifdef FLECS_NO_MONITOR +#error "FLECS_NO_MONITOR failed: MONITOR is required by other addons" +#endif +#include "../addons/monitor.h" +#endif #ifdef FLECS_PARSER #ifdef FLECS_NO_PARSER #error "FLECS_NO_PARSER failed: PARSER is required by other addons" diff --git a/meson.build b/meson.build index d26595e234..13560ae930 100644 --- a/meson.build +++ b/meson.build @@ -29,6 +29,7 @@ flecs_src = files( 'src/addons/meta/cursor.c', 'src/addons/meta_c.c', 'src/addons/module.c', + 'src/addons/monitor.c', 'src/addons/os_api_impl/os_api_impl.c', 'src/addons/parser.c', 'src/addons/pipeline/pipeline.c', diff --git a/src/addons/module.c b/src/addons/module.c index 5a5bbbc9e2..4a6a74944f 100644 --- a/src/addons/module.c +++ b/src/addons/module.c @@ -28,7 +28,7 @@ char* ecs_module_path_from_c( ecs_entity_t ecs_import( ecs_world_t *world, - ecs_module_action_t init_action, + ecs_module_action_t module, const char *module_name) { ecs_check(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); @@ -45,7 +45,7 @@ ecs_entity_t ecs_import( ecs_log_push(); /* Load module */ - init_action(world); + module(world); /* Lookup module entity (must be registered by module) */ e = ecs_lookup_fullpath(world, module_name); @@ -63,6 +63,17 @@ ecs_entity_t ecs_import( 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, diff --git a/src/addons/monitor.c b/src/addons/monitor.c new file mode 100644 index 0000000000..d2f8a725e2 --- /dev/null +++ b/src/addons/monitor.c @@ -0,0 +1,170 @@ +#include "flecs.h" +#include "../private_api.h" + +#ifdef FLECS_MONITOR + +ECS_COMPONENT_DECLARE(FlecsMonitor); +ECS_COMPONENT_DECLARE(EcsWorldStats); +ECS_DECLARE(EcsPeriod1s); +ECS_DECLARE(EcsPeriod1m); +ECS_DECLARE(EcsPeriod1h); +ECS_DECLARE(EcsPeriod1d); +ECS_DECLARE(EcsPeriod1w); + +void MonitorWorldStats(ecs_iter_t *it) { + EcsWorldStats *stats = ecs_term(it, EcsWorldStats, 1); + + FLECS_FLOAT elapsed = stats->elapsed; + stats->elapsed += it->delta_time; + + ecs_world_stats_get(it->real_world, &stats->stats); + + int32_t t_last = (int32_t)(elapsed * 60); + int32_t t_next = (int32_t)(stats->elapsed * 60); + int32_t i, dif = t_last - t_next; + if (!dif) { + /* Still in same interval, combine with last measurement */ + ecs_world_stats_reduce_last(&stats->stats); + } else if (dif > 1) { + /* More than 16ms has passed, backfill */ + for (i = 1; i < dif; i ++) { + ecs_world_stats_copy_last(&stats->stats); + } + } +} + +void ReduceWorldStats(ecs_iter_t *it) { + EcsWorldStats *dst = ecs_term(it, EcsWorldStats, 1); + EcsWorldStats *src = ecs_term(it, EcsWorldStats, 2); + + ecs_world_stats_reduce(&dst->stats, &src->stats); +} + +void ReduceWorldStats1Day(ecs_iter_t *it) { + EcsWorldStats *dst = ecs_term(it, EcsWorldStats, 1); + EcsWorldStats *src = ecs_term(it, EcsWorldStats, 2); + + /* Reduce from minutes to the current day */ + ecs_world_stats_reduce(&dst->stats, &src->stats); + + dst->elapsed ++; + if (dst->elapsed < 1440) { + /* 1440 minutes in a day */ + ecs_world_stats_reduce_last(&dst->stats); + } else { + dst->elapsed = 0; + } +} + +void ReduceWorldStats1Week(ecs_iter_t *it) { + EcsWorldStats *dst = ecs_term(it, EcsWorldStats, 1); + EcsWorldStats *src = ecs_term(it, EcsWorldStats, 2); + + /* Reduce from minutes to the current day */ + ecs_world_stats_reduce(&dst->stats, &src->stats); + + dst->elapsed ++; + if (dst->elapsed < 168) { + /* 1440 minutes in a day */ + ecs_world_stats_reduce_last(&dst->stats); + } else { + dst->elapsed = 0; + } +} + +void FlecsMonitorImport( + ecs_world_t *world) +{ + ECS_MODULE_DEFINE(world, FlecsMonitor); + + ecs_set_name_prefix(world, "Ecs"); + + ECS_COMPONENT_DEFINE(world, EcsWorldStats); + ECS_TAG_DEFINE(world, EcsPeriod1s); + ECS_TAG_DEFINE(world, EcsPeriod1m); + ECS_TAG_DEFINE(world, EcsPeriod1h); + ECS_TAG_DEFINE(world, EcsPeriod1d); + ECS_TAG_DEFINE(world, EcsPeriod1w); + + // Called each frame, collects 60 measurements per second + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1s", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1s), + .subj.entity = EcsWorld + }}, + .callback = MonitorWorldStats + }); + + // Called each second, reduces into 60 measurements per minute + ecs_entity_t mw1m = ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1m", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1m), + .subj.entity = EcsWorld + }, { + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1s), + .subj.entity = EcsWorld + }}, + .callback = ReduceWorldStats, + .interval = 1 + }); + + // Called each minute, reduces into 60 measurements per hour + ecs_entity_t mw1h = ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1h", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1h), + .subj.entity = EcsWorld + }, { + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1m), + .subj.entity = EcsWorld + }}, + .callback = ReduceWorldStats, + .rate = 60, + .tick_source = mw1m + }); + + // Called each minute, reduces into 60 measurements per day + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1d", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1d), + .subj.entity = EcsWorld + }, { + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1m), + .subj.entity = EcsWorld + }}, + .callback = ReduceWorldStats1Day, + .rate = 60, + .tick_source = mw1m + }); + + // Called each hour, reduces into 60 measurements per week + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "MonitorWorld1w", .add = {EcsPreFrame} }, + .query.filter.terms = {{ + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1w), + .subj.entity = EcsWorld + }, { + .id = ecs_pair(ecs_id(EcsWorldStats), EcsPeriod1h), + .subj.entity = EcsWorld + }}, + .callback = ReduceWorldStats1Week, + .rate = 60, + .tick_source = mw1h + }); + + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1s, {0}); + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1m, {0}); + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1h, {0}); + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1d, {0}); + ecs_set_pair(world, EcsWorld, EcsWorldStats, EcsPeriod1w, {0}); + + if (ecs_os_has_time()) { + ecs_measure_frame_time(world, true); + ecs_measure_system_time(world, true); + } +} + +#endif diff --git a/src/addons/rest.c b/src/addons/rest.c index cbd9c21377..921ae4fbfe 100644 --- a/src/addons/rest.c +++ b/src/addons/rest.c @@ -41,7 +41,7 @@ static ECS_DTOR(EcsRest, ptr, { static char *rest_last_err; static -void rest_capture_log( +void flecs_rest_capture_log( int32_t level, const char *file, int32_t line, @@ -55,14 +55,14 @@ void rest_capture_log( } static -char* rest_get_captured_log(void) { +char* flecs_rest_get_captured_log(void) { char *result = rest_last_err; rest_last_err = NULL; return result; } static -void reply_verror( +void flecs_reply_verror( ecs_http_reply_t *reply, const char *fmt, va_list args) @@ -73,19 +73,19 @@ void reply_verror( } static -void reply_error( +void flecs_reply_error( ecs_http_reply_t *reply, const char *fmt, ...) { va_list args; va_start(args, fmt); - reply_verror(reply, fmt, args); + flecs_reply_verror(reply, fmt, args); va_end(args); } static -void rest_bool_param( +void flecs_rest_bool_param( const ecs_http_request_t *req, const char *name, bool *value_out) @@ -101,7 +101,7 @@ void rest_bool_param( } static -void rest_int_param( +void flecs_rest_int_param( const ecs_http_request_t *req, const char *name, int32_t *value_out) @@ -113,41 +113,274 @@ void rest_int_param( } static -void rest_parse_json_ser_entity_params( +void flecs_rest_string_param( + const ecs_http_request_t *req, + const char *name, + char **value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + *value_out = (char*)value; + } +} + +static +void flecs_rest_parse_json_ser_entity_params( ecs_entity_to_json_desc_t *desc, const ecs_http_request_t *req) { - rest_bool_param(req, "path", &desc->serialize_path); - rest_bool_param(req, "label", &desc->serialize_label); - rest_bool_param(req, "brief", &desc->serialize_brief); - rest_bool_param(req, "link", &desc->serialize_link); - rest_bool_param(req, "id_labels", &desc->serialize_id_labels); - rest_bool_param(req, "base", &desc->serialize_base); - rest_bool_param(req, "values", &desc->serialize_values); - rest_bool_param(req, "private", &desc->serialize_private); - rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "path", &desc->serialize_path); + flecs_rest_bool_param(req, "label", &desc->serialize_label); + flecs_rest_bool_param(req, "brief", &desc->serialize_brief); + flecs_rest_bool_param(req, "link", &desc->serialize_link); + flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); + flecs_rest_bool_param(req, "base", &desc->serialize_base); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "private", &desc->serialize_private); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); } static -void rest_parse_json_ser_iter_params( +void flecs_rest_parse_json_ser_iter_params( ecs_iter_to_json_desc_t *desc, const ecs_http_request_t *req) { - rest_bool_param(req, "term_ids", &desc->serialize_term_ids); - rest_bool_param(req, "ids", &desc->serialize_ids); - rest_bool_param(req, "subjects", &desc->serialize_subjects); - rest_bool_param(req, "variables", &desc->serialize_variables); - rest_bool_param(req, "is_set", &desc->serialize_is_set); - rest_bool_param(req, "values", &desc->serialize_values); - rest_bool_param(req, "entities", &desc->serialize_entities); - rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); - rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); - rest_bool_param(req, "duration", &desc->measure_eval_duration); - rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); + flecs_rest_bool_param(req, "ids", &desc->serialize_ids); + flecs_rest_bool_param(req, "subjects", &desc->serialize_subjects); + flecs_rest_bool_param(req, "variables", &desc->serialize_variables); + flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "entities", &desc->serialize_entities); + flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); + flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); + flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); +} + +static +bool flecs_rest_reply_entity( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + char *path = &req->path[7]; + ecs_dbg_2("rest: request entity '%s'", path); + + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); + if (!e) { + ecs_dbg_2("rest: entity '%s' not found", path); + flecs_reply_error(reply, "entity '%s' not found", path); + reply->code = 404; + return true; + } + + ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; + flecs_rest_parse_json_ser_entity_params(&desc, req); + + ecs_entity_to_json_buf(world, e, &reply->body, &desc); + return true; +} + +static +bool flecs_rest_reply_query( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + const char *q = ecs_http_get_param(req, "q"); + if (!q) { + ecs_strbuf_appendstr(&reply->body, "Missing parameter 'q'"); + reply->code = 400; /* bad request */ + return true; + } + + ecs_dbg_2("rest: request query '%s'", q); + bool prev_color = ecs_log_enable_colors(false); + ecs_os_api_log_t prev_log_ = ecs_os_api.log_; + ecs_os_api.log_ = flecs_rest_capture_log; + + ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) { + .expr = q + }); + if (!r) { + char *err = flecs_rest_get_captured_log(); + char *escaped_err = ecs_astresc('"', err); + flecs_reply_error(reply, escaped_err); + reply->code = 400; /* bad request */ + ecs_os_free(escaped_err); + ecs_os_free(err); + } else { + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + flecs_rest_parse_json_ser_iter_params(&desc, req); + + int32_t offset = 0; + int32_t limit = 100; + + flecs_rest_int_param(req, "offset", &offset); + flecs_rest_int_param(req, "limit", &limit); + + ecs_iter_t it = ecs_rule_iter(world, r); + ecs_iter_t pit = ecs_page_iter(&it, offset, limit); + ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); + ecs_rule_fini(r); + } + + ecs_os_api.log_ = prev_log_; + ecs_log_enable_colors(prev_color); + + return true; +} + +static +void flecs_rest_array_append( + ecs_strbuf_t *reply, + const char *field, + const FLECS_FLOAT *values, + int32_t t) +{ + ecs_strbuf_list_append(reply, "\"%s\"", field); + ecs_strbuf_appendch(reply, ':'); + ecs_strbuf_list_push(reply, "[", ","); + + int32_t i; + for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { + int32_t index = i % ECS_STAT_WINDOW; + ecs_strbuf_list_next(reply); + ecs_strbuf_appendflt(reply, (double)values[index], '"'); + } + + ecs_strbuf_list_pop(reply, "]"); +} + +static +void flecs_rest_gauge_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t t) +{ + ecs_strbuf_list_append(reply, "\"%s\"", field); + ecs_strbuf_appendch(reply, ':'); + ecs_strbuf_list_push(reply, "{", ","); + + flecs_rest_array_append(reply, "avg", m->gauge.avg, t); + flecs_rest_array_append(reply, "min", m->gauge.min, t); + flecs_rest_array_append(reply, "max", m->gauge.max, t); + + ecs_strbuf_list_pop(reply, "}"); } static -bool rest_reply( +void flecs_rest_counter_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t t) +{ + flecs_rest_gauge_append(reply, m, field, t); +} + +#define ECS_GAUGE_APPEND(reply, s, field)\ + flecs_rest_gauge_append(reply, &s->stats.field, #field, s->stats.t) + +#define ECS_COUNTER_APPEND(reply, s, field)\ + flecs_rest_counter_append(reply, &s->stats.field, #field, s->stats.t) + +static +void flecs_world_stats_to_json( + ecs_strbuf_t *reply, + const EcsWorldStats *stats) +{ + ecs_strbuf_list_push(reply, "{", ","); + ECS_GAUGE_APPEND(reply, stats, entity_count); + ECS_GAUGE_APPEND(reply, stats, entity_not_alive_count); + ECS_GAUGE_APPEND(reply, stats, id_count); + ECS_GAUGE_APPEND(reply, stats, tag_id_count); + ECS_GAUGE_APPEND(reply, stats, component_id_count); + ECS_GAUGE_APPEND(reply, stats, pair_id_count); + ECS_GAUGE_APPEND(reply, stats, wildcard_id_count); + ECS_GAUGE_APPEND(reply, stats, component_count); + ECS_COUNTER_APPEND(reply, stats, id_create_count); + ECS_COUNTER_APPEND(reply, stats, id_delete_count); + ECS_GAUGE_APPEND(reply, stats, table_count); + ECS_GAUGE_APPEND(reply, stats, empty_table_count); + ECS_GAUGE_APPEND(reply, stats, tag_table_count); + ECS_GAUGE_APPEND(reply, stats, trivial_table_count); + ECS_GAUGE_APPEND(reply, stats, table_record_count); + ECS_GAUGE_APPEND(reply, stats, table_storage_count); + ECS_COUNTER_APPEND(reply, stats, table_create_count); + ECS_COUNTER_APPEND(reply, stats, table_delete_count); + ECS_GAUGE_APPEND(reply, stats, query_count); + ECS_GAUGE_APPEND(reply, stats, trigger_count); + ECS_GAUGE_APPEND(reply, stats, observer_count); + ECS_GAUGE_APPEND(reply, stats, system_count); + ECS_COUNTER_APPEND(reply, stats, new_count); + ECS_COUNTER_APPEND(reply, stats, bulk_new_count); + ECS_COUNTER_APPEND(reply, stats, delete_count); + ECS_COUNTER_APPEND(reply, stats, clear_count); + ECS_COUNTER_APPEND(reply, stats, add_count); + ECS_COUNTER_APPEND(reply, stats, remove_count); + ECS_COUNTER_APPEND(reply, stats, set_count); + ECS_COUNTER_APPEND(reply, stats, discard_count); + ECS_COUNTER_APPEND(reply, stats, world_time_total_raw); + ECS_COUNTER_APPEND(reply, stats, world_time_total); + ECS_COUNTER_APPEND(reply, stats, frame_time_total); + ECS_COUNTER_APPEND(reply, stats, system_time_total); + ECS_COUNTER_APPEND(reply, stats, merge_time_total); + ECS_GAUGE_APPEND(reply, stats, fps); + ECS_GAUGE_APPEND(reply, stats, delta_time); + ECS_COUNTER_APPEND(reply, stats, frame_count_total); + ECS_COUNTER_APPEND(reply, stats, merge_count_total); + ECS_COUNTER_APPEND(reply, stats, pipeline_build_count_total); + ECS_COUNTER_APPEND(reply, stats, systems_ran_frame); + ecs_strbuf_list_pop(reply, "}"); +} + +static +bool flecs_rest_reply_stats( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ +#ifdef FLECS_MONITOR + char *period_str = NULL; + flecs_rest_string_param(req, "period", &period_str); + char *category = &req->path[6]; + + ecs_entity_t period = EcsPeriod1s; + if (period_str) { + char *period_name = ecs_asprintf("Period%s", period_str); + period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); + ecs_os_free(period_name); + if (!period) { + flecs_reply_error(reply, "bad request (invalid period string)"); + reply->code = 400; + return false; + } + } + + if (!ecs_os_strcmp(category, "world")) { + const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, + EcsWorldStats, period); + flecs_world_stats_to_json(&reply->body, stats); + return true; + + } else { + flecs_reply_error(reply, "bad request (unsupported category)"); + reply->code = 400; + return false; + } + + return true; +#else + return false; +#endif +} + +static +bool flecs_rest_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, void *ctx) @@ -157,7 +390,7 @@ bool rest_reply( if (req->path == NULL) { ecs_dbg("rest: bad request (missing path)"); - reply_error(reply, "bad request (missing path)"); + flecs_reply_error(reply, "bad request (missing path)"); reply->code = 400; return false; } @@ -167,71 +400,18 @@ bool rest_reply( if (req->method == EcsHttpGet) { /* Entity endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { - char *path = &req->path[7]; - ecs_dbg_2("rest: request entity '%s'", path); - - ecs_entity_t e = ecs_lookup_path_w_sep( - world, 0, path, "/", NULL, false); - if (!e) { - ecs_dbg_2("rest: entity '%s' not found", path); - reply_error(reply, "entity '%s' not found", path); - reply->code = 404; - return true; - } - - ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; - rest_parse_json_ser_entity_params(&desc, req); - - ecs_entity_to_json_buf(world, e, &reply->body, &desc); - return true; + return flecs_rest_reply_entity(world, req, reply); /* Query endpoint */ } else if (!ecs_os_strcmp(req->path, "query")) { - const char *q = ecs_http_get_param(req, "q"); - if (!q) { - ecs_strbuf_appendstr(&reply->body, "Missing parameter 'q'"); - reply->code = 400; /* bad request */ - return true; - } - - ecs_dbg_2("rest: request query '%s'", q); - bool prev_color = ecs_log_enable_colors(false); - ecs_os_api_log_t prev_log_ = ecs_os_api.log_; - ecs_os_api.log_ = rest_capture_log; - - ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) { - .expr = q - }); - if (!r) { - char *err = rest_get_captured_log(); - char *escaped_err = ecs_astresc('"', err); - reply_error(reply, escaped_err); - reply->code = 400; /* bad request */ - ecs_os_free(escaped_err); - ecs_os_free(err); - } else { - ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; - rest_parse_json_ser_iter_params(&desc, req); - - int32_t offset = 0; - int32_t limit = 100; - - rest_int_param(req, "offset", &offset); - rest_int_param(req, "limit", &limit); - - ecs_iter_t it = ecs_rule_iter(world, r); - ecs_iter_t pit = ecs_page_iter(&it, offset, limit); - ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); - ecs_rule_fini(r); - } - - ecs_os_api.log_ = prev_log_; - ecs_log_enable_colors(prev_color); - - return true; + return flecs_rest_reply_query(world, req, reply); + + /* Stats endpoint */ + } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { + return flecs_rest_reply_stats(world, req, reply); } - } - if (req->method == EcsHttpOptions) { + + } else if (req->method == EcsHttpOptions) { return true; } @@ -239,7 +419,7 @@ bool rest_reply( } static -void on_set_rest(ecs_iter_t *it) +void flecs_on_set_rest(ecs_iter_t *it) { EcsRest *rest = it->ptrs[0]; @@ -253,7 +433,7 @@ void on_set_rest(ecs_iter_t *it) ecs_http_server_t *srv = ecs_http_server_init(&(ecs_http_server_desc_t){ .ipaddr = rest[i].ipaddr, .port = rest[i].port, - .callback = rest_reply, + .callback = flecs_rest_reply, .ctx = srv_ctx }); @@ -309,7 +489,7 @@ void FlecsRestImport( .move = ecs_move(EcsRest), .copy = ecs_copy(EcsRest), .dtor = ecs_dtor(EcsRest), - .on_set = on_set_rest + .on_set = flecs_on_set_rest }); ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); diff --git a/src/addons/stats.c b/src/addons/stats.c index b3fb3609d7..5b53c8d2fb 100644 --- a/src/addons/stats.c +++ b/src/addons/stats.c @@ -13,6 +13,18 @@ #include +#define ECS_GAUGE_RECORD(m, t, value)\ + flecs_gauge_record(m, t, (float)value) + +#define ECS_COUNTER_RECORD(m, t, value)\ + flecs_counter_record(m, t, (float)value) + +#define ECS_METRIC_FIRST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int32_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) @@ -28,38 +40,31 @@ int32_t t_prev( } static -void _record_gauge( - ecs_gauge_t *m, +void flecs_gauge_record( + ecs_metric_t *m, int32_t t, float value) { - m->avg[t] = value; - m->min[t] = value; - m->max[t] = value; + m->gauge.avg[t] = value; + m->gauge.min[t] = value; + m->gauge.max[t] = value; } static -float _record_counter( - ecs_counter_t *m, +float flecs_counter_record( + ecs_metric_t *m, int32_t t, float value) { int32_t tp = t_prev(t); - float prev = m->value[tp]; - m->value[t] = value; - _record_gauge((ecs_gauge_t*)m, t, value - prev); + float prev = m->counter.value[tp]; + m->counter.value[t] = value; + flecs_gauge_record(m, t, value - prev); return value - prev; } -/* Macro's to silence conversion warnings without adding casts everywhere */ -#define record_gauge(m, t, value)\ - _record_gauge(m, t, (float)value) - -#define record_counter(m, t, value)\ - _record_counter(m, t, (float)value) - static -void print_value( +void flecs_metric_print( const char *name, float value) { @@ -68,54 +73,85 @@ void print_value( } static -void print_gauge( +void flecs_gauge_print( const char *name, int32_t t, - const ecs_gauge_t *m) + const ecs_metric_t *m) { - print_value(name, m->avg[t]); + flecs_metric_print(name, m->gauge.avg[t]); } static -void print_counter( +void flecs_counter_print( const char *name, int32_t t, - const ecs_counter_t *m) + const ecs_metric_t *m) { - print_value(name, m->rate.avg[t]); + flecs_metric_print(name, m->counter.rate.avg[t]); } -void ecs_gauge_reduce( - ecs_gauge_t *dst, +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, int32_t t_dst, - ecs_gauge_t *src, 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->min[t_dst] = 0; - dst->avg[t_dst] = 0; - dst->max[t_dst] = 0; + dst->gauge.min[t_dst] = 0; + dst->gauge.avg[t_dst] = 0; + dst->gauge.max[t_dst] = 0; int32_t i; for (i = 0; i < ECS_STAT_WINDOW; i ++) { int32_t t = (t_src + i) % ECS_STAT_WINDOW; - dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW; - if (!min_set || (src->min[t] < dst->min[t_dst])) { - dst->min[t_dst] = src->min[t]; + dst->gauge.avg[t_dst] += src->gauge.avg[t] / (float)ECS_STAT_WINDOW; + + 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->max[t] > dst->max[t_dst])) { - dst->max[t_dst] = src->max[t]; + 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_get_world_stats( +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t t) +{ + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_metric_reduce(m, m, t, t_next(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; +} + +void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *s) { @@ -126,106 +162,111 @@ void ecs_get_world_stats( int32_t t = s->t = t_next(s->t); - float delta_world_time = record_counter(&s->world_time_total_raw, t, world->info.world_time_total_raw); - record_counter(&s->world_time_total, t, world->info.world_time_total); - record_counter(&s->frame_time_total, t, world->info.frame_time_total); - record_counter(&s->system_time_total, t, world->info.system_time_total); - record_counter(&s->merge_time_total, t, world->info.merge_time_total); + float delta_world_time = ECS_COUNTER_RECORD(&s->world_time_total_raw, t, world->info.world_time_total_raw); + ECS_COUNTER_RECORD(&s->world_time_total, t, world->info.world_time_total); + ECS_COUNTER_RECORD(&s->frame_time_total, t, world->info.frame_time_total); + ECS_COUNTER_RECORD(&s->system_time_total, t, world->info.system_time_total); + ECS_COUNTER_RECORD(&s->merge_time_total, t, world->info.merge_time_total); - float delta_frame_count = record_counter(&s->frame_count_total, t, world->info.frame_count_total); - record_counter(&s->merge_count_total, t, world->info.merge_count_total); - record_counter(&s->pipeline_build_count_total, t, world->info.pipeline_build_count_total); - record_counter(&s->systems_ran_frame, t, world->info.systems_ran_frame); + float delta_frame_count = ECS_COUNTER_RECORD(&s->frame_count_total, t, world->info.frame_count_total); + ECS_COUNTER_RECORD(&s->merge_count_total, t, world->info.merge_count_total); + ECS_COUNTER_RECORD(&s->pipeline_build_count_total, t, world->info.pipeline_build_count_total); + ECS_COUNTER_RECORD(&s->systems_ran_frame, t, world->info.systems_ran_frame); if (delta_world_time != 0.0f && delta_frame_count != 0.0f) { - record_gauge( + ECS_GAUGE_RECORD( &s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count)); } else { - record_gauge(&s->fps, t, 0); + ECS_GAUGE_RECORD(&s->fps, t, 0); } - record_gauge(&s->entity_count, t, flecs_sparse_count(ecs_eis(world))); - record_gauge(&s->entity_not_alive_count, t, + ECS_GAUGE_RECORD(&s->delta_time, t, delta_world_time); + + ECS_GAUGE_RECORD(&s->entity_count, t, flecs_sparse_count(ecs_eis(world))); + ECS_GAUGE_RECORD(&s->entity_not_alive_count, t, flecs_sparse_not_alive_count(ecs_eis(world))); - record_gauge(&s->id_count, t, world->info.id_count); - record_gauge(&s->tag_id_count, t, world->info.tag_id_count); - record_gauge(&s->component_id_count, t, world->info.component_id_count); - record_gauge(&s->pair_id_count, t, world->info.pair_id_count); - record_gauge(&s->wildcard_id_count, t, world->info.wildcard_id_count); - record_gauge(&s->component_count, t, ecs_sparse_count(world->type_info)); - - record_gauge(&s->query_count, t, flecs_sparse_count(world->queries)); - record_gauge(&s->trigger_count, t, ecs_count(world, EcsTrigger)); - record_gauge(&s->observer_count, t, ecs_count(world, EcsObserver)); - record_gauge(&s->system_count, t, ecs_count(world, EcsSystem)); - - record_counter(&s->id_create_count, t, world->info.id_create_total); - record_counter(&s->id_delete_count, t, world->info.id_delete_total); - record_counter(&s->table_create_count, t, world->info.table_create_total); - record_counter(&s->table_delete_count, t, world->info.table_delete_total); - - record_counter(&s->new_count, t, world->new_count); - record_counter(&s->bulk_new_count, t, world->bulk_new_count); - record_counter(&s->delete_count, t, world->delete_count); - record_counter(&s->clear_count, t, world->clear_count); - record_counter(&s->add_count, t, world->add_count); - record_counter(&s->remove_count, t, world->remove_count); - record_counter(&s->set_count, t, world->set_count); - record_counter(&s->discard_count, t, world->discard_count); - - /* Compute table statistics */ - int32_t empty_table_count = 0; - int32_t singleton_table_count = 0; - - int32_t i, count = flecs_sparse_count(&world->store.tables); - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, i); - int32_t entity_count = ecs_table_count(table); - - if (!entity_count) { - empty_table_count ++; - } + ECS_GAUGE_RECORD(&s->id_count, t, world->info.id_count); + ECS_GAUGE_RECORD(&s->tag_id_count, t, world->info.tag_id_count); + ECS_GAUGE_RECORD(&s->component_id_count, t, world->info.component_id_count); + ECS_GAUGE_RECORD(&s->pair_id_count, t, world->info.pair_id_count); + ECS_GAUGE_RECORD(&s->wildcard_id_count, t, world->info.wildcard_id_count); + ECS_GAUGE_RECORD(&s->component_count, t, ecs_sparse_count(world->type_info)); + + ECS_GAUGE_RECORD(&s->query_count, t, flecs_sparse_count(world->queries)); + ECS_GAUGE_RECORD(&s->trigger_count, t, ecs_count(world, EcsTrigger)); + ECS_GAUGE_RECORD(&s->observer_count, t, ecs_count(world, EcsObserver)); + ECS_GAUGE_RECORD(&s->system_count, t, ecs_count(world, EcsSystem)); + + ECS_COUNTER_RECORD(&s->id_create_count, t, world->info.id_create_total); + ECS_COUNTER_RECORD(&s->id_delete_count, t, world->info.id_delete_total); + ECS_COUNTER_RECORD(&s->table_create_count, t, world->info.table_create_total); + ECS_COUNTER_RECORD(&s->table_delete_count, t, world->info.table_delete_total); + + ECS_COUNTER_RECORD(&s->new_count, t, world->new_count); + ECS_COUNTER_RECORD(&s->bulk_new_count, t, world->bulk_new_count); + ECS_COUNTER_RECORD(&s->delete_count, t, world->delete_count); + ECS_COUNTER_RECORD(&s->clear_count, t, world->clear_count); + ECS_COUNTER_RECORD(&s->add_count, t, world->add_count); + ECS_COUNTER_RECORD(&s->remove_count, t, world->remove_count); + ECS_COUNTER_RECORD(&s->set_count, t, world->set_count); + ECS_COUNTER_RECORD(&s->discard_count, t, world->discard_count); + + ECS_GAUGE_RECORD(&s->table_count, t, world->info.table_count); + ECS_GAUGE_RECORD(&s->empty_table_count, t, world->info.empty_table_count); + ECS_GAUGE_RECORD(&s->tag_table_count, t, world->info.tag_table_count); + ECS_GAUGE_RECORD(&s->trivial_table_count, t, world->info.trivial_table_count); + ECS_GAUGE_RECORD(&s->table_storage_count, t, world->info.table_storage_count); + ECS_GAUGE_RECORD(&s->table_record_count, t, world->info.table_record_count); - /* Singleton tables are tables that have just one entity that also has - * itself in the table type. */ - if (entity_count == 1) { - ecs_entity_t *entities = ecs_storage_first_t( - &table->data.entities, ecs_entity_t); - if (ecs_search(world, table, entities[0], 0)) { - singleton_table_count ++; - } - } - } +error: + return; +} - /* Correct for root table */ - count --; - empty_table_count --; +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + int32_t t_dst = dst->t = t_next(dst->t); + int32_t t_src = src->t; - if (count != world->info.table_count) { - ecs_warn("world::table_count (%d) is not equal to computed number (%d)", - world->info.table_count, count); + ecs_metric_t *cur = ECS_METRIC_FIRST(dst), *last = ECS_METRIC_LAST(dst); + ecs_metric_t *src_cur = ECS_METRIC_FIRST(src); + + for (; cur != last; cur ++, src_cur ++) { + ecs_metric_reduce(cur, src_cur, t_dst, t_src); } - if (empty_table_count != world->info.empty_table_count) { - ecs_warn("world::empty_table_count (%d) is not equal to computed" - " number (%d)", - world->info.empty_table_count, empty_table_count); +} + +void ecs_world_stats_reduce_last( + ecs_world_stats_t *stats) +{ + int32_t t = stats->t; + + ecs_metric_t *cur = ECS_METRIC_FIRST(stats), *last = ECS_METRIC_LAST(stats); + + for (; cur != last; cur ++) { + ecs_metric_reduce_last(cur, t); } - record_gauge(&s->table_count, t, count); - record_gauge(&s->empty_table_count, t, empty_table_count); - record_gauge(&s->singleton_table_count, t, singleton_table_count); - record_gauge(&s->tag_table_count, t, world->info.tag_table_count); - record_gauge(&s->trivial_table_count, t, world->info.trivial_table_count); - record_gauge(&s->table_storage_count, t, world->info.table_storage_count); - record_gauge(&s->table_record_count, t, world->info.table_record_count); + stats->t = t_prev(t); +} -error: - return; +void ecs_world_stats_copy_last( + ecs_world_stats_t *stats) +{ + int32_t t_last = stats->t, t = t_next(t_last); + + ecs_metric_t *cur = ECS_METRIC_FIRST(stats), *last = ECS_METRIC_LAST(stats); + + for (; cur != last; cur ++) { + ecs_metric_copy(cur, t_last, t); + } + + stats->t = t; } -void ecs_get_query_stats( +void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *s) @@ -238,16 +279,16 @@ void ecs_get_query_stats( int32_t t = s->t = t_next(s->t); ecs_iter_t it = ecs_query_iter(world, (ecs_query_t*)query); - record_gauge(&s->matched_entity_count, t, ecs_iter_count(&it)); - record_gauge(&s->matched_table_count, t, ecs_query_table_count(query)); - record_gauge(&s->matched_empty_table_count, t, + ECS_GAUGE_RECORD(&s->matched_entity_count, t, ecs_iter_count(&it)); + 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)); error: return; } #ifdef FLECS_SYSTEM -bool ecs_get_system_stats( +bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *s) @@ -263,13 +304,13 @@ bool ecs_get_system_stats( return false; } - ecs_get_query_stats(world, ptr->query, &s->query_stats); + ecs_query_stats_get(world, ptr->query, &s->query_stats); int32_t t = s->query_stats.t; - record_counter(&s->time_spent, t, ptr->time_spent); - record_counter(&s->invoke_count, t, ptr->invoke_count); - record_gauge(&s->active, t, !ecs_has_id(world, system, EcsInactive)); - record_gauge(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); + ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); + ECS_GAUGE_RECORD(&s->active, t, !ecs_has_id(world, system, EcsInactive)); + ECS_GAUGE_RECORD(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); return true; error: @@ -298,7 +339,7 @@ ecs_system_stats_t* get_system_stats( return NULL; } -bool ecs_get_pipeline_stats( +bool ecs_pipeline_stats_get( ecs_world_t *stage, ecs_entity_t pipeline, ecs_pipeline_stats_t *s) @@ -385,7 +426,7 @@ bool ecs_get_pipeline_stats( for (i = 0; i < it.count; i ++) { ecs_system_stats_t *sys_stats = get_system_stats( s->system_stats, it.entities[i]); - ecs_get_system_stats(world, it.entities[i], sys_stats); + ecs_system_stats_get(world, it.entities[i], sys_stats); } } @@ -403,7 +444,7 @@ void ecs_pipeline_stats_fini( #endif -void ecs_dump_world_stats( +void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *s) { @@ -414,56 +455,55 @@ void ecs_dump_world_stats( world = ecs_get_world(world); - print_counter("Frame", t, &s->frame_count_total); + flecs_counter_print("Frame", t, &s->frame_count_total); ecs_trace("-------------------------------------"); - print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total); - print_counter("systems invocations", t, &s->systems_ran_frame); + flecs_counter_print("pipeline rebuilds", t, &s->pipeline_build_count_total); + flecs_counter_print("systems invocations", t, &s->systems_ran_frame); ecs_trace(""); - print_value("target FPS", world->info.target_fps); - print_value("time scale", world->info.time_scale); + flecs_metric_print("target FPS", world->info.target_fps); + flecs_metric_print("time scale", world->info.time_scale); ecs_trace(""); - print_gauge("actual FPS", t, &s->fps); - print_counter("frame time", t, &s->frame_time_total); - print_counter("system time", t, &s->system_time_total); - print_counter("merge time", t, &s->merge_time_total); - print_counter("simulation time elapsed", t, &s->world_time_total); + flecs_gauge_print("actual FPS", t, &s->fps); + flecs_counter_print("frame time", t, &s->frame_time_total); + flecs_counter_print("system time", t, &s->system_time_total); + flecs_counter_print("merge time", t, &s->merge_time_total); + flecs_counter_print("simulation time elapsed", t, &s->world_time_total); ecs_trace(""); - print_gauge("id count", t, &s->id_count); - print_gauge("tag id count", t, &s->tag_id_count); - print_gauge("component id count", t, &s->component_id_count); - print_gauge("pair id count", t, &s->pair_id_count); - print_gauge("wildcard id count", t, &s->wildcard_id_count); - print_gauge("component count", t, &s->component_count); + flecs_gauge_print("id count", t, &s->id_count); + flecs_gauge_print("tag id count", t, &s->tag_id_count); + flecs_gauge_print("component id count", t, &s->component_id_count); + flecs_gauge_print("pair id count", t, &s->pair_id_count); + flecs_gauge_print("wildcard id count", t, &s->wildcard_id_count); + flecs_gauge_print("component count", t, &s->component_count); ecs_trace(""); - print_gauge("alive entity count", t, &s->entity_count); - print_gauge("not alive entity count", t, &s->entity_not_alive_count); + flecs_gauge_print("alive entity count", t, &s->entity_count); + flecs_gauge_print("not alive entity count", t, &s->entity_not_alive_count); ecs_trace(""); - print_gauge("query count", t, &s->query_count); - print_gauge("trigger count", t, &s->trigger_count); - print_gauge("observer count", t, &s->observer_count); - print_gauge("system count", t, &s->system_count); + flecs_gauge_print("query count", t, &s->query_count); + flecs_gauge_print("trigger count", t, &s->trigger_count); + flecs_gauge_print("observer count", t, &s->observer_count); + flecs_gauge_print("system count", t, &s->system_count); ecs_trace(""); - print_gauge("table count", t, &s->table_count); - print_gauge("empty table count", t, &s->empty_table_count); - print_gauge("tag table count", t, &s->tag_table_count); - print_gauge("trivial table count", t, &s->trivial_table_count); - print_gauge("table storage count", t, &s->table_storage_count); - print_gauge("table cache record count", t, &s->table_record_count); - print_gauge("singleton table count", t, &s->singleton_table_count); + flecs_gauge_print("table count", t, &s->table_count); + flecs_gauge_print("empty table count", t, &s->empty_table_count); + flecs_gauge_print("tag table count", t, &s->tag_table_count); + flecs_gauge_print("trivial table count", t, &s->trivial_table_count); + flecs_gauge_print("table storage count", t, &s->table_storage_count); + flecs_gauge_print("table cache record count", t, &s->table_record_count); ecs_trace(""); - print_counter("table create count", t, &s->table_create_count); - print_counter("table delete count", t, &s->table_delete_count); - print_counter("id create count", t, &s->id_create_count); - print_counter("id delete count", t, &s->id_delete_count); + flecs_counter_print("table create count", t, &s->table_create_count); + flecs_counter_print("table delete count", t, &s->table_delete_count); + flecs_counter_print("id create count", t, &s->id_create_count); + flecs_counter_print("id delete count", t, &s->id_delete_count); ecs_trace(""); - print_counter("deferred new operations", t, &s->new_count); - print_counter("deferred bulk_new operations", t, &s->bulk_new_count); - print_counter("deferred delete operations", t, &s->delete_count); - print_counter("deferred clear operations", t, &s->clear_count); - print_counter("deferred add operations", t, &s->add_count); - print_counter("deferred remove operations", t, &s->remove_count); - print_counter("deferred set operations", t, &s->set_count); - print_counter("discarded operations", t, &s->discard_count); + flecs_counter_print("deferred new operations", t, &s->new_count); + flecs_counter_print("deferred bulk_new operations", t, &s->bulk_new_count); + flecs_counter_print("deferred delete operations", t, &s->delete_count); + flecs_counter_print("deferred clear operations", t, &s->clear_count); + flecs_counter_print("deferred add operations", t, &s->add_count); + flecs_counter_print("deferred remove operations", t, &s->remove_count); + flecs_counter_print("deferred set operations", t, &s->set_count); + flecs_counter_print("discarded operations", t, &s->discard_count); ecs_trace(""); error: diff --git a/src/id_record.c b/src/id_record.c index bbd407fbe8..ae4f61e5dc 100644 --- a/src/id_record.c +++ b/src/id_record.c @@ -424,15 +424,17 @@ bool flecs_set_type_info_for_id_record( ecs_id_record_t *idr, const ecs_type_info_t *ti) { - if (ti) { - if (!idr->type_info) { - world->info.tag_id_count --; - world->info.component_id_count ++; - } - } else { - if (idr->type_info) { - world->info.tag_id_count ++; - world->info.component_id_count --; + if (!ecs_id_is_wildcard(idr->id)) { + if (ti) { + if (!idr->type_info) { + world->info.tag_id_count --; + world->info.component_id_count ++; + } + } else { + if (idr->type_info) { + world->info.tag_id_count ++; + world->info.component_id_count --; + } } } diff --git a/src/observer.c b/src/observer.c index 95e806c52a..86a059fb75 100644 --- a/src/observer.c +++ b/src/observer.c @@ -277,7 +277,7 @@ ecs_entity_t ecs_observer_init( .last_event_id = &observer->last_event_id }; - bool optional_only = true; + bool optional_only = filter->flags & EcsFilterMatchThis; for (i = 0; i < filter->term_count; i ++) { if (filter->terms[i].oper != EcsOptional) { if (filter->terms[i].subj.entity == EcsThis) { diff --git a/src/world.c b/src/world.c index 4913d0d8aa..61830b1241 100644 --- a/src/world.c +++ b/src/world.c @@ -526,6 +526,9 @@ void log_addons(void) { #ifdef FLECS_STATS ecs_trace("FLECS_STATS"); #endif + #ifdef FLECS_MONITOR + ecs_trace("FLECS_MONITOR"); + #endif #ifdef FLECS_SYSTEM ecs_trace("FLECS_SYSTEM"); #endif diff --git a/test/addons/src/Modules.c b/test/addons/src/Modules.c index 631d5c27c2..7857c092e5 100644 --- a/test/addons/src/Modules.c +++ b/test/addons/src/Modules.c @@ -118,18 +118,15 @@ void Modules_import_module_from_system() { ecs_fini(world); } -ecs_entity_t import_module(ecs_world_t *world) { - ECS_IMPORT(world, SimpleModule); - return ecs_id(SimpleModule); -} - void Modules_import_again() { ecs_world_t *world = ecs_init(); - ECS_IMPORT(world, SimpleModule); + ecs_entity_t m1 = ECS_IMPORT(world, SimpleModule); + ecs_entity_t m2 = ECS_IMPORT(world, SimpleModule); - test_assert(ecs_id(SimpleModule) != 0); - test_assert(ecs_id(SimpleModule) == import_module(world)); + test_assert(m1 != 0); + test_assert(m2 != 0); + test_assert(m1 == m2); ecs_fini(world); } diff --git a/test/addons/src/Pipeline.c b/test/addons/src/Pipeline.c index 890824fed8..0fac892b4d 100644 --- a/test/addons/src/Pipeline.c +++ b/test/addons/src/Pipeline.c @@ -1087,7 +1087,7 @@ void Pipeline_stage_write_before_read() { test_int(sys_c_invoked, 1); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, + test_bool(ecs_pipeline_stats_get(world, ecs_get_pipeline(world), &stats), true); test_int(ecs_vector_count(stats.systems), 5); @@ -1163,7 +1163,7 @@ void Pipeline_mixed_multithreaded() { test_int(sys_f_invoked, 1); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, + test_bool(ecs_pipeline_stats_get(world, ecs_get_pipeline(world), &stats), true); test_int(ecs_vector_count(stats.systems), 10); @@ -1273,7 +1273,7 @@ void Pipeline_mixed_staging() { test_int(sys_f_world_readonly, true); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, + test_bool(ecs_pipeline_stats_get(world, ecs_get_pipeline(world), &stats), true); test_int(ecs_vector_count(stats.systems), 10); @@ -1541,7 +1541,7 @@ void Pipeline_no_staging_system_create_query() { test_assert(q_result != NULL); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, + test_bool(ecs_pipeline_stats_get(world, ecs_get_pipeline(world), &stats), true); test_int(ecs_vector_count(stats.systems), 2); diff --git a/test/addons/src/Stats.c b/test/addons/src/Stats.c index 8a44b418d9..a0164d54a5 100644 --- a/test/addons/src/Stats.c +++ b/test/addons/src/Stats.c @@ -8,7 +8,7 @@ void Stats_get_world_stats() { ecs_world_t *world = ecs_init(); ecs_world_stats_t stats = {0}; - ecs_get_world_stats(world, &stats); + ecs_world_stats_get(world, &stats); test_int(stats.t, 1); @@ -24,7 +24,7 @@ void Stats_get_pipeline_stats_before_progress_mini_world() { test_assert(pipeline != 0); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), false); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), false); test_assert(stats.systems == NULL); test_assert(stats.system_stats == NULL); @@ -39,7 +39,7 @@ void Stats_get_pipeline_stats_before_progress() { test_assert(pipeline != 0); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_assert(stats.systems == NULL); test_assert(stats.system_stats != NULL); /* Inactive systems */ @@ -58,7 +58,7 @@ void Stats_get_pipeline_stats_after_progress_no_systems() { ecs_progress(world, 0); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(ecs_vector_count(stats.systems), 1); test_int(ecs_vector_get(stats.systems, ecs_entity_t, 0)[0], 0); /* merge */ @@ -84,7 +84,7 @@ void Stats_get_pipeline_stats_after_progress_1_system() { ecs_progress(world, 0); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(ecs_vector_count(stats.systems), 2); test_int(ecs_vector_get(stats.systems, ecs_entity_t, 0)[0], ecs_id(FooSys)); @@ -95,13 +95,13 @@ void Stats_get_pipeline_stats_after_progress_1_system() { stats.system_stats, ecs_system_stats_t, ecs_id(FooSys)); test_assert(sys_stats != NULL); test_int(sys_stats->query_stats.t, 1); - test_int(sys_stats->invoke_count.value[1], 1); + test_int(sys_stats->invoke_count.counter.value[1], 1); ecs_progress(world, 0); - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(sys_stats->query_stats.t, 2); - test_int(sys_stats->invoke_count.value[2], 2); + test_int(sys_stats->invoke_count.counter.value[2], 2); ecs_pipeline_stats_fini(&stats); @@ -120,7 +120,7 @@ void Stats_get_pipeline_stats_after_progress_1_inactive_system() { ecs_progress(world, 0); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(ecs_vector_count(stats.systems), 1); test_int(ecs_vector_get(stats.systems, ecs_entity_t, 0)[0], 0); /* merge */ @@ -130,13 +130,13 @@ void Stats_get_pipeline_stats_after_progress_1_inactive_system() { stats.system_stats, ecs_system_stats_t, ecs_id(FooSys)); test_assert(sys_stats != NULL); test_int(sys_stats->query_stats.t, 1); - test_int(sys_stats->invoke_count.value[1], 0); + test_int(sys_stats->invoke_count.counter.value[1], 0); ecs_progress(world, 0); - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(sys_stats->query_stats.t, 2); - test_int(sys_stats->invoke_count.value[2], 0); + test_int(sys_stats->invoke_count.counter.value[2], 0); ecs_pipeline_stats_fini(&stats); @@ -155,7 +155,7 @@ void Stats_get_pipeline_stats_after_progress_2_systems() { ecs_progress(world, 0); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(ecs_vector_count(stats.systems), 3); test_int(ecs_vector_get(stats.systems, ecs_entity_t, 0)[0], ecs_id(FooSys)); @@ -167,24 +167,24 @@ void Stats_get_pipeline_stats_after_progress_2_systems() { stats.system_stats, ecs_system_stats_t, ecs_id(FooSys)); test_assert(sys_foo_stats != NULL); test_int(sys_foo_stats->query_stats.t, 1); - test_int(sys_foo_stats->invoke_count.value[1], 1); + test_int(sys_foo_stats->invoke_count.counter.value[1], 1); ecs_system_stats_t *sys_bar_stats = ecs_map_get( stats.system_stats, ecs_system_stats_t, ecs_id(BarSys)); test_assert(sys_bar_stats != NULL); test_int(sys_bar_stats->query_stats.t, 1); - test_int(sys_bar_stats->invoke_count.value[1], 1); + test_int(sys_bar_stats->invoke_count.counter.value[1], 1); ecs_progress(world, 0); ecs_run(world, ecs_id(BarSys), 0, 0); - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(sys_foo_stats->query_stats.t, 2); - test_int(sys_foo_stats->invoke_count.value[2], 2); + test_int(sys_foo_stats->invoke_count.counter.value[2], 2); test_int(sys_bar_stats->query_stats.t, 2); - test_int(sys_bar_stats->invoke_count.value[2], 3); + test_int(sys_bar_stats->invoke_count.counter.value[2], 3); ecs_pipeline_stats_fini(&stats); @@ -207,7 +207,7 @@ void Stats_get_pipeline_stats_after_progress_2_systems_one_merge() { ecs_progress(world, 0); ecs_pipeline_stats_t stats = {0}; - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(ecs_vector_count(stats.systems), 4); test_int(ecs_vector_get(stats.systems, ecs_entity_t, 0)[0], ecs_id(FooSys)); @@ -220,22 +220,22 @@ void Stats_get_pipeline_stats_after_progress_2_systems_one_merge() { stats.system_stats, ecs_system_stats_t, ecs_id(FooSys)); test_assert(sys_foo_stats != NULL); test_int(sys_foo_stats->query_stats.t, 1); - test_int(sys_foo_stats->invoke_count.value[1], 1); + test_int(sys_foo_stats->invoke_count.counter.value[1], 1); ecs_system_stats_t *sys_bar_stats = ecs_map_get( stats.system_stats, ecs_system_stats_t, ecs_id(BarSys)); test_assert(sys_bar_stats != NULL); test_int(sys_bar_stats->query_stats.t, 1); - test_int(sys_bar_stats->invoke_count.value[1], 1); + test_int(sys_bar_stats->invoke_count.counter.value[1], 1); ecs_progress(world, 0); - test_bool(ecs_get_pipeline_stats(world, pipeline, &stats), true); + test_bool(ecs_pipeline_stats_get(world, pipeline, &stats), true); test_int(sys_foo_stats->query_stats.t, 2); - test_int(sys_foo_stats->invoke_count.value[2], 2); + test_int(sys_foo_stats->invoke_count.counter.value[2], 2); test_int(sys_bar_stats->query_stats.t, 2); - test_int(sys_bar_stats->invoke_count.value[2], 2); + test_int(sys_bar_stats->invoke_count.counter.value[2], 2); ecs_pipeline_stats_fini(&stats); @@ -246,23 +246,23 @@ void Stats_get_entity_count() { ecs_world_t *world = ecs_init(); ecs_world_stats_t stats = {0}; - ecs_get_world_stats(world, &stats); + ecs_world_stats_get(world, &stats); float count; - float prev = count = stats.entity_count.avg[stats.t]; + float prev = count = stats.entity_count.gauge.avg[stats.t]; test_assert(count != 0); ecs_entity_t e = ecs_new_id(world); - ecs_get_world_stats(world, &stats); - count = stats.entity_count.avg[stats.t]; + ecs_world_stats_get(world, &stats); + count = stats.entity_count.gauge.avg[stats.t]; test_int(count - prev, 1); ecs_delete(world, e); prev = count; - ecs_get_world_stats(world, &stats); - count = stats.entity_count.avg[stats.t]; + ecs_world_stats_get(world, &stats); + count = stats.entity_count.gauge.avg[stats.t]; test_int(count - prev, -1); ecs_fini(world); @@ -272,24 +272,24 @@ void Stats_get_not_alive_entity_count() { ecs_world_t *world = ecs_init(); ecs_world_stats_t stats = {0}; - ecs_get_world_stats(world, &stats); + ecs_world_stats_get(world, &stats); float count; - float prev = count = stats.entity_not_alive_count.avg[stats.t]; + float prev = count = stats.entity_not_alive_count.gauge.avg[stats.t]; test_assert(count == 0); ecs_entity_t e = ecs_new_id(world); prev = count; - ecs_get_world_stats(world, &stats); - count = stats.entity_not_alive_count.avg[stats.t]; + ecs_world_stats_get(world, &stats); + count = stats.entity_not_alive_count.gauge.avg[stats.t]; test_int(count - prev, 0); ecs_delete(world, e); prev = count; - ecs_get_world_stats(world, &stats); - count = stats.entity_not_alive_count.avg[stats.t]; + ecs_world_stats_get(world, &stats); + count = stats.entity_not_alive_count.gauge.avg[stats.t]; test_int(count - prev, 1); ecs_fini(world); diff --git a/test/api/project.json b/test/api/project.json index c8b179bcf4..b0e453df3b 100644 --- a/test/api/project.json +++ b/test/api/project.json @@ -842,6 +842,7 @@ "filter_1_variable_as_pred_w_pair", "filter_w_pair_id", "filter_w_pred_obj", + "filter_w_pair_id_and_subj", "filter_move", "filter_copy", "filter_w_resources_copy", @@ -1186,7 +1187,8 @@ "rematch_optional_ref_tag_w_ref_component", "match_query_expr_from_scope", "query_long_or_w_ref", - "0_query" + "0_query", + "query_w_pair_id_and_subj" ] }, { "id": "Iter", diff --git a/test/api/src/Filter.c b/test/api/src/Filter.c index e99bd0835e..126a9bf2ad 100644 --- a/test/api/src/Filter.c +++ b/test/api/src/Filter.c @@ -734,6 +734,40 @@ void Filter_filter_w_pred_obj() { ecs_fini(world); } +void Filter_filter_w_pair_id_and_subj() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + ECS_TAG(world, Subj); + + ecs_id_t pair = ecs_pair(Rel, Obj); + + ecs_filter_t f; + test_int(ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{.id = pair, .subj.entity = Subj}} + }), 0); + + test_int(f.term_count, 1); + test_int(f.term_count_actual, 1); + test_assert(f.terms != NULL); + test_int(f.terms[0].id, pair); + test_int(f.terms[0].oper, EcsAnd); + test_int(f.terms[0].index, 0); + test_int(f.terms[0].pred.entity, Rel); + test_int(f.terms[0].pred.var, EcsVarIsEntity); + test_int(f.terms[0].subj.entity, Subj); + test_int(f.terms[0].subj.set.mask, EcsSelf|EcsSuperSet); + test_int(f.terms[0].subj.var, EcsVarIsEntity); + test_int(f.terms[0].obj.entity, Obj); + test_int(f.terms[0].obj.set.mask, EcsSelf); + test_int(f.terms[0].obj.var, EcsVarIsEntity); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + void Filter_term_w_id() { ecs_world_t *world = ecs_mini(); diff --git a/test/api/src/OnDelete.c b/test/api/src/OnDelete.c index 6709acc0cd..031bac471f 100644 --- a/test/api/src/OnDelete.c +++ b/test/api/src/OnDelete.c @@ -1244,9 +1244,9 @@ void OnDelete_stresstest_many_objects() { int i, ri; ecs_world_stats_t s = {0}; - ecs_get_world_stats(world, &s); + ecs_world_stats_get(world, &s); - float table_count = s.table_count.avg[s.t]; + float table_count = s.table_count.gauge.avg[s.t]; /* Precreate relations so we get different relationship ids */ ecs_entity_t relations[100]; @@ -1265,9 +1265,9 @@ void OnDelete_stresstest_many_objects() { ecs_delete(world, relations[ri]); } - ecs_get_world_stats(world, &s); + ecs_world_stats_get(world, &s); - test_int(s.table_count.avg[s.t] - table_count, 1); + test_int(s.table_count.gauge.avg[s.t] - table_count, 1); ecs_fini(world); } @@ -1280,9 +1280,9 @@ void OnDelete_stresstest_many_relations() { int i, oi; ecs_world_stats_t s = {0}; - ecs_get_world_stats(world, &s); + ecs_world_stats_get(world, &s); - float table_count = s.table_count.avg[s.t]; + float table_count = s.table_count.gauge.avg[s.t]; /* Precreate objects so we get different relationship ids */ ecs_entity_t objects[100]; @@ -1301,9 +1301,9 @@ void OnDelete_stresstest_many_relations() { ecs_delete(world, objects[oi]); } - ecs_get_world_stats(world, &s); + ecs_world_stats_get(world, &s); - test_int(s.table_count.avg[s.t] - table_count, 1); + test_int(s.table_count.gauge.avg[s.t] - table_count, 1); ecs_fini(world); } @@ -1323,8 +1323,8 @@ void OnDelete_stresstest_many_objects_on_delete() { } ecs_world_stats_t s = {0}; - ecs_get_world_stats(world, &s); - float table_count = s.table_count.avg[s.t]; + ecs_world_stats_get(world, &s); + float table_count = s.table_count.gauge.avg[s.t]; for (ri = 0; ri < 100; ri ++) { for (i = 0; i < 100; i ++) { @@ -1337,8 +1337,8 @@ void OnDelete_stresstest_many_objects_on_delete() { ecs_delete(world, relations[ri]); } - ecs_get_world_stats(world, &s); - test_int(s.table_count.avg[s.t] - table_count, 0); + ecs_world_stats_get(world, &s); + test_int(s.table_count.gauge.avg[s.t] - table_count, 0); ecs_fini(world); } @@ -1363,8 +1363,8 @@ void OnDelete_stresstest_many_relations_on_delete() { ecs_new_w_pair(world, EcsOnDeleteObject, EcsDelete); ecs_world_stats_t s = {0}; - ecs_get_world_stats(world, &s); - float table_count = s.table_count.avg[s.t]; + ecs_world_stats_get(world, &s); + float table_count = s.table_count.gauge.avg[s.t]; for (oi = 0; oi < COUNT; oi ++) { for (i = 0; i < COUNT; i ++) { @@ -1379,9 +1379,9 @@ void OnDelete_stresstest_many_relations_on_delete() { ecs_delete(world, objects[oi]); } - ecs_get_world_stats(world, &s); + ecs_world_stats_get(world, &s); - test_int(s.table_count.avg[s.t] - table_count, 0); + test_int(s.table_count.gauge.avg[s.t] - table_count, 0); #undef COUNT diff --git a/test/api/src/Query.c b/test/api/src/Query.c index 19fd08fd17..d92c90d8be 100644 --- a/test/api/src/Query.c +++ b/test/api/src/Query.c @@ -6646,3 +6646,32 @@ void Query_0_query() { ecs_fini(world); } + +void Query_query_w_pair_id_and_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + ECS_TAG(world, Subj); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .filter.terms = {{ + .id = ecs_pair(Rel, Obj), .subj.entity = Subj + }} + }); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(false, ecs_query_next(&it)); + + ecs_add_pair(world, Subj, Rel, Obj); + + it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 0); + test_uint(it.subjects[0], Subj); + test_uint(it.ids[0], ecs_pair(Rel, Obj)); + test_bool(false, ecs_query_next(&it)); + + ecs_fini(world); +} diff --git a/test/api/src/main.c b/test/api/src/main.c index c312f35541..b3c8dd98b3 100644 --- a/test/api/src/main.c +++ b/test/api/src/main.c @@ -789,6 +789,7 @@ void Filter_filter_1_variable_as_pred_w_subj(void); void Filter_filter_1_variable_as_pred_w_pair(void); void Filter_filter_w_pair_id(void); void Filter_filter_w_pred_obj(void); +void Filter_filter_w_pair_id_and_subj(void); void Filter_filter_move(void); void Filter_filter_copy(void); void Filter_filter_w_resources_copy(void); @@ -1130,6 +1131,7 @@ void Query_rematch_optional_ref_tag_w_ref_component(void); void Query_match_query_expr_from_scope(void); void Query_query_long_or_w_ref(void); void Query_0_query(void); +void Query_query_w_pair_id_and_subj(void); // Testsuite 'Iter' void Iter_page_iter_0_0(void); @@ -4908,6 +4910,10 @@ bake_test_case Filter_testcases[] = { "filter_w_pred_obj", Filter_filter_w_pred_obj }, + { + "filter_w_pair_id_and_subj", + Filter_filter_w_pair_id_and_subj + }, { "filter_move", Filter_filter_move @@ -6261,6 +6267,10 @@ bake_test_case Query_testcases[] = { { "0_query", Query_0_query + }, + { + "query_w_pair_id_and_subj", + Query_query_w_pair_id_and_subj } }; @@ -9526,7 +9536,7 @@ static bake_test_suite suites[] = { "Filter", NULL, NULL, - 189, + 190, Filter_testcases }, { @@ -9540,7 +9550,7 @@ static bake_test_suite suites[] = { "Query", NULL, NULL, - 165, + 166, Query_testcases }, {