diff --git a/examples/entity_builder_comparison.rs b/examples/entity_builder_comparison.rs new file mode 100644 index 0000000000..4f72282be9 --- /dev/null +++ b/examples/entity_builder_comparison.rs @@ -0,0 +1,191 @@ +use bevy::prelude::*; +use bevy::ecs::*; + +fn main() { + AppBuilder::new().add_defaults().setup_world(setup).run(); +} + +#[allow(dead_code)] +fn create_entities_insert_vec(world: &mut World, plane_handle: Handle, cube_handle: Handle) { + // plane + world.insert( + (), + vec![( + plane_handle.clone(), + Material::new(Albedo::Color(math::vec4(0.1, 0.2, 0.1, 1.0))), + LocalToWorld::identity(), + Translation::new(0.0, 0.0, 0.0), + )], + ); + + // cube + world.insert( + (), + vec![( + cube_handle, + Material::new(Albedo::Color(math::vec4(0.5, 0.3, 0.3, 1.0))), + LocalToWorld::identity(), + Translation::new(0.0, 0.0, 1.0), + )], + ); + + // light + world.insert( + (), + vec![( + Light { + color: wgpu::Color { + r: 0.8, + g: 0.8, + b: 0.5, + a: 1.0, + }, + fov: f32::to_radians(60.0), + depth: 0.1..50.0, + target_view: None, + }, + LocalToWorld::identity(), + Translation::new(4.0, -4.0, 5.0), + Rotation::from_euler_angles(0.0, 0.0, 0.0), + )], + ); + + // camera + world.insert( + (), + vec![( + Camera::new(CameraType::Projection { + fov: std::f32::consts::PI / 4.0, + near: 1.0, + far: 1000.0, + aspect_ratio: 1.0, + }), + ActiveCamera, + LocalToWorld(Mat4::look_at_rh( + Vec3::new(3.0, 8.0, 5.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, 0.0, 1.0), + )), + )], + ); +} + +#[allow(dead_code)] +fn create_entities_builder_add_component(world: &mut World, plane_handle: Handle, cube_handle: Handle) { + world.build() + // plane + .build_entity() + .add(plane_handle.clone()) + .add(Material::new(Albedo::Color(math::vec4(0.1, 0.2, 0.1, 1.0)))) + .add(LocalToWorld::identity()) + .add(Translation::new(0.0, 0.0, 0.0)) + // cube + .build_entity() + .add(cube_handle) + .add(Material::new(Albedo::Color(math::vec4(0.5, 0.3, 0.3, 1.0)))) + .add(LocalToWorld::identity()) + .add(Translation::new(0.0, 0.0, 1.0)) + // light + .build_entity() + .add(Light { + color: wgpu::Color { + r: 0.8, + g: 0.8, + b: 0.5, + a: 1.0, + }, + fov: f32::to_radians(60.0), + depth: 0.1..50.0, + target_view: None, + }) + .add(LocalToWorld::identity()) + .add(Translation::new(4.0, -4.0, 5.0)) + .add(Rotation::from_euler_angles(0.0, 0.0, 0.0)) + // camera + .build_entity() + .add(Camera::new(CameraType::Projection { + fov: std::f32::consts::PI / 4.0, + near: 1.0, + far: 1000.0, + aspect_ratio: 1.0, + })) + .add(ActiveCamera) + .add(LocalToWorld(Mat4::look_at_rh( + Vec3::new(3.0, 8.0, 5.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, 0.0, 1.0), + ))) + .build(); +} + +fn create_entities_builder_archetype(world: &mut World, plane_handle: Handle, cube_handle: Handle) { + world.build() + // plane + .build_archetype(Object3dEntity { + mesh: plane_handle.clone(), + material: Material::new(Albedo::Color(math::vec4(0.1, 0.2, 0.1, 1.0))), + local_to_world: LocalToWorld::identity(), + translation: Translation::new(0.0, 0.0, 0.0), + }) + // cube + .build_archetype(Object3dEntity { + mesh: cube_handle, + material: Material::new(Albedo::Color(math::vec4(0.5, 0.3, 0.3, 1.0))), + local_to_world: LocalToWorld::identity(), + translation: Translation::new(0.0, 0.0, 1.0), + }) + // light + .build_archetype(LightEntity { + light: Light { + color: wgpu::Color { + r: 0.8, + g: 0.8, + b: 0.5, + a: 1.0, + }, + fov: f32::to_radians(60.0), + depth: 0.1..50.0, + target_view: None, + }, + local_to_world: LocalToWorld::identity(), + translation: Translation::new(4.0, -4.0, 5.0), + rotation: Rotation::from_euler_angles(0.0, 0.0, 0.0), + }) + // camera + .build_archetype(CameraEntity { + camera: Camera::new(CameraType::Projection { + fov: std::f32::consts::PI / 4.0, + near: 1.0, + far: 1000.0, + aspect_ratio: 1.0, + }), + active_camera: ActiveCamera, + local_to_world: LocalToWorld(Mat4::look_at_rh( + Vec3::new(3.0, 8.0, 5.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, 0.0, 1.0), + )), + }) + .build(); +} + +fn setup(world: &mut World) { + let cube = Mesh::load(MeshType::Cube); + let plane = Mesh::load(MeshType::Plane { size: 10.0 }); + + let (cube_handle, plane_handle) = { + let mut mesh_storage = world.resources.get_mut::>().unwrap(); + (mesh_storage.add(cube), mesh_storage.add(plane)) + }; + + // no-archetype precompile: 1.24 sec + // archetype precompile: 1.07 sec + // create_entities_insert_vec(world, plane_handle, cube_handle); + + // no-archetype precompile: .93 + // noarchetype precompile: .93 + // create_entities_builder_add_component(world, plane_handle, cube_handle); + + // archetype precompile: 0.65 + create_entities_builder_archetype(world, plane_handle, cube_handle); +} diff --git a/src/ecs/archetypes.rs b/src/ecs/archetypes.rs new file mode 100644 index 0000000000..fe0700e870 --- /dev/null +++ b/src/ecs/archetypes.rs @@ -0,0 +1,65 @@ +use crate::prelude::*; +use legion::prelude::*; + + +// builder macro that makes defaults easy? Object3dBuilder { Option } impl Builder for Object3dBuilder { } +pub trait EntityArchetype { + fn insert(self, world: &mut World) -> Entity; + // add_components appears to be missing from World. it will be less efficient without that + // fn add_components(self, world: &mut World); +} + +pub struct Object3dEntity { + pub mesh: Handle, + pub material: Material, + pub local_to_world: LocalToWorld, + pub translation: Translation, +} + +// TODO: make this a macro +impl EntityArchetype for Object3dEntity { + fn insert(self, world: &mut World) -> Entity { + *world.insert((), vec![( + self.mesh, + self.material, + self.local_to_world, + self.translation, + )]).first().unwrap() + } +} + +pub struct LightEntity { + pub light: Light, + pub local_to_world: LocalToWorld, + pub translation: Translation, + pub rotation: Rotation, +} + +// TODO: make this a macro +impl EntityArchetype for LightEntity { + fn insert(self, world: &mut World) -> Entity { + *world.insert((), vec![( + self.light, + self.local_to_world, + self.translation, + self.rotation, + )]).first().unwrap() + } +} + +pub struct CameraEntity { + pub camera: Camera, + pub active_camera: ActiveCamera, + pub local_to_world: LocalToWorld, +} + +// TODO: make this a macro +impl EntityArchetype for CameraEntity { + fn insert(self, world: &mut World) -> Entity { + *world.insert((), vec![( + self.camera, + self.active_camera, + self.local_to_world, + )]).first().unwrap() + } +} \ No newline at end of file diff --git a/src/ecs/entity_builder.rs b/src/ecs/entity_builder.rs new file mode 100644 index 0000000000..39e767073a --- /dev/null +++ b/src/ecs/entity_builder.rs @@ -0,0 +1,55 @@ +use crate::ecs::EntityArchetype; +use legion::prelude::*; + +pub trait EntityBuilderSource { + fn build(&mut self) -> EntityBuilder; +} + +impl EntityBuilderSource for World { + fn build(&mut self) -> EntityBuilder { + EntityBuilder { + world: self, + current_entity: None, + } + } +} + +pub struct EntityBuilder<'a> { + world: &'a mut World, + current_entity: Option, +} + +impl<'a> EntityBuilder<'a> { + pub fn build_entity(mut self) -> Self { + let entity = *self.world.insert((), vec![()]).first().unwrap(); + self.current_entity = Some(entity); + self + } + pub fn build(self) {} + + // note: this is slow and does a full entity copy + pub fn add(self, component: T) -> Self + where + T: legion::storage::Component, + { + let _ = self + .world + .add_component(*self.current_entity.as_ref().unwrap(), component); + self + } + + pub fn tag(self, tag: T) -> Self + where + T: legion::storage::Tag, + { + let _ = self + .world + .add_tag(*self.current_entity.as_ref().unwrap(), tag); + self + } + + pub fn build_archetype(mut self, entity_archetype: impl EntityArchetype) -> Self { + self.current_entity = Some(entity_archetype.insert(self.world)); + self + } +} diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs index 79b738b2e3..69fb1d5dda 100644 --- a/src/ecs/mod.rs +++ b/src/ecs/mod.rs @@ -1,3 +1,9 @@ +mod entity_builder; +mod archetypes; + +pub use entity_builder::*; +pub use archetypes::*; + use crate::prelude::{Children, Entity, SubWorld, World}; pub fn run_on_hierarchy(