From 4a834181bd2e1621e7c2bdb7509fda6bc10bca28 Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Sat, 26 Oct 2024 13:58:11 +1100 Subject: [PATCH 1/3] Store trace events that mark the start of the trace (#157) The trigger that starts the trace itself wasn't logged properly. This made it difficult to get a "zero time" for the trace. Logging the BeginTrace reaction itself means you get to see when the begin trace was requested --- src/extension/TraceController.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/extension/TraceController.cpp b/src/extension/TraceController.cpp index 7e135d86..b2a40608 100644 --- a/src/extension/TraceController.cpp +++ b/src/extension/TraceController.cpp @@ -275,6 +275,11 @@ namespace extension { } write_trace_packet(data); + // Write the trace events that happened before the trace started + auto current_stats = threading::ReactionTask::get_current_task()->statistics; + encode_event(ReactionEvent(ReactionEvent::CREATED, current_stats)); + encode_event(ReactionEvent(ReactionEvent::STARTED, current_stats)); + // Bind new handles event_handle = on, Pool>().then([this](const ReactionEvent& e) { // encode_event(e); From 12fab9a01d8a459ba47bb30f476bb479074961d8 Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Sat, 26 Oct 2024 14:13:33 +1100 Subject: [PATCH 2/3] Add a macro which generates a SFINAE template for checking NUClear words (#155) All of the NUClear DSL words follow a similar pattern for working out if a DSL word exists. This makes a macro which can generate this pattern rather than having almost identical lines all through individual files. --- src/dsl/fusion/BindFusion.hpp | 5 +- src/dsl/fusion/GetFusion.hpp | 5 +- src/dsl/fusion/GroupFusion.hpp | 5 +- src/dsl/fusion/InlineFusion.hpp | 5 +- src/dsl/fusion/PoolFusion.hpp | 5 +- src/dsl/fusion/PostconditionFusion.hpp | 5 +- src/dsl/fusion/PreconditionFusion.hpp | 5 +- src/dsl/fusion/PriorityFusion.hpp | 5 +- src/dsl/fusion/has_bind.hpp | 61 ----------------------- src/dsl/fusion/has_get.hpp | 58 --------------------- src/dsl/fusion/has_group.hpp | 58 --------------------- src/dsl/fusion/has_nuclear_dsl_method.hpp | 60 ++++++++++++++++++++++ src/dsl/fusion/has_pool.hpp | 58 --------------------- src/dsl/fusion/has_postcondition.hpp | 58 --------------------- src/dsl/fusion/has_precondition.hpp | 58 --------------------- src/dsl/fusion/has_priority.hpp | 58 --------------------- src/dsl/fusion/has_run_inline.hpp | 58 --------------------- 17 files changed, 92 insertions(+), 475 deletions(-) delete mode 100644 src/dsl/fusion/has_bind.hpp delete mode 100644 src/dsl/fusion/has_get.hpp delete mode 100644 src/dsl/fusion/has_group.hpp create mode 100644 src/dsl/fusion/has_nuclear_dsl_method.hpp delete mode 100644 src/dsl/fusion/has_pool.hpp delete mode 100644 src/dsl/fusion/has_postcondition.hpp delete mode 100644 src/dsl/fusion/has_precondition.hpp delete mode 100644 src/dsl/fusion/has_priority.hpp delete mode 100644 src/dsl/fusion/has_run_inline.hpp diff --git a/src/dsl/fusion/BindFusion.hpp b/src/dsl/fusion/BindFusion.hpp index f93a68c8..35969fb4 100644 --- a/src/dsl/fusion/BindFusion.hpp +++ b/src/dsl/fusion/BindFusion.hpp @@ -27,12 +27,15 @@ #include "../../util/FunctionFusion.hpp" #include "../operation/DSLProxy.hpp" #include "FindWords.hpp" -#include "has_bind.hpp" +#include "has_nuclear_dsl_method.hpp" namespace NUClear { namespace dsl { namespace fusion { + /// Make a SFINAE type to check if a word has a run_inline method + HAS_NUCLEAR_DSL_METHOD(bind); + /** * This is our Function Fusion wrapper class that allows it to call bind functions. * diff --git a/src/dsl/fusion/GetFusion.hpp b/src/dsl/fusion/GetFusion.hpp index 9bbffe7f..95b6a659 100644 --- a/src/dsl/fusion/GetFusion.hpp +++ b/src/dsl/fusion/GetFusion.hpp @@ -27,12 +27,15 @@ #include "../../util/tuplify.hpp" #include "../operation/DSLProxy.hpp" #include "FindWords.hpp" -#include "has_get.hpp" +#include "has_nuclear_dsl_method.hpp" namespace NUClear { namespace dsl { namespace fusion { + /// Make a SFINAE type to check if a word has a get method + HAS_NUCLEAR_DSL_METHOD(get); + /** * This is our Function Fusion wrapper class that allows it to call get functions. * diff --git a/src/dsl/fusion/GroupFusion.hpp b/src/dsl/fusion/GroupFusion.hpp index e6668b54..21525e6c 100644 --- a/src/dsl/fusion/GroupFusion.hpp +++ b/src/dsl/fusion/GroupFusion.hpp @@ -30,12 +30,15 @@ #include "../../threading/Reaction.hpp" #include "../operation/DSLProxy.hpp" #include "FindWords.hpp" -#include "has_group.hpp" +#include "has_nuclear_dsl_method.hpp" namespace NUClear { namespace dsl { namespace fusion { + /// Make a SFINAE type to check if a word has a group method + HAS_NUCLEAR_DSL_METHOD(group); + // Default case where there are no group words template struct GroupFuser {}; diff --git a/src/dsl/fusion/InlineFusion.hpp b/src/dsl/fusion/InlineFusion.hpp index c73884e1..8ebf052d 100644 --- a/src/dsl/fusion/InlineFusion.hpp +++ b/src/dsl/fusion/InlineFusion.hpp @@ -27,12 +27,15 @@ #include "../../util/Inline.hpp" #include "../operation/DSLProxy.hpp" #include "FindWords.hpp" -#include "has_run_inline.hpp" +#include "has_nuclear_dsl_method.hpp" namespace NUClear { namespace dsl { namespace fusion { + /// Make a SFINAE type to check if a word has a run_inline method + HAS_NUCLEAR_DSL_METHOD(run_inline); + // Default case where there are no Inline words template struct InlineFuser {}; diff --git a/src/dsl/fusion/PoolFusion.hpp b/src/dsl/fusion/PoolFusion.hpp index 578b6e8d..aea11c46 100644 --- a/src/dsl/fusion/PoolFusion.hpp +++ b/src/dsl/fusion/PoolFusion.hpp @@ -29,12 +29,15 @@ #include "../../threading/ReactionTask.hpp" #include "../operation/DSLProxy.hpp" #include "FindWords.hpp" -#include "has_pool.hpp" +#include "has_nuclear_dsl_method.hpp" namespace NUClear { namespace dsl { namespace fusion { + /// Make a SFINAE type to check if a word has a pool method + HAS_NUCLEAR_DSL_METHOD(pool); + // Default case where there are no pool words template struct PoolFuser {}; diff --git a/src/dsl/fusion/PostconditionFusion.hpp b/src/dsl/fusion/PostconditionFusion.hpp index 3eeb9aac..ad2d1bad 100644 --- a/src/dsl/fusion/PostconditionFusion.hpp +++ b/src/dsl/fusion/PostconditionFusion.hpp @@ -26,12 +26,15 @@ #include "../../threading/ReactionTask.hpp" #include "../operation/DSLProxy.hpp" #include "FindWords.hpp" -#include "has_postcondition.hpp" +#include "has_nuclear_dsl_method.hpp" namespace NUClear { namespace dsl { namespace fusion { + /// Make a SFINAE type to check if a word has a postcondition method + HAS_NUCLEAR_DSL_METHOD(postcondition); + // Default case where there are no postcondition words template struct PostconditionFuser {}; diff --git a/src/dsl/fusion/PreconditionFusion.hpp b/src/dsl/fusion/PreconditionFusion.hpp index 09d0b696..103d25f3 100644 --- a/src/dsl/fusion/PreconditionFusion.hpp +++ b/src/dsl/fusion/PreconditionFusion.hpp @@ -26,12 +26,15 @@ #include "../../threading/ReactionTask.hpp" #include "../operation/DSLProxy.hpp" #include "FindWords.hpp" -#include "has_precondition.hpp" +#include "has_nuclear_dsl_method.hpp" namespace NUClear { namespace dsl { namespace fusion { + /// Make a SFINAE type to check if a word has a precondition method + HAS_NUCLEAR_DSL_METHOD(precondition); + // Default case where there are no precondition words template struct PreconditionFuser {}; diff --git a/src/dsl/fusion/PriorityFusion.hpp b/src/dsl/fusion/PriorityFusion.hpp index 5000f9ae..afd8b517 100644 --- a/src/dsl/fusion/PriorityFusion.hpp +++ b/src/dsl/fusion/PriorityFusion.hpp @@ -26,12 +26,15 @@ #include "../../threading/ReactionTask.hpp" #include "../operation/DSLProxy.hpp" #include "FindWords.hpp" -#include "has_priority.hpp" +#include "has_nuclear_dsl_method.hpp" namespace NUClear { namespace dsl { namespace fusion { + /// Make a SFINAE type to check if a word has a priority method + HAS_NUCLEAR_DSL_METHOD(priority); + // Default case where there are no priority words template struct PriorityFuser {}; diff --git a/src/dsl/fusion/has_bind.hpp b/src/dsl/fusion/has_bind.hpp deleted file mode 100644 index 7991f5da..00000000 --- a/src/dsl/fusion/has_bind.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2014 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_HAS_BIND_HPP -#define NUCLEAR_DSL_FUSION_HAS_BIND_HPP - -#include "../../threading/ReactionHandle.hpp" -#include "NoOp.hpp" - -namespace NUClear { -namespace dsl { - namespace fusion { - - /** - * SFINAE struct to test if the passed class has a bind function that conforms to the NUClear DSL. - * - * @tparam T the class to check - */ - template - struct has_bind { - private: - using yes = std::true_type; - using no = std::false_type; - - template - static yes test_func(R (*)(const std::shared_ptr&, A...)); - static no test_func(...); - - template - static auto test(int) -> decltype(test_func(U::template bind)); - template - static no test(...); - - public: - static constexpr bool value = std::is_same(0)), yes>::value; - }; - - } // namespace fusion -} // namespace dsl -} // namespace NUClear - -#endif // NUCLEAR_DSL_FUSION_HASBIND_HPP diff --git a/src/dsl/fusion/has_get.hpp b/src/dsl/fusion/has_get.hpp deleted file mode 100644 index e07f2a76..00000000 --- a/src/dsl/fusion/has_get.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2014 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_HAS_GET_HPP -#define NUCLEAR_DSL_FUSION_HAS_GET_HPP - -#include "../../threading/ReactionTask.hpp" -#include "NoOp.hpp" - -namespace NUClear { -namespace dsl { - namespace fusion { - - /** - * SFINAE struct to test if the passed class has a get function that conforms to the NUClear DSL. - * - * @tparam T the class to check - */ - template - struct has_get { - private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) -> decltype(U::template get(std::declval()), - yes()); - template - static no test(...); - - public: - static constexpr bool value = std::is_same(0)), yes>::value; - }; - - } // namespace fusion -} // namespace dsl -} // namespace NUClear - -#endif // NUCLEAR_DSL_FUSION_HAS_GET_HPP diff --git a/src/dsl/fusion/has_group.hpp b/src/dsl/fusion/has_group.hpp deleted file mode 100644 index e5bf5071..00000000 --- a/src/dsl/fusion/has_group.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 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_HAS_GROUP_HPP -#define NUCLEAR_DSL_FUSION_HAS_GROUP_HPP - -#include "../../threading/ReactionTask.hpp" -#include "NoOp.hpp" - -namespace NUClear { -namespace dsl { - namespace fusion { - - /** - * SFINAE struct to test if the passed class has a group function that conforms to the NUClear DSL. - * - * @tparam T the class to check - */ - template - struct has_group { - private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) -> decltype(U::template group(std::declval()), - yes()); - template - static no test(...); - - public: - static constexpr bool value = std::is_same(0)), yes>::value; - }; - - } // namespace fusion -} // namespace dsl -} // namespace NUClear - -#endif // NUCLEAR_DSL_FUSION_HAS_GROUP_HPP diff --git a/src/dsl/fusion/has_nuclear_dsl_method.hpp b/src/dsl/fusion/has_nuclear_dsl_method.hpp new file mode 100644 index 00000000..9b1ddad9 --- /dev/null +++ b/src/dsl/fusion/has_nuclear_dsl_method.hpp @@ -0,0 +1,60 @@ +/* + * 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_FUSION_HAS_NUCLEAR_DSL_METHOD_HPP +#define NUCLEAR_DSL_FUSION_FUSION_HAS_NUCLEAR_DSL_METHOD_HPP + +#include "NoOp.hpp" + +/** + * This macro to create an SFINAE type to check for the existence of a NUClear DSL method in a given class. + * + * The macro generates a template struct that can be used to determine if a class contains a specific method. + * + * The macro works by attempting to instantiate a function pointer to the method in question. + * If the method exists, the instantiation will succeed and the struct will inherit from + * `std::true_type`. Otherwise, it will inherit from `std::false_type`. + * + * @note This macro assumes that the method being checked for is a template method that can be instantiated with + * `ParsedNoOp`. + */ +// If you can find a way to do SFINAE in a constexpr for a variable function name let me know +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define HAS_NUCLEAR_DSL_METHOD(Method) \ + template \ + struct has_##Method { \ + private: \ + template \ + static auto test_func(R (*)(A...)) -> std::true_type; \ + static auto test_func(...) -> std::false_type; \ + \ + /* Parenthesis would literally break this code*/ \ + template /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + static auto test(int) -> decltype(test_func(&U::template Method)); \ + template \ + static auto test(...) -> std::false_type; \ + \ + public: \ + static constexpr bool value = decltype(test(0))::value; \ + } + +#endif // NUCLEAR_DSL_FUSION_FUSION_HAS_NUCLEAR_DSL_METHOD_HPP diff --git a/src/dsl/fusion/has_pool.hpp b/src/dsl/fusion/has_pool.hpp deleted file mode 100644 index 2f5c81e0..00000000 --- a/src/dsl/fusion/has_pool.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 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_HAS_POOL_HPP -#define NUCLEAR_DSL_FUSION_HAS_POOL_HPP - -#include "../../threading/ReactionTask.hpp" -#include "NoOp.hpp" - -namespace NUClear { -namespace dsl { - namespace fusion { - - /** - * SFINAE struct to test if the passed class has a pool function that conforms to the NUClear DSL. - * - * @tparam T the class to check - */ - template - struct has_pool { - private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) -> decltype(U::template pool(std::declval()), - yes()); - template - static no test(...); - - public: - static constexpr bool value = std::is_same(0)), yes>::value; - }; - - } // namespace fusion -} // namespace dsl -} // namespace NUClear - -#endif // NUCLEAR_DSL_FUSION_HAS_POOL_HPP diff --git a/src/dsl/fusion/has_postcondition.hpp b/src/dsl/fusion/has_postcondition.hpp deleted file mode 100644 index d782ab43..00000000 --- a/src/dsl/fusion/has_postcondition.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2014 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_HAS_POSTCONDITION_HPP -#define NUCLEAR_DSL_FUSION_HAS_POSTCONDITION_HPP - -#include "../../threading/ReactionTask.hpp" -#include "NoOp.hpp" - -namespace NUClear { -namespace dsl { - namespace fusion { - - /** - * SFINAE struct to test if the passed class has a postcondition function that conforms to the NUClear DSL. - * - * @tparam T the class to check - */ - template - struct has_postcondition { - private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) - -> decltype(U::template postcondition(std::declval()), yes()); - template - static no test(...); - - public: - static constexpr bool value = std::is_same(0)), yes>::value; - }; - - } // namespace fusion -} // namespace dsl -} // namespace NUClear - -#endif // NUCLEAR_DSL_FUSION_HAS_POSTCONDITION_HPP diff --git a/src/dsl/fusion/has_precondition.hpp b/src/dsl/fusion/has_precondition.hpp deleted file mode 100644 index 5fc22554..00000000 --- a/src/dsl/fusion/has_precondition.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2014 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_HAS_PRECONDITION_HPP -#define NUCLEAR_DSL_FUSION_HAS_PRECONDITION_HPP - -#include "../../threading/ReactionTask.hpp" -#include "NoOp.hpp" - -namespace NUClear { -namespace dsl { - namespace fusion { - - /** - * SFINAE struct to test if the passed class has a precondition function that conforms to the NUClear DSL. - * - * @tparam T the class to check - */ - template - struct has_precondition { - private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) - -> decltype(U::template precondition(std::declval()), yes()); - template - static no test(...); - - public: - static constexpr bool value = std::is_same(0)), yes>::value; - }; - - } // namespace fusion -} // namespace dsl -} // namespace NUClear - -#endif // NUCLEAR_DSL_FUSION_HAS_PRECONDITION_HPP diff --git a/src/dsl/fusion/has_priority.hpp b/src/dsl/fusion/has_priority.hpp deleted file mode 100644 index 6d97e01b..00000000 --- a/src/dsl/fusion/has_priority.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2014 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_HAS_PRIORITY_HPP -#define NUCLEAR_DSL_FUSION_HAS_PRIORITY_HPP - -#include "../../threading/ReactionTask.hpp" -#include "NoOp.hpp" - -namespace NUClear { -namespace dsl { - namespace fusion { - - /** - * SFINAE struct to test if the passed class has a priority function that conforms to the NUClear DSL. - * - * @tparam T the class to check - */ - template - struct has_priority { - private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) - -> decltype(U::template priority(std::declval()), yes()); - template - static no test(...); - - public: - static constexpr bool value = std::is_same(0)), yes>::value; - }; - - } // namespace fusion -} // namespace dsl -} // namespace NUClear - -#endif // NUCLEAR_DSL_FUSION_HAS_PRIORITY_HPP diff --git a/src/dsl/fusion/has_run_inline.hpp b/src/dsl/fusion/has_run_inline.hpp deleted file mode 100644 index 3b24b583..00000000 --- a/src/dsl/fusion/has_run_inline.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2014 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_HAS_RUN_INLINE_HPP -#define NUCLEAR_DSL_FUSION_HAS_RUN_INLINE_HPP - -#include "../../threading/ReactionTask.hpp" -#include "NoOp.hpp" - -namespace NUClear { -namespace dsl { - namespace fusion { - - /** - * SFINAE struct to test if the passed class has a run_inline function that conforms to the NUClear DSL. - * - * @tparam T the class to check - */ - template - struct has_run_inline { - private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) - -> decltype(U::template run_inline(std::declval()), yes()); - template - static no test(...); - - public: - static constexpr bool value = std::is_same(0)), yes>::value; - }; - - } // namespace fusion -} // namespace dsl -} // namespace NUClear - -#endif // NUCLEAR_DSL_FUSION_HAS_RUN_INLINE_HPP From 3350a93a13d6841e8220eba098287af15ef1781b Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Sat, 26 Oct 2024 14:28:12 +1100 Subject: [PATCH 3/3] Add a scope concept to the DSL words (#156) Adds a new concept called `scope` to the NUClear DSL. This concept can be used to add words which will be able to know when they are running within a specific reaction. 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. Then when another system needs to know if it is running in that context it can call the in_scope function. --- src/Reactor.hpp | 8 + src/dsl/Fusion.hpp | 10 +- src/dsl/Parse.hpp | 27 +++- src/dsl/fusion/NoOp.hpp | 20 ++- ...tconditionFusion.hpp => PostRunFusion.hpp} | 36 ++--- src/dsl/fusion/PreRunFusion.hpp | 76 +++++++++ src/dsl/fusion/ScopeFusion.hpp | 84 ++++++++++ src/dsl/word/Always.hpp | 2 +- src/dsl/word/IO.hpp | 2 +- src/dsl/word/Once.hpp | 4 +- src/dsl/word/TaskScope.hpp | 108 +++++++++++++ src/util/CallbackGenerator.hpp | 10 +- tests/tests/dsl/TaskScope.cpp | 145 ++++++++++++++++++ 13 files changed, 491 insertions(+), 41 deletions(-) rename src/dsl/fusion/{PostconditionFusion.hpp => PostRunFusion.hpp} (63%) create mode 100644 src/dsl/fusion/PreRunFusion.hpp create mode 100644 src/dsl/fusion/ScopeFusion.hpp create mode 100644 src/dsl/word/TaskScope.hpp create mode 100644 tests/tests/dsl/TaskScope.cpp 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)); + } + } +}