Skip to content
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

SDK DataLoaders 3: barebones C and C++ support #5330

Merged
merged 9 commits into from
Feb 29, 2024
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
2 changes: 1 addition & 1 deletion crates/rerun_c/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test = false

[dependencies]
re_log = { workspace = true, features = ["setup"] }
re_sdk.workspace = true
re_sdk = { workspace = true, features = ["data_loaders"] }

ahash.workspace = true
arrow2.workspace = true
Expand Down
95 changes: 93 additions & 2 deletions crates/rerun_c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod error;
mod ptr;
mod recording_streams;

use std::ffi::{c_char, CString};
use std::ffi::{c_char, c_uchar, CString};

use component_type_registry::COMPONENT_TYPES;
use once_cell::sync::Lazy;
Expand Down Expand Up @@ -48,6 +48,29 @@ impl CStringView {
}
}

/// This is called `rr_bytes` in the C API.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct CBytesView {
pub bytes: *const c_uchar,
pub length: u32,
}

impl CBytesView {
#[allow(clippy::result_large_err)]
pub fn as_bytes<'a>(&self, argument_name: &'a str) -> Result<&'a [u8], CError> {
ptr::try_ptr_as_slice(self.bytes, self.length, argument_name)
}

pub fn is_null(&self) -> bool {
self.bytes.is_null()
}

pub fn is_empty(&self) -> bool {
self.length == 0
}
}

pub type CRecordingStream = u32;

pub type CComponentTypeHandle = u32;
Expand Down Expand Up @@ -159,10 +182,10 @@ pub enum CErrorCode {
InvalidComponentTypeHandle,

_CategoryRecordingStream = 0x0000_00100,
RecordingStreamRuntimeFailure,
RecordingStreamCreationFailure,
RecordingStreamSaveFailure,
RecordingStreamStdoutFailure,
// TODO(cmc): Really this should be its own category…
RecordingStreamSpawnFailure,

_CategoryArrow = 0x0000_1000,
Expand Down Expand Up @@ -703,6 +726,74 @@ pub unsafe extern "C" fn rr_recording_stream_log(
}
}

#[allow(unsafe_code)]
#[allow(clippy::result_large_err)]
fn rr_log_file_from_path_impl(
stream: CRecordingStream,
filepath: CStringView,
) -> Result<(), CError> {
let stream = recording_stream(stream)?;

let filepath = filepath.as_str("filepath")?;
stream.log_file_from_path(filepath).map_err(|err| {
CError::new(
CErrorCode::RecordingStreamRuntimeFailure,
&format!("Couldn't load file {filepath:?}: {err}"),
)
})?;

Ok(())
}

#[allow(unsafe_code)]
#[no_mangle]
pub unsafe extern "C" fn rr_recording_stream_log_file_from_path(
stream: CRecordingStream,
filepath: CStringView,
error: *mut CError,
) {
if let Err(err) = rr_log_file_from_path_impl(stream, filepath) {
err.write_error(error);
}
}

#[allow(unsafe_code)]
#[allow(clippy::result_large_err)]
fn rr_log_file_from_contents_impl(
stream: CRecordingStream,
filepath: CStringView,
contents: CBytesView,
) -> Result<(), CError> {
let stream = recording_stream(stream)?;

let filepath = filepath.as_str("filepath")?;
let contents = contents.as_bytes("contents")?;

stream
.log_file_from_contents(filepath, std::borrow::Cow::Borrowed(contents))
.map_err(|err| {
CError::new(
CErrorCode::RecordingStreamRuntimeFailure,
&format!("Couldn't load file {filepath:?}: {err}"),
)
})?;

Ok(())
}

#[allow(unsafe_code)]
#[no_mangle]
pub unsafe extern "C" fn rr_recording_stream_log_file_from_contents(
stream: CRecordingStream,
filepath: CStringView,
contents: CBytesView,
error: *mut CError,
) {
if let Err(err) = rr_log_file_from_contents_impl(stream, filepath, contents) {
err.write_error(error);
}
}

// ----------------------------------------------------------------------------
// Private functions

Expand Down
13 changes: 13 additions & 0 deletions crates/rerun_c/src/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::ffi::c_char;

use crate::{CError, CErrorCode};

// ---
teh-cmc marked this conversation as resolved.
Show resolved Hide resolved

#[allow(unsafe_code)]
#[allow(clippy::result_large_err)]
pub fn try_ptr_as_ref<T>(ptr: *const T, argument_name: &str) -> Result<&T, CError> {
Expand All @@ -13,6 +15,17 @@ pub fn try_ptr_as_ref<T>(ptr: *const T, argument_name: &str) -> Result<&T, CErro
}
}

#[allow(unsafe_code)]
#[allow(clippy::result_large_err)]
pub fn try_ptr_as_slice<T>(
ptr: *const T,
length: u32,
argument_name: &str,
) -> Result<&[T], CError> {
try_ptr_as_ref(ptr, argument_name)?;
Ok(unsafe { std::slice::from_raw_parts(ptr.cast::<T>(), length as usize) })
}

/// Tries to convert a [`c_char`] pointer to a string, raises an error if the pointer is null or it can't be converted to a string.
#[allow(unsafe_code)]
#[allow(clippy::result_large_err)]
Expand Down
35 changes: 35 additions & 0 deletions crates/rerun_c/src/rerun.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ typedef struct rr_string {
uint32_t length_in_bytes;
} rr_string;

