Entwurf eines Abfrageereignis API
Frage
Sagen Sie bitte ein C ++ entwerfen Windowing-Bibliothek. Es kann oder auch nicht einen Callback-API zur Verfügung stellen, sondern braucht eine Abfrage API, um eine funktionale Art der Programmierung zu erleichtern.
Was das Abfrage-API aussehen würde?
Einige Optionen
SDL Stil
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;
}
}
}
}
Staat Stil
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;
}
}
}
Spaß funktionelle pseudo-C ++ Stil
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());
}
Antworten für andere Sprachen als C ++ sind in Ordnung, wenn sie interessante Einblicke bieten.
Keine Kommentare zu Verkapselung bitte - ja öffentliche Felder wirklich Accessoren sein sollten, verließ ich, dass aus Gründen der Klarheit aus
.Lösung
Ihre Frage schnell zu beantworten, ich die Einfachheit des „SDL-Stil-Code“ bevorzugen. Vor allem, weil Ihr etwas komplizierter „State Style“ Abfälle Speicher und kauft man absolut nichts (siehe unten), und die Rekursion in Ihrer gefoltert „Functional pseudo-C ++“ Stil innerhalb von wenigen Millisekunden den Stapel überläuft.
"State Style" : Ihre "typsicher yay" in dem "State Style" Code ist ein bisschen unberechtigt. Sie sind immer noch entscheiden, welches Mitglied auf einem switch
auf ein anderes Mitglied basierend auf Zugang, so dass der Code hat die gleichen Schwächen, dass die „SDL Style“ Code hat - für jeden Fehler, den Sie mit dem SDL-Stil-Code machen könnte, die dazu führt, Speicher als die falsche Art zu interpretieren, würden Sie das gleich schlecht Fehler machen, ein uninitialised Mitglied mit dem Staat-Stil-Code zugreifen.
"Funktions pseudo-C ++ Stil" : Jetzt bist du irgendwo bekommen, verschiedene Ereignistypen von einer Basisereignistyp erben. Offensichtlich ist die dumme Rekursion muss eine Schleife werden, und es gibt ein paar kleine Dinge aufzuräumen (Ich denke, Ihre 3 Methoden transform()
in UserWidget
nennen wollen handle()
genannt werden; ich vermute, dass Sie das Problem ohne Vorlage lösen können virtuelle Methoden Boost.Function oder ähnliches). Ich denke, dass dieser Ansatz Potenzial hat, obwohl ich die Einfachheit der SDL-Stil bevorzugen.
Aber noch grundsätzlicher: Ich frage die Notwendigkeit einer Polling-Schnittstelle. Gibt es einen Grund, warum pollEvent()
kann nicht blocken? Wie es aussieht, sind alle drei Codesegmente CPU-Zeit brennen nichts 99,99% der Zeit zu tun.