El mapa con 20 millones de fichas hace que el juego se quede sin memoria, ¿cómo lo evito?

11

Al cargar mapas extra grandes, el método de carga elimina la excepción de memoria donde se crea una nueva instancia de mosaico de mapa. Me gustaría tener un mapa completo procesado al menos en la aplicación del servidor (y en el cliente si es posible). ¿Cómo debo resolver este problema?

UPD: la pregunta aquí es cómo hacer que el juego deje de fallar cuando todavía hay memoria libre para usar. En cuanto a dividir el mapa en trozos, es un buen enfoque, pero no es lo que quiero en mi caso.

UPD2: Al principio fui y asigné una textura a cada nueva instancia de clase de mosaico y eso es lo que tomó tanta memoria (también tiempo de carga). Ahora toma alrededor de cuatro veces menos espacio. Gracias a todos, ahora puedo ejecutar mapas enormes sin pensar en dividirlos en pedazos todavía.

UPD3: Después de reescribir el código para ver si las matrices de propiedades de mosaico funcionan más rápido o consumen menos memoria (que dichas propiedades en sus respectivos mosaicos como propiedades de objeto), descubrí que no solo me llevó mucho tiempo intentarlo, sino que no trajo ninguna mejora en el rendimiento e hizo que el juego fuera terriblemente difícil de depurar.

usuario1306322
fuente
8
Solo cargue las áreas alrededor de los jugadores.
MichaelHouse
44
20 millones de mosaicos a 4 bytes por mosaico son solo unos 80 MB, por lo que parece que sus mosaicos no son tan pequeños. No podemos darle mágicamente más memoria, así que tenemos que averiguar cómo hacer que sus datos sean más pequeños. Entonces, muéstranos qué hay en estas fichas.
Kylotan
¿Qué hace el método de carga? Código postal, ¿utiliza la tubería de contenido? ¿Carga texturas en la memoria o solo metadatos? ¿Qué metadatos? Los tipos de contenido XNA generalmente hacen referencia a material DirectX no administrado, por lo que los objetos administrados son muy pequeños, pero en el lado no administrado podría haber un montón de cosas que no verá a menos que también esté ejecutando un generador de perfiles DirectX. stackoverflow.com/questions/3050188/…
Oskar Duveborn
2
Si el sistema arroja una excepción de falta de memoria, entonces no hay suficiente memoria libre para usar, a pesar de lo que pueda pensar. No habrá una línea secreta de código que podamos darle para habilitar memoria adicional. El contenido de cada mosaico y la forma en que los está asignando es importante.
Kylotan
3
¿Qué te hace pensar que tienes memoria libre? ¿Está ejecutando un proceso de 32 bits dentro de un sistema operativo de 64 bits?
Dalin Seivewright

Respuestas:

58

la aplicación se bloquea cuando alcanza 1.5Gb.

Esto sugiere fuertemente que no está representando sus mosaicos correctamente, ya que esto significaría que cada mosaico tiene un tamaño de ~ 80 bytes.

Lo que debes entender es que debe haber una separación entre el concepto de juego de un mosaico y el mosaico visual que ve el usuario. Estos dos conceptos no son lo mismo .

Toma Terraria por ejemplo. El mundo más pequeño de Terraria ocupa 4200x1200 fichas, que son 5 millones de fichas. Ahora, ¿cuánto recuerdo se necesita para representar ese mundo?

Bueno, cada mosaico tiene una capa de primer plano, una capa de fondo (las paredes de fondo), una "capa de alambre" donde van los cables y una "capa de muebles" donde van los muebles. ¿Cuánta memoria ocupa cada mosaico? Nuevamente, solo estamos hablando conceptualmente, no visualmente.

Un mosaico en primer plano podría almacenarse fácilmente en un corto sin firmar. No hay más de 65536 tipos de mosaicos en primer plano, por lo que no tiene sentido usar más memoria que esta. Los mosaicos de fondo podrían estar fácilmente en un byte sin signo, ya que hay menos de 256 tipos diferentes de mosaicos de fondo. La capa de cable es puramente binaria: o un mosaico tiene un cable o no. Eso es un bit por ficha. Y la capa de muebles podría volver a ser un byte sin firmar, dependiendo de la cantidad de muebles posibles que haya.

Tamaño de memoria total por mosaico: 2 bytes + 1 byte + 1 bit + 1 byte: 4 bytes + 1 bit. Por lo tanto, el tamaño total para un pequeño mapa de Terraria es 20790000 bytes, o ~ 20 MB. (nota: estos cálculos se basan en Terraria 1.1. El juego se ha expandido mucho desde entonces, pero incluso los Terraria modernos podrían caber dentro de 8 bytes por ubicación de mosaico, o ~ 40 MB. Todavía es bastante tolerable).

Usted debe no tener esta representación almacenada como matrices de clases de C #. Deben ser matrices de enteros o algo similar. AC # struct también funcionaría.

