1use std::collections::{BTreeMap, BTreeSet};
38use std::fmt;
39
40use crate::TextureLayers;
41use crate::asset::Asset;
42use crate::clip::ClipBinding;
43use crate::components::{Instance, Mesh, Texture, TextureFile, Topology};
44use crate::pass::PassLog;
45use crate::world::{Entity, RebuildLog, Rebuilt, World};
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub enum Measure {
50 Placements,
51 Primitives,
52 Triangles,
53 Textures,
54 CollisionTriangles,
56}
57
58impl Measure {
59 pub const ALL: [Measure; 5] = [
60 Measure::Placements,
61 Measure::Primitives,
62 Measure::Triangles,
63 Measure::Textures,
64 Measure::CollisionTriangles,
65 ];
66
67 pub const fn measured(self) -> bool {
69 !matches!(self, Measure::CollisionTriangles)
70 }
71}
72
73impl fmt::Display for Measure {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.write_str(match self {
76 Measure::Placements => "placements",
77 Measure::Primitives => "primitives",
78 Measure::Triangles => "triangles",
79 Measure::Textures => "textures",
80 Measure::CollisionTriangles => "collision triangles",
81 })
82 }
83}
84
85pub fn held(w: &World, e: Entity) -> Vec<(Measure, u64)> {
87 let mut out = Vec::new();
88 if w.has::<Instance>(e) {
89 out.push((Measure::Placements, 1));
90 }
91 if w.has::<Texture>(e) || w.has::<TextureLayers>(e) || w.has::<TextureFile>(e) {
92 out.push((Measure::Textures, 1));
93 }
94 if let Some(m) = w.get::<Mesh>(e) {
95 out.push((Measure::Primitives, m.primitives().len() as u64));
96 }
97 out
98}
99
100pub fn despawn(w: &mut World, e: Entity, log: &mut PassLog, why: &str) -> bool {
106 if !w.is_alive(e) {
107 return false;
108 }
109 for (m, n) in held(w, e) {
110 log.dropped(m, why, n);
111 }
112 let gone = w.despawn(e);
113 unlist(w, e, log);
114 unbind(w, e, log);
115 gone
116}
117
118pub fn unbind(w: &mut World, e: Entity, log: &mut PassLog) {
122 let naming: Vec<Entity> = w
123 .iter::<ClipBinding>()
124 .filter(|(_, b)| b.meshes.contains(&e))
125 .map(|(b, _)| b)
126 .collect();
127 for b in naming {
128 if let Some(binding) = w.get_mut::<ClipBinding>(b) {
129 let before = binding.meshes.len();
130 binding.meshes.retain(|m| *m != e);
131 log.count(
132 "clip binding meshes dropped with the entity they named",
133 (before - binding.meshes.len()) as u64,
134 );
135 }
136 }
137}
138
139pub fn unlist(w: &mut World, e: Entity, log: &mut PassLog) {
143 let listing: Vec<Entity> = w
144 .iter::<Asset>()
145 .filter(|(_, a)| a.members.iter().any(|(m, _)| *m == e))
146 .map(|(a, _)| a)
147 .collect();
148 for a in listing {
149 if let Some(asset) = w.get_mut::<Asset>(a) {
150 let before = asset.members.len();
151 asset.members.retain(|(m, _)| *m != e);
152 log.count(
153 "asset members dropped with the entity they listed",
154 (before - asset.members.len()) as u64,
155 );
156 }
157 }
158}
159
160#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
162pub enum Conservation {
163 #[default]
165 Off,
166 Report,
168 Enforce,
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
174pub struct Gap {
175 pub measure: Measure,
176 pub dropped: u64,
178 pub declared: u64,
180}
181
182impl fmt::Display for Gap {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 write!(
185 f,
186 "{} left out {} but declared {}",
187 self.measure, self.dropped, self.declared
188 )
189 }
190}
191
192fn triangles(mesh: &Mesh) -> Vec<u64> {
194 mesh.primitives()
195 .iter()
196 .map(|p| match p.topology {
197 Topology::Triangles => (p.indices.as_ref().map_or(p.vertex_count(), Vec::len) / 3) as u64,
198 _ => 0,
199 })
200 .collect()
201}
202
203fn exact(
206 before: &[u64],
207 id: u64,
208 rebuild: Option<&Option<Rebuilt>>,
209 now: Option<&(u64, Vec<u64>)>,
210) -> Option<(u64, u64)> {
211 let (now_id, now) = now?;
212 match rebuild {
213 Some(Some(r)) if r.source == id && r.made == *now_id => {
214 let kept: BTreeSet<usize> = r.from.iter().copied().collect();
215 let mut tris = 0;
216 for &j in &kept {
217 let pieces: u64 = r
218 .from
219 .iter()
220 .zip(now)
221 .filter(|(i, _)| **i == j)
222 .map(|(_, t)| t)
223 .sum();
224 tris += before[j].saturating_sub(pieces);
225 }
226 Some(((r.first - kept.len()) as u64, tris))
227 }
228 None if now_id == &id => Some((0, before.iter().zip(now).map(|(b, a)| b.saturating_sub(*a)).sum())),
229 _ => None,
230 }
231}
232
233#[derive(Debug)]
235pub(crate) struct Before {
236 placements: BTreeSet<Entity>,
237 textures: BTreeSet<Entity>,
238 meshes: BTreeMap<Entity, (u64, Vec<u64>)>,
240}
241
242impl Before {
243 pub(crate) fn take(w: &World) -> Self {
244 let mut textures: BTreeSet<Entity> = w.iter::<Texture>().map(|(e, _)| e).collect();
245 textures.extend(w.iter::<TextureLayers>().map(|(e, _)| e));
246 textures.extend(w.iter::<TextureFile>().map(|(e, _)| e));
247 Before {
248 placements: w.iter::<Instance>().map(|(e, _)| e).collect(),
249 textures,
250 meshes: w
251 .iter::<Mesh>()
252 .map(|(e, m)| (e, (m.id(), triangles(m))))
253 .collect(),
254 }
255 }
256
257 pub(crate) fn dropped(&self, w: &World, rebuilds: &RebuildLog) -> BTreeMap<Measure, u64> {
259 let placements = self.placements.iter().filter(|&&e| !w.has::<Instance>(e)).count() as u64;
260 let textures = self
261 .textures
262 .iter()
263 .filter(|&&e| !w.has::<Texture>(e) && !w.has::<TextureLayers>(e) && !w.has::<TextureFile>(e))
264 .count() as u64;
265 let (mut primitives, mut tris) = (0u64, 0u64);
266 let after: BTreeMap<Entity, (u64, Vec<u64>)> = w
267 .iter::<Mesh>()
268 .map(|(e, m)| (e, (m.id(), triangles(m))))
269 .collect();
270 let mut gone: Vec<(u64, &Vec<u64>)> = Vec::new();
273 let mut matched: BTreeSet<Entity> = BTreeSet::new();
274 for (e, (id, before)) in &self.meshes {
275 match exact(before, *id, rebuilds.get(e), after.get(e)) {
276 Some((p, t)) => {
277 primitives += p;
278 tris += t;
279 matched.insert(*e);
280 }
281 None => gone.push((*id, before)),
282 }
283 }
284 let (mut net_before, mut net_after) = (0u64, 0u64);
288 let mut unmatched: Vec<(Entity, &(u64, Vec<u64>))> = after
289 .iter()
290 .filter(|(e, _)| !matched.contains(*e))
291 .map(|(e, a)| (*e, a))
292 .collect();
293 for (id, before) in gone {
294 let found = unmatched.iter().position(|(e, (now_id, _))| {
295 *now_id == id
296 || rebuilds
297 .get(e)
298 .is_some_and(|r| r.as_ref().is_some_and(|r| r.source == id))
299 });
300 match found.and_then(|i| {
301 let (e, now) = unmatched[i];
302 exact(before, id, rebuilds.get(&e), Some(now)).map(|c| (i, c))
303 }) {
304 Some((i, (p, t))) => {
305 primitives += p;
306 tris += t;
307 unmatched.remove(i);
308 }
309 None => net_before += before.len() as u64,
310 }
311 }
312 net_after += unmatched
315 .iter()
316 .map(|(_, (_, now))| now.len() as u64)
317 .sum::<u64>();
318 primitives += net_before.saturating_sub(net_after);
319 BTreeMap::from([
320 (Measure::Placements, placements),
321 (Measure::Primitives, primitives),
322 (Measure::Triangles, tris),
323 (Measure::Textures, textures),
324 ])
325 }
326
327 pub(crate) fn gaps(&self, w: &World, rebuilds: &RebuildLog, log: &PassLog) -> Vec<Gap> {
330 self.dropped(w, rebuilds)
331 .into_iter()
332 .filter(|(measure, _)| measure.measured())
333 .map(|(measure, dropped)| Gap {
334 measure,
335 dropped,
336 declared: log.declared(measure),
337 })
338 .filter(|g| g.dropped != g.declared)
339 .collect()
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use crate::components::Primitive;
347
348 use crate::components::{ColorSpace, Semantic, Stream, TexRole};
349 use crate::pass::{Error, Pass, PassManager, PassRegistry, Pipeline, RunReport};
350 use crate::texformat::TexFormat;
351 use std::collections::BTreeMap as Map;
352
353 fn set(m: &mut Mesh, i: usize, p: Primitive) {
356 let mut q = m.primitive_mut(i).unwrap();
357 let f = q.fields();
358 (*f.topology, *f.streams, *f.indices, *f.material) = (p.topology, p.streams, p.indices, p.material);
359 }
360
361 fn prim(n: u32) -> Primitive {
362 Primitive {
363 topology: Topology::Triangles,
364 streams: Map::from([(Semantic::Position, Stream::F32x3(vec![[0.; 3]; 3]))]),
365 indices: Some((0..n).flat_map(|_| [0, 1, 2]).collect()),
366 material: None,
367 }
368 }
369
370 fn texture() -> Texture {
371 Texture {
372 width: 1,
373 height: 1,
374 format: TexFormat::Rgba8Unorm,
375 levels: vec![vec![0; 4]],
376 color_space: ColorSpace::Srgb,
377 role: TexRole::Color,
378 normal: None,
379 }
380 }
381
382 fn world() -> World {
384 let mut w = World::new();
385 let p = w.spawn();
386 w.insert(p, Mesh::new(vec![prim(1), prim(2), prim(3)]));
387 for _ in 0..2 {
388 let e = w.spawn();
389 w.insert(
390 e,
391 Instance {
392 prototype: p,
393 variant: 0,
394 },
395 );
396 w.insert(e, crate::Transform::IDENTITY);
397 }
398 let t = w.spawn();
399 w.insert(t, TextureFile { path: "a.png".into() });
400 w
401 }
402
403 struct Do {
405 f: fn(&mut World),
406 drops: Vec<(Measure, u64)>,
407 }
408 impl Pass for Do {
409 fn name(&self) -> &'static str {
410 "do"
411 }
412 fn removes(&self) -> &[&'static str] {
413 &["core.instance", "core.texture_file", "core.mesh"]
414 }
415 fn provides(&self) -> &[&'static str] {
416 &["core.texture", "core.mesh"]
417 }
418 fn modifies(&self) -> &[&'static str] {
419 &["core.mesh"]
420 }
421 fn despawns_entities(&self) -> bool {
422 true
423 }
424 fn run(&self, w: &mut World, log: &mut PassLog) -> Result<(), String> {
425 (self.f)(w);
426 for &(m, n) in &self.drops {
427 log.dropped(m, "on purpose", n);
428 }
429 Ok(())
430 }
431 }
432
433 fn run_with(c: Conservation, f: fn(&mut World), drops: Vec<(Measure, u64)>) -> Result<RunReport, Error> {
434 let mut report = RunReport::default();
435 PassManager::new(PassRegistry::new()).with_conservation(c).run(
436 &mut world(),
437 &Pipeline {
438 passes: vec![Box::new(Do { f, drops })],
439 },
440 &mut report,
441 )?;
442 Ok(report)
443 }
444
445 fn run(f: fn(&mut World), drops: Vec<(Measure, u64)>) -> Result<RunReport, Error> {
446 run_with(Conservation::Enforce, f, drops)
447 }
448
449 fn left_out(f: fn(&mut World), drops: Vec<(Measure, u64)>) -> (Measure, u64, u64) {
450 match run(f, drops) {
451 Err(Error::Conservation {
452 measure,
453 dropped,
454 declared,
455 ..
456 }) => (measure, dropped, declared),
457 other => panic!("{other:?}"),
458 }
459 }
460
461 #[test]
464 fn the_check_is_off_unless_asked_and_reports_or_fails_as_asked() {
465 let despawn = |w: &mut World| {
466 w.despawn(Entity::from_index(1));
467 };
468 let r = run_with(Conservation::Off, despawn, vec![]).unwrap();
469 assert!(r.steps[0].conservation.is_empty());
470 let r = run_with(Conservation::Report, despawn, vec![]).unwrap();
471 assert_eq!(
472 r.steps[0].conservation,
473 vec![Gap {
474 measure: Measure::Placements,
475 dropped: 1,
476 declared: 0
477 }]
478 );
479 assert!(
480 r.to_string().contains("!placements left out 1 but declared 0"),
481 "{r}"
482 );
483 assert_eq!(left_out(despawn, vec![]), (Measure::Placements, 1, 0));
484 }
485
486 #[test]
488 fn collision_drops_are_reported_not_measured() {
489 let nothing = |_: &mut World| {};
490 let r = run(nothing, vec![(Measure::CollisionTriangles, 3)]).unwrap();
491 assert!(r.steps[0].conservation.is_empty());
492 assert_eq!(r.steps[0].log.declared(Measure::CollisionTriangles), 3);
493 }
494
495 #[test]
496 fn placements_and_textures_are_counted_by_entity() {
497 let despawn = |w: &mut World| {
499 w.despawn(Entity::from_index(1));
500 };
501 run(despawn, vec![(Measure::Placements, 1)]).unwrap();
502 assert_eq!(
503 left_out(despawn, vec![(Measure::Placements, 2)]),
504 (Measure::Placements, 1, 2)
505 );
506 let swap = |w: &mut World| {
508 w.despawn(Entity::from_index(1));
509 let e = w.spawn();
510 w.insert(
511 e,
512 Instance {
513 prototype: Entity::from_index(0),
514 variant: 0,
515 },
516 );
517 w.insert(e, crate::Transform::IDENTITY);
518 };
519 assert_eq!(left_out(swap, vec![]), (Measure::Placements, 1, 0));
520 let load = |w: &mut World| {
522 let t = Entity::from_index(3);
523 w.remove::<TextureFile>(t);
524 w.insert(t, texture());
525 };
526 run(load, vec![]).unwrap();
527 let lose = |w: &mut World| {
528 w.remove::<TextureFile>(Entity::from_index(3));
529 };
530 assert_eq!(left_out(lose, vec![]), (Measure::Textures, 1, 0));
531 }
532
533 #[test]
534 fn a_rebuild_is_exact_so_a_split_cannot_hide_a_drop() {
535 let split = |w: &mut World| {
537 w.rebuild_primitives(Entity::from_index(0), |_| {
538 Ok((vec![prim(2), prim(1), prim(2)], vec![1, 2, 2]))
539 })
540 .unwrap();
541 };
542 assert_eq!(left_out(split, vec![]), (Measure::Primitives, 1, 0));
544 run(split, vec![(Measure::Primitives, 1)]).unwrap();
545 let twice = |w: &mut World| {
547 let e = Entity::from_index(0);
548 w.rebuild_primitives(e, |_| Ok((vec![prim(2), prim(1), prim(2)], vec![1, 2, 2])))
549 .unwrap();
550 w.rebuild_primitives(e, |_| Ok((vec![prim(2), prim(2)], vec![0, 2])))
551 .unwrap();
552 };
553 assert_eq!(
554 left_out(twice, vec![(Measure::Primitives, 1)]),
555 (Measure::Triangles, 1, 0)
556 );
557 run(twice, vec![(Measure::Primitives, 1), (Measure::Triangles, 1)]).unwrap();
558 }
559
560 #[test]
561 fn triangles_are_counted_inside_kept_primitives() {
562 let decimate = |w: &mut World| {
564 set(w.get_mut::<Mesh>(Entity::from_index(0)).unwrap(), 2, prim(1));
565 };
566 assert_eq!(left_out(decimate, vec![]), (Measure::Triangles, 2, 0));
567 run(decimate, vec![(Measure::Triangles, 2)]).unwrap();
568 }
569
570 #[test]
571 fn a_replaced_mesh_is_counted_net_whatever_its_primitive_count() {
572 let insert = |w: &mut World| {
575 w.insert(Entity::from_index(0), Mesh::new(vec![prim(1), prim(1), prim(1)]));
576 };
577 run(insert, vec![]).unwrap();
578 let assign = |w: &mut World| {
579 *w.get_mut::<Mesh>(Entity::from_index(0)).unwrap() = Mesh::new(vec![prim(1), prim(1), prim(1)]);
580 };
581 run(assign, vec![]).unwrap();
582 let between = |w: &mut World| {
584 let e = Entity::from_index(0);
585 w.rebuild_primitives(e, |_| Ok((vec![prim(2), prim(3), prim(1)], vec![1, 2, 0])))
586 .unwrap();
587 w.insert(e, Mesh::new(vec![prim(1), prim(1), prim(1)]));
588 w.rebuild_primitives(e, |_| Ok((vec![prim(1), prim(1)], vec![1, 2])))
589 .unwrap();
590 };
591 assert_eq!(left_out(between, vec![]), (Measure::Primitives, 1, 0));
592 run(between, vec![(Measure::Primitives, 1)]).unwrap();
593 let before = |w: &mut World| {
595 let e = Entity::from_index(0);
596 w.insert(e, Mesh::new(vec![prim(1), prim(1), prim(1)]));
597 w.rebuild_primitives(e, |_| Ok((vec![prim(1), prim(1)], vec![1, 2])))
598 .unwrap();
599 };
600 run(before, vec![(Measure::Primitives, 1)]).unwrap();
601 }
602
603 #[test]
607 fn an_edited_mesh_is_counted_exactly() {
608 let split_and_drop = |w: &mut World| {
609 w.edit_mesh(Entity::from_index(0), |m| {
610 let p = m.primitives().to_vec();
611 *m = Mesh::new(vec![p[1].clone(), prim(1), prim(2)]);
612 Ok(Some(vec![1, 2, 2]))
613 })
614 .unwrap();
615 };
616 assert_eq!(left_out(split_and_drop, vec![]), (Measure::Primitives, 1, 0));
617 run(split_and_drop, vec![(Measure::Primitives, 1)]).unwrap();
618 let in_place = |w: &mut World| {
619 w.edit_mesh(Entity::from_index(0), |m| {
620 set(m, 0, prim(0));
621 Ok(None)
622 })
623 .unwrap();
624 };
625 assert_eq!(left_out(in_place, vec![]), (Measure::Triangles, 1, 0));
626 let renewed_then_split = |w: &mut World| {
629 let e = Entity::from_index(0);
630 w.edit_mesh(e, |m| {
631 *m = Mesh::new(m.primitives().to_vec());
632 Ok(None)
633 })
634 .unwrap();
635 w.edit_mesh(e, |m| {
636 let p = m.primitives().to_vec();
637 *m = Mesh::new(vec![p[1].clone(), prim(1), prim(2)]);
638 Ok(Some(vec![1, 2, 2]))
639 })
640 .unwrap();
641 };
642 assert_eq!(left_out(renewed_then_split, vec![]), (Measure::Primitives, 1, 0));
643 let mut w = world();
644 let e = Entity::from_index(0);
645 assert!(w.edit_mesh(e, |_| Err("no".into())).is_err());
646 assert_eq!(w.get::<Mesh>(e).map(|m| m.primitives().len()), Some(3));
647 assert!(w.edit_mesh(e, |_| Ok(Some(vec![5]))).is_err());
648 assert_eq!(w.get::<Mesh>(e).map(|m| m.primitives().len()), Some(3));
649 assert_eq!(w.edit_mesh(Entity::from_index(1), |_| Ok(None)), Ok(false));
650 }
651
652 #[test]
657 fn clone_move_duplicate_and_linear_maps_count_as_they_should() {
658 let clone_back = |w: &mut World| {
659 let e = Entity::from_index(0);
660 let mut m = w.get::<Mesh>(e).unwrap().clone();
661 set(&mut m, 1, prim(1));
662 w.insert(e, m);
663 };
664 assert_eq!(left_out(clone_back, vec![]), (Measure::Triangles, 1, 0));
665 let moved = |w: &mut World| {
666 let form = w.spawn();
667 w.insert(form, Mesh::new(vec![prim(1)]));
668 w.move_components(form, Entity::from_index(0)).unwrap();
669 };
670 assert_eq!(left_out(moved, vec![]), (Measure::Primitives, 2, 0));
671 run(moved, vec![(Measure::Primitives, 2)]).unwrap();
672 let duplicated = |w: &mut World| {
673 let p = Entity::from_index(0);
674 let copy = w.duplicate(p).unwrap();
675 for e in [Entity::from_index(1), Entity::from_index(2)] {
676 w.get_mut::<Instance>(e).unwrap().prototype = copy;
677 }
678 w.despawn(p);
679 };
680 run(duplicated, vec![]).unwrap();
681 let mirrored = |w: &mut World| {
682 let l = crate::Linear::mirror_x();
683 w.apply_linear(Entity::from_index(0), &l).unwrap();
684 };
685 run(mirrored, vec![]).unwrap();
686 let mut w = world();
687 w.start_rebuild_log();
688 w.rebuild_primitives(Entity::from_index(0), |_| Ok((vec![prim(1)], vec![0])))
689 .unwrap();
690 assert!(w.clone().take_rebuild_log().is_empty());
691 assert!(w.subset(&w.entities().collect()).take_rebuild_log().is_empty());
692 assert_eq!(w.take_rebuild_log().len(), 1);
693 }
694
695 #[test]
699 fn a_mesh_is_followed_by_its_identity_across_entities() {
700 fn stand_in(w: &mut World) -> Entity {
701 let p = Entity::from_index(0);
702 let copy = w.duplicate(p).unwrap();
703 for e in [Entity::from_index(1), Entity::from_index(2)] {
704 w.get_mut::<Instance>(e).unwrap().prototype = copy;
705 }
706 w.despawn(p);
707 copy
708 }
709 fn moved(w: &mut World) -> Entity {
710 let to = w.spawn();
711 w.move_components(Entity::from_index(0), to).unwrap();
712 for e in [Entity::from_index(1), Entity::from_index(2)] {
713 w.get_mut::<Instance>(e).unwrap().prototype = to;
714 }
715 to
716 }
717 let stand_in_edited = |w: &mut World| {
719 let copy = stand_in(w);
720 set(w.get_mut::<Mesh>(copy).unwrap(), 2, prim(1));
721 };
722 assert_eq!(left_out(stand_in_edited, vec![]), (Measure::Triangles, 2, 0));
723 run(stand_in_edited, vec![(Measure::Triangles, 2)]).unwrap();
724 let stand_in_rebuilt = |w: &mut World| {
725 let copy = stand_in(w);
726 w.rebuild_primitives(copy, |_| Ok((vec![prim(1), prim(2)], vec![0, 1])))
727 .unwrap();
728 };
729 assert_eq!(left_out(stand_in_rebuilt, vec![]), (Measure::Primitives, 1, 0));
730 let moved_edited = |w: &mut World| {
732 let to = moved(w);
733 set(w.get_mut::<Mesh>(to).unwrap(), 2, prim(1));
734 };
735 assert_eq!(left_out(moved_edited, vec![]), (Measure::Triangles, 2, 0));
736 let moved_split = |w: &mut World| {
737 let to = moved(w);
738 w.rebuild_primitives(to, |_| Ok((vec![prim(2), prim(1), prim(2)], vec![1, 2, 2])))
739 .unwrap();
740 };
741 assert_eq!(left_out(moved_split, vec![]), (Measure::Primitives, 1, 0));
742 let copy_edited = |w: &mut World| {
744 let copy = w.duplicate(Entity::from_index(0)).unwrap();
745 set(w.get_mut::<Mesh>(copy).unwrap(), 2, prim(1));
746 };
747 run(copy_edited, vec![]).unwrap();
748 let copy_rebuilt = |w: &mut World| {
749 let to = w.spawn();
750 w.copy_components(Entity::from_index(0), to);
751 w.rebuild_primitives(to, |_| Ok((vec![prim(1)], vec![0])))
752 .unwrap();
753 };
754 run(copy_rebuilt, vec![]).unwrap();
755 let clone_elsewhere = |w: &mut World| {
756 let m = w.get::<Mesh>(Entity::from_index(0)).unwrap().clone();
757 let to = w.spawn();
758 w.insert(to, m);
759 set(w.get_mut::<Mesh>(to).unwrap(), 0, prim(0));
760 };
761 run(clone_elsewhere, vec![]).unwrap();
762 let source_rebuilt = |w: &mut World| {
764 w.duplicate(Entity::from_index(0)).unwrap();
765 w.rebuild_primitives(Entity::from_index(0), |_| {
766 Ok((vec![prim(1), prim(2)], vec![0, 1]))
767 })
768 .unwrap();
769 };
770 assert_eq!(left_out(source_rebuilt, vec![]), (Measure::Primitives, 1, 0));
771 }
772
773 #[test]
775 fn despawn_declares_what_the_entity_held() {
776 let mut w = World::new();
777 let proto = w.spawn();
778 let e = w.spawn();
779 w.insert(
780 e,
781 Instance {
782 prototype: proto,
783 variant: 0,
784 },
785 );
786 w.insert(e, crate::Transform::IDENTITY);
787 let prim = Primitive {
788 topology: Topology::Triangles,
789 streams: Default::default(),
790 indices: None,
791 material: None,
792 };
793 w.insert(e, Mesh::new(vec![prim.clone(), prim]));
794 let mut log = PassLog::default();
795 assert!(despawn(&mut w, e, &mut log, "left out"));
796 assert!(!despawn(&mut w, e, &mut log, "left out"));
797 assert_eq!(log.declared(Measure::Placements), 1);
798 assert_eq!(log.declared(Measure::Primitives), 2);
799 assert_eq!(log.declared(Measure::Textures), 0);
800 assert_eq!(
801 log.drops.get(&(Measure::Primitives, "left out".to_owned())),
802 Some(&2)
803 );
804 }
805
806 #[test]
809 fn despawn_drops_the_entity_from_the_assets_that_list_it() {
810 use crate::asset::Role;
811 let mut w = World::new();
812 let (part, rig, other) = (w.spawn(), w.spawn(), w.spawn());
813 let (a, b) = (w.spawn(), w.spawn());
814 w.insert(
815 a,
816 Asset {
817 members: vec![(rig, Role::Rig), (part, Role::Part)],
818 },
819 );
820 w.insert(
821 b,
822 Asset {
823 members: vec![(part, Role::Part), (other, Role::Part)],
824 },
825 );
826 let mut log = PassLog::default();
827 assert!(despawn(&mut w, part, &mut log, "left out"));
828 assert_eq!(w.get::<Asset>(a).unwrap().members, [(rig, Role::Rig)]);
829 assert_eq!(w.get::<Asset>(b).unwrap().members, [(other, Role::Part)]);
830 assert_eq!(log.counts["asset members dropped with the entity they listed"], 2);
831 let mut log = PassLog::default();
832 assert!(despawn(&mut w, other, &mut log, "left out"));
833 assert_eq!(log.counts["asset members dropped with the entity they listed"], 1);
834 let loose = w.spawn();
836 let mut log = PassLog::default();
837 assert!(despawn(&mut w, loose, &mut log, "left out"));
838 assert!(
839 !log.counts
840 .contains_key("asset members dropped with the entity they listed")
841 );
842 }
843
844 #[test]
847 fn despawn_drops_the_entity_from_the_clip_bindings_that_name_it() {
848 let mut w = World::new();
849 let (clip, a, b) = (w.spawn(), w.spawn(), w.spawn());
850 let (first, second) = (w.spawn(), w.spawn());
851 for (e, meshes) in [(first, vec![a, b]), (second, vec![a])] {
852 w.insert(
853 e,
854 ClipBinding {
855 clip,
856 rig: None,
857 meshes,
858 },
859 );
860 }
861 let mut log = PassLog::default();
862 assert!(despawn(&mut w, a, &mut log, "left out"));
863 assert_eq!(w.get::<ClipBinding>(first).unwrap().meshes, [b]);
864 assert!(w.get::<ClipBinding>(second).unwrap().meshes.is_empty());
865 assert_eq!(w.get::<ClipBinding>(first).unwrap().clip, clip);
866 assert_eq!(
867 log.counts["clip binding meshes dropped with the entity they named"],
868 2
869 );
870 let loose = w.spawn();
871 let mut log = PassLog::default();
872 assert!(despawn(&mut w, loose, &mut log, "left out"));
873 assert!(
874 !log.counts
875 .contains_key("clip binding meshes dropped with the entity they named")
876 );
877 }
878}