From 76d84d809a4fcd31bd81999fd288e688384c7cba Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Mon, 4 Mar 2024 11:32:51 +0100 Subject: [PATCH] wip --- crates/rerun_c/src/rerun.h | 29 ++++- examples/cpp/external_data_loader/main.cpp | 118 +++++++++++++-------- examples/cpp/log_file/main.cpp | 5 +- rerun_cpp/src/rerun/c/rerun.h | 29 ++++- rerun_cpp/src/rerun/recording_stream.cpp | 20 +++- rerun_cpp/src/rerun/recording_stream.hpp | 66 ++++++++++-- rerun_cpp/tests/CMakeLists.txt | 2 +- 7 files changed, 210 insertions(+), 59 deletions(-) diff --git a/crates/rerun_c/src/rerun.h b/crates/rerun_c/src/rerun.h index 7f1ab264501ab..b97a257c34785 100644 --- a/crates/rerun_c/src/rerun.h +++ b/crates/rerun_c/src/rerun.h @@ -141,6 +141,30 @@ typedef struct rr_spawn_options { rr_string executable_path; } rr_spawn_options; +/// Recommended settings for the [`DataLoader`]. +/// +/// The loader is free to ignore some or all of these. +/// +/// Refer to the field-level documentation for more information about each individual options. +// +// TODO(#3841): expose timepoint settings once we implement stateless APIs +typedef struct rr_data_loader_settings { + /// The recommended `RecordingId` to log the data to. + /// + /// Unspecified by default. + rr_string recording_id; + + /// What should the logged entity paths be prefixed with? + /// + /// Unspecified by default. + rr_string entity_path_prefix; + + /// Should the logged data be timeless? + /// + /// Defaults to `false` if not set. + bool timeless; +} rr_data_loader_settings; + typedef struct rr_store_info { /// The user-chosen name of the application doing the logging. rr_string application_id; @@ -432,7 +456,7 @@ extern void rr_recording_stream_log( /// /// See for more information. extern void rr_recording_stream_log_file_from_path( - rr_recording_stream stream, rr_string path, rr_error* error + rr_recording_stream stream, rr_data_loader_settings settings, rr_string path, rr_error* error ); /// Logs the given `contents` using all `DataLoader`s available. @@ -444,7 +468,8 @@ extern void rr_recording_stream_log_file_from_path( /// /// See for more information. extern void rr_recording_stream_log_file_from_contents( - rr_recording_stream stream, rr_string path, rr_bytes contents, rr_error* error + rr_recording_stream stream, rr_data_loader_settings settings, rr_string path, rr_bytes contents, + rr_error* error ); // ---------------------------------------------------------------------------- diff --git a/examples/cpp/external_data_loader/main.cpp b/examples/cpp/external_data_loader/main.cpp index adddc44269c2f..8196b8496a6d4 100644 --- a/examples/cpp/external_data_loader/main.cpp +++ b/examples/cpp/external_data_loader/main.cpp @@ -1,11 +1,54 @@ +#include #include #include #include #include #include +#include +#include + +void set_time_from_args(const rerun::RecordingStream& rec, cxxopts::ParseResult& args) { + if (args.count("time")) { + const auto times = args["time"].as>(); + for (const auto& time_str : times) { + auto pos = time_str.find('='); + if (pos != std::string::npos) { + auto timeline_name = time_str.substr(0, pos); + int64_t time = std::stoi(time_str.substr(pos + 1)); + rec.set_time_seconds(timeline_name, time); + } + } + } + + if (args.count("sequence")) { + const auto sequences = args["sequence"].as>(); + for (const auto& sequence_str : sequences) { + auto pos = sequence_str.find('='); + if (pos != std::string::npos) { + auto timeline_name = sequence_str.substr(0, pos); + int64_t sequence = std::stoi(sequence_str.substr(pos + 1)); + rec.set_time_sequence(timeline_name, sequence); + } + } + } +} + +int main(int argc, char* argv[]) { + // The Rerun Viewer will always pass these two pieces of information: + // 1. The path to be loaded, as a positional arg. + // 2. A shared recording ID, via the `--recording-id` flag. + // + // It is up to you whether you make use of that shared recording ID or not. + // If you use it, the data will end up in the same recording as all other plugins interested in + // that file, otherwise you can just create a dedicated recording for it. Or both. + // + // Check out `re_data_source::DataLoaderSettings` documentation for an exhaustive listing of + // the available CLI parameters. -static const char* USAGE = R"( + cxxopts::Options options( + "rerun-loader-cpp-file", + R"( This is an example executable data-loader plugin for the Rerun Viewer. Any executable on your `$PATH` with a name that starts with `rerun-loader-` will be treated as an external data-loader. @@ -15,51 +58,31 @@ special exit code to indicate that it doesn't support anything else. To try it out, compile it and place it in your $PATH as `rerun-loader-cpp-file`, then open a C++ source file with Rerun (`rerun file.cpp`). +)" + ); -USAGE: - rerun-loader-cpp-file [OPTIONS] FILEPATH - -FLAGS: - -h, --help Prints help information - -OPTIONS: - --recording-id RECORDING_ID ID of the shared recording - -ARGS: - -)"; - -int main(int argc, char* argv[]) { - // The Rerun Viewer will always pass these two pieces of information: - // 1. The path to be loaded, as a positional arg. - // 2. A shared recording ID, via the `--recording-id` flag. - // - // It is up to you whether you make use of that shared recording ID or not. - // If you use it, the data will end up in the same recording as all other plugins interested in - // that file, otherwise you can just create a dedicated recording for it. Or both. - std::string filepath; - std::string recording_id; - - for (int i = 1; i < argc; ++i) { - std::string arg(argv[i]); - - if (arg == "--recording-id") { - if (i + 1 < argc) { - recording_id = argv[i + 1]; - ++i; - } else { - std::cerr << USAGE << std::endl; - return 1; - } - } else { - filepath = arg; - } + // clang-format off + options.add_options() + ("h,help", "Print usage") + ("filepath", "The filepath to be loaded and logged", cxxopts::value()) + ("recording-id", "Optional recommended ID for the recording", cxxopts::value()) + ("entity-path-prefix", "Optional prefix for all entity paths", cxxopts::value()) + ("timeless", "Optionally mark data to be logged as timeless", cxxopts::value()->default_value("false")) + ("time", "Optional timestamps to log at (e.g. `--time sim_time=1709203426`) (repeatable)", cxxopts::value>()) + ("sequence", "Optional sequences to log at (e.g. `--sequence sim_frame=42`) (repeatable)", cxxopts::value>()) + ; + // clang-format on + + options.parse_positional({"filepath"}); + + auto args = options.parse(argc, argv); + + if (args.count("help")) { + std::cout << options.help() << std::endl; + exit(0); } - if (filepath.empty()) { - std::cerr << USAGE << std::endl; - return 1; - } + const auto filepath = args["filepath"].as(); bool is_file = std::filesystem::is_regular_file(filepath); bool is_cpp_file = std::filesystem::path(filepath).extension().string() == ".cpp"; @@ -75,12 +98,19 @@ int main(int argc, char* argv[]) { std::string text = "## Some C++ code\n```cpp\n" + body.str() + "\n```\n"; + auto recording_id = std::string_view(); + if (args.count("recording-id")) { + recording_id = args["recording-id"].as(); + } const auto rec = rerun::RecordingStream("rerun_example_external_data_loader", recording_id); // The most important part of this: log to standard output so the Rerun Viewer can ingest it! rec.to_stdout().exit_on_failure(); - rec.log_timeless( + set_time_from_args(rec, args); + + rec.log_with_timeless( filepath, + args["timeless"].as(), rerun::TextDocument(text).with_media_type(rerun::MediaType::markdown()) ); } diff --git a/examples/cpp/log_file/main.cpp b/examples/cpp/log_file/main.cpp index fa37f2e0aaa66..83e287f3412a4 100644 --- a/examples/cpp/log_file/main.cpp +++ b/examples/cpp/log_file/main.cpp @@ -58,7 +58,7 @@ int main(int argc, char** argv) { for (const auto& filepath : filepaths) { if (!from_contents) { // Either log the file using its path… - rec.log_file_from_path(filepath); + rec.log_file_from_path(filepath, "log_file_example"); } else { // …or using its contents if you already have them loaded for some reason. if (std::filesystem::is_regular_file(filepath)) { @@ -70,7 +70,8 @@ int main(int argc, char** argv) { rec.log_file_from_contents( filepath, reinterpret_cast(data.c_str()), - data.size() + data.size(), + entity_path_prefix = "log_file_example" ); } } diff --git a/rerun_cpp/src/rerun/c/rerun.h b/rerun_cpp/src/rerun/c/rerun.h index 1158f00ee31ff..83b810be6c564 100644 --- a/rerun_cpp/src/rerun/c/rerun.h +++ b/rerun_cpp/src/rerun/c/rerun.h @@ -141,6 +141,30 @@ typedef struct rr_spawn_options { rr_string executable_path; } rr_spawn_options; +/// Recommended settings for the [`DataLoader`]. +/// +/// The loader is free to ignore some or all of these. +/// +/// Refer to the field-level documentation for more information about each individual options. +// +// TODO(#3841): expose timepoint settings once we implement stateless APIs +typedef struct rr_data_loader_settings { + /// The recommended `RecordingId` to log the data to. + /// + /// Unspecified by default. + rr_string recording_id; + + /// What should the logged entity paths be prefixed with? + /// + /// Unspecified by default. + rr_string entity_path_prefix; + + /// Should the logged data be timeless? + /// + /// Defaults to `false` if not set. + bool timeless; +} rr_data_loader_settings; + typedef struct rr_store_info { /// The user-chosen name of the application doing the logging. rr_string application_id; @@ -432,7 +456,7 @@ extern void rr_recording_stream_log( /// /// See for more information. extern void rr_recording_stream_log_file_from_path( - rr_recording_stream stream, rr_string path, rr_error* error + rr_recording_stream stream, rr_data_loader_settings settings, rr_string path, rr_error* error ); /// Logs the given `contents` using all `DataLoader`s available. @@ -444,7 +468,8 @@ extern void rr_recording_stream_log_file_from_path( /// /// See for more information. extern void rr_recording_stream_log_file_from_contents( - rr_recording_stream stream, rr_string path, rr_bytes contents, rr_error* error + rr_recording_stream stream, rr_data_loader_settings settings, rr_string path, rr_bytes contents, + rr_error* error ); // ---------------------------------------------------------------------------- diff --git a/rerun_cpp/src/rerun/recording_stream.cpp b/rerun_cpp/src/rerun/recording_stream.cpp index 0c45d32990ae1..f51505e9bb3d2 100644 --- a/rerun_cpp/src/rerun/recording_stream.cpp +++ b/rerun_cpp/src/rerun/recording_stream.cpp @@ -257,14 +257,23 @@ namespace rerun { return status; } - Error RecordingStream::try_log_file_from_path(const std::filesystem::path& filepath) const { + Error RecordingStream::try_log_file_from_path( + const std::filesystem::path& filepath, std::string_view recording_id, + std::string_view entity_path_prefix, bool timeless + ) const { if (!is_enabled()) { return Error::ok(); } + rr_data_loader_settings settings = {}; + settings.recording_id = detail::to_rr_string(recording_id); + settings.entity_path_prefix = detail::to_rr_string(entity_path_prefix); + settings.timeless = timeless; + rr_error status = {}; rr_recording_stream_log_file_from_path( _id, + settings, detail::to_rr_string(filepath.string()), &status ); @@ -273,7 +282,8 @@ namespace rerun { } Error RecordingStream::try_log_file_from_contents( - const std::filesystem::path& filepath, const std::byte* contents, size_t contents_size + const std::filesystem::path& filepath, const std::byte* contents, size_t contents_size, + std::string_view recording_id, std::string_view entity_path_prefix, bool timeless ) const { if (!is_enabled()) { return Error::ok(); @@ -283,9 +293,15 @@ namespace rerun { data.bytes = reinterpret_cast(contents); data.length = static_cast(contents_size); + rr_data_loader_settings settings = {}; + settings.recording_id = detail::to_rr_string(recording_id); + settings.entity_path_prefix = detail::to_rr_string(entity_path_prefix); + settings.timeless = timeless; + rr_error status = {}; rr_recording_stream_log_file_from_contents( _id, + settings, detail::to_rr_string(filepath.string()), data, &status diff --git a/rerun_cpp/src/rerun/recording_stream.hpp b/rerun_cpp/src/rerun/recording_stream.hpp index b8c7a4424c571..31cd9d3f54e20 100644 --- a/rerun_cpp/src/rerun/recording_stream.hpp +++ b/rerun_cpp/src/rerun/recording_stream.hpp @@ -3,6 +3,7 @@ #include #include // uint32_t etc. #include +#include #include #include @@ -402,6 +403,27 @@ namespace rerun { return try_log_with_timeless(entity_path, true, archetypes_or_collectiones...); } + /// Logs one or more archetype and/or component batches optionally timeless, returning an error. + /// + /// See `log`/`log_timeless` for more information. + /// Returns an error if an error occurs during serialization or logging. + /// + /// \param entity_path Path to the entity in the space hierarchy. + /// \param timeless If true, the logged components will be timeless. + /// Otherwise, the data will be timestamped automatically with `log_time` and `log_tick`. + /// Additional timelines set by `set_time_sequence` or `set_time` will also be included. + /// \param archetypes_or_collectiones Any type for which the `AsComponents` trait is implemented. + /// This is the case for any archetype or `std::vector`/`std::array`/C-array of components implements. + /// \returns An error if an error occurs during serialization or logging. + /// + /// @see log, try_log, log_timeless, try_log_timeless + template + void log_with_timeless( + std::string_view entity_path, bool timeless, const Ts&... archetypes_or_collectiones + ) const { + try_log_with_timeless(entity_path, timeless, archetypes_or_collectiones...).handle(); + } + /// Logs one or more archetype and/or component batches optionally timeless, returning an error. /// /// See `log`/`log_timeless` for more information. @@ -505,10 +527,17 @@ namespace rerun { /// See for more information. /// /// \param filepath Path to the file to be logged. + /// \param recording_id The recommended `RecordingId` to log the data to. + /// \param entity_path_prefix What should the logged entity paths be prefixed with? + /// \param timeless Should the logged data be timeless? /// /// \see `try_log_file_from_path` - void log_file_from_path(const std::filesystem::path& filepath) const { - try_log_file_from_path(filepath).handle(); + void log_file_from_path( + const std::filesystem::path& filepath, + std::string_view recording_id = std::string_view(), + std::string_view entity_path_prefix = std::string_view(), bool timeless = false + ) const { + try_log_file_from_path(filepath, recording_id, entity_path_prefix, timeless).handle(); } /// Logs the file at the given `path` using all `DataLoader`s available. @@ -521,9 +550,16 @@ namespace rerun { /// See for more information. /// /// \param filepath Path to the file to be logged. + /// \param recording_id The recommended `RecordingId` to log the data to. + /// \param entity_path_prefix What should the logged entity paths be prefixed with? + /// \param timeless Should the logged data be timeless? /// /// \see `log_file_from_path` - Error try_log_file_from_path(const std::filesystem::path& filepath) const; + Error try_log_file_from_path( + const std::filesystem::path& filepath, + std::string_view recording_id = std::string_view(), + std::string_view entity_path_prefix = std::string_view(), bool timeless = false + ) const; /// Logs the given `contents` using all `DataLoader`s available. /// @@ -537,12 +573,25 @@ namespace rerun { /// \param filepath Path to the file that the `contents` belong to. /// \param contents Contents to be logged. /// \param contents_size Size in bytes of the `contents`. + /// \param recording_id The recommended `RecordingId` to log the data to. + /// \param entity_path_prefix What should the logged entity paths be prefixed with? + /// \param timeless Should the logged data be timeless? /// /// \see `try_log_file_from_contents` void log_file_from_contents( - const std::filesystem::path& filepath, const std::byte* contents, size_t contents_size + const std::filesystem::path& filepath, const std::byte* contents, size_t contents_size, + std::string_view recording_id = std::string_view(), + std::string_view entity_path_prefix = std::string_view(), bool timeless = false ) const { - try_log_file_from_contents(filepath, contents, contents_size).handle(); + try_log_file_from_contents( + filepath, + contents, + contents_size, + recording_id, + entity_path_prefix, + timeless + ) + .handle(); } /// Logs the given `contents` using all `DataLoader`s available. @@ -557,10 +606,15 @@ namespace rerun { /// \param filepath Path to the file that the `contents` belong to. /// \param contents Contents to be logged. /// \param contents_size Size in bytes of the `contents`. + /// \param recording_id The recommended `RecordingId` to log the data to. + /// \param entity_path_prefix What should the logged entity paths be prefixed with? + /// \param timeless Should the logged data be timeless? /// /// \see `log_file_from_contents` Error try_log_file_from_contents( - const std::filesystem::path& filepath, const std::byte* contents, size_t contents_size + const std::filesystem::path& filepath, const std::byte* contents, size_t contents_size, + std::string_view recording_id = std::string_view(), + std::string_view entity_path_prefix = std::string_view(), bool timeless = false ) const; /// @} diff --git a/rerun_cpp/tests/CMakeLists.txt b/rerun_cpp/tests/CMakeLists.txt index 3339d80159af6..efba589a407bf 100644 --- a/rerun_cpp/tests/CMakeLists.txt +++ b/rerun_cpp/tests/CMakeLists.txt @@ -19,7 +19,7 @@ list(APPEND rerun_sdk_tests_SRC "../docs/readme_snippets.cpp") add_executable(rerun_sdk_tests ${rerun_sdk_tests_SRC}) -rerun_strict_warning_settings(rerun_sdk_tests) +# rerun_strict_warning_settings(rerun_sdk_tests) # Include arrow explicitly again, otherwise the arrow headers won't be found. target_link_libraries(rerun_sdk_tests PRIVATE loguru::loguru Catch2::Catch2 rerun_sdk rerun_arrow_target)