Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Teoria De Motores

Iniciado por DraKKaR, 25 de Agosto de 2005, 03:39:07 PM

« anterior - próximo »

DraKKaR

 Hola, últimamente estoy replanteandome el diseño de gran parte del motor para conseguir algo más sólido. Me refiero al sistema de manejo de objetos/recursos del motor.

OPCION 1:
Ahora mismo, le pides al motor que cree una nueva instancia de un nodo de escena o de una malla. Entonces el motor te devuelve un shared_ptr (autopuntero compartido) del objeto. Entonces ese puntero se usa para añadirlo al grafo de escena, o en caso de una textura pse añade a las propiedades del material de un objeto. El "problema" de esto es que el objeto no "vive" de una forma clara en ningún lugar de la aplicación: mientras alguno de estos punteros compartidos apunte a un objeto, éste permanecerá vivo, y esos punteros están dispersados por todo el motor. Como además, estos punteros son visibles desde la aplicación que usa el motor, la responsabilidad de eliminar estos objetos es a medias del usuario/motor.

OPCION 2:
Una alternativa que habia pensado es, que cuando se cree un objeto/recurso, este se almacene en un lugar opaco del motor. Cuando se crea el objeto, el usuario recibe un puntero (normal) al objeto. El usuario sabe que no debe borrarlo nunca, sino que se eliminará cuando el motor lo crea oportuno. Aquí el puntero es un identificador del objeto y la resopnsabilidad de crear/eliminar el objeto es 100% responsabilidad del motor.
Aunque un problema que le veo a esto son inconsistencias del tipo: el motor ha eliminado un objeto, pero el usuario no lo sabe y está intentando usar el puntero identificador.

OPCION 3:
Esta opción requiriría un cambio más grande del motor. Se basaría en que el motro gestionara todos los objetos (creación y destrucción) de forma interna. El usuario no guarda ningún puntero al objeto, simlpemente un identificador numérico. De esta forma, cuando el usuario quiere acceder a un objeto para modificar sus propiedades o efectuar alguna operación, le pide al motor que le devuelve un puntero (lock) temporal al objeto/recurso, el usuario la modifica y lo libera (unlock). Esto es algo similar al mecanismo de Direct3D para modificar surfaces. De esta forma el usuario no va a intentar modificar un objeto/recurso eliminado por el motor, ya que este mecanismo controlaria si el objeto existe o no.

OPCION 4:
Similar a la opción 3, pero más similar al funcionamiento de OpenGL o ODE. El usuario guarda identificadores numéricos de los objetos y usa funciones del motor para modificar atributos de los recursos usando los identificadores. Esto sería como ODE, que aunque el motor está construido en C++, la interfaz es C puro. Es un cambio muy brusco a la interfaz del motor y a la forma de suarlo, pero le otorga mucha solidez.

Estas son las opciones que estoy barajando y a todas les veo cosas buenas o cosas malas. Me gustaría que discutiéramos estos aspectos y compartir las formas en que os habeis enfrentado a esto en vuestros trabajos.

Pensamient: Ciertamente, creo que la parte más complicada e importante de hacer un motor, es hacer su diseño de la mejor forma posible, ya que es la que lo limita o potencia.

ethernet

 Yo no he programado ningún motor, pero creo que hay que buscar un equilibro entre responsabilidad de usuario y facilidad de uso del motor. La idea de tener referencias en mi opinión no es mala, yo estoy programando python ahora, que tiene ref count y esas cosas, y no solo no me da problemas, si no que me es de gran ayuda.  Es cierto que tienes el problema de que no "vive" de una forma clara, pero el usuario no tendrá que preocuparse demasiado, porque si sus objetos contienen elementos de tu engine, terminarán por eliminarse cuando se destruyan. Es posible que no lo haga, pero eso es ya cosa del programador que use tu engine, el engine no puede hacer milagros si el coder se empeña en meter leaks, etc. :).

_Grey

 Estaba estos dias con un problema similar, pero para una coleccion de classes para 2D, nada de engines mastodonticos.
