2011-02-26 12 views
5

In questo momento, sto scrivendo un motore fisico per un gioco che sto sviluppando. Spesso, ci sono molti valori ripetuti quando si combina un motore fisico con un motore di gioco. Come le variabili che indicano la posizione e la rotazione di un oggetto. Con la maggior parte dei motori fisici, è necessario scorrere tutti gli oggetti e aggiornare le loro posizioni in base alle posizioni dell'oggetto del motore fisico. Quindi ho pensato che sarebbe auspicabile che la posizione e i valori di rotazione negli oggetti dei motori fisici fossero riferimenti alle variabili dell'oggetto di gestione dei motori che gestiscono la rotazione e la posizione. Tuttavia, a volte si desidera oggetti nel motore fisico che non correlano direttamente con gli oggetti nel motore di gioco. (Pareti invisibili, giunti). Quindi dovresti trattare gli oggetti come variabili membro regolari ... Quindi ecco quello che ho.Inizializza una variabile di riferimento dei membri della classe, come se fosse una variabile regolare

struct object{ 
    float & xPosition; 
    float & yPosition; 
    float & zPosition; 
    ... 
    object(float & xPos, float& yPos, float& zPos):xPosition(xPos), yPosition(yPos), zPosition(zPos){} 
    object():xPosition(*new float(0.0f)), yPosition(*new float(0.0f)), zPosition(*new float(0.0f)){} 
}; 

Tuttavia, questo porterà a perdite di memoria, in quanto tali oggetti non vengono eliminati. Hai qualche suggerimento su come posso ottenere il comportamento desiderato senza la perdita di memoria?

EDIT

Preferirei non usare boost. Tuttavia non sono contrario a una soluzione che richiede un modello. Inoltre, essendo questo un ottimizzazione delle prestazioni, boost :: shared_ptr, non sembra essere la soluzione giusta.

risposta

1

Fai quello che stai facendo ora, mantieni una variabile bool extra per indicare se la tua memoria è stata allocata o meno. Quindi, nel distruttore, puoi chiamare delete dopo aver controllato quel valore, ad esempio

struct object{ 
    float & xPosition; 
    float & yPosition; 
    float & zPosition; 

    object(float & xPos, float& yPos, float& zPos) 
    :xPosition(xPos), 
    yPosition(yPos), 
    zPosition(zPos), 
    allocated(false) 
    {} 
    object() 
    :xPosition(*new float(0.0f)), 
    yPosition(*new float(0.0f)), 
    zPosition(*new float(0.0f)), 
    allocated(true) 
    {} 

    ~object() { 
    if(allocated) { 
     delete &xPosition; 
     delete &yPosition; 
     delete &zPosition; 
    } 
    } 

private: 
    bool allocated; 
}; 
+0

Questo è quello che ho finito per fare, grazie. –

5

Suggerirei di utilizzare un boost::shared_ptr in queste strutture di posizione. In questo modo, non devi preoccuparti dell'eliminazione e puoi utilizzarlo come puntatore condiviso con l'oggetto del motore di gioco o come puntatore indipendente.

Poiché è presente un sovraccarico, è possibile limitare il rapporto tra dati e puntatore. In altre parole, non mantenere uno shared_ptr per ogni coordinata, ma un shared_ptr per il vettore di posizione e un shared_ptr per il rappresentante di rotazione o un shared_ptr su una trasformazione o un frame omogeneo (sistema di coordinate, frame cinetico o cornice cinetostatica).

Ad esempio, si potrebbe avere questo:

class object { 
    public: 
    typedef boost::shared_ptr<Vector3D> pVector3D; 
    private: 
    pVector3D position; 
    public: 
    object(pVector3D aPos = pVector3D(new Vector3D(0.0,0.0,0.0))) : position(aPos) { }; 
}; 

