// Test101ActorConcepts.cpp
//
// Simplest example of a system. Context diagram consists only
// of Input and Output systems.
//
// We use C++20 Concepts to replace V1 (outdated) policy-based design (PBD)
//
// Composition
//
// V2: design using C++20 Concepts
// V3: V2 + embedded actors
//
// Problem now becomes a pipeline Source -> SUD -> Sink
//
// Summary: this approach is feasible, so worth investigating..
// 1992 .. everything is an object
// 2024 .. everything is an actor
//
// (C) Datasim Education BV 2015-2024
//
#include <string>
#include <iostream>
#include <type_traits>
#include <mutex>
#include <agents.h>
// Interface contract specification
// 1. Each concept is an abstract method
// 2. interface == concept conjunction (e.g. ISUD)
// 1. Define abstract methods (building blocks)
template<typename Message, template <typename Message> class T> // abstract method
concept ISource = requires (T<Message> x) { x.message(); };
template<typename Message, template <typename Message> class T> // abstract method
concept ISink = requires (T<Message> x, const Message& s) { x.print(s); };
// 2. Interface relating to context diagram; cojunctional concepts are an alternative
// to multiple inheritance but no danger of fragile base class problem
template< typename Message, template<typename Message> class Input, template<typename Message> class Output>
concept ISUD = ISource<Message, Input> && ISink<Message, Output>;
// Agent actor-based constraints
template<typename Derived>
concept IActor1 = std::derived_from<Derived, concurrency::agent>;
template<typename T>
concept IActor2 = requires (T x) { x.run(); };
// 3. Interface relating to defining constraints pertaining to Actor technology
// https://learn.microsoft.com/en-us/cpp/parallel/concrt/asynchronous-agents-library?view=msvc-170
template<typename Derived>
concept IActor = IActor1<Derived> && IActor2<Derived>;
// The mediator using template template parameter "trick" => can use generic messages
template <typename Message, template <typename Message> class Source, template <typename Message> class Sink>
requires ISUD<Message, Source, Sink> && IActor<Source<Message>> && IActor<Sink<Message>>
class SUD : public concurrency::agent
{ // SUD is in a chain from Input to Output
private:
concurrency::ISource<Message>& _source; // formerly known as src
concurrency::ITarget<Message>& _target; // formerly known as snk
public:
explicit SUD(concurrency::ISource<Message>& source, concurrency::ITarget<Message>& target)
: _source(source), _target(target) {}
void run()
{
Message info = concurrency::receive(_source);
concurrency::send(_target, info);
done();
}
};
// Instance Systems
template <typename Message>
class MySource : public concurrency::agent
{
concurrency::ITarget<Message>& _target; // send to SUD
Message _msg;
public:
explicit MySource(const Message& msg, concurrency::ITarget<Message>& target)
: _target(target), _msg(msg) {}
Message message() const
{
// Get data from hardware device
return Message(_msg);
}
void run()
{
concurrency::send(_target, message());
done();
}
};
template <typename Message>
class MySink : public concurrency::agent
{
concurrency::ISource<Message>& _source; // received from SUD
int _id;
Message info;
std::mutex myMutex;
public:
explicit MySink(concurrency::ISource<Message>& source, int ID = 0) : _source(source), _id(ID) {}
void print(const Message& s)
{
std::lock_guard<std::mutex> guard(myMutex);
std::cout << "\nin a sink: " << _id << ": " << info << std::endl;
}
void run()
{
info = concurrency::receive(_source);
print(info);
done();
}
double compute(int val)
{
info *= val*_id;
return info;
}
};
int main()
{
{ // Single sink
using Message = std::string;
Message m(" good morning");
// All actors access (read from/write to) a single buffer
concurrency::overwrite_buffer<Message> buffer;
MySource i(m, buffer);
SUD<Message, MySource, MySink> s(buffer, buffer);
MySink o(buffer);
i.start(); o.start(); s.start();
concurrency::agent::wait(&i); concurrency::agent::wait(&s); concurrency::agent::wait(&o);
}
{ // Multiple sinks
using Message = int;
Message m(23);
// All actors access (read from/write to) a single buffer
concurrency::overwrite_buffer<Message> buffer;
MySource i(m, buffer);
SUD<Message, MySource, MySink> s(buffer, buffer);
MySink o1(buffer, 1);
MySink o2(buffer, 2);
MySink o3(buffer, 3);
i.start(); s.start();
o1.start(); o2.start(); o3.start();
concurrency::agent::wait(&i); concurrency::agent::wait(&s);
concurrency::agent::wait(&o1);
concurrency::agent::wait(&o2);
concurrency::agent::wait(&o3);
std::cout << "\ncompute: " << o1.compute(2) << '\n';
std::cout << "\ncompute: " << o2.compute(2) << '\n';
std::cout << "\ncompute: " << o3.compute(2) << '\n';
}
return 0;
}