From 782d4e490cb223d926d3090405c8e7ebd28b7b40 Mon Sep 17 00:00:00 2001 From: Willow Sparks Date: Sat, 31 Jan 2026 18:24:24 +0000 Subject: [PATCH] Port all consumers to new ECS API, remove Entity-based methods - Remove from_entity(), add_entity(), add_entity_with_color() from RenderableGeometry (renderable.rs) - Remove Entity re-export from rust4d_render/src/lib.rs - Port all 4 examples to use world.spawn() with component tuples - Port physics_integration.rs tests to world.spawn() - Port renderable.rs tests to use from_world/add_components_with_color All 478 tests pass. Zero references to Entity struct remain in compiled code (only docs have stale references). Co-Authored-By: Claude Opus 4.5 --- .../rust4d_core/tests/physics_integration.rs | 53 +++++++---- crates/rust4d_render/src/lib.rs | 2 +- crates/rust4d_render/src/renderable.rs | 93 +++++++++---------- examples/01_hello_tesseract.rs | 9 +- examples/02_multiple_shapes.rs | 10 +- examples/03_physics_demo.rs | 36 ++++--- examples/04_camera_exploration.rs | 28 +++--- 7 files changed, 127 insertions(+), 104 deletions(-) diff --git a/crates/rust4d_core/tests/physics_integration.rs b/crates/rust4d_core/tests/physics_integration.rs index 2efe972..38490c9 100644 --- a/crates/rust4d_core/tests/physics_integration.rs +++ b/crates/rust4d_core/tests/physics_integration.rs @@ -8,7 +8,7 @@ use rust4d_core::{ ActiveScene, Scene, World, EntityTemplate, Transform4D, Material, ShapeRef, - ShapeTemplate, DirtyFlags, PhysicsBody, + ShapeTemplate, DirtyFlags, PhysicsBody, Name, }; use rust4d_physics::{ PhysicsConfig, PhysicsWorld, RigidBody4D, BodyType, StaticCollider, PhysicsMaterial, @@ -226,10 +226,14 @@ fn test_entity_transform_syncs_from_physics() { // Create an entity linked to the physics body let tesseract = Tesseract4D::new(2.0); - let entity = rust4d_core::Entity::new(ShapeRef::shared(tesseract)) - .with_name("test") - .with_physics_body(body_key); - world.add_entity(entity); + let entity_handle = world.spawn(( + ShapeRef::shared(tesseract), + Transform4D::identity(), + Material::default(), + DirtyFlags::ALL, + Name::new("test"), + PhysicsBody(body_key), + )); // Clear dirty flags world.clear_all_dirty(); @@ -238,7 +242,6 @@ fn test_entity_transform_syncs_from_physics() { world.update(0.1); // Entity should have new position - let entity_handle = world.get_by_name("test").unwrap(); let transform = world.ecs().get::<&Transform4D>(entity_handle).unwrap(); assert!( transform.position.y < 10.0, @@ -471,10 +474,14 @@ fn test_remove_entity_cleans_up_physics_body() { // Create an entity linked to the physics body let tesseract = Tesseract4D::new(2.0); - let entity = rust4d_core::Entity::new(ShapeRef::shared(tesseract)) - .with_name("physics_test") - .with_physics_body(body_key); - let entity_key = world.add_entity(entity); + let entity_key = world.spawn(( + ShapeRef::shared(tesseract), + Transform4D::identity(), + Material::default(), + DirtyFlags::ALL, + Name::new("physics_test"), + PhysicsBody(body_key), + )); // Verify body exists in physics world assert!( @@ -487,7 +494,7 @@ fn test_remove_entity_cleans_up_physics_body() { ); // Remove the entity - let removed = world.remove_entity(entity_key); + let removed = world.despawn(entity_key); assert!(removed, "Entity should be removed"); // Verify physics body was also removed @@ -508,12 +515,16 @@ fn test_remove_entity_without_physics_body() { // Create an entity WITHOUT a physics body let tesseract = Tesseract4D::new(2.0); - let entity = rust4d_core::Entity::new(ShapeRef::shared(tesseract)) - .with_name("no_physics"); - let entity_key = world.add_entity(entity); + let entity_key = world.spawn(( + ShapeRef::shared(tesseract), + Transform4D::identity(), + Material::default(), + DirtyFlags::ALL, + Name::new("no_physics"), + )); // Remove the entity - should not panic - let removed = world.remove_entity(entity_key); + let removed = world.despawn(entity_key); assert!(removed, "Entity should be removed"); // Verify entity is gone assert!(!world.contains(entity_key)); @@ -526,11 +537,15 @@ fn test_remove_entity_world_without_physics() { // Create an entity let tesseract = Tesseract4D::new(2.0); - let entity = rust4d_core::Entity::new(ShapeRef::shared(tesseract)) - .with_name("test"); - let entity_key = world.add_entity(entity); + let entity_key = world.spawn(( + ShapeRef::shared(tesseract), + Transform4D::identity(), + Material::default(), + DirtyFlags::ALL, + Name::new("test"), + )); // Remove should work fine - let removed = world.remove_entity(entity_key); + let removed = world.despawn(entity_key); assert!(removed, "Entity should be removed even without physics world"); } diff --git a/crates/rust4d_render/src/lib.rs b/crates/rust4d_render/src/lib.rs index ab0f7ad..d40f0a4 100644 --- a/crates/rust4d_render/src/lib.rs +++ b/crates/rust4d_render/src/lib.rs @@ -22,7 +22,7 @@ pub mod pipeline; pub mod renderable; // Re-export core types for convenience -pub use rust4d_core::{World, Entity, Transform4D, Material, ShapeRef, DirtyFlags, Tags}; +pub use rust4d_core::{World, Transform4D, Material, ShapeRef, DirtyFlags, Tags}; pub use rust4d_core::{ConvexShape4D, Tetrahedron, Tesseract4D, Hyperplane4D}; pub use rust4d_core::{Vec4, Rotor4, RotationPlane}; diff --git a/crates/rust4d_render/src/renderable.rs b/crates/rust4d_render/src/renderable.rs index b8419ee..f966122 100644 --- a/crates/rust4d_render/src/renderable.rs +++ b/crates/rust4d_render/src/renderable.rs @@ -3,7 +3,7 @@ //! This module converts the abstract shape data from rust4d_core into //! GPU-compatible vertex and tetrahedra buffers. -use rust4d_core::{Entity, World, Material, Transform4D, ShapeRef}; +use rust4d_core::{World, Material, Transform4D, ShapeRef}; use rust4d_math::Vec4; use crate::pipeline::{Vertex4D, GpuTetrahedron}; @@ -35,20 +35,6 @@ impl RenderableGeometry { } } - /// Collect geometry from a single entity - /// - /// Uses the entity's material base_color for all vertices. - pub fn from_entity(entity: &Entity) -> Self { - Self::from_entity_with_color(entity, &default_color_fn) - } - - /// Collect geometry from a single entity with a custom color function - pub fn from_entity_with_color(entity: &Entity, color_fn: &dyn Fn(&Vec4, &Material) -> [f32; 4]) -> Self { - let mut result = Self::new(); - result.add_entity_with_color(entity, color_fn); - result - } - /// Collect geometry from all entities in a world /// /// Uses each entity's material base_color for all its vertices. @@ -68,18 +54,6 @@ impl RenderableGeometry { result } - /// Add an entity's geometry to this collection - /// - /// Uses the entity's material base_color for all vertices. - pub fn add_entity(&mut self, entity: &Entity) { - self.add_entity_with_color(entity, &default_color_fn); - } - - /// Add an entity's geometry with a custom color function - pub fn add_entity_with_color(&mut self, entity: &Entity, color_fn: &dyn Fn(&Vec4, &Material) -> [f32; 4]) { - self.add_components_with_color(&entity.transform, entity.shape(), &entity.material, color_fn); - } - /// Add geometry from individual ECS components with a custom color function /// /// This is the core method that works with decomposed components rather than @@ -194,14 +168,16 @@ impl CheckerboardGeometry { #[cfg(test)] mod tests { use super::*; - use rust4d_core::{ShapeRef, Tesseract4D, Transform4D}; + use rust4d_core::{ShapeRef, Tesseract4D, Transform4D, DirtyFlags}; - fn make_test_entity() -> Entity { + fn spawn_test_entity(world: &mut World) -> rust4d_core::hecs::Entity { let tesseract = Tesseract4D::new(2.0); - Entity::with_material( + world.spawn(( ShapeRef::shared(tesseract), + Transform4D::identity(), Material::from_rgb(1.0, 0.5, 0.25), - ) + DirtyFlags::ALL, + )) } #[test] @@ -212,9 +188,14 @@ mod tests { } #[test] - fn test_renderable_geometry_from_entity() { - let entity = make_test_entity(); - let geom = RenderableGeometry::from_entity(&entity); + fn test_renderable_geometry_from_components() { + let tesseract = Tesseract4D::new(2.0); + let shape_ref = ShapeRef::shared(tesseract); + let material = Material::from_rgb(1.0, 0.5, 0.25); + let transform = Transform4D::identity(); + + let mut geom = RenderableGeometry::new(); + geom.add_components_with_color(&transform, shape_ref.as_shape(), &material, &default_color_fn); assert_eq!(geom.vertex_count(), 16); // Tesseract has 16 vertices assert!(geom.tetrahedron_count() > 0); @@ -228,8 +209,8 @@ mod tests { #[test] fn test_renderable_geometry_from_world() { let mut world = World::new(); - world.add_entity(make_test_entity()); - world.add_entity(make_test_entity()); + spawn_test_entity(&mut world); + spawn_test_entity(&mut world); let geom = RenderableGeometry::from_world(&world); @@ -237,21 +218,29 @@ mod tests { } #[test] - fn test_renderable_geometry_add_entity() { + fn test_renderable_geometry_add_components() { let mut geom = RenderableGeometry::new(); - let entity = make_test_entity(); + let tesseract = Tesseract4D::new(2.0); + let shape_ref = ShapeRef::shared(tesseract); + let material = Material::from_rgb(1.0, 0.5, 0.25); + let transform = Transform4D::identity(); - geom.add_entity(&entity); + geom.add_components_with_color(&transform, shape_ref.as_shape(), &material, &default_color_fn); assert_eq!(geom.vertex_count(), 16); - geom.add_entity(&entity); + geom.add_components_with_color(&transform, shape_ref.as_shape(), &material, &default_color_fn); assert_eq!(geom.vertex_count(), 32); } #[test] fn test_renderable_geometry_clear() { - let entity = make_test_entity(); - let mut geom = RenderableGeometry::from_entity(&entity); + let tesseract = Tesseract4D::new(2.0); + let shape_ref = ShapeRef::shared(tesseract); + let material = Material::from_rgb(1.0, 0.5, 0.25); + let transform = Transform4D::identity(); + + let mut geom = RenderableGeometry::new(); + geom.add_components_with_color(&transform, shape_ref.as_shape(), &material, &default_color_fn); assert!(geom.vertex_count() > 0); geom.clear(); @@ -292,12 +281,12 @@ mod tests { #[test] fn test_transform_applied() { let tesseract = Tesseract4D::new(2.0); - let mut entity = Entity::new(ShapeRef::shared(tesseract)); - - // Translate by (10, 0, 0, 0) - entity.transform = Transform4D::from_position(Vec4::new(10.0, 0.0, 0.0, 0.0)); + let shape_ref = ShapeRef::shared(tesseract); + let material = Material::default(); + let transform = Transform4D::from_position(Vec4::new(10.0, 0.0, 0.0, 0.0)); - let geom = RenderableGeometry::from_entity(&entity); + let mut geom = RenderableGeometry::new(); + geom.add_components_with_color(&transform, shape_ref.as_shape(), &material, &default_color_fn); // All vertices should be offset by 10 in x for v in &geom.vertices { @@ -309,13 +298,15 @@ mod tests { #[test] fn test_tetrahedra_indices_offset() { let mut geom = RenderableGeometry::new(); - let entity1 = make_test_entity(); - let entity2 = make_test_entity(); + let tesseract = Tesseract4D::new(2.0); + let shape_ref = ShapeRef::shared(tesseract); + let material = Material::from_rgb(1.0, 0.5, 0.25); + let transform = Transform4D::identity(); - geom.add_entity(&entity1); + geom.add_components_with_color(&transform, shape_ref.as_shape(), &material, &default_color_fn); let first_entity_verts = geom.vertex_count(); - geom.add_entity(&entity2); + geom.add_components_with_color(&transform, shape_ref.as_shape(), &material, &default_color_fn); // Second entity's tetrahedra should have indices >= first_entity_verts let second_tet = geom.tetrahedra.last().unwrap(); diff --git a/examples/01_hello_tesseract.rs b/examples/01_hello_tesseract.rs index 96325f8..bebf1df 100644 --- a/examples/01_hello_tesseract.rs +++ b/examples/01_hello_tesseract.rs @@ -19,7 +19,7 @@ use winit::{ window::{Window, WindowId}, }; -use rust4d_core::{Entity, Material, ShapeRef, Tesseract4D, World}; +use rust4d_core::{Material, ShapeRef, Tesseract4D, Transform4D, DirtyFlags, World}; use rust4d_render::{ camera4d::Camera4D, context::RenderContext, @@ -46,11 +46,12 @@ impl App { // Create a tesseract (4D hypercube) with size 2.0 let tesseract = Tesseract4D::new(2.0); - let entity = Entity::with_material( + world.spawn(( ShapeRef::shared(tesseract), + Transform4D::identity(), Material::from_rgb(0.8, 0.4, 0.2), // Orange color - ); - world.add_entity(entity); + DirtyFlags::ALL, + )); // Build GPU geometry from the world let geometry = RenderableGeometry::from_world(&world); diff --git a/examples/02_multiple_shapes.rs b/examples/02_multiple_shapes.rs index 0257782..f412031 100644 --- a/examples/02_multiple_shapes.rs +++ b/examples/02_multiple_shapes.rs @@ -19,7 +19,7 @@ use winit::{ window::{Window, WindowId}, }; -use rust4d_core::{Entity, Material, ShapeRef, Tesseract4D, Transform4D, World}; +use rust4d_core::{Material, ShapeRef, Tesseract4D, Transform4D, DirtyFlags, World}; use rust4d_render::{ camera4d::Camera4D, context::RenderContext, @@ -78,8 +78,12 @@ impl App { for (position, material) in positions_and_colors { let tesseract = Tesseract4D::new(1.5); let transform = Transform4D::from_position(position); - let entity = Entity::with_transform(ShapeRef::shared(tesseract), transform, material); - world.add_entity(entity); + world.spawn(( + ShapeRef::shared(tesseract), + transform, + material, + DirtyFlags::ALL, + )); } let geometry = RenderableGeometry::from_world(&world); diff --git a/examples/03_physics_demo.rs b/examples/03_physics_demo.rs index a84e440..3c11f24 100644 --- a/examples/03_physics_demo.rs +++ b/examples/03_physics_demo.rs @@ -21,8 +21,8 @@ use winit::{ }; use rust4d_core::{ - Entity, Material, ShapeRef, Tesseract4D, Transform4D, World, Tags, - PhysicsConfig, RigidBody4D, StaticCollider, Hyperplane4D, + Material, ShapeRef, Tesseract4D, Transform4D, DirtyFlags, World, Tags, Name, + PhysicsConfig, PhysicsBody, RigidBody4D, StaticCollider, Hyperplane4D, }; use rust4d_render::{ camera4d::Camera4D, @@ -32,6 +32,7 @@ use rust4d_render::{ }; use rust4d_math::Vec4; use rust4d_physics::{BodyType, PhysicsMaterial}; +use rust4d_core::hecs; /// Application state struct App { @@ -60,11 +61,14 @@ impl App { // Add visual floor entity (shape at y=0 local, positioned by transform) let floor_shape = Hyperplane4D::new(15.0, 10, 2.0, 0.001); let floor_transform = Transform4D::from_position(Vec4::new(0.0, floor_y, 0.0, 0.0)); - world.add_entity( - Entity::with_transform(ShapeRef::shared(floor_shape), floor_transform, Material::GRAY) - .with_name("floor") - .with_tag("static"), - ); + world.spawn(( + ShapeRef::shared(floor_shape), + floor_transform, + Material::GRAY, + DirtyFlags::ALL, + Name::new("floor"), + Tags::new().with_tag("static"), + )); // Add falling tesseracts at different heights let spawn_positions = [ @@ -93,18 +97,20 @@ impl App { None }; - // Add visual entity + // Add visual entity with ECS components let tesseract = Tesseract4D::new(size); let transform = Transform4D::from_position(*position); - let mut entity = Entity::with_transform(ShapeRef::shared(tesseract), transform, *material) - .with_name(format!("tesseract_{}", i)) - .with_tag("dynamic"); - + let mut builder = hecs::EntityBuilder::new(); + builder.add(ShapeRef::shared(tesseract)); + builder.add(transform); + builder.add(*material); + builder.add(DirtyFlags::ALL); + builder.add(Name::new(format!("tesseract_{}", i))); + builder.add(Tags::new().with_tag("dynamic")); if let Some(key) = body_key { - entity = entity.with_physics_body(key); + builder.add(PhysicsBody(key)); } - - world.add_entity(entity); + world.spawn(builder.build()); } let geometry = Self::build_geometry(&world); diff --git a/examples/04_camera_exploration.rs b/examples/04_camera_exploration.rs index 9e154ae..dc14a32 100644 --- a/examples/04_camera_exploration.rs +++ b/examples/04_camera_exploration.rs @@ -34,7 +34,7 @@ use winit::{ }; use rust4d_core::{ - Entity, Material, ShapeRef, Tesseract4D, Transform4D, World, Tags, + Material, ShapeRef, Tesseract4D, Transform4D, DirtyFlags, World, Tags, Name, Hyperplane4D, }; use rust4d_render::{ @@ -67,11 +67,14 @@ impl App { // Add floor at Y = -2 for spatial reference (shape at y=0 local, positioned by transform) let floor_shape = Hyperplane4D::new(20.0, 12, 2.0, 0.001); let floor_transform = Transform4D::from_position(Vec4::new(0.0, -2.0, 0.0, 0.0)); - world.add_entity( - Entity::with_transform(ShapeRef::shared(floor_shape), floor_transform, Material::GRAY) - .with_name("floor") - .with_tag("static"), - ); + world.spawn(( + ShapeRef::shared(floor_shape), + floor_transform, + Material::GRAY, + DirtyFlags::ALL, + Name::new("floor"), + Tags::new().with_tag("static"), + )); // Create tesseracts at various 4D positions to explore // Each one at a different location in 4D space @@ -91,11 +94,14 @@ impl App { for (position, material, name) in tesseracts { let tesseract = Tesseract4D::new(1.5); let transform = Transform4D::from_position(position); - world.add_entity( - Entity::with_transform(ShapeRef::shared(tesseract), transform, material) - .with_name(name) - .with_tag("dynamic"), - ); + world.spawn(( + ShapeRef::shared(tesseract), + transform, + material, + DirtyFlags::ALL, + Name::new(name), + Tags::new().with_tag("dynamic"), + )); } let geometry = Self::build_geometry(&world); -- knotmirror 0.0.1