From ac8e8802a4c978ee7bcfa0fe351c00ea6facd760 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 29 Nov 2023 10:57:38 +0100 Subject: [PATCH 1/7] introduce RecordingStreamBuilder::recording_id --- crates/re_sdk/src/recording_stream.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/re_sdk/src/recording_stream.rs b/crates/re_sdk/src/recording_stream.rs index 6af9500fbf55..7999aa174554 100644 --- a/crates/re_sdk/src/recording_stream.rs +++ b/crates/re_sdk/src/recording_stream.rs @@ -157,6 +157,23 @@ impl RecordingStreamBuilder { self } + /// Set the `RecordingId` for this context. + /// + /// If you're logging from multiple processes and want all the messages to end up in the same + /// recording, you must make sure that they all set the same `RecordingId` using this function. + /// + /// Note that many stores can share the same [`ApplicationId`], but they all have + /// unique `RecordingId`s. + /// + /// The default is to use a random `RecordingId`. + pub fn recording_id(mut self, recording_id: impl Into) -> Self { + self.store_id = Some(StoreId::from_string( + StoreKind::Recording, + recording_id.into(), + )); + self + } + /// Set the [`StoreId`] for this context. /// /// If you're logging from multiple processes and want all the messages to end up as the same From 376df07d2d6e851c535e53d5deef157a781b9ee4 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 29 Nov 2023 11:17:19 +0100 Subject: [PATCH 2/7] add shared_recording example --- Cargo.lock | 7 +++++++ examples/rust/shared_recording/Cargo.toml | 10 ++++++++++ examples/rust/shared_recording/README.md | 19 +++++++++++++++++++ examples/rust/shared_recording/src/main.rs | 14 ++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 examples/rust/shared_recording/Cargo.toml create mode 100644 examples/rust/shared_recording/README.md create mode 100644 examples/rust/shared_recording/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index eeaace5cb208..542c5de4215f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5552,6 +5552,13 @@ dependencies = [ "digest", ] +[[package]] +name = "shared_recording" +version = "0.12.0-alpha.1+dev" +dependencies = [ + "rerun", +] + [[package]] name = "signal-hook" version = "0.3.15" diff --git a/examples/rust/shared_recording/Cargo.toml b/examples/rust/shared_recording/Cargo.toml new file mode 100644 index 000000000000..242539d9b1d6 --- /dev/null +++ b/examples/rust/shared_recording/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "shared_recording" +version = "0.12.0-alpha.1+dev" +edition = "2021" +rust-version = "1.72" +license = "MIT OR Apache-2.0" +publish = false + +[dependencies] +rerun = { path = "../../../crates/rerun" } diff --git a/examples/rust/shared_recording/README.md b/examples/rust/shared_recording/README.md new file mode 100644 index 000000000000..506992c7d260 --- /dev/null +++ b/examples/rust/shared_recording/README.md @@ -0,0 +1,19 @@ +--- +title: Shared Recording +rust: https://github.com/rerun-io/rerun/tree/latest/examples/python/shared_recording/main.py?speculative-link +--- + + + + + + + + + +This example demonstrates how to use `RecordingId`s to create a single shared recording across multiple processes. + +Run the following multiple times, and you'll see that each invocation adds data to the existing recording rather than creating a new one: +```bash +cargo run +``` diff --git a/examples/rust/shared_recording/src/main.rs b/examples/rust/shared_recording/src/main.rs new file mode 100644 index 000000000000..5eb785e10a7a --- /dev/null +++ b/examples/rust/shared_recording/src/main.rs @@ -0,0 +1,14 @@ +//! Demonstrates how to use `RecordingId`s to build a single recording from multiple processes. + +fn main() -> Result<(), Box> { + let rec = rerun::RecordingStreamBuilder::new("rerun_example_shared_recording") + .recording_id("my_shared_recording") + .spawn()?; + + rec.log( + "updates", + &rerun::TextLog::new(format!("hello from process #{}", std::process::id())), + )?; + + Ok(()) +} From b637fae5d2489e89a03400463fb8e5144aa1d062 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 29 Nov 2023 11:38:28 +0100 Subject: [PATCH 3/7] expose recording_id in C SDK --- crates/rerun_c/src/lib.rs | 12 ++++++++++++ crates/rerun_c/src/rerun.h | 5 +++++ examples/c/minimal/main.c | 1 + rerun_cpp/src/rerun/c/rerun.h | 5 +++++ 4 files changed, 23 insertions(+) diff --git a/crates/rerun_c/src/lib.rs b/crates/rerun_c/src/lib.rs index 413076a902fb..33667d5dff88 100644 --- a/crates/rerun_c/src/lib.rs +++ b/crates/rerun_c/src/lib.rs @@ -118,6 +118,11 @@ pub struct CStoreInfo { /// The user-chosen name of the application doing the logging. pub application_id: CStringView, + /// The user-chosen name of the recording being logged to. + /// + /// Defaults to a random ID if unspecified. + pub recording_id: CStringView, + pub store_kind: CStoreKind, } @@ -258,6 +263,7 @@ fn rr_recording_stream_new_impl( let CStoreInfo { application_id, + recording_id, store_kind, } = *store_info; @@ -269,6 +275,12 @@ fn rr_recording_stream_new_impl( .store_source(re_log_types::StoreSource::CSdk) .default_enabled(default_enabled); + if !(recording_id.is_null() || recording_id.is_empty()) { + if let Ok(recording_id) = recording_id.as_str("recording_id") { + rec_builder = rec_builder.recording_id(recording_id); + } + } + if store_kind == CStoreKind::Blueprint { rec_builder = rec_builder.blueprint(); } diff --git a/crates/rerun_c/src/rerun.h b/crates/rerun_c/src/rerun.h index 5a28c80fd785..ca0a02c599af 100644 --- a/crates/rerun_c/src/rerun.h +++ b/crates/rerun_c/src/rerun.h @@ -134,6 +134,11 @@ typedef struct rr_store_info { /// The user-chosen name of the application doing the logging. rr_string application_id; + /// The user-chosen name of the recording being logged to. + /// + /// Defaults to a random ID if unspecified. + rr_string recording_id; + /// `RR_STORE_KIND_RECORDING` or `RR_STORE_KIND_BLUEPRINT` rr_store_kind store_kind; } rr_store_info; diff --git a/examples/c/minimal/main.c b/examples/c/minimal/main.c index 21f43739ef4a..7b8b2d2cb0d0 100644 --- a/examples/c/minimal/main.c +++ b/examples/c/minimal/main.c @@ -7,6 +7,7 @@ int main(void) { rr_error error = {0}; const rr_store_info store_info = { .application_id = rr_make_string("c-example-app"), + .recording_id = rr_make_string(NULL), .store_kind = RR_STORE_KIND_RECORDING, }; rr_recording_stream rec = rr_recording_stream_new(&store_info, true, &error); diff --git a/rerun_cpp/src/rerun/c/rerun.h b/rerun_cpp/src/rerun/c/rerun.h index 3ca29241ca5e..83a06d6331fb 100644 --- a/rerun_cpp/src/rerun/c/rerun.h +++ b/rerun_cpp/src/rerun/c/rerun.h @@ -134,6 +134,11 @@ typedef struct rr_store_info { /// The user-chosen name of the application doing the logging. rr_string application_id; + /// The user-chosen name of the recording being logged to. + /// + /// Defaults to a random ID if unspecified. + rr_string recording_id; + /// `RR_STORE_KIND_RECORDING` or `RR_STORE_KIND_BLUEPRINT` rr_store_kind store_kind; } rr_store_info; From 8b2e23892825f15281560724f3e13683bcec8d8d Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 29 Nov 2023 11:39:20 +0100 Subject: [PATCH 4/7] expose recording_id in C++ SDK --- rerun_cpp/src/rerun/recording_stream.cpp | 5 ++++- rerun_cpp/src/rerun/recording_stream.hpp | 6 +++++- rerun_cpp/tests/recording_stream.cpp | 9 +++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/rerun_cpp/src/rerun/recording_stream.cpp b/rerun_cpp/src/rerun/recording_stream.cpp index aa2dd95a31f5..11705aa34782 100644 --- a/rerun_cpp/src/rerun/recording_stream.cpp +++ b/rerun_cpp/src/rerun/recording_stream.cpp @@ -29,12 +29,15 @@ namespace rerun { return RR_STORE_KIND_RECORDING; } - RecordingStream::RecordingStream(std::string_view app_id, StoreKind store_kind) + RecordingStream::RecordingStream( + std::string_view app_id, std::optional recording_id, StoreKind store_kind + ) : _store_kind(store_kind) { check_binary_and_header_version_match().handle(); rr_store_info store_info; store_info.application_id = detail::to_rr_string(app_id); + store_info.recording_id = detail::to_rr_string(recording_id); store_info.store_kind = store_kind_to_c(store_kind); rr_error status = {}; diff --git a/rerun_cpp/src/rerun/recording_stream.hpp b/rerun_cpp/src/rerun/recording_stream.hpp index d846011edd4b..35e62690bc10 100644 --- a/rerun_cpp/src/rerun/recording_stream.hpp +++ b/rerun_cpp/src/rerun/recording_stream.hpp @@ -59,8 +59,12 @@ namespace rerun { /// Creates a new recording stream to log to. /// /// \param app_id The user-chosen name of the application doing the logging. + /// \param recording_id The user-chosen name of the recording being logged to. /// \param store_kind Whether to log to the recording store or the blueprint store. - RecordingStream(std::string_view app_id, StoreKind store_kind = StoreKind::Recording); + RecordingStream( + std::string_view app_id, std::optional recording_id = std::nullopt, + StoreKind store_kind = StoreKind::Recording + ); ~RecordingStream(); /// \private diff --git a/rerun_cpp/tests/recording_stream.cpp b/rerun_cpp/tests/recording_stream.cpp index 466278a055fa..ee3380200517 100644 --- a/rerun_cpp/tests/recording_stream.cpp +++ b/rerun_cpp/tests/recording_stream.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -73,7 +74,7 @@ SCENARIO("RecordingStream can be created, destroyed and lists correct properties AND_GIVEN("a valid application id") { THEN("creating a new stream does not log an error") { rerun::RecordingStream stream = check_logged_error([&] { - return rerun::RecordingStream("rerun_example_test", kind); + return rerun::RecordingStream("rerun_example_test", std::nullopt, kind); }); AND_THEN("it does not crash on destruction") {} @@ -99,7 +100,7 @@ SCENARIO("RecordingStream can be created, destroyed and lists correct properties AND_GIVEN("invalid utf8 character sequence for the application id") { THEN("creating a new stream logs an invalid string argument error") { check_logged_error( - [&] { rerun::RecordingStream stream("\xc3\x28", kind); }, + [&] { rerun::RecordingStream stream("\xc3\x28", std::nullopt, kind); }, rerun::ErrorCode::InvalidStringArgument ); } @@ -123,7 +124,7 @@ SCENARIO("RecordingStream can be set as global and thread local", TEST_TAG) { } WHEN("creating a new stream") { - rerun::RecordingStream stream("test", kind); + rerun::RecordingStream stream("test", std::nullopt, kind); THEN("it can be set as global") { stream.set_global(); @@ -146,7 +147,7 @@ SCENARIO("RecordingStream can be used for logging archetypes and components", TE for (auto kind : std::array{rerun::StoreKind::Recording, rerun::StoreKind::Blueprint}) { GIVEN("a store kind" << kind) { WHEN("creating a new stream") { - rerun::RecordingStream stream("test", kind); + rerun::RecordingStream stream("test", std::nullopt, kind); // We can make single components work, but this would make error messages a lot // worse since we'd have to implement the base `AsComponents` template for this. From 157b0cac7277e4f8f5d82c3e6a6f6f5718ce61a6 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 29 Nov 2023 11:39:29 +0100 Subject: [PATCH 5/7] add shared_recording cpp example --- examples/cpp/CMakeLists.txt | 7 +++-- examples/cpp/shared_recording/CMakeLists.txt | 32 ++++++++++++++++++++ examples/cpp/shared_recording/README.md | 26 ++++++++++++++++ examples/cpp/shared_recording/main.cpp | 22 ++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 examples/cpp/shared_recording/CMakeLists.txt create mode 100644 examples/cpp/shared_recording/README.md create mode 100644 examples/cpp/shared_recording/main.cpp diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 7e93ab6d746a..1102cba5ebc5 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -1,12 +1,15 @@ add_subdirectory(clock) +add_subdirectory(custom_collection_adapter) add_subdirectory(dna) +add_subdirectory(shared_recording) add_subdirectory(minimal) add_subdirectory(spawn_viewer) -add_subdirectory(custom_collection_adapter) add_custom_target(examples) + add_dependencies(examples example_clock) +add_dependencies(examples example_custom_collection_adapter) add_dependencies(examples example_dna) +add_dependencies(examples example_shared_recording) add_dependencies(examples example_minimal) add_dependencies(examples example_spawn_viewer) -add_dependencies(examples example_custom_collection_adapter) diff --git a/examples/cpp/shared_recording/CMakeLists.txt b/examples/cpp/shared_recording/CMakeLists.txt new file mode 100644 index 000000000000..981be3e8f266 --- /dev/null +++ b/examples/cpp/shared_recording/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.16...3.27) + +# If you use the example outside of the Rerun SDK you need to specify +# where the rerun_c build is to be found by setting the `RERUN_CPP_URL` variable. +# This can be done by passing `-DRERUN_CPP_URL=` to cmake. +if(DEFINED RERUN_REPOSITORY) + add_executable(example_shared_recording main.cpp) + rerun_strict_warning_settings(example_shared_recording) +else() + project(example_shared_recording LANGUAGES CXX) + + add_executable(example_shared_recording main.cpp) + + # Set the path to the rerun_c build. + set(RERUN_CPP_URL "https://github.com/rerun-io/rerun/releases/latest/download/rerun_cpp_sdk.zip" CACHE STRING "URL to the rerun_cpp zip.") + option(RERUN_FIND_PACKAGE "Whether to use find_package to find a preinstalled rerun package (instead of using FetchContent)." OFF) + + if(RERUN_FIND_PACKAGE) + find_package(rerun_sdk REQUIRED) + else() + # Download the rerun_sdk + include(FetchContent) + FetchContent_Declare(rerun_sdk URL ${RERUN_CPP_URL}) + FetchContent_MakeAvailable(rerun_sdk) + endif() + + # Rerun requires at least C++17, but it should be compatible with newer versions. + set_property(TARGET example_shared_recording PROPERTY CXX_STANDARD 17) +endif() + +# Link against rerun_sdk. +target_link_libraries(example_shared_recording PRIVATE rerun_sdk) diff --git a/examples/cpp/shared_recording/README.md b/examples/cpp/shared_recording/README.md new file mode 100644 index 000000000000..769f57755745 --- /dev/null +++ b/examples/cpp/shared_recording/README.md @@ -0,0 +1,26 @@ +--- +title: Shared Recording +rust: https://github.com/rerun-io/rerun/tree/latest/examples/rust/shared_recording/src/main.rs?speculative-link +cpp: https://github.com/rerun-io/rerun/tree/latest/examples/cpp/shared_recording/main.cpp?speculative-link +--- + + + + + + + + + +This example demonstrates how to use `RecordingId`s to create a single shared recording across multiple processes. + +To build it from a checkout of the repository (requires a Rust toolchain): +```bash +cmake . +cmake --build . --target example_shared_recording +``` + +Run the following multiple times, and you'll see that each invocation adds data to the existing recording rather than creating a new one: +```bash +./examples/cpp/shared_recording/example_shared_recording +``` diff --git a/examples/cpp/shared_recording/main.cpp b/examples/cpp/shared_recording/main.cpp new file mode 100644 index 000000000000..3ca96b06ce66 --- /dev/null +++ b/examples/cpp/shared_recording/main.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +#include +#include + +using rerun::demo::grid3d; + +int main() { + const auto rec = + rerun::RecordingStream("rerun_example_shared_recording", "my_shared_recording"); + rec.spawn().exit_on_failure(); + + int pid = getpid(); + std::ostringstream oss; + oss << "Hello from " << pid; + + rec.log("updates", rerun::TextLog(oss.str())); + + std::cout << "Run me again to append more data to the recording!" << std::endl; +} From 54f92d90632fd6095778216719695db34f794f40 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Wed, 29 Nov 2023 18:42:00 +0100 Subject: [PATCH 6/7] fix getpid windows portability issue --- examples/cpp/shared_recording/main.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/cpp/shared_recording/main.cpp b/examples/cpp/shared_recording/main.cpp index 3ca96b06ce66..3f90a22b7e56 100644 --- a/examples/cpp/shared_recording/main.cpp +++ b/examples/cpp/shared_recording/main.cpp @@ -1,7 +1,13 @@ -#include #include #include +#if defined(WIN32) +#include +#define getpid _getpid +#else +#include +#endif + #include #include From c4688be862588a7a363efde3c63f0f89bdfd2503 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Thu, 30 Nov 2023 15:01:35 +0100 Subject: [PATCH 7/7] address PR comments --- examples/cpp/shared_recording/main.cpp | 6 +----- rerun_cpp/src/rerun/recording_stream.cpp | 2 +- rerun_cpp/src/rerun/recording_stream.hpp | 2 +- rerun_cpp/tests/recording_stream.cpp | 8 ++++---- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/examples/cpp/shared_recording/main.cpp b/examples/cpp/shared_recording/main.cpp index 3f90a22b7e56..06c0c76c820e 100644 --- a/examples/cpp/shared_recording/main.cpp +++ b/examples/cpp/shared_recording/main.cpp @@ -18,11 +18,7 @@ int main() { rerun::RecordingStream("rerun_example_shared_recording", "my_shared_recording"); rec.spawn().exit_on_failure(); - int pid = getpid(); - std::ostringstream oss; - oss << "Hello from " << pid; - - rec.log("updates", rerun::TextLog(oss.str())); + rec.log("updates", rerun::TextLog(std::string("Hello from ") + std::to_string(getpid()))); std::cout << "Run me again to append more data to the recording!" << std::endl; } diff --git a/rerun_cpp/src/rerun/recording_stream.cpp b/rerun_cpp/src/rerun/recording_stream.cpp index 11705aa34782..a4dd57e72b58 100644 --- a/rerun_cpp/src/rerun/recording_stream.cpp +++ b/rerun_cpp/src/rerun/recording_stream.cpp @@ -30,7 +30,7 @@ namespace rerun { } RecordingStream::RecordingStream( - std::string_view app_id, std::optional recording_id, StoreKind store_kind + std::string_view app_id, std::string_view recording_id, StoreKind store_kind ) : _store_kind(store_kind) { check_binary_and_header_version_match().handle(); diff --git a/rerun_cpp/src/rerun/recording_stream.hpp b/rerun_cpp/src/rerun/recording_stream.hpp index 35e62690bc10..b62b3350f14e 100644 --- a/rerun_cpp/src/rerun/recording_stream.hpp +++ b/rerun_cpp/src/rerun/recording_stream.hpp @@ -62,7 +62,7 @@ namespace rerun { /// \param recording_id The user-chosen name of the recording being logged to. /// \param store_kind Whether to log to the recording store or the blueprint store. RecordingStream( - std::string_view app_id, std::optional recording_id = std::nullopt, + std::string_view app_id, std::string_view recording_id = std::string_view(), StoreKind store_kind = StoreKind::Recording ); ~RecordingStream(); diff --git a/rerun_cpp/tests/recording_stream.cpp b/rerun_cpp/tests/recording_stream.cpp index ee3380200517..45dd9a87e762 100644 --- a/rerun_cpp/tests/recording_stream.cpp +++ b/rerun_cpp/tests/recording_stream.cpp @@ -74,7 +74,7 @@ SCENARIO("RecordingStream can be created, destroyed and lists correct properties AND_GIVEN("a valid application id") { THEN("creating a new stream does not log an error") { rerun::RecordingStream stream = check_logged_error([&] { - return rerun::RecordingStream("rerun_example_test", std::nullopt, kind); + return rerun::RecordingStream("rerun_example_test", std::string_view(), kind); }); AND_THEN("it does not crash on destruction") {} @@ -100,7 +100,7 @@ SCENARIO("RecordingStream can be created, destroyed and lists correct properties AND_GIVEN("invalid utf8 character sequence for the application id") { THEN("creating a new stream logs an invalid string argument error") { check_logged_error( - [&] { rerun::RecordingStream stream("\xc3\x28", std::nullopt, kind); }, + [&] { rerun::RecordingStream stream("\xc3\x28", std::string_view(), kind); }, rerun::ErrorCode::InvalidStringArgument ); } @@ -124,7 +124,7 @@ SCENARIO("RecordingStream can be set as global and thread local", TEST_TAG) { } WHEN("creating a new stream") { - rerun::RecordingStream stream("test", std::nullopt, kind); + rerun::RecordingStream stream("test", std::string_view(), kind); THEN("it can be set as global") { stream.set_global(); @@ -147,7 +147,7 @@ SCENARIO("RecordingStream can be used for logging archetypes and components", TE for (auto kind : std::array{rerun::StoreKind::Recording, rerun::StoreKind::Blueprint}) { GIVEN("a store kind" << kind) { WHEN("creating a new stream") { - rerun::RecordingStream stream("test", std::nullopt, kind); + rerun::RecordingStream stream("test", std::string_view(), kind); // We can make single components work, but this would make error messages a lot // worse since we'd have to implement the base `AsComponents` template for this.