11//! Rerun dataloader for MCAP files.
22
3- use std:: io:: Cursor ;
4- use std:: path:: Path ;
5- use std:: sync:: mpsc:: Sender ;
6-
3+ use crate :: { DataLoader , DataLoaderError , DataLoaderSettings , LoadedData } ;
4+ use lance_io:: object_store:: { ObjectStoreParams , ObjectStoreRegistry } ;
5+ use mcap:: sans_io:: IndexedReaderOptions ;
76use re_chunk:: RowId ;
87use re_log_types:: { SetStoreInfo , StoreId , StoreInfo } ;
9- use re_mcap:: { LayerRegistry , SelectedLayers } ;
8+ use re_mcap:: { AsyncSeekRead , LayerRegistry , SelectedLayers } ;
9+ use std:: io:: Cursor ;
10+ use std:: path:: Path ;
11+ use std:: str:: FromStr as _;
1012
11- use crate :: { DataLoader , DataLoaderError , DataLoaderSettings , LoadedData } ;
13+ use std:: sync:: mpsc:: Sender ;
14+ use url:: Url ;
1215
1316const MCAP_LOADER_NAME : & str = "McapLoader" ;
1417
@@ -56,6 +59,19 @@ impl McapLoader {
5659 }
5760}
5861
62+ fn mcap_option_from_url ( url : & Url ) -> anyhow:: Result < IndexedReaderOptions > {
63+ let mut mcap_options = IndexedReaderOptions :: default ( ) ;
64+ for ( name, val) in url. query_pairs ( ) {
65+ if name == "start" {
66+ mcap_options = mcap_options. log_time_on_or_after ( u64:: from_str ( & val) ?) ;
67+ } else if name == "end" {
68+ mcap_options = mcap_options. log_time_before ( u64:: from_str ( & val) ?) ;
69+ }
70+ }
71+
72+ Ok ( mcap_options)
73+ }
74+
5975impl DataLoader for McapLoader {
6076 fn name ( & self ) -> crate :: DataLoaderName {
6177 MCAP_LOADER_NAME . into ( )
@@ -68,11 +84,31 @@ impl DataLoader for McapLoader {
6884 path : std:: path:: PathBuf ,
6985 tx : Sender < crate :: LoadedData > ,
7086 ) -> std:: result:: Result < ( ) , DataLoaderError > {
71- if !is_mcap_file ( & path) {
72- return Err ( DataLoaderError :: Incompatible ( path) ) ; // simply not interested
87+ re_tracing:: profile_function!( ) ;
88+ let url = path. to_string_lossy ( ) ;
89+ let mcap_url = match Url :: parse ( & url) {
90+ Ok ( mcap_url) => mcap_url,
91+ Err ( err) => match err {
92+ url:: ParseError :: RelativeUrlWithoutBase => {
93+ match Url :: parse ( & format ! ( "file://{url}" ) ) {
94+ Ok ( mcap_url) => mcap_url,
95+ Err ( err) => return Err ( DataLoaderError :: Other ( err. into ( ) ) ) ,
96+ }
97+ }
98+ _ => return Err ( DataLoaderError :: Other ( err. into ( ) ) ) ,
99+ } ,
100+ } ;
101+
102+ let path = std:: path:: PathBuf :: from_str ( mcap_url. path ( ) ) . unwrap ( ) ;
103+
104+ if mcap_url. scheme ( ) == "file" && !is_mcap_file ( & path) {
105+ return Err ( DataLoaderError :: Incompatible ( path. clone ( ) ) ) ; // simply not interested
73106 }
74107
75- re_tracing:: profile_function!( ) ;
108+ let mcap_options = match mcap_option_from_url ( & mcap_url) {
109+ Ok ( mcap_options) => mcap_options,
110+ Err ( err) => return Err ( DataLoaderError :: Other ( err) ) ,
111+ } ;
76112
77113 // NOTE(1): `spawn` is fine, this whole function is native-only.
78114 // NOTE(2): this must spawned on a dedicated thread to avoid a deadlock!
@@ -85,8 +121,23 @@ impl DataLoader for McapLoader {
85121 std:: thread:: Builder :: new ( )
86122 . name ( format ! ( "load_mcap({path:?}" ) )
87123 . spawn ( move || {
88- if let Err ( err) = load_mcap_mmap (
89- & path,
124+ // Load from local disk.
125+ if mcap_url. scheme ( ) == "file" {
126+ if let Err ( err) = load_mcap_mmap (
127+ & path,
128+ & mcap_options,
129+ & settings,
130+ & tx,
131+ & selected_layers,
132+ raw_fallback_enabled,
133+ ) {
134+ re_log:: error!( "Failed to load MCAP file: {err}" ) ;
135+ }
136+ }
137+ // Load from cloud.
138+ else if let Err ( err) = load_mcap_cloud (
139+ & mcap_url,
140+ & mcap_options,
90141 & settings,
91142 & tx,
92143 & selected_layers,
@@ -128,6 +179,7 @@ impl DataLoader for McapLoader {
128179 . spawn ( move || {
129180 if let Err ( err) = load_mcap_mmap (
130181 & filepath,
182+ & IndexedReaderOptions :: default ( ) ,
131183 & settings,
132184 & tx,
133185 & selected_layers,
@@ -151,12 +203,11 @@ impl DataLoader for McapLoader {
151203 ) -> std:: result:: Result < ( ) , DataLoaderError > {
152204 if !is_mcap_file ( & filepath) {
153205 return Err ( DataLoaderError :: Incompatible ( filepath) ) ; // simply not interested
154- }
155-
206+ } ;
156207 let contents = contents. into_owned ( ) ;
157-
158208 load_mcap (
159- & contents,
209+ Box :: new ( Cursor :: new ( contents) ) ,
210+ & IndexedReaderOptions :: default ( ) ,
160211 settings,
161212 & tx,
162213 & self . selected_layers ,
@@ -165,9 +216,48 @@ impl DataLoader for McapLoader {
165216 }
166217}
167218
219+ #[ cfg( not( target_arch = "wasm32" ) ) ]
220+ fn load_mcap_cloud (
221+ url : & Url ,
222+ mcap_options : & IndexedReaderOptions ,
223+ settings : & DataLoaderSettings ,
224+ tx : & Sender < LoadedData > ,
225+ selected_layers : & SelectedLayers ,
226+ raw_fallback_enabled : bool ,
227+ ) -> anyhow:: Result < ( ) > {
228+ let mcap_reader = tokio:: runtime:: Builder :: new_current_thread ( )
229+ . enable_all ( )
230+ . build ( ) ?
231+ . block_on ( async {
232+ let store = ObjectStoreRegistry :: default ( )
233+ . get_store ( url. clone ( ) , & ObjectStoreParams :: default ( ) )
234+ . await ?;
235+ let meta = store
236+ . inner
237+ . head ( & object_store:: path:: Path :: parse ( url. path ( ) ) ?)
238+ . await ?;
239+ let mcap_reader = Box :: from ( object_store:: buffered:: BufReader :: new (
240+ store. inner . clone ( ) ,
241+ & meta,
242+ ) ) ;
243+
244+ Ok :: < Box < object_store:: buffered:: BufReader > , anyhow:: Error > ( mcap_reader)
245+ } ) ?;
246+
247+ Ok ( load_mcap (
248+ mcap_reader,
249+ mcap_options,
250+ settings,
251+ tx,
252+ selected_layers,
253+ raw_fallback_enabled,
254+ ) ?)
255+ }
256+
168257#[ cfg( not( target_arch = "wasm32" ) ) ]
169258fn load_mcap_mmap (
170259 filepath : & std:: path:: PathBuf ,
260+ mcap_options : & IndexedReaderOptions ,
171261 settings : & DataLoaderSettings ,
172262 tx : & Sender < LoadedData > ,
173263 selected_layers : & SelectedLayers ,
@@ -180,17 +270,26 @@ fn load_mcap_mmap(
180270 #[ expect( unsafe_code) ]
181271 let mmap = unsafe { memmap2:: Mmap :: map ( & file) ? } ;
182272
183- load_mcap ( & mmap, settings, tx, selected_layers, raw_fallback_enabled)
273+ load_mcap (
274+ Box :: new ( Cursor :: new ( mmap) ) ,
275+ mcap_options,
276+ settings,
277+ tx,
278+ selected_layers,
279+ raw_fallback_enabled,
280+ )
184281}
185282
186283pub fn load_mcap (
187- mcap : & [ u8 ] ,
284+ mut mcap : Box < dyn AsyncSeekRead > ,
285+ mcap_options : & IndexedReaderOptions ,
188286 settings : & DataLoaderSettings ,
189287 tx : & Sender < LoadedData > ,
190288 selected_layers : & SelectedLayers ,
191289 raw_fallback_enabled : bool ,
192290) -> Result < ( ) , DataLoaderError > {
193291 re_tracing:: profile_function!( ) ;
292+
194293 let store_id = settings. recommended_store_id ( ) ;
195294
196295 if tx
@@ -223,16 +322,14 @@ pub fn load_mcap(
223322 }
224323 } ;
225324
226- let reader = Cursor :: new ( & mcap) ;
227-
228- let summary = re_mcap:: read_summary ( reader) ?
325+ let summary = re_mcap:: read_summary ( & mut mcap) ?
229326 . ok_or_else ( || anyhow:: anyhow!( "MCAP file does not contain a summary" ) ) ?;
230327
231328 // TODO(#10862): Add warning for channel that miss semantic information.
232329 LayerRegistry :: all_builtin ( raw_fallback_enabled)
233330 . select ( selected_layers)
234331 . plan ( & summary) ?
235- . run ( mcap, & summary, & mut send_chunk) ?;
332+ . run ( & mut mcap, & summary, mcap_options , & mut send_chunk) ?;
236333
237334 Ok ( ( ) )
238335}
0 commit comments