Omitir la recarga del dominio: iteración más rápida mediante la función MethodReload.

Para tu comodidad, tradujimos esta página mediante traducción automática. No podemos garantizar la precisión ni la confiabilidad del contenido traducido. Si tienes alguna duda sobre la precisión del contenido traducido, consulta la versión oficial en inglés de la página web.
Todos hemos estado ahí: modificas algo del código de jugabilidad y luego esperas a que el Editor complete una recarga de dominio. El proceso puede ralentizar tu tiempo de iteración y agotar realmente tu creatividad. Sin mencionar el tiempo extra dedicado a iterar en un dispositivo, donde cada ciclo requiere una compilación completa.
Nuestra respuesta a esto es MethodReload. Lo demostramos en el escenario en Unite Seoul y ahora está disponible como paquete experimental. En esta publicación cubriremos cómo usar MethodReload, cuáles son sus limitaciones y cómo se compara con las herramientas de recarga en caliente que quizás ya tengas en tu proyecto.
Por qué estamos atascados recargando
Un poco de historia ayuda aquí, porque la razón por la que existe la recarga de dominio es también la razón por la que es difícil deshacerse de ella.
Play Mode se construyó sobre los AppDomains de Mono, donde los cambios de código iniciaban un proceso familiar: desmantelar el dominio, cargar los nuevos ensamblados, volver a JIT, y luego reanudar en el mismo proceso. Durante años fue un superpoder genuino, y es una gran parte de por qué iterar en Unity se sentía rápido en comparación con construir un jugador.
También es un instrumento tosco, lo que significa que durante la recarga todo desaparece y todo vuelve, ya sea que hayas cambiado un método o cien. Además, IL2CPP no admite recargas de dominio en absoluto, por lo que cada cambio requiere reconstruir todo el juego.
Quizás te estés preguntando: "¿No solucionará esto CoreCLR?" y la respuesta es: No mágicamente. Aunque CoreCLR se deshace de domain reloads, todavía tiene que recargar ensamblados, y MethodReload también puede ayudar aquí.
Cómo usar MethodReload
Como se mencionó anteriormente, MethodReload ya está disponible y se incluye en com.unity.pipeline, actualmente en 0.7.0-exp.1.
El proceso de usar MethodReload es bastante simple. Simplemente etiqueta uno o más métodos con el atributo [MethodReload], inicia el observador, entra en Modo de Juego o adjunta un jugador de desarrollo y luego edita el cuerpo de tus métodos. La instancia en ejecución recoge el nuevo cuerpo en su próxima llamada.
MethodReload no reemplaza las recargas de dominio por completo, sin embargo. Los cambios más grandes todavía pasan por la compilación habitual. Pero para los pequeños ajustes que componen la mayor parte de un ciclo de iteración, convierte una larga espera en una cuestión de segundos, tanto en el Editor como en el dispositivo.
Pruébala
La forma más rápida de entender MethodReload es ejecutarlo, así que si quieres probarlo tú mismo, aquí tienes algunos pasos para empezar. Nota: dado que esta es una característica experimental, el proceso puede cambiar en el futuro y ser diferente de lo que se describe aquí
1. Instalar el paquete (se requiere Unity 6.0+). Puedes instalarlo a través de la CLI de Unity o usar el Administrador de paquetes para agregar el paquete Pipeline manualmente usando "Instalar paquete por nombre técnico" y esta cadena: com.unity.pipeline@0.7.0-exp.1.
2. Etiqueta los métodos sobre los que iteras. El atributo tiene que estar ahí antes de entrar en Modo Juego o construir un jugador, porque la verificación de envío está tejida en tiempo de compilación. Los métodos marcados deben ser public (de instancia o estáticos) y devolver void o IEnumerator; cualquier otra cosa se omite en tiempo de compilación con una advertencia.
using Unity.Pipeline.MethodReload;
using UnityEngine;
public class Spinner : MonoBehaviour
{
public float rotationSpeed = 90f;
[MethodReload]
public void Update()
{
transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
}
}3. Iniciar el observador. Para que tu MethodReload vea tus cambios en vivo, necesitarás iniciar el Watcher de MethodReload. Haz esto yendo a Configuración del proyecto → Tubería → Editor, y luego en la sección "Método Recargar Observador del Intérprete", haz clic en Iniciar Observación.

