Skip to main content

oxigdal_wasm/
streaming.rs

1//! Tile streaming and progressive loading
2//!
3//! This module provides advanced streaming capabilities for loading geospatial tiles
4//! progressively, managing bandwidth, implementing adaptive quality, and providing
5//! smooth user experience even with slow or unreliable network connections.
6//!
7//! # Overview
8//!
9//! The streaming module implements intelligent tile loading strategies:
10//!
11//! - **Adaptive Quality**: Automatically adjusts quality based on bandwidth
12//! - **Bandwidth Estimation**: Tracks download speed and adjusts behavior
13//! - **Progressive Loading**: Shows low-res immediately, enhances gradually
14//! - **Priority-Based Loading**: Loads visible tiles before off-screen tiles
15//! - **Stream Buffering**: Maintains buffer of recently loaded tiles
16//! - **Prefetch Scheduling**: Intelligently prefetches likely-needed tiles
17//! - **Multi-Resolution**: Supports loading multiple quality levels simultaneously
18//!
19//! # Adaptive Quality System
20//!
21//! Quality automatically adjusts based on network conditions:
22//!
23//! ```text
24//! Bandwidth           Quality    Tile Size    Load Time
25//! ────────────────────────────────────────────────────
26//! < 2 Mbps           Low        128x128      Fast
27//! 2-10 Mbps          Medium     256x256      Moderate
28//! > 10 Mbps          High       512x512      Slow
29//! Variable           Adaptive   Dynamic      Reactive
30//! ```
31//!
32//! # Bandwidth Estimation
33//!
34//! The module continuously measures download performance:
35//!
36//! 1. **Track Transfers**: Record size and duration for each download
37//! 2. **Calculate Average**: Compute rolling average over recent samples
38//! 3. **Estimate Time**: Predict download time for future requests
39//! 4. **Adjust Quality**: Lower quality if bandwidth drops
40//!
41//! ```rust
42//! use oxigdal_wasm::BandwidthEstimator;
43//!
44//! let mut estimator = BandwidthEstimator::new();
45//!
46//! // Record downloads
47//! estimator.record_transfer(262_144, 500.0); // 256KB in 500ms
48//!
49//! // Check bandwidth
50//! let mbps = estimator.bandwidth_mbps();
51//! println!("Current bandwidth: {:.2} Mbps", mbps);
52//!
53//! // Get quality suggestion
54//! let quality = estimator.suggest_quality();
55//! println!("Suggested quality: {:?}", quality);
56//! ```
57//!
58//! # Progressive Loading Strategy
59//!
60//! Load tiles in order of importance:
61//!
62//! ```text
63//! Priority  What                 When            Why
64//! ──────────────────────────────────────────────────────
65//! 1         Viewport center      Immediate       User focus
66//! 2         Viewport edges       0-100ms         Nearby
67//! 3         Adjacent tiles       100-500ms       Panning
68//! 4         Parent tiles         500-1000ms      Zoom out
69//! 5         Child tiles          1000-2000ms     Zoom in
70//! 6         Distant tiles        2000ms+         Prefetch
71//! ```
72//!
73//! # Stream Buffer Management
74//!
75//! The stream buffer acts as an LRU cache:
76//!
77//! ```ignore
78//! use oxigdal_wasm::streaming::StreamBuffer;
79//!
80//! let mut buffer = StreamBuffer::new(50 * 1024 * 1024); // 50 MB
81//!
82//! // Add tiles to buffer
83//! buffer.add(coord, tile_data)?;
84//!
85//! // Retrieve from buffer
86//! if let Some(data) = buffer.get(&coord) {
87//!     render_tile(data);
88//! }
89//!
90//! // Check statistics
91//! let stats = buffer.stats();
92//! println!("Buffer: {} tiles, {:.1}% full",
93//!     stats.tile_count,
94//!     stats.utilization * 100.0
95//! );
96//! ```
97//!
98//! # Importance-Based Loading
99//!
100//! Tiles closer to viewport center load first:
101//!
102//! ```ignore
103//! use oxigdal_wasm::streaming::ImportanceCalculator;
104//!
105//! let calc = ImportanceCalculator::new(
106//!     (viewport_center_x, viewport_center_y),
107//!     (viewport_width, viewport_height)
108//! );
109//!
110//! let mut tiles = vec![
111//!     TileCoord::new(0, 10, 10),  // Center
112//!     TileCoord::new(0, 15, 15),  // Far
113//!     TileCoord::new(0, 11, 10),  // Near
114//! ];
115//!
116//! // Sort by importance (center first)
117//! calc.sort_by_importance(&mut tiles);
118//!
119//! // Load in order
120//! for tile in tiles {
121//!     load_tile(tile).await;
122//! }
123//! ```
124//!
125//! # Complete Streaming Example
126//!
127//! ```ignore
128//! use oxigdal_wasm::streaming::{TileStreamer, StreamingQuality};
129//!
130//! // Create streamer with 50 MB buffer
131//! let mut streamer = TileStreamer::new(50);
132//!
133//! // Enable adaptive quality
134//! streamer.set_quality(StreamingQuality::Adaptive);
135//!
136//! // Request visible tiles
137//! for coord in visible_tiles {
138//!     streamer.request_tile(coord, timestamp);
139//! }
140//!
141//! // Simulate tile load
142//! async fn load_tiles(streamer: &mut TileStreamer) {
143//!     while let Some(coord) = next_tile_to_load() {
144//!         let start = js_sys::Date::now();
145//!         let data = fetch_tile(coord).await?;
146//!         let elapsed = js_sys::Date::now() - start;
147//!
148//!         // Complete the load (updates bandwidth estimate)
149//!         streamer.complete_tile(
150//!             coord,
151//!             data,
152//!             elapsed,
153//!             start + elapsed
154//!         )?;
155//!     }
156//! }
157//!
158//! // Check streaming stats
159//! let stats = streamer.stats();
160//! println!("Quality: {:?}", stats.current_quality);
161//! println!("Bandwidth: {:.2} Mbps", stats.bandwidth_mbps);
162//! println!("Pending: {}", stats.pending_tiles);
163//! ```
164//!
165//! # Multi-Resolution Streaming
166//!
167//! Load multiple quality levels simultaneously:
168//!
169//! ```ignore
170//! use oxigdal_wasm::streaming::MultiResolutionStreamer;
171//!
172//! let mut mstreamer = MultiResolutionStreamer::new();
173//!
174//! // Add streamers for different resolutions
175//! mstreamer.add_resolution(128, 20);  // Low-res, 20MB buffer
176//! mstreamer.add_resolution(256, 50);  // Mid-res, 50MB buffer
177//! mstreamer.add_resolution(512, 100); // High-res, 100MB buffer
178//!
179//! // Request at appropriate resolution
180//! if bandwidth < 2.0 {
181//!     mstreamer.request_tile(128, coord, timestamp);
182//! } else if bandwidth < 10.0 {
183//!     mstreamer.request_tile(256, coord, timestamp);
184//! } else {
185//!     mstreamer.request_tile(512, coord, timestamp);
186//! }
187//! ```
188//!
189//! # Prefetch Scheduling
190//!
191//! Intelligently schedule background tile loading:
192//!
193//! ```ignore
194//! use oxigdal_wasm::streaming::{PrefetchScheduler, RequestPriority};
195//!
196//! let mut scheduler = PrefetchScheduler::new(4); // 4 concurrent prefetches
197//!
198//! // Schedule high-priority tiles (visible)
199//! for coord in visible_tiles {
200//!     scheduler.schedule(coord, RequestPriority::High);
201//! }
202//!
203//! // Schedule low-priority tiles (prefetch)
204//! for coord in adjacent_tiles {
205//!     scheduler.schedule(coord, RequestPriority::Low);
206//! }
207//!
208//! // Process next batch
209//! while let Some(coord) = scheduler.next(timestamp) {
210//!     tokio::spawn(async move {
211//!         load_and_cache_tile(coord).await;
212//!         scheduler.complete(coord);
213//!     });
214//! }
215//! ```
216//!
217//! # Performance Characteristics
218//!
219//! ## Bandwidth Estimation
220//! - Sample window: 20 recent transfers
221//! - Update frequency: Every transfer
222//! - Accuracy: ±10% typical
223//! - Latency: Adapts within 5-10 transfers
224//!
225//! ## Buffer Management
226//! - Lookup: O(1) average
227//! - Insert: O(1) average
228//! - Eviction: O(1)
229//! - Overhead: ~100 bytes per tile + data
230//!
231//! ## Importance Calculation
232//! - Euclidean distance from viewport center
233//! - Normalized to [0, 1] range
234//! - Sorting: O(n log n)
235//! - Typical: < 1ms for 1000 tiles
236//!
237//! # Best Practices
238//!
239//! 1. **Start Low**: Begin with low quality for quick feedback
240//! 2. **Enhance Progressively**: Load higher quality incrementally
241//! 3. **Monitor Bandwidth**: Track and react to changes
242//! 4. **Prioritize Visible**: Always load visible tiles first
243//! 5. **Limit Prefetch**: Don't prefetch more than 2-3 levels away
244//! 6. **Clean Up**: Remove old tiles from buffer periodically
245//! 7. **Handle Errors**: Network can fail, always have fallbacks
246//! 8. **Test Mobile**: Mobile bandwidth varies greatly
247//! 9. **Consider Latency**: High latency hurts more than low bandwidth
248//! 10. **Profile Real Users**: Test on actual user connections
249//!
250//! # Common Patterns
251//!
252//! ## Pattern: Smooth Zooming
253//! Load intermediate levels during zoom:
254//! ```ignore
255//! async fn zoom_smoothly(target_level: u32) {
256//!     // Load each intermediate level
257//!     for level in current_level..=target_level {
258//!         load_level(level).await;
259//!         render();
260//!         await next_frame();
261//!     }
262//! }
263//! ```
264//!
265//! ## Pattern: Bandwidth-Adaptive Quality
266//! Adjust quality based on measured performance:
267//! ```ignore
268//! fn update_quality(streamer: &mut TileStreamer) {
269//!     let stats = streamer.stats();
270//!
271//!     match stats.bandwidth_mbps {
272//!         bw if bw < 1.0 => streamer.set_quality(StreamingQuality::Low),
273//!         bw if bw < 5.0 => streamer.set_quality(StreamingQuality::Medium),
274//!         _ => streamer.set_quality(StreamingQuality::High),
275//!     }
276//! }
277//! ```
278//!
279//! ## Pattern: Predictive Prefetching
280//! Prefetch based on pan direction:
281//! ```ignore
282//! fn prefetch_ahead(pan_vector: (f64, f64)) {
283//!     let (dx, dy) = pan_vector;
284//!
285//!     // Prefetch 3 tiles ahead in pan direction
286//!     for i in 1..=3 {
287//!         let x = current_x + (dx * i as f64) as i32;
288//!         let y = current_y + (dy * i as f64) as i32;
289//!         schedule_prefetch(TileCoord::new(level, x, y));
290//!     }
291//! }
292//! ```
293//!
294//! # Troubleshooting
295//!
296//! ## Slow Initial Load
297//! - Reduce initial quality
298//! - Increase buffer size
299//! - Enable CDN caching
300//! - Optimize server response time
301//!
302//! ## Stuttering During Pan
303//! - Increase prefetch radius
304//! - Lower quality during motion
305//! - Use double buffering
306//! - Preload adjacent tiles
307//!
308//! ## High Memory Usage
309//! - Reduce buffer size
310//! - Enable compression
311//! - Clear old tiles more aggressively
312//! - Monitor with profiler
313//!
314//! ## Bandwidth Estimation Inaccurate
315//! - Increase sample window
316//! - Filter outliers
317//! - Account for CDN caching
318//! - Consider connection type detection
319
320use serde::{Deserialize, Serialize};
321use std::collections::{HashMap, VecDeque};
322
323use crate::error::{WasmError, WasmResult};
324use crate::fetch::RequestPriority;
325use crate::tile::TileCoord;
326
327/// Streaming quality level
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
329pub enum StreamingQuality {
330    /// Low quality (fast loading)
331    Low,
332    /// Medium quality (balanced)
333    Medium,
334    /// High quality (slow loading)
335    High,
336    /// Adaptive (adjusts based on connection)
337    Adaptive,
338}
339
340impl StreamingQuality {
341    /// Returns the quality multiplier
342    pub const fn multiplier(&self) -> f64 {
343        match self {
344            Self::Low => 0.5,
345            Self::Medium => 1.0,
346            Self::High => 2.0,
347            Self::Adaptive => 1.0, // Will be adjusted dynamically
348        }
349    }
350
351    /// Returns the tile resolution for this quality
352    pub const fn resolution(&self) -> u32 {
353        match self {
354            Self::Low => 128,
355            Self::Medium => 256,
356            Self::High => 512,
357            Self::Adaptive => 256,
358        }
359    }
360}
361
362/// Bandwidth estimator
363#[derive(Debug, Clone)]
364pub struct BandwidthEstimator {
365    /// Recent transfer sizes (bytes)
366    transfer_sizes: VecDeque<usize>,
367    /// Recent transfer times (milliseconds)
368    transfer_times: VecDeque<f64>,
369    /// Maximum samples to keep
370    max_samples: usize,
371    /// Current estimated bandwidth (bytes per second)
372    estimated_bandwidth: f64,
373}
374
375impl BandwidthEstimator {
376    /// Creates a new bandwidth estimator
377    pub fn new() -> Self {
378        Self {
379            transfer_sizes: VecDeque::new(),
380            transfer_times: VecDeque::new(),
381            max_samples: 20,
382            estimated_bandwidth: 1_000_000.0, // Default: 1 MB/s
383        }
384    }
385
386    /// Records a transfer
387    pub fn record_transfer(&mut self, bytes: usize, time_ms: f64) {
388        self.transfer_sizes.push_back(bytes);
389        self.transfer_times.push_back(time_ms);
390
391        if self.transfer_sizes.len() > self.max_samples {
392            self.transfer_sizes.pop_front();
393            self.transfer_times.pop_front();
394        }
395
396        self.update_estimate();
397    }
398
399    /// Records a download (alias for record_transfer for test compatibility)
400    pub fn record_download(&mut self, bytes: usize, time_ms: f64) {
401        self.record_transfer(bytes, time_ms);
402    }
403
404    /// Estimates current bandwidth (returns bandwidth in bytes per second)
405    pub const fn estimate(&self) -> f64 {
406        self.estimated_bandwidth
407    }
408
409    /// Updates the bandwidth estimate
410    fn update_estimate(&mut self) {
411        if self.transfer_sizes.is_empty() || self.transfer_times.is_empty() {
412            return;
413        }
414
415        let total_bytes: usize = self.transfer_sizes.iter().sum();
416        let total_time: f64 = self.transfer_times.iter().sum();
417
418        if total_time > 0.0 {
419            // Convert to bytes per second
420            self.estimated_bandwidth = (total_bytes as f64 / total_time) * 1000.0;
421        }
422    }
423
424    /// Returns the current bandwidth estimate in bytes per second
425    pub const fn bandwidth_bps(&self) -> f64 {
426        self.estimated_bandwidth
427    }
428
429    /// Returns the current bandwidth estimate in megabits per second
430    pub fn bandwidth_mbps(&self) -> f64 {
431        (self.estimated_bandwidth * 8.0) / 1_000_000.0
432    }
433
434    /// Estimates time to download a given size (in milliseconds)
435    pub fn estimate_download_time(&self, bytes: usize) -> f64 {
436        if self.estimated_bandwidth > 0.0 {
437            (bytes as f64 / self.estimated_bandwidth) * 1000.0
438        } else {
439            f64::MAX
440        }
441    }
442
443    /// Checks if bandwidth is sufficient for quality
444    pub fn is_sufficient_for_quality(&self, quality: StreamingQuality) -> bool {
445        let required_bps = match quality {
446            StreamingQuality::Low => 500_000.0,      // 500 KB/s
447            StreamingQuality::Medium => 1_000_000.0, // 1 MB/s
448            StreamingQuality::High => 5_000_000.0,   // 5 MB/s
449            StreamingQuality::Adaptive => 0.0,       // Always sufficient
450        };
451
452        self.estimated_bandwidth >= required_bps
453    }
454
455    /// Suggests optimal quality based on bandwidth
456    pub fn suggest_quality(&self) -> StreamingQuality {
457        let mbps = self.bandwidth_mbps();
458
459        if mbps < 2.0 {
460            StreamingQuality::Low
461        } else if mbps < 10.0 {
462            StreamingQuality::Medium
463        } else {
464            StreamingQuality::High
465        }
466    }
467}
468
469impl Default for BandwidthEstimator {
470    fn default() -> Self {
471        Self::new()
472    }
473}
474
475/// Quality adapter for dynamic quality adjustment based on bandwidth
476#[derive(Debug, Clone)]
477pub struct QualityAdapter {
478    /// Bandwidth estimator
479    estimator: BandwidthEstimator,
480    /// Current quality level
481    current_quality: StreamingQuality,
482    /// Quality change hysteresis to prevent oscillation
483    hysteresis_count: usize,
484    /// Number of consecutive samples before changing quality
485    hysteresis_threshold: usize,
486}
487
488impl QualityAdapter {
489    /// Creates a new quality adapter
490    pub fn new() -> Self {
491        Self {
492            estimator: BandwidthEstimator::new(),
493            current_quality: StreamingQuality::Medium,
494            hysteresis_count: 0,
495            hysteresis_threshold: 3,
496        }
497    }
498
499    /// Updates bandwidth measurement
500    pub fn update_bandwidth(&mut self, bandwidth_bps: f64, _timestamp: f64) {
501        // Simulate a transfer to update the estimator
502        // Clamp bandwidth to prevent overflow (max 1 GB/s = 1_000_000_000 bytes/s)
503        let bandwidth_bps = bandwidth_bps.min(8_000_000_000.0); // 8 Gbps max
504        let bytes = (bandwidth_bps / 8.0) as usize; // 1 second worth of data
505        self.estimator.record_transfer(bytes, 1000.0); // 1 second = 1000ms
506
507        self.update_quality();
508    }
509
510    /// Returns the current quality level
511    pub const fn current_quality(&self) -> StreamingQuality {
512        self.current_quality
513    }
514
515    /// Updates quality level based on bandwidth with hysteresis
516    fn update_quality(&mut self) {
517        let suggested = self.estimator.suggest_quality();
518
519        if suggested != self.current_quality {
520            self.hysteresis_count += 1;
521
522            if self.hysteresis_count >= self.hysteresis_threshold {
523                self.current_quality = suggested;
524                self.hysteresis_count = 0;
525            }
526        } else {
527            self.hysteresis_count = 0;
528        }
529    }
530}
531
532impl Default for QualityAdapter {
533    fn default() -> Self {
534        Self::new()
535    }
536}
537
538/// Stream buffer for managing loaded tiles
539#[derive(Debug, Clone)]
540pub struct StreamBuffer {
541    /// Buffered tiles
542    tiles: HashMap<TileCoord, Vec<u8>>,
543    /// Buffer size limit in bytes
544    max_size: usize,
545    /// Current buffer size
546    current_size: usize,
547    /// Access order for LRU eviction
548    access_order: VecDeque<TileCoord>,
549}
550
551impl StreamBuffer {
552    /// Creates a new stream buffer
553    pub fn new(max_size: usize) -> Self {
554        Self {
555            tiles: HashMap::new(),
556            max_size,
557            current_size: 0,
558            access_order: VecDeque::new(),
559        }
560    }
561
562    /// Adds a tile to the buffer
563    pub fn add(&mut self, coord: TileCoord, data: Vec<u8>) -> WasmResult<()> {
564        let data_size = data.len();
565
566        // Evict tiles if necessary
567        while self.current_size + data_size > self.max_size && !self.access_order.is_empty() {
568            self.evict_oldest()?;
569        }
570
571        // Check if tile would fit
572        if data_size > self.max_size {
573            return Err(WasmError::OutOfMemory {
574                requested: data_size,
575                available: Some(self.max_size),
576            });
577        }
578
579        // Remove old entry if exists
580        if self.tiles.contains_key(&coord) {
581            if let Some(old_data) = self.tiles.remove(&coord) {
582                self.current_size -= old_data.len();
583            }
584            if let Some(pos) = self.access_order.iter().position(|c| *c == coord) {
585                self.access_order.remove(pos);
586            }
587        }
588
589        // Add new tile
590        self.tiles.insert(coord, data);
591        self.current_size += data_size;
592        self.access_order.push_back(coord);
593
594        Ok(())
595    }
596
597    /// Gets a tile from the buffer
598    pub fn get(&mut self, coord: &TileCoord) -> Option<&[u8]> {
599        if let Some(data) = self.tiles.get(coord) {
600            // Update access order
601            if let Some(pos) = self.access_order.iter().position(|c| c == coord) {
602                self.access_order.remove(pos);
603                self.access_order.push_back(*coord);
604            }
605
606            Some(data)
607        } else {
608            None
609        }
610    }
611
612    /// Evicts the oldest tile
613    fn evict_oldest(&mut self) -> WasmResult<()> {
614        if let Some(coord) = self.access_order.pop_front() {
615            if let Some(data) = self.tiles.remove(&coord) {
616                self.current_size -= data.len();
617            }
618        }
619
620        Ok(())
621    }
622
623    /// Checks if a tile is in the buffer
624    pub fn contains(&self, coord: &TileCoord) -> bool {
625        self.tiles.contains_key(coord)
626    }
627
628    /// Clears the buffer
629    pub fn clear(&mut self) {
630        self.tiles.clear();
631        self.access_order.clear();
632        self.current_size = 0;
633    }
634
635    /// Returns buffer statistics
636    pub fn stats(&self) -> StreamBufferStats {
637        StreamBufferStats {
638            tile_count: self.tiles.len(),
639            current_size: self.current_size,
640            max_size: self.max_size,
641            utilization: self.current_size as f64 / self.max_size as f64,
642        }
643    }
644}
645
646/// Stream buffer statistics
647#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
648pub struct StreamBufferStats {
649    /// Number of tiles in buffer
650    pub tile_count: usize,
651    /// Current buffer size in bytes
652    pub current_size: usize,
653    /// Maximum buffer size in bytes
654    pub max_size: usize,
655    /// Buffer utilization (0.0 to 1.0)
656    pub utilization: f64,
657}
658
659/// Load strategy for streaming
660#[derive(Debug, Clone, Copy, PartialEq, Eq)]
661pub enum LoadStrategy {
662    /// Load nearest tiles first
663    Nearest,
664    /// Load in spiral pattern from center
665    Spiral,
666    /// Load by importance (based on viewport)
667    Importance,
668    /// Load adaptively based on connection
669    Adaptive,
670}
671
672/// Tile importance calculator
673pub struct ImportanceCalculator {
674    /// Viewport center
675    viewport_center: (f64, f64),
676    /// Viewport size
677    viewport_size: (f64, f64),
678}
679
680impl ImportanceCalculator {
681    /// Creates a new importance calculator
682    pub const fn new(viewport_center: (f64, f64), viewport_size: (f64, f64)) -> Self {
683        Self {
684            viewport_center,
685            viewport_size,
686        }
687    }
688
689    /// Calculates importance score for a tile (0.0 to 1.0)
690    pub fn calculate(&self, coord: &TileCoord) -> f64 {
691        let tile_center_x = (f64::from(coord.x) + 0.5) * 256.0;
692        let tile_center_y = (f64::from(coord.y) + 0.5) * 256.0;
693
694        // Distance from viewport center
695        let dx = tile_center_x - self.viewport_center.0;
696        let dy = tile_center_y - self.viewport_center.1;
697        let distance = (dx * dx + dy * dy).sqrt();
698
699        // Normalize by viewport size
700        let max_distance = (self.viewport_size.0 * self.viewport_size.0
701            + self.viewport_size.1 * self.viewport_size.1)
702            .sqrt();
703
704        let normalized_distance = if max_distance > 0.0 {
705            (distance / max_distance).min(1.0)
706        } else {
707            0.0
708        };
709
710        // Importance is inverse of distance
711        1.0 - normalized_distance
712    }
713
714    /// Sorts tiles by importance
715    pub fn sort_by_importance(&self, tiles: &mut Vec<TileCoord>) {
716        tiles.sort_by(|a, b| {
717            let imp_a = self.calculate(a);
718            let imp_b = self.calculate(b);
719            imp_b
720                .partial_cmp(&imp_a)
721                .unwrap_or(std::cmp::Ordering::Equal)
722        });
723    }
724}
725
726/// Progressive tile streamer
727pub struct TileStreamer {
728    /// Stream buffer
729    buffer: StreamBuffer,
730    /// Bandwidth estimator
731    bandwidth: BandwidthEstimator,
732    /// Current quality
733    quality: StreamingQuality,
734    /// Load strategy
735    strategy: LoadStrategy,
736    /// Pending tile requests
737    pending: HashMap<TileCoord, f64>, // coord -> request_time
738    /// Completed tile loads
739    completed: HashMap<TileCoord, f64>, // coord -> completion_time
740}
741
742impl TileStreamer {
743    /// Creates a new tile streamer
744    pub fn new(buffer_size_mb: usize) -> Self {
745        Self {
746            buffer: StreamBuffer::new(buffer_size_mb * 1024 * 1024),
747            bandwidth: BandwidthEstimator::new(),
748            quality: StreamingQuality::Adaptive,
749            strategy: LoadStrategy::Adaptive,
750            pending: HashMap::new(),
751            completed: HashMap::new(),
752        }
753    }
754
755    /// Sets the streaming quality
756    pub fn set_quality(&mut self, quality: StreamingQuality) {
757        self.quality = quality;
758    }
759
760    /// Sets the load strategy
761    pub fn set_strategy(&mut self, strategy: LoadStrategy) {
762        self.strategy = strategy;
763    }
764
765    /// Requests a tile
766    pub fn request_tile(&mut self, coord: TileCoord, timestamp: f64) {
767        if !self.buffer.contains(&coord) && !self.pending.contains_key(&coord) {
768            self.pending.insert(coord, timestamp);
769        }
770    }
771
772    /// Marks a tile as completed
773    pub fn complete_tile(
774        &mut self,
775        coord: TileCoord,
776        data: Vec<u8>,
777        load_time_ms: f64,
778        timestamp: f64,
779    ) -> WasmResult<()> {
780        self.pending.remove(&coord);
781        self.completed.insert(coord, timestamp);
782        self.bandwidth.record_transfer(data.len(), load_time_ms);
783        self.buffer.add(coord, data)?;
784
785        // Adjust quality if adaptive
786        if matches!(self.quality, StreamingQuality::Adaptive) {
787            let suggested = self.bandwidth.suggest_quality();
788            self.quality = suggested;
789        }
790
791        Ok(())
792    }
793
794    /// Gets a tile from the buffer
795    pub fn get_tile(&mut self, coord: &TileCoord) -> Option<&[u8]> {
796        self.buffer.get(coord)
797    }
798
799    /// Returns the number of pending requests
800    pub fn pending_count(&self) -> usize {
801        self.pending.len()
802    }
803
804    /// Returns the current quality
805    pub const fn current_quality(&self) -> StreamingQuality {
806        self.quality
807    }
808
809    /// Returns streaming statistics
810    pub fn stats(&self) -> StreamingStats {
811        StreamingStats {
812            buffer: self.buffer.stats(),
813            bandwidth_mbps: self.bandwidth.bandwidth_mbps(),
814            pending_tiles: self.pending.len(),
815            completed_tiles: self.completed.len(),
816            current_quality: self.quality,
817        }
818    }
819
820    /// Clears all state
821    pub fn clear(&mut self) {
822        self.buffer.clear();
823        self.pending.clear();
824        self.completed.clear();
825    }
826}
827
828/// Streaming statistics
829#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct StreamingStats {
831    /// Buffer statistics
832    pub buffer: StreamBufferStats,
833    /// Current bandwidth in Mbps
834    pub bandwidth_mbps: f64,
835    /// Number of pending tiles
836    pub pending_tiles: usize,
837    /// Number of completed tiles
838    pub completed_tiles: usize,
839    /// Current quality level
840    pub current_quality: StreamingQuality,
841}
842
843/// Multi-resolution tile streamer
844pub struct MultiResolutionStreamer {
845    /// Streamers for different resolutions
846    streamers: HashMap<u32, TileStreamer>,
847}
848
849impl MultiResolutionStreamer {
850    /// Creates a new multi-resolution streamer
851    pub fn new() -> Self {
852        Self {
853            streamers: HashMap::new(),
854        }
855    }
856
857    /// Adds a streamer for a resolution
858    pub fn add_resolution(&mut self, resolution: u32, buffer_size_mb: usize) {
859        self.streamers
860            .insert(resolution, TileStreamer::new(buffer_size_mb));
861    }
862
863    /// Requests a tile at a specific resolution
864    pub fn request_tile(&mut self, resolution: u32, coord: TileCoord, timestamp: f64) {
865        if let Some(streamer) = self.streamers.get_mut(&resolution) {
866            streamer.request_tile(coord, timestamp);
867        }
868    }
869
870    /// Gets a tile at a specific resolution
871    pub fn get_tile(&mut self, resolution: u32, coord: &TileCoord) -> Option<&[u8]> {
872        self.streamers
873            .get_mut(&resolution)
874            .and_then(|s| s.get_tile(coord))
875    }
876
877    /// Returns statistics for all resolutions
878    pub fn all_stats(&self) -> HashMap<u32, StreamingStats> {
879        self.streamers
880            .iter()
881            .map(|(&res, streamer)| (res, streamer.stats()))
882            .collect()
883    }
884}
885
886impl Default for MultiResolutionStreamer {
887    fn default() -> Self {
888        Self::new()
889    }
890}
891
892/// Prefetch scheduler
893pub struct PrefetchScheduler {
894    /// Tiles to prefetch
895    queue: VecDeque<(TileCoord, RequestPriority)>,
896    /// Maximum concurrent prefetches
897    max_concurrent: usize,
898    /// Active prefetches
899    active: HashMap<TileCoord, f64>, // coord -> start_time
900}
901
902impl PrefetchScheduler {
903    /// Creates a new prefetch scheduler
904    pub fn new(max_concurrent: usize) -> Self {
905        Self {
906            queue: VecDeque::new(),
907            max_concurrent,
908            active: HashMap::new(),
909        }
910    }
911
912    /// Schedules a tile for prefetch
913    pub fn schedule(&mut self, coord: TileCoord, priority: RequestPriority) {
914        // Remove from queue if already scheduled
915        self.queue.retain(|(c, _)| *c != coord);
916
917        // Insert based on priority
918        let pos = self
919            .queue
920            .iter()
921            .position(|(_, p)| *p < priority)
922            .unwrap_or(self.queue.len());
923
924        self.queue.insert(pos, (coord, priority));
925    }
926
927    /// Gets the next tile to prefetch
928    pub fn next(&mut self, timestamp: f64) -> Option<TileCoord> {
929        if self.active.len() >= self.max_concurrent {
930            return None;
931        }
932
933        if let Some((coord, _)) = self.queue.pop_front() {
934            self.active.insert(coord, timestamp);
935            Some(coord)
936        } else {
937            None
938        }
939    }
940
941    /// Marks a prefetch as complete
942    pub fn complete(&mut self, coord: TileCoord) {
943        self.active.remove(&coord);
944    }
945
946    /// Returns the number of pending prefetches
947    pub fn pending_count(&self) -> usize {
948        self.queue.len()
949    }
950
951    /// Returns the number of active prefetches
952    pub fn active_count(&self) -> usize {
953        self.active.len()
954    }
955
956    /// Clears all state
957    pub fn clear(&mut self) {
958        self.queue.clear();
959        self.active.clear();
960    }
961
962    /// Schedules prefetch for viewport
963    pub fn schedule_prefetch(&self, viewport: &crate::Viewport, _level: usize) -> Vec<TileCoord> {
964        // Calculate visible tiles based on viewport
965        let bounds = viewport.bounds();
966        let mut tiles = Vec::new();
967
968        // Estimate tile range based on viewport bounds
969        let min_x = (bounds.0.max(0.0) / viewport.width as f64) as u32;
970        let min_y = (bounds.1.max(0.0) / viewport.height as f64) as u32;
971        let max_x = ((bounds.2 / viewport.width as f64).ceil() as u32).min(100);
972        let max_y = ((bounds.3 / viewport.height as f64).ceil() as u32).min(100);
973
974        for x in min_x..=max_x {
975            for y in min_y..=max_y {
976                tiles.push(TileCoord::new(0, x, y));
977            }
978        }
979
980        tiles
981    }
982}
983
984/// Progressive loader for prioritized tile loading
985#[derive(Debug, Clone)]
986pub struct ProgressiveLoader {
987    /// Loaded tiles
988    loaded: Vec<TileCoord>,
989}
990
991impl ProgressiveLoader {
992    /// Creates a new progressive loader
993    pub const fn new() -> Self {
994        Self { loaded: Vec::new() }
995    }
996
997    /// Prioritizes tiles by distance from center
998    pub fn prioritize_tiles(&self, tiles: &[TileCoord]) -> Vec<TileCoord> {
999        let mut result = tiles.to_vec();
1000
1001        // Sort by level (lower levels first for progressive loading)
1002        result.sort_by_key(|coord| coord.level);
1003
1004        result
1005    }
1006
1007    /// Marks a tile as loaded
1008    pub fn mark_loaded(&mut self, coord: TileCoord) {
1009        if !self.loaded.contains(&coord) {
1010            self.loaded.push(coord);
1011        }
1012    }
1013
1014    /// Checks if a tile is loaded
1015    pub fn is_loaded(&self, coord: &TileCoord) -> bool {
1016        self.loaded.contains(coord)
1017    }
1018
1019    /// Clears loaded tiles
1020    pub fn clear(&mut self) {
1021        self.loaded.clear();
1022    }
1023}
1024
1025impl Default for ProgressiveLoader {
1026    fn default() -> Self {
1027        Self::new()
1028    }
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034
1035    #[test]
1036    fn test_bandwidth_estimator() {
1037        let mut estimator = BandwidthEstimator::new();
1038
1039        // Record some transfers
1040        estimator.record_transfer(1_000_000, 1000.0); // 1 MB in 1 second
1041        estimator.record_transfer(2_000_000, 2000.0); // 2 MB in 2 seconds
1042
1043        let bps = estimator.bandwidth_bps();
1044        assert!(bps > 900_000.0 && bps < 1_100_000.0);
1045    }
1046
1047    #[test]
1048    fn test_bandwidth_quality_suggestion() {
1049        let mut estimator = BandwidthEstimator::new();
1050
1051        // Simulate slow connection
1052        estimator.record_transfer(100_000, 1000.0); // 100 KB/s
1053        assert_eq!(estimator.suggest_quality(), StreamingQuality::Low);
1054
1055        // Simulate fast connection
1056        estimator.record_transfer(10_000_000, 1000.0); // 10 MB/s
1057        assert_eq!(estimator.suggest_quality(), StreamingQuality::High);
1058    }
1059
1060    #[test]
1061    fn test_stream_buffer() {
1062        let mut buffer = StreamBuffer::new(1000);
1063        let coord = TileCoord::new(0, 0, 0);
1064        let data = vec![1, 2, 3, 4, 5];
1065
1066        buffer.add(coord, data.clone()).expect("Add failed");
1067        assert!(buffer.contains(&coord));
1068
1069        let retrieved = buffer.get(&coord).expect("Get failed");
1070        assert_eq!(retrieved, &data[..]);
1071    }
1072
1073    #[test]
1074    fn test_stream_buffer_eviction() {
1075        let mut buffer = StreamBuffer::new(20); // Very small buffer
1076
1077        let coord1 = TileCoord::new(0, 0, 0);
1078        let coord2 = TileCoord::new(0, 1, 0);
1079
1080        buffer.add(coord1, vec![0; 15]).expect("Add 1");
1081        buffer.add(coord2, vec![0; 15]).expect("Add 2");
1082
1083        // First tile should be evicted
1084        assert!(!buffer.contains(&coord1));
1085        assert!(buffer.contains(&coord2));
1086    }
1087
1088    #[test]
1089    fn test_importance_calculator() {
1090        let calc = ImportanceCalculator::new((500.0, 500.0), (1000.0, 1000.0));
1091
1092        // Tile at center should have high importance
1093        let center_tile = TileCoord::new(0, 2, 2);
1094        let center_imp = calc.calculate(&center_tile);
1095        assert!(center_imp > 0.8);
1096
1097        // Tile far from center should have lower importance
1098        let far_tile = TileCoord::new(0, 10, 10);
1099        let far_imp = calc.calculate(&far_tile);
1100        assert!(far_imp < center_imp);
1101    }
1102
1103    #[test]
1104    fn test_tile_streamer() {
1105        let mut streamer = TileStreamer::new(10); // 10 MB buffer
1106        let coord = TileCoord::new(0, 0, 0);
1107
1108        streamer.request_tile(coord, 0.0);
1109        assert_eq!(streamer.pending_count(), 1);
1110
1111        let data = vec![0u8; 1000];
1112        streamer
1113            .complete_tile(coord, data, 10.0, 1.0)
1114            .expect("Complete failed");
1115
1116        assert_eq!(streamer.pending_count(), 0);
1117        assert!(streamer.get_tile(&coord).is_some());
1118    }
1119
1120    #[test]
1121    fn test_prefetch_scheduler() {
1122        let mut scheduler = PrefetchScheduler::new(2);
1123
1124        scheduler.schedule(TileCoord::new(0, 0, 0), RequestPriority::Low);
1125        scheduler.schedule(TileCoord::new(0, 1, 0), RequestPriority::High);
1126
1127        // High priority should come first
1128        let next = scheduler.next(0.0).expect("Should have tile");
1129        assert_eq!(next, TileCoord::new(0, 1, 0));
1130
1131        assert_eq!(scheduler.active_count(), 1);
1132        assert_eq!(scheduler.pending_count(), 1);
1133    }
1134}