Ahora, cuando llega el momento de dibujar parte de un mapa (tenga en cuenta el énfasis), Terraria necesita convertir estos mosaicos conceptuales en mosaicos reales . Cada mosaico debe elegir una imagen de primer plano, una imagen de fondo, una imagen de muebles opcional y una imagen de alambre. Aquí es donde entra XNA con sus diversas hojas de sprites y demás.

Lo que debe hacer es convertir la parte visible de su mapa conceptual en mosaicos de hojas de sprites XNA reales. No deberías intentar convertir todo de una vez . Cada mosaico que almacene debería ser un índice que diga "Soy un mosaico tipo X", donde X es un número entero. Utiliza ese índice entero para buscar qué sprite usa para mostrarlo. Y usa las hojas de sprites de XNA para hacer esto más rápido que solo dibujar quads individuales.

Ahora, la región visible de los mosaicos debe dividirse en varios trozos, de modo que no esté constantemente construyendo láminas de sprites cuando la cámara se mueva. Por lo tanto, es posible que tenga trozos de 64x64 del mundo como hojas de sprites. Cualesquiera trozos de 64x64 del mundo visibles desde la posición actual de la cámara del jugador son los trozos que dibujas. Cualquier otro trozo ni siquiera tiene hojas de sprites; si un trozo se cae de la pantalla, tira esa hoja (nota: en realidad no la borras; la guardas y la respetas para un nuevo trozo que pueda ser visible más adelante).

Me gustaría tener un mapa completo procesado al menos en la aplicación del servidor (y en el cliente si es posible).

Su servidor no necesita saber ni preocuparse por la representación visual de los mosaicos. Todo lo que necesita preocuparse es la representación conceptual. El usuario agrega un mosaico aquí, por lo que cambia ese índice de mosaico.

Nicol Bolas
fuente
44
+1 Excelente respuesta. Necesita ser votado más que el mío.
MichaelHouse
22

Divide el terreno en regiones o trozos. Luego solo carga los fragmentos que son visibles para los jugadores y descarga los que no lo son. Puedes pensarlo como una cinta transportadora, donde estás cargando trozos en un extremo y descargándolos en el otro a medida que el jugador avanza. Siempre por delante del jugador.

También puedes usar trucos como la creación de instancias. Donde si todos los mosaicos de un tipo solo tienen una instancia en la memoria y se dibujan varias veces en todas las ubicaciones donde se necesita.

Puedes encontrar más ideas como esta aquí:

Cómo lo hicieron: millones de azulejos en Terraria

Zonificación de áreas en un mapa de mosaico grande, así como mazmorras

MichaelHouse
fuente
El problema es que este enfoque es bueno para la aplicación cliente, pero no tan bueno para el servidor. Cuando algunos mosaicos en el mundo se disparan y comienza una reacción en cadena (y eso sucede mucho) todo el mapa debe estar en memoria para eso y es un problema cuando se agota.
user1306322
66
¿Qué contiene cada mosaico? ¿Cuánta memoria se usa por mosaico? Incluso a 10 bytes cada uno solo tiene 190 megabytes. El servidor no debería cargar la textura o cualquier otra información que no sea necesaria. Si necesita una simulación para 20 millones de mosaicos, debe separarse lo más posible de esos mosaicos para formar un conjunto de simulación que solo tenga la información requerida para sus reacciones en cadena.
MichaelHouse
5

La respuesta de Byte56 es buena, y comencé a escribir esto como un comentario a eso, pero se hizo demasiado largo y contiene algunos consejos que podrían ser más útiles en una respuesta.

El enfoque es absolutamente tan bueno para el servidor como para un cliente. De hecho, probablemente sea más apropiado, dado que se le pedirá al servidor que se preocupe por muchas más áreas que un cliente. El servidor tiene una idea diferente acerca de lo que debe cargarse de lo que lo haría un cliente (se preocupa por todas las regiones que les interesan a todos los clientes conectados), pero es igualmente capaz de administrar un conjunto de regiones en funcionamiento.

Incluso si pudieras colocar un mapa tan gigantesco (y probablemente no puedas) en la memoria, no quieres . No hay absolutamente ningún punto en cargar datos que no tiene que usar de inmediato, es ineficiente y lento. Incluso si pudiera guardarlo en la memoria, lo más probable es que no pueda procesarlo todo en un período de tiempo razonable.

Sospecho que su objeción a que se descarguen ciertas regiones es porque su actualización mundial está iterando sobre todos los mosaicos y procesándolos. Eso es ciertamente válido, pero no impide tener solo ciertas partes cargadas. En cambio, cuando se carga una región, aplique las actualizaciones que se perdieron para actualizarla. Eso también es mucho más eficiente en cuanto a procesamiento (concentrando un gran esfuerzo en un área pequeña de memoria). Si hay algún efecto de procesamiento que pueda cruzar los límites de la región (por ejemplo, un flujo de lava o un flujo de agua que se trasladará a otra región), entonces une esas dos regiones para que cuando una esté cargada, también lo haga la otra. Idealmente, esas situaciones se minimizarían, de lo contrario, se encontrará rápidamente de regreso en el caso 'todas las regiones deben cargarse en todo momento'.

