Détection des conversions implicites de UnityEngine.Object via Harmony

Dans l'écosystème Unity, certaines facilités de programmation, bien que pratiques, peuvent introduire des comportements opaques ou des baisses de performance. L'un des exemples les plus notables est la conversion implicite des instances de UnityEngine.Object en bool. Cet article explore comment intercepter ces appels à l'aide de la bibliothèque Harmony pour mieux comprendre et optimiser le flux d'exécution de vos scripts.

Le mécanisme de Harmony

Harmony est une bibliothèque puissante permettant de modifier, remplacer ou décorer des méthodes .NET au moment de l'exécution (runtime). Elle agit directement sur le code MSIL (Microsoft Intermediate Language). Pour détecter les conversions implicites, nous devons cibler la méthode spécifique générée par le compilateur lors de l'utilisation d'un objet Unity dans une condition booléenne.

Identifier la méthode cible via la réflexion

En C#, les opérateurs de conversion implicite sont compilés sous le nom de méthode op_Implicit. Puisqu'il peut y avoir plusieurs surcharges, il est nécessaire de filtrer par le type de retour souhaité (dans notre cas, bool).

private static MethodInfo FindImplicitOperator(Type targetType, Type returnType)
{
    var allMethods = targetType.GetMethods(BindingFlags.Public | BindingFlags.Static);
    foreach (var method in allMethods)
    {
        if (method.Name == "op_Implicit" && method.ReturnType == returnType)
        {
            return method;
        }
    }
    return null;
}

Implémentation du détournement (Hook)

Une fois la méthode identifiée, nous utilisons Harmony pour injecter un "Prefix". Ce code sera exécuté juste avant la méthode originale de Unity. L'objectif est de tracer l'origine de l'appel pour différencier le code utilisateur du code interne du moteur.

public static class ImplicitHookProvider
{
    private static int _callCounter = 0;

    public static bool Prefix(UnityEngine.Object __instance)
    {
        // Optimisation : On n'analyse pas chaque appel pour préserver les performances
        _callCounter++;
        if (_callCounter % 50 != 0) return true;

        var trace = new System.Diagnostics.StackTrace();
        var frame = trace.GetFrame(2); // Remonte à l'appelant d'origine
        var callerAssembly = frame.GetMethod().DeclaringType.Assembly.FullName;

        // On ignore les appels provenant des bibliothèques internes de Unity
        if (!callerAssembly.StartsWith("UnityEngine") && !callerAssembly.StartsWith("UnityEditor"))
        {
            UnityEngine.Debug.LogWarning($"[Audit] Conversion implicite détectée dans : {callerAssembly}");
        }

        return true; // Autorise l'exécution de la méthode originale
    }
}

Initialisation automatique dans l'éditeur

Pour activer ce mécanisme dès le chargement du projet, nous utilisons l'attribut InitializeOnLoad de Unity Editor. Cela permet d'appliquer les patchs Harmony sans intervention manuelle.

[UnityEditor.InitializeOnLoad]
public static class DiagnosticBootstrapper
{
    static DiagnosticBootstrapper()
    {
        var harmonyInstance = new HarmonyLib.Harmony("com.tech.implicit.detector");
        var target = FindImplicitOperator(typeof(UnityEngine.Object), typeof(bool));
        
        if (target != null)
        {
            var patchMethod = typeof(ImplicitHookProvider).GetMethod(nameof(ImplicitHookProvider.Prefix));
            harmonyInstance.Patch(target, prefix: new HarmonyLib.HarmonyMethod(patchMethod));
            UnityEngine.Debug.Log("Système de détection Harmony activé.");
        }
    }

    private static MethodInfo FindImplicitOperator(Type t, Type ret)
    {
        return System.Linq.Enumerable.FirstOrDefault(t.GetMethods(), 
            m => m.Name == "op_Implicit" && m.ReturnType == ret);
    }
}

Considérations sur les performances

L'utilisation de System.Diagnostics.StackTrace est une opération coûteuse en termes de processeur. Dans un environnement de production ou lors de tests de performance intensifs, ce type de monitoring doit être désactivé ou utilisé avec un échantillonnage très élevé (comme illustré avec le modulo sur _callCounter). Cette approche est principalement destinée à une phase de débogage ou d'audit de code pour identifier des patterns de programmation sous-optimaux au sein d'un projet de grande envergure.

Étiquettes: Unity Harmony C# Reflection Profiling

Publié le 14 juillet à 06h02