Skip to content

Commit

Permalink
SanderMertens#1391 Avoid size overflow of command buffer vector durin…
Browse files Browse the repository at this point in the history
…g world cleanup
  • Loading branch information
jpeletier authored Nov 7, 2024
1 parent 5621263 commit a5b8055
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 67 deletions.
89 changes: 57 additions & 32 deletions distr/flecs.c
Original file line number Diff line number Diff line change
Expand Up @@ -18351,45 +18351,70 @@ void flecs_fini_root_tables(
ecs_id_record_t *idr,
bool fini_targets)
{
ecs_table_cache_iter_t it;
ecs_stage_t *stage0 = world->stages[0];
bool finished = false;
const ecs_size_t MAX_DEFERRED_DELETE_QUEUE_SIZE = 4096;
while (!finished) {
ecs_table_cache_iter_t it;
ecs_size_t queue_size = 0;
finished = true;

bool has_roots = flecs_table_cache_iter(&idr->cache, &it);
ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL);
(void)has_roots;
bool has_roots = flecs_table_cache_iter(&idr->cache, &it);
ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL);
(void)has_roots;

const ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
ecs_table_t *table = tr->hdr.table;
if (table->flags & EcsTableHasBuiltins) {
continue; /* Query out modules */
}
const ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
ecs_table_t *table = tr->hdr.table;
if (table->flags & EcsTableHasBuiltins) {
continue; /* Query out modules */
}

int32_t i, count = ecs_table_count(table);
const ecs_entity_t *entities = ecs_table_entities(table);
int32_t i, count = ecs_table_count(table);
const ecs_entity_t *entities = ecs_table_entities(table);

if (fini_targets) {
/* Only delete entities that are used as pair target. Iterate
* backwards to minimize moving entities around in table. */
for (i = count - 1; i >= 0; i --) {
ecs_record_t *r = flecs_entities_get(world, entities[i]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL);
if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) {
ecs_delete(world, entities[i]);
if (fini_targets) {
/* Only delete entities that are used as pair target. Iterate
* backwards to minimize moving entities around in table. */
for (i = count - 1; i >= 0; i --) {
ecs_record_t *r = flecs_entities_get(world, entities[i]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL);
if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) {
ecs_delete(world, entities[i]);
queue_size++;
/* Flush the queue before it grows too big: */
if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) {
finished = false;
break; /* restart iteration */
}
}
}
}
} else {
/* Delete remaining entities that are not in use (added to another
* entity). This limits table moves during cleanup and delays
* cleanup of tags. */
for (i = count - 1; i >= 0; i --) {
ecs_record_t *r = flecs_entities_get(world, entities[i]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL);
if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) {
ecs_delete(world, entities[i]);
} else {
/* Delete remaining entities that are not in use (added to another
* entity). This limits table moves during cleanup and delays
* cleanup of tags. */
for (i = count - 1; i >= 0; i --) {
ecs_record_t *r = flecs_entities_get(world, entities[i]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL);
if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) {
ecs_delete(world, entities[i]);
queue_size++;
/* Flush the queue before it grows too big: */
if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) {
finished = false;
break; /* restart iteration */
}
}
}
}
if(!finished) {
/* flush queue and restart iteration */
flecs_defer_end(world, stage0);
flecs_defer_begin(world, stage0);
break;
}
}
}
}
Expand Down
91 changes: 58 additions & 33 deletions src/world.c
Original file line number Diff line number Diff line change
Expand Up @@ -633,45 +633,70 @@ void flecs_fini_root_tables(
ecs_id_record_t *idr,
bool fini_targets)
{
ecs_table_cache_iter_t it;
ecs_stage_t *stage0 = world->stages[0];
bool finished = false;
const ecs_size_t MAX_DEFERRED_DELETE_QUEUE_SIZE = 4096;
while (!finished) {
ecs_table_cache_iter_t it;
ecs_size_t queue_size = 0;
finished = true;

bool has_roots = flecs_table_cache_iter(&idr->cache, &it);
ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL);
(void)has_roots;
bool has_roots = flecs_table_cache_iter(&idr->cache, &it);
ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL);
(void)has_roots;

const ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
ecs_table_t *table = tr->hdr.table;
if (table->flags & EcsTableHasBuiltins) {
continue; /* Query out modules */
}
const ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
ecs_table_t *table = tr->hdr.table;
if (table->flags & EcsTableHasBuiltins) {
continue; /* Query out modules */
}

int32_t i, count = ecs_table_count(table);
const ecs_entity_t *entities = ecs_table_entities(table);

if (fini_targets) {
/* Only delete entities that are used as pair target. Iterate
* backwards to minimize moving entities around in table. */
for (i = count - 1; i >= 0; i --) {
ecs_record_t *r = flecs_entities_get(world, entities[i]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL);
if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) {
ecs_delete(world, entities[i]);
int32_t i, count = ecs_table_count(table);
const ecs_entity_t *entities = ecs_table_entities(table);

if (fini_targets) {
/* Only delete entities that are used as pair target. Iterate
* backwards to minimize moving entities around in table. */
for (i = count - 1; i >= 0; i --) {
ecs_record_t *r = flecs_entities_get(world, entities[i]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL);
if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) {
ecs_delete(world, entities[i]);
queue_size++;
/* Flush the queue before it grows too big: */
if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) {
finished = false;
break; /* restart iteration */
}
}
}
}
} else {
/* Delete remaining entities that are not in use (added to another
* entity). This limits table moves during cleanup and delays
* cleanup of tags. */
for (i = count - 1; i >= 0; i --) {
ecs_record_t *r = flecs_entities_get(world, entities[i]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL);
if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) {
ecs_delete(world, entities[i]);
} else {
/* Delete remaining entities that are not in use (added to another
* entity). This limits table moves during cleanup and delays
* cleanup of tags. */
for (i = count - 1; i >= 0; i --) {
ecs_record_t *r = flecs_entities_get(world, entities[i]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL);
if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) {
ecs_delete(world, entities[i]);
queue_size++;
/* Flush the queue before it grows too big: */
if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) {
finished = false;
break; /* restart iteration */
}
}
}
}
if(!finished) {
/* flush queue and restart iteration */
flecs_defer_end(world, stage0);
flecs_defer_begin(world, stage0);
break;
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion test/core/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -2023,7 +2023,8 @@
"get_entities",
"run_post_frame",
"run_post_frame_outside_of_frame",
"get_flags"
"get_flags",
"fini_queue_overflow"
]
}, {
"id": "WorldInfo",
Expand Down
28 changes: 28 additions & 0 deletions test/core/src/World.c
Original file line number Diff line number Diff line change
Expand Up @@ -1754,3 +1754,31 @@ void World_get_flags(void) {

ecs_fini(world);
}

void World_fini_queue_overflow(void) {
/* This test verifies command flushing happens in batches during
world fini to avoid overflowing the vector holding the
command queue */

ecs_world_t *world = ecs_mini();

ECS_COMPONENT(world, Position);

/* create a prefab entity: */
ecs_entity_t prefab = ecs_new(world);
ecs_add_id(world, prefab, EcsPrefab);
ecs_add_id(world, prefab, ecs_id(Position));

/* Create a large amount of entities. A number greater than 16777216,
2^30 / sizeof(ecs_cmd_t) would overflow the command queue vector */

ecs_bulk_init(world, &(ecs_bulk_desc_t) {
.count = 17000000,
.ids = { ecs_isa(prefab) }
});

/* on world fini, all entities must be destroyed in batches. */
ecs_fini(world);

test_assert(true); /* if ecs_fini did not crash we're good */
}
7 changes: 6 additions & 1 deletion test/core/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,7 @@ void World_get_entities(void);
void World_run_post_frame(void);
void World_run_post_frame_outside_of_frame(void);
void World_get_flags(void);
void World_fini_queue_overflow(void);

// Testsuite 'WorldInfo'
void WorldInfo_get_tick(void);
Expand Down Expand Up @@ -9838,6 +9839,10 @@ bake_test_case World_testcases[] = {
{
"get_flags",
World_get_flags
},
{
"fini_queue_overflow",
World_fini_queue_overflow
}
};

Expand Down Expand Up @@ -11394,7 +11399,7 @@ static bake_test_suite suites[] = {
"World",
World_setup,
NULL,
66,
67,
World_testcases
},
{
Expand Down

0 comments on commit a5b8055

Please sign in to comment.