Skip to content

Commit

Permalink
New ViewProperty utility for querying properties with fallbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
Wumpf committed Jun 3, 2024
1 parent 22ce481 commit b792caa
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5343,6 +5343,7 @@ dependencies = [
"re_viewer_context",
"slotmap",
"smallvec",
"thiserror",
]

[[package]]
Expand Down
4 changes: 4 additions & 0 deletions crates/re_viewer_context/src/component_fallbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ pub enum ComponentFallbackError {
/// Meaning, that this is an unknown component or something went wrong with the placeholder registration.
#[error("Missing placeholder for component. Was the component's default registered with the viewer?")]
MissingPlaceholderValue,

/// Not directly returned by the fallback provider, but useful when serializing a fallback value.
#[error("Fallback value turned up to be empty when we expected a value.")]
UnexpectedEmptyFallback,
}

/// Provides fallback values for components, implemented typically by [`crate::SpaceViewClass`] and [`crate::VisualizerSystem`].
Expand Down
3 changes: 3 additions & 0 deletions crates/re_viewer_context/src/space_view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub enum SpaceViewSystemExecutionError {

#[error("No render context error.")]
NoRenderContextError,

#[error(transparent)]
ComponentFallbackError(#[from] crate::ComponentFallbackError),
}

// Convenience conversions for some re_renderer error types since these are so frequent.
Expand Down
6 changes: 6 additions & 0 deletions crates/re_viewer_context/src/viewer_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ impl<'a> ViewerContext<'a> {
self.store_context.recording.store()
}

/// The active blueprint.
#[inline]
pub fn blueprint_db(&self) -> &re_entity_db::EntityDb {
self.store_context.blueprint
}

/// The `StoreId` of the active recording.
#[inline]
pub fn recording_id(&self) -> &re_log_types::StoreId {
Expand Down
1 change: 1 addition & 0 deletions crates/re_viewport_blueprint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ once_cell.workspace = true
parking_lot.workspace = true
slotmap.workspace = true
smallvec.workspace = true
thiserror.workspace = true
2 changes: 1 addition & 1 deletion crates/re_viewport_blueprint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub use space_view_contents::SpaceViewContents;
pub use tree_actions::TreeAction;
pub use view_properties::{
edit_blueprint_component, entity_path_for_view_property, query_view_property,
query_view_property_or_default, view_property,
query_view_property_or_default, view_property, ViewProperty,
};
pub use viewport_blueprint::ViewportBlueprint;

Expand Down
205 changes: 182 additions & 23 deletions crates/re_viewport_blueprint/src/view_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,186 @@ use re_entity_db::{
EntityDb,
};
use re_log_types::EntityPath;
use re_types::Archetype;
use re_viewer_context::{external::re_entity_db::EntityTree, SpaceViewId, ViewerContext};
use re_types::{external::arrow2, Archetype, ArchetypeName, ComponentName};
use re_viewer_context::{
external::re_entity_db::EntityTree, ComponentFallbackError, ComponentFallbackProvider,
QueryContext, SpaceViewId, SpaceViewSystemExecutionError, ViewerContext,
};

// TODO(andreas): Replace all usages with `ViewProperty`.
/// Returns `Ok(None)` if any of the required components are missing.
pub fn query_view_property<A: Archetype>(
space_view_id: SpaceViewId,
blueprint_db: &EntityDb,
query: &LatestAtQuery,
) -> (PromiseResult<Option<A>>, EntityPath)
where
LatestAtResults: ToArchetype<A>,
{
let path = entity_path_for_view_property::<A>(space_view_id, blueprint_db.tree());
(
blueprint_db
.latest_at_archetype(&path, query)
.map(|res| res.map(|(_, arch)| arch)),
path,
)
}

#[derive(thiserror::Error, Debug)]
pub enum ViewPropertyQueryError {
#[error(transparent)]
SerializationError(#[from] re_types::DeserializationError),

#[error(transparent)]
ComponentFallbackError(#[from] ComponentFallbackError),
}

impl From<ViewPropertyQueryError> for SpaceViewSystemExecutionError {
fn from(val: ViewPropertyQueryError) -> Self {
match val {
ViewPropertyQueryError::SerializationError(err) => err.into(),
ViewPropertyQueryError::ComponentFallbackError(err) => err.into(),
}
}
}

/// Utility for querying view properties.
pub struct ViewProperty<'a> {
blueprint_store_path: EntityPath,
archetype_name: ArchetypeName,
query_results: LatestAtResults,
viewer_ctx: &'a ViewerContext<'a>,
}

impl<'a> ViewProperty<'a> {
/// Query a specific view property for a given view.
pub fn from_archetype<A: Archetype>(
viewer_ctx: &'a ViewerContext<'a>,
view_id: SpaceViewId,
) -> Self {
Self::from_archetype_impl(viewer_ctx, view_id, A::name(), A::all_components().as_ref())
}

fn from_archetype_impl(
viewer_ctx: &'a ViewerContext<'a>,
space_view_id: SpaceViewId,
archetype_name: ArchetypeName,
component_names: &[ComponentName],
) -> Self {
let blueprint_db = viewer_ctx.blueprint_db();

let blueprint_store_path = entity_path_for_view_property_from_archetype_name(
space_view_id,
blueprint_db.tree(),
archetype_name,
);

let query_results = blueprint_db.latest_at(
viewer_ctx.blueprint_query,
&blueprint_store_path,
component_names.iter().copied(),
);

ViewProperty {
blueprint_store_path,
archetype_name,
query_results,

viewer_ctx,
}
}

/// Get the value of a specific component or its fallback if the component is not present.
// TODO(andreas): Unfortunately we can't use TypedComponentFallbackProvider here because it may not be implemented for all components of interest.
// This sadly means that there's a bit of unnecessary back and forth between arrow array and untyped that could be avoided otherwise.
pub fn component_or_fallback<C: re_types::Component + Default>(
&self,
fallback_provider: &dyn ComponentFallbackProvider,
view_state: &'a dyn re_viewer_context::SpaceViewState,
) -> Result<C, ViewPropertyQueryError> {
self.component_array_or_fallback::<C>(fallback_provider, view_state)?
.into_iter()
.next()
.ok_or(ComponentFallbackError::UnexpectedEmptyFallback.into())
}

/// Get the value of a specific component or its fallback if the component is not present.
pub fn component_array_or_fallback<C: re_types::Component + Default>(
&self,
fallback_provider: &dyn ComponentFallbackProvider,
view_state: &'a dyn re_viewer_context::SpaceViewState,
) -> Result<Vec<C>, ViewPropertyQueryError> {
let component_name = C::name();
Ok(C::from_arrow(
self.component_or_fallback_raw(component_name, fallback_provider, view_state)?
.as_ref(),
)?)
}

fn component_raw(
&self,
component_name: ComponentName,
) -> Option<Box<dyn arrow2::array::Array>> {
self.query_results.get(component_name).and_then(|result| {
result.raw(self.viewer_ctx.blueprint_db().resolver(), component_name)
})
}

fn component_or_fallback_raw(
&self,
component_name: ComponentName,
fallback_provider: &dyn ComponentFallbackProvider,
view_state: &'a dyn re_viewer_context::SpaceViewState,
) -> Result<Box<dyn arrow2::array::Array>, ComponentFallbackError> {
if let Some(value) = self.component_raw(component_name) {
Ok(value)
} else {
fallback_provider.fallback_for(&self.query_context(view_state), component_name)
}
}

/// Save change to a blueprint component.
pub fn save_blueprint_component<C: re_types::Component>(&self, component: &C) {
self.viewer_ctx
.save_blueprint_component(&self.blueprint_store_path, component);
}

/// Resets a blueprint component to the value it had in the default blueprint.
pub fn reset_blueprint_component<C: re_types::Component>(&self) {
self.viewer_ctx
.reset_blueprint_component_by_name(&self.blueprint_store_path, C::name());
}

fn query_context(
&self,
view_state: &'a dyn re_viewer_context::SpaceViewState,
) -> QueryContext<'_> {
QueryContext {
viewer_ctx: self.viewer_ctx,
target_entity_path: &self.blueprint_store_path,
archetype_name: Some(self.archetype_name),
query: self.viewer_ctx.blueprint_query,
view_state,
}
}
}

// TODO(andreas): Replace all usages with `ViewProperty`.
pub fn entity_path_for_view_property<T: Archetype>(
space_view_id: SpaceViewId,
_blueprint_entity_tree: &EntityTree,
) -> EntityPath {
entity_path_for_view_property_from_archetype_name(
space_view_id,
_blueprint_entity_tree,
T::name(),
)
}

fn entity_path_for_view_property_from_archetype_name(
space_view_id: SpaceViewId,
_blueprint_entity_tree: &EntityTree,
archetype_name: ArchetypeName,
) -> EntityPath {
// TODO(andreas,jleibs):
// We want to search the subtree for occurrences of the property archetype here.
Expand All @@ -19,9 +193,10 @@ pub fn entity_path_for_view_property<T: Archetype>(
let space_view_blueprint_path = space_view_id.as_entity_path();

// Use short_name instead of full_name since full_name has dots and looks too much like an indicator component.
space_view_blueprint_path.join(&EntityPath::from_single_string(T::name().short_name()))
space_view_blueprint_path.join(&EntityPath::from_single_string(archetype_name.short_name()))
}

// TODO(andreas): Replace all usages with `ViewProperty`.
/// Return the archetype value for the given space view, or `None` if it doesn't exist.
pub fn view_property<A: re_types::Archetype>(
ctx: &re_viewer_context::ViewerContext<'_>,
Expand All @@ -30,7 +205,7 @@ pub fn view_property<A: re_types::Archetype>(
where
LatestAtResults: ToArchetype<A>,
{
let blueprint_db = ctx.store_context.blueprint;
let blueprint_db = ctx.blueprint_db();
let blueprint_query = ctx.blueprint_query;
let path = entity_path_for_view_property::<A>(space_view_id, blueprint_db.tree());
blueprint_db
Expand All @@ -40,24 +215,7 @@ where
.map(|(_index, value)| value)
}

/// Returns `Ok(None)` if any of the required components are missing.
pub fn query_view_property<A: Archetype>(
space_view_id: SpaceViewId,
blueprint_db: &EntityDb,
query: &LatestAtQuery,
) -> (PromiseResult<Option<A>>, EntityPath)
where
LatestAtResults: ToArchetype<A>,
{
let path = entity_path_for_view_property::<A>(space_view_id, blueprint_db.tree());
(
blueprint_db
.latest_at_archetype(&path, query)
.map(|res| res.map(|(_, arch)| arch)),
path,
)
}

// TODO(andreas): Replace all usages with `ViewProperty`.
pub fn query_view_property_or_default<A: Archetype + Default>(
space_view_id: SpaceViewId,
blueprint_db: &EntityDb,
Expand All @@ -70,6 +228,7 @@ where
(arch.ok().flatten().unwrap_or_default(), path)
}

// TODO(andreas): Replace all usages with `ViewProperty`.
/// Edit a single component of a blueprint archetype in a space view.
///
/// Set to `None` to reset the value to the value in the default blueprint, if any,
Expand All @@ -79,7 +238,7 @@ pub fn edit_blueprint_component<A: re_types::Archetype, C: re_types::Component +
space_view_id: SpaceViewId,
edit_component: impl FnOnce(&mut Option<C>) -> R,
) -> R {
let active_blueprint = ctx.store_context.blueprint;
let active_blueprint = ctx.blueprint_db();
let active_path = entity_path_for_view_property::<A>(space_view_id, active_blueprint.tree());
let original_value: Option<C> = active_blueprint
.latest_at_component::<C>(&active_path, ctx.blueprint_query)
Expand Down

0 comments on commit b792caa

Please sign in to comment.