Skip to content

Commit 8790e6f

Browse files
authored
SDK DataLoaders 8: customizable (external) loaders for C++ (#5361)
Introduces the new `DataLoaderSettings` business to C++ and update examples accordingly (`external_data_loader` & `log_file`). ```bash ./build/debug/examples/cpp/log_file/example_log_file --recording-id this-one --entity-path-prefix a/b/c --time sim_time=1000 --time wall_time=1709204046 --sequence sim_frame=42 rerun_cpp/tests/main.cpp | rerun - ``` ![image](https://github.com/rerun-io/rerun/assets/2910679/b979a24c-29b6-473b-91b1-de3832bea436) Checks: - [x] external loader ran manually (`loader.exe | rerun`) - [x] external loader via rerun (`rerun xxx.cpp`) - [x] log_file with external loader (`log_file xxx.cpp`) --- Part of series of PR to expose configurable `DataLoader`s to our SDKs: - #5327 - #5328 - #5330 - #5337 - #5351 - #5355 - #5379 - #5361 - #5388
1 parent 7b314d8 commit 8790e6f

File tree

7 files changed

+227
-67
lines changed

7 files changed

+227
-67
lines changed

crates/rerun_c/src/lib.rs

+22-6
Original file line numberDiff line numberDiff line change
@@ -731,13 +731,16 @@ pub unsafe extern "C" fn rr_recording_stream_log(
731731
fn rr_log_file_from_path_impl(
732732
stream: CRecordingStream,
733733
filepath: CStringView,
734+
entity_path_prefix: CStringView,
735+
timeless: bool,
734736
) -> Result<(), CError> {
735737
let stream = recording_stream(stream)?;
736738

737739
let filepath = filepath.as_str("filepath")?;
740+
let entity_path_prefix = entity_path_prefix.as_str("entity_path_prefix").ok();
741+
738742
stream
739-
// TODO(cmc): expose settings
740-
.log_file_from_path(filepath, None, true)
743+
.log_file_from_path(filepath, entity_path_prefix.map(Into::into), timeless)
741744
.map_err(|err| {
742745
CError::new(
743746
CErrorCode::RecordingStreamRuntimeFailure,
@@ -753,9 +756,11 @@ fn rr_log_file_from_path_impl(
753756
pub unsafe extern "C" fn rr_recording_stream_log_file_from_path(
754757
stream: CRecordingStream,
755758
filepath: CStringView,
759+
entity_path_prefix: CStringView,
760+
timeless: bool,
756761
error: *mut CError,
757762
) {
758-
if let Err(err) = rr_log_file_from_path_impl(stream, filepath) {
763+
if let Err(err) = rr_log_file_from_path_impl(stream, filepath, entity_path_prefix, timeless) {
759764
err.write_error(error);
760765
}
761766
}
@@ -766,15 +771,22 @@ fn rr_log_file_from_contents_impl(
766771
stream: CRecordingStream,
767772
filepath: CStringView,
768773
contents: CBytesView,
774+
entity_path_prefix: CStringView,
775+
timeless: bool,
769776
) -> Result<(), CError> {
770777
let stream = recording_stream(stream)?;
771778

772779
let filepath = filepath.as_str("filepath")?;
773780
let contents = contents.as_bytes("contents")?;
781+
let entity_path_prefix = entity_path_prefix.as_str("entity_path_prefix").ok();
774782

775783
stream
776-
// TODO(cmc): expose settings
777-
.log_file_from_contents(filepath, std::borrow::Cow::Borrowed(contents), None, true)
784+
.log_file_from_contents(
785+
filepath,
786+
std::borrow::Cow::Borrowed(contents),
787+
entity_path_prefix.map(Into::into),
788+
timeless,
789+
)
778790
.map_err(|err| {
779791
CError::new(
780792
CErrorCode::RecordingStreamRuntimeFailure,
@@ -791,9 +803,13 @@ pub unsafe extern "C" fn rr_recording_stream_log_file_from_contents(
791803
stream: CRecordingStream,
792804
filepath: CStringView,
793805
contents: CBytesView,
806+
entity_path_prefix: CStringView,
807+
timeless: bool,
794808
error: *mut CError,
795809
) {
796-
if let Err(err) = rr_log_file_from_contents_impl(stream, filepath, contents) {
810+
if let Err(err) =
811+
rr_log_file_from_contents_impl(stream, filepath, contents, entity_path_prefix, timeless)
812+
{
797813
err.write_error(error);
798814
}
799815
}

crates/rerun_c/src/rerun.h

+28-2
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,30 @@ typedef struct rr_spawn_options {
141141
rr_string executable_path;
142142
} rr_spawn_options;
143143

144+
/// Recommended settings for the [`DataLoader`].
145+
///
146+
/// The loader is free to ignore some or all of these.
147+
///
148+
/// Refer to the field-level documentation for more information about each individual options.
149+
//
150+
// TODO(#3841): expose timepoint settings once we implement stateless APIs
151+
typedef struct rr_data_loader_settings {
152+
/// The recommended `RecordingId` to log the data to.
153+
///
154+
/// Unspecified by default.
155+
rr_string recording_id;
156+
157+
/// What should the logged entity paths be prefixed with?
158+
///
159+
/// Unspecified by default.
160+
rr_string entity_path_prefix;
161+
162+
/// Should the logged data be timeless?
163+
///
164+
/// Defaults to `false` if not set.
165+
bool timeless;
166+
} rr_data_loader_settings;
167+
144168
typedef struct rr_store_info {
145169
/// The user-chosen name of the application doing the logging.
146170
rr_string application_id;
@@ -432,7 +456,8 @@ extern void rr_recording_stream_log(
432456
///
433457
/// See <https://www.rerun.io/docs/howto/open-any-file> for more information.
434458
extern void rr_recording_stream_log_file_from_path(
435-
rr_recording_stream stream, rr_string path, rr_error* error
459+
rr_recording_stream stream, rr_string path, rr_string entity_path_prefix, bool timeless,
460+
rr_error* error
436461
);
437462

438463
/// Logs the given `contents` using all `DataLoader`s available.
@@ -444,7 +469,8 @@ extern void rr_recording_stream_log_file_from_path(
444469
///
445470
/// See <https://www.rerun.io/docs/howto/open-any-file> for more information.
446471
extern void rr_recording_stream_log_file_from_contents(
447-
rr_recording_stream stream, rr_string path, rr_bytes contents, rr_error* error
472+
rr_recording_stream stream, rr_string path, rr_bytes contents, rr_string entity_path_prefix,
473+
bool timeless, rr_error* error
448474
);
449475

450476
// ----------------------------------------------------------------------------
+87-47
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,55 @@
1+
#include <cstdint>
12
#include <fstream>
23
#include <iostream>
4+
#include <iterator>
35
#include <sstream>
46
#include <string>
57

68
#include <rerun.hpp>
9+
#include <rerun/third_party/cxxopts.hpp>
10+
#include <string_view>
11+
12+
void set_time_from_args(const rerun::RecordingStream& rec, cxxopts::ParseResult& args) {
13+
if (args.count("time")) {
14+
const auto times = args["time"].as<std::vector<std::string>>();
15+
for (const auto& time_str : times) {
16+
auto pos = time_str.find('=');
17+
if (pos != std::string::npos) {
18+
auto timeline_name = time_str.substr(0, pos);
19+
int64_t time = std::stol(time_str.substr(pos + 1));
20+
rec.set_time_seconds(timeline_name, static_cast<double>(time));
21+
}
22+
}
23+
}
24+
25+
if (args.count("sequence")) {
26+
const auto sequences = args["sequence"].as<std::vector<std::string>>();
27+
for (const auto& sequence_str : sequences) {
28+
auto pos = sequence_str.find('=');
29+
if (pos != std::string::npos) {
30+
auto timeline_name = sequence_str.substr(0, pos);
31+
int64_t sequence = std::stol(sequence_str.substr(pos + 1));
32+
rec.set_time_sequence(timeline_name, sequence);
33+
}
34+
}
35+
}
36+
}
737

8-
static const char* USAGE = R"(
38+
int main(int argc, char* argv[]) {
39+
// The Rerun Viewer will always pass these two pieces of information:
40+
// 1. The path to be loaded, as a positional arg.
41+
// 2. A shared recording ID, via the `--recording-id` flag.
42+
//
43+
// It is up to you whether you make use of that shared recording ID or not.
44+
// If you use it, the data will end up in the same recording as all other plugins interested in
45+
// that file, otherwise you can just create a dedicated recording for it. Or both.
46+
//
47+
// Check out `re_data_source::DataLoaderSettings` documentation for an exhaustive listing of
48+
// the available CLI parameters.
49+
50+
cxxopts::Options options(
51+
"rerun-loader-cpp-file",
52+
R"(
953
This is an example executable data-loader plugin for the Rerun Viewer.
1054
Any executable on your `$PATH` with a name that starts with `rerun-loader-` will be treated as an
1155
external data-loader.
@@ -15,57 +59,38 @@ special exit code to indicate that it doesn't support anything else.
1559
1660
To try it out, compile it and place it in your $PATH as `rerun-loader-cpp-file`, then open a C++ source
1761
file with Rerun (`rerun file.cpp`).
62+
)"
63+
);
1864

19-
USAGE:
20-
rerun-loader-cpp-file [OPTIONS] FILEPATH
21-
22-
FLAGS:
23-
-h, --help Prints help information
24-
25-
OPTIONS:
26-
--recording-id RECORDING_ID ID of the shared recording
27-
28-
ARGS:
29-
<FILEPATH>
30-
)";
31-
32-
int main(int argc, char* argv[]) {
33-
// The Rerun Viewer will always pass these two pieces of information:
34-
// 1. The path to be loaded, as a positional arg.
35-
// 2. A shared recording ID, via the `--recording-id` flag.
36-
//
37-
// It is up to you whether you make use of that shared recording ID or not.
38-
// If you use it, the data will end up in the same recording as all other plugins interested in
39-
// that file, otherwise you can just create a dedicated recording for it. Or both.
40-
std::string filepath;
41-
std::string recording_id;
42-
43-
for (int i = 1; i < argc; ++i) {
44-
std::string arg(argv[i]);
45-
46-
if (arg == "--recording-id") {
47-
if (i + 1 < argc) {
48-
recording_id = argv[i + 1];
49-
++i;
50-
} else {
51-
std::cerr << USAGE << std::endl;
52-
return 1;
53-
}
54-
} else {
55-
filepath = arg;
56-
}
65+
// clang-format off
66+
options.add_options()
67+
("h,help", "Print usage")
68+
("filepath", "The filepath to be loaded and logged", cxxopts::value<std::string>())
69+
("application-id", "Optional recommended ID for the application", cxxopts::value<std::string>())
70+
("recording-id", "Optional recommended ID for the recording", cxxopts::value<std::string>())
71+
("entity-path-prefix", "Optional prefix for all entity paths", cxxopts::value<std::string>())
72+
("timeless", "Optionally mark data to be logged as timeless", cxxopts::value<bool>()->default_value("false"))
73+
("time", "Optional timestamps to log at (e.g. `--time sim_time=1709203426`) (repeatable)", cxxopts::value<std::vector<std::string>>())
74+
("sequence", "Optional sequences to log at (e.g. `--sequence sim_frame=42`) (repeatable)", cxxopts::value<std::vector<std::string>>())
75+
;
76+
// clang-format on
77+
78+
options.parse_positional({"filepath"});
79+
80+
auto args = options.parse(argc, argv);
81+
82+
if (args.count("help")) {
83+
std::cout << options.help() << std::endl;
84+
exit(0);
5785
}
5886

59-
if (filepath.empty()) {
60-
std::cerr << USAGE << std::endl;
61-
return 1;
62-
}
87+
const auto filepath = args["filepath"].as<std::string>();
6388

6489
bool is_file = std::filesystem::is_regular_file(filepath);
6590
bool is_cpp_file = std::filesystem::path(filepath).extension().string() == ".cpp";
6691

6792
// Inform the Rerun Viewer that we do not support that kind of file.
68-
if (!is_file || is_cpp_file) {
93+
if (!is_file || !is_cpp_file) {
6994
return rerun::EXTERNAL_DATA_LOADER_INCOMPATIBLE_EXIT_CODE;
7095
}
7196

@@ -75,12 +100,27 @@ int main(int argc, char* argv[]) {
75100

76101
std::string text = "## Some C++ code\n```cpp\n" + body.str() + "\n```\n";
77102

78-
const auto rec = rerun::RecordingStream("rerun_example_external_data_loader", recording_id);
103+
auto application_id = std::string_view("rerun_example_external_data_loader");
104+
if (args.count("application-id")) {
105+
application_id = args["application-id"].as<std::string>();
106+
}
107+
auto recording_id = std::string_view();
108+
if (args.count("recording-id")) {
109+
recording_id = args["recording-id"].as<std::string>();
110+
}
111+
const auto rec = rerun::RecordingStream(application_id, recording_id);
79112
// The most important part of this: log to standard output so the Rerun Viewer can ingest it!
80113
rec.to_stdout().exit_on_failure();
81114

82-
rec.log_timeless(
83-
filepath,
115+
set_time_from_args(rec, args);
116+
117+
auto entity_path = std::string(filepath);
118+
if (args.count("entity-path-prefix")) {
119+
entity_path = args["entity-path-prefix"].as<std::string>() + "/" + filepath;
120+
}
121+
rec.log_with_timeless(
122+
entity_path,
123+
args["timeless"].as<bool>(),
84124
rerun::TextDocument(text).with_media_type(rerun::MediaType::markdown())
85125
);
86126
}

examples/cpp/log_file/main.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ int main(int argc, char** argv) {
5858
for (const auto& filepath : filepaths) {
5959
if (!from_contents) {
6060
// Either log the file using its path…
61-
rec.log_file_from_path(filepath);
61+
rec.log_file_from_path(filepath, "log_file_example");
6262
} else {
6363
// …or using its contents if you already have them loaded for some reason.
6464
if (std::filesystem::is_regular_file(filepath)) {
@@ -70,7 +70,8 @@ int main(int argc, char** argv) {
7070
rec.log_file_from_contents(
7171
filepath,
7272
reinterpret_cast<const std::byte*>(data.c_str()),
73-
data.size()
73+
data.size(),
74+
"log_file_example"
7475
);
7576
}
7677
}

rerun_cpp/src/rerun/c/rerun.h

+28-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)