Это полезное обсуждение возможных решений, которые помогли мне решить ту же проблему, и я добавляю свое решение на случай, если оно окажется полезным.
Постановка задачи
Мой сценарий более общий, чем сценарии, рассмотренные BIC, L.Senionis и N0mber. В частности, мой вариант использования требует:
- Как правило, данные экземпляра должны быть доступны для обратного вызова
- Многие приложения могут быть созданы с использованием общего набора обработчиков ответов
- В приложении может быть создано любое количество окон
- Набор обратных вызовов, прикрепленных к каждому окну, следует смешивать и сопоставлять с определенной библиотекой возможных респондентов.
Использование предлагаемого решения
Простой синглтон-дизайн больше не решает проблему. Вместо этого я предоставляю суперкласс GLFWResponder
, который обрабатывает все сложности установки. Чтобы использовать класс и прикрепить ответ к окну, вот что требуется.
// Implement custom responder
class MyResponder : public GLFWResponder {
public:
virtual void cursor_position_callback(GLFWwindow* w, double x, double y) {...}
... override relevant callbacks ...
};
// in main ************************************************
// Assuming initialized GLFWwindow* my_window and my_other_window
MyResponder resp;
MyResponder resp2; // Can be another subclass of GLFWResponder
// Two responders can respond to same window
resp.respond_to(my_window, GLFWResponder::CURSOR_POSITION);
resp2.respond_to(my_window, GLFWResponder::CURSOR_POSITION);
// One responder can respond to multiple windows
resp2.respond_to(my_other_window, GLFWResponder::CURSOR_POSITION);
// One window can have different handlers for different events
resp.respond_to(my_other_window, GLFWResponder::CURSOR_ENTER);
Реализация предлагаемого решения
Вот эскиз реализации GLFWResponder
, полностью функциональной, но с некоторыми TODO. Могут быть некоторые последствия для производительности, которые я еще не исследовал.
// GLFWResponder.h ************************************************
/**
* Responder superclass that allows subclasses to handle events from multiple
* GLFW windows (which have only C API for callbacks).
* Callbacks are automatically cleaned up when responder goes out of scope.
*/
class GLFWResponder {
public:
virtual ~GLFWResponder();
// Interface -----------------------------------
enum GLFWEventType {
CURSOR_POSITION = 0,
CURSOR_ENTER = 1
// TODO: add support for other callbacks
};
void respond_to(GLFWwindow* window, GLFWEventType event);
bool does_respond_to(GLFWwindow* window, GLFWEventType event) const;
// Subclasses implement ------------------------
virtual void cursor_position_callback(GLFWwindow* window, double xpos, double ypos);
virtual void cursor_enter_callback(GLFWwindow* window, int entered);
// TODO: add support for other callbacks
// Under the hood ------------------------------
static std::set<GLFWResponder*> getResponders(GLFWwindow* windo, GLFWEventType event);
private:
// Windows and events that this instance responds to
std::set<std::pair<GLFWwindow*, GLFWEventType> > enabled_events_;
// Global responders keyed by events they respond to
// (each responder knows which windows it responds to)
static std::map<GLFWEventType, std::set<GLFWResponder*> > responders_;
};
// GLFWResponder.cpp **************************************************
namespace {
void cursor_position_callback_private(GLFWwindow* window, double xpos, double ypos) {
for (GLFWResponder* r : GLFWResponder::getResponders(window, GLFWResponder::CURSOR_POSITION)) {
r->cursor_position_callback(window, xpos, ypos);
}
}
void cursor_enter_callback_private(GLFWwindow* window, int entered) {
for (GLFWResponder* r : GLFWResponder::getResponders(window, GLFWResponder::CURSOR_ENTER)) {
r->cursor_enter_callback(window, entered);
}
}
} // namespace
std::map<GLFWResponder::GLFWEventType, std::set<GLFWResponder*> > GLFWResponder::responders_;
GLFWResponder::~GLFWResponder() {
for (auto& pr : responders_) {
pr.second.erase(this);
}
// TODO: also clean up window's callbacks
}
void GLFWResponder::respond_to(GLFWwindow* window, GLFWResponder::GLFWEventType event) {
enabled_events_.insert(std::make_pair(window, event));
responders_[event].insert(this);
if (event == CURSOR_POSITION) {
glfwSetCursorPosCallback(window, cursor_position_callback_private);
} else if (event == CURSOR_ENTER) {
glfwSetCursorEnterCallback(window, cursor_enter_callback_private);
} else {
// TODO: add support for other callbacks
LOG(FATAL) << "Unknown GLFWResponder event: " << event;
}
}
bool GLFWResponder::does_respond_to(GLFWwindow* window, GLFWEventType event) const {
return enabled_events_.find(std::make_pair(window, event)) != enabled_events_.end();
}
std::set<GLFWResponder*> GLFWResponder::getResponders(
GLFWwindow* window, GLFWEventType event) {
std::set<GLFWResponder*> result;
auto it = responders_.find(event);
if (it != responders_.end()) {
for (GLFWResponder* resp : it->second) {
if (resp->does_respond_to(window, event)) {
result.insert(resp);
}
}
}
return result;
}
void GLFWResponder::cursor_position_callback(
GLFWwindow* window, double xpos, double ypos) {
// TODO: fail with message "GLFWResponder::do_respond called on a subclass that does not implement a handler for that event"
}
void GLFWResponder::cursor_enter_callback(GLFWwindow* window, int entered) {
// TODO: fail with message "GLFWResponder::do_respond called on a subclass that does not implement a handler for that event"
}