La proprietà automatica e riferimento conteggio del shared_ptr farà in modo che non sarà necessario preoccuparsi di mettere una dichiarazione di cancellazione (automatico) e non non c'è pericolo che l'oggetto scompaia dal motore di gioco mentre il motore fisico ha ancora bisogno di quelle variabili (il conteggio dei riferimenti garantisce che verranno eliminati solo quando tutti gli oggetti che ne hanno bisogno vengono anch'essi cancellati).

EDIT

Preferirei non usare boost. Tuttavia non sono contrario a una soluzione che richiede un modello. Inoltre, con questo in parte una ottimizzazione delle prestazioni , boost :: shared_ptr, non sembra che la soluzione giusta sia .

Beh, shared_ptr/shared_array si possono trovare anche nella relazione tecnica libreria standard 1 (TR1) (quindi è std::tr1::shared_ptr, invece, in modo che non c'è bisogno di utilizzare Boost usare quelli). Per quanto riguarda l'ottimizzazione delle prestazioni, è per questo che raccomando un rapporto abbastanza elevato di data-to-pointer.Il sovraccarico di shared_ptr è principalmente un overhead di memoria e qualche riferimento indiretto durante la cancellazione e la copia (che sono due operazioni che non vengono eseguite così spesso), non penso che ci sia molto overhead nell'accesso ai dati a cui punta rispetto a un normale puntatore o riferimento. Devi accettare che, anche utilizzando i riferimenti, stai scambiando i dati con un sovraccarico di dati con l'overhead di accesso diretto ai dati (stai sacrificando anche la località di memoria, che è un grosso problema!). Direi che il calo delle prestazioni legato alla localizzazione della memoria sarà molto peggiore rispetto alla sola indiretta. Quindi, quando si tratta di accedere a elementi, IMO, shared_ptr, puntatori e riferimenti grezzi avranno pochissime differenze di prestazioni. In molti algoritmi che utilizzano queste variabili condivise, probabilmente sarebbe meglio copiare i dati puntati dal puntatore/riferimento alle variabili locali, calcolare con e su quelle variabili locali e quindi copiarli nuovamente nella memoria puntata dal puntatore /riferimento.

Si consiglia di eseguire alcuni test personalizzati sulle prestazioni quando si utilizzano entrambe le soluzioni (utilizzando shared_ptr, utilizzando riferimenti o puntatori grezzi e copia dei dati tra il motore di gioco e il motore fisico) e vedere di persona, si potrebbe sii sorpreso di quello che trovi.

EDIT2

Hai pensato di usare schema di ereditarietà multipla. Questo problema può probabilmente essere molto ben servita con uno schema di diamante eredità:

class PositionedObject { 
    protected: 
    float Position[3]; 
    public: 
    PositionedObject(float x,float y, float z) { Position[0] = x; ... }; 
    virtual ~PositionedObject() { }; 
}; 

class VisibleObject : virtual public PositionedObject { //note that the "virtual" keyword is critical here. 
    ... rendering-related code ... i.e. the game-engine side of the implementation 
}; 

class RigidBody : virtual public PositionedObject { //again "virtual" is very important. 
    ... physics code here ... 
}; 

class MyObject : public VisibleObject, public RigidBody { 
    ... code specific to MyObject ... 
}; 

Questo sopra schemi fanno l'oggetto della fisica e la quota oggetto di gioco-motore gli stessi dati di posizione (con poca indirezione, poca memoria-in alto e poco problemi di memoria-località). Sono abbastanza sicuro che questo sarebbe più efficiente di qualsiasi altro schema, ma gli argomenti sulle prestazioni possono essere risolti solo con i risultati dei test che dovrai fare da soli se le prestazioni sono davvero la tua preoccupazione principale (assicurati di non farlo ottimizzazione prematura!).

3

Si potrebbe utilizzare Boost shared_array per condividere il XYZ coordinate tra un numero arbitrario di oggetti:

struct object { 
    boost::shared_array<float> coords; 

    object(const boost::shared_array<float>& coords_) 
     : coords(coords_) 
    { 
    } 

    object() 
     : coords(new float[3]) 
    { 
     coords[0] = coords[1] = coords[2] = 0.f; 
    } 
} 

Il shared_array e shared_ptr modelli impiegano reference counting per assicurare che la memoria viene cancellata dopo l'ultimo riferimento ad esso viene distrutto . Copia-la costruzione di un shared_array o shared_ptr aggiunge uno al conteggio dei riferimenti e distruggendo un shared_array o shared_ptr sottrae uno dal conteggio dei riferimenti. Quando il conteggio dei riferimenti raggiunge 0, la memoria condivisa viene cancellata.

0

Un sistema di contabilità personalizzato per i puntatori sarebbe appropriato. È possibile passare le coordinate al sistema fisico come puntatori e specificare con un valore booleano se il motore fisico deve aggiungerli a una struttura di contabilità: un elenco di indicatori sarebbe la scelta più semplice. Tutta la memoria che è riservata dinamicamente per il motore fisico potrebbe quindi essere liberata ripetendo l'elenco. In alternativa è possibile gestire la memoria in ogni oggetto, ma questo ha l'inconveniente di aggiungere dati per lo più inutili all'area di memoria che si sta utilizzando per eseguire i calcoli effettivi. Ovviamente, l'aggiunta di dati all'oggetto potrebbe essere l'unica soluzione sensata se è necessario gestire la memoria per oggetto anziché per livello. Le operazioni di liberazione/prenotazione sono più complesse in questo approccio, ma si salva qualche elaborazione mentre il motore è in esecuzione. Ecco una soluzione con un solo indicatore galleggiante in oggetto, che indicherebbe una matrice a tre float:

class physics_system_t { 
public: 
    std::vector<float *> my_arrays; 
} physics_system; 

class object { 
public: 
    object(float * coords_ptr, bool manage_memory) : coords_array(coords_ptr) 
    { 
     if (manage_memory) { 
      physics_system.my_arrays.push_back(coords_ptr); 
     } 
    } 
    float *coords_array; 
}; 

EDIT: Ecco un altro alternativo che non è ancora stato proposto. Usa un'unione per memorizzare i tuoi dati come variabili locali o riferimenti e avere un valore booleano per alternare i due.

class object { 
public: 
    object(float & x, float& y, float& z, bool use_refs); 
    union { 
     struct refs_t { 
      float &xr; 
      float &yr; 
      float &zr; 
     } refs; 
     struct vals_t { 
      float x; 
      float y; 
      float z; 
     } vals; 
    } 
    bool use_refs; 
}; 

Questo avrebbe preservato località dei dati quando la memoria è gestita da oggetto, non ha bisogno di spazio in più, e non c'è bisogno di fare alcuna cancellazione manuale. Naturalmente, le prestazioni potrebbero essere ancora un problema, poiché è necessario scegliere se utilizzare i riferimenti o le variabili ogni volta che si accede a esse. Dovresti anche fare attenzione, perché non vuoi usare accidentalmente i riferimenti quando intendi usare le variabili. I guadagni potrebbero non valere questo problema e l'aumento della dimensione del codice.

1

Un modo molto semplice ...

struct object 
{ 
    float& xPosition; 
    float& yPosition; 
    float& zPosition; 
    float x, y, z; 
    ... 

    object(float& xPos, float& yPos, float& zPos) 
     : xPosition(xPos), yPosition(yPos), zPosition(zPos) 
    { } 

    object() : xPosition(&x_), yPosition(&y_), zPosition(&z_), x(0), y(0), z(0) 
    { } 
}; 

... si finisce per dimezzare entrambi i riferimenti e le variabili quando è necessario un unico per ogni oggetto, ma il fastidio e le spese generali di gestione che altrove potrebbe facilmente costano di più comunque (sia in memoria che a gonfiare il codice dalle istanze dei template).

Se si desidera attenersi a qualcosa di più vicino a ciò che si è avuto, è sufficiente aggiungere un valore booleano per tenere traccia della necessità di eliminare la memoria. (EDIT: PigBen ha appena pubblicato il codice per tale). Personalmente, consiglierei di usare uno new float[3] in modo da eseguire un'allocazione di memoria anziché tre ... non solo sarà più veloce ma potrebbe anche sprecare meno memoria nella gestione dell'heap.

Problemi correlati