Last active
February 2, 2023 09:44
-
-
Save saltzm/f2acd7f656436dd63c9808ea1173807c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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