khora_sdk/
game_world.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The `GameWorld` facade — a safe, typed entry point for managing
16//! the ECS world and asset registry without exposing internal engine types.
17//!
18//! This follows the pattern of every major game engine: users interact with
19//! entities and components through a controlled API, never touching the raw
20//! `World` or `Assets` directly.
21
22use khora_core::asset::{AssetHandle, AssetUUID};
23use khora_core::ecs::entity::EntityId;
24use khora_core::renderer::api::scene::Mesh;
25use khora_core::EngineContext;
26use khora_data::ecs::{
27    Camera, Component, ComponentBundle, GlobalTransform, HandleComponent, Query, QueryMut,
28    Transform, World, WorldQuery,
29};
30use std::any::Any;
31
32/// A high-level facade over the internal ECS `World` and `Assets` registry.
33///
34/// `GameWorld` is the primary interface for game developers to create and
35/// manage entities, components, and assets. It hides the raw types from
36/// `khora-data` behind a clean, stable API surface.
37///
38/// # Examples
39///
40/// ```rust,ignore
41/// fn setup(&mut self, world: &mut GameWorld) {
42///     // Spawn a camera
43///     world.spawn_camera(Camera::new_perspective(
44///         std::f32::consts::FRAC_PI_4, 16.0 / 9.0, 0.1, 1000.0,
45///     ));
46///
47///     // Spawn a custom entity
48///     world.spawn((Transform::identity(), MyComponent { speed: 10.0 }));
49/// }
50/// ```
51pub struct GameWorld {
52    /// The internal ECS world.
53    world: World,
54}
55
56impl GameWorld {
57    /// Creates a new `GameWorld` with an empty world and asset registry.
58    pub(crate) fn new() -> Self {
59        Self {
60            world: World::new(),
61        }
62    }
63
64    // ─────────────────────────────────────────────────────────────────────
65    // Entity Lifecycle
66    // ─────────────────────────────────────────────────────────────────────
67
68    /// Spawns a new entity with the given component bundle.
69    ///
70    /// Returns the [`EntityId`] of the newly created entity.
71    ///
72    /// # Examples
73    ///
74    /// ```rust,ignore
75    /// let entity = world.spawn((Transform::identity(), Velocity::default()));
76    /// ```
77    pub fn spawn<B: ComponentBundle>(&mut self, bundle: B) -> EntityId {
78        self.world.spawn(bundle)
79    }
80
81    /// Removes an entity and all its components from the world.
82    ///
83    /// Returns `true` if the entity existed and was removed.
84    pub fn despawn(&mut self, entity: EntityId) -> bool {
85        self.world.despawn(entity)
86    }
87
88    // ─────────────────────────────────────────────────────────────────────
89    // Camera Helpers
90    // ─────────────────────────────────────────────────────────────────────
91
92    /// Spawns a camera entity with a [`Camera`] component and an identity
93    /// [`GlobalTransform`].
94    ///
95    /// This is the recommended way to add a camera to the scene. The
96    /// `RenderAgent` will automatically discover cameras during its
97    /// extraction phase.
98    ///
99    /// Returns the [`EntityId`] of the camera entity.
100    pub fn spawn_camera(&mut self, camera: Camera) -> EntityId {
101        self.world.spawn((camera, GlobalTransform::identity()))
102    }
103
104    // ─────────────────────────────────────────────────────────────────────
105    // Asset Management
106    // ─────────────────────────────────────────────────────────────────────
107
108    /// Adds a mesh to the asset registry and returns a handle component.
109    ///
110    /// The returned `HandleComponent<Mesh>` can be attached to entities
111    /// to give them a visible mesh. The `RenderAgent` will automatically
112    /// upload the mesh to GPU when the entity is rendered.
113    ///
114    /// # Arguments
115    /// * `mesh` - The CPU-side mesh data.
116    ///
117    /// # Returns
118    /// A `HandleComponent<Mesh>` that references the stored mesh.
119    ///
120    /// # Examples
121    ///
122    /// ```rust,ignore
123    /// let plane_mesh = create_plane(10.0, 0.0);
124    /// let handle = world.add_mesh(plane_mesh);
125    /// let entity = world.spawn((Transform::identity(), handle));
126    /// ```
127    pub fn add_mesh(&mut self, mesh: Mesh) -> HandleComponent<Mesh> {
128        let uuid = AssetUUID::new();
129        let handle = AssetHandle::new(mesh);
130        HandleComponent { handle, uuid }
131    }
132
133    // ─────────────────────────────────────────────────────────────────────
134    // Component Access
135    // ─────────────────────────────────────────────────────────────────────
136
137    /// Adds a component to an existing entity.
138    ///
139    /// If the entity already has a component of this type, the old value
140    /// is replaced.
141    pub fn add_component<C: Component>(&mut self, entity: EntityId, component: C) {
142        let _ = self.world.add_component(entity, component);
143    }
144
145    /// Removes a component (and its semantic domain) from an entity.
146    ///
147    /// This is an O(1) operation — the data is orphaned and cleaned up
148    /// later by the garbage collector agent.
149    pub fn remove_component<C: Component>(&mut self, entity: EntityId) {
150        self.world.remove_component_domain::<C>(entity);
151    }
152
153    // ─────────────────────────────────────────────────────────────────────
154    // Queries
155    // ─────────────────────────────────────────────────────────────────────
156
157    /// Creates a read-only query over the world.
158    ///
159    /// # Examples
160    ///
161    /// ```rust,ignore
162    /// for (pos, vel) in world.query::<(&Transform, &Velocity)>() {
163    ///     // iterate matching entities
164    /// }
165    /// ```
166    pub fn query<'a, Q: WorldQuery>(&'a self) -> Query<'a, Q> {
167        self.world.query::<Q>()
168    }
169
170    /// Creates a mutable query over the world.
171    ///
172    /// # Examples
173    ///
174    /// ```rust,ignore
175    /// for (pos,) in world.query_mut::<(&mut Transform,)>() {
176    ///     pos.translate(Vec3::Y * delta);
177    /// }
178    /// ```
179    pub fn query_mut<'a, Q: WorldQuery>(&'a mut self) -> QueryMut<'a, Q> {
180        self.world.query_mut::<Q>()
181    }
182
183    // ─────────────────────────────────────────────────────────────────────
184    // Convenience Methods
185    // ─────────────────────────────────────────────────────────────────────
186
187    /// Spawns an entity with just a transform component.
188    ///
189    /// Returns the [`EntityId`] of the newly created entity.
190    pub fn spawn_entity(&mut self, transform: &Transform) -> EntityId {
191        let global = GlobalTransform::at_position(transform.translation);
192        self.world.spawn((*transform, global))
193    }
194
195    /// Returns an iterator over all entity IDs in the world.
196    pub fn iter_entities(&self) -> impl Iterator<Item = EntityId> + '_ {
197        self.world.iter_entities()
198    }
199
200    /// Gets a mutable reference to a transform component.
201    ///
202    /// Returns `None` if the entity doesn't exist or has no transform.
203    pub fn get_transform_mut(&mut self, entity: EntityId) -> Option<&mut Transform> {
204        self.world.get_mut::<Transform>(entity)
205    }
206
207    /// Gets a reference to a transform component.
208    ///
209    /// Returns `None` if the entity doesn't exist or has no transform.
210    pub fn get_transform(&self, entity: EntityId) -> Option<&Transform> {
211        self.world.get::<Transform>(entity)
212    }
213
214    /// Gets a mutable reference to any component.
215    ///
216    /// Returns `None` if the entity doesn't exist or has no such component.
217    pub fn get_component_mut<C: Component>(&mut self, entity: EntityId) -> Option<&mut C> {
218        self.world.get_mut::<C>(entity)
219    }
220
221    /// Gets a reference to any component.
222    ///
223    /// Returns `None` if the entity doesn't exist or has no such component.
224    pub fn get_component<C: Component>(&self, entity: EntityId) -> Option<&C> {
225        self.world.get::<C>(entity)
226    }
227
228    // ─────────────────────────────────────────────────────────────────────
229    // Transform Synchronization
230    // ─────────────────────────────────────────────────────────────────────
231
232    /// Synchronizes the GlobalTransform component from the Transform component.
233    ///
234    /// This should be called after modifying a Transform to ensure the changes
235    /// are visible to the rendering system. This is a convenience method that
236    /// copies the local transform to the global transform.
237    ///
238    /// # Example
239    /// ```rust,ignore
240    /// // Move entity
241    /// if let Some(transform) = world.get_transform_mut(entity) {
242    ///     transform.translation += Vec3::Y * delta;
243    /// }
244    /// // Sync to rendering
245    /// world.sync_global_transform(entity);
246    /// ```
247    pub fn sync_global_transform(&mut self, entity: EntityId) {
248        if let Some(transform) = self.world.get::<Transform>(entity) {
249            let matrix = transform.to_mat4();
250            if let Some(global) = self.world.get_mut::<GlobalTransform>(entity) {
251                *global = GlobalTransform::new(matrix);
252            }
253        }
254    }
255
256    /// Updates an entity's transform and immediately syncs it to GlobalTransform.
257    ///
258    /// This is a convenience method that combines getting the transform,
259    /// applying a modification function, and syncing to GlobalTransform.
260    ///
261    /// # Example
262    /// ```rust,ignore
263    /// world.update_transform(entity, |t| {
264    ///     t.translation += Vec3::Y * delta;
265    /// });
266    /// ```
267    pub fn update_transform<F>(&mut self, entity: EntityId, f: F)
268    where
269        F: FnOnce(&mut Transform),
270    {
271        if let Some(transform) = self.world.get_mut::<Transform>(entity) {
272            f(transform);
273        }
274        self.sync_global_transform(entity);
275    }
276
277    /// Adds a material to the asset registry and returns a handle component.
278    ///
279    /// The returned `MaterialComponent` can be attached to entities
280    /// to give them a visible material.
281    ///
282    /// # Arguments
283    /// * `material` - The CPU-side material data (e.g., StandardMaterial).
284    ///
285    /// # Returns
286    /// A `MaterialComponent` that references the stored material.
287    pub fn add_material<M: khora_core::asset::Material>(
288        &mut self,
289        material: M,
290    ) -> khora_data::ecs::MaterialComponent {
291        let uuid = AssetUUID::new();
292        let handle = AssetHandle::new(Box::new(material) as Box<dyn khora_core::asset::Material>);
293        khora_data::ecs::MaterialComponent { handle, uuid }
294    }
295
296    // ─────────────────────────────────────────────────────────────────────
297    // Internal — used by the SDK, not exposed to users
298    // ─────────────────────────────────────────────────────────────────────
299
300    /// Builds an [`EngineContext`] for the DCC agent update cycle.
301    ///
302    /// This type-erases `World` and `Assets` into `dyn Any` pointers,
303    /// which agents downcast internally. Users never call this.
304    pub(crate) fn as_engine_context(
305        &mut self,
306        services: khora_core::ServiceRegistry,
307    ) -> EngineContext<'_> {
308        EngineContext {
309            world: Some(&mut self.world as &mut dyn Any),
310            services,
311        }
312    }
313}