Last active
October 12, 2024 01:14
-
-
Save xealits/9db916a1e539ab6a082afbf90a36f3cc to your computer and use it in GitHub Desktop.
Simple pattern examples in C++
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
/* | |
* 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); | |
} | |
} |
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
/* | |
* 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)...); | |
}); | |
} | |
); | |
}); | |
} | |
} |
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
/* | |
* 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