Inheritance and 2 interface types - c++

I have a question regarding inheritance and designing a user interface.
I have a class KeyboardKey which represents an individual keyboard key, such as Q, W, E, R, ... etc.
I have a class Keyboard which contains a vector of class Keyboardkey. [Important!]
I am using SFML, and so each time an event is generated from an event loop, it is sent to the keyboard class. This class then farms that event out to the corresponding key.
In addition, I have a class SynthesizerKey which inherits from KeyboardKey. In addition to the regular key stuff, such as "is the key enabled", "is the key pressed", this class contains data and functions to handle generating a sin wave tone. Variables include the amplitude and current phase of the sin wave.
I am now about to create a class SynthesizerKeyboard. I was about to copy and paste all the code from class Keyboard into this class, however this is not good programming practice, as code is duplicated!
The main issue I have is that SynthesizerKeyboard contains a function to generate a sequence of samples to be stored in a buffer. In order to generate the samples, a loop iterates over each KeyboardKey and checks if it is pressed. If it is, then we must generate a sample corresponding to that keys note/frequency.
However, since the vector contains class KeyboardKey and not class SynthesizerKey I do not have the variables for the phase and amplitude of the sin waves as member data of the elements of the vector.
I think I may have to do what is known as "refactoring" [?], and separate the "sin wave" parts of SynthesizerKey from the KeyboardKey parts. In other words, I ditch the SynthesizerKey class and have an Synthesizer class and a KeyboardKey class, separately. I then have a vector of Synthesizer in class SynthesizerKeyboard in addition to the vector of KeyboardKey in class Keyboard which I have access to in SynthesizerKeyboard through inheritance.
However this is less elegant. Is there another way?
Below is some code which might help the reader understand the quesiton in more detail.
SynthesizerKeyboard
class SynthesizerKeyboard : public Keyboard
{
public:
SynthesizerKeyboard(const sf::Font& sf_font)
: Keyboard(sf_font)
{
}
double Sample() const
{
for(std::vector<KeyboardKey>::iterator it = m_keyboardkey.begin()
it != m_keyboardkey.end(); ++ it)
{
if(it->IsKeyPressed())
{
it->Sample();
}
}
}
void GenerateBufferSamples(std::vector<sf::Int16> buffer)
{
for(std::size_t i = 0; i < buffer.size(); ++ i)
{
buffer[i] = Sample();
}
}
};
SynthesizerKey
class SynthesizerKey : public KeyboardKey
{
protected:
AbstractOscillator *m_abstractoscillator;
public:
double Sample() const
{
return m_abstractoscillator->Sample();
}
};
Keyboard
class Keyboard
{
protected:
std::vector<KeyboardKey> m_keyboardkey;
public:
Keyboard(const sf::Font& sf_font)
void Draw(sf::RenderWindow& window)
void Event(const sf::Event& event)
{
for(std::vector<KeyboardKey>::iterator it = m_keyboardkey.begin();
it != m_keyboardkey.end(); ++ it)
{
(*it).Event(event);
}
}
bool IsKeyPressed(const sf::Keyboard::Key& sf_key)
{
for(std::vector<KeyboardKey>::iterator it = m_keyboardkey.begin();
it != m_keyboardkey.end(); ++ it)
{
if((*it).Key() == sf_key)
{
return (*it).IsKeyPressed();
}
}
}
};
KeyboardKey
class KeyboardKey
{
protected:
KeyState m_keystate;
sf::Color m_pressed_color;
sf::Color m_release_color;
sf::Text m_sf_text;
sf::Keyboard::Key m_sf_keyboard_key;
sf::RectangleShape m_sf_rectangle;
public:
KeyboardKey(const sf::Keyboard::Key& sf_keyboard_key, const std::string& text, const sf::Font& sf_font,
const double position_x, const double position_y)
void Draw(sf::RenderWindow& window)
void Event(const sf::Event& event)
bool IsKeyPressed()
};

