goud_engine/sdk/entity_builder.rs
1//! Fluent entity builder for spawning entities with components.
2//!
3//! Provides [`EntityBuilder`] for ergonomic entity creation using
4//! method chaining.
5
6use crate::ecs::{Component, Entity, World};
7
8// =============================================================================
9// Entity Builder
10// =============================================================================
11
12/// A fluent builder for creating entities with components.
13///
14/// The `EntityBuilder` provides a convenient way to spawn entities and
15/// attach multiple components in a single expression chain.
16///
17/// # Example
18///
19/// ```rust
20/// use goud_engine::sdk::EntityBuilder;
21/// use goud_engine::sdk::components::{Transform2D, Sprite};
22/// use goud_engine::ecs::World;
23/// use goud_engine::core::math::Vec2;
24/// use goud_engine::assets::AssetServer;
25///
26/// let mut world = World::new();
27/// let mut assets = AssetServer::new();
28///
29/// // Create a fully configured entity
30/// let entity = EntityBuilder::new(&mut world)
31/// .with(Transform2D::from_position(Vec2::new(100.0, 200.0)))
32/// .build();
33/// ```
34pub struct EntityBuilder<'w> {
35 /// Reference to the world where the entity will be created.
36 world: &'w mut World,
37
38 /// The entity being built.
39 entity: Entity,
40}
41
42impl<'w> EntityBuilder<'w> {
43 /// Creates a new entity builder.
44 ///
45 /// This immediately spawns an empty entity in the world.
46 /// Use the `with()` method to add components.
47 pub fn new(world: &'w mut World) -> Self {
48 let entity = world.spawn_empty();
49 Self { world, entity }
50 }
51
52 /// Adds a component to the entity.
53 ///
54 /// This is the primary method for attaching components to the entity
55 /// being built. Components can be chained.
56 ///
57 /// # Example
58 ///
59 /// ```rust
60 /// use goud_engine::sdk::EntityBuilder;
61 /// use goud_engine::sdk::components::Transform2D;
62 /// use goud_engine::ecs::World;
63 /// use goud_engine::core::math::Vec2;
64 ///
65 /// let mut world = World::new();
66 /// let entity = EntityBuilder::new(&mut world)
67 /// .with(Transform2D::from_position(Vec2::new(10.0, 20.0)))
68 /// .build();
69 /// ```
70 pub fn with<T: Component>(self, component: T) -> Self {
71 self.world.insert(self.entity, component);
72 self
73 }
74
75 /// Conditionally adds a component to the entity.
76 ///
77 /// The component is only added if `condition` is true.
78 /// Useful for optional components based on game state.
79 ///
80 /// # Example
81 ///
82 /// ```rust
83 /// use goud_engine::sdk::EntityBuilder;
84 /// use goud_engine::sdk::components::Transform2D;
85 /// use goud_engine::ecs::World;
86 /// use goud_engine::core::math::Vec2;
87 ///
88 /// let mut world = World::new();
89 /// let has_physics = true;
90 ///
91 /// let entity = EntityBuilder::new(&mut world)
92 /// .with(Transform2D::default())
93 /// .with_if(has_physics, Transform2D::from_scale(Vec2::one()))
94 /// .build();
95 /// ```
96 pub fn with_if<T: Component>(self, condition: bool, component: T) -> Self {
97 if condition {
98 self.world.insert(self.entity, component);
99 }
100 self
101 }
102
103 /// Conditionally adds a component using a closure.
104 ///
105 /// The closure is only called if `condition` is true, avoiding
106 /// unnecessary component construction.
107 pub fn with_if_else<T: Component>(
108 self,
109 condition: bool,
110 if_true: impl FnOnce() -> T,
111 if_false: impl FnOnce() -> T,
112 ) -> Self {
113 let component = if condition { if_true() } else { if_false() };
114 self.world.insert(self.entity, component);
115 self
116 }
117
118 /// Finalizes the builder and returns the created entity.
119 ///
120 /// After calling `build()`, the builder is consumed and the entity
121 /// is ready for use.
122 pub fn build(self) -> Entity {
123 self.entity
124 }
125
126 /// Returns a reference to the entity being built.
127 ///
128 /// Useful for accessing the entity ID before finalizing.
129 #[inline]
130 pub fn entity(&self) -> Entity {
131 self.entity
132 }
133
134 /// Provides mutable access to the world during building.
135 ///
136 /// Use this for advanced scenarios where you need to perform
137 /// world operations while building an entity.
138 #[inline]
139 pub fn world_mut(&mut self) -> &mut World {
140 self.world
141 }
142}
143
144// =============================================================================
145// Tests
146// =============================================================================
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::core::math::Vec2;
152 use crate::sdk::components::{GlobalTransform2D, Transform2D};
153
154 #[test]
155 fn test_entity_builder_basic() {
156 let mut world = World::new();
157 let entity = EntityBuilder::new(&mut world).build();
158
159 assert!(world.is_alive(entity));
160 }
161
162 #[test]
163 fn test_entity_builder_with_component() {
164 let mut world = World::new();
165 let entity = EntityBuilder::new(&mut world)
166 .with(Transform2D::from_position(Vec2::new(10.0, 20.0)))
167 .build();
168
169 assert!(world.has::<Transform2D>(entity));
170
171 let transform = world.get::<Transform2D>(entity).unwrap();
172 assert_eq!(transform.position, Vec2::new(10.0, 20.0));
173 }
174
175 #[test]
176 fn test_entity_builder_with_multiple_components() {
177 let mut world = World::new();
178 let entity = EntityBuilder::new(&mut world)
179 .with(Transform2D::from_position(Vec2::new(10.0, 20.0)))
180 .with(GlobalTransform2D::IDENTITY)
181 .build();
182
183 assert!(world.has::<Transform2D>(entity));
184 assert!(world.has::<GlobalTransform2D>(entity));
185 }
186
187 #[test]
188 fn test_entity_builder_with_if() {
189 let mut world = World::new();
190
191 // Condition true
192 let e1 = EntityBuilder::new(&mut world)
193 .with_if(true, Transform2D::default())
194 .build();
195 assert!(world.has::<Transform2D>(e1));
196
197 // Condition false
198 let e2 = EntityBuilder::new(&mut world)
199 .with_if(false, Transform2D::default())
200 .build();
201 assert!(!world.has::<Transform2D>(e2));
202 }
203
204 #[test]
205 fn test_entity_builder_entity_access() {
206 let mut world = World::new();
207 let builder = EntityBuilder::new(&mut world);
208 let entity = builder.entity();
209
210 // Entity should be alive even before build()
211 assert!(world.is_alive(entity));
212 }
213}