diff --git a/docs/dsl.rst b/docs/dsl.rst index a5fd140a..91757951 100644 --- a/docs/dsl.rst +++ b/docs/dsl.rst @@ -207,9 +207,9 @@ Scope::LOCAL ```````````` .. doxygenstruct:: NUClear::dsl::word::emit::Local -Scope::DIRECT +Scope::INLINE ````````````` -.. doxygenstruct:: NUClear::dsl::word::emit::Direct +.. doxygenstruct:: NUClear::dsl::word::emit::Inline Scope::Initialise `````````````````` diff --git a/docs/extension.rst b/docs/extension.rst index 3bbbe0f6..55ff8d12 100644 --- a/docs/extension.rst +++ b/docs/extension.rst @@ -31,9 +31,9 @@ passed in. It is important to note that the type will only be considered by NUCl attributes need to be stored in the DSL word type template it and use static variables, see `Sync`. There are DSL words that are not meant to be used directly but as a part of other words, see `CacheGet` and `TypeBind`. -`TypeBind` adds the reaction to the list of reactions to be run when a `Local` or `Direct` emit is called for the data +`TypeBind` adds the reaction to the list of reactions to be run when a `Local` or `Inline` emit is called for the data type. `CacheGet` gets the last value from a thread-local cache (see `ThreadSore` below) this cache is usually populated -in the last a `Local` or `Direct` emit call for the data type. +in the last a `Local` or `Inline` emit call for the data type. If the type you want to become a DSL extension word is not defined within your control specialise `DSLProxy<>` with the type. Provide the template methods to the specialisation of `DSLProxy<>` as if it were the type. @@ -56,7 +56,7 @@ destructor. e.g. for the `IO` word we have .. codeblock:: c++ reaction->unbinders.push_back([](const threading::Reaction& r) { - r.reactor.emit(std::make_unique>(r.id)); + r.reactor.emit(std::make_unique>(r.id)); }); which will tell the extension reactor that this reaction no longer exists. diff --git a/docs/startup.rst b/docs/startup.rst index 01cc0554..da7cf231 100644 --- a/docs/startup.rst +++ b/docs/startup.rst @@ -63,10 +63,10 @@ Data Emission Statements As the system is single threaded at this time, the order in which reactors are installed can be significantly important. This is pertinent when dealing with any data emissions during reactor construction which are NOT emitted under -:ref:`Scope::Initialise`. For example; data emission during the construction of a reactor using :ref:`Scope::DIRECT`, +:ref:`Scope::Initialise`. For example; data emission during the construction of a reactor using :ref:`Scope::INLINE`, :ref:`Scope::UDP`, or :ref:`Scope::Network` will trigger any necessary activity to run inline. Should any reactions be defined to run as a result of the emission, the task will be generated and also run inline. It is here where the order -in which reactors are installed becomes important. Suppose Reactor1 were to emit under :ref:`Scope::DIRECT`, and +in which reactors are installed becomes important. Suppose Reactor1 were to emit under :ref:`Scope::INLINE`, and Reactor2 had a reaction defined to run on the associated datatype. In this case, the reaction defined by Reactor2 would not run, as it was not yet defined at the time of data emission. However, should the roles be reserved, then the reaction would run. @@ -95,7 +95,7 @@ reaction would run. to use :ref:`Scope::Initialise`. This will put a hold on the data emission, until the next step in the process :ref:`Initialise Scope Tasks`, ensuring that any reactions subscribed to the emission will run. - Anything else?** Emissions during the construction of reactors using :ref:`Scope::DIRECT`, :ref:`Scope::UDP` and + Anything else?** Emissions during the construction of reactors using :ref:`Scope::INLINE`, :ref:`Scope::UDP` and :ref:`Scope::Network` will trigger any reactions (which have already been defined - before the data emission) and force any associated tasks to run inline. @@ -164,7 +164,7 @@ Any on<:ref:`Shutdown`>() reaction requests will then be queued (in the order in :ref:`Priority`::IDLE. Note that during this phase, any other task which would normally be scheduled as a result of a non-direct emission will -be silently dropped, while any tasks which would occur as a result of a :ref:`Scope::DIRECT` emission will interrupt the +be silently dropped, while any tasks which would occur as a result of a :ref:`Scope::INLINE` emission will interrupt the shutdown process and run as normal. .. todo:: @@ -184,7 +184,7 @@ Emissions Scope Table +==========================+=============================================================================================================================================================================================================================================================================================================================================+======================================================================================================================================================================================================================+=====================================================================================================================================================================================================================+ | :ref:`Scope::LOCAL` | Schedules any tasks for reactions which are currently loaded and bound to the emission data. Adds to the queue of tasks to start running when the system shifts to the :ref:`Execution Phase (multithreaded)` | Schedules any tasks for reactions which are bound to the emission data. Adds to the queue of tasks based on the desired :ref:`Priority` level | Any emissions under this scope while the system is in the shutdown phase are ignored. | +--------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | :ref:`Scope::DIRECT` | Schedules any tasks for reactions which are currently loaded and bound to the emission data. Pauses the initialization phase, and runs the task in-line. The initialization phase continues upon task completion. | Schedules any tasks for reactions which are currently loaded and bound to the emission data. Pauses the task currently executing and runs the new task in-line. The execution phase continues upon task completion. | Schedules any tasks for reactions which are currently loaded and bound to the emission data. Pauses the task currently executing and runs the new task in-line. The shutdown phase continues upon task completion. | + | :ref:`Scope::INLINE` | Schedules any tasks for reactions which are currently loaded and bound to the emission data. Pauses the initialization phase, and runs the task in-line. The initialization phase continues upon task completion. | Schedules any tasks for reactions which are currently loaded and bound to the emission data. Pauses the task currently executing and runs the new task in-line. The execution phase continues upon task completion. | Schedules any tasks for reactions which are currently loaded and bound to the emission data. Pauses the task currently executing and runs the new task in-line. The shutdown phase continues upon task completion. | +--------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Scope::Initialise` | Data emitted under this scope during this phase will wait until all reactors have been installed into the powerPlant before triggering any reactions. Any tasks generated as a result of this emission type are the first tasks to run when the powerPlant starts. This is the recommended emission type for this phase of system startup. | Any emissions under this scope while the system is in the execution phase are ignored. | Any emissions under this scope while the system is in the shutdown phase are ignored. | +--------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/src/PowerPlant.cpp b/src/PowerPlant.cpp index 09eabc2b..fc3f6e90 100644 --- a/src/PowerPlant.cpp +++ b/src/PowerPlant.cpp @@ -29,7 +29,7 @@ #include "dsl/store/DataStore.hpp" #include "dsl/word/Shutdown.hpp" #include "dsl/word/Startup.hpp" -#include "dsl/word/emit/Direct.hpp" +#include "dsl/word/emit/Inline.hpp" #include "extension/ChronoController.hpp" #include "extension/IOController.hpp" #include "extension/NetworkController.hpp" @@ -80,9 +80,9 @@ PowerPlant::~PowerPlant() { void PowerPlant::start() { - // Direct emit startup event and command line arguments - emit(std::make_unique()); - emit_shared(dsl::store::DataStore::get()); + // Inline emit startup event and command line arguments + emit(std::make_unique()); + emit_shared(dsl::store::DataStore::get()); // Start all of the threads scheduler.start(); @@ -98,16 +98,16 @@ void PowerPlant::remove_idle_task(const NUClear::id_t& id, scheduler.remove_idle_task(id, pool_descriptor); } -void PowerPlant::submit(std::unique_ptr&& task, const bool& immediate) noexcept { - scheduler.submit(std::move(task), immediate); +void PowerPlant::submit(std::unique_ptr&& task) noexcept { + scheduler.submit(std::move(task)); } void PowerPlant::log(const LogLevel& level, std::string message) { // Get the current task const auto* current_task = threading::ReactionTask::get_current_task(); - // Direct emit the log message so that any direct loggers can use it - emit(std::make_unique( + // Inline emit the log message to default handlers to pause the current task until the log message is processed + emit(std::make_unique( level, current_task != nullptr ? current_task->parent->reactor.log_level : LogLevel::UNKNOWN, std::move(message), diff --git a/src/PowerPlant.hpp b/src/PowerPlant.hpp index becceb76..c7dc6969 100644 --- a/src/PowerPlant.hpp +++ b/src/PowerPlant.hpp @@ -179,9 +179,8 @@ class PowerPlant { * Submits a new task to the ThreadPool to be queued and then executed. * * @param task The Reaction task to be executed in the thread pool - * @param immediate If this task should run immediately in the current thread */ - void submit(std::unique_ptr&& task, const bool& immediate = false) noexcept; + void submit(std::unique_ptr&& task) noexcept; /** * Log a message through NUClear's system. diff --git a/src/Reactor.hpp b/src/Reactor.hpp index 43bb2cbd..d21740d5 100644 --- a/src/Reactor.hpp +++ b/src/Reactor.hpp @@ -75,6 +75,8 @@ namespace dsl { struct NetworkSource; + struct Inline; + template struct Trigger; @@ -112,7 +114,7 @@ namespace dsl { template struct Local; template - struct Direct; + struct Inline; template struct Delay; template @@ -233,6 +235,9 @@ class Reactor { /// @copydoc dsl::word::Network using NetworkSource = dsl::word::NetworkSource; + /// @copydoc dsl::word::Inline + using Inline = dsl::word::Inline; + /// @copydoc dsl::word::Shutdown using Shutdown = dsl::word::Shutdown; @@ -281,9 +286,9 @@ class Reactor { template using LOCAL = dsl::word::emit::Local; - /// @copydoc dsl::word::emit::Direct + /// @copydoc dsl::word::emit::Inline template - using DIRECT = dsl::word::emit::Direct; + using INLINE = dsl::word::emit::Inline; /// @copydoc dsl::word::emit::Delay template @@ -456,6 +461,7 @@ class Reactor { #include "dsl/word/Group.hpp" #include "dsl/word/IO.hpp" #include "dsl/word/Idle.hpp" +#include "dsl/word/Inline.hpp" #include "dsl/word/Last.hpp" #include "dsl/word/MainThread.hpp" #include "dsl/word/Network.hpp" @@ -473,8 +479,8 @@ class Reactor { #include "dsl/word/Watchdog.hpp" #include "dsl/word/With.hpp" #include "dsl/word/emit/Delay.hpp" -#include "dsl/word/emit/Direct.hpp" #include "dsl/word/emit/Initialise.hpp" +#include "dsl/word/emit/Inline.hpp" #include "dsl/word/emit/Local.hpp" #include "dsl/word/emit/Network.hpp" #include "dsl/word/emit/UDP.hpp" diff --git a/src/dsl/Fusion.hpp b/src/dsl/Fusion.hpp index 342b264d..ab5ce5d9 100644 --- a/src/dsl/Fusion.hpp +++ b/src/dsl/Fusion.hpp @@ -27,6 +27,7 @@ #include "fusion/BindFusion.hpp" #include "fusion/GetFusion.hpp" #include "fusion/GroupFusion.hpp" +#include "fusion/InlineFusion.hpp" #include "fusion/PoolFusion.hpp" #include "fusion/PostconditionFusion.hpp" #include "fusion/PreconditionFusion.hpp" @@ -40,9 +41,10 @@ namespace dsl { struct Fusion : fusion::BindFusion , fusion::GetFusion + , fusion::GroupFusion + , fusion::InlineFusion , fusion::PreconditionFusion , fusion::PriorityFusion - , fusion::GroupFusion , fusion::PoolFusion , fusion::PostconditionFusion {}; diff --git a/src/dsl/Parse.hpp b/src/dsl/Parse.hpp index 8cfb85bf..58983ab8 100644 --- a/src/dsl/Parse.hpp +++ b/src/dsl/Parse.hpp @@ -47,6 +47,16 @@ namespace dsl { task); } + static std::set> group(threading::ReactionTask& task) { + return std::conditional_t::value, DSL, fusion::NoOp>::template group< + Parse>(task); + } + + static util::Inline run_inline(threading::ReactionTask& task) { + return std::conditional_t::value, DSL, fusion::NoOp>::template run_inline< + Parse>(task); + } + static bool precondition(threading::ReactionTask& task) { return std::conditional_t::value, DSL, fusion::NoOp>::template precondition< Parse>(task); @@ -57,11 +67,6 @@ namespace dsl { Parse>(task); } - static std::set> group(threading::ReactionTask& task) { - return std::conditional_t::value, DSL, fusion::NoOp>::template group< - Parse>(task); - } - static std::shared_ptr pool(threading::ReactionTask& task) { return std::conditional_t::value, DSL, fusion::NoOp>::template pool< Parse>(task); diff --git a/src/dsl/fusion/InlineFusion.hpp b/src/dsl/fusion/InlineFusion.hpp new file mode 100644 index 00000000..c73884e1 --- /dev/null +++ b/src/dsl/fusion/InlineFusion.hpp @@ -0,0 +1,79 @@ +/* + * 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_INLINABLE_FUSION_HPP +#define NUCLEAR_DSL_FUSION_INLINABLE_FUSION_HPP + +#include "../../threading/ReactionTask.hpp" +#include "../../util/Inline.hpp" +#include "../operation/DSLProxy.hpp" +#include "FindWords.hpp" +#include "has_run_inline.hpp" + +namespace NUClear { +namespace dsl { + namespace fusion { + + // Default case where there are no Inline words + template + struct InlineFuser {}; + + // Case where there is only a single word remaining + template + struct InlineFuser> { + + template + static util::Inline run_inline(threading::ReactionTask& task) { + + // Run our remaining run_inline + return Word::template run_inline(task); + } + }; + + // Case where there is more 2 more more words remaining + template + struct InlineFuser> { + + template + static util::Inline run_inline(threading::ReactionTask& task) { + auto a = Word1::template run_inline(task); + auto b = InlineFuser>::template run_inline(task); + + // Must agree or make a choice + if ((a == util::Inline::ALWAYS && b == util::Inline::NEVER) + || (a == util::Inline::NEVER && b == util::Inline::ALWAYS)) { + throw std::logic_error("Cannot both always and never inline a reaction"); + } + + // Otherwise return the one that made a choice + return a == util::Inline::NEUTRAL ? b : a; + } + }; + + template + struct InlineFusion : InlineFuser> {}; + + } // namespace fusion +} // namespace dsl +} // namespace NUClear + +#endif // NUCLEAR_DSL_FUSION_INLINABLE_FUSION_HPP diff --git a/src/dsl/fusion/NoOp.hpp b/src/dsl/fusion/NoOp.hpp index c9f6be61..7ee1d03e 100644 --- a/src/dsl/fusion/NoOp.hpp +++ b/src/dsl/fusion/NoOp.hpp @@ -28,6 +28,7 @@ #include "../../threading/Reaction.hpp" #include "../../threading/ReactionTask.hpp" #include "../../util/GroupDescriptor.hpp" +#include "../../util/Inline.hpp" #include "../../util/ThreadPoolDescriptor.hpp" #include "../word/Pool.hpp" #include "../word/Priority.hpp" @@ -52,6 +53,17 @@ namespace dsl { return {}; } + template + static std::set> group( + const threading::ReactionTask& /*task*/) { + return {}; + } + + template + static util::Inline run_inline(const threading::ReactionTask& /*task*/) { + return util::Inline::NEUTRAL; + } + template static bool precondition(const threading::ReactionTask& /*task*/) { return true; @@ -62,12 +74,6 @@ namespace dsl { return word::Priority::NORMAL::value; } - template - static std::set> group( - const threading::ReactionTask& /*task*/) { - return {}; - } - template static std::shared_ptr pool(const threading::ReactionTask& /*task*/) { return word::Pool<>::descriptor(); @@ -90,12 +96,14 @@ namespace dsl { static std::tuple<> get(threading::ReactionTask&); + static std::set> group(threading::ReactionTask&); + + static util::Inline run_inline(threading::ReactionTask&); + static bool precondition(threading::ReactionTask&); static int priority(threading::ReactionTask&); - static std::set> group(threading::ReactionTask&); - static std::shared_ptr pool(threading::ReactionTask&); static void postcondition(threading::ReactionTask&); diff --git a/src/dsl/fusion/has_run_inline.hpp b/src/dsl/fusion/has_run_inline.hpp new file mode 100644 index 00000000..3b24b583 --- /dev/null +++ b/src/dsl/fusion/has_run_inline.hpp @@ -0,0 +1,58 @@ +/* + * 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 diff --git a/src/dsl/word/Always.hpp b/src/dsl/word/Always.hpp index d08d8613..24506c33 100644 --- a/src/dsl/word/Always.hpp +++ b/src/dsl/word/Always.hpp @@ -30,6 +30,7 @@ #include "../../id.hpp" #include "../../threading/ReactionIdentifiers.hpp" #include "../../threading/ReactionTask.hpp" +#include "../../util/Inline.hpp" #include "../../util/ThreadPoolDescriptor.hpp" namespace NUClear { @@ -89,6 +90,11 @@ namespace dsl { return pools.at(reaction.id); } + template + static util::Inline run_inline(const threading::ReactionTask& /*task*/) { + return util::Inline::NEVER; + } + template static void bind(const std::shared_ptr& reaction) { @@ -124,7 +130,9 @@ namespace dsl { auto idle_task = std::make_unique( reaction, + false, [](threading::ReactionTask& task) { return DSL::priority(task) - 1; }, + DSL::run_inline, DSL::pool, DSL::group); diff --git a/src/dsl/word/Every.hpp b/src/dsl/word/Every.hpp index 91639eab..2802b9d3 100644 --- a/src/dsl/word/Every.hpp +++ b/src/dsl/word/Every.hpp @@ -28,7 +28,7 @@ #include "../../threading/Reaction.hpp" #include "../operation/ChronoTask.hpp" #include "../operation/Unbind.hpp" -#include "emit/Direct.hpp" +#include "emit/Inline.hpp" namespace NUClear { namespace dsl { @@ -85,11 +85,11 @@ namespace dsl { static void bind(const std::shared_ptr& reaction, NUClear::clock::duration jump) { reaction->unbinders.push_back([](const threading::Reaction& r) { - r.reactor.emit(std::make_unique>(r.id)); + r.reactor.emit(std::make_unique>(r.id)); }); // Send our configuration out - reaction->reactor.emit(std::make_unique( + reaction->reactor.emit(std::make_unique( [reaction, jump](NUClear::clock::time_point& time) { // submit the reaction to the thread pool reaction->reactor.powerplant.submit(reaction->get_task()); diff --git a/src/dsl/word/IO.hpp b/src/dsl/word/IO.hpp index bd3a75f2..9b6e2648 100644 --- a/src/dsl/word/IO.hpp +++ b/src/dsl/word/IO.hpp @@ -30,7 +30,7 @@ #include "../store/ThreadStore.hpp" #include "../trait/is_transient.hpp" #include "Single.hpp" -#include "emit/Direct.hpp" +#include "emit/Inline.hpp" namespace NUClear { namespace dsl { @@ -132,13 +132,13 @@ namespace dsl { static void bind(const std::shared_ptr& reaction, fd_t fd, event_t watch_set) { reaction->unbinders.push_back([](const threading::Reaction& r) { - r.reactor.emit(std::make_unique>(r.id)); + r.reactor.emit(std::make_unique>(r.id)); }); auto io_config = std::make_unique(fd, watch_set, reaction); // Send our configuration out - reaction->reactor.emit(io_config); + reaction->reactor.emit(io_config); } template @@ -155,7 +155,7 @@ namespace dsl { template static void postcondition(threading::ReactionTask& task) { - task.parent->reactor.emit(std::make_unique(task.parent->id)); + task.parent->reactor.emit(std::make_unique(task.parent->id)); } }; diff --git a/src/dsl/word/Idle.hpp b/src/dsl/word/Idle.hpp index 50e85dd7..7b3763f3 100644 --- a/src/dsl/word/Idle.hpp +++ b/src/dsl/word/Idle.hpp @@ -71,7 +71,7 @@ namespace dsl { static void bind(const std::shared_ptr& reaction) { // Make a fake task to use for finding an appropriate descriptor - threading::ReactionTask task(reaction, DSL::priority, DSL::pool, DSL::group); + threading::ReactionTask task(reaction, false, DSL::priority, DSL::run_inline, DSL::pool, DSL::group); bind_idle(reaction, PoolType::template pool(task)); } }; diff --git a/src/dsl/word/Inline.hpp b/src/dsl/word/Inline.hpp new file mode 100644 index 00000000..88922fff --- /dev/null +++ b/src/dsl/word/Inline.hpp @@ -0,0 +1,72 @@ +/* + * MIT License + * + * Copyright (c) 2016 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_INLINE_HPP +#define NUCLEAR_DSL_WORD_INLINE_HPP + +#include "../../threading/ReactionTask.hpp" +#include "../../util/Inline.hpp" + +namespace NUClear { +namespace dsl { + namespace word { + + struct Inline { + + /** + * This word is used to specify that a reaction should be executed inline even if not emitted inline. + * + * @code on, Inline::ALWAYS>>() @endcode + * When this keyword is used, the reaction will always be inlined if it is not emitted using an inline emit. + * + * @par Implements + * Inline + */ + struct ALWAYS { + template + static util::Inline run_inline(const threading::ReactionTask& /*task*/) { + return util::Inline::ALWAYS; + } + }; + + /** + * This word is used to specify that a reaction should not be inlined. + * + * @code on, Inline::NEVER>>() @endcode + * When this keyword is used, the reaction will not be inlined if it is emitted using an inline emit. + * + * @par Implements + * Inline + */ + struct NEVER { + template + static util::Inline run_inline(const threading::ReactionTask& /*task*/) { + return util::Inline::NEVER; + } + }; + }; + + } // namespace word +} // namespace dsl +} // namespace NUClear + +#endif // NUCLEAR_DSL_WORD_INLINE_HPP diff --git a/src/dsl/word/Network.hpp b/src/dsl/word/Network.hpp index 4e064fc5..0f371fc8 100644 --- a/src/dsl/word/Network.hpp +++ b/src/dsl/word/Network.hpp @@ -80,12 +80,12 @@ namespace dsl { task->hash = util::serialise::Serialise::hash(); reaction->unbinders.push_back([](const threading::Reaction& r) { - r.reactor.emit(std::make_unique>(r.id)); + r.reactor.emit(std::make_unique>(r.id)); }); task->reaction = reaction; - reaction->reactor.emit(task); + reaction->reactor.emit(task); } template diff --git a/src/dsl/word/Watchdog.hpp b/src/dsl/word/Watchdog.hpp index 2f49c698..bdff51fa 100644 --- a/src/dsl/word/Watchdog.hpp +++ b/src/dsl/word/Watchdog.hpp @@ -30,7 +30,7 @@ #include "../operation/ChronoTask.hpp" #include "../operation/Unbind.hpp" #include "../store/DataStore.hpp" -#include "emit/Direct.hpp" +#include "emit/Inline.hpp" namespace NUClear { namespace dsl { @@ -213,11 +213,11 @@ namespace dsl { reaction->unbinders.push_back([data](const threading::Reaction& r) { // Remove the active service time from the data store WatchdogDataStore::unbind(data); - r.reactor.emit(std::make_unique>(r.id)); + r.reactor.emit(std::make_unique>(r.id)); }); // Send our configuration out - reaction->reactor.emit(std::make_unique( + reaction->reactor.emit(std::make_unique( [reaction, data](NUClear::clock::time_point& time) { return Watchdog::chrono_task(reaction, WatchdogDataStore::get(data), @@ -240,11 +240,11 @@ namespace dsl { reaction->unbinders.push_back([](const threading::Reaction& r) { // Remove the active service time from the data store WatchdogDataStore::unbind(); - r.reactor.emit(std::make_unique>(r.id)); + r.reactor.emit(std::make_unique>(r.id)); }); // Send our configuration out - reaction->reactor.emit(std::make_unique( + reaction->reactor.emit(std::make_unique( [reaction](NUClear::clock::time_point& time) { return Watchdog::chrono_task(reaction, WatchdogDataStore::get(), time); }, diff --git a/src/dsl/word/emit/Delay.hpp b/src/dsl/word/emit/Delay.hpp index b01424d3..96394487 100644 --- a/src/dsl/word/emit/Delay.hpp +++ b/src/dsl/word/emit/Delay.hpp @@ -24,7 +24,7 @@ #define NUCLEAR_DSL_WORD_EMIT_DELAY_HPP #include "../../operation/ChronoTask.hpp" -#include "Direct.hpp" +#include "Inline.hpp" namespace NUClear { namespace dsl { @@ -63,7 +63,7 @@ namespace dsl { -1); // Our ID is -1 as we will remove ourselves // Send this straight to the chrono controller - emit::Direct::emit(powerplant, msg); + emit::Inline::emit(powerplant, msg); } static void emit(PowerPlant& powerplant, @@ -83,7 +83,7 @@ namespace dsl { -1); // Our ID is -1 as we will remove ourselves // Send this straight to the chrono controller - emit::Direct::emit(powerplant, msg); + emit::Inline::emit(powerplant, msg); } }; diff --git a/src/dsl/word/emit/Initialise.hpp b/src/dsl/word/emit/Initialise.hpp index 7f5cf439..cd013fdd 100644 --- a/src/dsl/word/emit/Initialise.hpp +++ b/src/dsl/word/emit/Initialise.hpp @@ -58,7 +58,9 @@ namespace dsl { // Make a floating reaction task to submit which will emit this data auto emitter = std::make_unique( nullptr, + false, [](threading::ReactionTask& /*task*/) { return 1000; }, + [](threading::ReactionTask& /*task*/) { return util::Inline::NEVER; }, [](threading::ReactionTask& /*task*/) { return Pool<>::descriptor(); }, [](threading::ReactionTask& /*task*/) { return std::set>{}; diff --git a/src/dsl/word/emit/Direct.hpp b/src/dsl/word/emit/Inline.hpp similarity index 86% rename from src/dsl/word/emit/Direct.hpp rename to src/dsl/word/emit/Inline.hpp index 84cbe4a2..c20234b2 100644 --- a/src/dsl/word/emit/Direct.hpp +++ b/src/dsl/word/emit/Inline.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_WORD_EMIT_DIRECT_HPP -#define NUCLEAR_DSL_WORD_EMIT_DIRECT_HPP +#ifndef NUCLEAR_DSL_WORD_EMIT_INLINE_HPP +#define NUCLEAR_DSL_WORD_EMIT_INLINE_HPP #include "../../../PowerPlant.hpp" #include "../../store/DataStore.hpp" @@ -35,9 +35,10 @@ namespace dsl { /** * When emitting data under this scope, the tasks created as a result of this emission will bypass the - * thread pool, and be executed immediately. + * thread pool, and be executed immediately if they can be. + * If a task specifies that it is not inlinable, it will be executed in the thread pool as normal. * - * @code emit(data, dataType); @endcode + * @code emit(data, dataType); @endcode * When data is emitted via this scope, the task which is currently executing will be paused. * At this time any tasks created as a result of this emission are executed one at a time sequentially, * using the current thread. @@ -52,15 +53,15 @@ namespace dsl { * @param data The data to emit */ template - struct Direct { + struct Inline { static void emit(PowerPlant& powerplant, std::shared_ptr data) { // Run all our reactions that are interested for (auto& reaction : store::TypeCallbackStore::get()) { - // Set our thread local store data each time (as during direct it can be overwritten) + // Set our thread local store data each time (as during inline it can be overwritten) store::ThreadStore>::value = &data; - powerplant.submit(reaction->get_task(), true); + powerplant.submit(reaction->get_task(true)); } // Unset our thread local store data @@ -76,4 +77,4 @@ namespace dsl { } // namespace dsl } // namespace NUClear -#endif // NUCLEAR_DSL_WORD_EMIT_DIRECT_HPP +#endif // NUCLEAR_DSL_WORD_EMIT_INLINE_HPP diff --git a/src/dsl/word/emit/Network.hpp b/src/dsl/word/emit/Network.hpp index 57374001..84d17997 100644 --- a/src/dsl/word/emit/Network.hpp +++ b/src/dsl/word/emit/Network.hpp @@ -90,7 +90,7 @@ namespace dsl { e->payload = util::serialise::Serialise::serialise(*data); e->reliable = reliable; - powerplant.emit(e); + powerplant.emit(e); } static void emit(PowerPlant& powerplant, std::shared_ptr data, bool reliable) { diff --git a/src/threading/Reaction.cpp b/src/threading/Reaction.cpp index 778ac0e2..a3b10ba8 100644 --- a/src/threading/Reaction.cpp +++ b/src/threading/Reaction.cpp @@ -39,14 +39,14 @@ namespace threading { Reaction::~Reaction() = default; - std::unique_ptr Reaction::get_task() { + std::unique_ptr Reaction::get_task(const bool& request_inline) { // If we are not enabled, don't run if (!enabled) { return nullptr; } // Return the task returned by the generator - return generator(this->shared_from_this()); + return generator(this->shared_from_this(), request_inline); } void Reaction::unbind() { diff --git a/src/threading/Reaction.hpp b/src/threading/Reaction.hpp index 1e5b7a84..acc8439f 100644 --- a/src/threading/Reaction.hpp +++ b/src/threading/Reaction.hpp @@ -60,7 +60,8 @@ namespace threading { public: // The type of the generator that is used to create functions for ReactionTask objects - using TaskGenerator = std::function(const std::shared_ptr&)>; + using TaskGenerator = + std::function(const std::shared_ptr&, const bool&)>; /** * Constructs a new Reaction with the passed callback generator and options. @@ -83,9 +84,11 @@ namespace threading { /** * Creates a new databound callback task that can be executed. * + * @param request_inline if this is true, attempt to execute the current task inline on the curren thread + * * @return a unique_ptr to a Task which has the data for it's call bound into it */ - std::unique_ptr get_task(); + std::unique_ptr get_task(const bool& request_inline = false); /** * @return `true` if this reaction is currently enabled diff --git a/src/threading/ReactionTask.hpp b/src/threading/ReactionTask.hpp index c451f12e..1fb9ab96 100644 --- a/src/threading/ReactionTask.hpp +++ b/src/threading/ReactionTask.hpp @@ -30,6 +30,7 @@ #include "../clock.hpp" #include "../id.hpp" #include "../util/GroupDescriptor.hpp" +#include "../util/Inline.hpp" #include "../util/ThreadPoolDescriptor.hpp" #include "../util/platform.hpp" #include "Reaction.hpp" @@ -68,19 +69,24 @@ namespace threading { /** * Creates a new ReactionTask object bound with the parent Reaction object (that created it) and task. * - * @param parent The Reaction object that spawned this ReactionTask. - * @param priority_fn A function that can be called to get the priority of this task - * @param thread_pool_fn A function that can be called to get the thread pool descriptor for this task - * @param groups_fn A function that can be called to get the list of group descriptors for this task + * @param parent The Reaction object that spawned this ReactionTask. + * @param request_inline If this task should try to execute inline if should_inline allows it + * @param priority_fn A function that can be called to get the priority of this task + * @param inline_fn A function that can be called to get if this task can be executed inline + * @param thread_pool_fn A function that can be called to get the thread pool descriptor for this task + * @param groups_fn A function that can be called to get the list of group descriptors for this task */ - template + template ReactionTask(const std::shared_ptr& parent, + const bool& request_inline, const GetPriority& priority_fn, + const GetInline& inline_fn, const GetThreadPool& thread_pool_fn, const GetGroups& groups_fn) : parent(parent) , id(next_id()) , priority(priority_fn(*this)) + , should_inline(inline_fn(*this)) , pool_descriptor(thread_pool_fn(*this)) , group_descriptors(groups_fn(*this)) , stats(make_stats()) { @@ -88,6 +94,13 @@ namespace threading { if (parent != nullptr) { parent->active_tasks.fetch_add(1, std::memory_order_release); } + + // Calculate inline running + switch (should_inline) { + case util::Inline::NEVER: run_inline = false; break; + case util::Inline::ALWAYS: run_inline = true; break; + case util::Inline::NEUTRAL: run_inline = request_inline; break; + } } // No copying or moving of tasks (use unique_ptrs to manage tasks) @@ -119,9 +132,13 @@ namespace threading { std::shared_ptr parent; /// The task id of this task (the sequence number of this particular task) NUClear::id_t id; + /// If the task should execute inline + bool run_inline{false}; /// The priority to run this task at int priority; + /// If the task should be executed inline (in the current thread) or not + util::Inline should_inline{util::Inline::NEUTRAL}; /// Details about the thread pool that this task will run from, this will also influence what task queue /// the tasks will be queued on std::shared_ptr pool_descriptor; diff --git a/src/threading/scheduler/Pool.cpp b/src/threading/scheduler/Pool.cpp index 0c21420b..d8d94173 100644 --- a/src/threading/scheduler/Pool.cpp +++ b/src/threading/scheduler/Pool.cpp @@ -26,6 +26,7 @@ #include "../../dsl/word/MainThread.hpp" #include "../../dsl/word/Pool.hpp" #include "../../message/ReactionStatistics.hpp" +#include "../../util/Inline.hpp" #include "../ReactionTask.hpp" #include "CombinedLock.hpp" #include "CountingLock.hpp" @@ -240,13 +241,15 @@ namespace threading { // Make a reaction task which will submit all the idle tasks to the scheduler auto task = std::make_unique( nullptr, + true, [](const ReactionTask&) { return 0; }, + [](const ReactionTask&) { return util::Inline::ALWAYS; }, [](const ReactionTask&) { return dsl::word::Pool<>::descriptor(); }, [](const ReactionTask&) { return std::set>{}; }); - task->callback = [this, tasks = std::move(tasks)](const ReactionTask& /*task*/) { - for (const auto& idle_task : tasks) { + task->callback = [this, t = std::move(tasks)](const ReactionTask& /*task*/) { + for (const auto& idle_task : t) { // Submit all the idle tasks to the scheduler - scheduler.submit(idle_task->get_task(), false); + scheduler.submit(idle_task->get_task()); } }; diff --git a/src/threading/scheduler/Scheduler.cpp b/src/threading/scheduler/Scheduler.cpp index b90f1311..da99dd03 100644 --- a/src/threading/scheduler/Scheduler.cpp +++ b/src/threading/scheduler/Scheduler.cpp @@ -150,7 +150,7 @@ namespace threading { return lock; } - void Scheduler::submit(std::unique_ptr&& task, const bool& immediate) noexcept { + void Scheduler::submit(std::unique_ptr&& task) noexcept { // Ignore null tasks if (task == nullptr) { return; @@ -161,7 +161,7 @@ namespace threading { auto group_lock = get_groups_lock(task->id, task->priority, pool, task->group_descriptors); // If this task should run immediately and not limited by the group lock - if (immediate && (group_lock == nullptr || group_lock->lock())) { + if (task->run_inline && (group_lock == nullptr || group_lock->lock())) { task->run(); return; } diff --git a/src/threading/scheduler/Scheduler.hpp b/src/threading/scheduler/Scheduler.hpp index a984ccd4..51d95980 100644 --- a/src/threading/scheduler/Scheduler.hpp +++ b/src/threading/scheduler/Scheduler.hpp @@ -70,10 +70,8 @@ namespace threading { * be processed. * * @param task the reaction task task to submit - * @param immediate if this task should run immediately in the current thread. If immediate execution of - * this task is not possible (e.g. due to group concurrency restrictions) this task will be queued as normal */ - void submit(std::unique_ptr&& task, const bool& immediate = false) noexcept; + void submit(std::unique_ptr&& task) noexcept; /** * Adds a task to the idle task list. diff --git a/src/util/CallbackGenerator.hpp b/src/util/CallbackGenerator.hpp index f18746b1..db399280 100644 --- a/src/util/CallbackGenerator.hpp +++ b/src/util/CallbackGenerator.hpp @@ -25,7 +25,7 @@ #include -#include "../dsl/word/emit/Direct.hpp" +#include "../dsl/word/emit/Inline.hpp" #include "../message/ReactionStatistics.hpp" #include "../util/MergeTransient.hpp" #include "../util/TransientDataElements.hpp" @@ -68,16 +68,22 @@ namespace util { std::get(data))...); } - std::unique_ptr operator()(const std::shared_ptr& r) { + std::unique_ptr operator()(const std::shared_ptr& r, + const bool& request_inline) { - auto task = std::make_unique(r, DSL::priority, DSL::pool, DSL::group); + auto task = std::make_unique(r, + request_inline, + DSL::priority, + DSL::run_inline, + DSL::pool, + DSL::group); // Check if we should even run if (!DSL::precondition(*task)) { // Set the created status as rejected and emit it if (task->stats != nullptr) { - PowerPlant::powerplant->emit( + PowerPlant::powerplant->emit( std::make_unique(message::ReactionEvent::BLOCKED, task->stats)); } @@ -98,7 +104,7 @@ namespace util { // Set the created status as no data and emit it if (task->stats != nullptr) { - PowerPlant::powerplant->emit( + PowerPlant::powerplant->emit( std::make_unique(message::ReactionEvent::MISSING_DATA, task->stats)); } @@ -108,7 +114,7 @@ namespace util { // Set the created status as no data and emit it if (task->stats != nullptr) { - PowerPlant::powerplant->emit( + PowerPlant::powerplant->emit( std::make_unique(message::ReactionEvent::CREATED, task->stats)); } @@ -120,7 +126,7 @@ namespace util { if (task.stats != nullptr) { task.stats->started = message::ReactionStatistics::Event::now(); - PowerPlant::powerplant->emit( + PowerPlant::powerplant->emit( std::make_unique(message::ReactionEvent::STARTED, task.stats)); } @@ -139,10 +145,9 @@ namespace util { // Run our postconditions DSL::postcondition(task); - // Emit our reaction statistics if it wouldn't cause a loop if (task.stats != nullptr) { task.stats->finished = message::ReactionStatistics::Event::now(); - PowerPlant::powerplant->emit( + PowerPlant::powerplant->emit( std::make_unique(message::ReactionEvent::FINISHED, task.stats)); } }; diff --git a/src/util/Inline.hpp b/src/util/Inline.hpp new file mode 100644 index 00000000..7925438a --- /dev/null +++ b/src/util/Inline.hpp @@ -0,0 +1,49 @@ +/* + * 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_UTIL_INLINE_HPP +#define NUCLEAR_UTIL_INLINE_HPP + +#include +#include +#include +#include +#include + +#include "../id.hpp" + +namespace NUClear { +namespace util { + + enum class Inline : uint8_t { + /// Never inline this reaction, always execute it within its target thread pool + NEVER, + /// Inlining is left to the creator of the reaction + NEUTRAL, + /// Always inline this reaction, even if it was not emitted directly + ALWAYS + }; + +} // namespace util +} // namespace NUClear + +#endif // NUCLEAR_UTIL_INLINE_HPP diff --git a/tests/networktest.cpp b/tests/networktest.cpp index 64c3b713..959dd466 100644 --- a/tests/networktest.cpp +++ b/tests/networktest.cpp @@ -94,7 +94,7 @@ class TestReactor : public NUClear::Reactor { std::cout << "Testing network with node " << net_config->name << std::endl; - emit(net_config); + emit(net_config); emit(std::make_unique()); }); diff --git a/tests/test_util/TestBase.hpp b/tests/test_util/TestBase.hpp index da8e33fc..af5a3dc0 100644 --- a/tests/test_util/TestBase.hpp +++ b/tests/test_util/TestBase.hpp @@ -82,7 +82,7 @@ class TestBase : public NUClear::Reactor { if (!clean_shutdown) { powerplant.shutdown(true); - emit(std::make_unique("Test timed out")); + emit(std::make_unique("Test timed out")); } }); diff --git a/tests/tests/api/TimeTravel.cpp b/tests/tests/api/TimeTravel.cpp index ee71d7cf..12e8d67b 100644 --- a/tests/tests/api/TimeTravel.cpp +++ b/tests/tests/api/TimeTravel.cpp @@ -32,7 +32,7 @@ class TestReactor : public test_util::TestBase { results.zero = Results::TimePair{NUClear::clock::now(), std::chrono::steady_clock::now()}; // Emit a chrono task to run at time EVENT_1_TIME - emit(std::make_unique( + emit(std::make_unique( [this](NUClear::clock::time_point&) { results.events[0] = Results::TimePair{NUClear::clock::now(), std::chrono::steady_clock::now()}; return false; @@ -41,7 +41,7 @@ class TestReactor : public test_util::TestBase { 1)); // Emit a chrono task to run at time EVENT_2_TIME, and shutdown - emit(std::make_unique( + emit(std::make_unique( [this](NUClear::clock::time_point&) { results.events[1] = Results::TimePair{NUClear::clock::now(), std::chrono::steady_clock::now()}; powerplant.shutdown(); @@ -51,7 +51,7 @@ class TestReactor : public test_util::TestBase { 2)); // Time travel! - emit( + emit( std::make_unique(NUClear::clock::time_point(adjustment), rtf, action)); results.start = Results::TimePair{NUClear::clock::now(), std::chrono::steady_clock::now()}; diff --git a/tests/tests/api/TimeTravelFrozen.cpp b/tests/tests/api/TimeTravelFrozen.cpp index 4f2b65a1..d4d71ae9 100644 --- a/tests/tests/api/TimeTravelFrozen.cpp +++ b/tests/tests/api/TimeTravelFrozen.cpp @@ -21,7 +21,7 @@ class TestReactor : public test_util::TestBase { NUClear::clock::set_clock(NUClear::clock::time_point(), 0.0); // Emit a chrono task to run at time EVENT_1_TIME - emit(std::make_unique( + emit(std::make_unique( [this](NUClear::clock::time_point&) { add_event("Event 1"); return false; @@ -30,7 +30,7 @@ class TestReactor : public test_util::TestBase { 1)); // Emit a chrono task to run at time EVENT_2_TIME - emit(std::make_unique( + emit(std::make_unique( [this](NUClear::clock::time_point&) { add_event("Event 2"); return false; @@ -39,7 +39,7 @@ class TestReactor : public test_util::TestBase { 2)); // Time travel - emit( + emit( std::make_unique(NUClear::clock::time_point(adjustment), rtf, action)); // Shutdown after steady clock amount of time diff --git a/tests/tests/dsl/Inline.cpp b/tests/tests/dsl/Inline.cpp new file mode 100644 index 00000000..a078953b --- /dev/null +++ b/tests/tests/dsl/Inline.cpp @@ -0,0 +1,137 @@ +/* + * 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 "test_util/TestBase.hpp" +#include "test_util/TimeUnit.hpp" + +class TestReactor : public test_util::TestBase { +public: + struct SimpleMessage { + SimpleMessage(std::string data) : data(std::move(data)) {} + std::string data; + std::thread::id emitter = std::this_thread::get_id(); + }; + + TestReactor(std::unique_ptr environment) : TestBase(std::move(environment)) { + + on, MainThread, Inline::ALWAYS>().then([this](const SimpleMessage& message) { // + log_interaction(message, "Main Always"); + }); + on, MainThread, Inline::NEVER>().then([this](const SimpleMessage& message) { // + log_interaction(message, "Main Never"); + }); + on, MainThread>().then([this](const SimpleMessage& message) { // + log_interaction(message, "Main Neutral"); + }); + + on, Pool<>, Inline::ALWAYS>().then([this](const SimpleMessage& message) { // + log_interaction(message, "Default Always"); + }); + on, Pool<>, Inline::NEVER>().then([this](const SimpleMessage& message) { // + log_interaction(message, "Default Never"); + }); + on, Pool<>>().then([this](const SimpleMessage& message) { // + log_interaction(message, "Default Neutral"); + }); + + on>, MainThread>().then([this] { + emit(std::make_unique("Main Local")); + emit(std::make_unique("Main Inline")); + std::this_thread::sleep_for(test_util::TimeUnit(2)); // Sleep for a bit to give other threads a chance + }); + on>, Pool<>>().then([this] { + emit(std::make_unique("Default Local")); + emit(std::make_unique("Default Inline")); + std::this_thread::sleep_for(test_util::TimeUnit(2)); // Sleep for a bit to give other threads a chance + }); + + on().then([this] { + emit(std::make_unique>()); + emit(std::make_unique>()); + }); + } + + void log_interaction(const SimpleMessage& source, const std::string& target) { + const std::lock_guard lock(mutex); + const auto& pool = NUClear::threading::scheduler::Pool::current(); + events[source.data][target] = + pool->descriptor->name + " " + + (source.emitter == std::this_thread::get_id() ? "same thread" : "different thread"); + } + + /// A vector of events that have happened + std::mutex mutex; + std::map> events; +}; + + +TEST_CASE("Test the interactions between inline emits and the Inline dsl keyword") { + + NUClear::Configuration config; + config.thread_count = 4; + NUClear::PowerPlant plant(config); + const auto& reactor = plant.install(); + plant.start(); + + const std::vector expected = { + "Default Inline -> Default Always on Default same thread", + "Default Inline -> Default Neutral on Default same thread", + "Default Inline -> Default Never on Default different thread", + "Default Inline -> Main Always on Default same thread", + "Default Inline -> Main Neutral on Default same thread", + "Default Inline -> Main Never on Main different thread", + "Default Local -> Default Always on Default same thread", + "Default Local -> Default Neutral on Default different thread", + "Default Local -> Default Never on Default different thread", + "Default Local -> Main Always on Default same thread", + "Default Local -> Main Neutral on Main different thread", + "Default Local -> Main Never on Main different thread", + "Main Inline -> Default Always on Main same thread", + "Main Inline -> Default Neutral on Main same thread", + "Main Inline -> Default Never on Default different thread", + "Main Inline -> Main Always on Main same thread", + "Main Inline -> Main Neutral on Main same thread", + "Main Inline -> Main Never on Main same thread", + "Main Local -> Default Always on Main same thread", + "Main Local -> Default Neutral on Default different thread", + "Main Local -> Default Never on Default different thread", + "Main Local -> Main Always on Main same thread", + "Main Local -> Main Neutral on Main same thread", + "Main Local -> Main Never on Main same thread", + }; + + std::vector actual; + for (const auto& type : reactor.events) { + for (const auto& event : type.second) { + actual.push_back(type.first + " -> " + event.first + " on " + event.second); + } + } + + // Make an info print the diff in an easy to read way if we fail + INFO(test_util::diff_string(expected, actual)); + + // Check the events fired in order and only those events + REQUIRE(actual == expected); +}