Going to skip over the keys for now, but you should consider something similar for them.
First define a bare bones abstract that performs all of the common tasks and includes hooks for the classes that specialize the abstract to fill out to perform their particular behaviour:
class AbstractKeyboard
{
protected:
std::vector<std::unique_ptr<KeyboardKey>> m_keyboardkey;
void Draw();
void Event()
{
for(auto &key: m_keyboardkey)
{
key->Event();
}
}
bool IsKeyPressed(const sf::Keyboard::Key& sf_key)
{
for(auto &key: m_keyboardkey)
{
if(key->isKey(sf_key))
{
return key->IsKeyPressed();
}
}
return false; // need this to handle the no match case, otherwise undefined behaviour
}
void doStuff()
{
// generic keyboard stuff goes here
doSpecificStuff();
}
virtual void doSpecificStuff() = 0;
public:
AbstractKeyboard(const sf::Font& sf_font);
virtual ~AbstractKeyboard();
};
All keyboards have keys, so the vector of keys goes here. Note that we've gone from a vector of keys to a vector of smart pointers to keys. Now we can have any key that inherits the basic key, synthesizer key for example, and the smart pointer eliminates the usual memory woes of dealing with pointers.
The big takeaway here is the doStuff function. It does stuff. What is up to you. When it's done doing the basic stuff that all keyboards must do, it calls doSpecificStuff, a function that each inheritor must fill out, even if it does nothing. doSpecificStuff does whatever the inheritor does differently, add extra behaviours, and generally makes a synthesizer more than just a regular keyboard.
Here is the basic keyboard:
class Keyboard:public AbstractKeyboard
{
protected:
void doSpecificStuff()
{
// Do keyboard Stuff, if there is any specific stuff to do
}
public:
Keyboard(const sf::Font& sf_font): AbstractKeyboard(sf_font)
{
}
};
It doesn't do anything special, but it could but putting special code into doSpecificStuff.
The synthesizer adds a few functions for folk who know it is a synthesizer (Sample and GenerateBufferSamples) as well as implementing doSpecificStuff to do synthesizer stuff.
class SynthesizerKeyboard : public AbstractKeyboard
{
protected:
void doSpecificStuff()
{
// I do specific stuff! Yay me!
}
public:
SynthesizerKeyboard(const sf::Font& sf_font): AbstractKeyboard(sf_font)
{
}
// leaving these as an example of how to get a SynthesizerKey out of m_keyboardkey
double Sample() const
{
// just going to sum because I don't know what's supposed to happen in here
double sum = 0.0;
for(auto &key: m_keyboardkey)
{
if(key->IsKeyPressed())
{
if(SynthesizerKey* skey = dynamic_cast<SynthesizerKey*>(key.get()))
{
sum += skey->Sample();
}
else
{
// like freak out, man.
}
}
}
return sum;
}
void GenerateBufferSamples(std::vector<sf::Int16> buffer)
{
for(sf::Int16 & val: buffer)
{
val = Sample();
}
}
};
Since the synthesizer uses synthesizer keys, Sample contains an example of how to turn a pointer to a regular key into a synthesizer key and trap a configuration error that places the wrong type of key into m_keyboardkey
By adding a virtual destructor and the virtual keyword to Sample in SynthesizerKeyboard, we could also make a
class MoogSynthesizer: public SynthesizerKeyboard
{
public:
double Sample() const override
{
// I do Moog sampling!
}
}

Related

State Machine Change State

