Strutture di dati multiple contenenti gli stessi riferimenti

4

Mi chiedevo quale sia la pratica migliore per creare più strutture di dati differenti che possono contenere riferimenti agli stessi oggetti. Ad esempio, considera un videogioco con molte entità.

Per iterare attraverso le entità, si vorrebbe probabilmente usare una matrice o un elenco di entità in modo che possano essere localizzate in blocchi di memoria contigui e iterate rapidamente. Per gli aggiornamenti di fisica, tuttavia, si desidera che le stesse entità siano memorizzate in una struttura ad albero o in una matrice multidimensionale corrispondente alle loro posizioni (in modo da ottenere O (n) ridimensionamento per le collisioni anziché O (n ^ 2).

Mi chiedo quale sia il modo migliore per controllare l'accesso a tali strutture dati in modo tale che l'eliminazione di un'entità la rimuova da entrambe le strutture. Si può aggiungere / rimuovere dalle strutture solo attraverso un metodo / classe ausiliario, ma questo sembra un po 'hacky e non impedisce a qualcuno di eliminare l'oggetto da qualche altra parte. È possibile memorizzare un riferimento alle strutture di dati di contenimento negli oggetti e quindi richiamare la loro rimozione nel distruttore.

Qualcuno conosce un modo particolarmente efficace per farlo?

    
posta Graham Reid 06.07.2018 - 08:05
fonte

2 risposte

4

Gestire la rimozione tramite una funzione centrale che rimuove anche i riferimenti dagli indici correlati è la strada da percorrere, nonostante i dubbi:

You could add to/remove from the structures only through an auxiliary method/class, but this feels kinda hacky and doesn't prevent someone from just deleting the object from somewhere else.

Se stai lavorando in una lingua raccolta con garbage, l'oggetto non verrà rimosso mentre i riferimenti sono attivi. Almeno un riferimento è il blocco di memoria contiguo. Quindi devi rimuovere i riferimenti esplicitamente in ogni caso. L'uso di una funzione centrale rende tutto più semplice. (Naturalmente, la maggior parte dei linguaggi di GC non ti permette di usare blocchi di memoria contigui.)

Se lavori in un'altra lingua come C o C ++, solo il proprietario di quell'oggetto dovrebbe liberare / eliminare l'oggetto. La funzione per rimuovere un oggetto e i suoi riferimenti dovrebbe far parte del proprietario. Qui, il proprietario sarà il codice che gestisce il blocco di memoria contigua. Se un altro codice libera direttamente l'oggetto è semplicemente un bug (che potresti essere in grado di rilevare con un debugger di memoria come Valgrind).

Soprattutto per C ++ ci sono molti concetti che potrebbero essere utili, come deleteri personalizzati per std::unique_ptr , placement-new, allocatori personalizzati, operatori di overload sovraccarico e così via. Questo ti consente di creare astrazioni difficili da usare impropriamente e può aiutare a ripulire le risorse non necessarie tramite RAII.

    
risposta data 06.07.2018 - 12:06
fonte
0

Modifica: il secondo tentativo di questa risposta mi sono reso conto che stavo per essere trascinato in dettagli microscopici. Proverò a semplificare qui.

Pattern osservatore / coda eventi

Quindi tra le soluzioni più dirette a questo problema è usare qualcosa come Observer o qualche tipo di coda di eventi centralizzata. Il tuo sistema di rilevamento collisioni può quindi essere chiamato immediatamente in modo indiretto come osservatore del soggetto (la tua scena di gioco) o può essere passato a una serie di eventi che sono avvenuti indirettamente / astrattamente dal sistema centrale e poi scoprire cosa è successo al scena (es: elemento inserito / rimosso). Quindi puoi rispondere aggiornando l'indice spaziale di conseguenza.

Tuttavia, nel corso degli anni ho sviluppato un strong disgusto per questi tipi di soluzioni in grandi progetti perché, nel caso sfortunato in cui devi eseguire il debug di qualcosa che va storto durante una notifica di evento / osservatore, trovo che possa trasformarsi in uno scenario da incubo in cui mi sento come se fossi rimbalzato in giro come se fossi in un flipper su tutto il codice base cercando di capire cosa è andato storto.

Naturalmente uso ancora queste soluzioni qua e là, ma cerco di non appoggiarle troppo. In particolare, cerco di evitare uno scenario ricorsivo in cui un evento emette uno o più eventi aggiuntivi, o in cui un osservatore viene avvisato solo di fare qualcosa che fa sì che un altro soggetto (o peggio, lo stesso) invii più osservatori.

"Diffs"

Quindi l'alternativa principale che preferisco al giorno d'oggi è, invece di una di queste soluzioni, utilizzare una sorta di struttura dati che ti permette di trovare come "changeset" o "delta / diff" di sorta con il senno di poi.

La struttura dei dati è abbastanza leggera da copiare e tiene traccia delle informazioni necessarie in modo da poter confrontare una nuova versione di se stessa con una vecchia versione come new_version.diff(old_version) e ti dirà tutto quello che devi sapere su cosa è successo tra il momento in cui hai archiviato la vecchia versione di esso e il momento in cui hai ricevuto quello nuovo, come quali elementi sono stati inseriti / rimossi dalla / dalla scena. Questo è un po 'vago su come si progetta questa struttura dati, ma può variare in modo selvaggio in base alle proprie esigenze, ma l'idea generale è così e la si può affrontare in molti modi.

E in effetti ho iniziato a utilizzare questa soluzione come requisito pratico. Nel mio caso non ho più un'istanza centrale di una scena. Ho tutti i tipi di thread / sistemi in esecuzione in parallelo che memorizzano la propria copia locale di una scena e generano nuove versioni modificate che poi ottengono l'output su altri sistemi (es: renderer) che quindi memorizzano la propria copia ed eseguono in parallelo e così via. Quindi nel mio caso non vi è più alcuna istanza di scena centrale che renda impraticabile qualsiasi tipo di osservatore centrale / evento in coda se non addirittura impossibile, e questo tipo di soluzione "diff" sembrava il modo più pratico per risolvere il problema. E nel mio caso quel tipo di funzionalità "diffs" è parte della scena stessa ( new_scene.diff(old_scene) ) che poi mi dice informazioni rilevanti come quali entità sono state inserite / rimosse dalla copia precedente della scena che avevo, e la scena stessa una struttura dati persistente immutabile.

Eppure, dopo averlo fatto, l'ho trovato molto più facile poiché non c'è più bisogno di avere tutta questa comunicazione tra oggetti / sistemi / thread per dirsi l'un l'altro, direttamente o indirettamente, cosa è successo allo stato precedente. I tuoi oggetti / sistemi / thread possono semplicemente guardare il nuovo stato (scena del gioco) e scoprire, a loro piacimento, nel loro thread di elaborazione, cosa è successo e apportare gli aggiornamenti necessari a qualsiasi struttura in risposta (es: indice spaziale).

In genere, ciò che sto raccomandando non è un elenco di eventi verificatisi, anche se potrebbe sembrare il modo più semplice per tenere traccia di ciò che è accaduto tra la nuova versione e il vecchio. Non è molto pratico confrontare un intero elenco crescente di cose che si sono succedute l'un l'altra per capire i cambiamenti in questo modo, e anche se si attenua l'infinito desiderio di crescita memorizzando alcuni dati aggiuntivi come un "punto cache", che sta iniziando a prendere ancora un po 'ingombrante. Quello che faccio nel mio caso è iniziare con una struttura dati a bitset che tiene traccia di quali entità occupano quali indici. Confrontando il vecchio bitset con il nuovo usando le operazioni di set rapido, vi dirò immediatamente quali entità non occupano più gli ex indici e quali entità ora occupano nuove non occupate in precedenza. Ma ovviamente questo non ti dice se qualche entità è stata rimossa e ne è stata inserita una nuova per occupare il suo precedente indice, per esempio, quindi uso anche alcuni contatori di cambiamento e cose del genere in cima alle operazioni di set per eseguire rapidamente il drill-down e scopri quali elementi sono stati rimossi / inseriti / modificati. Questo è un esempio di base e quelli della mia vita reale sono molto più sfumati, ma dovrebbero darti un inizio per quanto riguarda il modo in cui potresti implementarlo.

    
risposta data 13.12.2018 - 15:05
fonte

Leggi altre domande sui tag