SanderMertens / flecs

A fast entity component system (ECS) for C & C++
https://www.flecs.dev
Other
6.42k stars 447 forks source link

Store entities without components in empty (root) archetype #1320

Open jpeletier opened 2 months ago

jpeletier commented 2 months ago

Describe the bug

On world fini, component entity gets destroyed before row instances of the component itself. I was only able to reproduce it when the component is added to a child entity.

This is a problem for implementing a fix for #1215, since I need that the component entity whose instance I am destroying is still alive, so I can access the serialization opcodes that describe the type to destroy the instance properly.

This only happens on world fini. If the entity is destroyed manually, the problem does not occur.

To Reproduce Steps to reproduce the behavior:

  flecs::world ecs;

  flecs::entity t = ecs.component("T").member<int32_t>("x");

  flecs::type_hooks_t h = *ecs_get_hooks_id(ecs, t);

  h.dtor = [](void *ptr, int32_t count, const ecs_type_info_t *type_info) {
    // If this component instance destructor gets called on world fini,
    // the following assertion fails:
    assert(ecs_is_alive(type_info->world, type_info->component));
  };

  ecs_set_hooks_id(ecs, t, &h);

  flecs::entity parent = ecs.entity();
  flecs::entity child = ecs.entity();
  child.add(t); // add the component that has a destructor

  // Problem only occurs if `child` is a child:
  child.child_of(parent);

  // If the child is destroyed before world fini, the destructor works as expected:
  // child.destruct();

Expected behavior

On world fini, entity whose destructor is called must be alive and with all its components available before invoking its destructor.

SanderMertens commented 2 months ago

The reason this happens is that parent is an entity without any components, which causes it to not get picked up by the cleanup logic that ordinarily ensures that regular entities get cleaned up before components.

This is not ideal, but to fix this I'd have to:

Option 1 could significantly increase world deletion time, and since this is an unlikely scenario in real applications (parent entities will in most cases have components), not worth the overhead.

Option 2 is something I'm considering since it has other benefits as well, but won't happen in the short term.

SanderMertens commented 2 months ago

Changed the title of the issue to track the storage refactor that will fix this (option 2) as well as other issues that this would fix like: