Unity y .NET, ¿qué sigue?

Recientemente comenzamos una iniciativa de varios años para ayudarlo a escribir código de mayor rendimiento más rápido y brindar estabilidad y compatibilidad a largo plazo. Siga leyendo para averiguar qué estamos haciendo para actualizar la pila tecnológica fundamental detrás de sus scripts.

Unity y .NET, ¿qué sigue?
Unity y .NET, ¿qué sigue?

Recientemente comenzamos una iniciativa de varios años para ayudarlo a escribir código de mayor rendimiento más rápido y brindar estabilidad y compatibilidad a largo plazo. Siga leyendo para averiguar qué estamos haciendo para actualizar la pila tecnológica fundamental detrás de sus scripts.

El ecosistema .NET está evolucionando dinámicamente de varias maneras beneficiosas, y queremos brindarle esas mejoras tan pronto como podamos. Nuestro grupo técnico interno de .NET trabaja en la mejora continua de nuestra integración de .NET, incluidas las características más nuevas de C# y .NET Standard 2.1 . Pero recientemente hemos acelerado las cosas para mejorar su experiencia de desarrollador en todos los ámbitos, en función de sus comentarios. 

Esta publicación de blog presenta los problemas en los que estamos trabajando. También discutimos este tema en Unity Dev Summit en GDC 2022. Puede ver la sesión completa aquí .

Este contenido está alojado por un proveedor externo que no permite ver videos sin aceptar las cookies de orientación. Establezca sus preferencias de cookies para las cookies de orientación en sí si desea ver videos de estos proveedores.

 

La evolución de .NET y Unity

La historia comienza hace 17 años, cuando nuestro CTO comenzó a aprovechar el tiempo de ejecución de Mono .NET con C#. Unity prefirió C# debido a su simplicidad, combinado con un compilador JIT (justo a tiempo) que traduce su C# en código nativo relativamente eficiente. Las partes restantes y mucho más grandes del motor de Unity se han desarrollado utilizando C++ para proporcionar un rendimiento equilibrado y controlado.

Durante muchos años, Unity se había estado ejecutando con una bifurcación específica del tiempo de ejecución Mono .NET y el lenguaje C# (2.0). Durante ese tiempo, agregamos soporte para plataformas adicionales. También hemos desarrollado nuestro propio compilador y tiempo de ejecución, IL2CPP, para permitirle apuntar a iOS y algunas plataformas de consola.

Mientras tanto, el ecosistema general de Microsoft .NET ha evolucionado, con nuevas licencias y soporte para plataformas que no son de Windows. Esta evolución nos ha permitido actualizar Unity .NET Mono Runtime en 2018 y adoptar versiones más modernas del lenguaje C# (7.0+). El mismo año, también lanzamos la primera versión del compilador Burst, pionero en el código nativo rápido generado para un subconjunto del lenguaje C#. Este avance permitió a Unity imaginar un mundo en el que podíamos extender el uso de C# en los otros segmentos críticos del motor sin tener que desarrollar estas partes en C++, lo que condujo al desarrollo del tiempo de ejecución DOTS .

Unity 2020 LTS y Unity 2021 LTS trajeron nuevas versiones del lenguaje C# y API de .NET como Span<T>. Paralelamente, hemos visto enormes mejoras de rendimiento en el ecosistema .NET, así como un entorno de desarrollo más amigable con la introducción del estilo SDK csproj y el floreciente ecosistema NuGet.

Qué necesitamos hacer

Como resultado de esta larga evolución, la plataforma Unity incluye una base de código C++ muy grande que interactúa directamente con objetos .NET utilizando suposiciones específicas heredadas de Mono .NET Runtime. Estos ya no son válidos ni eficientes para .NET (Core) Runtime. 

Además, hay una canalización de compilación personalizada complicada vinculada al Editor de Unity que no depende de MSBuild y, por lo tanto, no puede beneficiarse fácilmente de todas las funciones estándar.