I'm continuously running into the same problem, and can't fix it even when looking through tutorials.
I've "set up" my State machine, but I can't transition between states.
Here is my StateMachine:
class StateMachine
{
State* m_State;
public:
StateMachine();
~StateMachine();
void changeState(State* state);
};
And here is an example State:
class A : State
{
public:
A();
~A();
void handleInput(int a);
}
If I pass a = 1 into A::handleInput() I want to transition to State B. But when I implement it I can't access the StateMachine from A::handleInput(), making me scrub my head in agony.
But when I implement it I can't access the StateMachine from A::handleInput()
Well, that's a well known problem with the State Pattern, that there's no mention how to keep the state classes in track with an enclosing State Machine.
IMO, that's one of the valid use cases to consider the StateMachine class as being implemented as a Singleton.
This way it's instance would be accessible from any Stateclass implementation.
As I'm talking in terms of Design Patterns here, the State classes could be designed with help of the Flyweight Pattern, since they're usually stateless themselves.
I've once driven all that into a c++ template framework, which abstracts the interfaces of State and State Machine (see link below).
Here's a short code example by these means:
StateMachine.h
struct State {
virtual void handleInput(int x) = 0;
virtual ~State() {} = 0;
};
class StateMachine {
State* m_State;
StateMachine();
public:
static StateMachine& instance() {
static StateMachine theInstance;
return theInstance;
}
void changeState(State* state) {
m_State = state;
}
void triggerInput(int x) {
m_State->handleInput(x);
}
};
StateA.h
#include "StateMachine.h"
class StateB;
extern StateB* stateB;
class StateA : public State {
public:
virtual ~StateA() {}
virtual void handleInput(int x) {
if(x == 1) {
// Change to StateB
StateMachine::instance.changeState(stateB);
}
else {
// Do something with x
}
}
};
I omit the definition od StateB here, should be the same manner as StateA.
References:
C++ Singleton Design Pattern
State machine template class framework for C++
I've taken a look at the Sourcemaking example and for me the implementation example really sucks; having to create new instances upon every state change:
https://sourcemaking.com/design_patterns/state/cpp/1
Personally as someone who's designed state machines in electronics with JK flip flops, I would use a similar but semantically different approach. The complexity in state machines involves the action performed according to the state and input; typically in C you would do this with lots of switch statements and possibly arrays describing how to handle the current state and new input aka event.
So to me the OO approach to this would be to model the event handler. This would have an interface which describes the format of the inputs. You then have different implementations of that interface for each different state. With that, the state machine can simply implement a collection of states to event handlers - array, vector or map. Although the handlers still may contain case statements, the overall spaghettiness is very much reduced. You can easily extend the design with new state handlers as and when necessary:
So you could have something like this:
#include <map>
typedef enum
{
//TODO : state list, e.g.
eOff,
eOn
}
teCurrentState;
typedef struct
{
//TODO : Add inputs here, e.g.
bool switch1;
}
tsInputDesc;
typedef struct
{
//TODO : Add outputs here, e.g.
bool relay1;
}
tsOutputDesc;
// ------------------------------------------------
class IEventHandler
{
public:
virtual ~IEventHandler() {}
// returns new state
virtual teCurrentState handleInput(tsInputDesc const& input, tsOutputDesc& output) = 0;
};
// ------------------------------------------------
class OnStateHandler : public IEventHandler
{
public:
virtual teCurrentState handleInput(tsInputDesc const& input, tsOutputDesc& output) override
{
//TODO : IMPLEMENT
teCurrentState newState = TODO....
return (newState);
}
};
// ------------------------------------------------
class OffStateHandler : public IEventHandler
{
public:
virtual teCurrentState handleInput(tsInputDesc const& input, tsOutputDesc& output) override
{
//TODO : IMPLEMENT
teCurrentState newState = TODO....
return (newState);
}
};
// ------------------------------------------------
class StateMachine
{
protected:
teCurrentState mCurrentState;
std::map<teCurrentState, IEventHandler*> mStateHandlers;
void makeHandlers()
{
mStateHandlers[eOff] = new OffStateHandler();
mStateHandlers[eOn] = new OnStateHandler();
}
public:
StateMachine()
{
makeHandlers();
mCurrentState = eOff;
}
void handleInput(tsInputDesc const& input, tsOutputDesc output)
{
teCurrentState newState = mStateHandlers[mCurrentState]->handleInput(input, output);
mCurrentState = newState;
}
};
// ------------------------------------------------
void runFsm()
{
StateMachine fsm;
tsInputDesc input;
tsOutputDesc output;
bool alive = true;
while (alive)
{
// TODO : set input according to....inputs (e.g. read I/O port etc)
fsm.handleInput(input, output);
// TODO : use output
}
}

