# Gestion Mémoire WebGL - Prévention des erreurs OOM

## 🎯 Problème

En WebGL, la mémoire disponible est limitée (typiquement 2GB max). Lors du chargement de scènes, si on ne nettoie pas correctement les ressources (textures, sprites, RenderTextures), on peut atteindre la limite mémoire et provoquer un crash avec l'erreur :

```
abort("OOM") at error
```

## ⚠️ Situations à risque

### 1. Chargement de scène depuis un panel UI
**PROBLÈME** : Les panels d'options/settings contiennent souvent beaucoup de ressources :
- Avatars/images de quêtes chargées dynamiquement
- Sprites générés (gradients, coins arrondis)
- Textures de badges/médailles
- RenderTextures pour les vidéos

Si on charge une nouvelle scène sans nettoyer ces ressources, elles restent en mémoire !

### 2. Retours fréquents vers la scène Main/Map
**PROBLÈME** : La scène Main/Map charge de grosses textures de fond. Si on y retourne sans nettoyer la scène précédente, on accumule la mémoire.

### 3. Chargement de jeux avec vidéos/images lourdes
**PROBLÈME** : Les RenderTextures de vidéos + textures de fond peuvent rapidement saturer la mémoire.

## ✅ Solution : Pattern de nettoyage mémoire

### Pattern standard pour WebGL

```csharp
private IEnumerator LoadSceneWithMemoryCleanup(string sceneName)
{
    // 1. Nettoyer les ressources locales (textures, sprites, etc.)
    CleanupLocalResources();
    
    // 2. Fermer/désactiver les UI panels actifs
    if (SomeUIPanel != null) SomeUIPanel.SetActive(false);
    
#if UNITY_WEBGL && !UNITY_EDITOR
    Debug.Log($"🧹 WebGL - Nettoyage mémoire avant chargement de {sceneName}...");
    
    // 3. Purger les caches persistants (DontDestroyOnLoad)
    try 
    { 
        if (RemoteSpriteCache.GetExisting() != null) 
            RemoteSpriteCache.GetExisting().ClearAll(); 
    } catch { }
    
    try 
    { 
        if (GameDataManager.Instance != null) 
            GameDataManager.Instance.ClearAssetCache(); 
    } catch { }
    
    // 4. Attendre que les Destroy() prennent effet
    yield return null;
    yield return new WaitForEndOfFrame();
    
    // 5. Libérer les assets non référencés (COÛTEUX mais nécessaire)
    yield return Resources.UnloadUnusedAssets();
    
    // 6. Garbage collection
    try { System.GC.Collect(); } catch { }
    
    // 7. Attendre encore un frame pour finaliser
    yield return null;
    
    Debug.Log("✅ Nettoyage mémoire WebGL terminé");
#else
    // En dehors de WebGL, juste attendre un frame
    yield return null;
#endif
    
    // 8. MAINTENANT on peut charger la scène en toute sécurité
    SceneManager.LoadScene(sceneName);
}
```

### Méthode helper réutilisable

Vous pouvez créer cette méthode dans une classe utilitaire :

```csharp
public static class WebGLMemoryHelper
{
    public static IEnumerator CleanupBeforeSceneLoad()
    {
#if UNITY_WEBGL && !UNITY_EDITOR
        Debug.Log("🧹 WebGL - Nettoyage mémoire...");
        
        try { if (RemoteSpriteCache.GetExisting() != null) RemoteSpriteCache.GetExisting().ClearAll(); } catch { }
        try { if (GameDataManager.Instance != null) GameDataManager.Instance.ClearAssetCache(); } catch { }
        
        yield return null;
        yield return new WaitForEndOfFrame();
        
        yield return Resources.UnloadUnusedAssets();
        try { System.GC.Collect(); } catch { }
        
        yield return null;
        Debug.Log("✅ Nettoyage mémoire WebGL terminé");
#else
        yield return null;
#endif
    }
}
```

## 📋 Checklist : Où appliquer ce pattern ?

