Skip to main content

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}