I'm trying to create a Collider class which will process collisions between different classes of the same base. One restriction is that I want these classes know nothing about each other, now they depend on Base and Collider classes only. Here is my code:
collidees.h
#pragma once
#include "collider.h"
#define ALLOW_COLLIDER_VISIT \
virtual void visit(Collider& c) override { c.collide(*this); }; \
virtual void visit(Collider& c, Base& other) override { c.collide(*this, other); };
struct Base {
virtual ~Base() = default;
virtual void visit(Collider& c) = 0;
virtual void visit(Collider& c, Base& other) = 0;
};
struct A : Base {
ALLOW_COLLIDER_VISIT;
};
struct B : Base {
ALLOW_COLLIDER_VISIT;
};
struct C : Base {
ALLOW_COLLIDER_VISIT;
};
collider.h
#pragma once
#include <memory>
#include <iostream>
#define ADD_REVERSE_COLLISION(A, B) \
template<> \
inline void Collider::performCollision(B& b, A& a) \
{ \
performCollision(a, b); \
}
#define CALL_COLLIDER_FOR(CLASS) \
void collide(CLASS& a) { collider.performCollision<T, CLASS>(static_cast<T&>(base), a); }
struct Base;
struct A;
struct B;
struct C;
struct Collider;
struct HelperBase {
HelperBase(Base& base_, Collider& collider_) : base { base_ }, collider { collider_ } {}
virtual ~HelperBase() = default;
virtual void collide(A& base) = 0;
virtual void collide(B& base) = 0;
virtual void collide(C& base) = 0;
Base& base;
Collider& collider;
};
template<typename T>
struct Helper;
struct Collider {
template<typename T>
void collide(T& t) {
if (helper) {
helper->collide(t);
}
}
template<typename T>
void collide(T& t, Base& other) {
helper = std::make_unique<Helper<T>>(t, *this);
callOtherToVisit(other);
}
void callOtherToVisit(Base& other);
template<typename T1, typename T2>
void performCollision(T1& first, T2& second) {
std::cout << "No collision handler\n";
}
std::unique_ptr<HelperBase> helper = nullptr;
};
template<typename T>
struct Helper : HelperBase {
Helper(T& base, Collider& c) : HelperBase { base, c } {}
CALL_COLLIDER_FOR(A);
CALL_COLLIDER_FOR(B);
CALL_COLLIDER_FOR(C);
};
template<>
inline void Collider::performCollision(A& a, B& b) {
std::cout << "Colliding a and b\n";
}
ADD_REVERSE_COLLISION(A, B);
template<>
inline void Collider::performCollision(B& b1, B& b2) {
std::cout << "Colliding b1 and b2\n";
}
template<>
inline void Collider::performCollision(A& a, C& c) {
std::cout << "Colliding a and c\n";
}
ADD_REVERSE_COLLISION(A, C);
template<>
inline void Collider::performCollision(B& b, C& c) {
std::cout << "Colliding b and c\n";
}
ADD_REVERSE_COLLISION(B, C);
collider.cpp
#include "collider.h"
#include "collidees.h"
void Collider::callOtherToVisit(Base& other)
{
other.visit(*this);
}
main.cpp - driver code
#include "collidees.h"
#include "collider.h"
int main()
{
Collider collider;
auto a = std::make_unique<A>();
auto b = std::make_unique<B>();
auto c = std::make_unique<C>();
a->visit(collider, *b);
a->visit(collider, *c);
c->visit(collider, *b);
c->visit(collider, *a);
b->visit(collider, *a);
return 0;
}
I suppose there are some disadvantages of this code:
- It's quite complicated; adding a new collidee entails adding code in multiple places;
- There is one static_cast instead of true dispatching (although, I suppose it is safe);
- It's possible to call
void visit(Collider& c)yourself, which is meaningless; - Class
Helperstores reference to the object that can be easily deleted, which causes storing of dangling reference.
(Feel free to add other drawbacks in comments)
I would be glad to see any suggestions and improvements (or even your versions of this code).
UPDATE: Answers to comments:
- I assume collision detection as a separate step. At least in my case it is enough to add shape (or even just a rect) data to base class and find intersections of two objects, so derivatives still know nothing about each other.
- Virtual and override keywords in method declaration - it's just an inattention.
- Const correctness - again, I was focused on main problem, and missed it, although, yes, the
performCollisionmethod may modify arguments, so they cann't be const.
Anyway, thanks for replies. I think I will try to correct my code according to user673679's suggestions and then try to incorporate std::variant and std::visit as both commenters mentioned. I'll wait a little bit more for others and then aacept an answer.
Colliderclass has the actual work, and thus knows about all the different types involved, why do to collidable classes need a virtual visit function at all? \$\endgroup\$std::visit? I understand that this doesn't have a two-argument form, but I mean work in the same manner. The Collider has functions with signatures for all the classes it can handle, so having a master list as part of Collider is not out of the question. \$\endgroup\$