Y tenia la misma duda, la cuestion es que tengo dudas sobre hacer los objetos inteligentes de ese mismo modo, que cuenten las referencias que tienen y se destruyan al llegar a 0. La cuestion es que eso de tener varias clases quer no se puedan instanciar de forma normal, si no que tengan los constructores y destructores privados, y se creen y destruyan(o desreferiencen) con llamadas a funciones para eso, como los COM, no se si seria del todo buena idea, no podrias hacer algo tan simple como un array con un new, no lo hace mas dificil de usar??

Me gustaria tambien a mi leer mas opiniones al respecto, quiza los del Haddd3 tengan algo que decir.

Saludos.

DraKKaR

 Gracias por responder.

Ethernet: es cierto que se facilita mucho la tarea del programador, pero puede pasar que los objetos no se destruyan en el orden que sea necesario. Por ejemplo, se sale de la aplicación y hay que liberar toda la memoria del motor. Primero se eliminan las texturas y luego el módulo de render. Sin embargo, una de las texturas no se pudo eliminar porque el usuario aun mantenia una referencia a ese objeto. Por lo que la textura no se ha liberado. Cuando la textura va a liberarse finalmente, resulta que el módulo de render ya ha sido liberado y no se pueden hacer las llamadas al API 3D necesarias para liebrar ese recurso de la memoria.

_Grey: si usas la plantilla shared_ptr<>  de la biblioteca boost (que sabiamente me recomendaron aquí) no
tendrás ningún problema de uso. Las referencias aumentan automáticamente cuando se ejecuta un cosntructor copia o de asignación y se decrementa cuando se ejecuta el destructor. Es muy sencillo de utilizar.

EDITO: Creo que Grey no me preguntaba nada  de esto en su post  :P  

Haddd

 Bueno, lo cierto es que Managed Code se ocupa el solito de estos problemas. Eso evidentemente es bueno y malo, pero es lo que tenemos, no podemos hacer mucho...

Sobre tu reflexión, pues te diré que particularmente prefiero que el engine me lo de todo hecho. Que yo tenga que hacer un AddRef y un Release es un poco...¿pesado?. C# lo resuelve con el using:


using(texture=DX.Resource)
{
....
}


En el using en realidad se hace un Add y un Release, pero de forma transparente para el usuario.

Sobre la opción 1 y la 2, creo entender que en la 1 recibes un "puntero universal a cualquier cosa", y en la 2, recibirías un "puntero a una textura;puntero a un mesh...". ¿Es así, no acabo de entenderlo bien?


LC0

 Yo creo que con el tema este de los punteros automáticos hay que tener muchísimo cuidado, ya que, aunque la idea es buena y bien planteada puede simplificar mucho la programación y evitar posibles fugas de memoria, un mal diseño puede conllevar el desastre: La sencillez de uso se puede convertir en una ofuscación total, tal y como pasa con el Crystal Space, motor que, aun siendo potentísimo, peca de dificultad de manejo.
Haciendo buen uso de los destructores en C++, todo este rollo de referencias compartidas se puede ahorrar. Claro que este buen uso solo se hace un 10-20% de las veces que se programa algo bajo este lenguaje :D
Es mi humilde opinión.

AgeR

 Bueno, a ver si aporto algo, aunque seguramente suene raro a estas alturas...

Lo primero que deberías preguntarte son las características del motor y hacia qué vas a enfocarlo:
- Velocidad de render
- Calidad de render
- Sencillez de uso
- Complejidad del código
- Facilidad de actualización
- Tradicional vs Shaders


Parece una tontería, pero hacer un motor a la última, bien estructurado, fácilmente actualizable, rápido, fácil de usar... puedes volverte loco. Lo primero debería ser buscar un objetivo para el motor. Qué quieres hacer con él?

