diff --git a/TODO b/TODO index 3bfbbc2bf..03656fab2 100644 --- a/TODO +++ b/TODO @@ -4,7 +4,8 @@ * add examples (and credits) from @alanjfs :) WIP: -* dense map/set: support uses-allocator construction (single arg, multi-arg, key construction) +* uses-allocator construction: dense set (review), dense map, compressed pair, any (with allocator support), cache, dispatcher, poly, ... +* process scheduler: reviews, use free lists internally * runtime events (emitter) * iterator based try_emplace vs try_insert for perf reasons * registry: remove reference to basic_sparse_set diff --git a/src/entt/process/scheduler.hpp b/src/entt/process/scheduler.hpp index 125a8f207..82b215c69 100644 --- a/src/entt/process/scheduler.hpp +++ b/src/entt/process/scheduler.hpp @@ -42,8 +42,8 @@ template class scheduler { struct process_handler { using instance_type = std::unique_ptr; - using update_fn_type = bool(process_handler &, Delta, void *); - using abort_fn_type = void(process_handler &, bool); + using update_fn_type = bool(scheduler &, std::size_t, Delta, void *); + using abort_fn_type = void(scheduler &, std::size_t, bool); using next_type = std::unique_ptr; instance_type instance; @@ -75,17 +75,17 @@ class scheduler { }; template - [[nodiscard]] static bool update(process_handler &handler, const Delta delta, void *data) { - auto *process = static_cast(handler.instance.get()); + [[nodiscard]] static bool update(scheduler &owner, std::size_t pos, const Delta delta, void *data) { + auto *process = static_cast(owner.handlers[pos].instance.get()); process->tick(delta, data); if(process->rejected()) { return true; } else if(process->finished()) { - if(handler.next) { + if(auto &&handler = owner.handlers[pos]; handler.next) { handler = std::move(*handler.next); // forces the process to exit the uninitialized state - return handler.update(handler, {}, nullptr); + return handler.update(owner, pos, {}, nullptr); } return true; @@ -95,8 +95,8 @@ class scheduler { } template - static void abort(process_handler &handler, const bool immediately) { - static_cast(handler.instance.get())->abort(immediately); + static void abort(scheduler &owner, std::size_t pos, const bool immediately) { + static_cast(owner.handlers[pos].instance.get())->abort(immediately); } template @@ -172,10 +172,10 @@ public: auto attach(Args &&...args) { static_assert(std::is_base_of_v, Proc>, "Invalid process type"); auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; - process_handler handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}; + auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); // forces the process to exit the uninitialized state - handler.update(handler, {}, nullptr); - return continuation{&handlers.emplace_back(std::move(handler))}; + ref.update(*this, handlers.size() - 1u, {}, nullptr); + return continuation{&handlers.back()}; } /** @@ -247,17 +247,14 @@ public: * @param data Optional data. */ void update(const Delta delta, void *data = nullptr) { - auto sz = handlers.size(); - for(auto pos = handlers.size(); pos; --pos) { - auto &handler = handlers[pos - 1]; + const auto curr = pos - 1u; - if(const auto dead = handler.update(handler, delta, data); dead) { - std::swap(handler, handlers[--sz]); + if(const auto dead = handlers[curr].update(*this, curr, delta, data); dead) { + std::swap(handlers[curr], handlers.back()); + handlers.pop_back(); } } - - handlers.erase(handlers.begin() + sz, handlers.end()); } /** @@ -271,15 +268,10 @@ public: * @param immediately Requests an immediate operation. */ void abort(const bool immediately = false) { - decltype(handlers) exec; - exec.swap(handlers); - - for(auto &&handler: exec) { - handler.abort(handler, immediately); + for(auto pos = handlers.size(); pos; --pos) { + const auto curr = pos - 1u; + handlers[curr].abort(*this, curr, immediately); } - - std::move(handlers.begin(), handlers.end(), std::back_inserter(exec)); - handlers.swap(exec); } private: diff --git a/test/entt/process/scheduler.cpp b/test/entt/process/scheduler.cpp index 0febf87e4..c08c44ca5 100644 --- a/test/entt/process/scheduler.cpp +++ b/test/entt/process/scheduler.cpp @@ -22,29 +22,30 @@ struct foo_process: entt::process { struct succeeded_process: entt::process { void update(delta_type, void *) { - ASSERT_FALSE(updated); - updated = true; ++invoked; succeed(); } - static unsigned int invoked; - bool updated = false; + static inline unsigned int invoked; }; -unsigned int succeeded_process::invoked = 0; - struct failed_process: entt::process { void update(delta_type, void *) { - ASSERT_FALSE(updated); - updated = true; + ++invoked; fail(); } - bool updated = false; + static inline unsigned int invoked; }; -TEST(Scheduler, Functionalities) { +struct Scheduler: ::testing::Test { + void SetUp() override { + succeeded_process::invoked = 0u; + failed_process::invoked = 0u; + } +}; + +TEST_F(Scheduler, Functionalities) { entt::scheduler scheduler{}; bool updated = false; @@ -75,7 +76,7 @@ TEST(Scheduler, Functionalities) { ASSERT_TRUE(scheduler.empty()); } -TEST(Scheduler, Then) { +TEST_F(Scheduler, Then) { entt::scheduler scheduler; // failing process with successor @@ -93,15 +94,18 @@ TEST(Scheduler, Then) { scheduler.attach() .then(); - for(auto i = 0; i < 8; ++i) { + ASSERT_EQ(succeeded_process::invoked, 0u); + ASSERT_EQ(failed_process::invoked, 0u); + + while(!scheduler.empty()) { scheduler.update(0); } ASSERT_EQ(succeeded_process::invoked, 6u); - ASSERT_TRUE(scheduler.empty()); + ASSERT_EQ(failed_process::invoked, 2u); } -TEST(Scheduler, Functor) { +TEST_F(Scheduler, Functor) { entt::scheduler scheduler; bool first_functor = false; @@ -121,7 +125,7 @@ TEST(Scheduler, Functor) { scheduler.attach(std::move(attach)).then(std::move(then)).then([](auto...) { FAIL(); }); - for(auto i = 0; i < 8; ++i) { + while(!scheduler.empty()) { scheduler.update(0); } @@ -129,3 +133,22 @@ TEST(Scheduler, Functor) { ASSERT_TRUE(second_functor); ASSERT_TRUE(scheduler.empty()); } + +TEST_F(Scheduler, SpawningProcess) { + entt::scheduler scheduler; + + scheduler.attach([&scheduler](auto, void *, auto resolve, auto) { + scheduler.attach().then(); + resolve(); + }); + + ASSERT_EQ(succeeded_process::invoked, 0u); + ASSERT_EQ(failed_process::invoked, 0u); + + while(!scheduler.empty()) { + scheduler.update(0); + } + + ASSERT_EQ(succeeded_process::invoked, 1u); + ASSERT_EQ(failed_process::invoked, 1u); +}