Skip to content

Require descriptors to be provided on all log calls in C++ (either explicitly or implicitly via archetype) #8853

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

Merged
merged 10 commits into from
Jan 30, 2025
24 changes: 21 additions & 3 deletions docs/content/reference/migration/migration-0-22.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,29 @@ As part of the switch to "eager archetype serialization" (serialization of arche

However, it is still possible to do so with the `TensorData` component.

### C++ `RecordingStream::send_column` no longer takes raw component collections
### C++ `RecordingStream::log`/`send_column` no longer takes raw component collections

Previously, `RecordingStream::send_column` accepted raw component collections.
Previously, both `RecordingStream::log` and `RecordingStream::send_column` were able to
handle raw component collections which then would be serialized to arrow on the fly.

This is no longer the case and only `rerun::ComponentColumn` and anything else from which
#### `log`

Under the hood we allow any type that implements the `AsComponents` trait.
However, `AsComponents` is no longer implemented for collections of components / implementors of `Loggable`.

Instead, you're encouraged to use archetypes for cases where you'd previously use loose collections of components.
This is made easier by the fact that archetypes can now be created without specifying required components.
For example, colors of a point cloud can be logged without position data:

```cpp
rec.log("points", rerun::Points3D().with_colors(colors));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm.. I don't like pushing people towards this ("this" being an implicit partial update, as opposed to rerun::Points3D::update_fields()). If somehow executes that thing, they will end up in a very confusing situation where the viewer displays nothing...

```

Custom implementations of `AsComponents` still work as before.

#### `send_column`

Only `rerun::ComponentColumn` and anything else from which
a `Collection<ComponentColumn>` can be constructed is accepted.
The preferred way to create `rerun::ComponentColumn`s is to use the new `columns` method on archetypes.

Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/all/concepts/different_data_per_timeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ int main() {
// Log a red color on one timeline.
rec.reset_time(); // Clears all set timeline info.
rec.set_time_seconds("red timeline", 1.0);
rec.log("points", std::array{rerun::components::Color(0xFF0000FF)});
rec.log("points", rerun::Points2D::update_fields().with_colors(rerun::Color(0xFF0000FF)));

// And a blue color on the other.
rec.reset_time(); // Clears all set timeline info.
rec.set_time_sequence("blue timeline", 1);
rec.log("points", std::array{rerun::components::Color(0x0000FFFF)});
rec.log("points", rerun::Points2D::update_fields().with_colors(rerun::Color(0x0000FFFF)));

// TODO(#5521): log VisualBounds2D
}
9 changes: 7 additions & 2 deletions docs/snippets/all/descriptors/descr_builtin_component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ int main() {
const auto rec = rerun::RecordingStream("rerun_example_descriptors_builtin_component");
rec.spawn().exit_on_failure();

rerun::Position3D positions[1] = {{1.0f, 2.0f, 3.0f}};
rec.log_static("data", positions);
rec.log_static(
"data",
rerun::ComponentBatch::from_loggable<rerun::Position3D>(
{1.0f, 2.0f, 3.0f},
rerun::Loggable<rerun::Position3D>::Descriptor
)
);

// The tags are indirectly checked by the Rust version (have a look over there for more info).
}
9 changes: 7 additions & 2 deletions docs/snippets/all/descriptors/descr_custom_component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ int main() {
const auto rec = rerun::RecordingStream("rerun_example_descriptors_custom_component");
rec.spawn().exit_on_failure();

CustomPosition3D positions[1] = {{rerun::components::Position3D{1.0f, 2.0f, 3.0f}}};
rec.log_static("data", positions);
rec.log_static(
"data",
rerun::ComponentBatch::from_loggable<rerun::components::Position3D>(
{1.0f, 2.0f, 3.0f},
rerun::Loggable<CustomPosition3D>::Descriptor
)
);

// The tags are indirectly checked by the Rust version (have a look over there for more info).
}
10 changes: 3 additions & 7 deletions examples/cpp/incremental_logging/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,10 @@ int main() {
rerun::TextDocument(README).with_media_type(rerun::components::MediaType::markdown())
);

rerun::Collection<rerun::Color> colors = rerun::Color(255, 0, 0);
rerun::Collection<rerun::Radius> radii = rerun::Radius(0.1f);

// Only log colors and radii once.
rec.set_time_sequence("frame_nr", 0);
rec.log("points", colors, radii);
// Logging statically with `RecordingStream::log_static` would also work.
// rec.log_static("points", colors, radii);
rec.set_time_sequence("frame_nr", 0);
rec.log("points", rerun::Points3D().with_colors(rerun::Color(255, 0, 0)).with_radii(0.1f));

std::default_random_engine gen;
std::uniform_real_distribution<float> dist_pos(-5.0f, 5.0f);
Expand All @@ -71,6 +67,6 @@ int main() {
std::generate(points.begin(), points.end(), [&] {
return rerun::Position3D(dist_pos(gen), dist_pos(gen), dist_pos(gen));
});
rec.log("points", rerun::Points3D(points));
rec.log("points", rerun::Points3D::update_fields().with_positions(points));
}
}
86 changes: 37 additions & 49 deletions rerun_cpp/src/rerun/as_components.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,81 +22,69 @@ namespace rerun {
static_assert(
NoAsComponentsFor<T>::value,
"AsComponents is not implemented for this type. "
"It is implemented for all built-in archetypes as well as std::vector, std::array, and "
"c-arrays of components. "
"It is implemented for all built-in archetypes as well as invidiual & collections of `rerun::ComponentBatch`."
"You can add your own implementation by specializing AsComponents<T> for your type T."
);

// TODO(andreas): List methods that the trait should implement.
};

