Come è stato scelto un design per un sistema di eventi

2

Sto lavorando su un sistema di componenti di entità per scopi didattici. Ho apportato importanti modifiche al mio design, quindi mi chiedevo se avrei potuto scegliere un design migliore per il mio sistema di eventi.

The purpose of the event system (if you don't know ECS), is to allow communication between process. For example, in a video game (that's always more easy to understand with video games) your collision system may want to tell to other system when there is a collision and with which entities.

Con questo piccolo contesto, il mio attuale design per il sistema degli eventi era più:

  • Quando un evento viene emesso - > salvalo in event manager (ptr condiviso)
  • Quindi chiama tutti gli iscritti e invia loro un ptr condiviso dei dati ( grazie a Bart van Ingen Schenau )
  • All'interno del callback, salva l'evento in una coda per consumarlo sul prossima chiamata di aggiornamento

Ma gli eventi sono di sola lettura, quindi mi chiedo se qualcosa del genere non sarà migliore:

  • Quando un evento viene emesso - > chiama ogni abbonato e invia loro una copia dell'evento
  • All'interno del callback, spostare l'evento in una coda per consumarlo sul prossima chiamata di aggiornamento

I sistemi saranno multithreading (il che significa che almeno gli eventi possono essere emessi su qualsiasi thread).

Una delle parti più importanti del mio nuovo design, è che il sistema è "diviso" in blocchi (puoi vedere come diverse funzioni di aggiornamento). Ma gli eventi sono per l'intero sistema. Quindi le copie significheranno che avrò bisogno di una funzione di garbage collector per ogni sistema per liberare gli eventi che consumano. Beh, in effetti, ho ancora bisogno di questo con PTR condiviso.

Probabilmente ci sono molti altri design. Quindi, se pensi a qualcosa di buono per un ECS generico (non solo per i videogiochi), dove gli abbonati agli eventi tendono a raggrupparsi, dammi il tuo pensiero per favore.

    
posta Mathieu Van Nevel 11.01.2017 - 20:25
fonte

2 risposte

2

Nel mio caso non mi preoccupo di copiare eventi o qualcosa del genere e non c'è alcuna preoccupazione per la sicurezza dei thread a livello di elaborazione degli eventi poiché tutto è svolto da un singolo thread. :-D

Detto questo, utilizza ancora una coda concorrente poiché thread diversi potrebbero voler simultaneamente spingere eventi, ma c'è un solo gestore centrale scoppiare eventi e invocare un process_events callback su sistemi che lo specificano (puntatore a funzione non nulla alla registrazione del sistema - il mio SDK utilizza un'API C). Quel gestore di eventi centrale trascorre anche la maggior parte del tempo dormendo mentre attende di essere informato dal momento che non faccio spam con il sistema così tanto con gli eventi.

I sistemi quindi eseguono semplicemente il ciclo attraverso la matrice di sola lettura di eventi di sola lettura estratti dal gestore di eventi centrale e fanno tutto ciò che devono fare, e tutto ciò avviene all'interno del thread di gestione degli eventi. In base alla progettazione, è consigliabile che i sistemi non svolgano alcun lavoro pesante nell'evento che elabora i callback. Invece, se hanno un lavoro interessante e complesso da fare, possono farlo da soli per processare nei loro loop di elaborazione principali che potrebbero essere in esecuzione in un thread diverso dall'handling di gestione degli eventi o generare una nuova discussione specificatamente in risposta a quell'evento.

Trovo che questa sia la soluzione più semplice su cui ragionare. Cerco anche di semplificare la gestione degli eventi e di minimizzarne l'utilizzo poiché trovo il tipo più scomodo di "comunicazione tra sistemi" possibile. Personalmente non ho mai apprezzato la programmazione basata sugli eventi, trovandola una necessità ma non qualcosa su cui voglio aggrapparmi troppo e cercando sempre di evitarla quando possibile. Mi piacciono i flussi di controllo più facilmente prevedibili quando possibile, dove non sono mai sorpreso di "quando" qualcosa accade. Ho anche fatto dogmaticamente un errore superficiale (anche se il sistema è in grado di gestirlo) per provare a inviare eventi dall'evento che gestisce i callback. MrGreen odio gli eventi a cascata in cui un evento innesca un altro in modo ricorsivo e quindi ho deliberatamente reso doloroso il mio sistema nella speranza che chiunque sia tentato di farlo penserà qualcos'altro con un design "più piatto, non più profondo" che non eventi a cascata in tutto il sistema.

    
risposta data 12.12.2017 - 18:35
fonte
3

Entrambi i progetti sono in linea di principio fattibili e quale design è il migliore dipende più da come vengono utilizzati gli eventi e da quante informazioni un evento porta con sé.

Lo svantaggio principale dell'invio di copie di eventi è che se si hanno eventi con molti dati che vengono inviati a molti abbonati, questo richiederà un bel po 'di memoria. Inoltre, con l'invio di copie sarà più difficile supportare eventi di dimensioni variabili (dove eventA trasporta più informazioni di eventB, ma entrambi possono esistere nella stessa coda).

Il problema principale con il design del puntatore condiviso, come descritto, è il problema della durata degli eventi. Poiché gli abbonati ricevono solo un weak_ptr e lo memorizzano in una coda, è difficile dire quando tutti i sottoscrittori hanno gestito l'evento e quando è sicuro distruggere shared_ptr.
Questo può essere facilmente risolto inviando shared_ptrs ai subscriber e lasciando che il conteggio dei riferimenti inerente a shared_ptr faccia il suo lavoro. La modifica accidentale dell'evento può essere evitata utilizzando shared_ptr<const Event> .

    
risposta data 12.01.2017 - 09:28
fonte

Leggi altre domande sui tag