A ver si me explico, basandome en parte sobre mi motor... Yo lo planteé como un motor tradicional (nada de shaders, aunque sería relativamente fácil implementarlos). La velocidad de render no me preocupa todavía demasiado, ya que la mayoría de juegos y pruebas que he hecho han dado buenos resultados, y para los juegos que tengo previsto hacer no requiero de grandes cantidades de polígonos. La calidad de render es mejorable, aunque se debe a que suelo dejarme efectos por "enchufar", como niebla, buena iluminación y teselación... nada que sea culpa del motor propiamente. En cuanto a complejidad de código, hay de todo un poco, pero en general es muy sencillo, y muchas partes de mi motor se basan casi directamente en tutoriales o artículos que he leído para documentarme y me han parecido bien.

Lo más importante que me parece es la FACILIDAD DE USO. Mucho más que la velocidad incluso.
Por ejemplo, mi motor usa un manager de texturas típico, con una lista de texturas, con contador de referencias. Al final, cuando se han destruido todos los objetos y escenario, al finalizar el motor, reviso de nuevo las texturas que quedan en la lista y elimino las que quedan, por si el usuario (vamos, yo mismo  :lol: ) ha hecho alguna guarrada.
Con las mallas, de momento las cargo a lo bruto, no tengo ninguna lista ni nada, pero cuando vea que me hace falta, haré lo mismo.

De hecho no tengo ningún grafo de escena ni nada parecido, símplemente frustum culling, aunque repito que lo que he hecho tampoco necesita más.

Vamos, que sobre lo de la teoría de motores, mi consejo es que se lo pongas fácil al usuario, ya que de este modo tendrás más usuarios potenciales para tu motor (si es eso lo que pretendes). Si es para uso personal, sí que puedes meterte en cosas enrevesadas.

De todas formas, las cosas, cuanto más sencillas, mejor  :ph34r:  

DraKKaR

 
CitarSobre tu reflexión, pues te diré que particularmente prefiero que el engine me lo de todo hecho. Que yo tenga que hacer un AddRef y un Release es un poco...¿pesado?.

Lo bueno de los shared_ptr de la biblioteca boost es que no tienes que hacer nada de eso. Tú simlpemente los usas como putneros normales y la template se encarga de todo.

CitarSobre la opción 1 y la 2, creo entender que en la 1 recibes un "puntero universal a cualquier cosa", y en la 2, recibirías un "puntero a una textura;puntero a un mesh...". ¿Es así, no acabo de entenderlo bien?

No, debo haberme explicado mal. Para que se entienda mejor: la opción 1 te devolvería un objeto de tipo shared_ptr<Mesh> , mientras que la opción 2 te devovlería Mesh *. La diferencia es que en la 2, el motor decide cuando liberar la memoria, mientras que en la 1, al ser un puntero compartido, mientras el usuario lo retenga "vivo" el motor no podrá liebrarlo.



LCO, creo que el usar bien los destructores no le quita la utilidad a los auto-punteros compartidos.

¿Más opiniones?

DraKKaR

 AgeR, estoy deacuerdo contigo, pero además lo que busco es solidez en el diseño del motor y que sea de facil uso. La velocidad de render no me preocupa tanto como estos aspectos. De hecho, todas estas opciones que estoy dando constituyen la facilidad y solidez que el motor aporta al usuario, el problema es como conseguirlo...

DraKKaR

 Al final he decidido tener 2 opciones: la 1 y la 4. La 1 ya la tengo, pues es como está implementado el motor ahora.

La opción 4 la conseguiré haciendo un wrapper de mi motor. Será una capa de software por encima del motor que lo oculte totalmente, y se encargue de manejar y facilitar muchísimo la vida del programador usuario. La idea es hacerlo todo de forma que el usuario no vea ningún puntero a ningún objeto del motor, que sea todo a base de funciones planas e identificadores opacos para interactuar con el motor.  Esto es algo similar a como es la interfaz ODE: una interfaz en C para un programa escrito en C++.
He empezado y me gusta como va quedando... la sintaxis y limpieza gana muchísimo, y las posibilidades de suar mal el motor disminuyen mucho.