MrCranky
fuente
3

Una forma eficiente que he usado en un juego XNA fue:

  • use hojas de sprites, no una imagen para cada mosaico / objeto, y simplemente cargue lo que necesitará en ese mapa;
  • divida el mapa en partes más pequeñas, digamos mapas en trozos de 500 x 200 fichas si su mapa es 4 veces este tamaño, o algo que usted pueda tener;
  • cargar este fragmento en la memoria y pintar solo las partes visibles del mapa (por ejemplo, las fichas visibles más 1 ficha para cada dirección en la que se mueve el jugador) en un búfer fuera de la pantalla;
  • borre la ventana gráfica y copie los píxeles del búfer (esto se llama doble búfer y suaviza el movimiento);
  • cuando te muevas, rastrea los campos del mapa y carga el siguiente fragmento cuando se acerque a él y añádelo al actual;
  • Una vez que el jugador está completamente en este nuevo mapa, puedes descargar el último.

También podría usar un solo mapa grande de esta manera si no es TAN grande y usar la lógica presentada.

Por supuesto, el tamaño de sus texturas debe ser equilibrado y la resolución paga un gran precio en esto. Intente trabajar a una resolución más baja y, si lo necesita, haga un paquete de alta resolución para cargar si la configuración está configurada en.

Tendrá que recorrer solo las partes relevantes de este mapa cada vez y renderizar solo lo que se necesita. De esta manera mejorarás mucho el rendimiento.

Un servidor puede procesar un mapa enorme más rápido porque no tiene que renderizar (una de las operaciones más lentas) el juego, por lo que la lógica puede ser el uso de fragmentos más grandes o incluso todo el mapa, pero solo procesa la lógica alrededor de los jugadores, como en un área de visualización 2 veces superior a la del jugador.

Un consejo aquí es que de ninguna manera soy un experto en desarrollo de juegos y mi juego fue hecho para el proyecto final de mi graduación (que obtuve el mejor puntaje), por lo que puede que no sea tan correcto en todos los puntos, pero he investigé la web y sitios como http://www.gamasutra.com y el sitio de creadores de XNA creators.xna.com (en este momento http://create.msdn.com ) para reunir conocimientos y habilidades y funcionó bien para mí .

Ricardo Souza
fuente
2

Ya sea

  • comprar más memoria
  • representa tus estructuras de datos de forma más compacta
  • no guarde todos los datos en la memoria a la vez.
Thomas
fuente
Tengo 8 Gb de ram en win7 64bit y la aplicación se bloquea cuando alcanza 1.5Gb. Así que realmente no tiene sentido comprar más carnero a menos que me falte algo.
user1306322
1
@ user1306322: Si tiene 20 millones de mosaicos y ocupa aproximadamente ~ 1.5 GB de memoria, eso significa que sus mosaicos tienen aproximadamente 80 bytes por mosaico . ¿Qué estás almacenando exactamente en estas cosas?
Nicol Bolas
@NicolBolas como dije, algunas entradas, bytes y una texture2d. Además, no todos ocupan 1,5 GB, la aplicación se bloquea cuando alcanza este costo de memoria.
user1306322
2
@ user1306322: Eso es mucho más de lo que debe ser. ¿Qué tan grande es un texture2d? ¿Cada mosaico tiene uno único o comparten texturas? Además, ¿ dónde has dicho esto? Ciertamente no lo pusiste en tu pregunta, que es donde debería estar la información. Explica mejor tus circunstancias específicas, por favor.
Nicol Bolas
@ user1306322: Francamente, no entiendo por qué mi respuesta fue rechazada. He enumerado tres remedios para situaciones de falta de memoria, por supuesto, eso no significa que todos se apliquen necesariamente. Pero sigo pensando que son correctos. Sin embargo, probablemente debería haber escrito "extiende tu memoria" en lugar de "comprar" para incluir el caso de las máquinas virtuales. ¿Estoy siendo rechazado porque mi respuesta fue concisa en lugar de verbosa? Creo que sus puntos son lo suficientemente directos como para ser entendidos sin más explicaciones.
Thomas
1

La pregunta aquí es cómo hacer que el juego deje de fallar cuando todavía hay memoria libre para usar. En cuanto a dividir el mapa en trozos, es un buen enfoque, pero no es lo que quiero en mi caso.

En respuesta a su actualización, no puede asignar matrices de mayor Int32.MaxValuetamaño. Tienes que dividirlo en trozos. Incluso podría encapsularlo en una clase de contenedor que expone una fachada tipo matriz:

Palanqueta
fuente