diff --git a/crates/rerun_c/src/lib.rs b/crates/rerun_c/src/lib.rs index ebac91fcf505..fcbc4b9e9734 100644 --- a/crates/rerun_c/src/lib.rs +++ b/crates/rerun_c/src/lib.rs @@ -161,6 +161,7 @@ pub enum CErrorCode { _CategoryRecordingStream = 0x0000_00100, RecordingStreamCreationFailure, RecordingStreamSaveFailure, + RecordingStreamStdoutFailure, // TODO(cmc): Really this should be its own category… RecordingStreamSpawnFailure, @@ -468,6 +469,24 @@ pub extern "C" fn rr_recording_stream_save( } } +#[allow(clippy::result_large_err)] +fn rr_recording_stream_stdout_impl(stream: CRecordingStream) -> Result<(), CError> { + recording_stream(stream)?.stdout().map_err(|err| { + CError::new( + CErrorCode::RecordingStreamStdoutFailure, + &format!("Failed to forward recording stream to stdout: {err}"), + ) + }) +} + +#[allow(unsafe_code)] +#[no_mangle] +pub extern "C" fn rr_recording_stream_stdout(id: CRecordingStream, error: *mut CError) { + if let Err(err) = rr_recording_stream_stdout_impl(id) { + err.write_error(error); + } +} + #[allow(clippy::result_large_err)] fn rr_recording_stream_set_time_sequence_impl( stream: CRecordingStream, diff --git a/crates/rerun_c/src/rerun.h b/crates/rerun_c/src/rerun.h index ca0a02c599af..34f72b1d3840 100644 --- a/crates/rerun_c/src/rerun.h +++ b/crates/rerun_c/src/rerun.h @@ -198,6 +198,7 @@ enum { _RR_ERROR_CODE_CATEGORY_RECORDING_STREAM = 0x000000100, RR_ERROR_CODE_RECORDING_STREAM_CREATION_FAILURE, RR_ERROR_CODE_RECORDING_STREAM_SAVE_FAILURE, + RR_ERROR_CODE_RECORDING_STREAM_STDOUT_FAILURE, RR_ERROR_CODE_RECORDING_STREAM_SPAWN_FAILURE, // Arrow data processing errors. @@ -333,6 +334,16 @@ extern void rr_recording_stream_spawn( /// This function returns immediately. extern void rr_recording_stream_save(rr_recording_stream stream, rr_string path, rr_error* error); +/// Stream all log-data to stdout. +/// +/// Pipe the result into the Rerun Viewer to visualize it. +/// +/// If there isn't any listener at the other end of the pipe, the `RecordingStream` will +/// default back to `buffered` mode, in order not to break the user's terminal. +/// +/// This function returns immediately. +extern void rr_recording_stream_stdout(rr_recording_stream stream, rr_error* error); + /// Initiates a flush the batching pipeline and waits for it to propagate. /// /// See `rr_recording_stream` docs for ordering semantics and multithreading guarantees. diff --git a/docs/content/reference/sdk-operating-modes.md b/docs/content/reference/sdk-operating-modes.md index 9c8bcfa3d1c5..e59312d03f71 100644 --- a/docs/content/reference/sdk-operating-modes.md +++ b/docs/content/reference/sdk-operating-modes.md @@ -80,6 +80,12 @@ Use [`RecordingStream::save`](https://docs.rs/rerun/latest/rerun/struct.Recordin Streams all logging data to standard output, which can then be loaded by the Rerun Viewer by streaming it from standard input. +#### C++ + +Use [`RecordingStream::stdout`](https://ref.rerun.io/docs/cpp/stable/classrerun_1_1RecordingStream.html#SOMEHASH?speculative-link). + +Check out our [dedicated example](https://github.com/rerun-io/rerun/tree/latest/examples/cpp/stdio/main.cpp?speculative-link). + #### Python Use [`rr.stdout`](https://ref.rerun.io/docs/python/stable/common/initialization_functions/#rerun.stdout?speculative-link). diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 1102cba5ebc5..c603e3c35dff 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -1,15 +1,17 @@ add_subdirectory(clock) add_subdirectory(custom_collection_adapter) add_subdirectory(dna) -add_subdirectory(shared_recording) add_subdirectory(minimal) +add_subdirectory(shared_recording) add_subdirectory(spawn_viewer) +add_subdirectory(stdio) 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_shared_recording) add_dependencies(examples example_spawn_viewer) +add_dependencies(examples example_stdio) diff --git a/examples/cpp/stdio/CMakeLists.txt b/examples/cpp/stdio/CMakeLists.txt new file mode 100644 index 000000000000..bf72c54d6cae --- /dev/null +++ b/examples/cpp/stdio/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_stdio main.cpp) + rerun_strict_warning_settings(example_stdio) +else() + project(example_stdio LANGUAGES CXX) + + add_executable(example_stdio 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_stdio PROPERTY CXX_STANDARD 17) +endif() + +# Link against rerun_sdk. +target_link_libraries(example_stdio PRIVATE rerun_sdk) diff --git a/examples/cpp/stdio/README.md b/examples/cpp/stdio/README.md new file mode 100644 index 000000000000..823c505c7740 --- /dev/null +++ b/examples/cpp/stdio/README.md @@ -0,0 +1,24 @@ +--- +title: Standard Input/Output example +python: https://github.com/rerun-io/rerun/tree/latest/examples/python/stdio/main.py?speculative-link +rust: https://github.com/rerun-io/rerun/tree/latest/examples/rust/stdio/src/main.rs?speculative-link +cpp: https://github.com/rerun-io/rerun/tree/latest/examples/cpp/stdio/main.cpp?speculative-link +thumbnail: https://static.rerun.io/stdio/25c5aba992d4c8b3861386d8d9539a4823dca117/480w.png +--- + + + + + + + + + +Demonstrates how to log data to standard output with the Rerun SDK, and then visualize it from standard input with the Rerun Viewer. + +To build it from a checkout of the repository (requires a Rust toolchain): +```bash +cmake . +cmake --build . --target example_stdio +echo 'hello from stdin!' | ./examples/cpp/stdio/example_stdio | rerun - +``` diff --git a/examples/cpp/stdio/main.cpp b/examples/cpp/stdio/main.cpp new file mode 100644 index 000000000000..ce84081aad67 --- /dev/null +++ b/examples/cpp/stdio/main.cpp @@ -0,0 +1,17 @@ +#include +#include + +#include + +int main() { + const auto rec = rerun::RecordingStream("rerun_example_stdio"); + rec.to_stdout().exit_on_failure(); + + std::string input; + std::string line; + while (std::getline(std::cin, line)) { + input += line + '\n'; + } + + rec.log("stdin", rerun::TextDocument(input)); +} diff --git a/rerun_cpp/src/rerun/c/rerun.h b/rerun_cpp/src/rerun/c/rerun.h index 83a06d6331fb..93a072b9c3df 100644 --- a/rerun_cpp/src/rerun/c/rerun.h +++ b/rerun_cpp/src/rerun/c/rerun.h @@ -198,6 +198,7 @@ enum { _RR_ERROR_CODE_CATEGORY_RECORDING_STREAM = 0x000000100, RR_ERROR_CODE_RECORDING_STREAM_CREATION_FAILURE, RR_ERROR_CODE_RECORDING_STREAM_SAVE_FAILURE, + RR_ERROR_CODE_RECORDING_STREAM_STDOUT_FAILURE, RR_ERROR_CODE_RECORDING_STREAM_SPAWN_FAILURE, // Arrow data processing errors. @@ -333,6 +334,16 @@ extern void rr_recording_stream_spawn( /// This function returns immediately. extern void rr_recording_stream_save(rr_recording_stream stream, rr_string path, rr_error* error); +/// Stream all log-data to stdout. +/// +/// Pipe the result into the Rerun Viewer to visualize it. +/// +/// If there isn't any listener at the other end of the pipe, the `RecordingStream` will +/// default back to `buffered` mode, in order not to break the user's terminal. +/// +/// This function returns immediately. +extern void rr_recording_stream_stdout(rr_recording_stream stream, rr_error* error); + /// Initiates a flush the batching pipeline and waits for it to propagate. /// /// See `rr_recording_stream` docs for ordering semantics and multithreading guarantees. diff --git a/rerun_cpp/src/rerun/error.hpp b/rerun_cpp/src/rerun/error.hpp index 3623bf691553..a1b71c01ef42 100644 --- a/rerun_cpp/src/rerun/error.hpp +++ b/rerun_cpp/src/rerun/error.hpp @@ -45,6 +45,7 @@ namespace rerun { _CategoryRecordingStream = 0x0000'0100, RecordingStreamCreationFailure, RecordingStreamSaveFailure, + RecordingStreamStdoutFailure, RecordingStreamSpawnFailure, // Arrow data processing errors. diff --git a/rerun_cpp/src/rerun/recording_stream.cpp b/rerun_cpp/src/rerun/recording_stream.cpp index a4dd57e72b58..bd77c196cab6 100644 --- a/rerun_cpp/src/rerun/recording_stream.cpp +++ b/rerun_cpp/src/rerun/recording_stream.cpp @@ -128,6 +128,12 @@ namespace rerun { return status; } + Error RecordingStream::to_stdout() const { + rr_error status = {}; + rr_recording_stream_stdout(_id, &status); + return status; + } + void RecordingStream::flush_blocking() const { rr_recording_stream_flush_blocking(_id); } diff --git a/rerun_cpp/src/rerun/recording_stream.hpp b/rerun_cpp/src/rerun/recording_stream.hpp index b62b3350f14e..e3e063ade4d3 100644 --- a/rerun_cpp/src/rerun/recording_stream.hpp +++ b/rerun_cpp/src/rerun/recording_stream.hpp @@ -174,6 +174,20 @@ namespace rerun { /// This function returns immediately. Error save(std::string_view path) const; + /// Stream all log-data to standard output. + /// + /// Pipe the result into the Rerun Viewer to visualize it. + /// + /// If there isn't any listener at the other end of the pipe, the `RecordingStream` will + /// default back to `buffered` mode, in order not to break the user's terminal. + /// + /// This function returns immediately. + // + // NOTE: This should be called `stdout` like in other SDK, but turns out that `stdout` is a + // macro when compiling with msvc [1]. + // [1]: https://learn.microsoft.com/en-us/cpp/c-runtime-library/stdin-stdout-stderr?view=msvc-170 + Error to_stdout() const; + /// Initiates a flush the batching pipeline and waits for it to propagate. /// /// See `RecordingStream` docs for ordering semantics and multithreading guarantees.