Skip to content

Instantly share code, notes, and snippets.

@saltzm
Last active February 2, 2023 09:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saltzm/f2acd7f656436dd63c9808ea1173807c to your computer and use it in GitHub Desktop.
Save saltzm/f2acd7f656436dd63c9808ea1173807c to your computer and use it in GitHub Desktop.
#include <memory>
#include <iostream>
#include <variant>
#include <optional>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template <typename... Ts>
inline constexpr bool isVariant = false;
template <typename... Ts>
inline constexpr bool isVariant<std::variant<Ts...>> = true;
/*
template <typename T>
inline constexpr bool hasIgnoredMsgs = false;
template <typename T>
inline constexpr bool hasIgnoredMsgs<typename T::IgnoredMessages> = true;
*/
template<typename, typename = void>
constexpr bool is_type_complete_v = false;
template<typename T>
constexpr bool is_type_complete_v
<T, std::void_t<decltype(sizeof(T))>> = true;
/*
template <typename T>
inline constexpr bool hasIgnoredMsgs = false;
template <typename T, typename = typename T::IgnoredMessages>
inline constexpr bool hasIgnoredMsgs<T> = true;
*/
template<typename InternalStateMachine>
class StateMachine {
public:
StateMachine() : _behavior(typename InternalStateMachine::InitialBehavior{}), _self() {}
template <typename M>
void receive(M&& m) {
if constexpr (isVariant<typename InternalStateMachine::Message>) {
static_assert(std::is_constructible<typename InternalStateMachine::Message, M>::value);
} else {
static_assert(std::is_same<typename InternalStateMachine::Message, M>::value);
}
_self.prereceive();
if constexpr (isVariant<typename InternalStateMachine::Behavior>) {
std::visit(overloaded {
[this, &m] (auto &behavior) {
if constexpr (//!is_type_complete_v<typename std::remove_reference<decltype(behavior)>::type> ||
(isVariant<typename std::remove_reference<decltype(behavior)>::type::IgnoredMessage> &&
!std::is_constructible<typename std::remove_reference<decltype(behavior)>::type::IgnoredMessage, typename std::remove_cv<typename std::remove_reference<M>::type>::type>::value) ||
(!isVariant<typename std::remove_reference<decltype(behavior)>::type::IgnoredMessage> &&
!std::is_same<typename std::remove_reference<decltype(behavior)>::type::IgnoredMessage, typename std::remove_cv<typename std::remove_reference<M>::type>::type>::value)
) {
auto newBehavior = behavior.receive(_self, m);
//static_assert(std::is_constructible<typename std::remove_reference<decltype(behavior)>::type::NextBehavior,
// typename std::remove_reference<decltype(newBehavior)>::type>::value);
if (newBehavior) {
if constexpr (isVariant<typename std::remove_reference<decltype(behavior)>::type::NextBehavior>) {
// This extra dispatch is unfortunate
std::visit([this] (auto &newB) {
_behavior = newB;
}, *newBehavior);
} else {
_behavior = *newBehavior;
}
}
}
}
}, _behavior);
} else {
auto newBehavior = _behavior.receive(_self, m);
if (newBehavior) {
_behavior = *newBehavior;
}
}
_self.postreceive();
}
typename InternalStateMachine::Behavior _behavior;
InternalStateMachine _self;
};
struct Msg1 {};
struct Msg2 {};
struct Msg3 {};
class MyStateMachine {
public:
// Behaviors
struct Start; struct Running; struct Stopped;
using Message = std::variant<Msg1, Msg2, Msg3>;
using Behavior = std::variant<Start, Running, Stopped>;
using InitialBehavior = Start;
void prereceive() {
globalCounter++;
}
void postreceive() {
}
struct Start {
using IgnoredMessage = std::variant<Msg2, Msg3>;
using NextBehavior = std::variant<Running, Stopped>;
std::optional<NextBehavior> receive(MyStateMachine& self, const Msg1& m) {
std::cout << "Start: received msg 1, globalCounter: " << self.globalCounter << std::endl;
return Running{};
}
};
struct Running {
using IgnoredMessage = Msg3;
using NextBehavior = Stopped;
std::optional<NextBehavior> receive(MyStateMachine& self, const Msg1& m) {
std::cout << "Running: received msg 1, globalCounter: " << self.globalCounter << ", localCounter: " << localCounter << std::endl;
++localCounter;
if (localCounter >= 2) {
return Stopped{};
} else {
return std::nullopt;
}
}
std::optional<NextBehavior> receive(MyStateMachine& self, const Msg2& m) {
std::cout << "Running: received msg 2, globalCounter: " << self.globalCounter << ", localCounter: " << localCounter << std::endl;
++localCounter;
if (localCounter >= 2) {
return Stopped{};
} else {
return std::nullopt;
}
return std::nullopt;
}
int localCounter{0};
};
struct Stopped {
using IgnoredMessage = Msg3;
using NextBehavior = Stopped;
std::optional<NextBehavior> receive(MyStateMachine& self, const Msg1& m) {
std::cout << "Stopped: received msg 1, globalCounter: " << self.globalCounter << std::endl;
return std::nullopt;
}
std::optional<NextBehavior> receive(MyStateMachine& self, const Msg2& m) {
std::cout << "Stopped: received msg 2, globalCounter: " << self.globalCounter << std::endl;
return std::nullopt;
}
};
int globalCounter{0};
};
class SingleStateSingleMessageMachine {
public:
// Behaviors
struct Running;
// Works even without variant :)
using Message = Msg1;
using Behavior = Running;
using InitialBehavior = Running;
void prereceive() {
globalCounter++;
}
void postreceive() {
}
struct Running {
using IgnoredMessage = void;
std::optional<Behavior> receive(SingleStateSingleMessageMachine& self, const Msg1& m) {
std::cout << "Running: received msg 1, globalCounter: " << self.globalCounter << std::endl;
return std::nullopt;
}
};
int globalCounter{0};
};
int main() {
StateMachine<MyStateMachine> myStateMachine;
myStateMachine.receive(Msg1{});
myStateMachine.receive(Msg1{});
myStateMachine.receive(Msg2{});
myStateMachine.receive(Msg1{});
myStateMachine.receive(Msg2{});
myStateMachine.receive(Msg3{});
/* Outputs:
Start: received msg 1, globalCounter: 1
Running: received msg 1, globalCounter: 2, localCounter: 0
Running: received msg 2, globalCounter: 3, localCounter: 1
Stopped: received msg 1, globalCounter: 4
Stopped: received msg 2, globalCounter: 5
*/
StateMachine<SingleStateSingleMessageMachine> boringStateMachine;
boringStateMachine.receive(Msg1{});
boringStateMachine.receive(Msg1{});
/* Outputs:
Running: received msg 1, globalCounter: 1
Running: received msg 1, globalCounter: 2
*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment