diff --git a/core/include/cubos/core/ecs/cubos.hpp b/core/include/cubos/core/ecs/cubos.hpp index cb8d92a06c..2b429d0a2a 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 Makes the current tag inherit from 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 Adds a repeat condition to the current tag, also making it a group. + /// @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..d9a020ae75 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>& mConditions, + std::bitset mRunConditions, + std::bitset mRetConditions) = 0; + }; + + class SystemStep : public Step + { + public: + SystemStep(System* system) + : mSystem(system) + { + } + void call(CommandBuffer& cmds, std::vector>& mConditions, + std::bitset mRunConditions, + std::bitset mRetConditions); + + System* getSystem() + { + return mSystem; + } + + private: + System* mSystem; }; + class GroupStep : public Step, public std::enable_shared_from_this + { + public: + GroupStep(std::string grouptag, const std::shared_ptr parentStep) + : grouptag(std::move(grouptag)) + , mParentStep(std::move(parentStep)) + { + } + void call(CommandBuffer& cmds, std::vector>& mConditions, + std::bitset mRunConditions, + std::bitset mRetConditions) 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..f9535b25d0 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,35 @@ void Dispatcher::tagSetBeforeTag(const std::string& tag) mTagSettings[tag]->after.tag.push_back(mCurrTag); } +void Dispatcher::groupAddGroup() +{ + // CUBOS_WARN("{}", mCurrGroup->getGroupTag()); + mCurrGroup = mCurrGroup->addGroupStep(mCurrTag, mCurrGroup); + // CUBOS_WARN("{}", mCurrGroup->getGroupTag()); +} + 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; + } + + if (tag == "last") + { + // mMainStep->printStructure(); + } +} + +void Dispatcher::systemAddGroup(const std::string& grouptag) +{ + ENSURE_CURR_SYSTEM(); + mCurrSystem->grouptag = grouptag; } void Dispatcher::systemSetAfterTag(const std::string& tag) @@ -220,7 +250,9 @@ bool Dispatcher::dfsVisit(DFSNode& node, std::vector& nodes) node.m = DFSNode::BLACK; if (node.s != nullptr) { - mSystems.push_back(node.s); + // mSystems.push_back(node.s); + // CUBOS_WARN("{}", node.s->grouptag); + mMainStep->queueSystem(node.s->grouptag, node.s); } return false; } @@ -228,53 +260,116 @@ bool Dispatcher::dfsVisit(DFSNode& node, std::vector& nodes) return false; } -void Dispatcher::callSystems(CommandBuffer& cmds) +void Dispatcher::SystemStep::call(CommandBuffer& cmds, std::vector>& mConditions, + std::bitset mRunConditions, + std::bitset mRetConditions) { - // 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 (!mRunConditions.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)) + mRunConditions.set(i); + if (mConditions[i].run(cmds)) { - canRun = false; - break; + mRetConditions.set(i); } } - - i += 1; - conditionsMask >>= 1; + // Check if the condition returned true + if (!mRetConditions.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>& mConditions, + std::bitset mRunConditions, + std::bitset mRetConditions) +{ + bool canRun = true; + while (canRun) + { + auto conditionsMask = conditions; + std::size_t i = 0; + while (conditionsMask.any()) + { + if (conditionsMask.test(0)) + { + // 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); + } + else + { + mRetConditions.reset(i); + } + // Check if the condition returned true + if (!mRetConditions.test(i)) + { + canRun = false; + break; + } + } + + i += 1; + conditionsMask >>= 1; + } if (canRun) { - system->system.run(cmds); + for (auto& step : mSteps) + { + if (auto groupStepPtr = std::dynamic_pointer_cast(step)) + { + // CUBOS_WARN("{}", groupStepPtr->grouptag); + groupStepPtr->call(cmds, mConditions, mRunConditions, mRetConditions); + } + else if (auto systemStepPtr = std::dynamic_pointer_cast(step)) + { + // CUBOS_WARN("{}", SystemStepPtr->getSystem()->grouptag); + systemStepPtr->call(cmds, mConditions, mRunConditions, mRetConditions); + } + } + } + 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); +} \ No newline at end of file diff --git a/core/tests/ecs/dispatcher.cpp b/core/tests/ecs/dispatcher.cpp index 3ea0162285..59c6801ec6 100644 --- a/core/tests/ecs/dispatcher.cpp +++ b/core/tests/ecs/dispatcher.cpp @@ -39,6 +39,18 @@ 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 +80,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 +234,43 @@ TEST_CASE("ecs::Dispatcher") singleDispatch(dispatcher, cmdBuffer); assertOrder(world, {1, 3}); } + + SUBCASE("repeat while") + { + // Tests if #413 has been fixed. + 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 embedded") + { + // Tests if #413 has been fixed. + 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}); + } } diff --git a/engine/src/physics/plugin.cpp b/engine/src/physics/plugin.cpp index 090f59bf6e..a7c5b3fb85 100644 --- a/engine/src/physics/plugin.cpp +++ b/engine/src/physics/plugin.cpp @@ -71,6 +71,7 @@ void cubos::engine::physicsPlugin(Cubos& cubos) cubos.addPlugin(gravityPlugin); // executed every frame + cubos.system("increase fixed-step accumulator") .tagged("cubos.physics.simulation.prepare") .call(