From 1b10c6738eef747f048188de7c6dd959aef2b28f Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Mon, 26 Jun 2023 16:57:45 +0200 Subject: [PATCH] Simpler, sturdier stateful time tracking in both SDKs (#2506) (Probably easier to review commit by commit) Follow up to my discussion with @abey79 regarding his poor experience with time tracking, which was summarized in https://github.com/rerun-io/rerun/pull/2501#issuecomment-1603852573: > - When implementing the `step` timeline of the scalar example, I first searched for `set_time_sequence` (used in Python API) in the Rust docs, which I found in `RecordingStream`. Turns out it's not in 0.7 and bugged on `main` (or rather ignored by `MsgSender`). Again, it compiled and displayed no error, but no timeline was created. This PR makes it so that `RecordingStream` is always in charge of injecting its internal clock into outgoing rows (unless the caller ask it not to, e.g. because the data is meant to be timeless). This is pretty similar to what was already in place for `log_tick`, except it now applies to every timelines, whether they are builtin or user defined. - Within the Python SDK, this gets rid of all the existing manual time injection stuff. - On the Rust SDK's side, this fixes the issue that `MsgSender` used to ignore the internal clock altogether (i.e. the stateful time APIs were not supported at all for Rust users). - And finally this cleans up the Rust examples a bunch since we now have access to stateful time. --- PR Build Summary: https://build.rerun.io/pr/2506 Docs preview: https://rerun.io/preview/178edf5/docs Examples preview: https://rerun.io/preview/178edf5/examples --- crates/re_sdk/src/msg_sender.rs | 11 +- crates/re_sdk/src/recording_stream.rs | 52 +++++--- docs/content/getting-started/logging-rust.md | 12 +- examples/rust/api_demo/src/main.rs | 121 ++++++------------- examples/rust/clock/src/main.rs | 12 +- examples/rust/dna/src/main.rs | 12 +- examples/rust/minimal_options/src/main.rs | 5 +- examples/rust/raw_mesh/src/main.rs | 4 +- rerun_py/src/python_bridge.rs | 76 +++++------- 9 files changed, 120 insertions(+), 185 deletions(-) diff --git a/crates/re_sdk/src/msg_sender.rs b/crates/re_sdk/src/msg_sender.rs index 8565ea0a01c7..896bb1e2edb7 100644 --- a/crates/re_sdk/src/msg_sender.rs +++ b/crates/re_sdk/src/msg_sender.rs @@ -93,7 +93,7 @@ impl MsgSender { Self { entity_path: ent_path.into(), - timepoint: [(Timeline::log_time(), Time::now().into())].into(), + timepoint: TimePoint::default(), timeless: false, num_instances: None, @@ -116,7 +116,7 @@ impl MsgSender { let ent_path = re_log_types::EntityPath::from_file_path_as_single_string(file_path); let cell = re_components::data_cell_from_file_path(file_path)?; - let mut timepoint = TimePoint::from([(Timeline::log_time(), Time::now().into())]); + let mut timepoint = TimePoint::default(); // This may sounds like a good idea, but that means `rerun *.jpg` will // actually act like it is playing a bunch of files over time, perhaps over many years. @@ -160,6 +160,7 @@ impl MsgSender { /// `MsgSender` automatically keeps track of the logging time, which is recorded when /// [`Self::new`] is first called. #[inline] + #[doc(hidden)] pub fn with_timepoint(mut self, timepoint: TimePoint) -> Self { for (timeline, time) in timepoint { self.timepoint.insert(timeline, time); @@ -176,6 +177,7 @@ impl MsgSender { /// `MsgSender` automatically keeps track of the logging time, which is recorded when /// [`Self::new`] is first called. #[inline] + #[doc(hidden)] pub fn with_time(mut self, timeline: Timeline, time: impl Into) -> Self { self.timepoint.insert(timeline, time.into()); self @@ -294,16 +296,17 @@ impl MsgSender { return Ok(()); // silently drop the message } + let timeless = self.timeless; let [row_standard, row_splats] = self.into_rows(); if let Some(row_splats) = row_splats { - rec_stream.record_row(row_splats); + rec_stream.record_row(row_splats, !timeless); } // Always the primary component last so range-based queries will include the other data. // Since the primary component can't be splatted it must be in msg_standard, see(#1215). if let Some(row_standard) = row_standard { - rec_stream.record_row(row_standard); + rec_stream.record_row(row_standard, !timeless); } Ok(()) diff --git a/crates/re_sdk/src/recording_stream.rs b/crates/re_sdk/src/recording_stream.rs index f01779715121..0c840878f2f0 100644 --- a/crates/re_sdk/src/recording_stream.rs +++ b/crates/re_sdk/src/recording_stream.rs @@ -599,11 +599,7 @@ impl RecordingStream { /// /// This is a convenience wrapper for [`Self::record_msg`]. #[inline] - pub fn record_path_op( - &self, - timepoint: re_log_types::TimePoint, - path_op: re_log_types::PathOp, - ) { + pub fn record_path_op(&self, path_op: re_log_types::PathOp) { let Some(this) = &*self.inner else { re_log::warn_once!("Recording disabled - call to record_path_op() ignored"); return; @@ -613,7 +609,7 @@ impl RecordingStream { this.info.store_id.clone(), re_log_types::EntityPathOpMsg { row_id: re_log_types::RowId::random(), - time_point: timepoint, + time_point: self.now(), path_op, }, )); @@ -621,10 +617,13 @@ impl RecordingStream { /// Records a single [`DataRow`]. /// + /// If `inject_time` is set to `true`, the row's timestamp data will be overridden using the + /// [`RecordingStream`]'s internal clock. + /// /// Internally, incoming [`DataRow`]s are automatically coalesced into larger [`DataTable`]s to /// optimize for transport. #[inline] - pub fn record_row(&self, mut row: DataRow) { + pub fn record_row(&self, mut row: DataRow, inject_time: bool) { let Some(this) = &*self.inner else { re_log::warn_once!("Recording disabled - call to record_row() ignored"); return; @@ -635,8 +634,17 @@ impl RecordingStream { // // NOTE: We're incrementing the current tick still. let tick = this.tick.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - if !row.timepoint().is_timeless() { - row.timepoint.insert(Timeline::log_tick(), tick.into()); + if inject_time { + // Get the current time on all timelines, for the current recording, on the current + // thread... + let mut now = self.now(); + // ...and then also inject the current recording tick into it. + now.insert(Timeline::log_tick(), tick.into()); + + // Inject all these times into the row, overriding conflicting times, if any. + for (timeline, time) in now { + row.timepoint.insert(timeline, time); + } } this.batcher.push_row(row); @@ -860,7 +868,11 @@ impl RecordingStream { /// For example: `rec.set_time_sequence("frame_nr", frame_nr)`. /// /// You can remove a timeline again using `set_time_sequence("frame_nr", None)`. - pub fn set_time_sequence(&self, timeline: impl Into, sequence: Option) { + pub fn set_time_sequence( + &self, + timeline: impl Into, + sequence: impl Into>, + ) { let Some(this) = &*self.inner else { re_log::warn_once!("Recording disabled - call to set_time_sequence() ignored"); return; @@ -869,7 +881,7 @@ impl RecordingStream { ThreadInfo::set_thread_time( &this.info.store_id, Timeline::new(timeline, TimeType::Sequence), - sequence.map(TimeInt::from), + sequence.into().map(TimeInt::from), ); } @@ -880,7 +892,7 @@ impl RecordingStream { /// For example: `rec.set_time_seconds("sim_time", sim_time_secs)`. /// /// You can remove a timeline again using `rec.set_time_seconds("sim_time", None)`. - pub fn set_time_seconds(&self, timeline: &str, seconds: Option) { + pub fn set_time_seconds(&self, timeline: &str, seconds: impl Into>) { let Some(this) = &*self.inner else { re_log::warn_once!("Recording disabled - call to set_time_seconds() ignored"); return; @@ -889,7 +901,9 @@ impl RecordingStream { ThreadInfo::set_thread_time( &this.info.store_id, Timeline::new(timeline, TimeType::Time), - seconds.map(|secs| Time::from_seconds_since_epoch(secs).into()), + seconds + .into() + .map(|secs| Time::from_seconds_since_epoch(secs).into()), ); } @@ -900,7 +914,7 @@ impl RecordingStream { /// For example: `rec.set_time_seconds("sim_time", sim_time_nanos)`. /// /// You can remove a timeline again using `rec.set_time_seconds("sim_time", None)`. - pub fn set_time_nanos(&self, timeline: &str, ns: Option) { + pub fn set_time_nanos(&self, timeline: &str, ns: impl Into>) { let Some(this) = &*self.inner else { re_log::warn_once!("Recording disabled - call to set_time_nanos() ignored"); return; @@ -909,7 +923,7 @@ impl RecordingStream { ThreadInfo::set_thread_time( &this.info.store_id, Timeline::new(timeline, TimeType::Time), - ns.map(|ns| Time::from_ns_since_epoch(ns).into()), + ns.into().map(|ns| Time::from_ns_since_epoch(ns).into()), ); } @@ -956,7 +970,7 @@ mod tests { let mut table = data_table_example(false); table.compute_all_size_bytes(); for row in table.to_rows() { - rec_stream.record_row(row); + rec_stream.record_row(row, false); } let storage = rec_stream.memory(); @@ -1021,7 +1035,7 @@ mod tests { let mut table = data_table_example(false); table.compute_all_size_bytes(); for row in table.to_rows() { - rec_stream.record_row(row); + rec_stream.record_row(row, false); } let storage = rec_stream.memory(); @@ -1101,7 +1115,7 @@ mod tests { let mut table = data_table_example(false); table.compute_all_size_bytes(); for row in table.to_rows() { - rec_stream.record_row(row); + rec_stream.record_row(row, false); } { @@ -1169,7 +1183,7 @@ mod tests { let mut table = data_table_example(false); table.compute_all_size_bytes(); for row in table.to_rows() { - rec_stream.record_row(row); + rec_stream.record_row(row, false); } let mut msgs = { diff --git a/docs/content/getting-started/logging-rust.md b/docs/content/getting-started/logging-rust.md index ddf74abac41a..3c79dc1458ff 100644 --- a/docs/content/getting-started/logging-rust.md +++ b/docs/content/getting-started/logging-rust.md @@ -202,11 +202,11 @@ Rerun has rich support for time: whether you want concurrent or disjoint timelin Let's add our custom timeline: ```rust -let stable_time = Timeline::new("stable_time", TimeType::Time); - for i in 0..400 { let time = i as f32 * 0.01; + rec_stream.set_time_seconds("stable_time", time as f64); + let times = offsets.iter().map(|offset| time + offset).collect_vec(); let (beads, colors): (Vec<_>, Vec<_>) = points1 .iter() @@ -226,7 +226,6 @@ for i in 0..400 { .unzip(); MsgSender::new("dna/structure/scaffolding/beads") - .with_time(stable_time, Time::from_seconds_since_epoch(time as _)) .with_component(&beads)? .with_component(&colors)? .with_splat(Radius(0.06))? @@ -234,7 +233,7 @@ for i in 0..400 { } ``` -First we [declare a name and type](https://docs.rs/rerun/latest/rerun/time/struct.Timeline.html#new) for our `Timeline`, then we pass it to [`MsgSender::with_time`](https://docs.rs/rerun/latest/rerun/struct.MsgSender.html#with_time) along with a timestamp for our data. +First we use [`RecordingStream::set_time_seconds`](https://docs.rs/rerun/latest/rerun/struct.RecordingStream.html#method.set_time_seconds) to declare our own custom `Timeline` and set the current timestamp. You can add as many timelines and timestamps as you want when logging data. ⚠️ If you run this code as is, the result will be.. surprising: the beads are animating as expected, but everything we've logged until that point is gone! ⚠️ @@ -246,9 +245,9 @@ Enter... ### Latest At semantics That's because the Rerun Viewer has switched to displaying your custom timeline by default, but the original data was only logged to the *default* timeline (called `log_time`). -To fix this, go back through the previous logging calls we made and add this: +To fix this, add this at the beginning of the main function: ```rust -.with_time(stable_time, 0) +rec_stream.set_time_seconds("stable_time", 0f64); ``` ![logging data - latest at](https://static.rerun.io/0182b4795ca2fed2f2097cfa5f5271115dee0aaf_logging_data8_latest_at.png) @@ -270,7 +269,6 @@ Expand the previous loop to also include: for i in 0..400 { // ...everything else... MsgSender::new("dna/structure") - .with_time(stable_time, Time::from_seconds_since_epoch(time as _)) .with_component(&[Transform3D::new(transform::RotationAxisAngle::new( glam::Vec3::Z, rerun::transform::Angle::Radians(time / 4.0 * TAU), diff --git a/examples/rust/api_demo/src/main.rs b/examples/rust/api_demo/src/main.rs index 322d828253a5..a44d8db87340 100644 --- a/examples/rust/api_demo/src/main.rs +++ b/examples/rust/api_demo/src/main.rs @@ -26,22 +26,15 @@ use rerun::{ re_log, re_log_types::external::{arrow2, arrow2_convert}, }, - time::{Time, TimePoint, TimeType, Timeline}, transform::{Angle, RotationAxisAngle, TranslationRotationScale3D}, Component, ComponentName, EntityPath, MsgSender, RecordingStream, }; // --- Rerun logging --- -fn sim_time(at: f64) -> TimePoint { - let timeline_sim_time = Timeline::new("sim_time", TimeType::Time); - let time = Time::from_seconds_since_epoch(at); - [(timeline_sim_time, time.into())].into() -} - fn demo_bbox(rec_stream: &RecordingStream) -> anyhow::Result<()> { + rec_stream.set_time_seconds("sim_time", 0f64); MsgSender::new("bbox_demo/bbox") - .with_timepoint(sim_time(0 as _)) .with_component(&[Box3D::new(1.0, 0.5, 0.25)])? .with_component(&[Transform3D::new(RotationAxisAngle::new( glam::Vec3::Z, @@ -52,8 +45,8 @@ fn demo_bbox(rec_stream: &RecordingStream) -> anyhow::Result<()> { .with_component(&[Label("box/t0".to_owned())])? .send(rec_stream)?; + rec_stream.set_time_seconds("sim_time", 1f64); MsgSender::new("bbox_demo/bbox") - .with_timepoint(sim_time(1 as _)) .with_component(&[Box3D::new(1.0, 0.5, 0.25)])? .with_component(&[Transform3D::new(TranslationRotationScale3D::rigid( Vec3D::new(1.0, 0.0, 0.0), @@ -69,8 +62,8 @@ fn demo_bbox(rec_stream: &RecordingStream) -> anyhow::Result<()> { fn demo_extension_components(rec_stream: &RecordingStream) -> anyhow::Result<()> { // Hack to establish 2d view bounds + rec_stream.set_time_seconds("sim_time", 0f64); MsgSender::new("extension_components") - .with_timepoint(sim_time(0 as _)) .with_component(&[Rect2D::from_xywh(0.0, 0.0, 128.0, 128.0)])? .send(rec_stream)?; @@ -89,8 +82,8 @@ fn demo_extension_components(rec_stream: &RecordingStream) -> anyhow::Result<()> } // Single point with our custom component! + rec_stream.set_time_seconds("sim_time", 0f64); MsgSender::new("extension_components/point") - .with_timepoint(sim_time(0 as _)) .with_component(&[Point2D::new(64.0, 64.0)])? .with_component(&[ColorRGBA::from_rgb(255, 0, 0)])? .with_component(&[Confidence(0.9)])? @@ -119,8 +112,8 @@ fn demo_extension_components(rec_stream: &RecordingStream) -> anyhow::Result<()> } } + rec_stream.set_time_seconds("sim_time", 1f64); MsgSender::new("extension_components/points") - .with_timepoint(sim_time(1 as _)) .with_component(&[ Point2D::new(32.0, 32.0), Point2D::new(32.0, 96.0), @@ -142,50 +135,36 @@ fn demo_extension_components(rec_stream: &RecordingStream) -> anyhow::Result<()> fn demo_log_cleared(rec_stream: &RecordingStream) -> anyhow::Result<()> { // TODO(cmc): need abstractions for this - fn log_cleared( - rec_stream: &RecordingStream, - timepoint: &TimePoint, - ent_path: impl Into, - recursive: bool, - ) { + fn log_cleared(rec_stream: &RecordingStream, ent_path: impl Into, recursive: bool) { use rerun::external::re_log_types::PathOp; - let tp = timepoint.iter().collect::>(); - let timepoint = [ - (Timeline::log_time(), Time::now().into()), - (*tp[0].0, *tp[0].1), - ]; - rec_stream.record_path_op(timepoint.into(), PathOp::clear(recursive, ent_path.into())); + rec_stream.record_path_op(PathOp::clear(recursive, ent_path.into())); } - // sim_time = 1 + rec_stream.set_time_seconds("sim_time", 1f64); MsgSender::new("null_demo/rect/0") - .with_timepoint(sim_time(1 as _)) .with_component(&[Rect2D::from_xywh(5.0, 5.0, 4.0, 4.0)])? .with_component(&[ColorRGBA::from_rgb(255, 0, 0)])? .with_component(&[Label("Rect1".into())])? .send(rec_stream)?; MsgSender::new("null_demo/rect/1") - .with_timepoint(sim_time(1 as _)) .with_component(&[Rect2D::from_xywh(10.0, 5.0, 4.0, 4.0)])? .with_component(&[ColorRGBA::from_rgb(0, 255, 0)])? .with_component(&[Label("Rect2".into())])? .send(rec_stream)?; - // sim_time = 2 - log_cleared(rec_stream, &sim_time(2 as _), "null_demo/rect/0", false); + rec_stream.set_time_seconds("sim_time", 2f64); + log_cleared(rec_stream, "null_demo/rect/0", false); - // sim_time = 3 - log_cleared(rec_stream, &sim_time(3 as _), "null_demo/rect", true); + rec_stream.set_time_seconds("sim_time", 3f64); + log_cleared(rec_stream, "null_demo/rect", true); - // sim_time = 4 + rec_stream.set_time_seconds("sim_time", 4f64); MsgSender::new("null_demo/rect/0") - .with_timepoint(sim_time(4 as _)) .with_component(&[Rect2D::from_xywh(5.0, 5.0, 4.0, 4.0)])? .send(rec_stream)?; - // sim_time = 5 + rec_stream.set_time_seconds("sim_time", 5f64); MsgSender::new("null_demo/rect/1") - .with_timepoint(sim_time(5 as _)) .with_component(&[Rect2D::from_xywh(10.0, 5.0, 4.0, 4.0)])? .send(rec_stream)?; @@ -193,13 +172,13 @@ fn demo_log_cleared(rec_stream: &RecordingStream) -> anyhow::Result<()> { } fn demo_3d_points(rec_stream: &RecordingStream) -> anyhow::Result<()> { + rec_stream.set_time_seconds("sim_time", 1f64); + MsgSender::new("3d_points/single_point_unlabeled") - .with_timepoint(sim_time(1 as _)) .with_component(&[Point3D::new(10.0, 0.0, 0.0)])? .send(rec_stream)?; MsgSender::new("3d_points/single_point_labeled") - .with_timepoint(sim_time(1 as _)) .with_component(&[Point3D::new(0.0, 0.0, 0.0)])? .with_component(&[Label("labeled point".to_owned())])? .send(rec_stream)?; @@ -227,7 +206,6 @@ fn demo_3d_points(rec_stream: &RecordingStream) -> anyhow::Result<()> { let (labels, points, radii, _) = create_points(9, |x| x * 5.0, |y| y * 5.0 + 10.0, |z| z * 4.0 - 5.0); MsgSender::new("3d_points/spiral_small") - .with_timepoint(sim_time(1 as _)) .with_component(&points)? .with_component(&labels)? .with_component(&radii)? @@ -236,7 +214,6 @@ fn demo_3d_points(rec_stream: &RecordingStream) -> anyhow::Result<()> { let (labels, points, _, colors) = create_points(100, |x| x * 5.0, |y| y * 5.0 - 10.0, |z| z * 0.4 - 5.0); MsgSender::new("3d_points/spiral_big") - .with_timepoint(sim_time(1 as _)) .with_component(&points)? .with_component(&labels)? .with_component(&colors)? @@ -250,9 +227,9 @@ fn demo_rects(rec_stream: &RecordingStream) -> anyhow::Result<()> { use ndarray_rand::{rand_distr::Uniform, RandomExt as _}; // Add an image + rec_stream.set_time_seconds("sim_time", 1f64); let img = Array::::from_elem((1024, 1024, 3, 1).f(), 128); MsgSender::new("rects_demo/img") - .with_timepoint(sim_time(1 as _)) .with_component(&[Tensor::try_from(img.as_standard_layout().view())?])? .send(rec_stream)?; @@ -268,15 +245,16 @@ fn demo_rects(rec_stream: &RecordingStream) -> anyhow::Result<()> { .axis_iter(Axis(0)) .map(|c| ColorRGBA::from_rgb(c[0], c[1], c[2])) .collect::>(); + + rec_stream.set_time_seconds("sim_time", 2f64); MsgSender::new("rects_demo/rects") - .with_timepoint(sim_time(2 as _)) .with_component(&rects)? .with_component(&colors)? .send(rec_stream)?; // Clear the rectangles by logging an empty set + rec_stream.set_time_seconds("sim_time", 3f64); MsgSender::new("rects_demo/rects") - .with_timepoint(sim_time(3 as _)) .with_component(&Vec::::new())? .send(rec_stream)?; @@ -301,48 +279,42 @@ fn colored_tensor [u8; 3]>( fn demo_2d_layering(rec_stream: &RecordingStream) -> anyhow::Result<()> { use ndarray::prelude::*; - let time = sim_time(1.0); + rec_stream.set_time_seconds("sim_time", 1f64); // Add several overlapping images. // Large dark gray in the background let img = Array::::from_elem((512, 512, 1).f(), 64); MsgSender::new("2d_layering/background") - .with_timepoint(time.clone()) .with_component(&[Tensor::try_from(img.as_standard_layout().view())?])? .with_component(&[DrawOrder(0.0)])? .send(rec_stream)?; // Smaller gradient in the middle let img = colored_tensor(256, 256, |x, y| [x as u8, y as u8, 0]); MsgSender::new("2d_layering/middle_gradient") - .with_timepoint(time.clone()) .with_component(&[Tensor::try_from(img.as_standard_layout().view())?])? .with_component(&[DrawOrder(1.0)])? .send(rec_stream)?; // Slightly smaller blue in the middle, on the same layer as the previous. let img = colored_tensor(192, 192, |_, _| [0, 0, 255]); MsgSender::new("2d_layering/middle_blue") - .with_timepoint(time.clone()) .with_component(&[Tensor::try_from(img.as_standard_layout().view())?])? .with_component(&[DrawOrder(1.0)])? .send(rec_stream)?; // Small white on top. let img = Array::::from_elem((128, 128, 1).f(), 255); MsgSender::new("2d_layering/top") - .with_timepoint(time.clone()) .with_component(&[Tensor::try_from(img.as_standard_layout().view())?])? .with_component(&[DrawOrder(2.0)])? .send(rec_stream)?; // Rectangle in between the top and the middle. MsgSender::new("2d_layering/rect_between_top_and_middle") - .with_timepoint(time.clone()) .with_component(&[Rect2D::from_xywh(64.0, 64.0, 256.0, 256.0)])? .with_component(&[DrawOrder(1.5)])? .send(rec_stream)?; // Lines behind the rectangle. MsgSender::new("2d_layering/lines_behind_rect") - .with_timepoint(time.clone()) .with_component(&[LineStrip2D( (0..20) .map(|i| Vec2D([(i * 20) as f32, (i % 2 * 100 + 100) as f32])) @@ -353,7 +325,6 @@ fn demo_2d_layering(rec_stream: &RecordingStream) -> anyhow::Result<()> { // And some points in front of the rectangle. MsgSender::new("2d_layering/points_between_top_and_middle") - .with_timepoint(time) .with_component( &(0..256) .map(|i| Point2D::new(32.0 + (i / 16) as f32 * 16.0, 64.0 + (i % 16) as f32 * 16.0)) @@ -371,13 +342,8 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { // available for this. // In either case, this raises the question of tracking time at the SDK level, akin to what the // python SDK does. - fn log_info( - rec_stream: &RecordingStream, - timepoint: TimePoint, - text: &str, - ) -> anyhow::Result<()> { + fn log_info(rec_stream: &RecordingStream, text: &str) -> anyhow::Result<()> { MsgSender::new("logs/seg_demo_log") - .with_timepoint(timepoint) .with_component(&[TextEntry::new(text, Some("INFO".into()))])? .send(rec_stream) .map_err(Into::into) @@ -390,27 +356,25 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { segmentation_img.slice_mut(s![80..100, 60..80]).fill(42); segmentation_img.slice_mut(s![20..50, 90..110]).fill(99); + rec_stream.set_time_seconds("sim_time", 1f64); + let mut tensor = Tensor::try_from(segmentation_img.as_standard_layout().view())?; tensor.meaning = TensorDataMeaning::ClassId; MsgSender::new("seg_demo/img") - .with_timepoint(sim_time(1 as _)) .with_component(&[tensor])? .send(rec_stream)?; // Log a bunch of classified 2D points MsgSender::new("seg_demo/single_point") - .with_timepoint(sim_time(1 as _)) .with_component(&[Point2D::new(64.0, 64.0)])? .with_component(&[ClassId(13)])? .send(rec_stream)?; MsgSender::new("seg_demo/single_point_labeled") - .with_timepoint(sim_time(1 as _)) .with_component(&[Point2D::new(90.0, 50.0)])? .with_component(&[ClassId(13)])? .with_component(&[Label("labeled point".into())])? .send(rec_stream)?; MsgSender::new("seg_demo/several_points0") - .with_timepoint(sim_time(1 as _)) .with_component(&[ Point2D::new(20.0, 50.0), Point2D::new(100.0, 70.0), @@ -419,7 +383,6 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { .with_splat(ClassId(42))? .send(rec_stream)?; MsgSender::new("seg_demo/several_points1") - .with_timepoint(sim_time(1 as _)) .with_component(&[ Point2D::new(40.0, 50.0), Point2D::new(120.0, 70.0), @@ -428,7 +391,6 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { .with_component(&[ClassId(13), ClassId(42), ClassId(99)])? .send(rec_stream)?; MsgSender::new("seg_demo/many points") - .with_timepoint(sim_time(1 as _)) .with_component( &(0..25) .map(|i| Point2D::new(100.0 + (i / 5) as f32 * 2.0, 100.0 + (i % 5) as f32 * 2.0)) @@ -438,10 +400,11 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { .send(rec_stream)?; log_info( rec_stream, - sim_time(1 as _), "no rects, default colored points, a single point has a label", )?; + rec_stream.set_time_seconds("sim_time", 2f64); + // Log an initial segmentation map with arbitrary colors // TODO(cmc): Gotta provide _MUCH_ better helpers for building out annotations, this is just // unapologetically painful @@ -463,7 +426,6 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { ) } MsgSender::new("seg_demo") - .with_timepoint(sim_time(2 as _)) .with_component(&[AnnotationContext { class_map: [ create_class(13, "label1".into(), None), @@ -476,14 +438,14 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { .send(rec_stream)?; log_info( rec_stream, - sim_time(2 as _), "default colored rects, default colored points, all points except the \ bottom right clusters have labels", )?; + rec_stream.set_time_seconds("sim_time", 3f64); + // Log an updated segmentation map with specific colors MsgSender::new("seg_demo") - .with_timepoint(sim_time(3 as _)) .with_component(&[AnnotationContext { class_map: [ create_class(13, "label1".into(), [255, 0, 0].into()), @@ -494,15 +456,12 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { .collect(), }])? .send(rec_stream)?; - log_info( - rec_stream, - sim_time(3 as _), - "points/rects with user specified colors", - )?; + log_info(rec_stream, "points/rects with user specified colors")?; + + rec_stream.set_time_seconds("sim_time", 4f64); // Log with a mixture of set and unset colors / labels MsgSender::new("seg_demo") - .with_timepoint(sim_time(4 as _)) .with_component(&[AnnotationContext { class_map: [ create_class(13, None, [255, 0, 0].into()), @@ -515,7 +474,6 @@ fn demo_segmentation(rec_stream: &RecordingStream) -> anyhow::Result<()> { .send(rec_stream)?; log_info( rec_stream, - sim_time(4 as _), "label1 disappears and everything with label3 is now default colored again", )?; @@ -526,17 +484,14 @@ fn demo_text_logs(rec_stream: &RecordingStream) -> anyhow::Result<()> { // TODO(cmc): the python SDK has some magic that glues the standard logger directly into rerun // logs; we're gonna need something similar for rust (e.g. `tracing` backend). + rec_stream.set_time_seconds("sim_time", 0f64); + MsgSender::new("logs") - // TODO(cmc): The original api_demo has a sim_time associated with its logs because of the - // stateful nature of time in the python SDK... This tends to show that we really need the - // same system for the Rust SDK? - .with_timepoint(sim_time(0 as _)) .with_component(&[TextEntry::new("Text with explicitly set color", None)])? .with_component(&[ColorRGBA::from_rgb(255, 215, 0)])? .send(rec_stream)?; MsgSender::new("logs") - .with_timepoint(sim_time(0 as _)) .with_component(&[TextEntry::new( "this entry has loglevel TRACE", Some("TRACE".into()), @@ -573,6 +528,8 @@ fn demo_transforms_3d(rec_stream: &RecordingStream) -> anyhow::Result<()> { log_coordinate_space(rec_stream, "transforms3d/sun/planet")?; log_coordinate_space(rec_stream, "transforms3d/sun/planet/moon")?; + rec_stream.set_time_seconds("sim_time", 0f64); + // All are in the center of their own space: fn log_point( rec_stream: &RecordingStream, @@ -581,7 +538,6 @@ fn demo_transforms_3d(rec_stream: &RecordingStream) -> anyhow::Result<()> { color: [u8; 3], ) -> anyhow::Result<()> { MsgSender::new(ent_path.into()) - .with_timepoint(sim_time(0 as _)) .with_component(&[Point3D::ZERO])? .with_component(&[Radius(radius)])? .with_component(&[ColorRGBA::from_rgb(color[0], color[1], color[2])])? @@ -614,7 +570,6 @@ fn demo_transforms_3d(rec_stream: &RecordingStream) -> anyhow::Result<()> { .take(200) .collect::>(); MsgSender::new("transforms3d/sun/planet/dust") - .with_timepoint(sim_time(0 as _)) .with_component(&points)? .with_splat(Radius(0.025))? .with_splat(ColorRGBA::from_rgb(80, 80, 80))? @@ -632,19 +587,18 @@ fn demo_transforms_3d(rec_stream: &RecordingStream) -> anyhow::Result<()> { ) }; MsgSender::new("transforms3d/sun/planet_path") - .with_timepoint(sim_time(0 as _)) .with_component(&[create_path(sun_to_planet_distance)])? .send(rec_stream)?; MsgSender::new("transforms3d/sun/planet/moon_path") - .with_timepoint(sim_time(0 as _)) .with_component(&[create_path(planet_to_moon_distance)])? .send(rec_stream)?; for i in 0..6 * 120 { let time = i as f32 / 120.0; + rec_stream.set_time_seconds("sim_time", Some(time as f64)); + MsgSender::new("transforms3d/sun/planet") - .with_timepoint(sim_time(time as _)) .with_component(&[Transform3D::new(TranslationRotationScale3D::rigid( Vec3D::new( (time * rotation_speed_planet).sin() * sun_to_planet_distance, @@ -656,7 +610,6 @@ fn demo_transforms_3d(rec_stream: &RecordingStream) -> anyhow::Result<()> { .send(rec_stream)?; MsgSender::new("transforms3d/sun/planet/moon") - .with_timepoint(sim_time(time as _)) .with_component(&[Transform3D::from_parent(Vec3D::new( (time * rotation_speed_moon).cos() * planet_to_moon_distance, (time * rotation_speed_moon).sin() * planet_to_moon_distance, diff --git a/examples/rust/clock/src/main.rs b/examples/rust/clock/src/main.rs index d609b0cd4091..7b9d1c1aaef9 100644 --- a/examples/rust/clock/src/main.rs +++ b/examples/rust/clock/src/main.rs @@ -11,7 +11,6 @@ use std::f32::consts::TAU; use rerun::components::{Arrow3D, Box3D, ColorRGBA, Radius, Vec3D, ViewCoordinates}; use rerun::coordinates::SignedAxis3; -use rerun::time::{Time, TimePoint, TimeType, Timeline}; use rerun::{external::re_log, MsgSender, RecordingStream}; #[derive(Debug, clap::Parser)] @@ -46,12 +45,6 @@ fn run(rec_stream: &RecordingStream, args: &Args) -> anyhow::Result<()> { .with_component(&[Box3D::new(LENGTH_S, LENGTH_S, 1.0)])? .send(rec_stream)?; - fn sim_time(at: f64) -> TimePoint { - let timeline_sim_time = Timeline::new("sim_time", TimeType::Time); - let time = Time::from_seconds_since_epoch(at); - [(timeline_sim_time, time.into())].into() - } - fn pos(angle: f32, length: f32) -> Vec3D { Vec3D::new(length * angle.sin(), length * angle.cos(), 0.0) } @@ -72,13 +65,14 @@ fn run(rec_stream: &RecordingStream, args: &Args) -> anyhow::Result<()> { ) -> anyhow::Result<()> { let point = pos(angle * TAU, length); let color = color(angle, blue); + + rec_stream.set_time_seconds("sim_time", step as f64); + MsgSender::new(format!("world/{name}_pt")) - .with_timepoint(sim_time(step as _)) .with_component(&[point])? .with_component(&[color])? .send(rec_stream)?; MsgSender::new(format!("world/{name}_hand")) - .with_timepoint(sim_time(step as _)) .with_component(&[Arrow3D { origin: glam::Vec3::ZERO.into(), vector: point, diff --git a/examples/rust/dna/src/main.rs b/examples/rust/dna/src/main.rs index befc2a4b8ea1..6506821f2ae5 100644 --- a/examples/rust/dna/src/main.rs +++ b/examples/rust/dna/src/main.rs @@ -8,7 +8,6 @@ use rerun::{ components::{ColorRGBA, LineStrip3D, Point3D, Radius, Transform3D, Vec3D}, demo_util::{bounce_lerp, color_spiral}, external::glam, - time::{Time, TimeType, Timeline}, MsgSender, MsgSenderError, RecordingStream, }; @@ -23,20 +22,18 @@ fn main() -> Result<(), Box> { } fn run(rec_stream: &RecordingStream) -> Result<(), MsgSenderError> { - let stable_time = Timeline::new("stable_time", TimeType::Time); - let (points1, colors1) = color_spiral(NUM_POINTS, 2.0, 0.02, 0.0, 0.1); let (points2, colors2) = color_spiral(NUM_POINTS, 2.0, 0.02, TAU * 0.5, 0.1); + rec_stream.set_time_seconds("stable_time", 0f64); + MsgSender::new("dna/structure/left") - .with_time(stable_time, 0) .with_component(&points1.iter().copied().map(Point3D::from).collect_vec())? .with_component(&colors1.iter().copied().map(ColorRGBA::from).collect_vec())? .with_splat(Radius(0.08))? .send(rec_stream)?; MsgSender::new("dna/structure/right") - .with_time(stable_time, 0) .with_component(&points2.iter().copied().map(Point3D::from).collect_vec())? .with_component(&colors2.iter().copied().map(ColorRGBA::from).collect_vec())? .with_splat(Radius(0.08))? @@ -52,7 +49,6 @@ fn run(rec_stream: &RecordingStream) -> Result<(), MsgSenderError> { .map(|positions| LineStrip3D(positions.collect_vec())) .collect_vec(); MsgSender::new("dna/structure/scaffolding") - .with_time(stable_time, 0) .with_component(&scaffolding)? .with_splat(ColorRGBA::from([128, 128, 128, 255]))? .send(rec_stream)?; @@ -64,6 +60,8 @@ fn run(rec_stream: &RecordingStream) -> Result<(), MsgSenderError> { for i in 0..400 { let time = i as f32 * 0.01; + rec_stream.set_time_seconds("stable_time", time as f64); + let times = offsets.iter().map(|offset| time + offset).collect_vec(); let (beads, colors): (Vec<_>, Vec<_>) = points1 .iter() @@ -82,14 +80,12 @@ fn run(rec_stream: &RecordingStream) -> Result<(), MsgSenderError> { }) .unzip(); MsgSender::new("dna/structure/scaffolding/beads") - .with_time(stable_time, Time::from_seconds_since_epoch(time as _)) .with_component(&beads)? .with_component(&colors)? .with_splat(Radius(0.06))? .send(rec_stream)?; MsgSender::new("dna/structure") - .with_time(stable_time, Time::from_seconds_since_epoch(time as _)) .with_component(&[Transform3D::new(rerun::transform::RotationAxisAngle::new( glam::Vec3::Z, rerun::transform::Angle::Radians(time / 4.0 * TAU), diff --git a/examples/rust/minimal_options/src/main.rs b/examples/rust/minimal_options/src/main.rs index f56ac24677cf..600fe3c6559f 100644 --- a/examples/rust/minimal_options/src/main.rs +++ b/examples/rust/minimal_options/src/main.rs @@ -6,7 +6,6 @@ //! ``` use rerun::components::{ColorRGBA, Point3D, Radius}; -use rerun::time::{TimeType, Timeline}; use rerun::{external::re_log, MsgSender, RecordingStream}; use rerun::demo_util::grid; @@ -25,8 +24,6 @@ struct Args { } fn run(rec_stream: &RecordingStream, args: &Args) -> anyhow::Result<()> { - let timeline_keyframe = Timeline::new("keyframe", TimeType::Sequence); - let points = grid( glam::Vec3::splat(-args.radius), glam::Vec3::splat(args.radius), @@ -42,11 +39,11 @@ fn run(rec_stream: &RecordingStream, args: &Args) -> anyhow::Result<()> { .map(|v| ColorRGBA::from_rgb(v.x as u8, v.y as u8, v.z as u8)) .collect::>(); + rec_stream.set_time_sequence("keyframe", 0); MsgSender::new("my_points") .with_component(&points)? .with_component(&colors)? .with_splat(Radius(0.5))? - .with_time(timeline_keyframe, 0) .send(rec_stream)?; Ok(()) diff --git a/examples/rust/raw_mesh/src/main.rs b/examples/rust/raw_mesh/src/main.rs index 1ff9459b552d..a605ca396290 100644 --- a/examples/rust/raw_mesh/src/main.rs +++ b/examples/rust/raw_mesh/src/main.rs @@ -15,7 +15,6 @@ use bytes::Bytes; use rerun::components::{ ColorRGBA, Mesh3D, MeshId, RawMesh3D, Transform3D, Vec4D, ViewCoordinates, }; -use rerun::time::{TimeType, Timeline}; use rerun::transform::TranslationRotationScale3D; use rerun::{ external::{re_log, re_memory::AccountingAllocator}, @@ -77,9 +76,8 @@ fn log_node(rec_stream: &RecordingStream, node: GltfNode) -> anyhow::Result<()> .map(Mesh3D::from) .collect::>(); - let timeline_keyframe = Timeline::new("keyframe", TimeType::Sequence); + rec_stream.set_time_sequence("keyframe", 0); MsgSender::new(ent_path) - .with_time(timeline_keyframe, 0) .with_component(&primitives)? .with_component(transform.as_ref())? .send(rec_stream)?; diff --git a/rerun_py/src/python_bridge.rs b/rerun_py/src/python_bridge.rs index 63b375118e03..dcddb931ecd0 100644 --- a/rerun_py/src/python_bridge.rs +++ b/rerun_py/src/python_bridge.rs @@ -636,14 +636,6 @@ fn flush(py: Python<'_>, blocking: bool, recording: Option<&PyRecordingStream>) // --- Time --- -fn time(timeless: bool, rec: &RecordingStream) -> TimePoint { - if timeless { - TimePoint::timeless() - } else { - rec.now() - } -} - #[pyfunction] fn set_time_sequence(timeline: &str, sequence: Option, recording: Option<&PyRecordingStream>) { let Some(recording) = get_data_recording(recording) else { return; }; @@ -731,8 +723,6 @@ fn log_view_coordinates( parse_entity_path(entity_path_str)? }; - let time_point = time(timeless, &recording); - // We currently log view coordinates from inside the bridge because the code // that does matching and validation on different string representations is // non-trivial. Implementing this functionality on the python side will take @@ -742,12 +732,12 @@ fn log_view_coordinates( let row = DataRow::from_cells1( RowId::random(), entity_path, - time_point, + TimePoint::default(), 1, [coordinates].as_slice(), ); - recording.record_row(row); + recording.record_row(row, !timeless); Ok(()) } @@ -808,8 +798,6 @@ fn log_annotation_context( }); } - let time_point = time(timeless, &recording); - // We currently log AnnotationContext from inside the bridge because it's a // fairly complex type with a need for a fair amount of data-validation. We // already have the serialization implemented in rust so we start with this @@ -820,12 +808,12 @@ fn log_annotation_context( let row = DataRow::from_cells1( RowId::random(), entity_path, - time_point, + TimePoint::default(), 1, [annotation_context].as_slice(), ); - recording.record_row(row); + recording.record_row(row, !timeless); Ok(()) } @@ -865,8 +853,6 @@ fn log_meshes( ))); } - let time_point = time(timeless, &recording); - let mut meshes = Vec::with_capacity(position_buffers.len()); for (vertex_positions, vertex_colors, indices, normals, albedo_factor) in izip!( @@ -940,12 +926,12 @@ fn log_meshes( let row = DataRow::from_cells1( RowId::random(), entity_path, - time_point, + TimePoint::default(), meshes.len() as _, meshes, ); - recording.record_row(row); + recording.record_row(row, !timeless); Ok(()) } @@ -1012,8 +998,6 @@ fn log_mesh_file( ] }; - let time_point = time(timeless, &recording); - let mesh3d = Mesh3D::Encoded(EncodedMesh3D { mesh_id: MeshId::random(), format, @@ -1031,12 +1015,12 @@ fn log_mesh_file( let row = DataRow::from_cells1( RowId::random(), entity_path, - time_point, + TimePoint::default(), 1, [mesh3d].as_slice(), ); - recording.record_row(row); + recording.record_row(row, !timeless); Ok(()) } @@ -1080,17 +1064,15 @@ fn log_image_file( let tensor = Tensor::from_image_bytes(img_bytes, img_format) .map_err(|err| PyTypeError::new_err(err.to_string()))?; - let time_point = time(timeless, &recording); - let row = DataRow::from_cells1( RowId::random(), entity_path, - time_point, + TimePoint::default(), 1, [tensor].as_slice(), ); - recording.record_row(row); + recording.record_row(row, !timeless); Ok(()) } @@ -1115,9 +1097,8 @@ fn log_cleared( let Some(recording) = get_data_recording(recording) else { return Ok(()); }; let entity_path = parse_entity_path(entity_path)?; - let timepoint = time(false, &recording); - recording.record_path_op(timepoint, PathOp::clear(recursive, entity_path)); + recording.record_path_op(PathOp::clear(recursive, entity_path)); Ok(()) } @@ -1146,20 +1127,20 @@ fn set_panel( // TODO(jleibs): Validation this is a valid blueprint path? let entity_path = parse_entity_path(entity_path)?; - // TODO(jleibs) timeless? Something else? - let timepoint = time(true, &blueprint); let panel_state = PanelState { expanded }; let row = DataRow::from_cells1( RowId::random(), entity_path, - timepoint, + TimePoint::default(), 1, [panel_state].as_slice(), ); - blueprint.record_row(row); + // TODO(jleibs) timeless? Something else? + let timeless = true; + blueprint.record_row(row, !timeless); Ok(()) } @@ -1195,40 +1176,38 @@ fn add_space_view( ) .unwrap(); - // TODO(jleibs) timeless? Something else? - let timepoint = time(true, &blueprint); - let space_view = SpaceViewComponent { space_view }; let row = DataRow::from_cells1( RowId::random(), entity_path, - timepoint, + TimePoint::default(), 1, [space_view].as_slice(), ); - blueprint.record_row(row); + // TODO(jleibs) timeless? Something else? + let timeless = true; + blueprint.record_row(row, !timeless); } #[pyfunction] fn set_auto_space_views(enabled: bool, blueprint: Option<&PyRecordingStream>) { let Some(blueprint) = get_blueprint_recording(blueprint) else { return; }; - // TODO(jleibs) timeless? Something else? - let timepoint = time(true, &blueprint); - let enable_auto_space = AutoSpaceViews(enabled); let row = DataRow::from_cells1( RowId::random(), VIEWPORT_PATH, - timepoint, + TimePoint::default(), 1, [enable_auto_space].as_slice(), ); - blueprint.record_row(row); + // TODO(jleibs) timeless? Something else? + let timeless = true; + blueprint.record_row(row, !timeless); } #[pyfunction] @@ -1247,14 +1226,17 @@ fn log_arrow_msg( let Some(recording) = get_data_recording(recording) else { return Ok(()); }; let entity_path = parse_entity_path(entity_path)?; - let timepoint = time(timeless, &recording); // It's important that we don't hold the session lock while building our arrow component. // the API we call to back through pyarrow temporarily releases the GIL, which can cause // cause a deadlock. - let row = crate::arrow::build_data_row_from_components(&entity_path, components, &timepoint)?; + let row = crate::arrow::build_data_row_from_components( + &entity_path, + components, + &TimePoint::default(), + )?; - recording.record_row(row); + recording.record_row(row, !timeless); Ok(()) }