use unlit_ecs::reflect::ComponentEntry;
use crate::components::{
Camera, GpuMaterial, GpuMesh, GpuRenderPipeline, InstanceVisibility, MorphBinding,
MorphWeights, RenderLoadOps, SkinBinding, SkinPose, Transform, ZSortedDrawing,
};
use crate::input::InputEvents;
use crate::unlit::{InstanceColor, InstanceCutoff, UnlitPipelineKey};
inventory::submit! { ComponentEntry::new::<Transform>() }
inventory::submit! { ComponentEntry::new::<Camera>() }
inventory::submit! { ComponentEntry::new::<RenderLoadOps>() }
inventory::submit! { ComponentEntry::new::<GpuMesh>() }
inventory::submit! { ComponentEntry::new::<GpuMaterial>() }
inventory::submit! { ComponentEntry::new::<GpuRenderPipeline<UnlitPipelineKey>>() }
inventory::submit! { ComponentEntry::new::<ZSortedDrawing>() }
inventory::submit! { ComponentEntry::new::<InstanceVisibility>() }
inventory::submit! { ComponentEntry::new::<SkinPose>() }
inventory::submit! { ComponentEntry::new::<MorphWeights>() }
inventory::submit! { ComponentEntry::new::<SkinBinding>() }
inventory::submit! { ComponentEntry::new::<MorphBinding>() }
inventory::submit! { ComponentEntry::new::<InstanceColor>() }
inventory::submit! { ComponentEntry::new::<InstanceCutoff>() }
inventory::submit! { ComponentEntry::new::<InputEvents>() }
#[cfg(test)]
mod tests {
use super::*;
use unlit_ecs::reflect::{contains, encode_json, entry, names, push_json, set_json};
use unlit_ecs::{DynamicBundle, Entity, World, bundle};
#[test]
fn a_reflected_component_round_trips_through_its_own_fields() {
let mut world = World::new();
let entity = world.root_scope_mut().spawn(bundle![Transform::default()]);
set_json(
&world.root_scope(),
entity,
"Transform",
r#"{"translation":[1.0,2.0,3.0]}"#,
)
.expect("a partial transform is accepted");
let encoded =
encode_json(&world.root_scope(), entity, "Transform").expect("the transform encodes");
let value: Transform = facet_json::from_str(&encoded).expect("the encoding is JSON");
assert_eq!(value.translation, glam::Vec3::new(1.0, 2.0, 3.0));
assert_eq!(value.scale, glam::Vec3::ONE);
assert_eq!(value.rotation, glam::Quat::IDENTITY);
}
#[test]
fn a_qualified_name_addresses_the_same_component() {
assert!(contains("Transform"));
assert!(contains("unlit3d::components::Transform"));
assert!(!contains("unlit3d::other::Transform"));
let mut world = World::new();
let entity = world.root_scope_mut().spawn(bundle![Transform::default()]);
assert!(
encode_json(
&world.root_scope(),
entity,
"unlit3d::components::Transform"
)
.is_ok(),
"the qualified name reaches the entry"
);
}
#[test]
fn spawning_merges_over_the_default() {
let mut builder = DynamicBundle::new().named("reflect test");
push_json(
&mut builder,
"Transform",
r#"{"translation":[4.0,5.0,6.0]}"#,
)
.expect("a partial transform is accepted");
let mut world = World::new();
let entity = world.root_scope_mut().spawn(builder);
let encoded = encode_json(&world.root_scope(), entity, "Transform")
.expect("the spawned transform encodes");
let value: Transform = facet_json::from_str(&encoded).expect("the encoding is JSON");
assert_eq!(value.translation, glam::Vec3::new(4.0, 5.0, 6.0));
assert_eq!(value.scale, glam::Vec3::ONE);
}
#[test]
fn an_unknown_component_names_the_known_ones() {
let error = entry("Nope").expect_err("an unknown component is refused");
let message = error.to_string();
assert!(message.contains("Nope"), "{message}");
assert!(message.contains("Transform"), "{message}");
}
#[test]
fn a_read_only_component_reports_its_own_reason() {
use arrayvec::ArrayVec;
use std::rc::Rc;
use unlit_wgpu::resources::{ResourceGraph, Virtual};
use unlit_wgpu::specialize::VertexLayout;
use crate::bounds::Aabb;
use crate::components::MeshParts;
let mut world = World::new();
let mut graph = ResourceGraph::new();
let root = graph.insert(Virtual, None);
let mesh = GpuMesh {
parts: Rc::new(MeshParts {
root,
vertex_buffers: ArrayVec::new(),
index_buffer: None,
bind_group_id: None,
vertex_allocation: None,
index_allocation: None,
morph_deltas_allocation: None,
metadata_index: 0,
}),
vertex_layout: VertexLayout::default(),
count: 3,
first: 0,
base_vertex: 0,
indexed: false,
aabb: Aabb::new(glam::Vec3::ZERO, glam::Vec3::splat(0.5)),
morph_targets: 0,
skinned: false,
};
let entity = world.root_scope_mut().spawn(bundle![mesh]);
let error = set_json(&world.root_scope(), entity, "GpuMesh", "{}")
.expect_err("GpuMesh is read-only");
assert!(error.to_string().contains("read-only"), "{error}");
let encoded =
encode_json(&world.root_scope(), entity, "GpuMesh").expect("GpuMesh still encodes");
assert!(encoded.contains("count"), "{encoded}");
}
#[test]
fn visibility_is_settable_through_its_own_field() {
use crate::components::InstanceVisibility;
let mut world = World::new();
let entity = world
.root_scope_mut()
.spawn(bundle![InstanceVisibility::default()]);
set_json(
&world.root_scope(),
entity,
"InstanceVisibility",
r#"{"visible":false}"#,
)
.expect("visibility is writable");
let encoded =
encode_json(&world.root_scope(), entity, "InstanceVisibility").expect("it encodes");
let value: InstanceVisibility =
facet_json::from_str(&encoded).expect("the encoding is JSON");
assert!(!value.visible, "the write did not reach the component");
}
#[test]
fn a_component_the_entity_lacks_is_reported() {
let world = World::new();
let entity = Entity::from_raw(0, 0);
let error = encode_json(&world.root_scope(), entity, "Transform")
.expect_err("the entity has nothing");
assert!(error.to_string().contains("has no Transform"), "{error}");
}
#[test]
fn a_proxied_field_is_set_without_disturbing_the_others() {
let mut world = World::new();
let entity = world
.root_scope_mut()
.spawn(bundle![RenderLoadOps::default()]);
set_json(
&world.root_scope(),
entity,
"RenderLoadOps",
r#"{"depth":{"clear":0.5}}"#,
)
.expect("a partial load op is accepted");
let ops = world
.root_scope()
.query_one::<&RenderLoadOps>(entity)
.expect("the load ops are there");
assert_eq!(ops.depth, wgpu::LoadOp::Clear(0.5));
assert_eq!(ops.color, RenderLoadOps::default().color);
}
#[test]
fn every_submitted_name_is_distinct() {
let mut names: Vec<&str> = names().collect();
names.sort_unstable();
let count = names.len();
names.dedup();
assert_eq!(names.len(), count, "component names are unique");
assert!(
count >= 14,
"the built-in components are submitted: {names:?}"
);
}
}