### ✅ Déjà corrigé (2025-01-06)
- [x] `QuetesTab.cs` - Bouton JOUER depuis l'onglet Quêtes
- [x] `QuestPreviewPanelBuilder.cs` - Skip de quête (3 endroits)
- [x] `ParametresTab.cs` - Retour au menu + Déconnexion
- [x] `MapManager.cs` - Retour vers "menu" via bouton retour header
- [x] `MainMenuManager.cs` - Lancement vers "Player" depuis menu
- [x] `EscapeKeyHandler.cs` - Retour menu via ESC

### ℹ️ Pas besoin de correction

#### MapManager - Retours vers "main"
- `MapManager.cs` lignes 1443-1448 : Utilise déjà `UnifiedLoadingManager.LoadMenuScene()` (OK)
- `MapManager.cs` lignes 2491-2497 : Utilise déjà `UnifiedLoadingManager.LoadMenuScene()` (OK)

Le nettoyage automatique de `CleanupPoints.cs` s'active lors du chargement de la scène Main/Map, donc ces transitions sont déjà gérées.

### ⚠️ À vérifier/corriger

#### Priorité HAUTE (UI → Scene)
Tous les cas critiques ont été corrigés ✅

#### Priorité MOYENNE (Scene → Scene)
- [ ] `TrousGameManager.cs` - Fin de jeu → retour
- [ ] `CalculatorGameManager.cs` - Fin de jeu → retour
- [ ] `GameManager.cs` - Fin de jeu → retour

#### Priorité BASSE (Déjà géré ou rare)
- [ ] `UnifiedLoadingManager.cs` - Utilise déjà des coroutines
- [ ] `IntroScreenManager.cs` - Scène d'intro (une seule fois au démarrage)

## 🔍 Comment détecter les endroits à corriger

### 1. Rechercher tous les LoadScene
```bash
# Dans votre IDE
Rechercher: SceneManager.LoadScene
```

### 2. Identifier les cas à risque
Pour chaque `LoadScene` trouvé, posez-vous ces questions :
- ❓ Est-ce appelé depuis un **panel UI** avec des images/sprites ?
- ❓ Est-ce un **retour vers Main/Map** (grosses textures) ?
- ❓ Y a-t-il des **ressources runtime** (RenderTexture, Texture2D, Sprite créés dynamiquement) ?

Si OUI à l'une de ces questions → **Appliquer le pattern de nettoyage**

### 3. Convertir en coroutine
```csharp
// AVANT (❌ Risque OOM)
void OnButtonClick()
{
    SceneManager.LoadScene("main");
}

// APRÈS (✅ Sécurisé)
void OnButtonClick()
{
    StartCoroutine(LoadMainSceneWithCleanup());
}

private IEnumerator LoadMainSceneWithCleanup()
{
    // ... pattern de nettoyage ...
    SceneManager.LoadScene("main");
}
```

## 🧪 Tests

### Test en WebGL
1. **Build WebGL** (Development Build pour voir les logs)
2. **Ouvrir la Console navigateur** (F12)
3. **Tester le flow** : Menu → Jeu → Options → JOUER → ...
4. **Surveiller les logs** : Vérifier que "🧹 WebGL - Nettoyage mémoire..." apparaît
5. **Surveiller la mémoire** : Task Manager → Performance → Mémoire GPU

### Logs attendus
```
[QuetesTab] 🎮 JOUER: 🧹 Nettoyage des ressources...
[QuetesTab] 🎮 JOUER: 🧹 WebGL - Nettoyage mémoire agressif...
[QuetesTab] 🎮 JOUER: 🧹 Resources.UnloadUnusedAssets()...
[QuetesTab] 🎮 JOUER: ✅ Nettoyage mémoire WebGL terminé
[QuetesTab] 🎮 JOUER: 🚀 Chargement de la scène 'main'...
```

## 📚 Références

### Classes utilitaires existantes
- `CleanupPoints.cs` - Système de nettoyage automatique au changement de scène
- `RuntimeResourceTracker.cs` - Tracking des ressources runtime pour destruction explicite
- `RemoteSpriteCache.cs` - Cache de sprites avec méthode `ClearAll()`
- `GameDataManager.cs` - Cache d'assets avec méthode `ClearAssetCache()`

### Nettoyage automatique existant
Le fichier `CleanupPoints.cs` fait déjà un nettoyage automatique lors du chargement de Main/Map :

```csharp
if (newScene.name == "Main" || newScene.name == "Map")
{
    // Purge des caches
    RemoteSpriteCache.GetExisting()?.ClearAll();
    GameDataManager.Instance?.ClearAssetCache();
    
#if UNITY_WEBGL && !UNITY_EDITOR
    Resources.UnloadUnusedAssets();
    System.GC.Collect();
#endif
}
```

⚠️ **ATTENTION** : Ce nettoyage automatique se fait APRÈS le chargement de la scène. Si la nouvelle scène charge immédiatement de grosses ressources, on peut dépasser la limite mémoire AVANT que le nettoyage ne se fasse !

**Solution** : Faire le nettoyage AVANT `LoadScene()` dans les cas critiques (panels UI).

## 🎓 Bonnes pratiques générales

### 1. Toujours détruire les ressources runtime
```csharp
void OnDestroy()
{
    // Textures créées dynamiquement
    if (myTexture != null) Destroy(myTexture);
    
    // Sprites créés dynamiquement
    if (mySprite != null) Destroy(mySprite);
    
    // RenderTextures
    if (myRenderTexture != null)
    {
        myRenderTexture.Release();
        Destroy(myRenderTexture);
    }
}
```

### 2. Utiliser RuntimeResourceTracker
```csharp
private RuntimeResourceTracker _resourceTracker = new RuntimeResourceTracker();

void CreateSprite()
{
    Texture2D tex = new Texture2D(256, 256);
    Sprite sprite = Sprite.Create(...);
    
    // Track pour destruction automatique
    _resourceTracker.Track(tex, sprite);
}

void OnDestroy()
{
    _resourceTracker.Cleanup();
}
```

### 3. Éviter les fuites mémoire communes
- ❌ Ne jamais oublier `Destroy()` sur les Texture2D/Sprite créés par code
- ❌ Ne jamais garder de références à des objets détruits
- ❌ Toujours appeler `RenderTexture.Release()` avant `Destroy()`
- ✅ Utiliser `CleanupPoints` pour les nettoyages au changement de scène
- ✅ Vider les listes/dictionnaires qui contiennent des références

## 🚨 En cas d'erreur OOM malgré tout

### 1. Identifier la source
- Ouvrir la Console navigateur (F12)
- Regarder la stack trace de l'erreur
- Identifier quelle scène/action provoque le crash

### 2. Vérifier les logs Unity
```
// Rechercher ces patterns :
"Chargement de la scène"
"🧹" (emoji de nettoyage)
"Texture2D"
"RenderTexture"
```

### 3. Ajouter des logs de debug
```csharp
Debug.Log($"[MEMORY] Avant LoadScene - Textures actives: {Resources.FindObjectsOfTypeAll<Texture2D>().Length}");
yield return CleanupMemoryBeforeSceneLoad();
Debug.Log($"[MEMORY] Après cleanup - Textures actives: {Resources.FindObjectsOfTypeAll<Texture2D>().Length}");
```

### 4. Augmenter la mémoire WebGL (dernier recours)
Dans `Player Settings` → `WebGL` → `Memory Size` : Augmenter de 2048MB à 4096MB
⚠️ Attention : Tous les navigateurs ne supportent pas 4GB !

---

## 🖱️ Correction du scroll (molette + trackpad Mac) en WebGL

### Problème
Sur WebGL (particulièrement sur Mac), le scroll ne fonctionne ni avec la molette de souris ni avec le geste à deux doigts sur trackpad. Le nouveau Input System de Unity ne capture pas correctement les événements `wheel` du navigateur.

