Cómo hicimos el motor y el juego durante un año y medio. Segunda parte La infraestructura

Primero, algunos comentarios sobre los rastros del artículo anterior. Realmente solíamos trabajar en Wargaming , donde desarrollamos un motor conocido como dava.framework o dava.engine . Por lo tanto, muchos viejos colegas, con quienes todavía estamos en buenas relaciones, participan activamente en la discusión.

Algunas personas tienen dudas: ¿es esta la misma tecnología u otra? Respuesta: esta es una nueva tecnología escrita desde cero.

¿Cómo nos las arreglamos en solo un año? Nuestro equipo tiene una vasta experiencia. Muchos han estado desarrollando motores y juegos durante más de 15 años.

¿Por qué desde cero, si pudieras tomar nuestro viejo motor, que también se encuentra en código abierto? Tiene unos 10 años y la mayor parte del código está desactualizado. Incluso las mejores partes del motor, de las cuales estamos orgullosos, a veces contenían piezas de código y algunos rudimentos de 5, 7 y otras incluso hace 10 años. Muchas soluciones arquitectónicas fueron diseñadas para dispositivos de esa época, comenzando con un iPhone 3G. Ahora nos enfocamos al menos en el iPad Air 1 y similares en dispositivos Android potentes. En consecuencia, los enfoques han cambiado algo.

Y la pregunta más común: ¿por qué tener motor propio? En el último artículo hubo varios argumentos de diversos grados de persuasión. Quiero concentrarme en lo principal: solo nuestra propia tecnología puede permitirte aprovechar al máximo el hierro, hacer el máximo número de optimizaciones específicamente para tu estilo de juego y visual. Nos posicionamos, incluso como una empresa de tecnología, no solo como desarrollador de juegos. Creemos que con nuestro nivel de ingenieros y nuestra experiencia podemos competir seriamente en el mercado de productos móviles de alta tecnología.

Y ahora al grano: ¿qué herramientas y técnicas nos han ayudado a lograr esta tarea bastante ambiciosa en poco tiempo?

La infraestructura


Elegimos Atlassian Bitbucket Server + Jenkins. En el Bitbucket se encuentra el repositorio principal (maestro), al que está conectado Jenkins. Cada desarrollador tiene su propio tenedor. Para cada tarea, se crea una nueva bifurcación en la bifurcación, que se integra de nuevo a través de la solicitud de extracción. En general, el esquema es bastante estándar. Cada solicitud de extracción se somete a una revisión obligatoria y pruebas automáticas. Y, si tiene éxito, se fusiona automáticamente con el maestro.

Jenkins


Jenkins tiene varias deficiencias: es un bozal web antiguo, no muy rápido, glotón, que parece un portal de Internet de los años 90. Sin embargo, su flexibilidad, una gran cantidad de módulos y su uso gratuito lo convierten en una buena opción incluso en 2019. Después de jugar con los módulos y la configuración, puede lograr una apariencia digerible, una descripción declarativa de las tuberías (en el repositorio). Por cierto, ahora hay alrededor de 40 canales: pruebas, editores, un juego para todas las plataformas; trabajar con infraestructura de servidor y metagame. Recoge todo 20 buildagentov.

En el futuro, por supuesto, quiero probar soluciones modernas de hipster, por ejemplo, GitLab o TravisCI autohospedado. No consideramos soluciones completamente en la nube (Nevercode, Bitrise, CircleCI, etc.) debido al gran tamaño de nuestro repositorio, los activos y, en consecuencia, el tiempo de construcción y el tamaño de los artefactos.

Sistema de construcción


El principal requisito para el sistema era el siguiente: generación de proyectos para iOS, MacOS, Android, Windows, Linux en un solo script. Logramos probar Premake, SCons, Bazel y CMake. Por varias razones, nos detuvimos en el CMake probado por tiempo.

En los últimos años, CMake se ha convertido casi en el estándar para las bibliotecas C ++. Casi todo, desde abseil hasta SDL, se puede conectar a su proyecto CMake en solo unas pocas líneas. Por supuesto, hay excepciones, como OpenSSL o V8, con las que tuve que sudar un poco. Además del Zmeik desnudo, desarrollamos un pequeño marco (alrededor de 3.000 líneas en total). Características clave:
Modularidad Las partes individuales del motor están diseñadas como módulos. Por ejemplo, sonido, interfaz de usuario, física, red, etc. Cada módulo puede tener sus propios activos (por ejemplo, sombreadores) y puede depender de otros módulos.

La aplicación final en el motor (juego, editor, utilidades) conecta solo los módulos que necesita. Un poco aparte es el módulo central, que es una dependencia para la mayoría de los otros módulos. Core implementa el punto de entrada, el ciclo de aplicación principal, la interacción con el sistema operativo y otras entidades básicas.
Módulos de terceros. Nuestro marco le permite descargar un repositorio o archivo git en varias líneas, descomprimir, compilar, copiar bibliotecas y / o fuentes. Hoy tenemos 66 módulos de terceros de este tipo: análisis, formatos de archivos de terceros, middleware como física, una biblioteca de sonido, etc.

Proceso de desarrollo


En base a la experiencia previa, decidimos agregar tanto el motor como el juego en un repositorio. Esto te permite realizar cambios sin problemas en la API del motor y adaptar sincrónicamente el juego. El resultado fue el denominado monorepository con sus ventajas y desventajas. Pero, como planeamos de inmediato mantener un ritmo de desarrollo muy alto, la posibilidad de una refactorización sincrónica del motor y del juego superó todas las demás desventajas de esta solución.

