diff --git a/headers/enduro2d/high/_all.hpp b/headers/enduro2d/high/_all.hpp index e2019281..c4677ef9 100644 --- a/headers/enduro2d/high/_all.hpp +++ b/headers/enduro2d/high/_all.hpp @@ -11,6 +11,8 @@ #include "assets.hpp" #include "library.hpp" #include "library.inl" +#include "scene.hpp" +#include "scene.inl" #include "starter.hpp" #include "world.hpp" diff --git a/headers/enduro2d/high/_high.hpp b/headers/enduro2d/high/_high.hpp index e051a6a0..123669b1 100644 --- a/headers/enduro2d/high/_high.hpp +++ b/headers/enduro2d/high/_high.hpp @@ -31,6 +31,9 @@ namespace e2d class sprite_system; } + class node; + class scene; + class asset; class library; diff --git a/headers/enduro2d/high/scene.hpp b/headers/enduro2d/high/scene.hpp new file mode 100644 index 00000000..016e055f --- /dev/null +++ b/headers/enduro2d/high/scene.hpp @@ -0,0 +1,119 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#ifndef E2D_INCLUDE_GUARD_8703CE4A74D94C3CA27ED91AFF906936 +#define E2D_INCLUDE_GUARD_8703CE4A74D94C3CA27ED91AFF906936 +#pragma once + +#include "_high.hpp" + +namespace e2d +{ + class node; + using node_iptr = intrusive_ptr; + using const_node_iptr = intrusive_ptr; + + class scene; + using scene_iptr = intrusive_ptr; + using const_scene_iptr = intrusive_ptr; + + class node_children_ilist_tag {}; + using node_children = intrusive_list; +} + +namespace e2d +{ + class node final + : private noncopyable + , public ref_counter + , public intrusive_list_hook { + public: + ~node() noexcept; + static node_iptr create(); + static node_iptr create(const node_iptr& parent); + + node_iptr root() noexcept; + const_node_iptr root() const noexcept; + + node_iptr parent() noexcept; + const_node_iptr parent() const noexcept; + + bool remove_from_parent() noexcept; + std::size_t remove_all_children() noexcept; + + bool add_child( + const node_iptr& child) noexcept; + + bool add_child_to_back( + const node_iptr& child) noexcept; + + bool add_child_to_front( + const node_iptr& child) noexcept; + + bool add_child_after( + const const_node_iptr& after, + const node_iptr& child) noexcept; + + bool add_child_before( + const const_node_iptr& before, + const node_iptr& child) noexcept; + + bool send_backward() noexcept; + bool bring_to_back() noexcept; + + bool send_forward() noexcept; + bool bring_to_front() noexcept; + + std::size_t child_count() const noexcept; + std::size_t child_count_recursive() const noexcept; + + node_iptr last_child() noexcept; + const_node_iptr last_child() const noexcept; + + node_iptr first_child() noexcept; + const_node_iptr first_child() const noexcept; + + node_iptr prev_sibling() noexcept; + const_node_iptr prev_sibling() const noexcept; + + node_iptr next_sibling() noexcept; + const_node_iptr next_sibling() const noexcept; + + template < typename F > + void for_each_child(F&& f); + + template < typename F > + void for_each_child(F&& f) const; + + template < typename F > + std::size_t remove_child_if(F&& f); + private: + node(); + private: + node* parent_{nullptr}; + node_children children_; + }; +} + +namespace e2d +{ + class scene final + : private noncopyable + , public ref_counter { + public: + ~scene() noexcept; + static scene_iptr create(); + + const node_iptr& root() const noexcept; + private: + scene(); + private: + node_iptr root_ = node::create(); + }; +} + +#include "scene.inl" +#endif diff --git a/headers/enduro2d/high/scene.inl b/headers/enduro2d/high/scene.inl new file mode 100644 index 00000000..e5298a5b --- /dev/null +++ b/headers/enduro2d/high/scene.inl @@ -0,0 +1,46 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#ifndef E2D_INCLUDE_GUARD_27EFFE66AA124FFB862BE95C2FF38017 +#define E2D_INCLUDE_GUARD_27EFFE66AA124FFB862BE95C2FF38017 +#pragma once + +#include "scene.hpp" + +namespace e2d +{ + template < typename F > + void node::for_each_child(F&& f) { + for ( node& child : children_ ) { + f(node_iptr(&child)); + } + } + + template < typename F > + void node::for_each_child(F&& f) const { + for ( const node& child : children_ ) { + f(const_node_iptr(&child)); + } + } + + template < typename F > + std::size_t node::remove_child_if(F&& f) { + std::size_t count = 0u; + for ( auto iter = children_.begin(); iter != children_.end(); ) { + node_iptr child(&*iter); + if ( f(child) ) { + ++count; + ++iter; + child->remove_from_parent(); + } else { + ++iter; + } + } + return count; + } +} + +#endif diff --git a/sources/enduro2d/high/scene.cpp b/sources/enduro2d/high/scene.cpp new file mode 100644 index 00000000..cdf2ef4e --- /dev/null +++ b/sources/enduro2d/high/scene.cpp @@ -0,0 +1,267 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include + +namespace +{ + using namespace e2d; +} + +namespace e2d +{ + node::node() = default; + + node::~node() noexcept { + remove_all_children(); + remove_from_parent(); + } + + node_iptr node::create() { + return node_iptr(new node()); + } + + node_iptr node::create(const node_iptr& parent) { + node_iptr child = create(); + if ( parent ) { + parent->add_child(child); + } + return child; + } + + node_iptr node::root() noexcept { + node* root = this; + while ( root->parent_ ) { + root = root->parent_; + } + return node_iptr(root); + } + + const_node_iptr node::root() const noexcept { + const node* root = this; + while ( root->parent_ ) { + root = root->parent_; + } + return const_node_iptr(root); + } + + node_iptr node::parent() noexcept { + return node_iptr(parent_); + } + + const_node_iptr node::parent() const noexcept { + return const_node_iptr(parent_); + } + + bool node::remove_from_parent() noexcept { + if ( !parent_ ) { + return false; + } + parent_->children_.erase( + node_children::iterator_to(*this)); + parent_ = nullptr; + return true; + } + + std::size_t node::remove_all_children() noexcept { + std::size_t count = 0; + while ( !children_.empty() ) { + children_.back().remove_from_parent(); + ++count; + } + return count; + } + + bool node::add_child(const node_iptr& child) noexcept { + return add_child_to_front(child); + } + + bool node::add_child_to_back( + const node_iptr& child) noexcept + { + if ( !child ) { + return false; + } + child->remove_from_parent(); + children_.push_front(*child); + child->parent_ = this; + return true; + } + + bool node::add_child_to_front( + const node_iptr& child) noexcept + { + if ( !child ) { + return false; + } + child->remove_from_parent(); + children_.push_back(*child); + child->parent_ = this; + return true; + } + + bool node::add_child_after( + const const_node_iptr& after, + const node_iptr& child) noexcept + { + if ( !after ) { + return add_child_to_front(child); + } + + if ( !child || after->parent_ != this ) { + return false; + } + + child->remove_from_parent(); + const auto iter = ++node_children::iterator_to(*after); + children_.insert(iter, *child); + child->parent_ = this; + return true; + } + + bool node::add_child_before( + const const_node_iptr& before, + const node_iptr& child) noexcept + { + if ( !before ) { + return add_child_to_back(child); + } + + if ( !child || before->parent_ != this ) { + return false; + } + + child->remove_from_parent(); + const auto iter = node_children::iterator_to(*before); + children_.insert(iter, *child); + child->parent_ = this; + return true; + } + + bool node::send_backward() noexcept { + const_node_iptr prev = prev_sibling(); + return prev + ? parent_->add_child_before(prev, this) + : false; + } + + bool node::bring_to_back() noexcept { + const_node_iptr prev = prev_sibling(); + return prev + ? parent_->add_child_to_back(this) + : false; + } + + bool node::send_forward() noexcept { + const_node_iptr next = next_sibling(); + return next + ? parent_->add_child_after(next, this) + : false; + } + + bool node::bring_to_front() noexcept { + const_node_iptr next = next_sibling(); + return next + ? parent_->add_child_to_front(this) + : false; + } + + std::size_t node::child_count() const noexcept { + return children_.size(); + } + + std::size_t node::child_count_recursive() const noexcept { + std::size_t count = child_count(); + for ( const node& child : children_ ) { + count += child.child_count_recursive(); + } + return count; + } + + node_iptr node::last_child() noexcept { + return !children_.empty() + ? node_iptr(&children_.back()) + : node_iptr(); + } + + const_node_iptr node::last_child() const noexcept { + return !children_.empty() + ? const_node_iptr(&children_.back()) + : const_node_iptr(); + } + + node_iptr node::first_child() noexcept { + return !children_.empty() + ? node_iptr(&children_.front()) + : node_iptr(); + } + + const_node_iptr node::first_child() const noexcept { + return !children_.empty() + ? const_node_iptr(&children_.front()) + : const_node_iptr(); + } + + node_iptr node::prev_sibling() noexcept { + if ( !parent_ ) { + return nullptr; + } + auto iter = node_children::iterator_to(*this); + if ( iter == parent_->children_.begin() ) { + return nullptr; + } + --iter; + return node_iptr(&*iter); + } + + const_node_iptr node::prev_sibling() const noexcept { + if ( !parent_ ) { + return nullptr; + } + auto iter = node_children::iterator_to(*this); + if ( iter == parent_->children_.begin() ) { + return nullptr; + } + --iter; + return const_node_iptr(&*iter); + } + + node_iptr node::next_sibling() noexcept { + if ( !parent_ ) { + return nullptr; + } + auto iter = node_children::iterator_to(*this); + if ( ++iter == parent_->children_.end() ) { + return nullptr; + } + return node_iptr(&*iter); + } + + const_node_iptr node::next_sibling() const noexcept { + if ( !parent_ ) { + return nullptr; + } + auto iter = node_children::iterator_to(*this); + if ( ++iter == parent_->children_.end() ) { + return nullptr; + } + return const_node_iptr(&*iter); + } +} + +namespace e2d +{ + scene::scene() = default; + scene::~scene() noexcept = default; + + scene_iptr scene::create() { + return scene_iptr(new scene()); + } + + const node_iptr& scene::root() const noexcept { + return root_; + } +} diff --git a/untests/sources/untests_high/scene.cpp b/untests/sources/untests_high/scene.cpp new file mode 100644 index 00000000..dda45a5f --- /dev/null +++ b/untests/sources/untests_high/scene.cpp @@ -0,0 +1,424 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_high.hpp" +using namespace e2d; + +namespace +{ +} + +TEST_CASE("node") { + SECTION("empty_node") { + auto n = node::create(); + REQUIRE(n); + REQUIRE(n->root() == n); + REQUIRE_FALSE(n->parent()); + REQUIRE(n->child_count() == 0); + REQUIRE_FALSE(n->prev_sibling()); + REQUIRE_FALSE(n->next_sibling()); + { + const_node_iptr cn = n; + REQUIRE(cn); + REQUIRE(cn->root() == cn); + REQUIRE_FALSE(cn->parent()); + REQUIRE(cn->child_count() == 0); + REQUIRE_FALSE(cn->prev_sibling()); + REQUIRE_FALSE(cn->next_sibling()); + } + } + SECTION("parent/root") { + { + auto p = node::create(); + auto n = node::create(); + + REQUIRE(p->add_child(n)); + REQUIRE(n->parent() == p); + REQUIRE(p->child_count() == 1); + + REQUIRE(p->add_child(n)); + REQUIRE(n->parent() == p); + REQUIRE(p->child_count() == 1); + + REQUIRE_FALSE(p->add_child(nullptr)); + REQUIRE(n->parent() == p); + REQUIRE(p->child_count() == 1); + } + { + auto p = node::create(); + + auto n1 = node::create(p); + REQUIRE(n1->parent() == p); + REQUIRE(p->child_count() == 1); + + auto n2 = node::create(nullptr); + REQUIRE_FALSE(n2->parent()); + REQUIRE(p->child_count() == 1); + } + { + auto p1 = node::create(); + auto p2 = node::create(); + + auto n = node::create(p1); + REQUIRE(n->parent() == p1); + REQUIRE(p1->child_count() == 1); + REQUIRE(p2->child_count() == 0); + + REQUIRE(p2->add_child(n)); + REQUIRE(n->parent() == p2); + REQUIRE(p1->child_count() == 0); + REQUIRE(p2->child_count() == 1); + } + { + auto p1 = node::create(); + auto p2 = node::create(p1); + + auto n1 = node::create(p1); + auto n2 = node::create(p2); + + REQUIRE(n1->parent() == p1); + REQUIRE(n2->parent() == p2); + + REQUIRE(n1->root() == p1); + REQUIRE(n2->root() == p1); + + auto n3 = node::create(); + REQUIRE_FALSE(n3->parent()); + REQUIRE(n3->root() == n3); + + { + const_node_iptr cn1 = n1; + const_node_iptr cn2 = n2; + + REQUIRE(cn1->parent() == p1); + REQUIRE(cn2->parent() == p2); + + REQUIRE(cn1->root() == p1); + REQUIRE(cn2->root() == p1); + + const_node_iptr cn3 = node::create(); + REQUIRE_FALSE(cn3->parent()); + REQUIRE(cn3->root() == cn3); + } + } + } + SECTION("auto_remove/remove_all_children") { + { + auto p = node::create(); + auto n = node::create(p); + n.reset(); + REQUIRE(p->child_count() == 0); + } + { + auto p = node::create(); + auto n1 = node::create(p); + auto n2 = node::create(p); + REQUIRE(p->child_count() == 2); + REQUIRE(p->remove_all_children() == 2); + REQUIRE_FALSE(n1->parent()); + REQUIRE_FALSE(n2->parent()); + REQUIRE(p->child_count() == 0); + REQUIRE(p->remove_all_children() == 0); + } + } + SECTION("remove_from_parent") { + { + auto p = node::create(); + auto n1 = node::create(p); + auto n2 = node::create(p); + + auto np = node::create(); + np->remove_from_parent(); + REQUIRE(np->root() == np); + REQUIRE_FALSE(np->parent()); + + REQUIRE(p->child_count() == 2); + REQUIRE(n1->parent() == p); + REQUIRE(n2->parent() == p); + + REQUIRE(n1->remove_from_parent()); + REQUIRE_FALSE(n1->parent()); + REQUIRE(p->child_count() == 1); + + REQUIRE(n2->remove_from_parent()); + REQUIRE_FALSE(n2->parent()); + REQUIRE(p->child_count() == 0); + } + } + SECTION("child_count/child_count_recursive") { + auto p1 = node::create(); + REQUIRE(p1->child_count() == 0); + REQUIRE(p1->child_count_recursive() == 0); + + auto p2 = node::create(p1); + REQUIRE(p1->child_count() == 1); + REQUIRE(p1->child_count_recursive() == 1); + REQUIRE(p2->child_count() == 0); + REQUIRE(p2->child_count_recursive() == 0); + + auto n1 = node::create(p2); + auto n2 = node::create(p2); + REQUIRE(p1->child_count() == 1); + REQUIRE(p1->child_count_recursive() == 3); + REQUIRE(p2->child_count() == 2); + REQUIRE(p2->child_count_recursive() == 2); + } + SECTION("send_backward/bring_to_back") { + { + auto p = node::create(); + auto n1 = node::create(p); + auto n2 = node::create(p); + auto n3 = node::create(p); + + REQUIRE(n3->send_backward()); // n1 n3 n2 + REQUIRE(n1->next_sibling() == n3); + + REQUIRE(n3->send_backward()); // n3 n1 n2 + REQUIRE(n3->next_sibling() == n1); + + REQUIRE_FALSE(n3->send_backward()); // n3 n1 n2 + REQUIRE(n3->next_sibling() == n1); + + REQUIRE(n2->bring_to_back()); // n2 n3 n1 + REQUIRE(n2->next_sibling() == n3); + + REQUIRE_FALSE(n2->bring_to_back()); // n2 n3 n1 + REQUIRE(n2->next_sibling() == n3); + } + { + auto n = node::create(); + REQUIRE_FALSE(n->send_backward()); + REQUIRE_FALSE(n->bring_to_back()); + } + } + SECTION("send_forward/bring_to_front") { + { + auto p = node::create(); + auto n1 = node::create(p); + auto n2 = node::create(p); + auto n3 = node::create(p); + + REQUIRE(n1->send_forward()); // n2 n1 n3 + REQUIRE(n2->next_sibling() == n1); + + REQUIRE(n1->send_forward()); // n2 n3 n1 + REQUIRE(n3->next_sibling() == n1); + + REQUIRE_FALSE(n1->send_forward()); // n2 n3 n1 + REQUIRE(n3->next_sibling() == n1); + + REQUIRE(n2->bring_to_front()); // n3 n1 n2 + REQUIRE(n1->next_sibling() == n2); + + REQUIRE_FALSE(n2->bring_to_front()); // n3 n1 n2 + REQUIRE(n1->next_sibling() == n2); + } + { + auto n = node::create(); + REQUIRE_FALSE(n->send_forward()); + REQUIRE_FALSE(n->bring_to_back()); + } + } + SECTION("last_child/first_child") { + auto p = node::create(); + auto n1 = node::create(p); + auto n2 = node::create(p); + auto n3 = node::create(p); + + REQUIRE(p->last_child() == n3); + REQUIRE(p->first_child() == n1); + + { + const_node_iptr cp = p; + + REQUIRE(cp->last_child() == n3); + REQUIRE(cp->first_child() == n1); + } + } + SECTION("prev_sibling/next_sibling") { + auto p = node::create(); + + auto n1 = node::create(p); + auto n2 = node::create(p); + auto n3 = node::create(p); + + REQUIRE_FALSE(n1->prev_sibling()); + REQUIRE(n1->next_sibling() == n2); + + REQUIRE(n2->prev_sibling() == n1); + REQUIRE(n2->next_sibling() == n3); + + REQUIRE(n3->prev_sibling() == n2); + REQUIRE_FALSE(n3->next_sibling()); + + auto n4 = node::create(); + REQUIRE_FALSE(n4->prev_sibling()); + REQUIRE_FALSE(n4->next_sibling()); + + { + const_node_iptr cn1 = n1; + const_node_iptr cn2 = n2; + const_node_iptr cn3 = n3; + + REQUIRE_FALSE(cn1->prev_sibling()); + REQUIRE(cn1->next_sibling() == cn2); + + REQUIRE(cn2->prev_sibling() == cn1); + REQUIRE(cn2->next_sibling() == cn3); + + REQUIRE(cn3->prev_sibling() == cn2); + REQUIRE_FALSE(cn3->next_sibling()); + + const_node_iptr cn4 = n4; + REQUIRE_FALSE(cn4->prev_sibling()); + REQUIRE_FALSE(cn4->next_sibling()); + } + } + SECTION("add_child_to_back/add_child_to_front") { + auto p = node::create(); + auto n1 = node::create(); + auto n2 = node::create(); + auto n3 = node::create(); + { + p->remove_all_children(); + REQUIRE(p->add_child_to_front(n1)); + REQUIRE(p->add_child_to_front(n2)); + REQUIRE(p->add_child_to_front(n3)); + REQUIRE_FALSE(p->add_child_to_front(nullptr)); + + REQUIRE(n1->next_sibling() == n2); + REQUIRE(n2->next_sibling() == n3); + REQUIRE_FALSE(n3->next_sibling()); + } + { + p->remove_all_children(); + REQUIRE(p->add_child_to_back(n1)); + REQUIRE(p->add_child_to_back(n2)); + REQUIRE(p->add_child_to_back(n3)); + REQUIRE_FALSE(p->add_child_to_back(nullptr)); + + REQUIRE(n1->prev_sibling() == n2); + REQUIRE(n2->prev_sibling() == n3); + REQUIRE_FALSE(n3->prev_sibling()); + } + } + SECTION("add_child_after/add_child_before") { + auto p = node::create(); + auto n1 = node::create(); + auto n2 = node::create(); + auto n3 = node::create(); + { + p->remove_all_children(); + REQUIRE(p->add_child(n1)); // n1 + + REQUIRE(p->add_child_before(n1, n2)); // n2 n1 + REQUIRE(p->add_child_before(n1, n3)); // n2 n3 n1 + REQUIRE_FALSE(p->add_child_before(n1, nullptr)); + REQUIRE_FALSE(p->add_child_before(p, n3)); + + REQUIRE(n2->next_sibling() == n3); + REQUIRE(n3->next_sibling() == n1); + + auto n4 = node::create(); + REQUIRE(p->add_child_before(nullptr, n4)); // n4 n2 n3 n1 + REQUIRE(n4->next_sibling() == n2); + + REQUIRE(n1->parent() == p); + REQUIRE(n2->parent() == p); + REQUIRE(n3->parent() == p); + REQUIRE(n4->parent() == p); + } + { + p->remove_all_children(); + REQUIRE(p->add_child(n1)); // n1 + + REQUIRE(p->add_child_after(n1, n2)); // n1 n2 + REQUIRE(p->add_child_after(n1, n3)); // n1 n3 n2 + REQUIRE_FALSE(p->add_child_after(n1, nullptr)); + REQUIRE_FALSE(p->add_child_after(p, n3)); + + REQUIRE(n1->next_sibling() == n3); + REQUIRE(n3->next_sibling() == n2); + + auto n4 = node::create(); + REQUIRE(p->add_child_after(nullptr, n4)); // n1 n3 n2 n4 + REQUIRE(n2->next_sibling() == n4); + + REQUIRE(n1->parent() == p); + REQUIRE(n2->parent() == p); + REQUIRE(n3->parent() == p); + REQUIRE(n4->parent() == p); + } + } + SECTION("for_each_child") { + auto p = node::create(); + array ns{ + node::create(p), + node::create(p), + node::create(p)}; + { + std::size_t count = 0; + p->for_each_child([&ns, &count](const node_iptr& n){ + REQUIRE(ns[count++] == n); + }); + REQUIRE(count == 3); + } + { + const_node_iptr cp = p; + std::size_t count = 0; + cp->for_each_child([&ns, &count](const const_node_iptr& n){ + REQUIRE(ns[count++] == n); + }); + REQUIRE(count == 3); + } + } + SECTION("remove_child_if") { + auto p = node::create(); + auto n1 = node::create(p); + auto n2 = node::create(p); + auto n3 = node::create(p); + auto n4 = node::create(p); + + std::size_t num = 0; + std::size_t count = p->remove_child_if([&num](const node_iptr&){ + return (num++) % 2; + }); + + REQUIRE(count == 2); + REQUIRE(p->child_count() == 2); + REQUIRE(n1->parent() == p); + REQUIRE_FALSE(n2->parent()); + REQUIRE(n3->parent() == p); + REQUIRE_FALSE(n4->parent()); + } + SECTION("destroy_node") { + auto p1 = node::create(); + auto p2 = node::create(p1); + auto n1 = node::create(p2); + auto n2 = node::create(p2); + + p2.reset(); + + REQUIRE(p1->child_count() == 0); + REQUIRE(p1->child_count_recursive() == 0); + + REQUIRE(n1->root() == n1); + REQUIRE_FALSE(n1->parent()); + REQUIRE_FALSE(n1->prev_sibling()); + REQUIRE_FALSE(n1->next_sibling()); + + REQUIRE(n2->root() == n2); + REQUIRE_FALSE(n2->parent()); + REQUIRE_FALSE(n2->prev_sibling()); + REQUIRE_FALSE(n2->next_sibling()); + } +} + +TEST_CASE("scene") { + scene_iptr s = scene::create(); + REQUIRE(s); + REQUIRE(s->root()); +}