// TODO(andreas): make these return collection?
// TODO(andreas): Now that we no longer rely on `Loggable` trait implementations here, `serialize` is a misnomer. Consider using `operator()` instead.

// Documenting the builtin generic `AsComponents` impls is too much clutter for the doc class overview.
/// \cond private

/// `AsComponents` for a Collection of types implementing the `rerun::Loggable` trait.
template <typename TComponent>
struct AsComponents<Collection<TComponent>> {
static_assert(
is_loggable<TComponent>, "The given type does not implement the rerun::Loggable trait."
);

static Result<std::vector<ComponentBatch>> serialize(
const Collection<TComponent>& components
/// `AsComponents` for a `Collection<ComponentBatch>`.
template <>
struct AsComponents<Collection<ComponentBatch>> {
static Result<std::vector<ComponentBatch>> serialize(Collection<ComponentBatch> components
) {
auto batch_result = ComponentBatch::from_loggable<TComponent>(components);
RR_RETURN_NOT_OK(batch_result.error);

return Result<std::vector<ComponentBatch>>({std::move(batch_result.value)});
return Result<std::vector<ComponentBatch>>(std::move(components).to_vector());
}
};

/// `AsComponents` for a `std::vector` of types implementing the `rerun::Loggable` trait.
template <typename TComponent>
struct AsComponents<std::vector<TComponent>> {
static Result<std::vector<ComponentBatch>> serialize(
const std::vector<TComponent>& components
) {
return AsComponents<Collection<TComponent>>::serialize(components);
/// `AsComponents` for a single `ComponentBatch`.
template <>
struct AsComponents<ComponentBatch> {
static Result<std::vector<ComponentBatch>> serialize(ComponentBatch components) {
return Result<std::vector<ComponentBatch>>({std::move(components)});
}
};

/// AsComponents for `std::initializer_list`
template <typename TComponent>
struct AsComponents<std::initializer_list<TComponent>> {
/// `AsComponents` for a `Collection<ComponentBatch>` wrapped in a `Result`, forwarding errors for convenience.
template <>
struct AsComponents<Result<Collection<ComponentBatch>>> {
static Result<std::vector<ComponentBatch>> serialize(
std::initializer_list<TComponent> components
Result<Collection<ComponentBatch>> components
) {
return AsComponents<Collection<TComponent>>::serialize(components);
RR_RETURN_NOT_OK(components.error);
return Result<std::vector<ComponentBatch>>(std::move(components.value).to_vector());
}
};

/// `AsComponents` for an `std::array` of types implementing the `rerun::Loggable` trait.
template <typename TComponent, size_t NumInstances>
struct AsComponents<std::array<TComponent, NumInstances>> {
/// `AsComponents` for a `Collection<ComponentBatch>` individually wrapped in `Result`, forwarding errors for convenience.
template <>
struct AsComponents<Collection<Result<ComponentBatch>>> {
static Result<std::vector<ComponentBatch>> serialize(
const std::array<TComponent, NumInstances>& components
Collection<Result<ComponentBatch>> components
) {
return AsComponents<Collection<TComponent>>::serialize(components);
}
};

/// `AsComponents` for an c-array of types implementing the `rerun::Loggable` trait.
template <typename TComponent, size_t NumInstances>
struct AsComponents<TComponent[NumInstances]> {
static Result<std::vector<ComponentBatch>> serialize(const TComponent (&components
)[NumInstances]) {
return AsComponents<Collection<TComponent>>::serialize(components);
std::vector<ComponentBatch> result;
result.reserve(components.size());
for (auto& component : components) {
RR_RETURN_NOT_OK(component.error);
result.push_back(std::move(component.value));
}
return Result<std::vector<ComponentBatch>>(std::move(result));
}
};

/// `AsComponents` for single indicator components.
template <const char ComponentName[]>
struct AsComponents<components::IndicatorComponent<ComponentName>> {
static Result<std::vector<ComponentBatch>> serialize(
const components::IndicatorComponent<ComponentName>& indicator
) {
return AsComponents<
Collection<components::IndicatorComponent<ComponentName>>>::serialize(indicator);
/// `AsComponents` for a single `ComponentBatch` wrapped in a `Result`, forwarding errors for convenience.
template <>
struct AsComponents<Result<ComponentBatch>> {
static Result<std::vector<ComponentBatch>> serialize(Result<ComponentBatch> components) {
RR_RETURN_NOT_OK(components.error);
return Result<std::vector<ComponentBatch>>({std::move(components.value)});
}
};

Expand Down
48 changes: 0 additions & 48 deletions rerun_cpp/src/rerun/component_batch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,6 @@ namespace rerun {
return from_loggable(Collection<T>(), descriptor);
}

/// Creates a new component batch from a collection of component instances.
///
/// Automatically registers the component type the first time this type is encountered.
template <typename T>
static Result<ComponentBatch> from_loggable(const rerun::Collection<T>& components) {
return from_loggable(components, Loggable<T>::Descriptor);
}

/// Creates a new component batch from a collection of component instances.
///
/// Automatically registers the component type the first time this type is encountered.
Expand Down Expand Up @@ -89,16 +81,6 @@ namespace rerun {
return component_batch;
}

/// Creates a new component batch from a single component instance.
///
/// Automatically registers the component type the first time this type is encountered.
template <typename T>
static Result<ComponentBatch> from_loggable(const T& component) {
// Collection adapter will automatically borrow for single elements, but let's do this explicitly, avoiding the extra hoop.
const auto collection = Collection<T>::borrow(&component, 1);
return from_loggable(collection);
}

/// Creates a new component batch from a single component instance.
///
/// Automatically registers the component type the first time this type is encountered.
Expand All @@ -111,20 +93,6 @@ namespace rerun {
return from_loggable(collection, descriptor);
}

/// Creates a new data cell from a single optional component instance.
///
/// None is represented as a data cell with 0 instances.
///
/// Automatically registers the component type the first time this type is encountered.
template <typename T>
static Result<ComponentBatch> from_loggable(const std::optional<T>& component) {
if (component.has_value()) {
return from_loggable(component.value());
} else {
return from_loggable(Collection<T>());
}
}

/// Creates a new data cell from a single optional component instance.
///
/// None is represented as a data cell with 0 instances.
Expand All @@ -141,22 +109,6 @@ namespace rerun {
}
}

/// Creates a new data cell from an optional collection of component instances.
///
/// None is represented as a data cell with 0 instances.
///
/// Automatically registers the component type the first time this type is encountered.
template <typename T>
static Result<ComponentBatch> from_loggable(
const std::optional<rerun::Collection<T>>& components
) {
if (components.has_value()) {
return from_loggable(components.value());
} else {
return from_loggable(Collection<T>());
}
}

/// Creates a new data cell from an optional collection of component instances.
///
/// None is represented as a data cell with 0 instances.
Expand Down
38 changes: 2 additions & 36 deletions rerun_cpp/src/rerun/component_column.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,6 @@ namespace rerun {
ComponentTypeHandle component_type;

public:
/// Creates a new component column from a collection of component instances.
///
/// Automatically registers the component type the first time this type is encountered.
///
/// \param components Continuous collection of components which is about to be partitioned.
/// \param lengths The number of components in each run. for `rerun::RecordingStream::send_columns`,
/// this specifies the number of components at each time point.
/// The sum of the lengths must be equal to the number of components in the batch.
template <typename T>
[[deprecated(
"Use from_loggable_with_lengths(components, lengths, descriptor) (with explicit descriptor) instead"
)]] static Result<ComponentColumn>
from_loggable_with_lengths(
const Collection<T>& components, const Collection<uint32_t>& lengths
) {
return from_loggable_with_lengths(components, lengths, Loggable<T>::Descriptor);
}

/// Creates a new component column from a collection of component instances.
///
/// Automatically registers the component type the first time this type is encountered.
Expand All @@ -55,7 +37,7 @@ namespace rerun {
template <typename T>
static Result<ComponentColumn> from_loggable_with_lengths(
const Collection<T>& components, const Collection<uint32_t>& lengths,
const ComponentDescriptor& descriptor = Loggable<T>::Descriptor
const ComponentDescriptor& descriptor
) {
auto component_batch_result = ComponentBatch::from_loggable(components, descriptor);
if (component_batch_result.is_err()) {
Expand All @@ -64,21 +46,6 @@ namespace rerun {
return from_batch_with_lengths(component_batch_result.value, lengths);
}

/// Creates a new component column from a collection of component instances where each run has a length of one.
///
/// When used with `rerun::RecordingStream::send_columns`, this is equivalent to `from_loggable(components, std::vector{1, 1, ...})`.
/// I.e. there's a single component for each time point.
///
/// Automatically registers the component type the first time this type is encountered.
///
/// \param components Continuous collection of components which is about to be partitioned into runs of length one.
template <typename T>
[[deprecated("Use from_loggable(components, descriptor) (with explicit descriptor) instead"
)]] static Result<ComponentColumn>
from_loggable(const Collection<T>& components) {
return from_loggable(components, Loggable<T>::Descriptor);
}

/// Creates a new component column from a collection of component instances where each run has a length of one.
///
/// When used with `rerun::RecordingStream::send_columns`, this is equivalent to `from_loggable(components, std::vector{1, 1, ...})`.
Expand All @@ -90,8 +57,7 @@ namespace rerun {
/// \param descriptor Descriptor of the component type for this column.
template <typename T>
static Result<ComponentColumn> from_loggable(
const Collection<T>& components,
const ComponentDescriptor& descriptor = Loggable<T>::Descriptor
const Collection<T>& components, const ComponentDescriptor& descriptor
) {
return ComponentColumn::from_loggable_with_lengths(
components,
Expand Down
Loading
Loading