Разработка API события опроса
Вопрос
Скажем, вы разрабатывали библиотеку окон C ++. Он может предоставлять или не предоставлять API обратного вызова, но должен предоставлять API опроса для упрощения функционального стиля программирования.
Как будет выглядеть API опроса?
Некоторые параметры
стиль SDL
struct Event {
enum { MousePress, KeyPress } type;
union {
struct { Point pos; MouseButton b; } mousePress;
struct { Modifiers mods; char key; } keyPress;
};
};
void userCode() {
for(;;) {
Event e; if(pollEvent(&e)) {
switch(e.type) {
case MousePress: cout<<event.mousePress.pos.x; break; // not typesafe
case KeyPress: cout<<event.keyPress.key; break;
}
}
}
}
Государственный стиль
struct Input {
enum { Mouse, Keyboard, Nothing } whatChanged;
MouseButtonsBitfield pressedButtons;
bool keysPressed[keyCount];
};
void userCode() {
for(;;) {
Input in = pollInput();
switch(in.whatChanged) {
// typesafe yay
case Mouse: cout << "is LMB pressed? " << bool(in.pressedButtons&LeftButton); break;
case Keyboard: cout << "is A pressed? " << in.keysPressed['A']; break;
}
}
}
Интересный функциональный псевдо-C ++ стиль
struct Event {
// transforms listener by notifying it of event,
// returns transormed listener. nondestructive.
template<class Listener> // sadly invalid, templates can't be virtual.
// a solution is to make Listener the base
// of a hierarchy and make Listener::handle virtual
// but then we're forced to use imperative style
virtual Listener transform(Listener const&) =0;
};
struct MousePress : Event { // yay we're extensible via inheritance
template<class Listener>
virtual Listener transform(Listener const& listener) {
return listener.handle(*this); // calls the MousePress overload
}
Point pos; MouseButton b;
};
struct KeyPress : Event {
template<class Listener>
virtual Listener transform(Listener const& listener) {
return listener.handle(*this); // calls the KeyPress overload
}
Modifiers mods; char key;
};
struct NoEvent : Event {
template<class Listener>
virtual Listener transform(Listener const& listener) {
return listener.handle(*this);
}
};
struct UserWidget {
UserWidget handle(NoEvent) {
return UserWidget();
}
UserWidget handle(MousePress p) {
return (UserWidget) { string("pressed at")+lex_cast<string>(p.pos)) };
}
UserWidget handle(KeyPress k) {
return (UserWidget) { string("pressed key=")+lex_cast<string>(k.key)) };
}
string pendingOutput;
};
void userTick(UserWidget const& w) {
cout<<w.pendingOutput;
userTick(pollEvent().transform(w));
}
void userCode() {
userTick(UserWidget());
}
Ответы для языков, отличных от C ++, в порядке, если они дают интересную информацию.
Пожалуйста, не комментируйте инкапсуляцию - да, публичные поля действительно должны быть средствами доступа, я оставил это для ясности.
Решение
Чтобы быстро ответить на ваш вопрос, я предпочитаю простоту «кода в стиле SDL». Главным образом потому, что ваш немного более сложный «State Style» напрасно тратит память и абсолютно ничего не покупает (см. ниже), а рекурсия в вашем замученном «Функциональном псевдо-C ++» Стиль переполняет стек в течение нескольких миллисекунд.
" State Style " : ваша " безопасная yay " в «Государственном стиле» код немного неоправдан. Вы по-прежнему решаете, к какому члену обращаться, основываясь на switch
на другом члене, поэтому у кода есть те же слабые стороны, что и у "SDL Style". В коде есть - за любую ошибку, которую вы могли бы совершить с кодом в стиле SDL, который приводит к интерпретации памяти как неправильного типа, вы бы совершили столь же грубую ошибку при доступе к неинициализированному элементу с кодом в стиле State.
" Функциональный стиль псевдо-C ++ " . Теперь вы попадаете куда-то, наследуя различные типы событий от базового типа события. Очевидно, что глупая рекурсия должна стать циклом, и есть несколько мелких вещей, которые нужно привести в порядок (я думаю, что ваши 3 метода с именем transform ()
в UserWidget
хотят быть вызванными handle ()
; я предполагаю, что вы можете решить проблему отсутствия шаблонных виртуальных методов, используя Boost.Function или аналогичные). Я думаю, что у этого подхода есть потенциал, хотя я предпочитаю простоту стиля SDL.
Но более существенно: я подвергаю сомнению необходимость интерфейса опроса. Есть ли причина, по которой pollEvent ()
не может блокироваться? На самом деле, все 3 сегмента кода сжигают процессорное время, ничего не делая 99,99% времени.