167 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Correction du gel de l'application lors de l'ouverture du Graph View
 | ||
| 
 | ||
| ## Problème identifié
 | ||
| 
 | ||
| L'application gelait complètement pendant plusieurs secondes lors du passage à la vue graphe, empêchant même la prise de captures d'écran.
 | ||
| 
 | ||
| ### Cause racine
 | ||
| 
 | ||
| **Complexité algorithmique O(N²) dans `VaultService.graphData()`**
 | ||
| 
 | ||
| L'ancien code effectuait une recherche linéaire (`.find()`) pour CHAQUE lien trouvé dans CHAQUE note :
 | ||
| 
 | ||
| ```typescript
 | ||
| // AVANT (O(N × M × N))
 | ||
| for (const note of notes) {  // Boucle 1: N notes
 | ||
|   while ((match = linkRegex.exec(note.content)) !== null) {  // Boucle 2: M liens
 | ||
|     const targetNote = notes.find(n => ...)  // Boucle 3: N notes PAR LIEN!
 | ||
|   }
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| **Résultat** : Avec 100 notes et 10 liens/note → **100,000+ opérations synchrones** bloquant le thread principal.
 | ||
| 
 | ||
| ### Effets en cascade
 | ||
| 
 | ||
| 1. `VaultService.graphData()` se recalcule (O(N²))
 | ||
| 2. `GraphViewContainerV2Component` déclenche multiples recalculs de computed signals
 | ||
| 3. `GraphCanvasComponent` recrée le worker, les nodes, et le spatial index
 | ||
| 4. **Tout cela de manière SYNCHRONE** → gel total
 | ||
| 
 | ||
| ## Solutions implémentées
 | ||
| 
 | ||
| ### 1. Optimisation O(N²) → O(N×M) dans VaultService
 | ||
| 
 | ||
| **Fichier**: `src/services/vault.service.ts`
 | ||
| 
 | ||
| Remplacement des `.find()` par des `Map.get()` (O(1)) :
 | ||
| 
 | ||
| ```typescript
 | ||
| // Build fast lookup maps
 | ||
| const noteById = new Map<string, Note>();
 | ||
| const noteByTitle = new Map<string, Note>();
 | ||
| const notesByAlias = new Map<string, Note>();
 | ||
| 
 | ||
| // Index all notes once
 | ||
