diff --git a/examples/messaging/messaging.cpp b/examples/messaging/messaging.cpp index 92cc1b3..b4033cb 100644 --- a/examples/messaging/messaging.cpp +++ b/examples/messaging/messaging.cpp @@ -1,136 +1,169 @@ /******************************************************************************* * * File: messaging.cpp * * Contents: An example showcasing features related to Messages. * * Copyright 2017 * * Author: David Juhasz (david.juhasz@tuwien.ac.at) * ******************************************************************************/ #include "rosa/config/version.h" -#include "rosa/core/Invoker.hpp" +#include "rosa/core/MessageHandler.hpp" #include "rosa/support/log.h" #include "rosa/support/terminal_colors.h" #include using namespace rosa; using namespace rosa::terminal; // A simple implementation of the Message interface with two hardcoded types. class MyMessage : public Message { const uint8_t U8; // First value const uint16_t U16; // Second value public: MyMessage(const uint8_t U8, const uint16_t U16) : Message(U8, U16), U8(U8), U16(U16) {} const void *getPointerTo(const size_t Pos) const noexcept override { switch (Pos) { default: ROSA_CRITICAL("wrong position"); case 0: return &U8; case 1: return &U16; } } }; +// Another implementation of the Message interface with an AtomValue. class MyAtomMessage : public Message { const AtomValue A; // First value public: template MyAtomMessage(AtomConstant C) : Message(C), A(V) {} const void *getPointerTo(const size_t Pos) const noexcept override { switch (Pos) { default: ROSA_CRITICAL("wrong position"); case 0: return &A; } } }; int main(void) { LOG_INFO_STREAM << library_string() << " -- " << Color::Red << "messaging" << Color::Default << std::endl; - auto &Log = LOG_TRACE_STREAM << std::endl; + auto &Log = LOG_INFO_STREAM << std::endl; + // Message interface. MyMessage Msg(1, 2); Log << "Checking on 'MyMessage(1, 2)' that is of 'Message with " "TypeList<>':" << std::endl << " Size: " << Msg.Size << std::endl << " Pos 0 is uint8_t: " << Msg.isTypeAt(0) << std::endl << " Pos 1 is uint16_t: " << Msg.isTypeAt(1) << std::endl << " Pos 2 is uint32_t: " << Msg.isTypeAt(1) << std::endl << " Value at pos 0: " << PRINTABLE(uint8_t)(Msg.getValueAt(0)) << std::endl << " Value at pos 1: " << Msg.getValueAt(1) << std::endl << std::endl; + // MessageMatcher. using MyMatcher = MsgMatcher; Log << "Matching against 'TypeList':" << std::endl << " matching: " << MyMatcher::doesStronglyMatch(Msg) << std::endl; auto Vs = MyMatcher::extractedValues(Msg); Log << " value: '(" << PRINTABLE(uint8_t)(std::get<0>(Vs)) << ", " << std::get<1>(Vs) << ")'" << std::endl << std::endl; using MyWrongMatcher = MsgMatcher; Log << "Matching against 'TypeList':" << std::endl << " matching: " << MyWrongMatcher::doesStronglyMatch(Msg) << std::endl << std::endl; using MyAtom = AtomConstant; const MyAtom &A = MyAtom::Value; using MyNAtom = AtomConstant; MyAtomMessage AMsg(A); Log << "Checking on 'MyAtomMessage(AtomConstant::Value)' " "that is of 'Message with TypeList>':" << std::endl << " Size: " << AMsg.Size << std::endl << " Pos 0 is 'AtomValue': " << AMsg.isTypeAt(0) << std::endl << " Pos 0 is 'AtomConstant': " << AMsg.isTypeAt(0) << std::endl << std::endl; using MyAtomMatcher = MsgMatcher; Log << "Matching against 'TypeList>':" << std::endl << " matching: " << MyAtomMatcher::doesStronglyMatch(AMsg) << std::endl << " value: '(" << to_string(std::get<0>(MyAtomMatcher::extractedValues(AMsg))) << ")'" << std::endl << std::endl; using MyWrongAtomMatcher = MsgMatcher; Log << "Matching against 'TypeList>':" << std::endl << " matching: " << MyWrongAtomMatcher::doesStronglyMatch(AMsg) << std::endl << std::endl; + // Invoker. auto IP = Invoker::wrap(Invoker::F([&Log](MyAtom) noexcept->void { - Log << "** Lambda-function called via Invoker." << std::endl; + Log << "** Handling 'Message with TypeList'." << std::endl; })); auto &I = *IP; // Get a reference from the pointer. Log << "Invoking a function of signature 'void(MyAtom) noexcept':" << std::endl << " with 'Message with TypeList'" << std::endl << " does Message match Invoker: " << I.match(Msg) << std::endl << " invoking..." << std::endl; I(Msg); Log << " with 'Message with TypeList'..." << std::endl << " does Message match Invoker: " << I.match(AMsg) << std::endl << " invoking..." << std::endl; I(AMsg); + Log << std::endl; + + // MessageHandler. + MessageHandler Handler{ + Invoker::F([&](uint8_t, uint16_t) { + LOG_TRACE("** Handling 'Message with TypeList'"); + }), + Invoker::F([&](MyAtom) { + LOG_TRACE("** Handling 'Message with TypeList'"); + })}; + MyAtomMessage ANMsg(MyNAtom::Value); + Log << "Handling Messages with 'MessageHandler " + "{ Invoker::F, Invoker::F }':" + << std::endl + << " 'Message with TypeList'" << std::endl + << " can handle: " << Handler.canHandle(Msg) << std::endl + << " handling..." << std::endl; + Handler(Msg); + Log << " 'Message with TypeList'" << std::endl + << " can handle: " << Handler.canHandle(AMsg) << std::endl + << " handling..." << std::endl; + Handler(AMsg); + Log << " 'Message with TypeList'" << std::endl + << " can handle: " << Handler.canHandle(ANMsg) << std::endl + << " handling..." << std::endl; + Handler(ANMsg); + Log << std::endl; + + Log << "Terminating, destroying automatic variables." << std::endl; return 0; } diff --git a/include/rosa/core/Invoker.hpp b/include/rosa/core/Invoker.hpp index 82ba840..b344ebe 100644 --- a/include/rosa/core/Invoker.hpp +++ b/include/rosa/core/Invoker.hpp @@ -1,156 +1,158 @@ /******************************************************************************* * * File: Invoker.hpp * * Contents: Definition of Invoker interface and its implementation. * * Copyright 2017 * * Author: David Juhasz (david.juhasz@tuwien.ac.at) * ******************************************************************************/ #ifndef ROSA_CORE_INVOKER_HPP #define ROSA_CORE_INVOKER_HPP #include "rosa/core/MessageMatcher.hpp" #include "rosa/support/log.h" #include #include namespace rosa { // Wraps a function and provides a simple interface to invoke the stored // function by passing actual arguments as a Message. // NOTE: Invoker instances are supposed to be owned by Message handlers, and // not being used directly from user code. class Invoker { protected: // Protected ctor, only subclasses can instantiate. Invoker(void) noexcept; public: // Dtor. virtual ~Invoker(void); // Enumeration of possible results of an invocation. enum class Result { NoMatch, Invoked }; // Type alias for a smart-pointer for Invoker. using invoker_t = std::unique_ptr; // Type alias for Result. using result_t = Result; // Tells if Msg can be used to invoke F. virtual bool match(const Message &Msg) const noexcept = 0; // Invokes F with Msg if Msg can be used to invoke F. virtual result_t operator()(const Message &Msg) const noexcept = 0; // Instantiates an implementation of Invoker with the given function. // NOTE: As there is no empty Message, no Invoker wraps a function without an // argument. template static invoker_t wrap(std::function &&F) noexcept; // Convenience template alias for casting callable stuff to function objects // for wrapping. + // FIXME: Should make it possible to avoid using an explicit conversion for + // the arguments of wrap. template using F = std::function; }; // Nested namespace with Invoker implementation and helper templates, consider // it private. namespace { // Implementation of the Invoker interface, for functions with different // signature. // NOTE: As there is no empty Message, no Invoker wraps a function without an // argument. template class InvokerImpl; // Empty struct just to store a sequence of numbers in compile time as template // arguments. template struct Seq {}; // Sequence generator, the general case when counting down by extending the // sequence. template struct GenSeq : GenSeq {}; // Sequence generator, the terminal case when storing the generated sequence // into Seq. template struct GenSeq<0, S...> { using Type = Seq; }; // Specialization of InvokerImpl template for std::function. // NOTE: No std::function. template class InvokerImpl> final : public Invoker { // Type alias for the stored function. using function_t = std::function; // Type alias for correctly typed argument-tuples as obtained from Messages. using args_t = std::tuple; // Alias for MessageMatcher for the arguments of the stored function. using Matcher = MsgMatcher; // The wrapped function. const function_t F; // Helper function invoking F by unpacking Args with the help of actual // template arguments. // PRE: sizeof...(S) == std::tuple_size::value template inline void invokeFunction(Seq, const args_t &Args) const noexcept; public: // Ctor. InvokerImpl(function_t &&F) noexcept : F(F) { ASSERT(bool(F)); // Sanity check. } // Dtor. ~InvokerImpl(void) = default; // Tells if Msg can be used to invoke F. bool match(const Message &Msg) const noexcept override { return Matcher::doesStronglyMatch(Msg); }; // Invokes F with Msg if Msg can be used to invoke F. result_t operator()(const Message &Msg) const noexcept override { if (match(Msg)) { LOG_TRACE("Invoking with matching arguments"); invokeFunction(typename GenSeq::Type(), Matcher::extractedValues(Msg)); return result_t::Invoked; } else { LOG_TRACE("Tried to invoke with non-matching arguments"); return result_t::NoMatch; } } }; template template void InvokerImpl>::invokeFunction( Seq, const args_t &Args) const noexcept { ASSERT(sizeof...(S) == std::tuple_size::value); // Sanity check. F(std::get(Args)...); } } // End namespace template Invoker::invoker_t Invoker::wrap(std::function &&F) noexcept { return std::unique_ptr( new InvokerImpl>(std::move(F))); } } // End namespace rosa #endif // ROSA_CORE_INVOKER_HPP diff --git a/include/rosa/core/MessageHandler.hpp b/include/rosa/core/MessageHandler.hpp new file mode 100644 index 0000000..c2bb605 --- /dev/null +++ b/include/rosa/core/MessageHandler.hpp @@ -0,0 +1,113 @@ +/******************************************************************************* + * + * File: MessageHandler.hpp + * + * Contents: Definition of MessageHandler interface. + * + * Copyright 2017 + * + * Author: David Juhasz (david.juhasz@tuwien.ac.at) + * + ******************************************************************************/ + +#ifndef ROSA_CORE_MESSAGEHANDLER_HPP +#define ROSA_CORE_MESSAGEHANDLER_HPP + +#include "rosa/core/Invoker.hpp" +#include "rosa/support/log.h" +#include + +namespace rosa { + +// Handles Message instances. A MessageHandler stores Invokers and tries to +// apply Messages to them in the order of definition. The first matching +// Invoker is invoked with the Message, after which handling of Message is +// completed. +// +// For example, consider the following snippet: +// +// MessageHandler { +// Invoker::F([](uint8_t) { /* ... */ }), +// Invoker::F([](uint8_t) { /* Never invoked */ }) +// }; +// +// Applying a Message with TypeList invokes the first function, and +// the second function would never be invoked because any matching Message had +// already been handled by the first one. +class MessageHandler { + + // Using invoker_t from Invoker. + using invoker_t = Invoker::invoker_t; + + // Type alias for a vector storing Invokers. + using invokers_t = std::vector; + + // Stores the Invokers of this MessageHandler. + const invokers_t Invokers; + + // Creates a container with Invokers from the given functions. + template + static inline invokers_t createInvokers(Fun &&F, Funs &&... Fs) noexcept; + + // Wraps F into an Invoker and stores it into I at position Pos. + // PRE: Pos < I.size() + template + static inline void wrapFun(invokers_t &I, const size_t Pos, Fun &&F, + Funs &&... Fs) noexcept; + + // Terminal case for function wrapper. + // PRE: Pos == I.size(); + static inline void wrapFun(invokers_t &I, const size_t Pos) noexcept; + +public: + // Ctor, stores the given functions into the new MessageHandler instance. + template + MessageHandler(Fun &&F, Funs &&... Fs) noexcept; + + virtual ~MessageHandler(void); + + // Tells if there is any Invoker that can handle Msg. + bool canHandle(const Message &Msg) const noexcept; + + // Applies Msg to the first Invoker which can handle Msg, and tells if there + // was any. + // NOTE: This operator finds the first applicable Invoker and invokes it with + // Msg, while the member function canHandle only checks if there is any + // Invoker that can be invoked with Msg. + bool operator()(const Message &Msg) const noexcept; +}; + +template +MessageHandler::MessageHandler(Fun &&F, Funs &&... Fs) noexcept + : Invokers(createInvokers(std::move(F), std::move(Fs)...)) { + LOG_TRACE("MessageHandler is created"); +} + +template +MessageHandler::invokers_t +MessageHandler::createInvokers(Fun &&F, Funs &&... Fs) noexcept { + // Create a container with the required size and get all the functions + // wrapped. + invokers_t I(1 + sizeof...(Funs)); + wrapFun(I, 0, std::move(F), std::move(Fs)...); + return I; +} + +template +void MessageHandler::wrapFun(invokers_t &I, const size_t Pos, Fun &&F, + Funs &&... Fs) noexcept { + ASSERT(Pos < I.size()); // Sanity check. + // Wrap the current function and continue with the rest. + I[Pos] = Invoker::wrap(std::move(F)); + wrapFun(I, Pos + 1, std::move(Fs)...); +} + +void MessageHandler::wrapFun(invokers_t &I, const size_t Pos) noexcept { + ASSERT(Pos == I.size()); // Sanity check. + // Nothing to do here. +} + +} // End namespace rosa + +#endif // ROSA_CORE_MESSAGEHANDLER_HPP + diff --git a/lib/core/CMakeLists.txt b/lib/core/CMakeLists.txt index 5e40be0..55fc488 100644 --- a/lib/core/CMakeLists.txt +++ b/lib/core/CMakeLists.txt @@ -1,10 +1,11 @@ add_library(ROSACore Unit.cpp System.cpp SystemImpl.cpp Message.cpp Invoker.cpp + MessageHandler.cpp ) ROSA_add_library_dependencies(ROSACore ROSASupport) diff --git a/lib/core/MessageHandler.cpp b/lib/core/MessageHandler.cpp new file mode 100644 index 0000000..f6c8348 --- /dev/null +++ b/lib/core/MessageHandler.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* + * + * File: MessageHandler.cpp + * + * Contents: Implementation of non-template part of MessageHandler. + * + * Copyright 2017 + * + * Author: David Juhasz (david.juhasz@tuwien.ac.at) + * + ******************************************************************************/ + +#include "rosa/core/MessageHandler.hpp" +#include + +namespace rosa { + +MessageHandler::~MessageHandler(void) { + LOG_TRACE("Destroying MessageHandler"); +} + +bool MessageHandler::canHandle(const Message &Msg) const noexcept { + return std::any_of(Invokers.begin(), Invokers.end(), + [&Msg](const invoker_t &I) { return I->match(Msg); }); +} + +bool MessageHandler::operator()(const Message &Msg) const noexcept { + return std::any_of(Invokers.begin(), Invokers.end(), + [&Msg](const invoker_t &I) { + return (*I)(Msg) == Invoker::result_t::Invoked; + }); +} + +} // End namespace rosa +