use crate::components::{Children, RenderHandle, SkeletonPose};
use crate::ecs::{Entity, PipelineContext};
use crate::gfx::ops::RenderOps;
use crate::gfx::render_slots::RenderSlots;
pub(super) fn collect_subtree(ctx: &PipelineContext, root: Entity) -> Vec<Entity> {
let mut out: Vec<Entity> = Vec::new();
let mut stack = vec![root];
while let Some(entity) = stack.pop() {
if out.contains(&entity) {
continue;
}
out.push(entity);
if let Some(children) = ctx.get::<Children>(entity) {
stack.extend(children.0.iter().copied());
}
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum RetiredSlot {
Draw(usize),
Skinned(usize),
}
fn despawn_collected(
ctx: &mut PipelineContext,
root: Entity,
mut retire: impl FnMut(RetiredSlot),
) -> usize {
let entities = collect_subtree(ctx, root);
for &entity in &entities {
let slots: concinnity_core::memory::InlineVec<u32> = ctx
.get::<RenderHandle>(entity)
.map(|h| h.draws.clone())
.unwrap_or_default();
for slot in slots {
retire(RetiredSlot::Draw(slot as usize));
}
if let Some(skinned_index) = ctx.get::<SkeletonPose>(entity).map(|p| p.skinned_index) {
retire(RetiredSlot::Skinned(skinned_index));
}
ctx.despawn(entity);
}
entities.len()
}
pub(super) fn despawn_subtree(
ctx: &mut PipelineContext,
ops: &mut RenderOps,
slots: &mut RenderSlots,
root: Entity,
) -> usize {
despawn_collected(ctx, root, |slot| match slot {
RetiredSlot::Draw(i) => {
slots.free_draw(i);
ops.record(move |backend| backend.retire_draw_object(i));
}
RetiredSlot::Skinned(i) => {
slots.release_skinned(i);
ops.record(move |backend| backend.retire_skinned_draw_object(i));
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blob::BlobData;
use crate::components::{Parent, Transform};
use crate::ecs::{ComponentStorage, Resources};
use crate::gfx::profile::FrameProfile;
fn run<R>(body: impl FnOnce(&mut PipelineContext) -> R) -> R {
let mut components = ComponentStorage::default();
let mut blob = BlobData::empty();
let mut profile = FrameProfile::default();
let mut resources = Resources::new();
let scratch = crate::ecs::Arena::with_capacity(64 * 1024);
let mut ctx = PipelineContext {
components: &mut components,
blob: &mut blob,
profile: &mut profile,
resources: &mut resources,
frame: crate::ecs::FrameContext::new(&scratch),
};
body(&mut ctx)
}
#[test]
fn collect_subtree_gathers_root_and_descendants() {
run(|ctx| {
let root = ctx.components.spawn();
let a = ctx.components.spawn();
let b = ctx.components.spawn();
let c = ctx.components.spawn();
ctx.insert(root, Children([a, b].into()));
ctx.insert(a, Children([c].into()));
let mut got = collect_subtree(ctx, root);
got.sort_by_key(|e| e.index());
let mut want = vec![root, a, b, c];
want.sort_by_key(|e| e.index());
assert_eq!(got, want);
});
}
#[test]
fn collect_subtree_of_a_leaf_is_just_itself() {
run(|ctx| {
let lone = ctx.components.spawn();
ctx.insert(lone, Transform::default());
assert_eq!(collect_subtree(ctx, lone), vec![lone]);
});
}
#[test]
fn despawn_cascade_removes_subtree_and_retires_its_slots() {
run(|ctx| {
let parent = ctx.components.spawn();
let child = ctx.components.spawn();
let other = ctx.components.spawn();
ctx.insert(parent, Transform::default());
ctx.insert(
parent,
RenderHandle {
draws: [10, 11].into(),
},
);
ctx.insert(parent, Children([child].into()));
ctx.insert(child, Transform::default());
ctx.insert(child, RenderHandle { draws: [12].into() });
ctx.insert(child, Parent(parent));
ctx.insert(other, Transform::default());
ctx.insert(other, RenderHandle { draws: [99].into() });
let mut retired: Vec<usize> = Vec::new();
let removed = despawn_collected(ctx, parent, |slot| {
if let RetiredSlot::Draw(i) = slot {
retired.push(i);
}
});
assert_eq!(removed, 2, "parent + child removed");
retired.sort_unstable();
assert_eq!(
retired,
vec![10, 11, 12],
"every slot in the subtree retired"
);
assert!(ctx.get::<Transform>(parent).is_none());
assert!(ctx.get::<Transform>(child).is_none());
assert!(ctx.get::<RenderHandle>(child).is_none());
assert_eq!(ctx.query::<Transform>().count(), 1, "only `other` remains");
let survivor = ctx.get::<RenderHandle>(other).expect("other survives");
assert_eq!(survivor.draws, vec![99]);
});
}
#[test]
fn despawn_an_entity_without_a_render_handle_retires_nothing() {
run(|ctx| {
let e = ctx.components.spawn();
ctx.insert(e, Transform::default());
let mut retired: Vec<RetiredSlot> = Vec::new();
let removed = despawn_collected(ctx, e, |slot| retired.push(slot));
assert_eq!(removed, 1);
assert!(retired.is_empty(), "no slots to retire");
assert!(ctx.get::<Transform>(e).is_none(), "entity despawned");
});
}
#[test]
fn despawn_retires_a_skinned_instance_slot() {
use crate::components::SkeletonPose;
use crate::gfx::skeleton::Skeleton;
run(|ctx| {
let skinned = ctx.components.spawn();
ctx.insert(
skinned,
SkeletonPose::new(
crate::ecs::SkinnedMeshHandle(1),
4,
Skeleton::new(Vec::new()),
),
);
let mut retired: Vec<RetiredSlot> = Vec::new();
let removed = despawn_collected(ctx, skinned, |slot| retired.push(slot));
assert_eq!(removed, 1);
assert_eq!(
retired,
vec![RetiredSlot::Skinned(4)],
"the skinned_index was retired, and there were no draw slots"
);
assert!(
ctx.get::<SkeletonPose>(skinned).is_none(),
"entity despawned"
);
});
}
}