La duda que tengo es: intentar hacerlo en puro C o no. La cuestion es que para el manejo de errores me gustaría uasr el sistema de excepciones try..catch, ya que le añade bastante limpeza al código y es muy potente. Sin embargo, si pretendo que el wrapper sea completamente en C, no puedo usar try...catch.

De esta forma quien quiera peude programar usando el motor a pelo, a más "bajo nivel", con mas responsabilidades pero con más flexibilidad. Y quien quiera puede suar el wrapper que facilitará mucho el uso del motor y aumentará la solidez.

Ya os comentaré como va todo.
¿Que opinais de esto?

AgeR

 Yo opino que es demasiado trabajo, que sólo valdrá la pena si logras de este modo conseguir una base de usuarios. De otro modo, creo que es liarse en demasiadas cosas. De hecho si lo haces así a partir de ahora tendrás que programar el motor, y luego revisar y actualizar el wrapper cada vez.

Vamos, para el usuario final, lo veo genial, pero para ti, lo veo un infierno inminente  :ph34r:  

DraKKaR

 Ya se que es algo de trabajo, pero como la mayoria de cosas de este motor, no lo hago para conseguir nada, simlpemente porque me apetece  ;)

EDIT: esta es una muestra de cosas que añado al motor porque me apetece:

Es una especie de raytracer a nivel "local".



En realdiad lo que envio a renderizar es un cubo, pero el pixel shader calcula el color para cada pixel del cubo para que parezca una esfera. No solo calcular el color final del pixel teniendo en cuenta la iluminación, sino que transforma la coordenada de profundidad por pixel para que la esfera virtual se integre perfectamente a nivel de pixel con la escena. Por eso digo que es un efecto de raytracing local, porque solo se utiliza para renderizar el objeto, no todo lo que le envuelve.
Es curioso ver como te acercas a la esfera todo lo que quieras y nunca se ve ningún triángulo, siempre es perfectamente esférico.

Haddd

 me parece una gran idea eso que dices de crear 2 formas de trabajar con el motor. Creo que es lo mejor, incluso podrías enfocar wrapper para tipos de juegos:2D, FPS, estrategia...

Lo del cubo y la esfera está muy bien, es un buen efecto. Así consigues exactamente lo que dices, que no haya triangulación. Lo malo es que pierdes toda la optimización del Zbuffer al modificar en el pixel shader la z, pero es muy ingenioso  (ole)  

DraKKaR

 ¿Porqué dices que pierdo la optimización del Z buffer? Si pintara otra esfera detrás de esta, se usarían los valores calculados de la primera esfera para descartar los píxeles de la esfera de atrás.

Sobre lo de Hacer un wrapper enfocado a un tipo de juego... eso en realidad romperia un poco el esquema del motor que es de ámbito general. Sin embargo sería buena idea hacer otro ( :lol: ) wrapper a parte de este (:lol:) que se enfocara en un tipo de juegos como tu dices.

Miki

 Drakkar,

Efectivamente, si lo que quieres es solidez solo tienes 2 opciones:
1) Dar todo el control al usuario (para k la pifie de vez en cuando, cosa lógica por otra parte).
2) Dar todo el control al engine.

La creación y liberación de recursos se debe hacer siempre de forma jerárkika. Es decir, se usa la metodología tradicional de AddRef o DropRef como hace DirectX, pero
debes tener en cuenta que habrán recursos k debarán ser descargados antes k otros (pues unos recursos contendrán otros sub-recursos). Por ejemplo, una malla
tiene asignados varios materiales, y a su vez estos tienen asignados una o varias texturas. Por otra parte, al tenerlo todo gestionado en el engine, puedes sacar siempre
la cuenta exacta de recursos consumidos (en megas, kilobytes o como kieras) en texturas, buffers de vertices, de índices, etc...
Si le dejas esta terea al usuario lo único k puede pasar es k éste se vuelva completamente loco, por eso lo mejor es la opción 2. El usuario no debería hacer jamás
un delete de un recurso/entidad creada y gestionada por el motor, de esta forma todo queda en casa  :P
Todo claro, extremadamente sencillo, y poco flexible en ese sentido.

Salud2






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.