diff --git a/src/Magnum/SceneGraph/Test/ObjectTest.cpp b/src/Magnum/SceneGraph/Test/ObjectTest.cpp index dec800bd4..a2649a79e 100644 --- a/src/Magnum/SceneGraph/Test/ObjectTest.cpp +++ b/src/Magnum/SceneGraph/Test/ObjectTest.cpp @@ -57,6 +57,8 @@ struct ObjectTest: TestSuite::Tester { template void rangeBasedForChildren(); template void rangeBasedForFeatures(); + + void treeDestructionOrder(); }; ObjectTest::ObjectTest() { @@ -96,7 +98,9 @@ ObjectTest::ObjectTest() { &ObjectTest::rangeBasedForChildren, &ObjectTest::rangeBasedForChildren, &ObjectTest::rangeBasedForFeatures, - &ObjectTest::rangeBasedForFeatures}); + &ObjectTest::rangeBasedForFeatures, + + &ObjectTest::treeDestructionOrder}); } template using Object3D = SceneGraph::Object>; @@ -609,6 +613,98 @@ template void ObjectTest::rangeBasedForFeatures() { CORRADE_COMPARE(features, (std::vector*>{&a, &b, &c})); } +void ObjectTest::treeDestructionOrder() { + struct AccessingParent: Object3D { + explicit AccessingParent(Int id, Object3D* parent): Object3D{parent}, id{id} {} + + ~AccessingParent() { + int parentDepth = 0; + Object3D* p = parent(); + while(p) { + ++parentDepth; + p = p->parent(); + } + + Debug{} << "Destructing an object" << id << "with" << parentDepth << "parents and" << (scene() ? "a scene" : "no scene"); + } + + Int id; + }; + + struct AccessingObject: AbstractFeature3D { + explicit AccessingObject(Object3D& object, int id): AbstractFeature3D{object}, id{id} {} + + ~AccessingObject() { + Debug{} << "Destructing a feature" << id << "attached to an object" << static_cast(object()).id; + } + + int id; + }; + + std::stringstream out; + Debug redirectOutput{&out}; + { + struct Scene: Scene3D { + ~Scene() { + Debug{} << "Destructing the scene"; + } + } scene; + + /* These get deleted at the end of scope */ + AccessingParent a{0, &scene}; + AccessingParent b{1, &a}; + AccessingObject bf{b, 0}; + + /* These get deleted as a consequence of b getting out of scope */ + AccessingParent* c = new AccessingParent{2, &b}; + new AccessingObject{*(new AccessingParent{3, c}), 1}; + new AccessingObject{*c, 2}; + + /* These during scene destruction */ + AccessingParent* d = new AccessingParent{4, &scene}; + new AccessingObject{*d, 3}; + + /* These get deleted right now */ + AccessingParent{5, nullptr}; + AccessingObject{b, 4}; + + CORRADE_COMPARE(out.str(), + "Destructing an object 5 with 0 parents and no scene\n" + "Destructing a feature 4 attached to an object 1\n"); + } + + CORRADE_COMPARE(out.str(), + "Destructing an object 5 with 0 parents and no scene\n" + "Destructing a feature 4 attached to an object 1\n" + + /* First a feature that was on stack gets destructed */ + "Destructing a feature 0 attached to an object 1\n" + + /* Then `b`, which then proceeds with destructing heap-allocated child + `c` and its grand child */ + "Destructing an object 1 with 2 parents and a scene\n" + "Destructing an object 2 with 3 parents and a scene\n" + "Destructing an object 3 with 4 parents and a scene\n" + /* and after that the heap-allocated feature attached to the + grandchild */ + "Destructing a feature 1 attached to an object 3\n" + /* and then the feature attached to `c` */ + "Destructing a feature 2 attached to an object 2\n" + + /* Then `a`, which has nothing */ + "Destructing an object 0 with 1 parents and a scene\n" + + /* Then the scene and everything remaining attached to it -- first the + object and then the feature attached to it */ + "Destructing the scene\n" + /* The scene got partially destructed at this point and only an Object + remains of it, which means the isScene() override saying it's a + scene is no longer present */ + "Destructing an object 4 with 1 parents and no scene\n" + "Destructing a feature 3 attached to an object 4\n" + ); +} + }}}} CORRADE_TEST_MAIN(Magnum::SceneGraph::Test::ObjectTest)