Skip to content

Commit

Permalink
Spawn via $PATH 3: C and C++ implementations (#3998)
Browse files Browse the repository at this point in the history
- Introduces standalone `rr_spawn(rr_spawn_options)` C API that allows
one to start a Rerun Viewer process ready to listen for TCP connections,
as well as the associated `rr_recording_stream` integration.
- Introduces standalone `rerun::spawn()` C++ API that allows one to
start a Rerun Viewer process ready to listen for TCP connections, as
well as the associated `RecordingStream` integration.



https://github.com/rerun-io/rerun/assets/2910679/8ae5003a-78b2-4e75-91d3-6dc4b8dd22ac



---

Spawn via `$PATH` series:
- #3996
- #3997
- #3998

---

- Fixes #3757 
- Fixes #3942
  • Loading branch information
teh-cmc authored Oct 26, 2023
1 parent c1a91d7 commit 2f0fce7
Show file tree
Hide file tree
Showing 85 changed files with 508 additions and 81 deletions.
19 changes: 13 additions & 6 deletions crates/rerun_c/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
use crate::{CError, CErrorCode};

impl CError {
/// The maximum size in bytes of the [`CError::message`] field.
///
/// Error message larger than this value will be automatically truncated.
//
// NOTE: You must update `rr_error.description` too if you modify this value.
pub const MAX_MESSAGE_SIZE_BYTES: usize = 2048;

pub const OK: CError = CError {
code: CErrorCode::Ok,
message: [0; 512],
message: [0; Self::MAX_MESSAGE_SIZE_BYTES],
};

pub fn new(code: CErrorCode, message: &str) -> Self {
let mut message_c = [0; 512];
let mut message_c = [0; Self::MAX_MESSAGE_SIZE_BYTES];

// Copy string character by character.
// Ensure that when truncating is necessary, we don't truncate in the middle of a UTF-8 character!
Expand Down Expand Up @@ -77,7 +84,7 @@ mod tests {
#[allow(unsafe_code)]
fn write_error_handles_message_overflow() {
// With ASCII character.
let description = "a".repeat(1024);
let description = "a".repeat(CError::MAX_MESSAGE_SIZE_BYTES * 2);
let error = CError::new(CErrorCode::Ok, &description);
let num_expected_bytes = error.message.len() - 1;
assert_eq!(
Expand All @@ -86,7 +93,7 @@ mod tests {
);

// With 2 byte UTF8 character
let description = "œ".repeat(1024);
let description = "œ".repeat(CError::MAX_MESSAGE_SIZE_BYTES * 2);
let error = CError::new(CErrorCode::Ok, &description);
let num_expected_bytes = ((error.message.len() - 1) / 2) * 2;
assert_eq!(
Expand All @@ -95,7 +102,7 @@ mod tests {
);

// With 3 byte UTF8 character
let description = "∂".repeat(1024);
let description = "∂".repeat(CError::MAX_MESSAGE_SIZE_BYTES * 2);
let error = CError::new(CErrorCode::Ok, &description);
let num_expected_bytes = ((error.message.len() - 1) / 3) * 3;
assert_eq!(
Expand All @@ -104,7 +111,7 @@ mod tests {
);

// With 4 byte UTF8 character
let description = "😀".repeat(1024);
let description = "😀".repeat(CError::MAX_MESSAGE_SIZE_BYTES * 2);
let error = CError::new(CErrorCode::Ok, &description);
let num_expected_bytes = ((error.message.len() - 1) / 4) * 4;
assert_eq!(
Expand Down
106 changes: 104 additions & 2 deletions crates/rerun_c/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! The Rerun C SDK.
//!
//! The functions here must match `rerun.h`.
// TODO(emilk): error handling
#![crate_type = "staticlib"]
#![allow(clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)] // Too much unsafe
Expand All @@ -25,6 +24,44 @@ use re_sdk::{

type CRecordingStream = u32;

/// C version of [`re_sdk::SpawnOptions`].
#[derive(Debug, Clone)]
#[repr(C)]
pub struct CSpawnOptions {
pub port: u16,
pub memory_limit: *const c_char,
pub executable_name: *const c_char,
pub executable_path: *const c_char,
}

impl CSpawnOptions {
#[allow(clippy::result_large_err)]
pub fn as_rust(&self) -> Result<re_sdk::SpawnOptions, CError> {
let mut spawn_opts = re_sdk::SpawnOptions::default();

if self.port != 0 {
spawn_opts.port = self.port;
}

if !self.memory_limit.is_null() {
spawn_opts.memory_limit =
ptr::try_char_ptr_as_str(self.memory_limit, "memory_limit")?.to_owned();
}

if !self.executable_name.is_null() {
spawn_opts.executable_name =
ptr::try_char_ptr_as_str(self.executable_name, "executable_name")?.to_owned();
}

if !self.executable_path.is_null() {
spawn_opts.executable_path =
Some(ptr::try_char_ptr_as_str(self.executable_path, "executable_path")?.to_owned());
}

Ok(spawn_opts)
}
}

#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CStoreKind {
Expand Down Expand Up @@ -87,6 +124,8 @@ pub enum CErrorCode {
_CategoryRecordingStream = 0x0000_00100,
RecordingStreamCreationFailure,
RecordingStreamSaveFailure,
// TODO(cmc): Really this should be its own category…
RecordingStreamSpawnFailure,

_CategoryArrow = 0x0000_1000,
ArrowIpcMessageParsingFailure,
Expand All @@ -99,7 +138,7 @@ pub enum CErrorCode {
#[derive(Clone)]
pub struct CError {
pub code: CErrorCode,
pub message: [c_char; 512],
pub message: [c_char; Self::MAX_MESSAGE_SIZE_BYTES],
}

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -166,6 +205,29 @@ pub extern "C" fn rr_version_string() -> *const c_char {
VERSION.as_ptr()
}

#[allow(clippy::result_large_err)]
fn rr_spawn_impl(spawn_opts: *const CSpawnOptions) -> Result<(), CError> {
let spawn_opts = if spawn_opts.is_null() {
re_sdk::SpawnOptions::default()
} else {
let spawn_opts = ptr::try_ptr_as_ref(spawn_opts, "spawn_opts")?;
spawn_opts.as_rust()?
};

re_sdk::spawn(&spawn_opts)
.map_err(|err| CError::new(CErrorCode::RecordingStreamSpawnFailure, &err.to_string()))?;

Ok(())
}

#[allow(unsafe_code)]
#[no_mangle]
pub extern "C" fn rr_spawn(spawn_opts: *const CSpawnOptions, error: *mut CError) {
if let Err(err) = rr_spawn_impl(spawn_opts) {
err.write_error(error);
}
}

#[allow(clippy::result_large_err)]
fn rr_recording_stream_new_impl(store_info: *const CStoreInfo) -> Result<CRecordingStream, CError> {
initialize_logging();
Expand Down Expand Up @@ -304,6 +366,46 @@ pub extern "C" fn rr_recording_stream_connect(
}
}

#[allow(clippy::result_large_err)]
fn rr_recording_stream_spawn_impl(
stream: CRecordingStream,
spawn_opts: *const CSpawnOptions,
flush_timeout_sec: f32,
) -> Result<(), CError> {
let stream = recording_stream(stream)?;

let spawn_opts = if spawn_opts.is_null() {
re_sdk::SpawnOptions::default()
} else {
let spawn_opts = ptr::try_ptr_as_ref(spawn_opts, "spawn_opts")?;
spawn_opts.as_rust()?
};
let flush_timeout = if flush_timeout_sec >= 0.0 {
Some(std::time::Duration::from_secs_f32(flush_timeout_sec))
} else {
None
};

stream
.spawn_opts(&spawn_opts, flush_timeout)
.map_err(|err| CError::new(CErrorCode::RecordingStreamSpawnFailure, &err.to_string()))?;

Ok(())
}

#[allow(unsafe_code)]
#[no_mangle]
pub extern "C" fn rr_recording_stream_spawn(
id: CRecordingStream,
spawn_opts: *const CSpawnOptions,
flush_timeout_sec: f32,
error: *mut CError,
) {
if let Err(err) = rr_recording_stream_spawn_impl(id, spawn_opts, flush_timeout_sec) {
err.write_error(error);
}
}

#[allow(clippy::result_large_err)]
fn rr_recording_stream_save_impl(
stream: CRecordingStream,
Expand Down
68 changes: 66 additions & 2 deletions crates/rerun_c/src/rerun.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,38 @@ enum {
/// set it as a the global.
typedef uint32_t rr_recording_stream;

/// Options to control the behavior of `spawn`.
///
/// Refer to the field-level documentation for more information about each individual options.
///
/// The defaults are ok for most use cases.
typedef struct rr_spawn_options {
/// The port to listen on.
///
/// Defaults to `9876` if set to `0`.
uint16_t port;

/// An upper limit on how much memory the Rerun Viewer should use.
/// When this limit is reached, Rerun will drop the oldest data.
/// Example: `16GB` or `50%` (of system total).
///
/// Defaults to `75%` if null.
const char* memory_limit;

/// Specifies the name of the Rerun executable.
///
/// You can omit the `.exe` suffix on Windows.
///
/// Defaults to `rerun` if null.
const char* executable_name;

/// Enforce a specific executable to use instead of searching though PATH
/// for [`Self::executable_name`].
///
/// Unspecified by default.
const char* executable_path;
} rr_spawn_options;

typedef struct rr_store_info {
/// The user-chosen name of the application doing the logging.
const char* application_id;
Expand Down Expand Up @@ -122,7 +154,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_INVALID_TIMELINE_TYPE,
RR_ERROR_CODE_RECORDING_STREAM_SPAWN_FAILURE,

// Arrow data processing errors.
_RR_ERROR_CODE_CATEGORY_ARROW = 0x000001000,
Expand All @@ -143,7 +175,9 @@ typedef struct rr_error {
rr_error_code code;

/// Human readable description of the error in null-terminated UTF8.
char description[512];
//
// NOTE: You must update `CError::MAX_MESSAGE_SIZE_BYTES` too if you modify this value.
char description[2048];
} rr_error;

// ----------------------------------------------------------------------------
Expand All @@ -152,6 +186,14 @@ typedef struct rr_error {
/// Returns a human-readable version string of the Rerun C SDK.
extern const char* rr_version_string(void);

/// Spawns a new Rerun Viewer process from an executable available in PATH, ready to
/// listen for incoming TCP connections.
///
/// `spawn_opts` can be set to NULL to use the recommended defaults.
///
/// If a Rerun Viewer is already listening on this TCP port, this does nothing.
extern void rr_spawn(const rr_spawn_options* spawn_opts, rr_error* error);

/// Creates a new recording stream to log to.
///
/// You must call this at least once to enable logging.
Expand Down Expand Up @@ -202,6 +244,28 @@ extern void rr_recording_stream_connect(
rr_recording_stream stream, const char* tcp_addr, float flush_timeout_sec, rr_error* error
);

/// Spawns a new Rerun Viewer process from an executable available in PATH, then connects to it
/// over TCP.
///
/// This function returns immediately and will only raise an error for argument parsing errors,
/// not for connection errors as these happen asynchronously.
///
/// ## Parameters
///
/// spawn_opts:
/// Configuration of the spawned process.
/// Refer to `rr_spawn_options` documentation for details.
/// Passing null is valid and will result in the recommended defaults.
///
/// flush_timeout_sec:
/// The minimum time the SDK will wait during a flush before potentially
/// dropping data if progress is not being made. Passing a negative value indicates no timeout,
/// and can cause a call to `flush` to block indefinitely.
extern void rr_recording_stream_spawn(
rr_recording_stream stream, const rr_spawn_options* spawn_opts, float flush_timeout_sec,
rr_error* error
);

/// Stream all log-data to a given file.
///
/// This function returns immediately.
Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/annotation_context_connections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

int main() {
auto rec = rerun::RecordingStream("rerun_example_annotation_context_connections");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

// Log an annotation context to assign a label and color to each class
// Create a class description with labels and color for each keypoint ID as well as some
Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/annotation_context_rects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

int main() {
auto rec = rerun::RecordingStream("rerun_example_annotation_context_rects");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

// Log an annotation context to assign a label and color to each class
rec.log_timeless(
Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/annotation_context_segmentation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

int main() {
auto rec = rerun::RecordingStream("rerun_example_annotation_context_connections");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

// create an annotation context to describe the classes
rec.log_timeless(
Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/arrow3d_simple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ constexpr float TAU = 6.28318530717958647692528676655900577f;

int main() {
auto rec = rerun::RecordingStream("rerun_example_arrow3d");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

std::vector<rerun::components::Position3D> origins;
std::vector<rerun::components::Vector3D> vectors;
Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/asset3d_out_of_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ int main(int argc, char** argv) {
auto path = argv[1];

auto rec = rerun::RecordingStream("rerun_example_asset3d_out_of_tree");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

rec.log_timeless("world", rerun::ViewCoordinates::RIGHT_HAND_Z_UP); // Set an up-axis

Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/asset3d_simple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ int main(int argc, char* argv[]) {
std::string path = args[1];

auto rec = rerun::RecordingStream("rerun_example_asset3d_simple");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

rec.log_timeless("world", rerun::ViewCoordinates::RIGHT_HAND_Z_UP); // Set an up-axis
rec.log("world/asset", rerun::Asset3D::from_file(path).value_or_throw());
Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/bar_chart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

int main() {
auto rec = rerun::RecordingStream("rerun_example_bar_chart");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

rec.log("bar_chart", rerun::BarChart::i64({8, 4, 0, 9, 1, 4, 1, 6, 9, 0}));
}
2 changes: 1 addition & 1 deletion docs/code-examples/box2d_simple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

int main() {
auto rec = rerun::RecordingStream("rerun_example_box2d");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

rec.log("simple", rerun::Boxes2D::from_mins_and_sizes({{-1.f, -1.f}}, {{2.f, 2.f}}));

Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/box3d_batch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

int main() {
auto rec = rerun::RecordingStream("rerun_example_box3d_batch");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

rec.log(
"batch",
Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/box3d_simple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

int main() {
auto rec = rerun::RecordingStream("rerun_example_box3d_simple");
rec.connect().throw_on_failure();
rec.spawn().throw_on_failure();

rec.log("simple", rerun::Boxes3D::from_half_sizes({{2.f, 2.f, 1.0f}}));
}
Loading

0 comments on commit 2f0fce7

Please sign in to comment.