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
Helper
stores 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
performCollision
method 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.
Collider
class 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\$