Lua/C++ : Programmazione Event-Driven

Prima cosa. Così la programmazione event-driven? Risposta semplice: un paradigma di programmazione in cui parti di codice vengono avviare dal lancio di eventi. Risposta ancora più semplice (per gli esperti: perdonatemi la banalizzazione). Immaginate tre persone in una casa. Immaginate che queste persone, dotate di genitori nerd molto crudeli, si chiamino Stampa, AggiungiUno e, ovviamente, Main. Main, il leader del gruppo, vuole contare tutte le macchine rosse che passano davanti la finestra.

Main ha due possibilità: andare a chiamare personalmente AggiungiUno per chiedergli di aumentare un contatore e Stampa per avere il risultato, oppure urlare “MACCHINA ROSSA” in modo tale che chi di dovere capisca che è passata una macchina rossa e aggiunga il contatore.

In questo scenario i due metodi sono pressocché equivalenti, ma supponiamo che Main non conosca il nome di chi è nella casa oppure che ad un certo punto AggiungiUno si faccia sostituire da IncrementaUno. In questo caso sarebbe impossibile programmare Main secondo il primo approccio mentre sarebbe banale programmare Main in modo da gridare MACCHINA ROSSA assumendo che ci sia qualcuno nella casa in grado di capire il significato di quel grido.

La programmazione event-driven consiste proprio nel gridare MACCHINA ROSSA. Ovviamente in programmazione non c’è il suono, sta a noi programmare il mezzo in cui gli eventi vengono propagati.

Senza entrare nel dettaglio vediamo come implementare i Lua e C++ un semplice meccanismo a eventi. Gli eventi saranno lanciati da C++ e verranno raccolti da una funzione Lua.

-- Esempio di programmazione Event-Driven
-- in Lua.

MACCHINA_ROSSA = 1000
STAMPA = 2000

numero_macchine_rosse = 0

RegisterEventHandler("EventHandler")

function EventHandler(id)
    if id == MACCHINA_ROSSA then
    print("Macchine +1")
        numero_macchine_rosse = numero_macchine_rosse + 1
    elseif id == STAMPA then
        print(string.format("%s%d", "Num. Macchine: ", numero_macchine_rosse))
    end
end

Cominciamo con lo script Lua.

Le prime due righe associano solo l’indice dell’evento che vogliamo ad una variabile mnemonica. Nulla di particolare, serve solo a rendere il codice più leggibile e chiaro.

La funzione RegisterEventHandler è importante. È una funzione LuaGlue, ovvero una funzione implementata in C++. Questa funzione comunica alla parte C++ quale è il nome della funzione che gestisce gli eventi. La funzione non è in teoria strettamente necessaria, si potrebbe, ad esempio, scrivere direttamente nel codice C++ che la funzione che gestisce gli eventi è EventHandler. Tuttavia in questo modo renderemmo il codice C++ dipendente dal codice Lua: se cambiamo il nome della funzione o se vogliamo cambiarla in run-time dobbiamo riscrivere anche il codice C++. Usando RegisterEventHandler invece le due cose sono completamente disaccoppiate.

Vediamo ora la funzione principe. EventHandler è il mezzo tramite cui gli eventi si propagano ed ha un comportamento molto semplice: riceve in ingresso l’id dell’evento e lo smista verso la funzione idonea. Nel nostro caso, poiché le funzioni sono piuttosto semplici sono scritte direttamente in EventHandler.

Ora vediamo il listato C++.

#include<iostream>
#include<luagrip.hpp>
#include<string>

#define MACCHINA_ROSSA 1000
#define STAMPA 2000

std::string strEventHandler = "";
LuaGrip* pLua;

LuaGlue _RegisterEventHandler(lua_State *L)
{
    strEventHandler = pLua->GetStringArgument(1,"");
   
    return 1;
}

void FireEvent(int id)
{
    if (strEventHandler != "")
    {
        char buf[254];
        sprintf(buf, "%s(%d)", strEventHandler.c_str(), id);
        pLua->RunString(buf);
    }
}

int main()
{
    pLua = new LuaGrip();
    pLua->AddFunction("RegisterEventHandler",_RegisterEventHandler);
    pLua->RunScript("event.lua"); // Carica il modulo Lua.
   
    FireEvent(MACCHINA_ROSSA);
    FireEvent(MACCHINA_ROSSA);
    FireEvent(STAMPA);
    FireEvent(MACCHINA_ROSSA);
    FireEvent(STAMPA);
   
    delete pLua;
}

Il codice è composto da tre funzioni.

La prima è _RegisterEventHandler. Abbiamo già parlato di questa funzione che si limita a ricevere il nome della funzione incaricata di gestire gli eventi e di memorizzarlo in una variabile globale.

La seconda è FireEvent. Come possiamo intuire dal nome questa funzione lancia gli eventi (nel nostro esempio corrisponde all’azione di gridare). In che modo? Ci sono molti modi diversi, il più semplice dei quali consiste nel creare una stringa che corrisponde ad una chiamata di funzione Lua e poi eseguirla nel solito modo.

Infine la funzione main inizializza Lua e lancia gli eventi. La sequenza descritta in main da luogo al seguente output:

Macchine +1
Macchine +1
Num. Macchine: 2
Macchine +1
Num. Macchine: 3

Questa struttura è la base del meccanismo event-driven. Ovviamente la struttura può essere ulteriormente complicata, ad esempio permettendo l’uso di eventi con argomenti (ad esempio un evento IncrementaN).

La prossima volta vorrei farvi vedere invece come sfruttare Lua per il salvataggio e il caricamento dei dati.

Categorized: C/C++, Lua
Tagged: ,
  • VP

    Domanda: perchè può risultare comodo scrivere un gestore di eventi in Lua piuttosto che in codice nativo?

    • THeK3nger

      In questo esempio banale ovviamente non ha una vera e propria utilità. Tuttavia, in generale, la regola è scrivi in C++ solo le parti che richiedono un uso intensivo della CPU e in Lua tutto il resto. L’idea è di avere in C++ tutta una serie di funzioni di medio-basso livello e usare Lua per assemblare tutto il resto.

      Alla fine di questa introduzione mi piacerebbe farvi vedere come usare Lua e C++ per realizzare un clone di Pac-Man. Prima però voglio coprire la teoria, finire di programmare il gioco e riorganizzare il codice per uno scopo didattico (fondamentalmente aggiungere tonnellate di commenti non essenziali).

    • Anonymous

      In questo esempio banale ovviamente non ha una vera e propria utilità. Tuttavia, in generale, la regola è scrivi in C++ solo le parti che richiedono un uso intensivo della CPU e in Lua tutto il resto. L’idea è di avere in C++ tutta una serie di funzioni di medio-basso livello e usare Lua per assemblare tutto il resto.

      Alla fine di questa introduzione mi piacerebbe farvi vedere come usare Lua e C++ per realizzare un clone di Pac-Man. Prima però voglio coprire la teoria, finire di programmare il gioco e riorganizzare il codice per uno scopo didattico (fondamentalmente aggiungere tonnellate di commenti non essenziali).