Skip to content

New C++ API for timestamp/duration indices #9200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 20 additions & 57 deletions crates/top/rerun_c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,14 @@ impl CSortingStatus {
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub enum CTimeType {
/// Normal wall time.
Time = 0,

/// Used e.g. for frames in a film.
Sequence = 1,

/// Nanoseconds.
Duration = 2,

/// Nanoseconds since Unix epoch (1970-01-01 00:00:00 UTC).
Timestamp = 3,
}

/// See `rr_timeline` in the C header.
Expand All @@ -251,8 +254,9 @@ impl TryFrom<CTimeline> for Timeline {
fn try_from(timeline: CTimeline) -> Result<Self, CError> {
let name = timeline.name.as_str("timeline.name")?;
let typ = match timeline.typ {
CTimeType::Time => TimeType::Time,
CTimeType::Sequence => TimeType::Sequence,
// TODO(#8635): differentiate between duration and timestamp
CTimeType::Duration | CTimeType::Timestamp => TimeType::Time,
};
Ok(Self::new(name, typ))
}
Expand Down Expand Up @@ -720,73 +724,32 @@ pub extern "C" fn rr_recording_stream_stdout(id: CRecordingStream, error: *mut C
}

#[allow(clippy::result_large_err)]
fn rr_recording_stream_set_time_sequence_impl(
fn rr_recording_stream_set_index_impl(
stream: CRecordingStream,
timeline_name: CStringView,
sequence: i64,
time_type: CTimeType,
value: i64,
) -> Result<(), CError> {
let timeline = timeline_name.as_str("timeline_name")?;
recording_stream(stream)?.set_time_sequence(timeline, sequence);
Ok(())
}

#[allow(unsafe_code)]
#[no_mangle]
pub extern "C" fn rr_recording_stream_set_time_sequence(
stream: CRecordingStream,
timeline_name: CStringView,
sequence: i64,
error: *mut CError,
) {
if let Err(err) = rr_recording_stream_set_time_sequence_impl(stream, timeline_name, sequence) {
err.write_error(error);
}
}

#[allow(clippy::result_large_err)]
fn rr_recording_stream_set_time_seconds_impl(
stream: CRecordingStream,
timeline_name: CStringView,
seconds: f64,
) -> Result<(), CError> {
let timeline = timeline_name.as_str("timeline_name")?;
recording_stream(stream)?.set_time_seconds(timeline, seconds);
Ok(())
}

#[allow(unsafe_code)]
#[no_mangle]
pub extern "C" fn rr_recording_stream_set_time_seconds(
stream: CRecordingStream,
timeline_name: CStringView,
seconds: f64,
error: *mut CError,
) {
if let Err(err) = rr_recording_stream_set_time_seconds_impl(stream, timeline_name, seconds) {
err.write_error(error);
let stream = recording_stream(stream)?;
match time_type {
CTimeType::Sequence => stream.set_time_sequence(timeline, value),
// TODO(#8635): do different things for Duration and Timestamp
CTimeType::Duration | CTimeType::Timestamp => stream.set_time_nanos(timeline, value),
}
}

#[allow(clippy::result_large_err)]
fn rr_recording_stream_set_time_nanos_impl(
stream: CRecordingStream,
timeline_name: CStringView,
nanos: i64,
) -> Result<(), CError> {
let timeline = timeline_name.as_str("timeline_name")?;
recording_stream(stream)?.set_time_nanos(timeline, nanos);
Ok(())
}

#[allow(unsafe_code)]
#[no_mangle]
pub extern "C" fn rr_recording_stream_set_time_nanos(
pub extern "C" fn rr_recording_stream_set_index(
stream: CRecordingStream,
timeline_name: CStringView,
nanos: i64,
time_type: CTimeType,
value: i64,
error: *mut CError,
) {
if let Err(err) = rr_recording_stream_set_time_nanos_impl(stream, timeline_name, nanos) {
if let Err(err) = rr_recording_stream_set_index_impl(stream, timeline_name, time_type, value) {
err.write_error(error);
}
}
Expand Down
26 changes: 22 additions & 4 deletions docs/content/reference/migration/migration-0-23.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ Previously, you could (confusingly) have two timelines with the same name, as lo
This is no longer possible.
Timelines are now uniquely identified by name, and if you use different types on the same timeline, you will get a logged warning, and the _latest_ type will be used to interpret the full set of time data.

## 🐍 Python: replaced `rr.set_time_*` with `rr.set_index`
## Rename some timeline-related things as "index"
We're planning on adding support for different types of indices in the future, so to that point we're slowly migrating our API to refer to these things as _indices_ rather than _timelines_.

## Differentiate between timestamps and durations
We've added a explicit API for setting time, where you need to explicitly specify if a time is either a timestamp (e.g. `2025-03-03T14:34:56.123456789`) or a duration (e.g. `123s`).

Before, Rerun would try to guess what you meant (small values were assumed to be durations, and large values were assumes to be durations since the Unix epoch, i.e. timestamps).
Now you need to be explicit.

### 🐍 Python: replaced `rr.set_time_*` with `rr.set_index`
We're moving towards a more explicit API for setting time, where you need to explicitly specify if a time is either a datetime (e.g. `2025-03-03T14:34:56.123456789`) or a timedelta (e.g. `123s`).

Previously we would infer the user intent at runtime based on the value: if it was large enough, it was interpreted as time since the Unix epoch, otherwise it was interpreted as a timedelta.
Expand All @@ -26,7 +35,7 @@ To this end, we're deprecated `rr.set_time_seconds`, `rr.set_time_nanos`, as wel
* [`datetime.datetime`](https://docs.python.org/3/library/datetime.html#datetime.datetime)
* [`numpy.datetime64`](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.datetime64)

### Migrating
#### Migrating
##### `rr.set_sequence("foo", 42)`
New: `rr.set_index("foo", sequence=42)`

Expand All @@ -53,11 +62,11 @@ Either:
The former is subject to (double-precision) floating point precision loss (still microsecond precision for the next century), while the latter is lossless.


## 🐍 Python: replaced `rr.Time*Column` with `rr.IndexColumn`
### 🐍 Python: replaced `rr.Time*Column` with `rr.IndexColumn`
Similarly to the above new `set_index` API, there is also a new `IndexColumn` class that replaces `TimeSequenceColumn`, `TimeSecondsColumn`, and `TimeNanosColumn`.
The migration is very similar to the above.

### Migration
#### Migration
##### `rr.TimeSequenceColumn("foo", values)`
New: `rr.IndexColumn("foo", sequence=values)`

Expand All @@ -81,6 +90,15 @@ Either:

The former is subject to (double-precision) floating point precision loss (still microsecond precision for the next century), while the latter is lossless.

### 🌊 C++: replaced `RecordingStream::set_time_*` with `set_index_*`
We've deprecated the following functions, with the following replacements:
* `set_time_sequence` -> `set_index_sequence`
* `set_time` -> `set_index_duration` or `set_index_timestamp`
* `set_time_seconds` -> `set_index_duration_secs` or `set_index_timestamp_seconds_since_epoch`
* `set_time_nanos` -> `set_index_duration_nanos` or `set_index_timestamp_nanos_since_epoch`

`TimeColumn` also has deprecated functions.

## 🐍 Python: removed `rr.log_components()`, `rr.connect()`, `rr.connect_tcp()`, and `rr.serve()`

These functions were [deprecated](migration-0-22.md#python-api-changes) in 0.22 and are no longer available.
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/image_row_updates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ int main() {
const size_t WIDTH = 300;

for (size_t t = 0; t < 20; ++t) {
rec.set_time_sequence("time", static_cast<int64_t>(t));
rec.set_index_sequence("time", static_cast<int64_t>(t));

std::vector<uint8_t> data(WIDTH * HEIGHT * 3, 0);
for (size_t i = 0; i < data.size(); i += 3) {
Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/all/archetypes/instance_poses3d_combined.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

int main() {
const auto rec = rerun::RecordingStream("rerun_example_instance_pose3d_combined");
rec.set_time_sequence("frame", 0);
rec.set_index_sequence("frame", 0);

// Log a box and points further down in the hierarchy.
rec.log("world/box", rerun::Boxes3D::from_half_sizes({{1.0, 1.0, 1.0}}));
Expand All @@ -15,7 +15,7 @@ int main() {
);

for (int i = 0; i < 180; ++i) {
rec.set_time_sequence("frame", i);
rec.set_index_sequence("frame", i);

// Log a regular transform which affects both the box and the points.
rec.log(
Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/all/archetypes/mesh3d_instancing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ int main() {
const auto rec = rerun::RecordingStream("rerun_example_mesh3d_instancing");
rec.spawn().exit_on_failure();

rec.set_time_sequence("frame", 0);
rec.set_index_sequence("frame", 0);
rec.log(
"shape",
rerun::Mesh3D(
Expand All @@ -19,7 +19,7 @@ int main() {
rec.log("shape/box", rerun::Boxes3D::from_half_sizes({{5.0f, 5.0f, 5.0f}}));

for (int i = 0; i < 100; ++i) {
rec.set_time_sequence("frame", i);
rec.set_index_sequence("frame", i);
rec.log(
"shape",
rerun::InstancePoses3D()
Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/all/archetypes/mesh3d_partial_updates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ int main() {
};

// Log the initial state of our triangle:
rec.set_time_sequence("frame", 0);
rec.set_index_sequence("frame", 0);
rec.log(
"triangle",
rerun::Mesh3D(vertex_positions)
Expand All @@ -35,7 +35,7 @@ int main() {

// Only update its vertices' positions each frame
for (int i = 1; i < 300; ++i) {
rec.set_time_sequence("frame", i);
rec.set_index_sequence("frame", i);

const auto factor = fabsf(sinf(static_cast<float>(i) * 0.04f));
const auto modified_vertex_positions = {
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/points3d_column_updates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ int main() {

// Log at seconds 10-14
auto times = rerun::Collection{10s, 11s, 12s, 13s, 14s};
auto time_column = rerun::TimeColumn::from_times("time", std::move(times));
auto time_column = rerun::TimeColumn::from_durations("time", std::move(times));

// Partition our data as expected across the 5 timesteps.
auto position = rerun::Points3D().with_positions(positions).columns({2, 4, 4, 3, 4});
Expand Down
6 changes: 3 additions & 3 deletions docs/snippets/all/archetypes/points3d_partial_updates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ int main() {
positions.emplace_back(static_cast<float>(i), 0.0f, 0.0f);
}

rec.set_time_sequence("frame", 0);
rec.set_index_sequence("frame", 0);
rec.log("points", rerun::Points3D(positions));

for (int i = 0; i < 10; ++i) {
Expand All @@ -37,14 +37,14 @@ int main() {
}

// Update only the colors and radii, leaving everything else as-is.
rec.set_time_sequence("frame", i);
rec.set_index_sequence("frame", i);
rec.log("points", rerun::Points3D::update_fields().with_radii(radii).with_colors(colors));
}

std::vector<rerun::Radius> radii;
radii.emplace_back(0.3f);

// Update the positions and radii, and clear everything else in the process.
rec.set_time_sequence("frame", 20);
rec.set_index_sequence("frame", 20);
rec.log("points", rerun::Points3D::clear_fields().with_positions(positions).with_radii(radii));
}
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/points3d_row_updates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ int main() {
std::vector<float> radii = {0.05f, 0.01f, 0.2f, 0.1f, 0.3f};

for (size_t i = 0; i < 5; i++) {
rec.set_time_seconds("time", 10.0 + static_cast<double>(i));
rec.set_index_duration_secs("time", 10.0 + static_cast<double>(i));
rec.log(
"points",
rerun::Points3D(positions[i]).with_colors(colors[i]).with_radii(radii[i])
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/scalar_multiple_plots.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ int main() {

// Log the data on a timeline called "step".
for (int t = 0; t < static_cast<int>(TAU * 2.0 * 100.0); ++t) {
rec.set_time_sequence("step", t);
rec.set_index_sequence("step", t);

rec.log("trig/sin", rerun::Scalar(sin(t / 100.0)));
rec.log("trig/cos", rerun::Scalar(cos(static_cast<float>(t) / 100.0f)));
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/scalar_row_updates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ int main() {
rec.spawn().exit_on_failure();

for (int step = 0; step < 64; ++step) {
rec.set_time_sequence("step", step);
rec.set_index_sequence("step", step);
rec.log("scalars", rerun::Scalar(sin(static_cast<double>(step) / 10.0)));
}
}
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/scalar_simple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ int main() {

// Log the data on a timeline called "step".
for (int step = 0; step < 64; ++step) {
rec.set_time_sequence("step", step);
rec.set_index_sequence("step", step);
rec.log("scalar", rerun::Scalar(std::sin(static_cast<double>(step) / 10.0)));
}
}
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/series_line_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ int main() {

// Log the data on a timeline called "step".
for (int t = 0; t < static_cast<int>(TAU * 2.0 * 100.0); ++t) {
rec.set_time_sequence("step", t);
rec.set_index_sequence("step", t);

rec.log("trig/sin", rerun::Scalar(sin(static_cast<double>(t) / 100.0)));
rec.log("trig/cos", rerun::Scalar(cos(static_cast<double>(t) / 100.0)));
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/series_point_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ int main() {

// Log the data on a timeline called "step".
for (int t = 0; t < static_cast<int>(TAU * 2.0 * 10.0); ++t) {
rec.set_time_sequence("step", t);
rec.set_index_sequence("step", t);

rec.log("trig/sin", rerun::Scalar(sin(static_cast<double>(t) / 10.0)));
rec.log("trig/cos", rerun::Scalar(cos(static_cast<double>(t) / 10.0)));
Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/all/archetypes/transform3d_axes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ int main() {

auto base_axes = rerun::Transform3D().with_axis_length(1.0);

rec.set_time_sequence("step", 0);
rec.set_index_sequence("step", 0);

rec.log("base", base_axes);

for (int deg = 0; deg < 360; deg++) {
rec.set_time_sequence("step", deg);
rec.set_index_sequence("step", deg);

rec.log(
"base/rotated",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ int main() {
const auto rec = rerun::RecordingStream("rerun_example_transform3d_column_updates");
rec.spawn().exit_on_failure();

rec.set_time_sequence("tick", 0);
rec.set_index_sequence("tick", 0);
rec.log(
"box",
rerun::Boxes3D::from_half_sizes({{4.f, 2.f, 1.0f}}).with_fill_mode(rerun::FillMode::Solid),
Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/all/archetypes/transform3d_hierarchy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ int main() {

// TODO(#5521): log two views as in the python example

rec.set_time_seconds("sim_time", 0.0);
rec.set_index_duration_secs("sim_time", 0.0);

// Planetary motion is typically in the XY plane.
rec.log_static("/", rerun::ViewCoordinates::RIGHT_HAND_Z_UP);
Expand Down Expand Up @@ -52,7 +52,7 @@ int main() {
// Movement via transforms.
for (int i = 0; i < 6 * 120; i++) {
float time = static_cast<float>(i) / 120.0f;
rec.set_time_seconds("sim_time", time);
rec.set_index_duration_secs("sim_time", time);
float r_moon = time * 5.0f;
float r_planet = time * 2.0f;

Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/all/archetypes/transform3d_row_updates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ int main() {
const auto rec = rerun::RecordingStream("rerun_example_transform3d_row_updates");
rec.spawn().exit_on_failure();

rec.set_time_sequence("tick", 0);
rec.set_index_sequence("tick", 0);
rec.log(
"box",
rerun::Boxes3D::from_half_sizes({{4.f, 2.f, 1.0f}}).with_fill_mode(rerun::FillMode::Solid),
rerun::Transform3D().with_axis_length(10.0)
);

for (int t = 0; t < 100; t++) {
rec.set_time_sequence("tick", t + 1);
rec.set_index_sequence("tick", t + 1);
rec.log(
"box",
rerun::Transform3D()
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/all/archetypes/video_auto_frames.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ int main(int argc, char* argv[]) {
video_asset.read_frame_timestamps_ns().value_or_throw();
// Note timeline values don't have to be the same as the video timestamps.
auto time_column =
rerun::TimeColumn::from_times("video_time", rerun::borrow(frame_timestamps_ns));
rerun::TimeColumn::from_durations("video_time", rerun::borrow(frame_timestamps_ns));

std::vector<rerun::components::VideoTimestamp> video_timestamps(frame_timestamps_ns.size());
for (size_t i = 0; i < frame_timestamps_ns.size(); i++) {
Expand Down
Loading
Loading