diff --git a/core/include/cubos/core/ecs/cubos.hpp b/core/include/cubos/core/ecs/cubos.hpp index cb8d92a06c..feeb2c0d26 100644 --- a/core/include/cubos/core/ecs/cubos.hpp +++ b/core/include/cubos/core/ecs/cubos.hpp @@ -72,6 +72,11 @@ namespace cubos::core::ecs /// @return Reference to this object, for chaining. TagBuilder& after(const std::string& tag); + /// @brief Tags all systems on this tag with the given tag. + /// @param tag Tag to be inherited from. + /// @return Reference to this object, for chaining. + TagBuilder& tagged(const std::string& tag); + /// @brief Adds a condition to the current tag. If this condition returns false, systems /// with this tag will not be executed. For the tagged systems to run, all conditions must /// return true. @@ -80,6 +85,12 @@ namespace cubos::core::ecs template TagBuilder& runIf(F func); + /// @brief Makes all systems within the tag repeat as a group until the given condition evaluates to false. + /// @param func Condition function. + /// @return Reference to this object, for chaining. + template + TagBuilder& repeatWhile(F func); + private: World& mWorld; core::ecs::Dispatcher& mDispatcher; @@ -316,6 +327,13 @@ namespace cubos::core::ecs return *this; } + template + TagBuilder& TagBuilder::repeatWhile(F func) + { + mDispatcher.tagRepeatWhile(System::make(mWorld, std::move(func), {})); + return *this; + } + template Cubos& Cubos::addResource(TArgs... args) { diff --git a/core/include/cubos/core/ecs/system/dispatcher.hpp b/core/include/cubos/core/ecs/system/dispatcher.hpp index b9ec4f7f7e..52d60d9c34 100644 --- a/core/include/cubos/core/ecs/system/dispatcher.hpp +++ b/core/include/cubos/core/ecs/system/dispatcher.hpp @@ -78,6 +78,13 @@ namespace cubos::core::ecs /// @param condition Condition to add. void tagAddCondition(ecs::System condition); + /// @brief Adds a repeat condition to the current tag. + /// @param func Condition to add. + void tagRepeatWhile(ecs::System condition); + + /// @brief Adds a (sub)group to the current group. + void groupAddGroup(); + /// @brief Adds a system, and sets it as the current system for further configuration. /// @param system System to add. void addSystem(ecs::System system); @@ -86,6 +93,10 @@ namespace cubos::core::ecs /// @param tag Tag to run under. void systemAddTag(const std::string& tag); + /// @brief Links a system to a certain group. + /// @param grouptag Tag of the group. + void systemAddGroup(const std::string& grouptag); + /// @brief Sets the current system to run after the tag. /// @param tag Tag to run after. void systemSetAfterTag(const std::string& tag); @@ -108,7 +119,6 @@ namespace cubos::core::ecs /// @param cmds Command buffer. void callSystems(CommandBuffer& cmds); - private: struct Dependency; struct SystemSettings; struct System; @@ -137,8 +147,125 @@ namespace cubos::core::ecs std::shared_ptr settings; ecs::System system; std::unordered_set tags; + std::string groupTag = "main"; + }; + + class Step + { + public: + virtual ~Step() = default; + virtual void call(CommandBuffer& cmds, std::vector>& conditions, + std::bitset runConditions, + std::bitset retConditions) = 0; + }; + + class SystemStep : public Step + { + public: + SystemStep(System* system) + : mSystem(system) + { + } + void call(CommandBuffer& cmds, std::vector>& conditions, + std::bitset runConditions, + std::bitset retConditions); + + System* getSystem() + { + return mSystem; + } + + private: + System* mSystem; }; + class GroupStep : public Step, public std::enable_shared_from_this + { + public: + GroupStep(std::string grouptag, std::shared_ptr parentStep) + : groupTag(std::move(grouptag)) + , mParentStep(std::move(parentStep)) + { + } + void call(CommandBuffer& cmds, std::vector>& conditions, + std::bitset runConditions, + std::bitset retConditions) override; + + void addSystemStep(System* system) + { + mSteps.insert(mSteps.begin(), std::make_shared(system)); + } + + std::shared_ptr addGroupStep(const std::string& grouptag, + const std::shared_ptr& parent) + { + std::shared_ptr newStep = std::make_shared(grouptag, parent); + mSteps.insert(mSteps.begin(), newStep); + return std::dynamic_pointer_cast(mSteps[0]); + } + + void moveToFront(const std::shared_ptr& step) + { + auto it = std::find(mSteps.begin(), mSteps.end(), step); + if (it != mSteps.end()) + { + std::rotate(mSteps.begin(), it, it + 1); + } + } + + std::shared_ptr findGroup(const std::string& tag) + { + if (tag == groupTag) + { + return std::dynamic_pointer_cast(shared_from_this()); + } + + for (const auto& step : this->mSteps) + { + if (auto groupStepPtr = std::dynamic_pointer_cast(step)) + { + return groupStepPtr->findGroup(tag); + } + } + + return nullptr; + } + + void queueSystem(const std::string& grouptag, System* system) + { + if (grouptag == this->groupTag) + { + addSystemStep(system); + + if (this->groupTag != "main") + { + this->mParentStep->moveToFront(shared_from_this()); + } + return; + } + + for (const auto& step : this->mSteps) + { + if (auto groupStepPtr = std::dynamic_pointer_cast(step)) + { + groupStepPtr->queueSystem(grouptag, system); + } + } + } + + const std::string& getGroupTag() + { + return groupTag; + } + std::bitset conditions; + + private: + const std::string groupTag; + std::shared_ptr mParentStep; + std::vector> mSteps; + }; + + private: /// @brief Internal class used to implement a DFS algorithm for call chain compilation struct DFSNode { @@ -183,6 +310,8 @@ namespace cubos::core::ecs // Variables for holding information after call chain is compiled. std::vector mSystems; ///< Compiled order of running systems. + std::shared_ptr mMainStep = std::make_shared("main", nullptr); + std::shared_ptr mCurrGroup = mMainStep; }; inline void Dispatcher::tagAddCondition(ecs::System condition) @@ -192,6 +321,14 @@ namespace cubos::core::ecs mTagSettings[mCurrTag]->conditions |= bit; } + inline void Dispatcher::tagRepeatWhile(ecs::System condition) + { + ENSURE_CURR_TAG(); + groupAddGroup(); + auto bit = assignConditionBit(std::move(condition)); + mCurrGroup->conditions |= bit; + } + inline void Dispatcher::addSystem(ecs::System system) { // Wrap the system and put it in the pending queue diff --git a/core/src/ecs/cubos.cpp b/core/src/ecs/cubos.cpp index d16c112617..f1e7be093b 100644 --- a/core/src/ecs/cubos.cpp +++ b/core/src/ecs/cubos.cpp @@ -47,6 +47,12 @@ TagBuilder& TagBuilder::after(const std::string& tag) return *this; } +TagBuilder& TagBuilder::tagged(const std::string& tag) +{ + mDispatcher.tagInheritTag(tag); + return *this; +} + Cubos& Cubos::addPlugin(void (*func)(Cubos&)) { if (!mPlugins.contains(func)) diff --git a/core/src/ecs/system/dispatcher.cpp b/core/src/ecs/system/dispatcher.cpp index 1abebc20fa..9dd4fec5c3 100644 --- a/core/src/ecs/system/dispatcher.cpp +++ b/core/src/ecs/system/dispatcher.cpp @@ -28,6 +28,7 @@ void Dispatcher::addTag(const std::string& tag) { ENSURE_TAG_SETTINGS(tag); mCurrTag = tag; + mCurrGroup = mMainStep; } void Dispatcher::tagInheritTag(const std::string& tag) @@ -40,6 +41,11 @@ void Dispatcher::tagInheritTag(const std::string& tag) CUBOS_INFO("Tag already inherits from '{}'", tag); } mTagSettings[mCurrTag]->inherits.push_back(tag); + mCurrGroup = mMainStep->findGroup(tag); + if (mCurrGroup == nullptr) + { + mCurrGroup = mMainStep; + } } void Dispatcher::tagSetAfterTag(const std::string& tag) @@ -62,11 +68,29 @@ void Dispatcher::tagSetBeforeTag(const std::string& tag) mTagSettings[tag]->after.tag.push_back(mCurrTag); } +void Dispatcher::groupAddGroup() +{ + mCurrGroup = mCurrGroup->addGroupStep(mCurrTag, mCurrGroup); +} + void Dispatcher::systemAddTag(const std::string& tag) { ENSURE_CURR_SYSTEM(); ENSURE_TAG_SETTINGS(tag); mCurrSystem->tags.insert(tag); + std::shared_ptr group = mMainStep->findGroup(tag); + if (group != nullptr) + { + mCurrSystem->groupTag = tag; + mCurrGroup = group; + } +} + +void Dispatcher::systemAddGroup(const std::string& grouptag) +{ + ENSURE_CURR_SYSTEM(); + CUBOS_ASSERT(mCurrSystem->groupTag == "main", "Systems can only be tagged with at most one repeating tag"); + mCurrSystem->groupTag = grouptag; } void Dispatcher::systemSetAfterTag(const std::string& tag) @@ -220,7 +244,7 @@ bool Dispatcher::dfsVisit(DFSNode& node, std::vector& nodes) node.m = DFSNode::BLACK; if (node.s != nullptr) { - mSystems.push_back(node.s); + mMainStep->queueSystem(node.s->groupTag, node.s); } return false; } @@ -228,53 +252,106 @@ bool Dispatcher::dfsVisit(DFSNode& node, std::vector& nodes) return false; } -void Dispatcher::callSystems(CommandBuffer& cmds) +void Dispatcher::SystemStep::call(CommandBuffer& cmds, std::vector>& conditions, + std::bitset runConditions, + std::bitset retConditions) { - // Clear conditions bitmasks - mRunConditions.reset(); - mRetConditions.reset(); - - for (auto& system : mSystems) + bool canRun = true; + if (mSystem->settings != nullptr) { - // Query for conditions - bool canRun = true; - - if (system->settings != nullptr) + auto conditionsMask = mSystem->settings->conditions; + std::size_t i = 0; + while (conditionsMask.any()) { - auto conditionsMask = system->settings->conditions; - std::size_t i = 0; - while (conditionsMask.any()) + if (conditionsMask.test(0)) { - if (conditionsMask.test(0)) + // We have a condition, check if it has run already + if (!runConditions.test(i)) { - // We have a condition, check if it has run already - if (!mRunConditions.test(i)) - { - mRunConditions.set(i); - if (mConditions[i].run(cmds)) - { - mRetConditions.set(i); - } - } - // Check if the condition returned true - if (!mRetConditions.test(i)) + runConditions.set(i); + if (conditions[i].run(cmds)) { - canRun = false; - break; + retConditions.set(i); } } - - i += 1; - conditionsMask >>= 1; + // Check if the condition returned true + if (!retConditions.test(i)) + { + canRun = false; + break; + } } + + i += 1; + conditionsMask >>= 1; } + } + if (canRun) + { + mSystem->system.run(cmds); + } + + // TODO: Check synchronization concerns when this gets multithreaded + cmds.commit(); +} + +void Dispatcher::GroupStep::call(CommandBuffer& cmds, std::vector>& conditions, + std::bitset runConditions, + std::bitset retConditions) +{ + bool canRun = true; + while (canRun) + { + auto conditionsMask = this->conditions; + std::size_t i = 0; + while (conditionsMask.any()) + { + if (conditionsMask.test(0)) + { + // We have a condition, check if it has run already + if (!runConditions.test(i)) + { + runConditions.set(i); + } + if ((conditions[i]).run(cmds)) + { + retConditions.set(i); + } + else + { + retConditions.reset(i); + } + // Check if the condition returned true + if (!retConditions.test(i)) + { + canRun = false; + break; + } + } + + i += 1; + conditionsMask >>= 1; + } if (canRun) { - system->system.run(cmds); + for (auto& step : mSteps) + { + step->call(cmds, conditions, runConditions, retConditions); + } + } + if (groupTag == "main") + { + canRun = false; } - - // TODO: Check synchronization concerns when this gets multithreaded - cmds.commit(); } } + +void Dispatcher::callSystems(CommandBuffer& cmds) +{ + // Clear conditions bitmasks + mRunConditions.reset(); + mRetConditions.reset(); + + mMainStep->call(cmds, mConditions, mRunConditions, mRetConditions); +} diff --git a/core/tests/ecs/dispatcher.cpp b/core/tests/ecs/dispatcher.cpp index 3ea0162285..f3bd4aeef0 100644 --- a/core/tests/ecs/dispatcher.cpp +++ b/core/tests/ecs/dispatcher.cpp @@ -39,6 +39,17 @@ static bool pushToOrderAndSucceed(std::vector& order) return true; } +static bool succeed2Times(int& counter) +{ + counter++; + if (counter == 4) + { + counter++; + return false; + } + return counter <= 8; +} + /// Asserts that the order vector contains the given values in order. /// @param world The world the order vector is in. /// @param values The values to check for. @@ -68,6 +79,7 @@ TEST_CASE("ecs::Dispatcher") CommandBuffer cmdBuffer{world}; Dispatcher dispatcher{}; world.registerResource>(); + world.registerResource(); auto wrapSystem = [&](auto f) { return System::make(world, f); }; auto wrapCondition = [&](auto f) { return System::make(world, f); }; @@ -221,4 +233,41 @@ TEST_CASE("ecs::Dispatcher") singleDispatch(dispatcher, cmdBuffer); assertOrder(world, {1, 3}); } + + SUBCASE("repeat while") + { + dispatcher.addTag("repeat"); + dispatcher.tagRepeatWhile(wrapCondition(succeed2Times)); + dispatcher.addSystem(wrapSystem(pushToOrder<1>)); + dispatcher.systemAddGroup("repeat"); + + singleDispatch(dispatcher, cmdBuffer); + assertOrder(world, {1, 1, 1}); + } + + SUBCASE("repeat while inside repeat while") + { + dispatcher.addTag("principal"); + dispatcher.tagRepeatWhile(wrapCondition(succeed2Times)); + + dispatcher.addTag("subtag"); + dispatcher.tagInheritTag("principal"); + dispatcher.tagRepeatWhile(wrapCondition(succeed2Times)); + + dispatcher.addSystem(wrapSystem(pushToOrder<1>)); + dispatcher.systemAddTag("principal"); + dispatcher.systemAddTag("first"); + + dispatcher.addSystem(wrapSystem(pushToOrder<2>)); + dispatcher.systemAddTag("subtag"); + dispatcher.systemSetAfterTag("first"); + dispatcher.systemSetBeforeTag("last"); + + dispatcher.addSystem(wrapSystem(pushToOrder<3>)); + dispatcher.systemAddTag("subtag"); + dispatcher.systemAddTag("last"); + + singleDispatch(dispatcher, cmdBuffer); + assertOrder(world, {1, 2, 3, 2, 3, 1, 2, 3, 2, 3}); + } }