refactor: migrate graph canvas to use web worker for force layout computation
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user