Progettazione di un'API di eventi polling
Domanda
Supponi che stavi progettando una libreria di finestre C ++. Può o meno fornire un'API di callback, ma deve fornire un'API di polling per facilitare uno stile funzionale di programmazione.
Che aspetto avrebbe l'API di polling?
Alcune opzioni
stile 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;
}
}
}
}
Stile stato
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;
}
}
}
Divertente stile pseudo-C ++ funzionale
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());
}
Le risposte per lingue diverse da C ++ sono OK, se forniscono informazioni interessanti.
Non ci sono commenti sull'incapsulamento per favore - sì, i campi pubblici dovrebbero davvero essere accessori, l'ho lasciato fuori per chiarezza.
Soluzione
Per rispondere rapidamente alla tua domanda, preferisco la semplicità del "codice in stile SDL". Principalmente perché il tuo stile "stato" leggermente più complicato spreca memoria e non ti compra assolutamente nulla (vedi sotto), e la ricorsione nel tuo pseudo-C ++ funzionale torturato " lo stile traboccerà dello stack in pochi millisecondi.
" Stile di stato " : il tuo " typesafe yay " nello "Stile di stato" il codice è un po 'ingiustificato. Stai ancora decidendo a quale membro accedere in base a un switch
su un altro membro, quindi il codice presenta tutti gli stessi punti deboli dello stile "SDL" codice ha - per qualsiasi errore che potresti commettere con il codice in stile SDL che porta a interpretare la memoria come il tipo sbagliato, commetterai l'errore altrettanto grave di accedere a un membro non inizializzato con il codice in stile Stato.
" Stile pseudo-C ++ funzionale " : ora stai arrivando da qualche parte, ereditando diversi tipi di eventi da un tipo di evento di base. Ovviamente la stupida ricorsione deve diventare un ciclo, e ci sono alcune piccole cose da mettere in ordine (penso che i tuoi 3 metodi chiamati transform ()
in UserWidget
vogliano essere chiamati handle ()
; Immagino che tu possa risolvere il problema senza metodi virtuali di template usando Boost.Function o simili). Penso che questo approccio abbia un potenziale, anche se preferisco la semplicità dello stile SDL.
Ma soprattutto: metto in dubbio la necessità di un'interfaccia di polling. C'è un motivo per cui pollEvent ()
non può bloccare? Allo stato attuale, tutti e 3 i segmenti di codice stanno bruciando il tempo della CPU senza fare il 99,99% delle volte.