using System.Collections.Generic; using UnityEngine; /// /// Registre des matériaux INSTANCIÉS pour un objet précis. /// /// Le problème : partout dans le projet on écrit `renderer.material = Instantiate(_G.QUELQUECHOSE)` /// ou `renderer.sharedMaterial = Instantiate(MAT)`. Une copie créée par `Instantiate` n'est PAS /// suivie par Unity — contrairement à celle que crée le getter `renderer.material`. Détruire le /// GameObject ne la libère donc pas : elle reste en mémoire jusqu'à la fin de la session. /// Ces appels sont sur des chemins PAR FACE (chaque face de caisson, chaque porte, chaque panneau), /// et l'IA détruit puis reconstruit toute la cuisine à chaque génération : la mémoire montait en /// escalier jusqu'au `abortOnCannotGrowMemory` du build web. /// /// Le principe retenu : chaque copie est rattachée à l'objet qui la porte, et libérée par son /// OnDestroy. Aucun balayage global, donc aucun risque pour les matériaux PARTAGÉS chargés par /// Resources.Load (_G.INV, _G.GYPSE, _G.ST...) ni pour ceux que UIT_MATERIAL met en cache dans /// _C.ColorInScene : ils ne passent jamais par ici. /// /// Couvre aussi les suppressions manuelles, pas seulement DeleteAllCab : la libération suit la /// destruction de l'objet, d'où qu'elle vienne. /// public class MAT_TRASH : MonoBehaviour { readonly List _owned = new(); /// Nombre total de matériaux libérés depuis le lancement. Trace de vérification. public static int FreedCount { get; private set; } /// Nombre de matériaux actuellement enregistrés, tous objets confondus. public static int TrackedCount { get; private set; } /// /// Remplace `Instantiate(source)` : crée la copie ET la rattache à 'owner', qui la libérera /// en mourant. Retourne null si la source est nulle, comme Instantiate. /// public static Material New(Material source, GameObject owner) { if (source == null) return null; Material m = Instantiate(source); Register(m, owner); return m; } /// /// Surcharges pour un porteur donné sous forme de Component — Transform, Renderer, MonoBehaviour. /// Les appelants écrivent souvent `foreach (Transform child in ...)` : sans elles il faudrait /// ajouter `.gameObject` à chaque site. /// public static Material New(Material source, Component owner) => New(source, owner != null ? owner.gameObject : null); public static void Register(Material m, Component owner) => Register(m, owner != null ? owner.gameObject : null); /// Rattache une copie déjà créée à son objet porteur. public static void Register(Material m, GameObject owner) { if (m == null || owner == null) return; // JAMAIS un materiau PARTAGE. _G.MaterialOnScene est le cache que sert Get.MaterialInScene : // la meme instance est rendue a tous les appelants. Le detruire avec un seul de ses // porteurs laisse tous les autres en ROSE. Le registre ne doit suivre que des copies. if (_G.MaterialOnScene != null) { foreach (var kv in _G.MaterialOnScene) { if (kv.Value != m) continue; Debug.LogWarning("[MAT_TRASH] " + m.name + " est un materiau PARTAGE (" + kv.Key + ") -> non enregistre, il aurait ete detruit avec " + owner.name + "."); return; } } if (!owner.TryGetComponent(out MAT_TRASH trash)) { trash = owner.AddComponent(); trash.hideFlags = HideFlags.HideInInspector; } trash._owned.Add(m); TrackedCount++; } void OnDestroy() { for (int i = 0; i < _owned.Count; i++) { if (_owned[i] == null) continue; Destroy(_owned[i]); FreedCount++; TrackedCount--; } _owned.Clear(); } }