@@ -13,6 +13,8 @@ use re_log_types::{AbsoluteTimeRange, StoreKind};
1313
1414use crate :: sorted_range_map:: SortedRangeMap ;
1515
16+ mod time_range_merger;
17+
1618/// Errors that can occur during prefetching.
1719#[ derive( thiserror:: Error , Debug ) ]
1820pub enum PrefetchError {
@@ -48,6 +50,19 @@ pub enum LoadState {
4850 Loaded ,
4951}
5052
53+ impl LoadState {
54+ pub fn is_loaded ( & self ) -> bool {
55+ !self . is_unloaded ( )
56+ }
57+
58+ pub fn is_unloaded ( & self ) -> bool {
59+ match self {
60+ Self :: Unloaded | Self :: InTransit => true ,
61+ Self :: Loaded => false ,
62+ }
63+ }
64+ }
65+
5166/// How to calculate which chunks to prefetch.
5267#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
5368pub struct ChunkPrefetchOptions {
@@ -410,37 +425,124 @@ impl RrdManifestIndex {
410425 ) ?)
411426 }
412427
413- #[ must_use]
414- pub fn time_ranges_all_chunks (
428+ /// Creates an iterator of time ranges which are loaded on a specific timeline.
429+ ///
430+ /// The ranges are guaranteed to be ordered and non-overlapping.
431+ pub fn loaded_ranges_on_timeline (
415432 & self ,
416433 timeline : & Timeline ,
417- ) -> Vec < ( LoadState , AbsoluteTimeRange ) > {
418- re_tracing:: profile_function!( ) ;
419-
420- let mut time_ranges_all_chunks = Vec :: new ( ) ;
434+ ) -> impl Iterator < Item = AbsoluteTimeRange > {
435+ let mut scratch = Vec :: new ( ) ;
436+ let mut ranges = Vec :: new ( ) ;
421437
438+ // First we merge ranges for individual components, since chunks' time ranges
439+ // often have gaps which we don't want to display other components' chunks
440+ // loaded state in.
422441 for timelines in self . native_temporal_map . values ( ) {
423- let Some ( entity_component_chunks ) = timelines. get ( timeline) else {
442+ let Some ( data ) = timelines. get ( timeline) else {
424443 continue ;
425444 } ;
426445
427- for chunks in entity_component_chunks . values ( ) {
428- for ( chunk_id , entry ) in chunks {
429- let RrdManifestTemporalMapEntry { time_range , .. } = entry ;
446+ for chunks in data . values ( ) {
447+ scratch . extend ( chunks . iter ( ) . filter_map ( | ( c , range ) | {
448+ let state = self . remote_chunk_info ( c ) ? . state ;
430449
431- let Some ( info ) = self . remote_chunks . get ( chunk_id ) else {
432- continue ;
433- } ;
434- debug_assert ! (
435- time_range . min <= time_range . max ,
436- "Unexpected negative time range in RRD manifest"
437- ) ;
438- time_ranges_all_chunks . push ( ( info . state , * time_range ) ) ;
439- }
450+ Some ( time_range_merger :: TimeRange {
451+ range : range . time_range ,
452+ loaded : state . is_loaded ( ) ,
453+ } )
454+ } ) ) ;
455+
456+ ranges . extend ( time_range_merger :: merge_ranges ( & scratch ) ) ;
457+
458+ scratch . clear ( ) ;
440459 }
441460 }
442461
443- time_ranges_all_chunks
462+ time_range_merger:: merge_ranges ( & ranges)
463+ . into_iter ( )
464+ . filter ( |r| r. loaded )
465+ . map ( |r| r. range )
466+ }
467+
468+ /// If `component` is some, this returns all unloaded temporal entries for that specific
469+ /// component on the given timeline.
470+ ///
471+ /// If not, this returns all unloaded temporal entries for `entity`'s components and its
472+ /// descendants' unloaded temporal entries.
473+ pub fn unloaded_temporal_entries_for (
474+ & self ,
475+ timeline : & re_chunk:: Timeline ,
476+ entity : & re_chunk:: EntityPath ,
477+ component : Option < re_chunk:: ComponentIdentifier > ,
478+ ) -> Vec < RrdManifestTemporalMapEntry > {
479+ re_tracing:: profile_function!( ) ;
480+
481+ if let Some ( component) = component {
482+ let Some ( entity_ranges_per_timeline) = self . native_temporal_map . get ( entity) else {
483+ return Vec :: new ( ) ;
484+ } ;
485+
486+ let Some ( entity_ranges) = entity_ranges_per_timeline. get ( timeline) else {
487+ return Vec :: new ( ) ;
488+ } ;
489+
490+ let Some ( component_ranges) = entity_ranges. get ( & component) else {
491+ return Vec :: new ( ) ;
492+ } ;
493+
494+ component_ranges
495+ . iter ( )
496+ . filter ( |( chunk, _) | self . is_chunk_unloaded ( chunk) )
497+ . map ( |( _, entry) | * entry)
498+ . collect ( )
499+ } else {
500+ // If we don't have a specific component we want to include the entity's children
501+ let mut res = Vec :: new ( ) ;
502+
503+ if let Some ( tree) = self . entity_tree . subtree ( entity) {
504+ tree. visit_children_recursively ( |child| {
505+ self . unloaded_temporal_entries_for_entity ( & mut res, timeline, child) ;
506+ } ) ;
507+ } else {
508+ #[ cfg( debug_assertions) ]
509+ re_log:: warn_once!(
510+ "[DEBUG] Missing entity tree for {entity} while fetching temporal entities"
511+ ) ;
512+
513+ self . unloaded_temporal_entries_for_entity ( & mut res, timeline, entity) ;
514+ }
515+
516+ res
517+ }
518+ }
519+
520+ /// Fills `ranges` with unloaded temporal entries for this exact entity (descendants aren't included).
521+ fn unloaded_temporal_entries_for_entity (
522+ & self ,
523+ ranges : & mut Vec < RrdManifestTemporalMapEntry > ,
524+ timeline : & re_chunk:: Timeline ,
525+ entity : & re_chunk:: EntityPath ,
526+ ) {
527+ re_tracing:: profile_function!( ) ;
528+
529+ if let Some ( entity_ranges_per_timeline) = self . native_temporal_map . get ( entity)
530+ && let Some ( entity_ranges) = entity_ranges_per_timeline. get ( timeline)
531+ {
532+ for ( _, entry) in entity_ranges
533+ . values ( )
534+ . flatten ( )
535+ . filter ( |( chunk, _) | self . is_chunk_unloaded ( chunk) )
536+ {
537+ ranges. push ( * entry) ;
538+ }
539+ }
540+ }
541+
542+ fn is_chunk_unloaded ( & self , chunk_id : & ChunkId ) -> bool {
543+ self . remote_chunks
544+ . get ( chunk_id)
545+ . is_none_or ( |c| c. state . is_unloaded ( ) )
444546 }
445547
446548 pub fn full_uncompressed_size ( & self ) -> Option < u64 > {
0 commit comments