From dcb62437ca89b7be22feafb877272c8f30e26877 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 17 Oct 2019 11:18:41 +0700 Subject: [PATCH] features and events instead system priorities --- README.md | 96 ++++++++++---- headers/ecs.hpp/ecs.hpp | 237 +++++++++++++++++++++++---------- untests/ecs_readme.cpp | 121 +++++++++++++++++ untests/ecs_tests.cpp | 281 +++++++++++++++++++--------------------- 4 files changed, 490 insertions(+), 245 deletions(-) create mode 100644 untests/ecs_readme.cpp diff --git a/README.md b/README.md index 4af9d6a..55c7228 100644 --- a/README.md +++ b/README.md @@ -41,73 +41,113 @@ target_link_libraries(your_project_target ecs.hpp) ## Basic usage ```cpp - #include namespace ecs = ecs_hpp; +// events + +struct update_event { + float dt{}; +}; + +struct render_event { + std::string camera; +}; + +// components + struct movable {}; struct disabled {}; +struct sprite { + std::string name; +}; + struct position { float x{}; float y{}; }; struct velocity { - float dx{}; - float dy{}; + float x{}; + float y{}; }; -class movement_system : public ecs::system { -public: - void process(ecs::registry& owner) override { - owner.for_joined_components< - position, - velocity - >([](ecs::entity, position& p, const velocity& v) { - p.x += v.dx; - p.y += v.dy; - }, ecs::exists{} && !ecs::exists{}); - } -}; +// systems -class gravity_system : public ecs::system { +class gravity_system : public ecs::system { public: gravity_system(float gravity) : gravity_(gravity) {} - void process(ecs::registry& owner) override { - owner.for_each_component< - velocity - >([this](ecs::entity e, velocity& v) { - v.dx += gravity_; - v.dy += gravity_; - }, !ecs::exists{}); + void process(ecs::registry& world, const update_event& evt) override { + world.for_each_component( + [this, &evt](ecs::entity, velocity& vel) { + vel.x += gravity_ * evt.dt; + vel.y += gravity_ * evt.dt; + }, ecs::exists{} && !ecs::exists{}); } private: float gravity_{}; }; +class movement_system : public ecs::system { +public: + void process(ecs::registry& world, const update_event& evt) override { + world.for_joined_components( + [&evt](ecs::entity, position& pos, const velocity& vel) { + pos.x += vel.x * evt.dt; + pos.y += vel.y * evt.dt; + }, ecs::exists{} && !ecs::exists{}); + } +}; + +class render_system : public ecs::system { +public: + void process(ecs::registry& world, const render_event& evt) override { + world.for_joined_components( + [&evt](ecs::entity, const sprite& s, const position& p) { + std::cout << "Render sprite:" << std::endl; + std::cout << "--> pos: " << p.x << "," << p.y << std::endl; + std::cout << "--> sprite: " << s.name << std::endl; + std::cout << "--> camera: " << evt.camera << std::endl; + }, !ecs::exists{}); + } +}; + +// world + ecs::registry world; -ecs::registry_filler(world) - .system(0) - .system(1, 9.8f); +struct physics_feature {}; +world.assign_feature() + .add_system() + .add_system(9.8f); + +struct rendering_feature {}; +world.assign_feature() + .add_system(); + +// entities auto entity_one = world.create_entity(); ecs::entity_filler(entity_one) .component() + .component("ship") .component(4.f, 2.f) .component(10.f, 20.f); auto entity_two = world.create_entity(); ecs::entity_filler(entity_two) .component() - .component() + .component("player") .component(4.f, 2.f) .component(10.f, 20.f); -world.process_all_systems(); +// processing + +world.process_event(update_event{0.1f}); +world.process_event(render_event{"main"}); ``` ## API diff --git a/headers/ecs.hpp/ecs.hpp b/headers/ecs.hpp/ecs.hpp index 1ccccd8..4305a36 100644 --- a/headers/ecs.hpp/ecs.hpp +++ b/headers/ecs.hpp/ecs.hpp @@ -39,7 +39,9 @@ namespace ecs_hpp class prototype; + template < typename Event > class system; + class feature; class registry; template < typename T > @@ -61,14 +63,12 @@ namespace ecs_hpp class aspect; class entity_filler; - class registry_filler; } namespace ecs_hpp { using family_id = std::uint16_t; using entity_id = std::uint32_t; - using priority_t = std::int32_t; constexpr std::size_t entity_id_index_bits = 22u; constexpr std::size_t entity_id_version_bits = 10u; @@ -1244,10 +1244,55 @@ namespace ecs_hpp namespace ecs_hpp { - class system { + namespace detail + { + class system_base { + public: + virtual ~system_base() = default; + }; + } + + template < typename Event > + class system : public detail::system_base { public: - virtual ~system() = default; - virtual void process(registry& owner) = 0; + using event_type = Event; + virtual void process(registry& owner, const Event& event) = 0; + }; +} + +// ----------------------------------------------------------------------------- +// +// feature +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + class feature final { + public: + feature() = default; + + feature(const feature&) = delete; + feature& operator=(const feature&) = delete; + + feature(feature&&) noexcept = default; + feature& operator=(feature&&) noexcept = default; + + feature& enable() noexcept; + feature& disable() noexcept; + + bool is_enabled() const noexcept; + bool is_disabled() const noexcept; + + template < typename T, typename... Args > + feature& add_system(Args&&... args); + template < typename Event > + feature& process_event(registry& owner, const Event& event); + private: + bool disabled_{false}; + mutable detail::incremental_locker base_systems_locker_; + using system_uptr = std::unique_ptr; + std::vector> base_systems_; }; } @@ -1383,13 +1428,23 @@ namespace ecs_hpp template < typename... Ts, typename F, typename... Opts > void for_joined_components(F&& f, Opts&&... opts) const; - template < typename T, typename... Args > - void add_system(priority_t priority, Args&&... args); + template < typename Tag, typename... Args > + feature& assign_feature(Args&&... args); - void process_all_systems(); - void process_systems_above(priority_t min); - void process_systems_below(priority_t max); - void process_systems_in_range(priority_t min, priority_t max); + template < typename Tag, typename... Args > + feature& ensure_feature(Args&&... args); + + template < typename Tag > + bool has_feature() const noexcept; + + template < typename Tag > + feature& get_feature(); + + template < typename Tag > + const feature& get_feature() const; + + template < typename Event > + registry& process_event(const Event& event); struct memory_usage_info { std::size_t entities{0u}; @@ -1481,14 +1536,15 @@ namespace ecs_hpp private: entity_id last_entity_id_{0u}; std::vector free_entity_ids_; + mutable detail::incremental_locker entity_ids_locker_; detail::sparse_set entity_ids_; using storage_uptr = std::unique_ptr; detail::sparse_map storages_; - using system_uptr = std::unique_ptr; - std::vector> systems_; + mutable detail::incremental_locker features_locker_; + detail::sparse_map features_; }; } @@ -1729,22 +1785,6 @@ namespace ecs_hpp private: entity& entity_; }; - - class registry_filler final { - public: - registry_filler(registry& registry) noexcept - : registry_(registry) {} - - template < typename T, typename... Args > - registry_filler& system(priority_t priority, Args&&... args) { - registry_.add_system( - priority, - std::forward(args)...); - return *this; - } - private: - registry& registry_; - }; } // ----------------------------------------------------------------------------- @@ -2310,6 +2350,55 @@ namespace ecs_hpp } } +// ----------------------------------------------------------------------------- +// +// feature impl +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + inline feature& feature::enable() noexcept { + disabled_ = false; + return *this; + } + + inline feature& feature::disable() noexcept { + disabled_ = true; + return *this; + } + + inline bool feature::is_enabled() const noexcept { + return !disabled_; + } + + inline bool feature::is_disabled() const noexcept { + return disabled_; + } + + template < typename T, typename... Args > + feature& feature::add_system(Args&&... args) { + assert(!base_systems_locker_.is_locked()); + base_systems_.push_back({ + detail::type_family::id(), + std::make_unique(std::forward(args)...)}); + return *this; + } + + template < typename Event > + feature& feature::process_event(registry& owner, const Event& event) { + const auto event_id = detail::type_family::id(); + detail::incremental_lock_guard lock(base_systems_locker_); + for ( const auto& [system_event_id, base_system] : base_systems_ ) { + if ( event_id == system_event_id ) { + auto system_ptr = static_cast*>(base_system.get()); + system_ptr->process(owner, event); + } + } + return *this; + } +} + // ----------------------------------------------------------------------------- // // registry impl @@ -2691,45 +2780,59 @@ namespace ecs_hpp std::forward(opts)...); } - template < typename T, typename... Args > - void registry::add_system(priority_t priority, Args&&... args) { - auto iter = std::upper_bound( - systems_.begin(), systems_.end(), priority, - [](priority_t pr, const auto& r){ - return pr < r.first; - }); - systems_.insert( - iter, - std::make_pair(priority, std::make_unique(std::forward(args)...))); - } - - inline void registry::process_all_systems() { - process_systems_in_range( - std::numeric_limits::min(), - std::numeric_limits::max()); - } - - inline void registry::process_systems_above(priority_t min) { - process_systems_in_range( - min, - std::numeric_limits::max()); - } - - inline void registry::process_systems_below(priority_t max) { - process_systems_in_range( - std::numeric_limits::min(), - max); - } - - inline void registry::process_systems_in_range(priority_t min, priority_t max) { - const auto first = std::lower_bound( - systems_.begin(), systems_.end(), min, - [](const auto& p, priority_t pr) noexcept { - return p.first < pr; - }); - for ( auto iter = first; iter != systems_.end() && iter->first <= max; ++iter ) { - iter->second->process(*this); + template < typename Tag, typename... Args > + feature& registry::assign_feature(Args&&... args) { + const auto feature_id = detail::type_family::id(); + if ( feature* f = features_.find(feature_id) ) { + return *f = feature{std::forward(args)...}; } + assert(!features_locker_.is_locked()); + return *features_.insert(feature_id, feature()).first; + } + + template < typename Tag, typename... Args > + feature& registry::ensure_feature(Args&&... args) { + const auto feature_id = detail::type_family::id(); + if ( feature* f = features_.find(feature_id) ) { + return *f; + } + assert(!features_locker_.is_locked()); + return *features_.insert(feature_id, feature{std::forward(args)...}).first; + } + + template < typename Tag > + bool registry::has_feature() const noexcept { + const auto feature_id = detail::type_family::id(); + return features_.has(feature_id); + } + + template < typename Tag > + feature& registry::get_feature() { + const auto feature_id = detail::type_family::id(); + if ( feature* f = features_.find(feature_id) ) { + return *f; + } + throw std::logic_error("ecs_hpp::registry (feature not found)"); + } + + template < typename Tag > + const feature& registry::get_feature() const { + const auto feature_id = detail::type_family::id(); + if ( const feature* f = features_.find(feature_id) ) { + return *f; + } + throw std::logic_error("ecs_hpp::registry (feature not found)"); + } + + template < typename Event > + registry& registry::process_event(const Event& event) { + detail::incremental_lock_guard lock(features_locker_); + for ( const auto family : features_ ) { + if ( feature& f = features_.get(family); f.is_enabled() ) { + f.process_event(*this, event); + } + } + return *this; } inline registry::memory_usage_info registry::memory_usage() const noexcept { diff --git a/untests/ecs_readme.cpp b/untests/ecs_readme.cpp new file mode 100644 index 0000000..8a5dcd4 --- /dev/null +++ b/untests/ecs_readme.cpp @@ -0,0 +1,121 @@ +/******************************************************************************* + * This file is part of the "https://github.com/blackmatov/ecs.hpp" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#include + +#include +namespace ecs = ecs_hpp; + +#include +#include + +TEST_CASE("example") { + + // events + + struct update_event { + float dt{}; + }; + + struct render_event { + std::string camera; + }; + + // components + + struct movable {}; + struct disabled {}; + + struct sprite { + std::string name; + }; + + struct position { + float x{}; + float y{}; + }; + + struct velocity { + float x{}; + float y{}; + }; + + // systems + + class gravity_system : public ecs::system { + public: + gravity_system(float gravity) + : gravity_(gravity) {} + + void process(ecs::registry& world, const update_event& evt) override { + world.for_each_component( + [this, &evt](ecs::entity, velocity& vel) { + vel.x += gravity_ * evt.dt; + vel.y += gravity_ * evt.dt; + }, ecs::exists{} && !ecs::exists{}); + } + private: + float gravity_{}; + }; + + class movement_system : public ecs::system { + public: + void process(ecs::registry& world, const update_event& evt) override { + world.for_joined_components( + [&evt](ecs::entity, position& pos, const velocity& vel) { + pos.x += vel.x * evt.dt; + pos.y += vel.y * evt.dt; + }, ecs::exists{} && !ecs::exists{}); + } + }; + + class render_system : public ecs::system { + public: + void process(ecs::registry& world, const render_event& evt) override { + world.for_joined_components( + [&evt](ecs::entity, const sprite& s, const position& p) { + std::cout << "Render sprite:" << std::endl; + std::cout << "--> pos: " << p.x << "," << p.y << std::endl; + std::cout << "--> sprite: " << s.name << std::endl; + std::cout << "--> camera: " << evt.camera << std::endl; + }, !ecs::exists{}); + } + }; + + // world + + ecs::registry world; + + struct physics_feature {}; + world.assign_feature() + .add_system() + .add_system(9.8f); + + struct rendering_feature {}; + world.assign_feature() + .add_system(); + + // entities + + auto entity_one = world.create_entity(); + ecs::entity_filler(entity_one) + .component() + .component("ship") + .component(4.f, 2.f) + .component(10.f, 20.f); + + auto entity_two = world.create_entity(); + ecs::entity_filler(entity_two) + .component() + .component("player") + .component(4.f, 2.f) + .component(10.f, 20.f); + + // processing + + world.process_event(update_event{0.1f}); + world.process_event(render_event{"main"}); +} diff --git a/untests/ecs_tests.cpp b/untests/ecs_tests.cpp index ec9addc..7757e83 100644 --- a/untests/ecs_tests.cpp +++ b/untests/ecs_tests.cpp @@ -60,14 +60,19 @@ namespace TEST_CASE("detail") { SECTION("get_type_id") { using namespace ecs::detail; - REQUIRE(type_family::id() == 1u); - REQUIRE(type_family::id() == 1u); - REQUIRE(type_family::id() == 2u); - REQUIRE(type_family::id() == 2u); + const auto p_id = type_family::id(); + REQUIRE(p_id == type_family::id()); - REQUIRE(type_family::id() == 1u); - REQUIRE(type_family::id() == 2u); + const auto v_id = type_family::id(); + REQUIRE(v_id == type_family::id()); + + REQUIRE(type_family::id() == type_family::id()); + REQUIRE(type_family::id() == type_family::id()); + REQUIRE_FALSE(type_family::id() == type_family::id()); + + REQUIRE(p_id == type_family::id()); + REQUIRE(v_id == type_family::id()); } SECTION("tuple_tail") { using namespace ecs::detail; @@ -1463,88 +1468,128 @@ TEST_CASE("registry") { } } SECTION("systems") { - { - class movement_system : public ecs::system { - public: - void process(ecs::registry& owner) override { - owner.for_joined_components([]( - ecs::entity, position_c& p, const velocity_c& v) - { - p.x += v.x; - p.y += v.y; - }); - } - }; + struct update_evt { + int dt{}; + }; - ecs::registry w; - w.add_system(0); + class gravity_system : public ecs::system { + public: + gravity_system(int g) + : g_(g) {} - auto e1 = w.create_entity(); - auto e2 = w.create_entity(); + void process(ecs::registry& owner, const update_evt& evt) override { + owner.for_each_component< + velocity_c + >([this, &evt](ecs::entity, velocity_c& v) { + v.x += g_ * evt.dt; + v.y += g_ * evt.dt; + }, !ecs::exists{}); + } + private: + int g_{}; + }; - e1.assign_component(1, 2); - e1.assign_component(3, 4); - e2.assign_component(5, 6); - e2.assign_component(7, 8); + class movement_system : public ecs::system { + public: + void process(ecs::registry& owner, const update_evt& evt) override { + owner.for_joined_components([&evt]( + ecs::entity, position_c& p, const velocity_c& v) + { + p.x += v.x * evt.dt; + p.y += v.y * evt.dt; + }, !ecs::exists{}); + } + }; - w.process_all_systems(); + ecs::registry w; + REQUIRE_FALSE(w.has_feature()); - REQUIRE(e1.get_component().x == 1 + 3); - REQUIRE(e1.get_component().y == 2 + 4); + w.assign_feature() + .add_system(9); - REQUIRE(e2.get_component().x == 5 + 7); - REQUIRE(e2.get_component().y == 6 + 8); - } - { - class system_n : public ecs::system { - public: - system_n(int& i, int n) : i_(i), n_(n) {} - void process(ecs::registry&) override { - i_ += n_; - } - private: - int& i_; - int n_; - }; + REQUIRE(w.has_feature()); - int i = 0; - ecs::registry w; - w.add_system(20, std::ref(i), 2); - w.add_system(10, std::ref(i), 1); - REQUIRE(i == 0); - w.process_all_systems(); - REQUIRE(i == 3); - w.process_systems_below(10); - REQUIRE(i == 4); - w.process_systems_above(20); - REQUIRE(i == 6); - w.process_systems_below(20); - REQUIRE(i == 9); - w.process_systems_above(10); - REQUIRE(i == 12); + w.ensure_feature() + .add_system(); - w.process_systems_below(9); - w.process_systems_above(21); - REQUIRE(i == 12); + REQUIRE(w.has_feature()); - w.process_systems_in_range(0, 9); - w.process_systems_in_range(11, 19); - w.process_systems_in_range(21, 30); - REQUIRE(i == 12); + ecs::entity e = w.create_entity(); + e.assign_component(1,2); + e.assign_component(3,4); - w.process_systems_in_range(0, 10); - REQUIRE(i == 13); - w.process_systems_in_range(10, 19); - REQUIRE(i == 14); - w.process_systems_in_range(10, 20); - REQUIRE(i == 17); - w.process_systems_in_range(20, 30); - REQUIRE(i == 19); - w.process_systems_in_range(10, 20); - REQUIRE(i == 22); - w.process_systems_in_range(0, 30); - REQUIRE(i == 25); - } + w.get_feature().disable(); + w.process_event(update_evt{2}); + + REQUIRE(e.get_component().x == 1); + REQUIRE(e.get_component().y == 2); + + w.get_feature().enable(); + w.process_event(update_evt{2}); + + REQUIRE(e.get_component().x == 1 + (3 + 9 * 2) * 2); + REQUIRE(e.get_component().y == 2 + (4 + 9 * 2) * 2); + } + SECTION("recursive_systems") { + struct update_evt { + int dt{}; + }; + + struct physics_evt { + update_evt parent_evt{}; + }; + + class gravity_system : public ecs::system { + public: + gravity_system(int g) + : g_(g) {} + + void process(ecs::registry& owner, const physics_evt& evt) override { + owner.for_each_component< + velocity_c + >([this, &evt](ecs::entity, velocity_c& v) { + v.x += g_ * evt.parent_evt.dt; + v.y += g_ * evt.parent_evt.dt; + }, !ecs::exists{}); + } + private: + int g_{}; + }; + + class movement_system : public ecs::system { + public: + void process(ecs::registry& owner, const physics_evt& evt) override { + owner.for_joined_components([&evt]( + ecs::entity, position_c& p, const velocity_c& v) + { + p.x += v.x * evt.parent_evt.dt; + p.y += v.y * evt.parent_evt.dt; + }, !ecs::exists{}); + } + }; + + class physics_system : public ecs::system { + public: + void process(ecs::registry& owner, const update_evt& evt) override { + owner.process_event(physics_evt{evt}); + } + }; + + ecs::registry w; + + w.assign_feature() + .add_system(9) + .add_system() + .add_system(); + + ecs::entity e = w.create_entity(); + e.assign_component(1,2); + e.assign_component(3,4); + + w.process_event(update_evt{2}); + + REQUIRE(e.get_component().x == 1 + (3 + 9 * 2) * 2); + REQUIRE(e.get_component().y == 2 + (4 + 9 * 2) * 2); } SECTION("fillers") { struct component_n { @@ -1552,10 +1597,12 @@ TEST_CASE("registry") { component_n(int ni) : i(ni) {} }; - class system_n : public ecs::system { + struct update_evt {}; + + class system_n : public ecs::system { public: system_n(int n) : n_(n) {} - void process(ecs::registry& owner) override { + void process(ecs::registry& owner, const update_evt&) override { owner.for_each_component( [this](const ecs::const_entity&, component_n& c) noexcept { c.i += n_; @@ -1566,9 +1613,9 @@ TEST_CASE("registry") { }; { ecs::registry w; - ecs::registry_filler(w) - .system(0, 1) - .system(0, 2); + w.assign_feature() + .add_system(1) + .add_system(2); ecs::entity e1 = w.create_entity(); ecs::entity_filler(e1) @@ -1579,7 +1626,7 @@ TEST_CASE("registry") { ecs::entity_filler(e2) .component(2); - w.process_all_systems(); + w.process_event(update_evt{}); REQUIRE(e1.get_component().i == 4); REQUIRE(e2.get_component().i == 5); @@ -1682,69 +1729,3 @@ TEST_CASE("registry") { }); } } - -TEST_CASE("example") { - struct movable {}; - struct disabled {}; - - struct position { - float x{}; - float y{}; - }; - - struct velocity { - float dx{}; - float dy{}; - }; - - class movement_system : public ecs::system { - public: - void process(ecs::registry& owner) override { - owner.for_joined_components< - position, - velocity - >([](ecs::entity, position& p, const velocity& v) { - p.x += v.dx; - p.y += v.dy; - }, ecs::exists{} && !ecs::exists{}); - } - }; - - class gravity_system : public ecs::system { - public: - gravity_system(float gravity) - : gravity_(gravity) {} - - void process(ecs::registry& owner) override { - owner.for_each_component< - velocity - >([this](ecs::entity, velocity& v) { - v.dx += gravity_; - v.dy += gravity_; - }, !ecs::exists{}); - } - private: - float gravity_{}; - }; - - ecs::registry world; - - ecs::registry_filler(world) - .system(0) - .system(1, 9.8f); - - auto entity_one = world.create_entity(); - ecs::entity_filler(entity_one) - .component() - .component(4.f, 2.f) - .component(10.f, 20.f); - - auto entity_two = world.create_entity(); - ecs::entity_filler(entity_two) - .component() - .component() - .component(4.f, 2.f) - .component(10.f, 20.f); - - world.process_all_systems(); -}