2011-02-26 16 views
5

Ahora mismo, estoy escribiendo un motor de física para un juego que estoy desarrollando. Muchas veces, hay muchos valores repetidos cuando se combina un motor de física con un motor de juego. Tales como variables que denotan la posición y rotación de un objeto. Con la mayoría de los motores de física, debe recorrer todos los objetos y actualizar sus posiciones de acuerdo con las posiciones de los objetos del motor de física. Así que pensé que sería deseable tener la posición y los valores de rotación en los objetos de los motores físicos para hacer referencia a las variables del objeto de los motores de juego que manejan la rotación y la posición. Sin embargo, a veces quieres objetos en el motor de física que no se correlacionen directamente con los objetos en el motor del juego. (Paredes invisibles, articulaciones). Entonces, necesitaría tratar los objetos como variables de miembros regulares ... Así que aquí está lo que tengo.Inicializa una variable de referencia de los miembros de la clase, como si fuera una variable regular

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)){} 
}; 

Sin embargo, esto dará lugar a pérdidas de memoria, ya que esos flotadores no se eliminan. ¿Tiene alguna sugerencia sobre cómo puedo lograr el comportamiento deseado sin la pérdida de memoria?

EDITAR

preferiría no utilizar impulso. Sin embargo, no me opongo a una solución que requiera una plantilla. Además, dado que esto es parcialmente una optimización del rendimiento, boost :: shared_ptr, no parece ser la solución correcta.

Respuesta

1

Haga lo que está haciendo ahora, simplemente mantenga una variable bool adicional para indicar si su memoria fue asignada o no. Luego, en el destructor, puede llamar elimina después de comprobar que el valor, es decir

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

Esto es lo que terminé haciendo, gracias. –

5

Le sugiero que utilice un boost::shared_ptr para estas estructuras de posición. De esta forma, no tiene que preocuparse por la eliminación y puede usarlo como un puntero que se comparte con el objeto del motor del juego o como un puntero independiente.

Como hay sobrecarga para estos, es posible que desee limitar la relación de datos a puntero. En otras palabras, no guarde shared_ptr para cada coordenada, sino shared_ptr en el vector de posición y shared_ptr en la representación de rotación, o shared_ptr en una transformación o marco homogéneo (sistema de coordenadas, fotograma cinético o fotograma cinético)

Por ejemplo, usted podría tener esto:

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 propiedad cuenta automática y referencia de la shared_ptr lo hará de manera que no tendrá que preocuparse de poner una instrucción de eliminación (automático) y hay no hay peligro de que el objeto desaparezca del motor del juego, mientras que el motor de física aún necesita esas variables (el conteo de referencias garantiza que se eliminarán solo cuando se eliminen también todos los objetos que lo necesitan).

EDITAR

preferiría no utilizar impulso. Sin embargo, no me opongo a una solución que requiere una plantilla. Además, con este siendo parcialmente una optimización de rendimiento , boost :: shared_ptr, does no parece ser la solución correcta.

Bueno, shared_ptr/shared_array también se puede encontrar en el informe técnico de la biblioteca estándar 1 (TR1) (por lo que es std::tr1::shared_ptr lugar, por lo que no es necesario usar Boost para usar esos). En cuanto a la optimización del rendimiento, es por eso que recomiendo una relación bastante alta de datos a puntero.La sobrecarga de shared_ptr es principalmente una sobrecarga de memoria y algo indirecto durante la eliminación y copia (que son dos operaciones que no se realizan con tanta frecuencia), no creo que haya demasiados gastos indirectos en el acceso a los datos a los que apunta en comparación con un puntero o una referencia. Tienes que aceptar que, incluso al usar referencias, estás intercambiando gastos indirectos de copia de datos con sobrecarga indirecta de acceso a datos (también estás sacrificando la localidad de memoria, ¡lo cual es un gran problema!). Diría que la caída en el rendimiento relacionada con la ruptura de la localidad de memoria va a ser mucho peor que solo la indirección. Por lo tanto, cuando se trata de acceder a elementos, IMO, shared_ptr, punteros sin formato y referencias tendrán muy poca diferencia en el rendimiento. En muchos algoritmos que usan estas variables compartidas, probablemente sería mejor copiar los datos apuntados por el puntero/referencia a variables locales, calcular con y sobre esas variables locales, y luego copiarlas de nuevo a la memoria apuntada por el puntero /referencia.

