1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
use std::collections::{BTreeMap, BTreeSet};
use nohash_hasher::IntMap;
use re_data_store::{EntityPath, EntityProperties, EntityPropertyMap};
use re_viewer_context::{DataBlueprintGroupHandle, PerSystemEntities};
use slotmap::SlotMap;
use smallvec::{smallvec, SmallVec};
/// A grouping of several data-blueprints.
#[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
pub struct DataBlueprintGroup {
pub display_name: String,
/// Individual settings. Mutate & display this.
pub properties_individual: EntityProperties,
/// Properties, as inherited from parent. Read from this.
///
/// Recalculated at the start of each frame from [`Self::properties_individual`].
#[cfg_attr(feature = "serde", serde(skip))]
pub properties_projected: EntityProperties,
/// Parent of this blueprint group. Every data blueprint except the root has a parent.
pub parent: DataBlueprintGroupHandle,
pub children: SmallVec<[DataBlueprintGroupHandle; 4]>,
/// Direct child entities of this blueprint group.
///
/// Musn't be a `HashSet` because we want to preserve order of entity paths.
pub entities: BTreeSet<EntityPath>,
}
impl DataBlueprintGroup {
/// Determine whether this `DataBlueprints` has user-edits relative to another `DataBlueprints`
fn has_edits(&self, other: &Self) -> bool {
let Self {
display_name,
properties_individual,
properties_projected: _,
parent,
children,
entities,
} = self;
display_name != &other.display_name
|| properties_individual.has_edits(&other.properties_individual)
|| parent != &other.parent
|| children != &other.children
|| entities != &other.entities
}
}
/// Data blueprints for all entity paths in a space view.
#[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
pub struct DataBlueprints {
/// Individual settings. Mutate this.
individual: EntityPropertyMap,
/// Properties, as inherited from parent. Read from this.
///
/// Recalculated at the start of each frame from [`Self::individual`].
#[cfg_attr(feature = "serde", serde(skip))]
projected: EntityPropertyMap,
}
// Manually implement `PartialEq` since projected is serde skip
impl DataBlueprints {
/// Determine whether this `DataBlueprints` has user-edits relative to another `DataBlueprints`
fn has_edits(&self, other: &Self) -> bool {
let Self {
individual,
projected: _,
} = self;
individual.has_edits(&other.individual)
}
}
/// Tree of all data blueprint groups for a single space view.
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct SpaceViewContents {
/// All data blueprint groups.
groups: SlotMap<DataBlueprintGroupHandle, DataBlueprintGroup>,
/// Mapping from entity paths to blueprints.
///
/// We also use this for building up groups from hierarchy, meaning that some paths in here
/// may not represent existing entities, i.e. the blueprint groups they are pointing to may not
/// necessarily have the respective path as a child.
path_to_group: IntMap<EntityPath, DataBlueprintGroupHandle>,
/// List of all entities that we query via this data blueprint collection.
///
/// Currently this is reset every frame in `SpaceViewBlueprint::reset_systems_per_entity_path`.
/// In the future, we may want to keep this around and only add/remove systems
/// for entities. But at this point we'd likely handle the heuristics a bit differently as well
/// and don't use serde here for serialization.
#[serde(skip)]
per_system_entity_list: PerSystemEntities,
/// Root group, always exists as a placeholder
root_group_handle: DataBlueprintGroupHandle,
data_blueprints: DataBlueprints,
}
/// Determine whether this `DataBlueprintTree` has user-edits relative to another `DataBlueprintTree`
impl SpaceViewContents {
pub fn has_edits(&self, other: &Self) -> bool {
let Self {
groups,
path_to_group,
per_system_entity_list: _,
root_group_handle,
data_blueprints,
} = self;
groups.len() != other.groups.len()
|| groups.iter().any(|(key, val)| {
other
.groups
.get(key)
.map_or(true, |other_val| val.has_edits(other_val))
})
|| *path_to_group != other.path_to_group
|| *root_group_handle != other.root_group_handle
|| data_blueprints.has_edits(&other.data_blueprints)
}
}
impl Default for SpaceViewContents {
fn default() -> Self {
let mut groups = SlotMap::default();
let root_group = groups.insert(DataBlueprintGroup::default());
let mut path_to_blueprint = IntMap::default();
path_to_blueprint.insert(EntityPath::root(), root_group);
Self {
groups,
path_to_group: path_to_blueprint,
per_system_entity_list: BTreeMap::default(),
root_group_handle: root_group,
data_blueprints: DataBlueprints::default(),
}
}
}
impl SpaceViewContents {
/// Returns a handle to the root data blueprint.
///
/// Even if there are no other groups, we always have a root group at the top.
/// Typically, we don't show the root group in the ui.
pub fn root_handle(&self) -> DataBlueprintGroupHandle {
self.root_group_handle
}
/// Returns the root data blueprint.
///
/// Even if there are no other groups, we always have a root group at the top.
/// Typically, we don't show the root group in the ui.
pub fn root_group(&self) -> &DataBlueprintGroup {
self.groups.get(self.root_group_handle).unwrap()
}
/// Resolves a data blueprint group handle.
pub fn group(&self, handle: DataBlueprintGroupHandle) -> Option<&DataBlueprintGroup> {
self.groups.get(handle)
}
/// Resolves a data blueprint group handle.
pub fn group_mut(
&mut self,
handle: DataBlueprintGroupHandle,
) -> Option<&mut DataBlueprintGroup> {
self.groups.get_mut(handle)
}
/// Calls the visitor function on every entity path in the given group and its descending groups.
pub fn visit_group_entities_recursively(
&self,
handle: DataBlueprintGroupHandle,
visitor: &mut impl FnMut(&EntityPath),
) {
let Some(group) = self.groups.get(handle) else {
return;
};
for entity in &group.entities {
visitor(entity);
}
for child in &group.children {
self.visit_group_entities_recursively(*child, visitor);
}
}
/// Returns entity properties with the hierarchy applied.
pub fn data_blueprints_projected(&self) -> &EntityPropertyMap {
&self.data_blueprints.projected
}
/// Returns mutable individual entity properties, the hierarchy was not applied to this.
pub fn data_blueprints_individual(&mut self) -> &mut EntityPropertyMap {
&mut self.data_blueprints.individual
}
pub fn contains_entity(&self, path: &EntityPath) -> bool {
// If an entity is in path_to_group it is *likely* also an entity in the Space View.
// However, it could be that the path *only* refers to a group, not also an entity.
// So once we resolved the group, we need to check if it contains the entity of interest.
self.path_to_group
.get(path)
.and_then(|group| {
self.groups
.get(*group)
.and_then(|group| group.entities.get(path))
})
.is_some()
}
/// List of all entities that we query via this data blueprint collection.
pub fn entity_paths(&self) -> impl Iterator<Item = &EntityPath> {
// Each entity is only ever in one group, therefore collecting all entities from all groups, gives us all entities.
self.groups.values().flat_map(|group| group.entities.iter())
}
pub fn per_system_entities(&self) -> &PerSystemEntities {
&self.per_system_entity_list
}
pub fn per_system_entities_mut(&mut self) -> &mut PerSystemEntities {
&mut self.per_system_entity_list
}
pub fn contains_all_entities_from(&self, other: &SpaceViewContents) -> bool {
for (system, entities) in &other.per_system_entity_list {
let Some(self_entities) = self.per_system_entity_list.get(system) else {
if entities.is_empty() {
continue;
} else {
return false;
}
};
if !entities.is_subset(self_entities) {
return false;
}
}
true
}
/// Should be called on frame start.
///
/// Propagates any data blueprint changes along the tree.
pub fn propagate_individual_to_tree(&mut self) {
re_tracing::profile_function!();
// NOTE: We could do this projection only when the entity properties changes
// and/or when new entity paths are added, but such memoization would add complexity.
fn project_tree(
tree: &mut SpaceViewContents,
parent_properties: &EntityProperties,
group_handle: DataBlueprintGroupHandle,
) {
let Some(group) = tree.groups.get_mut(group_handle) else {
debug_assert!(false, "Invalid group handle in blueprint group tree");
return;
};
let group_properties_projected =
parent_properties.with_child(&group.properties_individual);
group.properties_projected = group_properties_projected.clone();
for entity_path in &group.entities {
let projected_properties = group_properties_projected
.with_child(&tree.data_blueprints.individual.get(entity_path));
tree.data_blueprints
.projected
.set(entity_path.clone(), projected_properties);
}
let children = group.children.clone(); // TODO(andreas): How to avoid this clone?
for child in &children {
project_tree(tree, &group_properties_projected, *child);
}
}
project_tree(self, &EntityProperties::default(), self.root_group_handle);
}
/// Adds a list of entity paths to the tree, using grouping as dictated by their entity path hierarchy.
///
/// `base_path` indicates a path at which we short-circuit to the root group.
///
/// Creates a group at *every* step of every path, unless a new group would only contain the entity itself.
pub fn insert_entities_according_to_hierarchy<'a>(
&mut self,
paths: impl Iterator<Item = &'a EntityPath>,
base_path: &EntityPath,
) {
re_tracing::profile_function!();
let mut new_leaf_groups = Vec::new();
for path in paths {
// Is there already a group associated with this exact path? (maybe because a child was logged there earlier)
// If so, we can simply move it under this existing group.
let group_handle = if let Some(group_handle) = self.path_to_group.get(path) {
*group_handle
} else if path == base_path {
// An entity might have directly been logged on the base_path. We map then to the root!
self.root_group_handle
} else {
// Otherwise, create a new group which only contains this entity and add the group to the hierarchy.
let new_group = self.groups.insert(DataBlueprintGroup {
display_name: path_to_group_name(path),
..Default::default()
});
self.add_group_to_hierarchy_recursively(new_group, path, base_path);
new_leaf_groups.push(new_group);
new_group
};
self.add_entity_to_group(group_handle, path);
}
// If a leaf group contains only a single element, move that element to the parent and remove the leaf again.
// (we can't do this as we iterate initially on `paths`, as we don't know if we're data on non-leaf paths until we touched all of them)
for leaf_group_handle in new_leaf_groups {
let Some(leaf_group) = self.groups.get_mut(leaf_group_handle) else {
continue;
};
if !leaf_group.children.is_empty() || leaf_group.entities.len() != 1 {
continue;
}
// Remove group.
let single_entity = leaf_group.entities.iter().next().unwrap().clone();
let parent_group_handle = leaf_group.parent;
self.groups.remove(leaf_group_handle);
// Add entity to its parent and remove the now deleted child.
let parent_group = self.groups.get_mut(parent_group_handle).unwrap();
parent_group
.children
.retain(|child_group| *child_group != leaf_group_handle);
parent_group.entities.insert(single_entity.clone());
self.path_to_group
.insert(single_entity, parent_group_handle);
}
}
fn add_group_to_hierarchy_recursively(
&mut self,
new_group: DataBlueprintGroupHandle,
associated_path: &EntityPath,
base_path: &EntityPath,
) {
let Some(mut parent_path) = associated_path.parent() else {
// Already the root, nothing to do.
return;
};
// Short circuit to the root group at base_path.
// If the entity is outside of the base path we would walk up all the way to the root
if &parent_path == base_path {
parent_path = EntityPath::root();
}
let parent_group = match self.path_to_group.entry(parent_path.clone()) {
std::collections::hash_map::Entry::Occupied(parent_group) => {
let parent_group = *parent_group.get();
self.groups
.get_mut(parent_group)
.unwrap()
.children
.push(new_group);
parent_group
}
std::collections::hash_map::Entry::Vacant(vacant_mapping) => {
let parent_group = self.groups.insert(DataBlueprintGroup {
display_name: path_to_group_name(&parent_path),
children: smallvec![new_group],
..Default::default()
});
vacant_mapping.insert(parent_group);
self.add_group_to_hierarchy_recursively(parent_group, &parent_path, base_path);
parent_group
}
};
self.groups.get_mut(new_group).unwrap().parent = parent_group;
}
/// Adds an entity path to a group.
///
/// If it was already associated with this group, nothing will happen.
/// If it was already associated with a different group, it will move from there.
pub fn add_entity_to_group(
&mut self,
group_handle: DataBlueprintGroupHandle,
path: &EntityPath,
) {
if let Some(group) = self.groups.get_mut(group_handle) {
if !group.entities.insert(path.clone()) {
// If the entity was already in here it won't be in another group previously.
return;
}
} else {
return;
}
if let Some(previous_group) = self.path_to_group.insert(path.clone(), group_handle) {
if previous_group != group_handle {
if let Some(previous_group) = self.groups.get_mut(previous_group) {
previous_group.entities.retain(|ent| ent != path);
}
}
}
}
/// Removes an entity from the data blueprint collection.
///
/// If the entity was not known by this data blueprint tree nothing happens.
pub fn remove_entity(&mut self, path: &EntityPath) {
re_tracing::profile_function!();
if let Some(group_handle) = self.path_to_group.get(path) {
if let Some(group) = self.groups.get_mut(*group_handle) {
group.entities.remove(path);
self.remove_group_if_empty(*group_handle);
}
}
self.path_to_group.remove(path);
for per_system_list in self.per_system_entity_list.values_mut() {
per_system_list.remove(path);
}
}
/// Removes a group and all its entities and subgroups from the blueprint tree
pub fn remove_group(&mut self, group_handle: DataBlueprintGroupHandle) {
re_tracing::profile_function!();
let Some(group) = self.groups.get(group_handle) else {
return;
};
// Clone group to work around borrow checker issues.
let group = group.clone();
// Remove all child groups.
for child_group in &group.children {
self.remove_group(*child_group);
}
// Remove all child entities.
for entity_path in &group.entities {
for per_system_list in self.per_system_entity_list.values_mut() {
per_system_list.remove(entity_path);
}
}
// Remove from `path_to_group` map.
// `path_to_group` may map arbitrary paths to this group, some of which aren't in the entity_paths list!
self.path_to_group
.retain(|_, group_mapping| *group_mapping != group_handle);
// Remove group from parent group
if let Some(parent_group) = self.groups.get_mut(group.parent) {
parent_group
.children
.retain(|child_group| *child_group != group_handle);
}
// Never completely remove the root group.
if group_handle != self.root_group_handle {
self.groups.remove(group_handle);
}
}
fn remove_group_if_empty(&mut self, group_handle: DataBlueprintGroupHandle) {
let Some(group) = self.groups.get(group_handle) else {
return;
};
if group.entities.is_empty() && group.children.is_empty() {
let parent_group_handle = group.parent;
if let Some(parent_group) = self.groups.get_mut(parent_group_handle) {
parent_group
.children
.retain(|child_group| *child_group != group_handle);
self.remove_group_if_empty(parent_group_handle);
}
}
}
}
fn path_to_group_name(path: &EntityPath) -> String {
path.iter().last().map_or("/".to_owned(), |c| c.to_string())
}