spacebox/src/Switch.hpp

162 lines
4.8 KiB
C++

/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+--------------+
A switch object contains a state of either on or off which can be read by reading the switch in a bool context. A
switch can be flipped to change the state. If a reaction function has been set, it will run every time the switch
is flipped. The function must accept a bool as its first argument which will provide the state of the switch. It can
also accept an arbitrary number of arguments after that and can return a value.
This example will flip the switch three times and print the state each time.
sb::Switch<> sw1;
sw1.on_state_change([](bool state) {
std::cout << "Door is " << (state ? "OPEN" : "CLOSED") << std::endl;
});
sw1.flip();
sw1.flip();
sw1.flip();
The expected output is
Door is OPEN
Door is CLOSED
Door is OPEN
This example ignores the switch state and adds an arbitrary amount to a running tally each flip.
sb::Switch<int, int, int> sw2;
sw2.on_state_change([](bool state, int tally, int add) -> int {
tally += add;
std::cout << "Adding " << add << " to tally: " << tally << std::endl;
return tally;
});
int tally = 0;
tally = sw2.flip(tally, 1);
tally = sw2.flip(tally, 5);
tally = sw2.flip(tally, 99);
The expected output is
Adding 1 to tally: 1
Adding 5 to tally: 6
Adding 99 to tally: 105
Although the state can just be ignored, it can also be forced to remain in one state. This example uses the
`&` operator to allow access to the switch itself so it can be turned off automatically after every flip.
sb::Switch<void, const std::string&> sw3;
sw3.on_state_change([&](bool state, const std::string& visitor) {
if (state)
{
std::cout << "Come in " << visitor << ", my door is always " << (state ? "OPEN" : "CLOSED") << std::endl;
sw3.flip("");
}
});
sw3.flip("Mario");
sw3.flip("Luigi");
sw3.flip("Toad");
The expected output is
Come in Mario, my door is always OPEN
Come in Luigi, my door is always OPEN
Come in Toad, my door is always OPEN
*/
#pragma once
#include <functional>
#include <ostream>
namespace sb
{
template<typename return_type = void, typename... arguments>
class Switch
{
private:
using Reaction = std::function<return_type(bool, arguments...)>;
bool state = false;
Reaction reaction;
public:
/*!
* The switch object will initialized to the off state. A reaction function can be specified, otherwise it will have an
* empty reaction function.
*
* @param callback function to run on state change
*/
Switch(Reaction reaction = Reaction()) : reaction(reaction) {}
Switch(bool state, Reaction reaction = Reaction()) : state(state), reaction(reaction) {}
/*!
* Toggle state, triggering the reaction function.
*
* @param args arbitrary list of arguments that are expected by the reaction function
* @return return the return value of the reaction function
*/
return_type flip(arguments... args)
{
state = !state;
return reaction(state, args...);
}
/*!
* Assign the reaction function.
*
* @param reaction function to be called when the state is changed
*/
void on_state_change(Reaction reaction)
{
this->reaction = reaction;
}
/*!
* @return when called as a boolean, return state
*/
operator bool() const
{
return state;
}
/*!
* Assign the switch object's state using the assignment operator with a boolean value, without triggering the reaction function.
* To trigger the reaction function, use `Switch::flip` instead.
*
* @param state state the switch will be set to
*/
Switch<return_type, arguments...>& operator=(bool state)
{
this->state = state;
return *this;
}
operator std::string() const
{
return state ? "true" : "false";
}
};
}
namespace std
{
template<typename return_type, typename... arguments>
std::ostream& operator<<(std::ostream& stream, const sb::Switch<return_type, arguments...>& sw)
{
stream << std::string(sw);
return stream;
}
}