### Cause racine
Le nouveau Input System (`UnityEngine.InputSystem`) en WebGL ne reçoit pas les événements de scroll natifs du navigateur. Les événements `wheel` JavaScript sont perdus avant d'atteindre Unity.

### Solution 1 : Pont JavaScript (RECOMMANDÉ ✅)

Cette solution capture les événements `wheel` directement depuis le navigateur et les transmet à Unity via un plugin JavaScript.

#### Fichiers créés

**1. Plugin JavaScript** : `Plugins/WebGL/WebGLScrollBridge.jslib`
```javascript
mergeInto(LibraryManager.library, {
    InitWebGLScrollListener: function(gameObjectNamePtr, callbackNamePtr) {
        var gameObjectName = UTF8ToString(gameObjectNamePtr);
        var callbackName = UTF8ToString(callbackNamePtr);
        
        // Trouver le canvas Unity
        var canvas = document.getElementById("unity-canvas") || 
                     document.querySelector("canvas");
        
        // Handler pour les événements wheel
        var wheelHandler = function(e) {
            e.preventDefault();
            e.stopPropagation();
            
            var deltaY = e.deltaY;
            
            try {
                SendMessage(gameObjectName, callbackName, deltaY);
            } catch (err) {
                console.error("[WebGLScrollBridge] Erreur SendMessage:", err);
            }
            
            return false;
        };
        
        // Installer listeners sur plusieurs niveaux
        canvas.addEventListener('wheel', wheelHandler, { passive: false, capture: true });
        document.addEventListener('wheel', wheelHandler, { passive: false, capture: true });
        window.addEventListener('wheel', wheelHandler, { passive: false, capture: true });
        
        canvas.focus();
        console.log("[WebGLScrollBridge] ✅ Scroll handler installé");
    }
});
```

**2. Handler C#** : `Scripts/WebGLScrollHandler.cs`
```csharp
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

[RequireComponent(typeof(ScrollRect))]
public class WebGLScrollHandler : MonoBehaviour
{
#if UNITY_WEBGL && !UNITY_EDITOR
    [DllImport("__Internal")]
    private static extern void InitWebGLScrollListener(string gameObjectName, string callbackName);
#endif

    private ScrollRect scrollRect;
    public float scrollSpeed = 1000f; // Plus c'est élevé, plus c'est lent

    void Start()
    {
        scrollRect = GetComponent<ScrollRect>();
        
#if UNITY_WEBGL && !UNITY_EDITOR
        InitWebGLScrollListener(gameObject.name, "OnWebGLScroll");
#endif
    }

    // Appelé par JavaScript
    public void OnWebGLScroll(float deltaY)
    {
        if (scrollRect == null) return;
        
        float scrollDelta = -deltaY / scrollSpeed;
        float newPosition = Mathf.Clamp01(scrollRect.verticalNormalizedPosition + scrollDelta);
        scrollRect.verticalNormalizedPosition = newPosition;
    }
}
```

#### Intégration

Dans votre script de configuration du ScrollRect (ex: `MainMenuManager.cs`, `QuetesTab.cs`) :

```csharp
// Après avoir configuré le ScrollRect
#if UNITY_WEBGL && !UNITY_EDITOR
if (scrollRect.GetComponent<WebGLScrollHandler>() == null)
{
    var handler = scrollRect.gameObject.AddComponent<WebGLScrollHandler>();
    handler.scrollSpeed = 1000f; // Ajuster selon vos besoins
}
#endif
```

#### Ajustement de la sensibilité

- `scrollSpeed = 1000` → Scroll fluide, lent (RECOMMANDÉ pour molette)
- `scrollSpeed = 800` → Scroll un peu plus rapide (bon pour listes courtes)
- `scrollSpeed = 500` → Scroll rapide (risque de sauts)

**Formule** : `scrollDelta = deltaY / scrollSpeed`
- Plus `scrollSpeed` est élevé, plus le scroll est lent et précis

#### Résultat