Te recomiendo que hagas algunas pruebas tuyas sobre el rendimiento cuando usas cualquiera de las soluciones (usando shared_ptr, usando referencias o punteros crudos, y copiando los datos entre el motor del juego y el motor de física) y verás por ti mismo, podrías sorpréndete con lo que encuentres

Edit2

Ha considerado el uso esquema de herencia múltiple. Este problema puede ser probablemente muy bien servido con un esquema de herencia de diamantes:

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 ... 
}; 

esto por encima de los esquemas hacen que el objeto de la física y la cuota objeto de juego-motor de los mismos datos de posición (con poco indirecta, poca memoria, los gastos generales y poco problemas de localización de memoria). Estoy bastante seguro de que esto sería más eficiente que cualquier otro esquema, pero los argumentos sobre el rendimiento solo pueden responderse con los resultados de las pruebas que tendrá que hacer usted mismo si el rendimiento es realmente su principal preocupación (asegúrese de que no está haciendo optimización prematura!).

3

Se podría usar Boost shared_array compartir las coordenadas XYZ entre un número arbitrario de objetos:

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; 
    } 
} 

El shared_array y shared_ptr plantillas emplean reference counting para asegurarse de que la memoria se borra después de la última referencia a que se destruye . Copiar-construir un shared_array o shared_ptr agrega uno al recuento de referencia y destruir un shared_array o shared_ptr resta uno del recuento de referencia. Cuando el recuento de referencia llega a 0, la memoria compartida se elimina.

0

Un sistema de contabilidad personalizado para los punteros sería apropiado. Puede pasar coordenadas al sistema de física como punteros y especificar con un valor booleano si el motor de física debe agregarlos a una estructura de contabilidad; una lista de punteros sería la opción más simple. Toda la memoria que se reserva dinámicamente para el motor de física se puede liberar iterando sobre la lista. Alternativamente, podría manejar la memoria en cada objeto, pero eso tiene el inconveniente de agregar datos innecesarios al área de memoria que está utilizando para hacer los cálculos reales. Por supuesto, agregar datos a un objeto puede ser la única solución sensata si necesita administrar memoria por objeto en lugar de por nivel. Las operaciones de liberación/reserva son más complejas en este enfoque, pero se ahorra algo de procesamiento mientras el motor está en funcionamiento. Aquí hay una solución con un único puntero del flotador en objeto, lo que apuntaría a una matriz de tres 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: He aquí una alternativa más que aún no se ha propuesto. Use una unión para almacenar sus datos como variables locales o referencias, y tenga un booleano para alternar entre los dos.

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; 
}; 

Esto preservaría la localidad de datos cuando la memoria es manejada por objeto, no se necesita espacio adicional, y usted no necesita hacer ningún borrado manual. Por supuesto, el rendimiento aún puede ser un problema, ya que debe elegir si usar las referencias o las variables cada vez que accede a ellas. También debe tener cuidado con eso, ya que no quiere usar accidentalmente las referencias cuando quiera usar las variables. Las ganancias pueden no valer este problema y el tamaño de código aumentado.

1

Una forma muy simple ...

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) 
    { } 
}; 

... terminas reducir a la mitad ambas referencias y variables, cuando sólo una es necesaria para cualquier objeto dado, pero la molestia y los gastos generales de gestión que podría fácilmente en otros lugares cuestan más de todos modos (ya sea en la memoria o en la saturación del código a partir de las instancias de las plantillas).

Si desea atenerse a algo más cercano a lo que tenía usted mismo, simplemente puede agregar un booleano para rastrear si la memoria debe ser eliminada. (EDITAR: PigBen acaba de publicar el código para tal). Personalmente, recomendaría usar uno new float[3], por lo que está haciendo una asignación de memoria en lugar de tres ... no solo será más rápido sino que también desperdiciará menos memoria en la administración de heap.

Cuestiones relacionadas