ObsiViewer/docs/GRAPH/GRAPH_FREEZE_FIX.md

167 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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