diff --git a/examples/messaging-system/messaging-system.cpp b/examples/messaging-system/messaging-system.cpp index 34ccbdf..cc53fbf 100644 --- a/examples/messaging-system/messaging-system.cpp +++ b/examples/messaging-system/messaging-system.cpp @@ -1,115 +1,144 @@ /******************************************************************************* * * File: messaging-system.cpp * * Contents: A simple example on the MessagingSystem and Agent classes of the * RoSA Core library. * * Copyright 2017 * * Author: David Juhasz (david.juhasz@tuwien.ac.at) * ******************************************************************************/ #include "rosa/config/version.h" #include "rosa/core/Agent.hpp" #include "rosa/core/MessagingSystem.hpp" #include "rosa/support/log.h" #include "rosa/support/terminal_colors.h" #include using namespace rosa; using namespace rosa::terminal; // A dummy wrapper for testing MessagingSystem. // NOTE: Since we test MessagingSystem directly here, we need to get access to // its protected members. That we do by imitating to be a decent subclass of // MessagingSystem, while calling protected member functions on an object of a // type from which we actually don't inherit. struct SystemTester : protected MessagingSystem { - template + template static AgentHandle createMyAgent(MessagingSystem *S, const std::string &Name, - Fun &&F, Funs &&... Fs) { - return ((SystemTester *)S) - ->createAgent(Name, std::move(F), std::move(Fs)...); + Funs &&... Fs) { + return ((SystemTester *)S)->createAgent(Name, std::move(Fs)...); } static void destroyMyAgent(MessagingSystem *S, const AgentHandle &H) { ((SystemTester *)S)->destroyUnit(H.agent()); } +}; - template - static void sendToMyAgent(MessagingSystem *S, const AgentHandle &H, - const Type &T, const Types &... Ts) { - ((SystemTester *)S)->send(H, T, Ts...); - } +// A special Agent with its own state. +class MyAgent : public Agent { +public: + using Tick = AtomConstant; + using Report = AtomConstant; + +private: + size_t Counter; + +public: - template - static void sendToMyAgent(MessagingSystem *S, const AgentHandle &H, Type &&T, - Types &&... Ts) { - ((SystemTester *)S) - ->send(H, std::move(T), std::move(Ts)...); + void handler(Tick) noexcept { + LOG_INFO_STREAM << "MyAgent Tick count: " << ++Counter << std::endl; } + + MyAgent(const AtomValue Kind, const rosa::id_t Id, const std::string &Name, + MessagingSystem &S) + : Agent(Kind, Id, Name, S, Invoker::F([this](Report) noexcept { + LOG_INFO_STREAM << "MyAgent count: " << Counter << std::endl; + }), + THISMEMBER(handler)), + Counter(0) {} }; int main(void) { LOG_INFO_STREAM << library_string() << " -- " << Color::Red << "messaging-system example" << Color::Default << std::endl; std::unique_ptr S = MessagingSystem::createSystem("Sys"); MessagingSystem *SP = S.get(); + LOG_INFO_STREAM << std::endl + << std::endl + << "** Stateless Agents" << std::endl + << std::endl; + AgentHandle Agent1 = SystemTester::createMyAgent( SP, "Agent1", Invoker::F([](const std::string &M) noexcept { LOG_INFO("Agent1: " + M); })); - using PrintAtom = AtomConstant; - using ForwardAtom = AtomConstant; + using Print = AtomConstant; + using Forward = AtomConstant; AgentHandle Agent2 = SystemTester::createMyAgent( - SP, "Agent2", - Invoker::F([](PrintAtom, uint8_t N) noexcept { + SP, "Agent2", Invoker::F([](Print, uint8_t N) noexcept { LOG_INFO("Agent2: " + std::to_string(N)); }), - Invoker::F([&Agent1](ForwardAtom, - uint8_t N) noexcept { + Invoker::F([&Agent1](Forward, uint8_t N) noexcept { if (Agent1) { Agent1.send(std::to_string(N)); } else { LOG_INFO("Agent2 cannot forward: Agent1 is not valid"); } })); LOG_INFO_STREAM << std::endl << "Agent1 is valid: " << bool(Agent1) << std::endl << "Agent2 is valid: " << bool(Agent2) << std::endl; LOG_INFO("Sending a print-message to Agent2..."); - SystemTester::sendToMyAgent(SP, Agent2, PrintAtom::Value, - 42); + SP->send(Agent2, Print::Value, 42); LOG_INFO("Sending a forward-message to Agent2..."); - SystemTester::sendToMyAgent(SP, Agent2, - ForwardAtom::Value, 42); + SP->send(Agent2, Forward::Value, 42); LOG_INFO("Sending an unexpected message to Agent2..."); - SystemTester::sendToMyAgent(SP, Agent2, unit); + SP->send(Agent2, unit); SystemTester::destroyMyAgent(SP, Agent1); LOG_INFO_STREAM << std::endl << "Agent1 is valid: " << bool(Agent1) << std::endl << "Agent2 is valid: " << bool(Agent2) << std::endl; LOG_INFO("Sending a forward-message to Agent2..."); - SystemTester::sendToMyAgent(SP, Agent2, - ForwardAtom::Value, 42); + SP->send(Agent2, Forward::Value, 42); SystemTester::destroyMyAgent(SP, Agent2); + LOG_INFO_STREAM << std::endl + << std::endl + << "** Stateful Agents" << std::endl + << std::endl; + + AgentHandle Agent3 = SystemTester::createMyAgent(SP, "Agent3"); + + for (size_t I = 0; I < 2; ++I) { + LOG_INFO("Sending report-message to Agent3..."); + Agent3.send(MyAgent::Report::Value); + + LOG_INFO("Sending tick-message to Agent3..."); + Agent3.send(MyAgent::Tick::Value); + } + + SystemTester::destroyMyAgent(SP, Agent3); + + LOG_INFO_STREAM << std::endl << std::endl; + return 0; } diff --git a/include/rosa/core/Invoker.hpp b/include/rosa/core/Invoker.hpp index 2b031b2..b980022 100644 --- a/include/rosa/core/Invoker.hpp +++ b/include/rosa/core/Invoker.hpp @@ -1,160 +1,183 @@ /******************************************************************************* * * 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; + + // Convenience template for preparing non-static member functions into + // function objects for wrapping. + template + static inline F M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept; }; +// Convenience preprocessor macro for the typical use of Invoker::M. It can be +// used inside a class to turn a non-static member function into a function +// object capturing this pointer, so using the actual object when handling a +// Message. +// NOTE: FUN is the identifier of the non-static member function to wrap. +// NOTE: Inside the class MyClass, +// use +// THISMEMBER(fun) +// instead of +// Invoker::M(this, &MyClass::fun) +#define THISMEMBER(FUN) \ + Invoker::M(this, &std::decay::type::FUN) + // 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))); } +template +Invoker::F Invoker::M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept { + return [ O, Fun ](Ts... Vs) noexcept->void { (O->*Fun)(Vs...); }; +} + } // End namespace rosa #endif // ROSA_CORE_INVOKER_HPP diff --git a/include/rosa/core/MessagingSystem.hpp b/include/rosa/core/MessagingSystem.hpp index f040563..53acddd 100644 --- a/include/rosa/core/MessagingSystem.hpp +++ b/include/rosa/core/MessagingSystem.hpp @@ -1,93 +1,96 @@ /******************************************************************************* * * File: MessagingSystem.hpp * * Contents: Declaration of the class MessagingSystem. * * Copyright 2017 * * Author: David Juhasz (david.juhasz@tuwien.ac.at) * ******************************************************************************/ #ifndef ROSA_CORE_MESSAGINGSYSTEM_HPP #define ROSA_CORE_MESSAGINGSYSTEM_HPP #include "rosa/core/AgentHandle.hpp" #include "rosa/core/System.hpp" #include "rosa/support/atom.hpp" namespace rosa { // Extends the System interface with features to create Agents and register // Messages for them. class MessagingSystem : public System { friend class AgentHandle; // AgentHandle is our friend. public: // Returns an object implementing the interface defined by the class. static std::unique_ptr createSystem(const std::string &Name) noexcept; private: // Kind used for Unit categorization of Agents. static constexpr AtomValue AgentKind = atom("agent"); protected: // Ctor. MessagingSystem(void) noexcept = default; protected: // Creates an Agent owned by the MessagingSystem and returns a handle for it. + // NOTE: Agent requires at least one Fun for its constructor, but derived + // classes may do not need that. That's the reason of allowing even zero Funs + // for this template function. // STATIC PRE: std::is_base_of::value - template - AgentHandle createAgent(const std::string &Name, Fun &&F, Funs &&... Fs); + template + AgentHandle createAgent(const std::string &Name, Funs &&... Fs); public: // Sends the given Message to the Agent referred by the given AgentHandle. // NOTE: If the given Message cannot be handled by the referred Agent, the // Message is simply ignored. // PRE: isUnitRegistered(H.agent()) virtual void send(const AgentHandle &H, message_t &&M) noexcept = 0; // Convenience template, which creates the Message from the given constant // lvalue references and sends to the Agent. // PRE: isUnitRegistered(H.agent()) template void send(const AgentHandle &H, const Type &T, const Types &... Ts) noexcept; // Convenience template, which creates the Message from the given rvalue // references and sends to the Agent. // PRE: isUnitRegistered(H.agent()) template void send(const AgentHandle &H, Type &&T, Types &&... Ts) noexcept; }; -template -AgentHandle MessagingSystem::createAgent(const std::string &Name, Fun &&F, +template +AgentHandle MessagingSystem::createAgent(const std::string &Name, Funs &&... Fs) { STATIC_ASSERT((std::is_base_of::value), "not an Agent"); Agent &A = createUnit([&](const id_t Id, MessagingSystem &S) noexcept { - return new T(AgentKind, Id, Name, S, std::move(F), std::move(Fs)...); + return new T(AgentKind, Id, Name, S, std::move(Fs)...); }); return {A}; } template void MessagingSystem::send(const AgentHandle &H, const Type &T, const Types &... Ts) noexcept { send(H, Message::create(T, Ts...)); } template void MessagingSystem::send(const AgentHandle &H, Type &&T, Types &&... Ts) noexcept { send(H, Message::create(std::move(T), std::move(Ts)...)); } } // End namespace rosa #endif // ROSA_CORE_MESSAGINGSYSTEM_HPP