Esiste un modo migliore per impostare un sistema di eventi?


9

I sistemi di eventi sono sorprendenti, rendono il codice estremamente ingombrante e consentono davvero la creazione dinamica di giochi attraverso una facile comunicazione degli oggetti e del ciclo di gioco. Sto attraversando un periodo difficile con l'efficienza della mia attuale implementazione. Attualmente la mia leggera ottimizzazione nel separare le liste di oggetti dagli eventi a cui rispondono ha fatto meraviglie, ma dovrei fare di più.

Attualmente ho due metodi:

  1. Più semplice: tutti gli oggetti vengono aggiunti a un vettore quando viene inviato un evento tutti gli oggetti vengono inviati tramite il suo metodo handle_event ()

  2. Più complesso: ho una mappa con stringa come chiave e int come valore. Quando viene aggiunto un tipo di evento, viene aggiunto a questa mappa, con l'int semplicemente incrementato (deve esserci un modo migliore)
    il vettore dei vettori degli oggetti, quindi rinvia un nuovo vettore per gestire quel tipo di evento.
    Quando viene chiamato un evento, chiama semplicemente l'int corrispondente nella mappa eventTypes sul tipo all'interno del vettore dei vettori degli oggetti e invia quell'evento a ciascun oggetto che gestisce quel tipo di evento.

Questo primo metodo è piuttosto lento (ovviamente) per molti oggetti, ma abbastanza veloce per pochissimi oggetti. Considerando che il secondo metodo è abbastanza veloce con oggetti di grandi dimensioni che vorrebbero gestire diversi tipi di eventi, ma più lento del primo metodo per oggetto con oggetti che gestiscono lo stesso tipo di evento.

Esiste un modo più veloce (tempo di esecuzione)? C'è un modo più veloce per cercare un int dal tipo di stringa? (Inizialmente avevo un enum, ma non permettevo tipi personalizzati, che sono necessari a causa del livello desiderato di dinamismo.)


1
Questo è il tipo di Hash Maps (o Hash Table), la stringa viene calcolata in base a un numero di hash che viene quindi utilizzato per cercare direttamente in un array. en.wikipedia.org/wiki/Hash_table
Patrick Hughes,

Una buona domanda è: è davvero un collo di bottiglia delle prestazioni o ti preoccupi solo prematuramente?
Jari Komppa,

È già implementato e c'è un collo di bottiglia, quando vengono utilizzati molti oggetti. Il mio precedente commento sulla mancanza di colli di bottiglia non era nell'applicazione stessa, ma in realtà la differenza di velocità tra le due implementazioni sopra in cui lo stesso numero di oggetti è stato effettivamente gestito. Speravo in altri metodi per creare sistemi di eventi ... Tuttavia alcune delle idee in questo thread aumenteranno un po 'la velocità, specialmente nel caricamento del sistema di eventi (tempo di caricamento di 11-25 secondi completi con 100000 oggetti)
ultifinitus

100.000 oggetti sembrano terribilmente alti solo per un gioco. Si tratta di un server o di un'applicazione client per l'utente finale?
Patrick Hughes,

Questo è il mio motore, quindi vado davvero alla flessibilità. Verrà utilizzato nelle applicazioni server e nelle applicazioni degli utenti finali (meno spesso il primo, ottimizzazione)
ultifinitus,

Risposte:


5

Sembra che tu stia dicendo che il collo di bottiglia delle grandi prestazioni sta cercando gli ID evento (numeri interi) dai loro nomi di stringa. È possibile preelaborare i dati di gioco per convertire tutti i nomi di eventi in numeri interi prima di avviare il gioco o eventualmente durante il caricamento del livello; quindi non dovresti fare alcuna conversione durante il gioco.

Se gli oggetti vengono spesso creati e distrutti, potrebbe esserci un sacco di sfocatura nei vettori degli oggetti. In questo caso potresti trarre vantaggio dall'uso di elenchi collegati anziché di vettori; sono più veloci per l'inserimento e l'eliminazione.