También hemos estado hablando con muchos de ustedes en los últimos años, tanto en entrevistas como en el foro de Unity , para ver qué podríamos mejorar para facilitar su éxito. Lo que escuchamos es que desea usar el lenguaje C# más reciente, la tecnología de tiempo de ejecución de .NET y el código C# de terceros de NuGet. Cuando se trata de usar la plataforma Unity, nos dijo que quería sacar el máximo partido del hardware de destino con herramientas de prueba, depuración y creación de perfiles de C# de alta calidad, y una buena integración entre la API estándar de .NET y la API de Unity. Como programador de C# Unity, desea herramientas de Unity que funcionen a la perfección con el resto de su caja de herramientas y permitan una iteración rápida para que pueda lograr el mejor rendimiento de tiempo de ejecución de su clase.

Llegar allí nos llevará varios años. Lo mantendremos informado con actualizaciones frecuentes en blogs y foros sobre los desafíos técnicos que encontremos en el camino.

como lo vamos a hacer

Nuestro primer paso en esta iniciativa fue reunirnos con todas las personas internas apasionadas por C# y .NET en Unity para formar un grupo tecnológico de C#/.NET para impulsar este esfuerzo. 

Queremos construir sobre el ecosistema .NET en lugar de desarrollar soluciones personalizadas. Para permitirle aprovechar las mejoras de rendimiento y productividad que vienen con el último .NET SDK/Runtime y MSBuild, queremos migrar de Mono .NET Runtime a CoreCLR, el moderno .NET (Core) Runtime.

Esta iniciativa también le brinda innovación más allá del universo .NET existente, con el objetivo de brindar ciclos de iteración .NET más rápidos en sus scripts C#. Trabajaremos en la convergencia de las soluciones JIT y AOT (antes de tiempo), IL2CPP y Burst, para ofrecer el mejor equilibrio entre la eficiencia del tiempo de compilación y la calidad de CodeGen. 

Externamente, estamos trabajando con socios de la industria como Microsoft y JetBrains para garantizar que los creadores de Unity utilicen la tecnología .NET más reciente. También estamos aumentando nuestra participación en comunidades de código abierto. Vamos a dividir este esfuerzo en varios pasos. Vamos a ver lo que viene después.

En qué estamos trabajando en 2022

Este año, los equipos planean trabajar en las siguientes pistas.

Una infografía de los pilares de Unity Developer Experience

Flujo de trabajo de desarrollo de C#

El tiempo de iteración sigue siendo nuestra principal prioridad , ya que sabemos que desea aprovechar al máximo su tiempo. Estos son algunos ejemplos de lo que estamos haciendo para mejorar esto.

  • Como parte de la canalización de compilación, estamos mejorando el tiempo que dedica el procesamiento posterior de IL, que es responsable de modificar los ensamblados .NET compilados después de que se haya compilado su C#. Ahora estamos utilizando un proceso persistente para ejecutar el procesamiento posterior de IL después de la fase de compilación, y esto puede ahorrar unos pocos cientos de milisegundos.
  • Con el uso más frecuente del compilador Burst, estamos mejorando la granularidad de la detección de cambios de código con un algoritmo hash transitivo. Esto nos permite identificar qué código Burstable necesitamos compilar más rápidamente. Estamos trabajando para sacar el compilador Burst del proceso para que pueda compilar su código más rápido gracias a la ejecución en un ejecutable .NET 6.0 separado.
  • También estamos realizando mejoras en la recarga del dominio al mejorar los datos de reflexión creados detrás de escena cada vez que se usa TypeCache .
  • Vamos a agregar pruebas y validación para realizar un mejor seguimiento de la regresión del tiempo de iteración para paquetes y plantillas de proyectos.

Para la migración a MSBuild , el primer paso es desacoplar nuestra canalización de compilación del Editor de Unity y moverla a un proceso separado. Esta es una operación complicada porque hay años de código heredado con miles de líneas de código C++ y C# que debemos desenredar para lograr esto, al mismo tiempo que permanecemos compatibles con versiones anteriores. No verá cambios desde su punto de vista, pero allanará nuestro camino hacia MSBuild y simplificará el mantenimiento.

También vamos a mejorar la experiencia de depuración del IDE de C# con Burst mediante la introducción de un modo que cambiará automáticamente el depurador a depuración administrada cuando se establezca un punto de interrupción en una ruta de código que se ejecuta con Burst. Esto significa que no tendrá que eliminar manualmente el atributo [BurstCompile] en la ruta de código que se está depurando.

