= Il modulo event e la gestione degli eventi =
La generazione di eventi da parte di un modulo (A) di Netsukuku e l'ascolto di tali eventi e la loro gestione da parte di un altro modulo (B) sono uno dei meccanismi di comunicazione tra due moduli distinti.
Le funzioni contenute in un modulo, servono di norma a gestire un particolare aspetto del programma. Di norma sono eseguite da un microthread a cui questo aspetto viene demandato. Quando durante questa esecuzione si vogliono inviare messaggi, richiedere servizi ad altri moduli, o comunque segnalare il completamento di particolari azioni, si emettono dei segnali, o eventi.
''Nota'': nel corso di questa trattazione i termini "''emettere segnali''" e "''generare eventi''" sono equivalenti.
I moduli che sono interessati a questi eventi lo hanno già dichiarato '''registrando''' come ''listeners'' di questi eventi alcune loro funzioni.
<
>
In particolare se queste funzioni sono definite come ''microfunc'', come vedremo meglio in seguito, si ottiene che la gestione degli eventi da parte di questi moduli avvenga in distinti microthread, o in un microthread ''dispatcher'' che li gestisce uno alla volta, e così via.
== Il modulo A dichiara di generare certi eventi ==
Di norma gli aspetti di un modulo sono gestiti da una istanza della classe principale definita nel moduolo stesso.
<
>
Nel suo costruttore, questa classe dice quali eventi può generare con una sintassi di questo tipo:
{{{
class A:
def __init__(self):
self.events = Event( ['EVENT1', 'EVENT2', 'FOOEVENT'] )
...
self.events.add( ['EVENT3', 'EVENT4'] )
}}}
== Il modulo B dichiara di voler gestire certi eventi generati dal modulo A ==
Anche qui tale dichiarazione avviene nel costruttore della classe principale del modulo, la cui istanza gestisce gli aspetti del programma demandati al modulo.
<
>
Nel costruttore dunque, il modulo B, che deve avere un riferimento alla istanza di A, dichiara di voler gestire i suoi eventi con una sintassi di questo tipo:
{{{
class B:
def __init__(self, a)
# a is an instance of the class A
a.events.listen('EVENT1', self.func_for_ev1)
}}}
B dice che quando A genera eventi di tipo '{{{EVENT1}}}' deve essere eseguito il suo metodo {{{func_for_ev1}}}.
<
>
Il metodo {{{func_for_ev1}}} deve accettare i parametri che sono indicati dal modulo A quando genera eventi di tipo '{{{EVENT1}}}'.
{{{
class B:
...
def func_for_ev1(param1, param2, param3):
...
}}}
Se la funzione registrata come ''listener'' è una normale funzione, questa viene direttamente richiamata nel momento in cui A genera l'evento. Questo significa che l'algoritmo svolto da A viene bloccato. Se invece si vuole che la gestione degli eventi da parte del modulo B sia svolta in un diverso microthread per non bloccare l'algoritmo di A, basterà definire la funzione come ''microfunc'' (vedere il [[../ModuloMicro|modulo micro]])
{{{
class B:
...
@microfunc()
def func_for_ev1(param1, param2, param3):
...
}}}
== Il modulo A genera un evento ==
Durante l'esecuzione del programma Netsukuku, il modulo A vuole segnalare al modulo B (e a chi altri fosse interessato) che è avvenuto qualcosa, cioè vuole generare un evento di tipo '{{{EVENT1}}}'.
{{{
class A:
...
def f(self):
self.events.send('EVENT1', (1, 2, 'terzo'))
}}}
''Nota'': fare attenzione che i parametri sono passati come una tupla. Ricordare che per dichiarare una tupla con un solo elemento va messa una virgola prima di chiudere la parentesi. Ad esempio:
{{{
self.events.send('EVENT1', (1, ))
}}}
== La classe Event ==
La classe {{{Event}}} definita nel modulo '''event''' realizza il meccanismo di dichiarazione/registrazione/generazione di eventi descritto sopra.
<
>
Infatti nel suo costruttore il modulo A crea una istanza di {{{Event}}} e la memorizza, per convenzione, nel suo membro {{{self.events}}}. Il modulo B usa il metodo {{{listen}}} della istanza di {{{Event}}} memorizzata da A in {{{a.events}}} per registrare le sue funzioni ''listeners''. Il modulo A usa il metodo {{{send}}} della istanza di {{{Event}}} memorizzata in {{{self.events}}} per generare gli eventi, e questo fa si che le funzioni ''listeners'' siano richiamate.
== Il decoratore wakeup_on_event ==
Nel modulo event viene definito anche un decoratore di funzioni chiamato '''wakeup_on_event'''.
Con questo decoratore si indica che una funzione può mettersi in attesa, durante la sua esecuzione, di un certo evento.
<
>
Al momento della definizione della funzione occorre indicare di quali eventi si può mettere in attesa la funzione nel suo interno. Ogni evento è definito dalla tupla {{{(EventInstance, EventName)}}} dove {{{EventInstance}}} è una istanza della classe {{{Event}}} (ad esempio {{{neigh.events}}}) e {{{EventName}}} è il nome dell'evento (ad esempio 'NEIGH_NEW')
<
>
Inoltre occorre che la funzione definisca l'argomento con keyword {{{event_wait}}} con un valore default arbitrario.
{{{
@wakeup_on_event(events=[(wheater_events, 'IT_IS_SUNNY'), (self.neigh.events, 'NEIGH_NEW')])
def go_out(param1, param2, event_wait=None):
...
msg = event_wait[(wheater_events, 'IT_IS_SUNNY')]() # Block and wait the event 'IT_IS_SUNNY'
...
msg2 = event_wait[(self.neigh.events, 'NEIGH_NEW')]() # Block and wait the event 'NEIGH_NEW'
...
}}}
La funzione al suo interno si ritrova valorizzato l'argomento {{{event_wait}}} con un dizionario che associa la tupla {{{(EventInstance, EventName)}}} con una funzione.
<
>
Richiamando questa funzione con la sintassi
{{{
msg = event_wait[(wheater_events, 'IT_IS_SUNNY')]()
}}}
si ottiene che il microthread si blocca (lasciando lo schedulatore agli altri) in attesa dell'evento. Quando l'evento viene generato la funzione restituisce i dati associati all'evento. Quindi avremo nella variabile {{{msg}}} la tupla {{{(param1, param2, param3)}}}.