skypjack / entt

Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more
https://github.com/skypjack/entt/wiki
MIT License
10.2k stars 891 forks source link

"EnTTish" way to "get children" of parent? #1083

Closed keithlostracco closed 1 year ago

keithlostracco commented 1 year ago

Thank you for this amazing library.

I'm just starting to use EnTT for a project and thought it would be good to ask if my method for finding all the children in a hierarchy can be better implemented using built-in EnTT functions. I'm currently passing the parent I want to find the children of to a function that calls another function that recursively iterates through all the children (accumulating those children in std::vector). Until either a specified depth is reached or it has found all children.

public:
std::vector<entt::entity> findChildren(entt::entity parent, size_t searchDepth)
{
    std::vector<entt::entity> children;

    if (searchDepth == 0)
        searchDepth= std::numeric_limits<size_t>::max();
    _findChildren_(parent, children, searchDepth, 0);

    return children;
} 
private:
void _findChildren_(entt::entity parent, std::vector<entt::entity>& children, size_t searchDepth, size_t depthStep)
{
    auto& rel = reg.get<Relationship>(parent);
    size_t parentDepth = rel.depth;
    auto child = rel.firstChild;

    ++depthStep;
    while (child != entt::null)
    {
        children.push_back(child);
        if (depthStep < searchDepth)
            _findChildren_(child, children, searchDepth, depthStep);
        child = reg.get<Relationship>(child).nextSibling;
    }
}

Any thoughts? Is std::vector the best container to use in this case?

skypjack commented 1 year ago

There are many ways to address it and the right solution depends on the requirements. To be honest, I don't think there exists a one-fits-all approach for this kind of problem. For example, if you access these elements mostly randomly, pointer stability can make everything much easier. On the other hand, if linear access is a thing on hot paths for this hierarchy, a fully packed storage is the way to go. You solution works too, no doubts about it. I can't say if it's the best for your use case because I don't have enough context. However, I can suggest to avoid calling registry.get<T> again and again. This triggers a pool lookup all the times and you don't really need it. Just create a view and get the component from there or directly access the storage and use it instead. 👍

keithlostracco commented 1 year ago

Thanks for the quick response.

At this time, the only use case I can specifically optimize for is GUI interaction. I'm not building a game engine. It's more of an application akin to classic 3D animation and rendering software but has the specific requirement of rendering/running fast (like a game). Eventually, as things progress, I'm sure I'll have more specific requirements and will want to optimize for those requirements at that time.

I do know that I need to adhere to standard 3D formats/workflows, which typically have a scene graph structure (OpenUSD etc...).

Right now, since I'm just working on UI, my findChildren() function is just used to display the nodes in a tree list. It sounds like my findChildren() modified to use a view rather than a full pool lookup is wise and is likely good enough for now.

Regarding your mention of "pointer stability," do you mean to simply save children in a Relationship in a std::array (or map)?

skypjack commented 1 year ago

I'll have more specific requirements and will want to optimize for those requirements at that time.

This makes perfectly sense, do not try to optimize everything just because from the beginning.

Regarding your mention of "pointer stability," do you mean to simply save children in a Relationship in a std::array (or map)?

No, no, EnTT offers a pointer-stable mode for components. You can enable it for one, N or all components as you like. This makes it possible to store pointers aside directly and the latter works pretty well with hierarchies as you can guess.

keithlostracco commented 1 year ago

No, no, EnTT offers a pointer-stable mode for components. You can enable it for one, N or all components as you like. This makes it possible to store pointers aside directly and the latter works pretty well with hierarchies as you can guess.

Sweet! I'll look into that.