✅ Molette de souris fonctionne parfaitement  
✅ Trackpad Mac (geste à deux doigts) fonctionne parfaitement  
✅ Fonctionne même si les enfants bloquent les raycasts  
✅ Performances excellentes (pas d'appel Update())  

#### Où appliquer

✅ **Déjà appliqué** (2025-01-06) :
- [x] `MainMenuManager.cs` - Mosaïque de jeux (scrollSpeed = 1000)
- [x] `QuetesTab.cs` - Liste des quêtes (scrollSpeed = 800)

À appliquer si besoin :
- [ ] `ScoresTab.cs` - Liste des scores (si ScrollRect ajouté)
- [ ] `ClassementTab.cs` - Liste du classement (si ScrollRect ajouté)
- [ ] Tout autre ScrollRect dans des panels UI

---

### Solution 2 : Raycast Target (Ancienne méthode - OBSOLÈTE)

#### 1. Image transparente sur le Viewport
Dans `MainMenuManager.cs`, ajout d'une Image transparente sur le viewport avec `raycastTarget = true` :
```csharp
if (viewport != null)
{
    Image viewportImage = viewport.GetComponent<Image>();
    if (viewportImage == null)
    {
        viewportImage = viewport.gameObject.AddComponent<Image>();
    }
    viewportImage.color = new Color(0, 0, 0, 0); // Transparent
    viewportImage.raycastTarget = true; // ✅ CRUCIAL pour capturer le scroll
}
```

Cette Image transparente **capture** les événements de scroll (molette + trackpad) et les transmet au ScrollRect.

#### 2. Désactivation du raycast sur les cartes
Les Images de fond des cartes de jeu ont maintenant `raycastTarget = false` :
```csharp
Image bgImage = cardObj.AddComponent<Image>();
bgImage.color = GetTypeColor(entry);
bgImage.raycastTarget = false; // ✅ Ne bloque pas le scroll
```

Les boutons JOUER et ADMIN sur les cartes ont leur propre `raycastTarget`, donc ils restent cliquables.

#### 3. Sensibilité augmentée en WebGL
```csharp
#if UNITY_WEBGL && !UNITY_EDITOR
    scrollRect.scrollSensitivity = 50f; // Double sensibilité
#endif
```

### Résultat
✅ Molette de souris fonctionne  
✅ Trackpad Mac (geste à deux doigts) fonctionne  
✅ Les boutons sur les cartes restent cliquables  

### Appliquer à d'autres ScrollRect

#### Checklist pour activer le scroll en WebGL
1. **Viewport doit avoir une Image transparente avec raycastTarget = true**
   ```csharp
   Image viewportImage = viewport.AddComponent<Image>();
   viewportImage.color = new Color(0, 0, 0, 0);
   viewportImage.raycastTarget = true;
   ```

2. **Les éléments enfants (cartes, items) ne doivent PAS bloquer le scroll**
   - Mettre `raycastTarget = false` sur les Images de fond
   - Les boutons/éléments interactifs gardent leur propre `raycastTarget = true`

3. **Augmenter la sensibilité pour WebGL (optionnel)**
   ```csharp
   #if UNITY_WEBGL && !UNITY_EDITOR
       scrollRect.scrollSensitivity = 50f;
   #endif
   ```

4. **Vérifier qu'il y a un EventSystem dans la scène**
   - Nécessaire pour tous les événements UI
   - MainMenuManager le crée automatiquement si absent

### Debug
Si le scroll ne fonctionne toujours pas :
1. **Vérifier la hiérarchie** : Viewport → Content → Items
2. **Inspector du Viewport** : Doit avoir `Image` avec `Raycast Target` coché
3. **Inspector des Items** : Les Images de fond doivent avoir `Raycast Target` décoché
4. **Console navigateur** : Chercher des erreurs JavaScript
5. **Tester avec F12** : Vérifier que les événements `wheel` sont bien reçus

---

**Dernière mise à jour** : 2025-01-06  
**Auteur** : Assistant IA (correction bug OOM + amélioration trackpad)