/// A byte slice.
typedef struct rr_bytes {
/// Pointer to the bytes.
///
/// Rerun is guaranteed to not read beyond bytes[length-1].
const uint8_t* bytes;

/// The length of the data in bytes.
uint32_t length;
} rr_bytes;

#ifndef __cplusplus

#include <string.h> // For strlen
Expand Down Expand Up @@ -412,6 +423,30 @@ extern void rr_recording_stream_log(
rr_recording_stream stream, rr_data_row data_row, bool inject_time, rr_error* error
);

/// Logs the file at the given `path` using all `DataLoader`s available.
///
/// A single `path` might be handled by more than one loader.
///
/// This method blocks until either at least one `DataLoader` starts streaming data in
/// or all of them fail.
///
/// See <https://www.rerun.io/docs/howto/open-any-file> for more information.
extern void rr_recording_stream_log_file_from_path(
rr_recording_stream stream, rr_string path, rr_error* error
);

/// Logs the given `contents` using all `DataLoader`s available.
///
/// A single `path` might be handled by more than one loader.
///
/// This method blocks until either at least one `DataLoader` starts streaming data in
/// or all of them fail.
///
/// See <https://www.rerun.io/docs/howto/open-any-file> 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
);

// ----------------------------------------------------------------------------
// Private functions

Expand Down
2 changes: 2 additions & 0 deletions examples/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_subdirectory(clock)
add_subdirectory(custom_collection_adapter)
add_subdirectory(dna)
add_subdirectory(external_data_loader)
add_subdirectory(log_file)
add_subdirectory(minimal)
add_subdirectory(shared_recording)
add_subdirectory(spawn_viewer)
Expand All @@ -12,6 +13,7 @@ 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_log_file)
add_dependencies(examples example_minimal)
add_dependencies(examples example_shared_recording)
add_dependencies(examples example_spawn_viewer)
Expand Down
32 changes: 32 additions & 0 deletions examples/cpp/log_file/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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=<path to rerun_sdk_cpp zip>` to cmake.
if(DEFINED RERUN_REPOSITORY)
add_executable(example_log_file main.cpp)
rerun_strict_warning_settings(example_log_file)
else()
project(example_log_file LANGUAGES CXX)

add_executable(example_log_file 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_log_file PROPERTY CXX_STANDARD 17)
endif()

# Link against rerun_sdk.
target_link_libraries(example_log_file PRIVATE rerun_sdk)
12 changes: 12 additions & 0 deletions examples/cpp/log_file/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!--[metadata]
title = "Log file example"
-->

Demonstrates how to log any file from the SDK using the [`DataLoader`](https://www.rerun.io/docs/howto/open-any-file) machinery.

To build it from a checkout of the repository (requires a Rust toolchain):
```bash
cmake .
cmake --build . --target example_log_file
./examples/cpp/log_file/example_log_file examples/assets/
```
79 changes: 79 additions & 0 deletions examples/cpp/log_file/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

#include <rerun.hpp>
#include <rerun/third_party/cxxopts.hpp>

int main(int argc, char** argv) {
// Create a new `RecordingStream` which sends data over TCP to the viewer process.
const auto rec = rerun::RecordingStream("rerun_example_log_file");

cxxopts::Options options(
"rerun_example_log_file",
"Demonstrates how to log any file from the SDK using the `DataLoader` machinery."
);

// clang-format off
options.add_options()
("h,help", "Print usage")
// Rerun
("spawn", "Start a new Rerun Viewer process and feed it data in real-time")
("connect", "Connects and sends the logged data to a remote Rerun viewer")
("save", "Log data to an rrd file", cxxopts::value<std::string>())
("stdout", "Log data to standard output, to be piped into a Rerun Viewer")
// Example
("from-contents", "Log the contents of the file directly (files only -- not supported by external loaders)", cxxopts::value<bool>()->default_value("false"))
("filepaths", "The filepaths to be loaded and logged", cxxopts::value<std::vector<std::string>>())
;
// clang-format on

options.parse_positional({"filepaths"});

auto args = options.parse(argc, argv);

if (args.count("help")) {
std::cout << options.help() << std::endl;
exit(0);
}

// TODO(#4602): need common rerun args helper library
if (args["spawn"].as<bool>()) {
rec.spawn().exit_on_failure();
} else if (args["connect"].as<bool>()) {
rec.connect().exit_on_failure();
} else if (args["stdout"].as<bool>()) {
rec.to_stdout().exit_on_failure();
} else if (args.count("save")) {
rec.save(args["save"].as<std::string>()).exit_on_failure();
} else {
rec.spawn().exit_on_failure();
}

const auto from_contents = args["from-contents"].as<bool>();
if (args.count("filepaths")) {
const auto filepaths = args["filepaths"].as<std::vector<std::string>>();
for (const auto& filepath : filepaths) {
if (!from_contents) {
// Either log the file using its path…
rec.log_file_from_path(filepath);
} else {
// …or using its contents if you already have them loaded for some reason.
if (std::filesystem::is_regular_file(filepath)) {
std::ifstream file(filepath);
std::stringstream contents;
contents << file.rdbuf();

const auto data = contents.str();
rec.log_file_from_contents(
filepath,
reinterpret_cast<const std::byte*>(data.c_str()),
data.size()
);
}
}
}
}
}
Loading
Loading