# Audit PMO Control - Optimisation Playlist OpenHome ## Résumé Exécutif L'utilisateur rapporte des lenteurs significatives lors de la manipulation de playlists de ~1000 titres avec les renderers OpenHome. Les renderers Chromecast et UPnP (avec queue interne) ne sont pas affectés. ## État des Optimisations Deja Implémentées Le precedent plan dans `Blackboard/Todo/enorme_playlist.md` a deja été partiellement implémenté: | optimisation | Statut | Emplacement | |-------------|-------|------------| | MAX_BATCH = 256 pour ReadList | ✅ FAIT | `openhome.rs:1015` | | Élimination double queue_snapshot() | ✅ FAIT | `replace_queue_with_pivot()` et `replace_queue_standard_lcs()` | | LCS préfixe/suffixe (lcs_flags_optimized) | ✅ FAIT | `openhome.rs:869` | | Polling adaptatif (is_active) | ✅ FAIT | `musicrenderer.rs:325-374` | | Consolidation invalidation caches | ✅ FAIT | `openhome.rs:222-235` | ## Contraintes Protocolaires Découvertes L'action `Insert` **ne supporte PAS l'insertion par lot** - chaque appel prend un seul Uri/Metadata. ## Nouvelles Optimisations (Contre-propositions Utilisateur) ### OPT-1: Fast Path pour 99% des cas de sync_queue **Observation**: 99% des changements de playlist sont: - Insertion de nouvelles tracks en **fin de queue** - Délétion de tracks en **début de queue** - Rarement des changements nécessitant un vrai alignement LCS **Solution**: Ajouter une détection de pattern avant d'appeler LCS: ```rust fn smart_sync(&mut self, items: Vec) -> Result<(), ControlPointError> { let current_ids = self.track_ids()?; // Cas 1: Append only (insertion en fin) if items.starts_with(¤t_ids) { // Fast path: juste ajouter les nouveaux items return self.append_only(items.skip(current_ids.len())); } // Cas 2: Delete from beginning if current_ids.starts_with(&items) { // Fast path: supprimer de la fin return self.delete_from_beginning(current_ids.len() - items.len()); } // Cas 3: Full LCS only for complex reorderings return self.replace_queue_standard_lcs(items); } ``` **Impact**: 99% des sync_queue passent de O(N²) à O(N) --- ### OPT-2: Queue FIFO pour Opérations OpenHome (Thread Background) **Concept**: Une file d'attente FIFO des opérations SOAP exécutée dans un thread dédié. ```rust pub struct OpenHomeOpQueue { queue: Arc>>, worker_handle: Option>, } pub enum OpenHomeOp { Insert { uri: String, metadata: String, after_id: u32 }, Delete { track_id: u32 }, DeleteAll, SeekId { id: u32 }, Play, Pause, Stop, SetVolume { volume: u16 }, // Meta operations UpdateMetadata { track_id: u32, metadata: String }, } impl OpenHomeOpQueue { /// Push operation to the FIFO queue pub fn push(&self, op: OpenHomeOp) { self.queue.lock().unwrap().push(op); } /// Push with priority (volume, play, stop - need fast response) pub fn push_first(&self, op: OpenHomeOp) { self.queue.lock().unwrap().push_front(op); } /// Clear all pending operations (client can flush) pub fn clear(&self) { self.queue.lock().unwrap().clear(); } /// Worker thread consumes operations fn worker_loop(&self) { loop { let op = self.queue.lock().unwrap().pop_front(); match op { Some(op) => self.execute(op), None => thread::sleep(Duration::from_millis(10)), } } } } ``` **Benefits**: - UI non-bloquante (les operations sont lancées et exec en background) - Batching naturel (plusieurs operations sont executes en sequence) - Priorité via `push_first()` pour play/stop/volume - `clear()` permet d'annuler les operations en attente (ex: playlist changée) **Implémentation suggérée**: 1. Créer `src/queue/openhome_op_queue.rs` avec la structure 2. Intégrer dans `OpenHomeQueue` ou `OpenHomeRenderer` 3. Thread de worker lancé au démarrage du control point --- ### OPT-3: Métadonnées en Tâche de Fond **Observation**: L'appli utilise-t-elle vraiment les métadonnées de la queue OpenHome, ou un cache local? Si le control-point maintient son propre cache (plus probable): - Les mises à jour de métadonnées peuvent être traitées en background - Pas besoin de sync immédiate des métadonnées **Solution**: Queue séparée pour les operations de métadonnées: ```rust // Haute priorité (opérations critiques) let high_priority_queue: OpenHomeOpQueue; // Basse priorité (métadonnées) let metadata_queue: OpenHomeOpQueue; ``` **Implémentation**: 1. Séparer les operations critiques (play/stop/seek/volume) de metadata 2. Metadata update traités en background avec délais 3. Le cache local du control-point est mis à jour indépendamment --- ### OPT-4: Connection Pooling HTTP Chaque appel SOAP crée une nouvelle connexion. Avec 1000 insertions: - Overhead TCP: ~10-50ms par appel - Total: 10-50 secondes overhead réseau **Solution**: Agent HTTP static avec connection reuse: ```rust // soap_client.rs static HTTP_AGENT: Lazy = Lazy::new(|| { Agent::config_builder() .timeout_global(Some(Duration::from_secs(30))) .build() }); ``` --- ## Plan d'Implémentation Proposé ### Phase 1: Fast Path LCS (Priorité Haute) 1. Ajouter `detect_sync_pattern()` dans `openhome.rs` 2. Implémenter `append_only()` et `delete_from_beginning()` 3. Tester avec playlists réelles ### Phase 2: Queue FIFO Opérations (Priorité Haute) 1. Créer `src/queue/openhome_op_queue.rs` 2. Implémenter `push()`, `push_first()`, `clear()` 3. Thread worker avec loop de consommation 4. Intégrer dans `OpenHomeRenderer` ### Phase 3: Séparation Métadonnées (Priorité Moyenne) 1. Créer queue séparée pour metadata 2. Implémenter batch processing ### Phase 4: Connection Pooling (Priorité Basse) 1. Modifier `soap_client.rs` pour agent static --- ## Questions pour Clarification 1. **Cache Métadonnées**: Le control-point utilise-t-il vraiment les métadonnées de la queue OpenHome, ou maintient-il son propre cache qui est alimenté indépendamment? 2. **Priorité des Opérations**: Pour `push_first()`, quelles opérations nécessitent une réponse rapide? - Volume (immédiat) - Play/Pause/Stop (immédiat) - Seek (rapide) - Insert (peut être différé) 3. **Comportement en cas de conflit**: Si le client fait `clear()` et que le worker est en train d'exécuter une opération: - Annuler l'opération en cours? ( risky - peut laisser le renderer dans un état inconsistent) - Laisser finir l'opération en cours? (plus sur) --- ## Tests Recommandés ```bash # Compiler cargo build -p pmocontrol # Benchmark LCS fast paths # - Cas: append 100 tracks to 900 = O(N) # - Cas: delete 100 from 900 = O(N) # - Cas: reorder = O(N²) avec LCS ``` --- *Plan mis à jour avec contre-propositions utilisateur* *Date: 2026-04-08*