Modernización del tiempo de ejecución de .NET

El trabajo involucrado en la migración al tiempo de ejecución de .NET CoreCLR ya comenzó y es un viaje muy desafiante. Para que podamos entregar con éxito esta migración, nos gustaría abordar el problema gradualmente y asegurarnos de que podemos lanzar partes de una manera que mantenga la estabilidad de los proyectos de Unity existentes.

Por lo tanto, estamos planeando entregar esta migración en varias fases:

  • Primero, proporcionaremos compatibilidad con .NET CoreCLR para reproductores independientes en plataformas de escritorio. Podrá seleccionar este tiempo de ejecución en la configuración de su reproductor junto con el backend Mono e IL2CPP existente. Esta primera fase debería ayudarnos a migrar la parte central de Unity Engine (que es mucho más pequeña que la parte del Editor) y, con suerte, resolverá una buena parte de los desafíos técnicos que implica esta migración. Seguirá accediendo al tiempo de ejecución de .NET a través de la API de .NET Standard 2.1, y nuestro objetivo es lanzar este nuevo tiempo de ejecución durante 2023.  
  • En segundo lugar, migraremos Unity Editor a .NET CoreCLR y eliminaremos la compatibilidad con el tiempo de ejecución de .NET Mono al mismo tiempo. Esta segunda fase desafiará cómo recargaremos sus scripts en el Editor sin usar AppDomains y completaremos el cambio a .NET CoreCLR. También implicará la actualización de IL2CPP para admitir las bibliotecas de clase base del repositorio dotnet/runtime. Finalmente tendrá acceso a la API completa de .NET 7.xo 8.0. Esperamos lanzar este nuevo Editor durante 2024. 

Modernización del tiempo de ejecución de Unity

La compatibilidad con .NET Standard 2.1 en Unity 2021 LTS nos permite comenzar a modernizar el tiempo de ejecución de Unity de varias maneras. Actualmente estamos trabajando en dos mejoras.

Mejora del modelo de programación async/await . Async/await es un enfoque de programación fundamental para escribir código de juego que debe esperar a que se complete una operación asíncrona sin bloquear el bucle principal del motor. 

En 2011, antes de que async/await se generalizara en .NET, Unity introdujo operaciones asincrónicas con rutinas basadas en iteradores , pero este enfoque es incompatible con async/await y puede ser menos eficiente. Mientras tanto, .NET Standard 2.1 ha estado mejorando la compatibilidad con async/await en C# y .NET con la introducción de un manejo más eficiente de las operaciones async/await a través de ValueTask y al permitir su propio sistema similar a una tarea a través de AsyncMethodBuilder. 

Ahora podemos aprovechar estas mejoras, por lo que estamos trabajando para habilitar el uso de async/await con las operaciones asincrónicas existentes en Unity (como esperar el siguiente marco o esperar a que se complete UnityWebRequest). Como primer paso, estamos mejorando la compatibilidad con la cancelación de tareas asincrónicas pendientes cuando se destruye un MonoBehavior o al salir del modo Play mediante el uso de tokens de cancelación . También hemos estado trabajando en estrecha colaboración con nuestros mayores contribuyentes de la comunidad, como el autor de UniTask , para garantizar que puedan aprovechar estas nuevas funcionalidades.

Reducir las asignaciones de memoria y las copias aprovechando Span<T>. Debido a que Unity es un motor de C++ con una capa de secuencias de comandos de C#, se intercambian muchos datos entre los dos. Esto puede ser ineficiente ya que a menudo requiere copiar datos de un lado a otro o asignar nuevos objetos administrados. 

Span<T> se introdujo en C# 7.2 para mejorar estos escenarios y está disponible de forma predeterminada en .NET Standard 2.1. En los últimos años, es posible que haya escuchado o leído acerca de muchas mejoras de rendimiento significativas realizadas en .NET Runtime gracias a Span<T> (consulte los detalles de las mejoras en .NET Core 2.1 , .NET Core 3.0 , .NET 6 , .NET 6 ) . Queremos aprovechar su uso en Unity, ya que esto ayudará a reducir las asignaciones y, en consecuencia, la recolección de elementos no utilizados se detiene mientras mejora el rendimiento general de muchas API.