SanderMertens / flecs

A fast entity component system (ECS) for C & C++

Home Page:https://www.flecs.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Assert `!(table->flags & EcsTableHasTarget)` while merging stage

Tisten opened this issue · comments

Describe the bug
When adding a component to an entity which is part of a flattened branch I get fatal: table.c: 1533: assert: !(table->flags & EcsTableHasTarget) INVALID_OPERATION with the callstack:

flecs.dll!abort() Line 77	C++
flecs.dll!flecs_table_append(ecs_world_t * world, ecs_table_t * table, unsigned __int64 entity, ecs_record_t * record, bool construct, bool on_add) Line 1532	C
flecs.dll!flecs_move_entity(ecs_world_t * world, unsigned __int64 entity, ecs_record_t * record, ecs_table_t * dst_table, ecs_table_diff_t * diff, bool ctor, unsigned int evt_flags) Line 622	C
flecs.dll!flecs_commit(ecs_world_t * world, unsigned __int64 entity, ecs_record_t * record, ecs_table_t * dst_table, ecs_table_diff_t * diff, bool construct, unsigned int evt_flags) Line 738	C
flecs.dll!flecs_add_id(ecs_world_t * world, unsigned __int64 entity, unsigned __int64 id) Line 914	C
flecs.dll!flecs_defer_end(ecs_world_t * world, ecs_stage_t * stage) Line 4612	C
flecs.dll!flecs_stages_merge(ecs_world_t * world, bool force_merge) Line 115	C
flecs.dll!flecs_stage_auto_merge(ecs_world_t * world) Line 139	C
flecs.dll!ecs_readonly_end(ecs_world_t * world) Line 759	C
flecs.dll!flecs_worker_end(ecs_world_t * world, ecs_stage_t * stage) Line 306	C
flecs.dll!flecs_worker_sync(ecs_world_t * world, ecs_stage_t * stage, ecs_pipeline_state_t * pq, ecs_pipeline_op_t * * cur_op, int * cur_i) Line 324	C
flecs.dll!flecs_run_pipeline(ecs_world_t * world, ecs_pipeline_state_t * pq, float delta_time) Line 598	C
flecs.dll!flecs_workers_progress(ecs_world_t * world, ecs_pipeline_state_t * pq, float delta_time) Line 351	C
flecs.dll!ecs_progress(ecs_world_t * world, float user_delta_time) Line 681	C

To Reproduce
I wrote a test in the FixedHierarchies suite for it, the test is based on Get_component_get_1_from_2_add_in_progress():

static
void Add_in_progress(ecs_iter_t* it) {
    ecs_id_t ecs_id(Velocity) = 0;

    if (it->field_count >= 2) {
        ecs_id(Velocity) = ecs_field_id(it, 2);
    }

    for (int i = 0; i < it->count; i++) {
        ecs_entity_t e = it->entities[i];
        ecs_add(it->world, e, Velocity);
        test_assert(!ecs_has(it->world, e, Velocity));
    }
}

void FixedHierarchies_get_1_from_2_add_in_progress() {
    ecs_world_t* world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ECS_ENTITY(world, e, Position);
    test_assert(e != 0);

    ECS_ENTITY(world, e_e, (ChildOf, e), Position);
    ECS_ENTITY(world, e_e_e, (ChildOf, e.e_e), Position);

    ecs_flatten(world, ecs_pair(EcsChildOf, e), NULL);

    ECS_SYSTEM(world, Add_in_progress, EcsOnUpdate, Position, Velocity());

    ecs_progress(world, 1);
    test_assert(ecs_has(world, e, Velocity));
    test_assert(ecs_has(world, e_e, Velocity));
    test_assert(ecs_has(world, e_e_e, Velocity));
    test_assert(ecs_get_type(world, e)->array[1] == ecs_id(Velocity));

    ecs_fini(world);
}

Adding/removing entities to a flattened tree is not yet supported. As per the documentation:

