Skip to content

Commit 57cf31c

Browse files
Wumpfjleibs
andauthored
Introduce store subscriber (VisualizerEntitySubscriber) to maintain list of entities per system incrementally (#4558)
### What * Part of #3110 Turns out this doesn't result in a large perf win so far since in samples like ` python .\examples\python\open_photogrammetry_format\main.py --no-frames ` the accumulated cost of doing queries in `all_possible_space_views` overshadows any of these smaller wins. Furthermore, the biggest of getting the per-entities list seems to be the queries necessary to call `heuristic_filter` for each entity. Still, this is an important step for our general dataflow & infrastructure in this area! ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Using newly built examples: [app.rerun.io](https://app.rerun.io/pr/4558/index.html) * Using examples from latest `main` build: [app.rerun.io](https://app.rerun.io/pr/4558/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json) * Using full set of examples from `nightly` build: [app.rerun.io](https://app.rerun.io/pr/4558/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG - [PR Build Summary](https://build.rerun.io/pr/4558) - [Docs preview](https://rerun.io/preview/1493e68738be62586a7012eb970f6206eb16e638/docs) <!--DOCS-PREVIEW--> - [Examples preview](https://rerun.io/preview/1493e68738be62586a7012eb970f6206eb16e638/examples) <!--EXAMPLES-PREVIEW--> - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html) --------- Co-authored-by: Jeremy Leibs <[email protected]>
1 parent 984d159 commit 57cf31c

File tree

8 files changed

+271
-135
lines changed

8 files changed

+271
-135
lines changed

Cargo.lock

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

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ atty = "0.2"
106106
backtrace = "0.3"
107107
bincode = "1.3"
108108
bitflags = { version = "2.4", features = ["bytemuck"] }
109+
bit-vec = "0.6"
109110
blackbox = "0.2.0"
110111
bytemuck = { version = "1.11", features = ["extern_crate_alloc"] }
111112
camino = "1.1"

crates/re_viewer_context/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ re_ui.workspace = true
3030

3131
ahash.workspace = true
3232
anyhow.workspace = true
33+
bit-vec.workspace = true
3334
bytemuck.workspace = true
3435
egui-wgpu.workspace = true
3536
egui.workspace = true

crates/re_viewer_context/src/space_view/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod system_execution_output;
1515
mod view_context_system;
1616
mod view_part_system;
1717
mod view_query;
18+
mod visualizer_entity_subscriber;
1819

1920
pub use auto_spawn_heuristic::AutoSpawnHeuristic;
2021
pub use dyn_space_view_class::{

crates/re_viewer_context/src/space_view/space_view_class_registry.rs

+68-24
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
use ahash::{HashMap, HashSet};
2+
use nohash_hasher::IntSet;
3+
use re_arrow_store::DataStore;
4+
use re_log_types::EntityPath;
25

36
use crate::{
47
DynSpaceViewClass, IdentifiedViewSystem, SpaceViewClassIdentifier, ViewContextCollection,
58
ViewContextSystem, ViewPartCollection, ViewPartSystem, ViewSystemIdentifier,
69
};
710

8-
use super::space_view_class_placeholder::SpaceViewClassPlaceholder;
11+
use super::{
12+
space_view_class_placeholder::SpaceViewClassPlaceholder,
13+
visualizer_entity_subscriber::VisualizerEntitySubscriber,
14+
};
915

1016
#[derive(Debug, thiserror::Error)]
1117
#[allow(clippy::enum_variant_names)]
@@ -54,7 +60,7 @@ impl SpaceViewSystemRegistrator<'_> {
5460
self.registry
5561
.context_systems
5662
.entry(T::identifier())
57-
.or_insert_with(|| SystemTypeRegistryEntry {
63+
.or_insert_with(|| ContextSystemTypeRegistryEntry {
5864
factory_method: Box::new(|| Box::<T>::default()),
5965
used_by: Default::default(),
6066
})
@@ -91,9 +97,16 @@ impl SpaceViewSystemRegistrator<'_> {
9197
self.registry
9298
.visualizers
9399
.entry(T::identifier())
94-
.or_insert_with(|| SystemTypeRegistryEntry {
95-
factory_method: Box::new(|| Box::<T>::default()),
96-
used_by: Default::default(),
100+
.or_insert_with(|| {
101+
let entity_subscriber_handle = DataStore::register_subscriber(Box::new(
102+
VisualizerEntitySubscriber::new(&T::default()),
103+
));
104+
105+
VisualizerTypeRegistryEntry {
106+
factory_method: Box::new(|| Box::<T>::default()),
107+
used_by: Default::default(),
108+
entity_subscriber_handle,
109+
}
97110
})
98111
.used_by
99112
.insert(self.identifier);
@@ -110,37 +123,53 @@ impl SpaceViewSystemRegistrator<'_> {
110123
}
111124

112125
/// Space view class entry in [`SpaceViewClassRegistry`].
113-
struct SpaceViewClassRegistryEntry {
114-
class: Box<dyn DynSpaceViewClass>,
115-
context_systems: HashSet<ViewSystemIdentifier>,
116-
visualizers: HashSet<ViewSystemIdentifier>,
126+
pub struct SpaceViewClassRegistryEntry {
127+
pub class: Box<dyn DynSpaceViewClass>,
128+
pub context_system_ids: HashSet<ViewSystemIdentifier>,
129+
pub visualizer_system_ids: HashSet<ViewSystemIdentifier>,
117130
}
118131

119132
#[allow(clippy::derivable_impls)] // Clippy gets this one wrong.
120133
impl Default for SpaceViewClassRegistryEntry {
121134
fn default() -> Self {
122135
Self {
123136
class: Box::<SpaceViewClassPlaceholder>::default(),
124-
context_systems: Default::default(),
125-
visualizers: Default::default(),
137+
context_system_ids: Default::default(),
138+
visualizer_system_ids: Default::default(),
126139
}
127140
}
128141
}
129142

130-
/// System type entry in [`SpaceViewClassRegistry`].
131-
struct SystemTypeRegistryEntry<T: ?Sized> {
132-
factory_method: Box<dyn Fn() -> Box<T> + Send + Sync>,
143+
/// Context system type entry in [`SpaceViewClassRegistry`].
144+
struct ContextSystemTypeRegistryEntry {
145+
factory_method: Box<dyn Fn() -> Box<dyn ViewContextSystem> + Send + Sync>,
133146
used_by: HashSet<SpaceViewClassIdentifier>,
134147
}
135148

149+
/// Visualizer entry in [`SpaceViewClassRegistry`].
150+
struct VisualizerTypeRegistryEntry {
151+
factory_method: Box<dyn Fn() -> Box<dyn ViewPartSystem> + Send + Sync>,
152+
used_by: HashSet<SpaceViewClassIdentifier>,
153+
154+
/// Handle to subscription of [`VisualizerEntitySubscriber`] for this visualizer.
155+
entity_subscriber_handle: re_arrow_store::StoreSubscriberHandle,
156+
}
157+
158+
impl Drop for VisualizerTypeRegistryEntry {
159+
fn drop(&mut self) {
160+
// TODO(andreas): DataStore unsubscribe is not yet implemented!
161+
//DataStore::unregister_subscriber(self.entity_subscriber_handle);
162+
}
163+
}
164+
136165
/// Registry of all known space view types.
137166
///
138167
/// Expected to be populated on viewer startup.
139168
#[derive(Default)]
140169
pub struct SpaceViewClassRegistry {
141170
space_view_classes: HashMap<SpaceViewClassIdentifier, SpaceViewClassRegistryEntry>,
142-
visualizers: HashMap<ViewSystemIdentifier, SystemTypeRegistryEntry<dyn ViewPartSystem>>,
143-
context_systems: HashMap<ViewSystemIdentifier, SystemTypeRegistryEntry<dyn ViewContextSystem>>,
171+
context_systems: HashMap<ViewSystemIdentifier, ContextSystemTypeRegistryEntry>,
172+
visualizers: HashMap<ViewSystemIdentifier, VisualizerTypeRegistryEntry>,
144173
placeholder: SpaceViewClassRegistryEntry,
145174
}
146175

@@ -175,8 +204,8 @@ impl SpaceViewClassRegistry {
175204
identifier,
176205
SpaceViewClassRegistryEntry {
177206
class,
178-
context_systems,
179-
visualizers,
207+
context_system_ids: context_systems,
208+
visualizer_system_ids: visualizers,
180209
},
181210
)
182211
.is_some()
@@ -245,10 +274,25 @@ impl SpaceViewClassRegistry {
245274
}
246275

247276
/// Iterates over all registered Space View class types.
248-
pub fn iter_classes(&self) -> impl Iterator<Item = &dyn DynSpaceViewClass> {
249-
self.space_view_classes
250-
.values()
251-
.map(|entry| entry.class.as_ref())
277+
pub fn iter_registry(&self) -> impl Iterator<Item = &SpaceViewClassRegistryEntry> {
278+
self.space_view_classes.values()
279+
}
280+
281+
/// Returns the set of entities that are applicable to the given visualizer.
282+
///
283+
/// The list is kept up to date by a store subscriber.
284+
pub fn applicable_entities_for_visualizer_system(
285+
&self,
286+
visualizer: ViewSystemIdentifier,
287+
store: &re_log_types::StoreId,
288+
) -> Option<IntSet<EntityPath>> {
289+
self.visualizers.get(&visualizer).and_then(|entry| {
290+
DataStore::with_subscriber::<VisualizerEntitySubscriber, _, _>(
291+
entry.entity_subscriber_handle,
292+
|subscriber| subscriber.entities(store).cloned(),
293+
)
294+
.flatten()
295+
})
252296
}
253297

254298
pub fn new_context_collection(
@@ -266,7 +310,7 @@ impl SpaceViewClassRegistry {
266310

267311
ViewContextCollection {
268312
systems: class
269-
.context_systems
313+
.context_system_ids
270314
.iter()
271315
.filter_map(|name| {
272316
self.context_systems.get(name).map(|entry| {
@@ -293,7 +337,7 @@ impl SpaceViewClassRegistry {
293337

294338
ViewPartCollection {
295339
systems: class
296-
.visualizers
340+
.visualizer_system_ids
297341
.iter()
298342
.filter_map(|name| {
299343
self.visualizers.get(name).map(|entry| {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use ahash::HashMap;
2+
use bit_vec::BitVec;
3+
use nohash_hasher::{IntMap, IntSet};
4+
5+
use re_arrow_store::StoreSubscriber;
6+
use re_log_types::{EntityPath, EntityPathHash, StoreId};
7+
use re_types::ComponentName;
8+
9+
use crate::{IdentifiedViewSystem, ViewPartSystem, ViewSystemIdentifier};
10+
11+
/// A store subscriber that keep track which entities in a store can be
12+
/// processed by a single given visualizer type.
13+
///
14+
/// The list of entities is additive:
15+
/// If an entity was at any point in time applicable to the visualizer, it will be
16+
/// kept in the list of entities.
17+
///
18+
/// Applicability is determined by the visualizer's set of required components.
19+
///
20+
/// There's only a single entity subscriber per visualizer *type*.
21+
/// This means that if the same visualizer is used in multiple space views, only a single
22+
/// `VisualizerEntitySubscriber` is created for all of them.
23+
pub struct VisualizerEntitySubscriber {
24+
/// Visualizer type this subscriber is associated with.
25+
visualizer: ViewSystemIdentifier,
26+
27+
/// Assigns each required component an index.
28+
required_components_indices: IntMap<ComponentName, usize>,
29+
30+
per_store_mapping: HashMap<StoreId, VisualizerEntityMapping>,
31+
}
32+
33+
#[derive(Default)]
34+
struct VisualizerEntityMapping {
35+
/// For each entity, which of the required components are present.
36+
///
37+
/// In order of `required_components`.
38+
/// If all bits are set, the entity is applicable to the visualizer.
39+
// TODO(andreas): We could just limit the number of required components to 32 or 64 and
40+
// then use a single u32/u64 as a bitmap.
41+
required_component_bitmap_per_entity: IntMap<EntityPathHash, BitVec>,
42+
43+
/// Which entities the visualizer can be applied to.
44+
///
45+
/// Guaranteed to not have any duplicate entries.
46+
/// Order is not defined.
47+
// TODO(andreas): It would be nice if these were just `EntityPathHash`.
48+
applicable_entities: IntSet<EntityPath>,
49+
}
50+
51+
impl VisualizerEntitySubscriber {
52+
pub fn new<T: IdentifiedViewSystem + ViewPartSystem>(visualizer: &T) -> Self {
53+
Self {
54+
visualizer: T::identifier(),
55+
required_components_indices: visualizer
56+
.required_components()
57+
.into_iter()
58+
.enumerate()
59+
.map(|(i, name)| (name, i))
60+
.collect(),
61+
per_store_mapping: Default::default(),
62+
}
63+
}
64+
65+
/// List of entities that are applicable to the visualizer.
66+
#[inline]
67+
pub fn entities(&self, store: &StoreId) -> Option<&IntSet<EntityPath>> {
68+
self.per_store_mapping
69+
.get(store)
70+
.map(|mapping| &mapping.applicable_entities)
71+
}
72+
}
73+
74+
impl StoreSubscriber for VisualizerEntitySubscriber {
75+
#[inline]
76+
fn name(&self) -> String {
77+
self.visualizer.as_str().to_owned()
78+
}
79+
80+
#[inline]
81+
fn as_any(&self) -> &dyn std::any::Any {
82+
self
83+
}
84+
85+
#[inline]
86+
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
87+
self
88+
}
89+
90+
fn on_events(&mut self, events: &[re_arrow_store::StoreEvent]) {
91+
// TODO(andreas): Need to react to store removals as well. As of writing doesn't exist yet.
92+
93+
for event in events {
94+
if event.diff.kind != re_arrow_store::StoreDiffKind::Addition {
95+
// Applicability is only additive, don't care about removals.
96+
continue;
97+
}
98+
99+
let store_mapping = self
100+
.per_store_mapping
101+
.entry(event.store_id.clone())
102+
.or_default();
103+
104+
let required_components_bitmap = store_mapping
105+
.required_component_bitmap_per_entity
106+
.entry(event.diff.entity_path.hash())
107+
.or_insert_with(|| {
108+
BitVec::from_elem(self.required_components_indices.len(), false)
109+
});
110+
111+
if required_components_bitmap.all() {
112+
// We already know that this entity is applicable to the visualizer.
113+
continue;
114+
}
115+
116+
for component_name in event.diff.cells.keys() {
117+
if let Some(index) = self.required_components_indices.get(component_name) {
118+
required_components_bitmap.set(*index, true);
119+
}
120+
}
121+
122+
if required_components_bitmap.all() {
123+
re_log::debug!(
124+
"Entity {:?} in store {:?} is now applicable to visualizer {:?}",
125+
event.diff.entity_path,
126+
event.store_id,
127+
self.visualizer
128+
);
129+
130+
store_mapping
131+
.applicable_entities
132+
.insert(event.diff.entity_path.clone());
133+
}
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)