En promedio, agregamos más de 20 solicitudes de extracción por día. Esto significa que el maestro puede romperse potencialmente 20 veces al día. Afortunadamente, en 1991, se les ocurrió la técnica de integración continua. ¿A qué hemos llegado?

Integración continua


Como se mencionó anteriormente, se crea un brunch para cada tarea en la bifurcación del desarrollador. A continuación, se crea una solicitud de extracción desde este brunch al repositorio principal. Esta solicitud de extracción pasa una serie de pruebas automatizadas en Jenkins:

  1. Pruebas unitarias para todas las plataformas (windows, linux, macos, ios, android). Googletest se usa como base, y OpenCppCoverage, cuyo informe es verificado por un script de python adicional, se usa para verificar el porcentaje de cobertura. Si el porcentaje de cobertura para un archivo en particular es inferior al 75%, la prueba se considera fallida. Por lo tanto, hemos cubierto la mayoría de las clases de motores de bajo nivel con pruebas.
  2. Formateador de código Para el código C ++ usamos el formato clang. El formateo del código modificado primero ocurre automáticamente cuando se confirma en la máquina del desarrollador, y luego se verifica en la prueba. Para javascript, que se usa como lenguaje de script, se usa npm linter.
  3. Pruebas de activos. Un grupo bastante grande de pruebas: desde la validación de formatos de archivo hasta la verificación de dependencias (por ejemplo, verificar que la textura utilizada en el nivel del juego existe).
  4. Pruebas unitarias y funcionales del editor. Una parte integral del motor es el editor, donde se crean y editan los niveles de juego y otros activos. Además de las pruebas unitarias, froglogic Squish for Qt se usa para probar el editor, una utilidad para pruebas automáticas de GUI. Todo esto nos permite prescindir de las pruebas manuales del editor. Además, según las reseñas de artistas y diseñadores de nivel, el nivel de su calidad y estabilidad es más alto que en la compañía anterior, cuando teníamos un equipo de cinco evaluadores. Al mismo tiempo, las liberaciones ocurren diariamente, y con las pruebas manuales, las liberaciones ocurren cada 2 semanas.
  5. Pruebas funcionales del juego. Está claro que quiero usar pruebas funcionales automáticas para el juego. Por lo tanto, comenzamos a desarrollar el siguiente sistema:

  • La aplicación de prueba (específicamente, un script de Python) lanza un servidor de juegos y un cliente con ciertos parámetros
  • el servidor y el cliente en ejecución abren el puerto de red,
  • La aplicación de prueba se conecta a ellos y envía comandos: descargue un mapa, seleccione un personaje y armas, muévase a un punto, apunte, dispare, etc.
  • la sintaxis de prueba en sí es python pytest. Este sistema se encuentra actualmente en desarrollo activo.

La mayoría de los proyectos para pruebas se recopilan con el indicador "tratar advertencias como errores" activado y en la plataforma MacOS con el clang AddressSanitizer activado adicionalmente, lo que le permite detectar aún más errores en la etapa de preparación de la solicitud de extracción.

Además de las pruebas, cada solicitud de extracción es revisada por al menos otros dos desarrolladores y, si es necesario, se envía para su revisión. Cuando se hayan completado todas las pruebas y los revisores no tengan comentarios, la solicitud de extracción se congela automáticamente.
Dado que algunas pruebas pueden llevar un tiempo considerable (por ejemplo, una prueba completa del editor de GUI dura más de una hora), se utiliza un script acortado en las solicitudes de extracción. El conjunto completo de pruebas se inicia en el asistente cada 4 horas.

Hasta la fecha, 6.600 misiones de extracción han sido creadas y mantenidas de tal manera.



Entrega continua


Utilizamos el concepto de lanzamientos automáticos diarios (o más bien diarios). Cómo sucede exactamente esto:

  1. se crea la etiqueta git,
  2. ejecuta versiones completas de todas las pruebas,
  3. Si tiene éxito, se recogen los artefactos:

  • editores para MacOS y Windows. Por lo tanto, cada mañana todos tienen una versión nueva de las herramientas. Y, gracias a las pruebas automáticas, confiamos en su cierta calidad y estabilidad.
  • Juego cliente y servidor para todas las plataformas. El cliente para iOS se carga en TestFlight, en Android, en Google Play, en otras plataformas, en JFrog Artifactory, servidores de juegos y otros servicios, en la nube. Es decir, todas las mañanas tenemos una nueva versión del juego, lista para pruebas y pruebas de juego.

Por supuesto, no todos los lanzamientos nocturnos finalizan con éxito. Algunas pruebas pueden fallar o se producirá un error crítico al iniciar la aplicación. En este caso, el desarrollador de servicio repara los problemas encontrados durante el día y se reinicia el proceso de lanzamiento.
Hay varios en servicio todos los días:

  1. Asistente de 1er nivel. Supervisa la estabilidad de las pruebas en el repositorio principal.
  2. Asistente de segundo nivel en el juego. Reparación de errores del juego.
  3. Asistente de segundo nivel en editores. Corrige errores editoriales, aconseja a los usuarios (artistas, diseñadores de niveles, diseñadores de juegos).

También en el día de servicio, puede incurrir en deudas técnicas: agregue las pruebas faltantes para la funcionalidad anterior, complemente la documentación o realice la refactorización, lo que no toma tiempo durante la planificación de lanzamiento habitual.



En el próximo artículo, veremos más de cerca la arquitectura de software del motor, así como los módulos y subsistemas principales.

Continuará ...

La primera parte: habr.com/en/post/461623

Source: https://habr.com/ru/post/465343/


All Articles