Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible wrong order of on_destroy calls #1169

Open
rulkvi opened this issue Aug 22, 2024 · 3 comments
Open

Possible wrong order of on_destroy calls #1169

rulkvi opened this issue Aug 22, 2024 · 3 comments
Assignees
Labels
documentation docs related issues/requests

Comments

@rulkvi
Copy link

rulkvi commented Aug 22, 2024

Hello,
I spotted strange problem with entity destruction: On the wiki there is statement "Listeners for the destruction signals are invoked before components have been removed from entities." which have sense. But when I register on_destroy callback I see an entity without any component associated with it. I tested it on todays latest ENTT with code:

#include <iostream>
#include <entt/entt.hpp>

struct position {
    int x;
    int y;
};

class TestObserver
{
public:
    TestObserver(entt::registry* registry)
    {
        m_registry = registry;
    }
    entt::registry* m_registry;
    void component_check(entt::entity e)
    {
        bool has_component = m_registry->all_of<position>(e);
        std::string result = has_component ? "component exists" : "component doesnt exists";
        std::cout << "\t" << result << std::endl;
    }
    void entity_deleted_callback(const entt::entity e )
    {
        std::cout << "entity_deleted_callback called" << std::endl;
        component_check(e);
        std::cout << "\t" <<"Entity deleted\n";
    }

    void component_deleted_callback(const entt::entity e)
    {
        std::cout << "component_deleted_callback called" << std::endl;
        component_check(e);
        std::cout << "\t" << "Component deleted\n";
    }
};


int main() {
    entt::registry registry;
    TestObserver observer(&registry);
    registry.on_destroy<entt::entity>().connect<&TestObserver::entity_deleted_callback>(observer);
    registry.on_destroy<position>().connect<&TestObserver::component_deleted_callback>(observer);

    auto e = registry.create();
    registry.emplace<position>(e, 0, 0);

    registry.destroy(e);

    std::cout << "end\n";

}

and the result is:
image

I think the order of calls is wrong, or at least the fact that on_destroy callback of entity happens at the time when any contextual information about entity is already destroyed is not according to the documentation.
Can you please verify if it is a bug, or it is intended behaviour of the system?

@skypjack skypjack self-assigned this Aug 23, 2024
@skypjack
Copy link
Owner

Actually, the behavior is correct and by design. Maybe it needs to be better described though, especially now that entities have their own storage. In fact, entity storage is nothing more than another storage used by the registry and the users for validity checks. Nothing more, nothing less.
So, what the documentation initially meant and still means is that listeners are invoked on the single storage before the entity is removed from it. This for each storage. However, as you can guess, entity storage is the last to be taken into account. This is because any user check would fail otherwise, since the entity is no longer valid within the registry once removed, even if available in other storages.
Therefore, if the expectation was that the entity listener would be invoked before all components were removed, this is not the case and will never be. This is not the goal and there are other more ECS-y methods for this in fact.
For example, rather than responding to an event in the middle of some unknown operation and without control, it's safer to assign a destroy_me component to the entity and have a system that, at a specific and known point in the loop, performs all the necessary operations before actually destroying the entity.

@skypjack skypjack added the invalid not a bug/not an issue/not whatever you want label Aug 23, 2024
@rulkvi
Copy link
Author

rulkvi commented Aug 23, 2024

Hi,
Thank you for quick answer. And for this amazing entt framework too!
I understand your vision of architecture here; it seems like information on project’s wiki is little bit out of date: “ Note also that:

Listeners for the construction signals are invoked after components have been assigned to entities.

Listeners designed to observe changes are invoked after components have been updated.

Listeners for the destruction signals are invoked before components have been removed from entities.”

what I’m trying to do with this - is to make backup of deleted entity with all its components, and callback like that seemed perfect place to write saving code.

Thank you!

@skypjack
Copy link
Owner

Roughly speaking, the three sentences are right. The fact is that they apply to their own storage. Like, from the beginning.
However, entities didn't have their own storage or signals initially, so I see why someone could misunderstand it today. I'll fix the doc accordingly. Thanks for pointing this out. 👍

is to make backup of deleted entity with all its components, and callback like that seemed perfect place to write saving code.

Wut? No, I wouldn't do so to be honest. A matter of tastes ofc but I'd prefer to mark entities to back up with a component and have a system that does so. I don't like doing heavy things in a signal callback. You never know when it fires after a while. On the other hand, a system is invoked during your loop at a specific position, no matter what.

@skypjack skypjack added documentation docs related issues/requests and removed invalid not a bug/not an issue/not whatever you want labels Aug 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation docs related issues/requests
Projects
None yet
Development

No branches or pull requests

2 participants