Esempio di progettazione orientata ai dati


8

Non riesco a trovare una bella spiegazione del Design orientato ai dati per un gioco di zombi generico (è solo un esempio, un esempio abbastanza comune).

Potresti fare un esempio del Design orientato ai dati sulla creazione di una classe di zombi generica? È il seguente buono?

Classe dell'elenco di zombi:

class ZombieList {
    GLuint vbo; // generic zombie vertex model
    std::vector<color>;    // object default color
    std::vector<texture>;  // objects textures
    std::vector<vector3D>; // objects positions
public:
    unsigned int create(); // return object id
    void move(unsigned int objId, vector3D offset);
    void rotate(unsigned int objId, float angle);
    void setColor(unsigned int objId, color c);
    void setPosition(unsigned int objId, color c);
    void setTexture(unsigned int, unsigned int);
    ...
    void update(Player*); // move towards player, attack if near
}

Esempio:

Player p;

Zombielist zl;
unsigned int first = zl.create();
zl.setPosition(first, vector3D(50, 50));
zl.setTexture(first, texture("zombie1.png"));
...

while (running) { // main loop
    ...
    zl.update(&p);
    zl.draw(); // draw every zombie
}

O sarebbe la creazione di un contenitore mondiale generico che contiene ogni azione da bite(zombieId, playerId)a moveTo(playerId, vector)a createPlayer()a shoot(playerId, vector)a face(radians)/face(vector); e contiene:

std::vector<zombie>
std::vector<player>
...
std::vector<mapchunk>
...
std::vector<vbobufferid> player_run_animation;
...

essere un buon esempio?

Qual è il modo corretto di organizzare un gioco con DOD?

Risposte:


11

Non esiste un "gioco con DOD". Firmabile, quella parola d'ordine è un po 'confusa, perché ogni sistema è progettato orientato ai dati. Ogni programma funziona su un insieme di dati e apporta alcune trasformazioni ad esso. Impossibile farlo senza orientare il design verso i dati. Quindi non si escludono a vicenda con un design "normale", ma aggiunge vincoli nel layout della memoria e nel modo in cui si accede alla memoria rispettivamente.

L'idea alla base di DOD è di raggruppare e raggruppare i dati appartenenti a una funzionalità più vicini tra loro in un blocco di memoria continuo, al fine di avere meno errori di cache, eliminare funzioni virtuali e vtabili, parallelizzazione più semplice, nessun (o minimo) accesso casuale alla memoria e scrivere codice per processori altamente ottimizzati come le SPU di Cell nella PS3 con le sue risorse di memoria limitate, ottimizzando l'accesso alla memoria e i DMA da e verso la sua memoria principale.

Questo non significa semplicemente cambiare tutto da "Array-of-Structures" (AoS) a "Structure of Arrays" (SoA) come mostrato negli esempi qui. Può anche significare mescolare entrambi e interfogliare e comprimere i dati appartenenti a una funzionalità strettamente insieme, come ad esempio "posizione" e "velocità" per evitare di saltare in memoria per l'integrazione della posizione.

Tuttavia, i sistemi DOD puri sono molto difficili da implementare, poiché ogni accesso al puntatore è una violazione di quel concetto puro , poiché non si accede più a un blocco di memoria continuo, ma si fa un accesso casuale alla memoria dereferenziando un puntatore. Ciò è particolarmente importante per la scrittura di codice per la SPU quando si sposta ad esempio un sistema di particelle dalla CPU alla SPU, ma nel normale sviluppo del gioco quotidiano non è importante. È un modo per ottimizzare le funzionalità secondarie, non per scrivere giochi con esso (ancora, come spiega l'articolo di Noels).

Mike Acton di Insomniac Games ha un sacco di materiale di approfondimento relativo a questo argomento, puoi trovare alcune delle sue cose qui e gli articoli di Noel , entrambi altamente raccomandati.


Una cosa che vorrei aggiungere a questa risposta: DOD non è TUTTO sui sistemi SoA. Mentre le strutture SoA tendono a funzionare meglio per DOD, non sempre si adattano al concetto DOD attuale. Il concetto alla base di DOD è semplicemente l'idea che stai progettando il codice attorno ai dati, non viceversa, che è il solito metodo.
Gurgadurgen,

0

Ho cercato anche un buon esempio di questo, ma con risorse limitate in rete e nessuno che mi dicesse come farlo correttamente, l'ho fatto con la seguente implementazione. (potrebbe non essere il migliore, ma segue l'idea di base)

Object
   //Basic data
   Vector3* Position;
   Vector3* Rotation;
   Vector3* Scale;



Car : Object
    float* acceleration;
    Object* GetObjectData();
    //invoke the updateCars, to update all cars.
    void    UpdateCar() { UpdateCars(Postion, Rotation, Scale);

    //Update all your objects in a big loop.
    void    UpdateCars(vector3* Position, vector3* Rotation, Vector3* scale);

Quindi l'implementazione è più o meno così: hai una classe di oggetti base, che contiene tutti i dati comuni. Quando la classe della tua auto viene costruita, specifichi quale quantità di dati vuoi mettere in comune e quindi hai abbastanza memoria per tutti gli oggetti.

da lì puoi offcourse aggiungere identificatori o qualsiasi cosa sia mai sentimentale per la tua implementazione. ma l'ho provato su un gioco più semplice e ha funzionato abbastanza bene.

Non è poi così lontano dal tuo progetto, e francamente non conosco un modo più efficace per farlo.


Alcuni problemi DOD: 1. Perdere scala, di sicuro. I calcoli relativi alla posizione e alla rotazione non sono praticamente sempre influenzati dalla scala, quindi praticamente non verranno mai utilizzati e occuperanno solo spazio nella cache. 2. Perderei anche la rotazione e la sostituirei con la velocità. Un'auto è destinata a muoversi dritta, ma alla fine la sua velocità determinerà la sua direzione. L'autista preme il petalo di gas, ma la fisica muove la macchina. 3. Non ereditare dalle classi per i dati se non si prevede di utilizzarli in quasi tutti i calcoli, insieme. 4. Anche in OOP, le auto non si aggiornano a vicenda. Usa funzioni gratuite.
Gurgadurgen,

Questo è più un esempio, non una guida definitiva. devi ovviamente scegliere il migliore per la tua implementazione. (come si dice)
Tordin,

Il tuo esempio è un esempio di astrazione OOP standard e sfrutta poco o nulla le strategie DoD. DoD riguarda i dati, non il modello. Il fatto che tu abbia anche un oggetto "auto" è un omaggio morto che questo non è molto di un esempio di DoD. Un'auto è piuttosto specifica e DoD tende a fare la composizione e il polimorfismo degli oggetti basati sull'esistenza, piuttosto che sull'ereditarietà. Quindi, ad esempio, potresti avere un oggetto che contiene informazioni necessarie per una trasformazione specifica e creare una matrice di tali oggetti, anziché un oggetto con informazioni per più trasformazioni.
Gurgadurgen,
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.