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

Cascade and topological sort with a custom relation

elaucoin opened this issue · comments

Describe the bug

I tried to use Cascade with a custom Acyclic (but not Exclusive) relation, hoping that it would iterate on entities following a topological sort.
I'm afraid that this is true.

To Reproduce
I tried the following code :

#include <flecs.h>
#include <iostream>

struct Tag {};

int main(int argc, char** argv)
{
    auto world = flecs::world{};

    auto DependsOn = world.entity("DependsOn");
    DependsOn.add(flecs::Acyclic);

    auto e1 = world.entity("e1").add<Tag>();
    auto e2 = world.entity("e2").add<Tag>();
    auto e3 = world.entity("e3").add<Tag>();
    auto e4 = world.entity("e4").add<Tag>();
    auto e5 = world.entity("e5").add<Tag>();
    auto e6 = world.entity("e6").add<Tag>();

    auto q = world.query_builder<>()
        .term<Tag>()
            .set( flecs::Cascade | flecs::Optional, DependsOn)
         .build();

    q.each( [](flecs::entity e){ std::cout << e.name() << " "; } );
    std::cout << std::endl;

    e3.add( DependsOn, e1 );
    e3.add( DependsOn, e2 );
    e3.add( DependsOn, e4 );
    e1.add( DependsOn, e5 );
    e2.add( DependsOn, e6 );
    e4.add( DependsOn, e1 );
    e4.add( DependsOn, e2 );

    q.each( [](flecs::entity e){ std::cout << e.name() << " "; } );
    std::cout << std::endl;

    e3.remove(DependsOn, e1);
    e3.remove(DependsOn, e2);
    e3.remove(DependsOn, e4);
    e1.remove(DependsOn, e5);
    e2.remove(DependsOn, e6);
    e4.remove(DependsOn, e1);
    e4.remove(DependsOn, e2);

    q.each( [](flecs::entity e){ std::cout << e.name() << " "; } );
    std::cout << std::endl;

    e3.add(DependsOn, e1);
    e3.add(DependsOn, e2);
    e3.add(DependsOn, e4);
    e1.add(DependsOn, e5);
    e2.add(DependsOn, e6);
    e4.add(DependsOn, e1);
    e4.add(DependsOn, e2);

    q.each( [](flecs::entity e){ std::cout << e.name() << " "; } );
    std::cout << std::endl;

    return 0
}

And it produces :

e1 e2 e3 e4 e5 e6
e5 e6 e1 e2 e3 e4
e5 e6 e3 e1 e2 e4
e5 e6 e1 e2 e4 e3

Expected behavior
The second line produces e3 before e4, but e3 depends on e1, e2, and e4.

Either I misunderstood Cascade's behaviour (and in this case, could you help me build iterate on my entities following a topological ordering), or I am afraid that the BFS ordering of the Cascade does not garantee the topological ordering.

The reason for this behavior is that e3 has multiple DependsOn relationships. The cascade feature uses a breadth-first order where depth is calculated by the distance from root, when following the specified relationship. When an entity has multiple instances of the relationship, the depth is ambiguous, which is why the observed ordering is unexpected.

I do think the proposed behavior makes sense to implement as an enhancement, I'll take a look at how feasible it is.

Thanks for this quick answer.
I understand better now the reasons for the behavior of my code.

In order to generalize the behaviour of cascade to non-exclusive relations, I think that depth can be disambiguated using max(depth(related_entities))+1. This would result in a topological ordering of the whole DAG, without requiring the entity to have only one instance of the relationship.

Best regards

Yup I agree, that'd be nice

Fixed!