Il collo di bottiglia non è grande (per 100.000 oggetti perde .0000076 ms / oggetto) ma penso che la tua idea sia una grande idea! Penso che farò effettivamente una ricerca dell'ID una volta e che l'ID evento sia memorizzato come int anziché come dati della stringa originale. E in realtà non avevo pensato alle liste collegate, buona idea.
ultifinito

1
+1 Per preelaborare gli ID. Puoi anche farlo pigramente avendo un tipo EventType che ogni modulo potrebbe ottenere tramite qualcosa di simile EventType takeDamageEvent = EventSystem::CacheEventType("takeDamageEvent");. Rendilo un membro statico delle classi e avrai una sola copia fluttuante in ogni classe che ne ha bisogno.
michael.bartnett,

2
Si noti che la memorizzazione di ID anziché di stringhe renderà un po 'difficile il debug. È sempre utile poter vedere del testo che specifica da dove proviene un oggetto. Puoi andare a metà strada memorizzando sia l'id che la stringa, che tieni in giro per scopi di debug (forse viene persino rimosso nelle build di rilascio).
Nicol Bolas,

2

Bene, per prima cosa togliamo le cose semplici. Hai questo maptra stringhe (il nome dell'evento, presumibilmente) e numeri interi (l'indice dei listener di eventi registrati).

Il tempo di ricerca in a mapsi basa su due cose: il numero di elementi nella mappa e il tempo impiegato per eseguire il confronto tra due chiavi (ignorando i problemi di cache). Se il tempo di ricerca è un problema, un modo per gestirlo è cambiare la funzione di confronto cambiando il tipo di stringa.

Supponiamo che tu stia usando std::stringe operator<per il confronto. Questo è altamente inefficiente; fa un confronto per byte. Non ti importa della stringa reale meno del confronto; hai solo bisogno di un tipo di confronto che dia un ordinamento rigorosamente debole (perché mapnon funziona diversamente).

Quindi dovresti usare una stringa di lunghezza fissa a 32 byte invece di std::string. Li uso per identificatori. I test di confronto per queste stringhe fisse non eseguono confronti a byte; fanno invece confronti a 32-bit (o 64-bit). Prende ogni 4 byte come numero intero senza segno e lo confronta con i 4 byte corrispondenti dell'altra stringa. In questo modo, il confronto richiede solo al massimo 8 confronti. Assicura un ordinamento rigorosamente debole, sebbene l'ordinamento non abbia nulla a che fare con i dati come caratteri.

La memorizzazione di stringhe più lunghe di 31 byte (è necessario il carattere NULL) tronca la stringa (ma dal centro, piuttosto che dalle estremità. Trovo che l'entropia tende ad essere massima all'inizio e alla fine). E stringhe più corte di quel pad con i rimanenti caratteri \0.

Ora, potresti abbandonare mapcompletamente e usare una tabella hash. Se hai davvero oltre 100.000 diversi tipi di eventi, questa potrebbe essere una buona idea. Ma non conosco un gioco in cui sarebbe una cosa lontanamente ragionevole.


Quindi dovresti usare una stringa di lunghezza fissa a 32 byte invece di std :: string. Fantastico! Non avevo pensato di cambiare il tipo di stringa.
ultifinito

0

Per rispondere alla domanda generale:

Modo migliore per impostare un sistema di eventi

Non c'è nessuno. Tutto quello che puoi fare è identificare le esigenze specifiche che avrai per i tuoi sistemi di eventi (ci possono essere diversi) e quindi utilizzare lo strumento giusto per il lavoro giusto.

Ho implementato e cercato di generalizzare tonnellate di sistemi di eventi e sono giunto a questa conclusione. Poiché i compromessi sono davvero diversi se lo si esegue in fase di compilazione o di runtime, se si utilizzano gerarchie di oggetti, lavagne, ecc.

Il modo migliore è studiare i diversi tipi di sistemi di eventi e quindi avrai indizi su quale utilizzare in quale caso.

Ora, se vuoi il sistema davvero più flessibile, implementa un sistema black-board (runtime) con proprietà dinamiche degli eventi e avrai qualcosa di molto flessibile ma forse molto lento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.