= 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)}}}.