Skip to content

Commit b51df6a

Browse files
authored
DataLoaders 1: introduce, and migrate to, DataLoaders (#4517)
**COMMIT PER COMMIT!** _That's the nasty one... there's always a nasty one 😬_ Introduces the `DataLoader` trait, which can load any file, either from a filepath by reading from the local filesystem, or from a pre-loaded file's contents. `DataLoader`s are run in parallel where possible, and have complete say over what data they are interested in or not (i.e. they are not registered based on an extension, which is very limited in practice). I've decided to commit small example assets for the types we support, cause I feel we really need a fast way of checking whether everything still works from time to time. It's pretty light but might be controversial, I figure. Checks: - [x] `cargo r -p rerun-cli --no-default-features --features native_viewer -- examples/assets/example.{glb,gltf,obj,jpg,png,rrd}` - [x] Native: `File > Open > examples/assets/*` - [x] Native: `Drag-n-drop > examples/assets/*` - [x] Web: `File > Open > examples/assets/*` - [x] Web: `Drag-n-drop > examples/assets/*` --- 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 - TODO: register custom loaders - TODO: high level docs and guides for everything related to loading files
1 parent 490fc63 commit b51df6a

File tree

22 files changed

+1019
-400
lines changed

22 files changed

+1019
-400
lines changed

Cargo.lock

Lines changed: 4 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/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ re_tracing.workspace = true
2929
re_types = { workspace = true, features = ["image"] }
3030
re_ws_comms = { workspace = true, features = ["client"] }
3131

32+
ahash.workspace = true
3233
anyhow.workspace = true
3334
image.workspace = true
3435
itertools.workspace = true
36+
once_cell.workspace = true
37+
parking_lot.workspace = true
3538
rayon.workspace = true
3639
thiserror.workspace = true
37-
40+
walkdir.workspace = true
3841

3942
[build-dependencies]
4043
re_build_tools.workspace = true
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use re_log_types::{DataRow, EntityPath, RowId, TimePoint};
2+
3+
use crate::{DataLoader, DataLoaderError, LoadedData};
4+
5+
// ---
6+
7+
/// Loads data from any supported file or in-memory contents as native [`re_types::Archetype`]s.
8+
///
9+
/// This is a simple generic [`DataLoader`] for filetypes that match 1-to-1 with our builtin
10+
/// archetypes.
11+
pub struct ArchetypeLoader;
12+
13+
impl DataLoader for ArchetypeLoader {
14+
#[inline]
15+
fn name(&self) -> String {
16+
"rerun.data_loaders.Archetype".into()
17+
}
18+
19+
#[cfg(not(target_arch = "wasm32"))]
20+
fn load_from_path(
21+
&self,
22+
store_id: re_log_types::StoreId,
23+
filepath: std::path::PathBuf,
24+
tx: std::sync::mpsc::Sender<LoadedData>,
25+
) -> Result<(), crate::DataLoaderError> {
26+
use anyhow::Context as _;
27+
28+
if filepath.is_dir() {
29+
return Ok(()); // simply not interested
30+
}
31+
32+
re_tracing::profile_function!(filepath.display().to_string());
33+
34+
let contents = std::fs::read(&filepath)
35+
.with_context(|| format!("Failed to read file {filepath:?}"))?;
36+
let contents = std::borrow::Cow::Owned(contents);
37+
38+
self.load_from_file_contents(store_id, filepath, contents, tx)
39+
}
40+
41+
fn load_from_file_contents(
42+
&self,
43+
_store_id: re_log_types::StoreId,
44+
filepath: std::path::PathBuf,
45+
contents: std::borrow::Cow<'_, [u8]>,
46+
tx: std::sync::mpsc::Sender<LoadedData>,
47+
) -> Result<(), crate::DataLoaderError> {
48+
re_tracing::profile_function!(filepath.display().to_string());
49+
50+
let entity_path = EntityPath::from_file_path(&filepath);
51+
52+
let mut timepoint = TimePoint::timeless();
53+
// TODO(cmc): log these once heuristics (I think?) are fixed
54+
if false {
55+
if let Ok(metadata) = filepath.metadata() {
56+
use re_log_types::{Time, Timeline};
57+
58+
if let Some(created) = metadata.created().ok().and_then(|t| Time::try_from(t).ok())
59+
{
60+
timepoint.insert(Timeline::new_temporal("created_at"), created.into());
61+
}
62+
if let Some(modified) = metadata
63+
.modified()
64+
.ok()
65+
.and_then(|t| Time::try_from(t).ok())
66+
{
67+
timepoint.insert(Timeline::new_temporal("modified_at"), modified.into());
68+
}
69+
if let Some(accessed) = metadata
70+
.accessed()
71+
.ok()
72+
.and_then(|t| Time::try_from(t).ok())
73+
{
74+
timepoint.insert(Timeline::new_temporal("accessed_at"), accessed.into());
75+
}
76+
}
77+
}
78+
79+
let extension = crate::extension(&filepath);
80+
81+
let mut rows = Vec::new();
82+
83+
if crate::SUPPORTED_MESH_EXTENSIONS.contains(&extension.as_str()) {
84+
re_log::debug!(?filepath, loader = self.name(), "Loading 3D model…",);
85+
rows.extend(load_mesh(
86+
filepath,
87+
timepoint,
88+
entity_path,
89+
contents.into_owned(),
90+
)?);
91+
} else if crate::SUPPORTED_IMAGE_EXTENSIONS.contains(&extension.as_str()) {
92+
re_log::debug!(?filepath, loader = self.name(), "Loading image…",);
93+
rows.extend(load_image(
94+
&filepath,
95+
timepoint,
96+
entity_path,
97+
contents.into_owned(),
98+
)?);
99+
};
100+
101+
for row in rows {
102+
if tx.send(row.into()).is_err() {
103+
break; // The other end has decided to hang up, not our problem.
104+
}
105+
}
106+
107+
Ok(())
108+
}
109+
}
110+
111+
// ---
112+
113+
fn load_mesh(
114+
filepath: std::path::PathBuf,
115+
timepoint: TimePoint,
116+
entity_path: EntityPath,
117+
contents: Vec<u8>,
118+
) -> Result<impl ExactSizeIterator<Item = DataRow>, DataLoaderError> {
119+
re_tracing::profile_function!();
120+
121+
let rows = [
122+
{
123+
let arch = re_types::archetypes::Asset3D::from_file_contents(
124+
contents,
125+
re_types::components::MediaType::guess_from_path(filepath),
126+
);
127+
DataRow::from_archetype(RowId::new(), timepoint, entity_path, &arch)?
128+
},
129+
//
130+
];
131+
132+
Ok(rows.into_iter())
133+
}
134+
135+
fn load_image(
136+
filepath: &std::path::Path,
137+
timepoint: TimePoint,
138+
entity_path: EntityPath,
139+
contents: Vec<u8>,
140+
) -> Result<impl ExactSizeIterator<Item = DataRow>, DataLoaderError> {
141+
re_tracing::profile_function!();
142+
143+
let rows = [
144+
{
145+
let arch = re_types::archetypes::Image::from_file_contents(
146+
contents,
147+
image::ImageFormat::from_path(filepath).ok(),
148+
)?;
149+
DataRow::from_archetype(RowId::new(), timepoint, entity_path, &arch)?
150+
},
151+
//
152+
];
153+
154+
Ok(rows.into_iter())
155+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use re_log_encoding::decoder::Decoder;
2+
3+
// ---
4+
5+
/// Loads data from any `rrd` file or in-memory contents.
6+
pub struct RrdLoader;
7+
8+
impl crate::DataLoader for RrdLoader {
9+
#[inline]
10+
fn name(&self) -> String {
11+
"rerun.data_loaders.Rrd".into()
12+
}
13+
14+
#[cfg(not(target_arch = "wasm32"))]
15+
fn load_from_path(
16+
&self,
17+
// NOTE: The Store ID comes from the rrd file itself.
18+
_store_id: re_log_types::StoreId,
19+
filepath: std::path::PathBuf,
20+
tx: std::sync::mpsc::Sender<crate::LoadedData>,
21+
) -> Result<(), crate::DataLoaderError> {
22+
use anyhow::Context as _;
23+
24+
re_tracing::profile_function!(filepath.display().to_string());
25+
26+
let extension = crate::extension(&filepath);
27+
if extension != "rrd" {
28+
return Ok(()); // simply not interested
29+
}
30+
31+
re_log::debug!(
32+
?filepath,
33+
loader = self.name(),
34+
"Loading rrd data from filesystem…",
35+
);
36+
37+
let version_policy = re_log_encoding::decoder::VersionPolicy::Warn;
38+
let file = std::fs::File::open(&filepath)
39+
.with_context(|| format!("Failed to open file {filepath:?}"))?;
40+
let file = std::io::BufReader::new(file);
41+
42+
let decoder = re_log_encoding::decoder::Decoder::new(version_policy, file)?;
43+
decode_and_stream(&filepath, &tx, decoder);
44+
45+
Ok(())
46+
}
47+
48+
fn load_from_file_contents(
49+
&self,
50+
// NOTE: The Store ID comes from the rrd file itself.
51+
_store_id: re_log_types::StoreId,
52+
filepath: std::path::PathBuf,
53+
contents: std::borrow::Cow<'_, [u8]>,
54+
tx: std::sync::mpsc::Sender<crate::LoadedData>,
55+
) -> Result<(), crate::DataLoaderError> {
56+
re_tracing::profile_function!(filepath.display().to_string());
57+
58+
let extension = crate::extension(&filepath);
59+
if extension != "rrd" {
60+
return Ok(()); // simply not interested
61+
}
62+
63+
let version_policy = re_log_encoding::decoder::VersionPolicy::Warn;
64+
let contents = std::io::Cursor::new(contents);
65+
let decoder = match re_log_encoding::decoder::Decoder::new(version_policy, contents) {
66+
Ok(decoder) => decoder,
67+
Err(err) => match err {
68+
// simply not interested
69+
re_log_encoding::decoder::DecodeError::NotAnRrd
70+
| re_log_encoding::decoder::DecodeError::Options(_) => return Ok(()),
71+
_ => return Err(err.into()),
72+
},
73+
};
74+
decode_and_stream(&filepath, &tx, decoder);
75+
Ok(())
76+
}
77+
}
78+
79+
fn decode_and_stream<R: std::io::Read>(
80+
filepath: &std::path::Path,
81+
tx: &std::sync::mpsc::Sender<crate::LoadedData>,
82+
decoder: Decoder<R>,
83+
) {
84+
re_tracing::profile_function!(filepath.display().to_string());
85+
86+
for msg in decoder {
87+
let msg = match msg {
88+
Ok(msg) => msg,
89+
Err(err) => {
90+
re_log::warn_once!("Failed to decode message in {filepath:?}: {err}");
91+
continue;
92+
}
93+
};
94+
if tx.send(msg.into()).is_err() {
95+
break; // The other end has decided to hang up, not our problem.
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)