use std::collections::{BTreeMap, HashMap};
use crate::components::{
BodyDynamics, Children, Collider, Held, Interactable, MeshRenderer, ModelRenderer, Parent,
Pickup, Prop, PropBody, PropInstance, SceneMember, Transform,
};
use crate::ecs::asset_id::AssetId;
use crate::ecs::{Entity, PipelineContext};
pub(crate) use concinnity_core::ecs::EntityByName;
pub(crate) fn run(ctx: &mut PipelineContext) {
let props: Vec<(Entity, Prop)> = ctx
.query_with_entity::<Prop>()
.map(|(entity, prop)| (entity, prop.clone()))
.collect();
if props.is_empty() {
return;
}
let mut by_name: BTreeMap<AssetId, Entity> = BTreeMap::new();
for (entity, prop) in &props {
by_name.insert(prop.asset_id, *entity);
}
for (entity, prop) in &props {
ctx.insert(*entity, PropInstance);
ctx.insert(
*entity,
Transform {
position: prop.position,
rotation_deg: prop.rotation_deg,
scale: prop.scale,
},
);
if let Some(model) = prop.model {
ctx.insert(
*entity,
ModelRenderer {
model,
cull_distance: prop.cull_distance,
},
);
} else {
ctx.insert(
*entity,
MeshRenderer {
mesh: prop.mesh,
material: prop.material,
texture: prop.texture,
cull_distance: prop.cull_distance,
},
);
}
if let Some(collider) = &prop.collider {
ctx.insert(*entity, Collider(collider.clone()));
}
if prop.interactable {
ctx.insert(*entity, Interactable);
}
if prop.pickup {
ctx.insert(*entity, Pickup);
}
if prop.is_held {
ctx.insert(*entity, Held);
}
if let Some(scene) = prop.scene {
ctx.insert(*entity, SceneMember(scene));
}
}
for body in ctx.drain::<PropBody>() {
let Some(name) = body.prop_name else { continue };
let Some(&entity) = by_name.get(&name) else {
continue;
};
ctx.insert(
entity,
BodyDynamics {
mass: body.mass,
friction: body.friction,
restitution: body.restitution,
gravity_scale: body.gravity_scale,
linear_damping: body.linear_damping,
impact_clip: body.impact_clip,
impact_volume: body.impact_volume,
},
);
}
let mut children: HashMap<Entity, concinnity_memory::InlineVec<Entity>> = HashMap::new();
for (entity, prop) in &props {
if let Some(parent_id) = prop.parent
&& let Some(&parent) = by_name.get(&parent_id)
{
ctx.insert(*entity, Parent(parent));
children.entry(parent).or_default().push(*entity);
}
}
for (parent, kids) in children {
ctx.insert(parent, Children(kids));
}
if let Some(index) = ctx.resource_mut::<EntityByName>() {
index.0.extend(by_name);
} else {
ctx.insert_resource(EntityByName(by_name));
}
ctx.drain::<Prop>();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::components::{Prop, PropCollider};
use crate::ecs::SYSTEMS;
use crate::ecs::{MaterialHandle, MeshHandle, World};
fn prop(id: u32) -> Prop {
Prop {
asset_id: AssetId(id),
..Default::default()
}
}
#[test]
fn decomposes_props_onto_their_entities() {
let mut world = World::new();
let mut frame = prop(1);
frame.model = Some(AssetId(100));
frame.position = [1.0, 2.0, 3.0];
world.add_component(frame);
let mut panel = prop(2);
panel.mesh = Some(MeshHandle(101));
panel.material = Some(MaterialHandle(102));
panel.collider = Some(PropCollider::default());
panel.interactable = true;
panel.scene = Some(AssetId(200));
panel.parent = Some(AssetId(1));
panel.position = [4.0, 5.0, 6.0];
panel.rotation_deg = [0.0, 90.0, 0.0];
world.add_component(panel);
world.start(SYSTEMS).expect("start");
assert_eq!(world.query::<Prop>().count(), 0);
let models: Vec<_> = world
.join2::<ModelRenderer, Transform>()
.map(|(e, m, t)| (e, m.model, t.position))
.collect();
assert_eq!(models.len(), 1);
let (frame_e, model_id, frame_pos) = models[0];
assert_eq!(model_id, AssetId(100));
assert_eq!(frame_pos, [1.0, 2.0, 3.0]);
let meshes: Vec<_> = world
.join2::<MeshRenderer, Transform>()
.map(|(e, m, t)| (e, m.mesh, m.material, t.position, t.rotation_deg))
.collect();
assert_eq!(meshes.len(), 1);
let (panel_e, mesh_id, material_id, panel_pos, panel_rot) = meshes[0];
assert_eq!(mesh_id, Some(MeshHandle(101)));
assert_eq!(material_id, Some(MaterialHandle(102)));
assert_eq!(panel_pos, [4.0, 5.0, 6.0]);
assert_eq!(panel_rot, [0.0, 90.0, 0.0]);
assert_eq!(world.query::<Collider>().count(), 1);
assert_eq!(world.query::<Interactable>().count(), 1);
assert_eq!(world.query::<Pickup>().count(), 0);
let scene_members: Vec<_> = world
.join2::<SceneMember, MeshRenderer>()
.map(|(_, s, _)| s.0)
.collect();
assert_eq!(scene_members, vec![AssetId(200)]);
let parents: Vec<_> = world
.join2::<Parent, Transform>()
.map(|(e, p, _)| (e, p.0))
.collect();
assert_eq!(parents, vec![(panel_e, frame_e)]);
let kids: Vec<_> = world
.join2::<Children, ModelRenderer>()
.map(|(e, c, _)| (e, c.0.to_vec()))
.collect();
assert_eq!(kids, vec![(frame_e, vec![panel_e])]);
}
#[test]
fn forward_parent_reference_resolves() {
let mut world = World::new();
let mut child = prop(1);
child.mesh = Some(MeshHandle(10));
child.parent = Some(AssetId(2));
world.add_component(child);
let mut parent = prop(2);
parent.mesh = Some(MeshHandle(11));
world.add_component(parent);
world.start(SYSTEMS).expect("start");
let by_parent: Vec<_> = world
.join2::<Parent, MeshRenderer>()
.map(|(_, p, m)| (p.0, m.mesh))
.collect();
assert_eq!(by_parent.len(), 1);
let (parent_e, child_mesh) = by_parent[0];
assert_eq!(child_mesh, Some(MeshHandle(10)));
let parent_mesh = world
.join2::<MeshRenderer, Transform>()
.find(|(e, _, _)| *e == parent_e)
.map(|(_, m, _)| m.mesh);
assert_eq!(parent_mesh, Some(Some(MeshHandle(11))));
}
#[test]
fn decomposed_default_drains_prop_keeping_components() {
let mut world = World::new();
let mut a = prop(1);
a.mesh = Some(MeshHandle(10));
world.add_component(a);
let mut b = prop(2);
b.model = Some(AssetId(20));
world.add_component(b);
world.start(SYSTEMS).expect("start");
assert_eq!(world.query::<Prop>().count(), 0, "Prop column drained");
assert_eq!(world.query::<Transform>().count(), 2, "Transforms survive");
assert_eq!(world.query::<MeshRenderer>().count(), 1);
assert_eq!(world.query::<ModelRenderer>().count(), 1);
}
#[test]
fn prop_body_decomposes_to_body_dynamics_on_the_owner() {
let mut world = World::new();
let mut crate_prop = prop(1);
crate_prop.mesh = Some(MeshHandle(10));
crate_prop.collider = Some(PropCollider::default());
world.add_component(crate_prop);
let mut wall = prop(2);
wall.mesh = Some(MeshHandle(11));
wall.collider = Some(PropCollider::default());
world.add_component(wall);
world.add_component(crate::components::PropBody {
prop_name: Some(AssetId(1)),
mass: 4.0,
..Default::default()
});
world.start(SYSTEMS).expect("start");
assert_eq!(world.query::<crate::components::PropBody>().count(), 0);
let dynamics: Vec<_> = world
.join2::<BodyDynamics, MeshRenderer>()
.map(|(_, b, m)| (m.mesh, b.mass))
.collect();
assert_eq!(dynamics, vec![(Some(MeshHandle(10)), 4.0)]);
}
#[test]
fn despawn_removes_an_entitys_components() {
use crate::components::MeshRenderer;
let mut world = World::new();
world.add_component(Prop::default());
world.start(SYSTEMS).expect("start");
let entity = world
.join2::<Transform, MeshRenderer>()
.next()
.map(|(e, _, _)| e)
.expect("the Prop decomposed onto one entity");
world.despawn(entity);
assert_eq!(world.query::<Transform>().count(), 0);
assert_eq!(world.query::<MeshRenderer>().count(), 0);
}
}