Mientras el observador cubre todo el árbol de Assets, un .cs guardado solo compila si contiene un atributo MethodReload, por lo que los guardados no relacionados no te cuestan nada.
4. Si se prueba en el editor: introducir Modo Juego. Una vez en funcionamiento, cualquier cambio de código etiquetado se compilará e inyectará en su lugar. Una vez que salgas del modo de juego, tus cambios se compilarán y pasarán por la recarga de dominio habitual.
5. Si se prueba en el dispositivo: conecta un dispositivo. Construye un jugador de desarrollo con el driver de tubería de tiempo de ejecución habilitado. Conecte el dispositivo con Autoconnect Profiler, o adjunte a través de la ventana Profiler o Console. A partir de entonces, cada guardado compila en el Editor usando las definiciones del preprocesador player's y se envía a cada jugador conectado. No se necesita reinicio en ninguna dirección.
Dos detalles del flujo de trabajo que vale la pena saber de antemano:
- La actualización automática está desactivada mientras se ejecuta el observador, por lo que un guardado recarga en lugar de activar una recarga de dominio; otros recursos no se importarán hasta que pulses Ctrl/Cmd+R, y Detener la observación restablece tus configuraciones.
- El reloj sobrevive a las recargas del dominio: vuelve a aplicar las ediciones que una recarga en Modo de Juego borró y vuelve a enviar a un jugador que se reconecta, ya que un jugador reiniciado inicia el código compilado original.
¿Qué tal usar la CLI?
Si prefieres trabajar en una terminal, quieres trabajar a través de un agente o quieres enviar cada método etiquetado en lugar de solo lo que ha cambiado desde que comenzó la vigilancia, la misma funcionalidad se expone como comandos de la CLI de Unity:
# Reload in the Editor through the interpreter
unity command reload_file_editor_interpreter "Assets/Scripts/Spinner.cs"
# Compile in the Editor, push to connected players (folders work too)
unity command reload_file_player_interpreter "Assets/Scripts" --player 2Limitaciones
No hay forma de evitar la recarga de dominio sin compromisos, y las limitaciones de MethodReload están en tres lugares: qué tipos de cambios puede aplicar, qué C# puede ejecutar y qué tan rápido se ejecuta.
Solo cambian los cuerpos de los métodos etiquetados. Una recarga nunca toca sitios de llamada, campos o diseño de tipo. Sin nuevos campos, sin nuevas clases. Esa restricción es lo que lo hace seguro de aplicar a una instancia en vivo, y es en torno a lo que planeas: decide qué métodos vas a iterar y etiquétalos. Un reload puede declarar un nuevo método y llamarlo desde un cuerpo recargado, pero ese método no puede ser un punto de entrada de recarga, porque nada compilado sabe llamarlo hasta tu próximo compilación real.
Ejecuta un subconjunto de C#. El intérprete está construido a mano y tiene que ser lo suficientemente pequeño para vivir dentro de un jugador, por lo que no implementa todo C#. Dentro de un cuerpo de [MethodReload] no puedes usar:
awaityTask,try/catch/finally,lock, sintaxis de consulta LINQ,checkeddecimal,dinámico, tipos de reflexión, tipos de hilo- parámetros
ref,out, yin,refretornos y locales, arreglos multidimensionales (los irregulares están bien), métodos genéricos
Lo que hace cubre la mayor parte del código de juego: primitivas, llamadas a métodos, bucles, delegados, interpolación de cadenas, corutina yield, Collections. Un analizador Roslyn viene con el paquete y marca violaciones en tu IDE, solo en cuerpos etiquetados, por lo que te enteras mientras escribes en lugar de después de un envío.
En el dispositivo, el código interpretado solo puede llamar a lo que el jugador expone. IL2CPP elimina código sin referencias, por lo que un cuerpo recargado que llama a un miembro de UnityEngine o BCL que tu compilación nunca usó no tiene nada que llamar. En el Editor, los miembros faltantes se vinculan bajo demanda. En un dispositivo, las comprobaciones de empuje revisan por delante y enumeran los miembros faltantes como advertencias; si el código llega a uno de todos modos, lanza una excepción. Regla general: cambia lo que un método hace, no el conjunto de APIs de las que depende.
El código interpretado es más lento que el código compilado. Esa es la naturaleza de un intérprete, ya sea la comparación de IL2CPP por adelantado o Mono y CoreCLR justo a tiempo. Solo afecta a los métodos marcados en las compilaciones de desarrollo. En las compilaciones de lanzamiento no hay prólogo ni intérprete; un método etiquetado simplemente ejecuta su cuerpo compilado. En última instancia, depende de ti decidir si la penalización de rendimiento durante la iteración vale la pena por la velocidad y conveniencia mejoradas de la iteración.
Las corutinas capturan los cambios en la creación. Un método [MethodReload] IEnumerator usa el nuevo cuerpo la próxima vez que se ejecuta StartCoroutine. Las corrutinas ya en ejecución mantienen el cuerpo con el que comenzaron. Párelo con [OnMethodReloaded], un método de instancia sin parámetros que se dispara en cada instancia viva después de una recarga que aterriza en su tipo, y tiene un OnEnable libre de recarga de dominio para reiniciar bucles de larga duración:
[OnMethodReloaded]
void OnMethodReloaded()
{
StopAllCoroutines();
StartCoroutine(MainLoop());
}Cómo funciona internamente
Es la combinación de dos ideas: la primera es que compilar C# produce bytecode IL, y que el IL que permanece dentro de un subconjunto modesto puede ser ejecutado por un intérprete en lugar de ser compilado. Ese intérprete es C# ordinario en sí mismo, por lo que IL2CPP lo compila junto con el resto de tu juego, lo que permite que se ejecute en el dispositivo.
La segunda idea es meterse en ello. Cada método [MethodReload] tiene un camino cableado al intérprete. La ramificación al intérprete se realiza tejiendo un prólogo en los métodos etiquetados en tiempo de compilación. Descompilado, parece esto:
[MethodReload]
public void TakeDamage(int amount)
{
// weaved during compilation
if (MethodReloadRegistry.HasOverride("Player.TakeDamage") &&
MethodReloadRegistry.TryInvokeMethodReload(
"Player.TakeDamage", this, amount))
{
return;
}
// initial code
hp -= amount;
}La comprobación tejida pregunta al registro si existe una anulación para este método. El observador y la CLI llenan ese registro con IL recién compilado. Si hay una entrada, el intérprete la ejecuta y el cuerpo original se omite. De lo contrario, el código compilado se ejecuta como de costumbre.
Cómo se compara
Ya existen buenas herramientas para iterar en C# sin recargar el dominio, y preferimos ser precisos sobre dónde aplica cada una que pretender que no lo hacen.
Paquetes de la Tienda de Activos métodos de parcheo en un entorno de ejecución Mono en ejecución. Eso les da una excelente experiencia de Editor, C# completo en el cuerpo recargado, sin paso de etiquetado y, en el caso de Hot Reload, un modo en el dispositivo para compilaciones de Mono player. Todas las ventajas reales. También significa que no trabajan con un jugador de IL2CPP porque no hay tiempo de ejecución para parchear.
.NET Hot Reload, la característica en Visual Studio y Rider para aplicaciones .NET, aplica ediciones a través de la ruta de editar y continuar del tiempo de ejecución. Está construido para CoreCLR y no apunta a los entornos de ejecución Mono o IL2CPP de Unity.
MethodReload se sitúa en un lugar diferente con su backend de intérprete cubriendo el lenguaje de trading para alcance: un subconjunto de C#, pero ejecutándose en la misma compilación de desarrollo IL2CPP a la que de otro modo estarías volviendo a implementar en un teléfono o un kit de desarrollo de consola, a través del cable que ya tienes enchufado, sin ningún servidor extra en el jugador ni ningún puerto que abrir.
Versión honesta: si tu dolor de iteración reside en el Editor, las herramientas existentes son maduras y deberías seguir usando lo que te funciona. Si tu dolor de iteración vive en un dispositivo, y especialmente en IL2CPP, esta es la primera opción que va allí.
Consejos finales
Si estás configurando un flujo de trabajo para un proyecto ahora mismo:
- Etiqueta tus puntos calientes.
[MethodReload]no cuesta nada en compilaciones de lanzamiento, así que etiqueta los métodosUpdate, de ajuste y de sensación de juego en los que sabes que vas a meterte en el dispositivo. - Deja que el analizador te mantenga dentro del subconjunto. Si un método necesita
try/catchoawait, mueve esa parte a un ayudante sin etiquetas y recarga la lógica a su alrededor. El analizador te avisará si estás haciendo algo mal. - Pruébalo primero en el Editor. El observador, o
reload_file_editor_interpreterdesde la CLI, ejecuta tu edición a través del mismo intérprete que se ejecuta en el dispositivo. Si funciona allí, funcionará en el hardware, así que capturas problemas de subconjunto y de API faltante sin esperar una compilación.
¿Qué sigue?
La referencia de comandos, la página de configuración de tiempo de ejecución y la lista completa de restricciones están en com.unity.pipeline docs.
Te animamos a probar esto en tus proyectos y háznos saber cómo va. Se agradece mucho la retroalimentación y los paquetes experimentales mejoran más rápido cuando la gente los impulsa.
La recarga del dominio no va a ninguna parte para los cambios que la requieren. Para los cambios que no, puedes dejar de esperar.