Relationship flattening is an experimental feature, and some limitations apply for the current implementation. After a subtree has been flattened, the entities in that subtree can no longer be individually deleted from a target, and cannot be moved to another parent. Additionally, no components can be added/removed to entities in a flattened subtree. Relationship flattening is currently supported only for exclusive, acyclic relationships.

https://www.flecs.dev/flecs/md_docs_Relationships.html

I would like to make this possible eventually, but hierarchy flattening makes some changes to the storage that make this non-trivial.

I'm not removing or adding any entities, the entities in the flattened tree and their relationship are all kept intact and I'm only adding components, i.e changing the C in the EntityComponentSystem. The documentation only speaks about entities which is the E in ECS. This makes me very confused.

Would it be possible to add an assert with a clear error message when a component is added to a flattened tree? Because I don't get any assert if I reduce the depth one step (and that should still be invalid) and the !(table->flags & EcsTableHasTarget) assert didn't give me any clue that I had made an error. It might very well be this error which end up causing the segfault in #965

In flecs, any entity can be added to an entity. Components are entities, that when added to other entities, just happen to come with a value attached as well. Non-component entities are usually referred to as "tags" when added to an entity.

Okay, thanks for the clarification.

So when I've been reading all this talk about what a component is and what an entity is, then flecs could be said to be an Entity-EntityWithData-System instead, and "component" is just syntactic sugar. And if I'm interpreting this correctly then I can't use flecs' tags with flattened subtrees, but need to add another tagging system on top of flecs if I want both featues, right?

I'm also getting the same assert when I remove the root entity of a flattened tree. Is there a special function to use when removing the whole subtree?

You can also enable / disable specific components or tags on entities without removing them from the entity. I'm unsure if that is fully supported with flattened trees but I'm guessing since it doesn't change the entity type and only affects query matching, it might work? Also not sure about the performance impact.

C: https://www.flecs.dev/flecs/group__enabling__disabling.html
C++: https://www.flecs.dev/flecs/structflecs_1_1entity__builder.html#a5ba67bf384c2d2f96e6e1a2651243a11

This would require knowing which tags and components might appear on these entities and adding them (disabled) beforehand, but it might be a possibility to consider. Otherwise you might just have to wait for the ability to modify flattened hierarchies to be implemented, or not use the feature for now.

Great, I will have a look at disabling components. It won't be an option for tags since content creators are free to add their own tags. But I wouldn't like tags to cause table switches anyway so separating them out could be a good thing overall, but it will make tag based queries more complicated.

If an entity can be added to an entity, perhaps I can tweak flecs to only partially flatten a subtree and keep an unflattened conjoined twin in each entity which can be used for dynamic components and tags... It would add an extra indirection when I want to look at those dynamic entities, and again it would make queries and filters more complicated which goes against the direction of core flecs.

From your comment:

I'm not removing or adding any entities, the entities in the flattened tree and their relationship are all kept intact and I'm only adding components

The documentation states:

Additionally, no components can be added/removed to entities in a flattened subtree.

In the code example you add Velocity, which is what is causing the assert.

it would make queries and filters more complicated which goes against the direction of core flecs.

I 100% agree with that, and this limitation is something that I'd like to eventually get rid of. Tree flattening is still pretty new and experimental, but over time I expect the rough edges will get ironed out :)

About:

You can also enable / disable specific components or tags on entities without removing them from the entity. I'm unsure if that is fully supported with flattened trees but I'm guessing since it doesn't change the entity type and only affects query matching, it might work? Also not sure about the performance impact.

The first time you enable or disable anything you add a "toggle" entity to the entity, so all tags and components you want to be able to disable you need to make togglable before you flatten.

The documentation of ecs_enable_id says Enabling or disabling a component does not add or remove a component from an entity which feel a bit misleading since it does add an entity with data (aka component) to the entity, and it adds one per component and tag.

Disabling tags works fine in all unittests I've made so far, but the disabled tags still make it through the queries I make in my engine so there is something wrong with how I do tag based queries. :(

Apparently our engine used a filter instead of a query, and disabling things seems to onlyt work with queries and not with filters.

Will dig deeper into this, but reported it right away: #973