Command pattern - Commands that execute the task with a "weight"

I'm currently working on designing a base for future projects, more specifically I'm working on the input handling. I'm using the command pattern for handling the input, when creating an input context the programmer can bind a command to a key or mouse button through an invoker that executes the command depending on the different conditions of the application, key pressed, where the mouse is on the window and so on.
I ran into trouble when I got to the part of adding handling of the mouse when in an input context where the cursor is disabled, e.g. when controlling a 3D camera (This is actually the only situation I can think of where this would be useful).
The way I see this working, is the programmer binds a command, one that rotates the camera, to be activeated once an event is created that describes mouse movement. The command would hold a pointer to the camera object and call a function like camera->pan() when executed. This command would be executed when the mouse moved in the X-Axis. However if this was the case, the camera would always pan with a constant speed, no matter how fast or slow the mouse was moved. If the cameras function pan() had a parameter for specifying how much to pan, the Command object would need to have a value for this parameter when executing. If that value is specified on creation of the command and stored as a member, the problem would arise again, since the parameter would have the same value every time the function is called.
My proposed solution to this problem is to simply create a variant of the Command class called something like WeightedCommand that had a parameter in its execute() function. This parameter would be a "weight" passed on to the cameras pan() function. This would allow for the command to be executed with a differen "weight" everytime it's called, or the same "weight", it would be up to the programmer to decide.
For reference, this is an example of the Command pattern, from wikipedia.
class Light {
public:
void TurnOn() { std::cout << "The light is on." << std::endl; }
void TurnOff() { std::cout << "The light is off." << std::endl; }
};
class ICommand {
public:
virtual ~ICommand() = default;
virtual void Execute() = 0;
};
// The Command for turning on the light - ConcreteCommand #1
class FlipUpCommand : public ICommand {
public:
FlipUpCommand(Light* light) : light_(light) { assert(light_); }
void Execute() { light_->TurnOn(); }
private:
Light* light_;
};
An example of the WeightedCommand:
class WeightedCommand
{
public:
virtual ~WeightedCommand() = default;
virtual void execute(double weight) = 0;
};
class PanCamera : public WeightedCommand
{
public:
PanCamer(Camera* cam)
: _camera(cam;
{}
void execute(double weight)
{
_camera->pan(weight);
}
private:
Camera* _camera;
};
Can you see any flaws with this approach. Is there a better solution already available? I tried searching for solutions to similar problems, but couldn't find anything that really fit.
This is rather an opinion based question but here are some suggestions.
You could keep your approach and generalize it a bit more by making ICommand a template.
template <typename ...Args>
class ICommand
{
public:
virtual ~ICommand() = default;
virtual void Execute(Args const& ...args) = 0;
};
class PanCamera : public ICommand<double>
{
void Execute(double const& pan) override
{
_camera->pan(pan);
}
};
If you want to store your commands in a container you need a common type, which wouldn't work with the above example. To avoid this you can replace the double parameter with a std::variant like you've already mentioned.
using CommandArgs = std::variant<double, std::string>;
class PanCamera : public ICommand<CommandArgs>
{
void Execute(CommandArgs const& args) override
{
_camera->pan(std::get<double>(args));
}
};
class SayHello : public ICommand<CommandArgs>
{
void Execute(CommandArgs const& args) override
{
display->sayHello(std::get<std::string>(args));
}
};
You can also ditch your ICommand interface alltogether and use the visitor pattern for std::variant.
struct PanCameraArgs
{
double value = 0;
};
struct SayHelloArgs
{
std::string text;
};
struct RotateCameraArgs
{
double angle = 0;
};
using CommandArgs = std::variant<PanCameraArgs, SayHelloArgs, RotateCameraArgs>;
void dispatchCommand(CommandArgs const& command)
{
std::visit( overloaded {
[&] (PanCameraArgs const& args)
{
_camera->pan(pan.value);
}
[&] (SayHelloArgs const& args)
{
display->sayHello(args.text);
}
[&] (RotateCameraArgs const& args)
{
_camera->rotate(args.angle);
}
}, command);
}

Dynamic lists and polymorphism

I have a map of type < lookup_ID, vector< parentclass*>> each location in the map holds a vector of type child class. The idea behind this system is the ability to add a child into its associated map using an add(parentclass*) function and it would be able to find its associated vector of child type. I tried using templates and casting to get the vector to recognized the type of child input into the add function with no luck. I don't want to have to declare an add function for each child of parent, and no matter how I did it I had to declare a function for each type. I could take them out of the map but then again I'm left with the issue of calling each child for any function I want to implement. Is their no way to match types of polymorphic structures into dynamically allocated lists?`
class Piece
{
public:
pieceType ID;
//...
}
class Infantry :
public Piece
{
public:
//...
};
class Artillery :
public Piece
{
public:
//...
};
//...
//In some other classes somewhere
std::map<pieceType, std::vector<Piece*>*> units;
units.emplace(Infantry_, new std::vector<Infantry*>);
units.emplace(Artillery_, new std::vector<Artillery*>);
//...
template<typename T>
std::vector<T*> operator+(std::vector<T*> a, Piece * b) {
a.push_back(static_cast<T*>(b));
return a;
}
add(Piece * piece){
units.at(piece->ID) = units.at(piece->ID) + piece;
}
Also I am aware that this code has some errors, it was more for an example of what i'm trying to say.
You have to use virtual funciton to get the ID for each child class
class Piece
{
public:
virtual PieceType ID() const = 0;
}
class Artillery
{
public:
virtual PieceType ID() const override { /* return your ID for Artillery */ }
}
class Infantery
{
public:
virtual PieceType ID() const override { /* return your ID for Infantery */ }
}
There's no relation between std::vector<Piece*> and either of std::vector<Infantry*> or std::vector<Artillery*>, so your map can only contain std::vector<Piece*>s.
This is for good reason. Imagine you have a std::vector<Infantry*>, and put a pointer to it into your std::map<pieceType, std::vector<Piece*>*>. You could then insert an Artillery * into that through the map.
Rather than exposing the std::vector<Piece*> directly, you could expose a (read only) view of it that casts to the particular subtype.
Using the ranges library
auto asInfantry = ranges::view::transform([](Piece * p){ return static_cast<Infantry *>(p); });
auto asArtillery = ranges::view::transform([](Piece * p){ return static_cast<Artillery *>(p); });
class PieceMap
{
std::map<pieceType, std::vector<Piece*>> units;
public:
auto Infantry() { return units.at(Infantry_) | asInfantry; }
auto Artillery() { return units.at(Artillery_) | asArtillery; }
};

Create a temporary interface into an object?

I have an object "World obj;" that has a normal interface of methods for it's typical funcitonality, but I want to have an additional interface of methods specifically for initializing that should only be visible when I specifically need them.
An example might be like this:
class World{
public:
void draw();
void update();
void normalStuff();
void addATree(); // this should not be ordinarily available or visible,
void addACar(); // calling this might break the object
void addAClown();// if it's not in a ready state for it
private:
int m_data;
};
Is there a way to relatively hide addATree(); etc in a way that makes sense? Ideally the mechanism for revealing those methods would also put the object into a ready state for them, or at least fault if it's not possible.
Different approaches would be possible:
Don't change the code, just change the spec
No need to change the code. Change the API specification and if the caller throws garbage in he gets garbage out.
Make the functions check if they are allowed
Always safe.
class World{
public:
...
void addAClown() {
if(not allowed)
throw error or crash or output error message or just return;
else {
do the work;
}
}
private:
int m_data;
};
Write a function that only exposes the Interface if allowed
You can't protect against someone getting the interface early and use it longer than allowed.
You could extract the interface functions into a separate class.
class WorldInterfaceToProtect {
public:
void addATree() = 0; // this should not be ordinarily available or visible,
void addACar() = 0; // calling this might break the object
void addAClown() = 0;// if it's not in a ready state for it
};
then the main class can protect these functions.
class World : protected WorldInterfaceToProtect {
public:
void draw();
void update();
void normalStuff();
protected:
void addATree(); // this should not be ordinarily available or visible,
void addACar(); // calling this might break the object
void addAClown();// if it's not in a ready state for it
private:
int m_data;
};
You then need to add a function that exposes the interface.
class World ... {
public:
WorldInterfaceToProtect *GetInterface() { return allowed_cond ? this : nullptr; }
...
}
Separate the class itself and the builder
This only helps if the functions to be called are only allowed during construction and not later. Depending on the design of the builder you can get a good protection.
class World{
friend class WorldBuilder;
public:
void draw();
void update();
void normalStuff();
protected:
void addATree(); // this should not be ordinarily available or visible,
void addACar(); // calling this might break the object
void addAClown();// if it's not in a ready state for it
private:
int m_data;
};
class WorldBuilder {
static World *Build(...);
}
Perhaps split the world into more composable parts:
struct WorldInterface
{
virtual void draw() = 0;
virtual void update() = 0;
virtual void normalStuff() = 0;
};
class World : public WorldInterface
{
public:
void draw() override { /* actual drawing here */};
void update() override {};
void normalStuff() override {};
private:
int m_data;
};
class TreeWorld : public WorldInterface
{
public:
// takes a reference to the actual world engine and defers work to
// that
TreeWorld(World& worldEngine) : worldEngine_(worldEngine) {}
void draw() override { worldEngine_.get().draw(); };
void update() override { worldEngine_.get().update(); };
void normalStuff() override { worldEngine_.get().normalStuff(); };
void addATree() {
//do tree/world interaction here
}
private:
std::reference_wrapper<World> worldEngine_;
};
class CarWorld : public WorldInterface
{
public:
// takes a reference to the actual world engine and defers work to
// that
CarWorld(World& worldEngine) : worldEngine_(worldEngine) {}
void draw() override { worldEngine_.get().draw(); };
void update() override { worldEngine_.get().update(); };
void normalStuff() override { worldEngine_.get().normalStuff(); };
void addACar() {
//do car/world interaction here
}
private:
std::reference_wrapper<World> worldEngine_;
};
extern void play_tree_game(TreeWorld world);
extern void play_car_game(CarWorld world);
int main()
{
World worldEngine;
// initialise engine here
// play tree-phase of game
play_tree_game(TreeWorld(worldEngine));
// play car phase of game
play_car_game(CarWorld(worldEngine));
}
Good answers all around, I'll just add this because it was missing(?)
class World{
public:
void draw();
void update();
void normalStuff();
private:
int m_data;
};
class BuildableWorld : public World
{
public:
void addATree();
void addACar();
void addAClown();
};
Use the BuildableWorld at initialization phase and then just give a pointer to the base class type for others to use.
Sure, you need some way to give the "built" data for the base class to access, but that was not the issue here, right?
an alternative approach that has not been mentioned so far, may be to let addX() functions take parameters whose existence implies that World is in a valid state. Say, if you cannot add trees to a world without water, let World return an (optional) water object to pass to addTree ... in other words, you need to properly formalize World invariants:
class World{
public:
void normalStuff();
auto getAvaliableWaterBuckets() -> optional<WaterBuckets>;
auto getAvaliableSoil() -> optional<SoilPack>;
//...
void addATree( WaterBuckets&&, SoilPack&& );
//...
};
// in the meanwhile, in user land:
if( auto water = world->getAvaliableWaterBuckets() )
if( auto soil = world->getAvaliableSoil() )
world->addTree( std::move(*water), std::move(*soil) );
else
world->recycleWater( std::move(*water) );
the benefit of this approach is that the user is not forced to think about world state validity ( an error prone task ), he just thinks about what he needs in order to add a tree ( simpler, hard to use incorrectly ). Moreover, this scales well because addX() functions can share different objects ( addFlowers needs water, ... ) enabling the correct management of a possibly complex internal world state.
Of course, IMHO, if you need to use addX() strictly on world construction only ( and you don't plan to add trees later ), then the factory approach already mentioned in the comments seems the way to go ...

Event-based Game engine based on polymorphism of Entities

I would like to create a simple framework for throwing and catching events in a game. Events could be things like a Collision which (according to the type) can take several arguments (note that every Event type may take another amount of arguments, not just two as in the example).
I would then like to implement functions/classes/... to deal with a Collision, based on polymorphism. This example should illustrate the problem:
#include <iostream>
#include <vector>
class Entity {};
class Player: public Entity {};
class Bomb: public Entity {
public:
bool exploded;
};
class MineSweeper: public Entity {};
// For now, I only included Collisions, but I eventually want to extend it to
// more types of Events too (base class Event, Collision is derived class)
void onCollision(Player* p, Bomb* b) {
if (! b->exploded) {
std::cout << "BOOM";
b->exploded = true;
}
}
void onCollision(Entity* e, Entity* f) {
std::cout << "Unhandled collision\n";
}
// Possibility for Collision between Minesweeper and Bomb later
class Game {
public:
std::vector<Entity*> board; // some kind of linear board
Game() {
board = {new Player, new Bomb, new MineSweeper};
}
void main_loop() {
onCollision(board[0], board[1]); // player and bomb!
onCollision(board[1], board[2]);
}
};
int main() {
Game g;
g.main_loop();
}
Note that I understand perfectly well why the above code doesn't work as intended, I included this example solely to illustrate my problem better.
The above example uses functions for the events, but I'm perfectly fine with classes or any other solution that is maintainable.
I hope it is clear that I would like C++ to decide which event handler to use based on the types of the arguments (presumably at runtime).
My question: How can I do this in C++? An example would be appreciated.
(not my question: fix my code please)
user2864740 provided enough clues for me to find a solution myself. Multiple dispatch was indeed the missing piece.
The following code works as intended, making use of dynamic_cast to dispatch correctly.
#include <iostream>
#include <vector>
class Entity {
virtual void please_make_this_polymorphic() {}
// although this function does nothing, it is needed to tell C++ that it
// needs to make Entity polymorphic (and thus needs to know about the type
// of derived classes at runtime).
};
class Player: public Entity {};
class Bomb: public Entity {
public:
bool exploded;
};
class MineSweeper: public Entity {};
// For now, I only included Collisions, but I eventually want to extend it to
// more types of Events too (base class Event, Collision is derived class)
void onCollision(Player* p, Bomb* b) {
if (!b->exploded) {
std::cout << "BOOM\n";
b->exploded = true;
}
}
void onCollision(Entity* e, Entity* f) {
std::cout << "Unhandled collision\n";
}
void dispatchCollision(Entity* e, Entity* f) {
Player* p = dynamic_cast<Player*>(e);
Bomb* b = dynamic_cast<Bomb*>(f);
if (p != nullptr && b != nullptr) {
onCollision(p, b); // player and bomb
} else {
onCollision(e, f); // default
}
}
class Game {
public:
std::vector<Entity*> board; // some kind of linear board
Game() {
board = {new Player, new Bomb, new MineSweeper};
}
void main_loop() {
dispatchCollision(board[0], board[1]); // player and bomb
dispatchCollision(board[1], board[2]);
}
};
int main() {
Game g;
g.main_loop();
}
Although it works, I'd like to point out some problems with this code:
Manual editing of dispatchCollision needed when adding new Collisions.
Currently, the dispatcher using a simple kind of rule-based system. (Does it fit rule 1? What about rule 2? ...) When adding loads of different functions it needs to dispatch, that may have an impact on the performance.
A collision between A and B should be the same as a collision between B and A, but that isn't properly handled yet.
Solving these problems is not necessarily in the scope of this question IMHO.
Also, the example given should work just as well for more than 2 arguments. (Multiple dispatch, not just double dispatch.)
You should decide first what event subscription model you need.
It could be signal/slot mechanism and you can find plenty of libraries:
https://code.google.com/p/cpp-events/ , http://sigslot.sourceforge.net/ and the like.
Or it could be bubbling/sinking events like in HTML DOM when event gets propagated on parent/child chain ( from event source element to its containers).
Or even other schema.
It is quite easy to create whatever you need with std::function holders in modern C++.
Maybe a good structure for your case could be something like this:
class Entity{
public:
virtual int getType() = 0;
};
enum EntityTypes {
ACTOR,
BOMB,
MINESWEEPER,
};
class Actor : public Entity{
public:
virtual int getType() {return int(ACTOR);}
void applyDamage() {
std::cout << "OUCH";
}
};
class Bomb : public Entity{
public:
Bomb() : exploded(false) {}
virtual int getType() {return int(BOMB);}
void explode() {
this->exploded = true;
}
bool isExploded() {
return this->exploded;
}
protected:
bool exploded;
};
class MineSweeper : public Entity{
public:
virtual int getType() {return int(MINESWEEPER);}
};
class CollisionSolver {
public:
virtual solve(Entity* entity0, Entity* entity1) = 0;
};
class ActorBombCollisionSolver : public CollisionSolver {
public:
virtual solve(Entity* entity0, Entity* entity1) {
Actor* actor;
Bomb* bomb;
if (entity0->getType() == ACTOR && entity1->getType() == BOMB) {
actor = static_cast<Actor*>(entity0);
bomb = static_cast<Bomb*>(entity1);
}else if (entity1->getType() == ACTOR && entity0->getType() == BOMB) {
actor = static_cast<Actor*>(entity1);
bomb = static_cast<Bomb*>(entity0);
}else {
//throw error;
}
if (!bomb->isExploded()) {
bomb->explode();
actor->applyDamage();
}
}
};
class CollisionDispatcher {
public:
CollisionDispatcher() {
CollisionSolver* actorBombCollisionSolver = new ActorBombCollisionSolver;
this->solvers[ACTOR][BOMB] = actorBombCollisionSolver;
this->solvers[BOMB][ACTOR] = actorBombCollisionSolver;
// this part wouldn't be necessary if you used smart pointers instead of raw... :)
this->solvers[BOMB][MINESWEEPER] = 0;
this->solvers[MINESWEEPER][BOMB] = 0;
this->solvers[ACTOR][MINESWEEPER] = 0;
this->solvers[MINESWEEPER][ACTOR] = 0;
}
void dispatchCollision(Entity* entity0, Entity* entity1) {
CollisionSolver* solver = this->solvers[entity0->getType()][entity1->getType()];
if (!solver) {
return;
}
solver->solve(entity0, entity1);
}
protected:
unordered_map<int, unordered_map<int, CollisionSolver*> > solvers;
};
class Game {
public:
std::vector<Entity*> board; // some kind of linear board
Game() : dispatcher(new CollisionDispatcher)
{
board = {new Player, new Bomb, new MineSweeper};
}
void main_loop() {
dispatcher->dispatchCollision(board[0], board[1]);
dispatcher->dispatchCollision(board[0], board[2]);
dispatcher->dispatchCollision(board[1], board[2]);
}
protected:
CollisionDispatcher* dispatcher;
};
int main() {
Game g;
g.main_loop();
}
This way you can easily add new collision solvers, just define the class, and register t in the CollisionDispatcher constructor.
If you use smart pointers you won't need to set zeroes in the map entries not registered, but if you use raw pointers you have to set them to zero OR use unordered_map::find method instead of just grabbing the solver using operator []
Hope it helps!