diff --git a/src/Reactor.hpp b/src/Reactor.hpp index d21740d5..f072002e 100644 --- a/src/Reactor.hpp +++ b/src/Reactor.hpp @@ -70,6 +70,9 @@ namespace dsl { struct MainThread; + template + struct TaskScope; + template struct Network; @@ -225,6 +228,10 @@ class Reactor { /// @copydoc dsl::word::MainThread using MainThread = dsl::word::MainThread; + /// @copydoc dsl::word::TaskScope + template + using TaskScope = dsl::word::TaskScope; + /// @copydoc dsl::word::Startup using Startup = dsl::word::Startup; @@ -474,6 +481,7 @@ class Reactor { #include "dsl/word/Startup.hpp" #include "dsl/word/Sync.hpp" #include "dsl/word/TCP.hpp" +#include "dsl/word/TaskScope.hpp" #include "dsl/word/Trigger.hpp" #include "dsl/word/UDP.hpp" #include "dsl/word/Watchdog.hpp" diff --git a/src/dsl/Fusion.hpp b/src/dsl/Fusion.hpp index ab5ce5d9..f80b3553 100644 --- a/src/dsl/Fusion.hpp +++ b/src/dsl/Fusion.hpp @@ -29,9 +29,11 @@ #include "fusion/GroupFusion.hpp" #include "fusion/InlineFusion.hpp" #include "fusion/PoolFusion.hpp" -#include "fusion/PostconditionFusion.hpp" +#include "fusion/PostRunFusion.hpp" +#include "fusion/PreRunFusion.hpp" #include "fusion/PreconditionFusion.hpp" #include "fusion/PriorityFusion.hpp" +#include "fusion/ScopeFusion.hpp" namespace NUClear { namespace dsl { @@ -43,10 +45,12 @@ namespace dsl { , fusion::GetFusion , fusion::GroupFusion , fusion::InlineFusion + , fusion::PoolFusion + , fusion::PostRunFusion + , fusion::PreRunFusion , fusion::PreconditionFusion , fusion::PriorityFusion - , fusion::PoolFusion - , fusion::PostconditionFusion {}; + , fusion::ScopeFusion {}; } // namespace dsl } // namespace NUClear diff --git a/src/dsl/Parse.hpp b/src/dsl/Parse.hpp index 58983ab8..1e38116d 100644 --- a/src/dsl/Parse.hpp +++ b/src/dsl/Parse.hpp @@ -57,25 +57,36 @@ namespace dsl { Parse>(task); } - static bool precondition(threading::ReactionTask& task) { - return std::conditional_t::value, DSL, fusion::NoOp>::template precondition< + static std::shared_ptr pool(threading::ReactionTask& task) { + return std::conditional_t::value, DSL, fusion::NoOp>::template pool< Parse>(task); } - static int priority(threading::ReactionTask& task) { - return std::conditional_t::value, DSL, fusion::NoOp>::template priority< + static void post_run(threading::ReactionTask& task) { + std::conditional_t::value, DSL, fusion::NoOp>::template post_run< + Parse>(task); + } + static void pre_run(threading::ReactionTask& task) { + std::conditional_t::value, DSL, fusion::NoOp>::template pre_run< Parse>(task); } - static std::shared_ptr pool(threading::ReactionTask& task) { - return std::conditional_t::value, DSL, fusion::NoOp>::template pool< + static bool precondition(threading::ReactionTask& task) { + return std::conditional_t::value, DSL, fusion::NoOp>::template precondition< Parse>(task); } - static void postcondition(threading::ReactionTask& task) { - std::conditional_t::value, DSL, fusion::NoOp>::template postcondition< + static int priority(threading::ReactionTask& task) { + return std::conditional_t::value, DSL, fusion::NoOp>::template priority< Parse>(task); } + + static auto scope(threading::ReactionTask& task) + -> decltype(std::conditional_t::value, DSL, fusion::NoOp>::template scope< + Parse>(task)) { + return std::conditional_t::value, DSL, fusion::NoOp>::template scope>( + task); + } }; } // namespace dsl diff --git a/src/dsl/fusion/NoOp.hpp b/src/dsl/fusion/NoOp.hpp index 7ee1d03e..5b20e5ee 100644 --- a/src/dsl/fusion/NoOp.hpp +++ b/src/dsl/fusion/NoOp.hpp @@ -69,6 +69,16 @@ namespace dsl { return true; } + template + static void post_run(const threading::ReactionTask& /*task*/) { + // Empty as this is a no-op placeholder + } + + template + static void pre_run(const threading::ReactionTask& /*task*/) { + // Empty as this is a no-op placeholder + } + template static int priority(const threading::ReactionTask& /*task*/) { return word::Priority::NORMAL::value; @@ -80,8 +90,8 @@ namespace dsl { } template - static void postcondition(const threading::ReactionTask& /*task*/) { - // Empty as this is a no-op placeholder + static std::tuple<> scope(const threading::ReactionTask& /*task*/) { + return {}; } }; @@ -102,11 +112,15 @@ namespace dsl { static bool precondition(threading::ReactionTask&); + static void post_run(threading::ReactionTask&); + + static void pre_run(threading::ReactionTask&); + static int priority(threading::ReactionTask&); static std::shared_ptr pool(threading::ReactionTask&); - static void postcondition(threading::ReactionTask&); + static std::tuple<> scope(threading::ReactionTask&); }; } // namespace fusion diff --git a/src/dsl/fusion/PostconditionFusion.hpp b/src/dsl/fusion/PostRunFusion.hpp similarity index 63% rename from src/dsl/fusion/PostconditionFusion.hpp rename to src/dsl/fusion/PostRunFusion.hpp index ad2d1bad..a02c15ad 100644 --- a/src/dsl/fusion/PostconditionFusion.hpp +++ b/src/dsl/fusion/PostRunFusion.hpp @@ -20,8 +20,8 @@ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef NUCLEAR_DSL_FUSION_POSTCONDITION_FUSION_HPP -#define NUCLEAR_DSL_FUSION_POSTCONDITION_FUSION_HPP +#ifndef NUCLEAR_DSL_FUSION_POST_RUN_FUSION_HPP +#define NUCLEAR_DSL_FUSION_POST_RUN_FUSION_HPP #include "../../threading/ReactionTask.hpp" #include "../operation/DSLProxy.hpp" @@ -32,45 +32,45 @@ namespace NUClear { namespace dsl { namespace fusion { - /// Make a SFINAE type to check if a word has a postcondition method - HAS_NUCLEAR_DSL_METHOD(postcondition); + /// Make a SFINAE type to check if a word has a post_run method + HAS_NUCLEAR_DSL_METHOD(post_run); - // Default case where there are no postcondition words + // Default case where there are no post_run words template - struct PostconditionFuser {}; + struct PostRunFuser {}; // Case where there is only a single word remaining template - struct PostconditionFuser> { + struct PostRunFuser> { template - static void postcondition(threading::ReactionTask& task) { + static void post_run(threading::ReactionTask& task) { - // Run our remaining postcondition - Word::template postcondition(task); + // Run our remaining post_run + Word::template post_run(task); } }; // Case where there is more 2 more more words remaining template - struct PostconditionFuser> { + struct PostRunFuser> { template - static void postcondition(threading::ReactionTask& task) { + static void post_run(threading::ReactionTask& task) { - // Run our postcondition - Word1::template postcondition(task); + // Run our post_run + Word1::template post_run(task); - // Run the rest of our postconditions - PostconditionFuser>::template postcondition(task); + // Run the rest of our post_runs + PostRunFuser>::template post_run(task); } }; template - struct PostconditionFusion : PostconditionFuser> {}; + struct PostRunFusion : PostRunFuser> {}; } // namespace fusion } // namespace dsl } // namespace NUClear -#endif // NUCLEAR_DSL_FUSION_POSTCONDITION_FUSION_HPP +#endif // NUCLEAR_DSL_FUSION_POST_RUN_FUSION_HPP diff --git a/src/dsl/fusion/PreRunFusion.hpp b/src/dsl/fusion/PreRunFusion.hpp new file mode 100644 index 00000000..4ba83c0a --- /dev/null +++ b/src/dsl/fusion/PreRunFusion.hpp @@ -0,0 +1,76 @@ +/* + * MIT License + * + * Copyright (c) 2024 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef NUCLEAR_DSL_FUSION_PRE_RUN_FUSION_HPP +#define NUCLEAR_DSL_FUSION_PRE_RUN_FUSION_HPP + +#include "../../threading/ReactionTask.hpp" +#include "../operation/DSLProxy.hpp" +#include "FindWords.hpp" +#include "has_nuclear_dsl_method.hpp" + +namespace NUClear { +namespace dsl { + namespace fusion { + + /// Make a SFINAE type to check if a word has a pre_run method + HAS_NUCLEAR_DSL_METHOD(pre_run); + + // Default case where there are no pre_run words + template + struct PreRunFuser {}; + + // Case where there is only a single word remaining + template + struct PreRunFuser> { + + template + static void pre_run(threading::ReactionTask& task) { + + // Run our remaining pre_run + Word::template pre_run(task); + } + }; + + // Case where there is more 2 more more words remaining + template + struct PreRunFuser> { + + template + static void pre_run(threading::ReactionTask& task) { + + // Run our pre_run + Word1::template pre_run(task); + + // Run the rest of our pre_runs + PreRunFuser>::template pre_run(task); + } + }; + + template + struct PreRunFusion : PreRunFuser> {}; + + } // namespace fusion +} // namespace dsl +} // namespace NUClear + +#endif // NUCLEAR_DSL_FUSION_PRE_RUN_FUSION_HPP diff --git a/src/dsl/fusion/ScopeFusion.hpp b/src/dsl/fusion/ScopeFusion.hpp new file mode 100644 index 00000000..3257c6d3 --- /dev/null +++ b/src/dsl/fusion/ScopeFusion.hpp @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2024 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef NUCLEAR_DSL_FUSION_SCOPE_FUSION_HPP +#define NUCLEAR_DSL_FUSION_SCOPE_FUSION_HPP + +#include "../../threading/Reaction.hpp" +#include "../../util/tuplify.hpp" +#include "../operation/DSLProxy.hpp" +#include "FindWords.hpp" +#include "has_nuclear_dsl_method.hpp" + +namespace NUClear { +namespace dsl { + namespace fusion { + + /// Make a SFINAE type to check if a word has a scope method + HAS_NUCLEAR_DSL_METHOD(scope); + + /** + * This is our Function Fusion wrapper class that allows it to call scope functions. + * + * @tparam Function the scope function that we are wrapping for + * @tparam DSL the DSL that we pass to our scope function + */ + template + struct ScopeCaller { + static auto call(threading::ReactionTask& task) -> decltype(Function::template scope(task)) { + return Function::template scope(task); + } + }; + + // Default case where there are no scope words + template + struct ScopeFuser {}; + + // Case where there is at least one get word + template + struct ScopeFuser> { + + template + static auto scope(threading::ReactionTask& task) + -> decltype(util::FunctionFusion, + decltype(std::forward_as_tuple(task)), + ScopeCaller, + std::tuple, + 1>::call(task)) { + + // Perform our function fusion + return util::FunctionFusion, + decltype(std::forward_as_tuple(task)), + ScopeCaller, + std::tuple, + 1>::call(task); + } + }; + + template + struct ScopeFusion : ScopeFuser> {}; + + } // namespace fusion +} // namespace dsl +} // namespace NUClear + +#endif // NUCLEAR_DSL_FUSION_SCOPE_FUSION_HPP diff --git a/src/dsl/word/Always.hpp b/src/dsl/word/Always.hpp index 24506c33..9292f739 100644 --- a/src/dsl/word/Always.hpp +++ b/src/dsl/word/Always.hpp @@ -110,7 +110,7 @@ namespace dsl { } template - static void postcondition(threading::ReactionTask& task) { + static void post_run(threading::ReactionTask& task) { // Get a task for the always reaction and submit it to the scheduler PowerPlant::powerplant->submit(task.parent->get_task()); } diff --git a/src/dsl/word/IO.hpp b/src/dsl/word/IO.hpp index 9b6e2648..6b7f3cb0 100644 --- a/src/dsl/word/IO.hpp +++ b/src/dsl/word/IO.hpp @@ -154,7 +154,7 @@ namespace dsl { } template - static void postcondition(threading::ReactionTask& task) { + static void post_run(threading::ReactionTask& task) { task.parent->reactor.emit(std::make_unique(task.parent->id)); } }; diff --git a/src/dsl/word/Once.hpp b/src/dsl/word/Once.hpp index d0bb339b..7699971f 100644 --- a/src/dsl/word/Once.hpp +++ b/src/dsl/word/Once.hpp @@ -35,13 +35,13 @@ namespace dsl { * * @code on() @endcode * Any reactions listed with this DSL word will run only once. - * This is the only time these reactions will run as the postcondition Unbinds the current reaction. + * This is the only time these reactions will run as the post_run Unbinds the current reaction. */ struct Once : Single { // Post condition to unbind this reaction. template - static void postcondition(threading::ReactionTask& task) { + static void post_run(threading::ReactionTask& task) { // Unbind: task.parent->unbind(); } diff --git a/src/dsl/word/TaskScope.hpp b/src/dsl/word/TaskScope.hpp new file mode 100644 index 00000000..18f2e0a3 --- /dev/null +++ b/src/dsl/word/TaskScope.hpp @@ -0,0 +1,108 @@ +/* + * MIT License + * + * Copyright (c) 2024 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NUCLEAR_DSL_WORD_TASK_SCOPE_HPP +#define NUCLEAR_DSL_WORD_TASK_SCOPE_HPP +#include + +#include "../../id.hpp" +#include "../../threading/ReactionTask.hpp" +#include "../../util/platform.hpp" + +namespace NUClear { +namespace dsl { + namespace word { + + /** + * This template can be used when you need other DSL words to know that they are running in a specific context. + * + * For example, if you want to have a DSL word that modifies how events are emitted within that task you can use + * this to track when the current task is one with that word. + * It utilises scope to track when the task is running by storing the task id in a thread local variable. + * Then when another system needs to know if it is running in that context it can call the in_scope function. + * + * @tparam Group a unique type to identify this scope + */ + template + struct TaskScope { + + /** + * Locks the current task id to the word until the lock goes out of scope + */ + struct Lock { + explicit Lock(NUClear::id_t old_id) : old_id(old_id) {} + + // No copying the lock + Lock(const Lock&) = delete; + Lock& operator=(const Lock&) = delete; + + // Moving the lock must invalidate the old lock + Lock(Lock&& other) noexcept : valid(std::exchange(other.valid, false)), old_id(other.old_id) {} + Lock& operator=(Lock&& other) noexcept { + if (this != &other) { + valid = std::exchange(other.valid, false); + old_id = other.old_id; + } + } + ~Lock() { // Only restore if this lock hasn't been invalidated + current_task_id = valid ? old_id : current_task_id; + } + + private: + /// If this lock is still valid (hasn't been moved) + bool valid = true; + /// The old task id to restore + NUClear::id_t old_id; + }; + + template + static Lock scope(const threading::ReactionTask& task) { + // Store the old task id + NUClear::id_t old_id = current_task_id; + // Set the current task id to the word + current_task_id = task.id; + // Return a lock that will restore the old task id + return Lock(old_id); + } + + static bool in_scope() { + // Get the current task id + const auto* task = threading::ReactionTask::get_current_task(); + + // Check if the current task id is the word + return task != nullptr && task->id == current_task_id; + } + + private: + /// The current task id that is running + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + static ATTRIBUTE_TLS NUClear::id_t current_task_id; + }; + + // Initialise the current task id + template // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + ATTRIBUTE_TLS NUClear::id_t TaskScope::current_task_id{0}; + + } // namespace word +} // namespace dsl +} // namespace NUClear + +#endif // NUCLEAR_DSL_WORD_TASK_SCOPE_HPP diff --git a/src/util/CallbackGenerator.hpp b/src/util/CallbackGenerator.hpp index 2c2b039a..93ada18c 100644 --- a/src/util/CallbackGenerator.hpp +++ b/src/util/CallbackGenerator.hpp @@ -132,8 +132,11 @@ namespace util { // We have to catch any exceptions try { - // We call with only the relevant arguments to the passed function - util::apply_relevant(c, std::move(data)); + auto scope = DSL::scope(task); // Acquire the scope + DSL::pre_run(task); // Pre run tasks + util::apply_relevant(c, std::move(data)); // Run the callback + DSL::post_run(task); // Post run tasks + std::ignore = scope; // Ignore unused variable warning } catch (...) { // Catch our exception if it happens @@ -142,9 +145,6 @@ namespace util { } } - // Run our postconditions - DSL::postcondition(task); - if (task.statistics != nullptr) { task.statistics->finished = message::ReactionStatistics::Event::now(); PowerPlant::powerplant->emit(std::make_unique(Event::FINISHED, task.statistics)); diff --git a/tests/tests/dsl/TaskScope.cpp b/tests/tests/dsl/TaskScope.cpp new file mode 100644 index 00000000..eb192a5a --- /dev/null +++ b/tests/tests/dsl/TaskScope.cpp @@ -0,0 +1,145 @@ +/* + * MIT License + * + * Copyright (c) 2013 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "test_util/TestBase.hpp" +#include "test_util/common.hpp" + +/** + * Holds information about a single step in the test + */ +struct StepData { + int scope; ///< The scope that this step was run in + bool next_inline; ///< If the next step was run inline + std::array scope_states; ///< The state of the scopes during this step +}; + +namespace Catch { +template <> +struct StringMaker> { + static std::string convert(const std::map& value) { + std::stringstream event; + for (const auto& items : value) { + event << items.first << "(" << items.second.scope << "):"; + for (const auto& scope : items.second.scope_states) { + event << (scope ? "t" : "f"); + } + event << (items.second.next_inline ? " -> " : " -| "); + } + return event.str(); + } +}; +} // namespace Catch + +class TestReactor : public test_util::TestBase { +public: + /** + * Holds the accumulated data for the test + */ + template + struct Data { + std::map steps; ///< The steps that have been run + }; + + template + void process_step(const Message& d) { + + using NextData = Data; + + // Get the scope state before the inline event + auto next_inline = std::make_unique(); + next_inline->steps = d.steps; + next_inline->steps[Current] = { + ScopeID, + true, + {TaskScope>::in_scope(), TaskScope>::in_scope(), TaskScope>::in_scope()}, + }; + emit(next_inline); + + // Get the scope state after the inline event + auto next_normal = std::make_unique(); + next_normal->steps = d.steps; + next_normal->steps[Current] = { + ScopeID, + false, + {TaskScope>::in_scope(), TaskScope>::in_scope(), TaskScope>::in_scope()}}; + emit(next_normal); + } + + TestReactor(std::unique_ptr environment) : TestBase(std::move(environment)) { + + on>>().then([this](const Data<0>& a) { process_step<0, -1>(a); }); + on>, TaskScope>>().then([this](const Data<0>& m) { process_step<0, 0>(m); }); + on>, TaskScope>>().then([this](const Data<0>& m) { process_step<0, 1>(m); }); + on>, TaskScope>>().then([this](const Data<0>& m) { process_step<0, 2>(m); }); + + on>>().then([this](const Data<1>& m) { process_step<1, -1>(m); }); + on>, TaskScope>>().then([this](const Data<1>& m) { process_step<1, 0>(m); }); + on>, TaskScope>>().then([this](const Data<1>& m) { process_step<1, 1>(m); }); + on>, TaskScope>>().then([this](const Data<1>& m) { process_step<1, 2>(m); }); + + on>>().then([this](const Data<2>& m) { process_step<2, -1>(m); }); + on>, TaskScope>>().then([this](const Data<2>& m) { process_step<2, 0>(m); }); + on>, TaskScope>>().then([this](const Data<2>& m) { process_step<2, 1>(m); }); + on>, TaskScope>>().then([this](const Data<2>& m) { process_step<2, 2>(m); }); + + // Store the results of the test + on>>().then([this](const Data<3>& m) { events.push_back(m.steps); }); + + // Start the test + on().then([this] { emit(std::make_unique>()); }); + } + + /// A vector of events that have happened + std::vector> events; +}; + + +TEST_CASE("Test that Trigger statements get the correct data", "[api][trigger]") { + + NUClear::Configuration config; + config.default_pool_concurrency = 1; + NUClear::PowerPlant plant(config); + // test_util::add_tracing(plant); + const auto& reactor = plant.install(); + plant.start(); + + CHECK(reactor.events.size() == 512); + for (const auto& test : reactor.events) { + CAPTURE(test); + CHECK(test.size() == 3); + for (const auto& step : test) { + const auto& step_no = step.first; + const auto& data = step.second; + CAPTURE(step_no); + CAPTURE(data.scope); + + // Only the active scope (if there is one) should be true + CHECK(data.scope_states[0] == (0 == data.scope)); + CHECK(data.scope_states[1] == (1 == data.scope)); + CHECK(data.scope_states[2] == (2 == data.scope)); + } + } +}