Skip to content

Instantly share code, notes, and snippets.

@xealits
Last active October 12, 2024 01:14
Show Gist options
  • Save xealits/9db916a1e539ab6a082afbf90a36f3cc to your computer and use it in GitHub Desktop.
Save xealits/9db916a1e539ab6a082afbf90a36f3cc to your computer and use it in GitHub Desktop.
Simple pattern examples in C++
/*
* Just some notes while reading Game Programming Patterns by Robert Nystrom
* https://gameprogrammingpatterns.com
*
* A command or event made with C++ std::variant.
* The idea is to pack some similar-size but different types into the command.
* How to subscribe components to events from each other
* and construct it declaratively, at compile time?
* The components send events or commands (command pattern).
* You want to verify the interfaces, preferably statically, i.e. at link time.
*
* send commands as variant<similar-size classes> -- that's the publisher
* interface react to them via operator() overloading -- that's the subscriber
* interface
*
* https://stackoverflow.com/questions/66961406/c-variant-visit-overloaded-function
*
* Compile with C++17
* clang++ -std=c++17 ./pattern_command_1.cpp -o pattern_command_1
*/
#include <iostream>
#include <variant>
#include <vector>
struct A {
std::string name = "spencer";
};
struct B {
std::string type = "person";
};
struct C {
double age = 5;
};
// Declaration of the Command type
// for the commands/events from the publisher
// the subscriber might need only some of the types inside variant...
typedef std::variant<A, B, C> CommandA;
// The command handler 1)
struct ReactCommandA {
void operator()(const A &a) { std::cout << a.name << std::endl; }
void operator()(const B &b) { std::cout << b.type << std::endl; }
// void operator()(C c) {
// std::cout << c.age << std::endl;
// }
// if it's commented out, the compiler catches the incomplete overloads:
// ./pattern_command_switch.cpp:48:8: note: in instantiation of function
// template specialization 'std::visit<ReactCommandA, std::variant<A, B, C>
// &, void>' requested here
// std::visit(ReactCommandA(), something);
// /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/variant:582:12:
// error: cannot deduce return type 'auto' from returned value of type
// '<overloaded function type>'
// the catch-all case
template <typename Else> void operator()(const Else &c) {
std::cout << "not handling!" << std::endl;
}
/// call the overloads for variant content _without throwing_
/// like in SYCL case https://www.reddit.com/r/sycl/comments/1f55t32/comment/llj2ict/
// there should be a meta-way for this if
void var_react(const CommandA& var) {
if (const A* pval = std::get_if<A>(&var)) {
//std::cout << "variant value A: " << pval->name << '\n';
operator()(*pval);
}
else if (const B* pval = std::get_if<B>(&var)) {
operator()(*pval);
}
else {
std::cout << "failed to get value!" << '\n';
}
};
/// meta-way: recursive template with the variant compile-time info
// variant info and examples:
// https://en.cppreference.com/w/cpp/utility/variant/variant_size
// https://en.cppreference.com/w/cpp/utility/variant/variant_alternative
// https://stackoverflow.com/questions/57642102/how-to-iterate-over-the-types-of-stdvariant
template <std::size_t alt_i = 0>
void var_react_meta(const CommandA& var) {
//static_assert(alt_i < std::variant_size_v<CommandA>);
// nope
// just end the template recursion
if constexpr(alt_i >= std::variant_size_v<CommandA>) return;
else {
using var_T = std::variant_alternative_t<alt_i, CommandA>;
if (const var_T* pval = std::get_if<var_T>(&var)) {
//std::cout << "variant value A: " << pval->name << '\n';
operator()(*pval);
}
else {
var_react_meta<alt_i+1>(var);
}
}
};
};
// Another command handler, 2) with overloaded lambdas:
// overloaded example from
// https://en.cppreference.com/w/cpp/utility/variant/visit
// helper type for the visitor #4
template <class... Ts> struct overloaded : Ts... {
using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20)
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
int main() {
CommandA something{B{}};
std::visit(ReactCommandA(), something);
CommandA somethingC{C{}};
std::visit(ReactCommandA(), somethingC);
CommandA something2{A{}};
std::visit(ReactCommandA(), something2);
std::cout << "\nno-throw get_if\n";
ReactCommandA().var_react(something);
ReactCommandA().var_react(somethingC);
ReactCommandA().var_react(something2);
std::cout << "\nno-throw get_if meta\n";
ReactCommandA().var_react_meta(something);
ReactCommandA().var_react_meta(somethingC);
ReactCommandA().var_react_meta(something2);
std::cout << "\noverloaded\n";
std::vector<CommandA> vec{something, something2, somethingC};
for (auto &v : vec) {
// https://en.cppreference.com/w/cpp/utility/variant/visit
// from the overloaded example:
// 4. another type-matching visitor: a class with 3 overloaded operator()'s
// Note: The `(auto arg)` template operator() will bind to `int` and `long`
// in this case, but in its absence the `(double arg)` operator()
// *will also* bind to `int` and `long` because both are implicitly
// convertible to double. When using this form, care has to be taken
// that implicit conversions are handled correctly.
std::visit(
overloaded{
//[](auto arg) { std::cout << arg << ' '; },
[](const auto &arg) { std::cout << "not handling 2 !\n"; },
[](const B &arg) { std::cout << arg.type << '\n'; },
[](const C &arg) { std::cout << std::to_string(arg.age) << '\n'; }},
v);
}
}
/*
* https://www.reddit.com/r/sycl/comments/1f55t32/comment/llj2ict/
* std::visit can throw, so it does not work in SYCL kernels
* this is a couple tests with an extension of pattern_command_1
*
* to compile with Intel SYCL:
* source /opt/intel/oneapi/setvars.sh
* icpx -fsycl -o pattern_command_2 pattern_command_2.cpp
* ./pattern_command_2
*/
#include <sycl/sycl.hpp>
#include <variant>
#include <vector>
struct A{double a;};
struct B{char b[5];};
// Incompatible types! not implicitly convertible to double
using Mix = std::variant<A,B>;
auto funk(const A& a) -> decltype(a.a) {return a.a;}
auto funk(const B& b) -> decltype(&b.b[0]) {return b.b;}
// TODO: a catch-all funk?
using MixOutputs = std::variant<decltype(funk(A{})), decltype(funk(B{}))>;
void print_funk(const sycl::stream& os, const decltype(funk(A{}))& x) {
os << "\nHello 1 " << x << "\n";
}
void print_funk(const sycl::stream& os, const decltype(funk(B{}))& x) {
os << "\nHello 2 " << x << "\n";
}
// how to turn the overloaded funk into a template parameter?
// https://stackoverflow.com/a/25875538/1420489
// https://fiona.onl/en/passing_overloaded_functions.html
#define LIFT(overloaded_fname) \
[&] (auto&&... args) -> decltype(auto) \
{ \
return overloaded_fname(std::forward<decltype(args)>(args)...); \
}
template<typename variant_T, typename output_T>
struct ReactCommand2 {
// I don't really need a struct here anymore, since the functions are global.
// This template with the if chain over variant alternatives may be useful.
// It takes a variant input and any type output. (If there are overloads for the inputs and whatnot.)
template <std::size_t alt_i = 0, typename overloaded_func_T>
output_T var_react_meta(const variant_T& var, overloaded_func_T funk_templ) {
static_assert(alt_i < std::variant_size_v<variant_T>);
using var_T = std::variant_alternative_t<alt_i, variant_T>;
const var_T* pval = std::get_if<var_T>(&var);
if constexpr (alt_i+1 < std::variant_size_v<variant_T>) {
if (pval == nullptr) {
return var_react_meta<alt_i+1, overloaded_func_T>(var, funk_templ);
}
else {
return funk_templ(*pval);
}
}
else {
// this never happens, because I cover all alternatives of variant
// but to end the template without warnings:
//return output_T{}; // this does not work for void: illegal initializer type 'void'
// // in fact, it won't work for any type without a default constructor!
// shove it into the function:
//static_assert(false); // -- is this line generated at all? yes
// for the last member of variant,
// the only option is to call the corresponding function overload
// without caring for runtime nullptr
// -- it is the last variant, if the control got here, it must not be a nullptr
return funk_templ(*pval);
}
}
};
int main()
{
//std::vector<Mix> mix = {A{0.0}, B{1.0}, A{2.0}};
std::vector<Mix> mix = {A{0.0}, B{"ab$"}, A{2.0}};
{
sycl::buffer buffer_Mix_variant(mix);
sycl::queue q;
q.submit([&](sycl::handler& sycl_handler){
auto os = sycl::stream{128, 128, sycl_handler};
sycl::accessor mix_acc(buffer_Mix_variant, sycl_handler);
//sycl_handler.single_task([=](){
// //std::visit([](auto x){return funk(x);}, mix_acc[0]); // compilation error: SYCL kernel cannot use exceptions
// auto ret = ReactCommand<Mix>().var_react_meta(mix_acc[0]);
// os << "\nHello " << ret << "\n";
//});
constexpr unsigned n_dimensions = 1;
sycl_handler.parallel_for(
sycl::range{mix.size()},
[=](sycl::id<n_dimensions> idx){
//std::visit([](auto x){return funk(x);}, mix_acc[0]); // compilation error: SYCL kernel cannot use exceptions
MixOutputs ret = ReactCommand2<Mix, MixOutputs>().var_react_meta(mix_acc[idx], LIFT(funk));
using out_T0 = std::variant_alternative_t<0, MixOutputs>;
using out_T1 = std::variant_alternative_t<1, MixOutputs>;
if (const auto* out_val = std::get_if<out_T0>(&ret)) {
os << "\nHello 1 " << *out_val << "\n";
}
//else if (const auto* out_val = std::get_if<out_T1>(&ret)) {
// os << "\nHello 2 " << *out_val << "\n"; // same
//}
else
os << "\nHello Not supported variant type!\n"; // same
// or with another variant visitor:
//ReactCommand2<MixOutputs, void>().var_react_meta(ret, LIFT(print_funk<os>));
// The LIFT macro did not work there for some reason. The lambda could not capture os properly,
// and the expansion to the template failed because of "invalid parameter os".
ReactCommand2<MixOutputs, void>().var_react_meta(ret,
[os](auto&&... args) -> decltype(auto) {
return print_funk(os, std::forward<decltype(args)>(args)...);
});
}
);
});
}
}
/*
* Just some notes while reading Game Programming Patterns by Robert Nystrom
* https://gameprogrammingpatterns.com
*
* The idea is to have states with some memory attached, some parameters.
* And they also should be statically allocated.
* It is a very much manual implementation.
* So, to add a state, you need to write some code in 3 places, which may be a
* pain. It would be easier with some proper metaprogramming (reflection). But,
* it's not too bad.
*
* The pattern is more about having a bunch of classes registered within a
* class.
*/
#include <iostream>
struct AllStates;
struct State {
public:
State(AllStates *FSM_toRegisterIn) : _fsm(FSM_toRegisterIn) {
std::cout << "State::State " << std::hex << this << " in FSM " << _fsm
<< '\n';
}
virtual State *processEvent(int ev) {
return this;
}; // return nullptr or this as the next state?
protected:
AllStates *_fsm;
};
struct AllStates {
AllStates()
: standing{this}, jumping{this} {}; // to add a new state: update this 1)
// to add a new state: add a class for the state behavior here 2)
struct StandingState : public State {
public:
StandingState(AllStates *FSM_toRegisterIn) : State(FSM_toRegisterIn) {
// I'm defining this constructor just for printout
// it could be just using State::State
std::cout << "StandingState::StandingState " << std::hex << this << '\n';
};
State *processEvent(int ev) {
switch (ev) {
case 0:
return /*(State*)*/ &_fsm->jumping;
default:
return this;
return this;
};
}
} standing; // to add a new state: and instantiate this class 3)
struct JumpingState : public State {
public:
JumpingState(AllStates *FSM_toRegisterIn) : State(FSM_toRegisterIn) {
std::cout << "JumpingState::JumpingState " << std::hex << this << '\n';
};
State *processEvent(int ev) {
switch (ev) {
case 0:
return &_fsm->standing;
default:
return this;
return this;
};
}
} jumping;
};
class StateMachine {
public:
StateMachine() {
// initial state:
_state = &_fsm.standing;
std::cout << "StateMachine::StateMachine standing " << std::hex
<< &_fsm.standing << '\n';
std::cout << "StateMachine::StateMachine jumping " << std::hex
<< &_fsm.jumping << '\n';
std::cout << "StateMachine::StateMachine _state " << std::hex << _state
<< '\n';
};
void processEvent(int ev) {
_state = _state->processEvent(ev);
std::cout << "StateMachine::StateMachine " << ev << " new _state "
<< std::hex << _state << '\n';
};
private:
State *_state;
AllStates _fsm;
};
int main() {
StateMachine s;
s.processEvent(0);
s.processEvent(0);
s.processEvent(0);
s.processEvent(0);
s.processEvent(1);
s.processEvent(1);
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment