Skip to content

Commit 47bec77

Browse files
authored
DataLoaders 7: support for custom DataLoaders (#4566)
Add support for registering custom loaders, the same way we offer support for registering custom space views and store subscribers. Checks: - [x] cargo r -p custom_data_loader -- Cargo.toml --- Part of a series of PRs to make it possible to load _any_ file from the local filesystem, by any means, on web and native: - #4516 - #4517 - #4518 - #4519 - #4520 - #4521 - #4565 - #4566 - #4567
1 parent b7f7a55 commit 47bec77

File tree

8 files changed

+157
-19
lines changed

8 files changed

+157
-19
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/re_data_source/src/data_loader/mod.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,26 @@ static BUILTIN_LOADERS: Lazy<Vec<Arc<dyn DataLoader>>> = Lazy::new(|| {
229229

230230
/// Iterator over all registered [`DataLoader`]s.
231231
#[inline]
232-
pub fn iter_loaders() -> impl ExactSizeIterator<Item = Arc<dyn DataLoader>> {
233-
BUILTIN_LOADERS.clone().into_iter()
232+
pub fn iter_loaders() -> impl Iterator<Item = Arc<dyn DataLoader>> {
233+
BUILTIN_LOADERS
234+
.clone()
235+
.into_iter()
236+
.chain(CUSTOM_LOADERS.read().clone())
237+
}
238+
239+
/// Keeps track of all custom [`DataLoader`]s.
240+
///
241+
/// Use [`register_custom_data_loader`] to add new loaders.
242+
static CUSTOM_LOADERS: Lazy<parking_lot::RwLock<Vec<Arc<dyn DataLoader>>>> =
243+
Lazy::new(parking_lot::RwLock::default);
244+
245+
/// Register a custom [`DataLoader`].
246+
///
247+
/// Any time the Rerun Viewer opens a file or directory, this custom loader will be notified.
248+
/// Refer to [`DataLoader`]'s documentation for more information.
249+
#[inline]
250+
pub fn register_custom_data_loader(loader: impl DataLoader + 'static) {
251+
CUSTOM_LOADERS.write().push(Arc::new(loader));
234252
}
235253

236254
// ---

crates/re_data_source/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ mod web_sockets;
1515
mod load_stdin;
1616

1717
pub use self::data_loader::{
18-
iter_loaders, ArchetypeLoader, DataLoader, DataLoaderError, DirectoryLoader, LoadedData,
19-
RrdLoader,
18+
iter_loaders, register_custom_data_loader, ArchetypeLoader, DataLoader, DataLoaderError,
19+
DirectoryLoader, LoadedData, RrdLoader,
2020
};
2121
pub use self::data_source::DataSource;
2222
pub use self::load_file::{extension, load_from_file_contents};

crates/re_data_source/src/load_file.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@ pub fn load_from_path(
3434

3535
re_log::info!("Loading {path:?}…");
3636

37-
let store_info = prepare_store_info(store_id, file_source, path, path.is_dir());
37+
let data = load(store_id, path, None)?;
38+
39+
// If we reach this point, then at least one compatible `DataLoader` has been found.
40+
let store_info = prepare_store_info(store_id, file_source, path);
3841
if let Some(store_info) = store_info {
3942
if tx.send(store_info).is_err() {
4043
return Ok(()); // other end has hung up.
4144
}
4245
}
4346

44-
let data = load(store_id, path, None)?;
4547
send(store_id, data, tx);
4648

4749
Ok(())
@@ -67,14 +69,16 @@ pub fn load_from_file_contents(
6769

6870
re_log::info!("Loading {filepath:?}…");
6971

70-
let store_info = prepare_store_info(store_id, file_source, filepath, false);
72+
let data = load(store_id, filepath, Some(contents))?;
73+
74+
// If we reach this point, then at least one compatible `DataLoader` has been found.
75+
let store_info = prepare_store_info(store_id, file_source, filepath);
7176
if let Some(store_info) = store_info {
7277
if tx.send(store_info).is_err() {
7378
return Ok(()); // other end has hung up.
7479
}
7580
}
7681

77-
let data = load(store_id, filepath, Some(contents))?;
7882
send(store_id, data, tx);
7983

8084
Ok(())
@@ -92,20 +96,11 @@ pub fn extension(path: &std::path::Path) -> String {
9296
.to_string()
9397
}
9498

95-
/// Returns whether the given path is supported by builtin [`crate::DataLoader`]s.
96-
///
97-
/// This does _not_ access the filesystem.
98-
#[inline]
99-
pub fn is_associated_with_builtin_loader(path: &std::path::Path, is_dir: bool) -> bool {
100-
is_dir || crate::is_supported_file_extension(&extension(path))
101-
}
102-
10399
/// Prepares an adequate [`re_log_types::StoreInfo`] [`LogMsg`] given the input.
104100
pub(crate) fn prepare_store_info(
105101
store_id: &re_log_types::StoreId,
106102
file_source: FileSource,
107103
path: &std::path::Path,
108-
is_dir: bool,
109104
) -> Option<LogMsg> {
110105
re_tracing::profile_function!(path.display().to_string());
111106

@@ -114,10 +109,9 @@ pub(crate) fn prepare_store_info(
114109
let app_id = re_log_types::ApplicationId(path.display().to_string());
115110
let store_source = re_log_types::StoreSource::File { file_source };
116111

117-
let is_builtin = is_associated_with_builtin_loader(path, is_dir);
118112
let is_rrd = crate::SUPPORTED_RERUN_EXTENSIONS.contains(&extension(path).as_str());
119113

120-
(!is_rrd && is_builtin).then(|| {
114+
(!is_rrd).then(|| {
121115
LogMsg::SetStoreInfo(SetStoreInfo {
122116
row_id: re_log_types::RowId::new(),
123117
info: re_log_types::StoreInfo {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "custom_data_loader"
3+
version = "0.12.0-alpha.1+dev"
4+
edition = "2021"
5+
rust-version = "1.72"
6+
license = "MIT OR Apache-2.0"
7+
publish = false
8+
9+
[dependencies]
10+
rerun = { path = "../../../crates/rerun", features = ["native_viewer"] }
11+
12+
[build-dependencies]
13+
re_build_tools = { path = "../../../crates/re_build_tools" }
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
title: Custom data-loader
3+
tags: [data-loader, extension]
4+
rust: https://github.com/rerun-io/rerun/tree/latest/examples/rust/custom_data_loader/src/main.rs?speculative-link
5+
---
6+
7+
<picture>
8+
<img src="https://static.rerun.io/custom_data_loader/e44aadfa02fade5a3cf5d7cbdd6e0bf65d9f6446/full.png" alt="">
9+
<source media="(max-width: 480px)" srcset="https://static.rerun.io/custom_data_loader/e44aadfa02fade5a3cf5d7cbdd6e0bf65d9f6446/480w.png">
10+
<source media="(max-width: 768px)" srcset="https://static.rerun.io/custom_data_loader/e44aadfa02fade5a3cf5d7cbdd6e0bf65d9f6446/768w.png">
11+
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/custom_data_loader/e44aadfa02fade5a3cf5d7cbdd6e0bf65d9f6446/1024w.png">
12+
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/custom_data_loader/e44aadfa02fade5a3cf5d7cbdd6e0bf65d9f6446/1200w.png">
13+
</picture>
14+
15+
This example demonstrates how to implement and register a `DataLoader` into the Rerun Viewer in order to add support for loading arbitrary files.
16+
17+
Usage:
18+
```sh
19+
$ cargo r -p custom_data_loader -- path/to/some/file
20+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
re_build_tools::export_build_info_vars_for_crate("custom_data_loader");
3+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//! This example demonstrates how to implement and register a [`re_data_source::DataLoader`] into
2+
//! the Rerun Viewer in order to add support for loading arbitrary files.
3+
//!
4+
//! Usage:
5+
//! ```sh
6+
//! $ cargo r -p custom_data_loader -- path/to/some/file
7+
//! ```
8+
9+
use rerun::{
10+
external::{anyhow, re_build_info, re_data_source, re_log, tokio},
11+
log::{DataRow, RowId},
12+
EntityPath, TimePoint,
13+
};
14+
15+
#[tokio::main]
16+
async fn main() -> anyhow::Result<std::process::ExitCode> {
17+
re_log::setup_native_logging();
18+
19+
re_data_source::register_custom_data_loader(HashLoader);
20+
21+
let build_info = re_build_info::build_info!();
22+
rerun::run(build_info, rerun::CallSource::Cli, std::env::args())
23+
.await
24+
.map(std::process::ExitCode::from)
25+
}
26+
27+
// ---
28+
29+
/// A custom [`re_data_source::DataLoader`] that logs the hash of file as a [`rerun::TextDocument`].
30+
struct HashLoader;
31+
32+
impl re_data_source::DataLoader for HashLoader {
33+
fn name(&self) -> String {
34+
"rerun.data_loaders.HashLoader".into()
35+
}
36+
37+
fn load_from_path(
38+
&self,
39+
_store_id: rerun::external::re_log_types::StoreId,
40+
path: std::path::PathBuf,
41+
tx: std::sync::mpsc::Sender<re_data_source::LoadedData>,
42+
) -> Result<(), re_data_source::DataLoaderError> {
43+
let contents = std::fs::read(&path)?;
44+
if path.is_dir() {
45+
return Err(re_data_source::DataLoaderError::Incompatible(path)); // simply not interested
46+
}
47+
hash_and_log(&tx, &path, &contents)
48+
}
49+
50+
fn load_from_file_contents(
51+
&self,
52+
_store_id: rerun::external::re_log_types::StoreId,
53+
filepath: std::path::PathBuf,
54+
contents: std::borrow::Cow<'_, [u8]>,
55+
tx: std::sync::mpsc::Sender<re_data_source::LoadedData>,
56+
) -> Result<(), re_data_source::DataLoaderError> {
57+
hash_and_log(&tx, &filepath, &contents)
58+
}
59+
}
60+
61+
fn hash_and_log(
62+
tx: &std::sync::mpsc::Sender<re_data_source::LoadedData>,
63+
filepath: &std::path::Path,
64+
contents: &[u8],
65+
) -> Result<(), re_data_source::DataLoaderError> {
66+
use std::collections::hash_map::DefaultHasher;
67+
use std::hash::{Hash, Hasher};
68+
69+
let mut h = DefaultHasher::new();
70+
contents.hash(&mut h);
71+
72+
let doc = rerun::TextDocument::new(format!("{:08X}", h.finish()))
73+
.with_media_type(rerun::MediaType::TEXT);
74+
75+
let entity_path = EntityPath::from_file_path(filepath);
76+
let entity_path = format!("{entity_path}/hashed").into();
77+
let row = DataRow::from_archetype(RowId::new(), TimePoint::timeless(), entity_path, &doc)?;
78+
79+
tx.send(row.into()).ok();
80+
81+
Ok(())
82+
}

0 commit comments

Comments
 (0)