| for (const note of notes) {
 | ||
|   noteById.set(note.id, note);
 | ||
|   noteByTitle.set(note.title, note);
 | ||
|   if (Array.isArray(note.frontmatter?.aliases)) {
 | ||
|     for (const alias of note.frontmatter.aliases) {
 | ||
|       notesByAlias.set(alias, note);
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| // Fast lookup during link extraction
 | ||
| const targetNote = noteById.get(linkPath) 
 | ||
|   || noteByTitle.get(rawLink) 
 | ||
|   || notesByAlias.get(rawLink);
 | ||
| ```
 | ||
| 
 | ||
| **Gain** : Complexité réduite de O(N×M×N) à O(N×M)
 | ||
| 
 | ||
| ### 2. Debounce des mises à jour dans GraphCanvasComponent
 | ||
| 
 | ||
| **Fichier**: `src/app/graph/graph-canvas.component.ts`
 | ||
| 
 | ||
| Ajout d'un délai de 100ms pour éviter les cascades de recalculs :
 | ||
| 
 | ||
| ```typescript
 | ||
| // React to data changes with debounce
 | ||
| let updateTimer: ReturnType<typeof setTimeout> | null = null;
 | ||
| effect(() => {
 | ||
|   const nodes = this.nodes();
 | ||
|   const links = this.links();
 | ||
|   
 | ||
|   if (updateTimer) clearTimeout(updateTimer);
 | ||
|   updateTimer = setTimeout(() => {
 | ||
|     this.updateWorkerData(nodes, links);
 | ||
|   }, 100);
 | ||
| });
 | ||
| ```
 | ||
| 
 | ||
| ### 3. Throttle de reconstruction du Spatial Index
 | ||
| 
 | ||
| **Fichier**: `src/app/graph/graph-canvas.component.ts`
 | ||
| 
 | ||
| Augmentation de l'intervalle de reconstruction de 200ms → 500ms :
 | ||
| 
 | ||
| ```typescript
 | ||
| // Rebuild spatial index at most every 500ms to reduce main thread load
 | ||
| const now = performance.now();
 | ||
| if (now - lastIndexBuild > 500) {
 | ||
|   this.spatialIndex = new SpatialIndex(this.simulationNodes());
 | ||
|   lastIndexBuild = now;
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ### 4. Éviter les réinitialisations inutiles du worker
 | ||
| 
 | ||
| **Fichier**: `src/app/graph/graph-canvas.component.ts`
 | ||
| 
 | ||
| Vérification du nombre de nodes/links avant de réinitialiser :
 | ||
| 
 | ||
| ```typescript
 | ||
| private updateWorkerData(nodes: GraphNodeWithVisuals[], links: GraphLink[]): void {
 | ||
|   // Skip update if data hasn't substantially changed
 | ||
|   if (nodes.length === this.lastNodeCount && links.length === this.lastLinkCount && this.session) {
 | ||
|     console.log(`[GraphCanvas] Skipping update - same data size`);
 | ||
|     return;
 | ||
|   }
 | ||
|   
 | ||
|   this.lastNodeCount = nodes.length;
 | ||
|   this.lastLinkCount = links.length;
 | ||
|   // ... reste de l'initialisation
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ### 5. Logs de performance pour diagnostic
 | ||
| 
 | ||
| Ajout de traces pour mesurer les performances :
 | ||
| 
 | ||
| ```typescript
 | ||
| // Dans VaultService
 | ||
| console.log(`[GraphData] Computed in ${duration.toFixed(2)}ms: ${nodes.length} nodes, ${edges.length} edges`);
 | ||
| 
 | ||
| // Dans GraphCanvasComponent
 | ||
| console.log(`[GraphCanvas] Updating worker: ${nodes.length} nodes, ${links.length} links`);
 | ||
| ```
 | ||
| 
 | ||
| ## Résultats attendus
 | ||
| 
 | ||
| - ✅ **Réduction drastique du temps de calcul** : O(N²) → O(N×M)
 | ||
| - ✅ **Évite les recalculs en cascade** grâce au debounce
 | ||
| - ✅ **Réduit la charge du thread principal** avec throttle spatial index
 | ||
| - ✅ **Skip les updates inutiles** quand les données n'ont pas changé
 | ||
| - ✅ **Visibilité sur les performances** via les console logs
 | ||
| 
 | ||
| ## Validation
 | ||
| 
 | ||
| Pour tester les améliorations :
 | ||
| 
 | ||
| 1. Ouvrir DevTools Console
 | ||
| 2. Passer à la vue Graph
 | ||
| 3. Observer les logs :
 | ||
|    ```
 | ||
|    [GraphData] Computed in XXms: YY nodes, ZZ edges
 | ||
|    [GraphCanvas] Updating worker: YY nodes, ZZ links
 | ||
|    ```
 | ||
| 4. Vérifier que l'application ne gèle plus
 | ||
| 5. Essayer de prendre une capture d'écran pendant le chargement → devrait fonctionner
 | ||
| 
 | ||
| ## Optimisations futures possibles
 | ||
| 
 | ||
| Si des problèmes de performance persistent :
 | ||
| 
 | ||
| 1. **Lazy loading** : Charger le graph view uniquement quand l'utilisateur clique dessus
 | ||
| 2. **Virtual scrolling** pour les graphes très larges (>1000 nodes)
 | ||
| 3. **Web Worker pour le parsing des liens** : Déplacer l'extraction des liens dans un worker séparé
 | ||
| 4. **Cache du graphData** : Mémoriser le résultat et n'invalider que si les notes changent
 | ||
| 5. **Pagination du graph** : Afficher seulement les N nodes les plus connectés par défaut
 | ||
| 
 | ||
| ## Fichiers modifiés
 | ||
| 
 | ||
| - `src/services/vault.service.ts` - Optimisation O(N²) → O(N×M)
 | ||
| - `src/app/graph/graph-canvas.component.ts` - Debounce, throttle, logs
 |