From 6992e0b95172c22ae2c37f95c66fe598e6db3d33 Mon Sep 17 00:00:00 2001 From: Philpax Date: Thu, 31 Aug 2023 14:50:40 +0200 Subject: [PATCH 1/2] wip(guest): switch to using a World --- Cargo.lock | 1 + guest/rust/Cargo.lock | 12 + guest/rust/Cargo.toml | 1 + guest/rust/api_core/Cargo.toml | 1 + .../api_core/api_macros/src/main_macro.rs | 6 +- guest/rust/api_core/src/animation.rs | 162 +++++------ guest/rust/api_core/src/client/audio.rs | 81 +++--- guest/rust/api_core/src/ecs.rs | 256 ++++++++++++++++++ guest/rust/api_core/src/entity.rs | 245 ----------------- guest/rust/api_core/src/global/runtime.rs | 21 +- guest/rust/api_core/src/global/shapes.rs | 6 +- .../api_core/src/internal/component/entity.rs | 15 +- .../api_core/src/internal/component/mod.rs | 2 + guest/rust/api_core/src/internal/generated.rs | 56 ++-- guest/rust/api_core/src/internal/mod.rs | 8 +- guest/rust/api_core/src/internal/world.rs | 124 +++++++++ guest/rust/api_core/src/lib.rs | 2 - guest/rust/api_core/src/prelude.rs | 6 +- .../basics/asset_loading/src/client.rs | 2 +- .../basics/asset_loading/src/server.rs | 18 +- .../examples/basics/physics/src/client.rs | 4 +- shared_crates/guest_bridge/src/guest.rs | 69 +---- .../package_macro_common/src/concepts.rs | 17 +- 23 files changed, 609 insertions(+), 506 deletions(-) delete mode 100644 guest/rust/api_core/src/entity.rs create mode 100644 guest/rust/api_core/src/internal/world.rs diff --git a/Cargo.lock b/Cargo.lock index 9297fe3d55..f38b1f9c50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,6 +210,7 @@ dependencies = [ "ambient_package_rt", "ambient_shared_types", "anyhow", + "async-trait", "byteorder", "data-encoding", "futures", diff --git a/guest/rust/Cargo.lock b/guest/rust/Cargo.lock index 4ba93bbfd7..8156817d45 100644 --- a/guest/rust/Cargo.lock +++ b/guest/rust/Cargo.lock @@ -130,6 +130,7 @@ dependencies = [ "ambient_package_rt", "ambient_shared_types", "anyhow", + "async-trait", "byteorder", "data-encoding", "futures", @@ -702,6 +703,17 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "atomic_refcell" version = "0.1.10" diff --git a/guest/rust/Cargo.toml b/guest/rust/Cargo.toml index 37757dd4b0..6edfbffcd5 100644 --- a/guest/rust/Cargo.toml +++ b/guest/rust/Cargo.toml @@ -110,3 +110,4 @@ wit-bindgen = { version = "0.9.0", features = ["realloc"] } ulid = { version = "1.0.0", features = ["serde"] } indexmap = { version = "2.0", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } +async-trait = "0.1" diff --git a/guest/rust/api_core/Cargo.toml b/guest/rust/api_core/Cargo.toml index fefc516487..b0002931ec 100644 --- a/guest/rust/api_core/Cargo.toml +++ b/guest/rust/api_core/Cargo.toml @@ -33,6 +33,7 @@ wit-bindgen = { workspace = true } ulid = { workspace = true } indexmap = { workspace = true } serde = { workspace = true } +async-trait = { workspace = true } [features] client = [] diff --git a/guest/rust/api_core/api_macros/src/main_macro.rs b/guest/rust/api_core/api_macros/src/main_macro.rs index 84d3f15208..96754e5a14 100644 --- a/guest/rust/api_core/api_macros/src/main_macro.rs +++ b/guest/rust/api_core/api_macros/src/main_macro.rs @@ -33,9 +33,9 @@ pub fn main(item: TokenStream, ambient_toml: RetrievableFile) -> TokenStream { let call_stmt = if let Some(ParsedFunction { fn_name, is_async }) = parsed { let call_expr = if is_async { - quote! { #fn_name() } + quote! { #fn_name(world) } } else { - quote! { async { #fn_name() } } + quote! { async { #fn_name(world) } } }; quote! { #path::global::run_async(#call_expr) } @@ -50,7 +50,7 @@ pub fn main(item: TokenStream, ambient_toml: RetrievableFile) -> TokenStream { #[no_mangle] #[doc(hidden)] - pub fn main() { + pub fn main(world: &mut dyn #path::ecs::World) { #call_stmt } } diff --git a/guest/rust/api_core/src/animation.rs b/guest/rust/api_core/src/animation.rs index 1ebe316d95..9c8543bb59 100644 --- a/guest/rust/api_core/src/animation.rs +++ b/guest/rust/api_core/src/animation.rs @@ -8,8 +8,7 @@ use crate::{ app::components::{name, ref_count}, ecs::components::{children, parent}, }, - entity, - prelude::{epoch_time, Entity, EntityId}, + prelude::{epoch_time, Entity, EntityId, World, WorldExt}, }; /// This plays animations, and can handle blending and masking of animations together to create @@ -18,40 +17,40 @@ use crate::{ pub struct AnimationPlayer(pub EntityId); impl AnimationPlayer { /// Create a new animation player, with `root` as the currently playing node - pub fn new(root: impl AsRef) -> Self { + pub fn new(world: &mut dyn World, root: impl AsRef) -> Self { let root: &AnimationNode = root.as_ref(); let player = Entity::new() .with(is_animation_player(), ()) .with(children(), vec![root.0]) .with(name(), "Animation player".to_string()) - .spawn(); - entity::add_component(root.0, parent(), player); + .spawn(world); + world.add_component(root.0, parent(), player); Self(player) } - fn root(&self) -> Option { - if let Some(children) = entity::get_component(self.0, children()) { + fn root(&self, world: &dyn World) -> Option { + if let Some(children) = world.get_component(self.0, children()) { children.get(0).copied() } else { None } } - fn free_root(&self) { - if let Some(root) = self.root() { - entity::remove_component(root, parent()); + fn free_root(&self, world: &mut dyn World) { + if let Some(root) = self.root(world) { + world.remove_component(root, parent()); } } /// Replaces the current root node of the animation player with a new node - pub fn play(&self, node: impl AsRef) { - self.free_root(); + pub fn play(&self, world: &mut dyn World, node: impl AsRef) { + self.free_root(world); let new_root: &AnimationNode = node.as_ref(); - entity::add_component(self.0, children(), vec![new_root.0]); - entity::add_component(new_root.0, parent(), self.0); + world.add_component(self.0, children(), vec![new_root.0]); + world.add_component(new_root.0, parent(), self.0); } /// Despawn this animation player. /// Note that dropping this player won't despawn it automatically; only call this method will despawn it. - pub fn despawn(self) { - self.free_root(); - entity::despawn(self.0); + pub fn despawn(self, world: &mut dyn World) { + self.free_root(world); + world.despawn(self.0); } } @@ -64,22 +63,22 @@ impl AnimationNode { self.0 } /// Use an existing node - pub fn from_entity(entity: EntityId) -> Self { - entity::mutate_component(entity, ref_count(), |x| *x += 1); + pub fn from_entity(world: &mut dyn World, entity: EntityId) -> Self { + world.mutate_component(entity, ref_count(), |x| *x += 1); Self(entity) } } -impl Clone for AnimationNode { - fn clone(&self) -> Self { - entity::mutate_component(self.0, ref_count(), |x| *x += 1); - Self(self.0) - } -} -impl Drop for AnimationNode { - fn drop(&mut self) { - entity::mutate_component(self.0, ref_count(), |x| *x -= 1); - } -} +// impl Clone for AnimationNode { +// fn clone(&self) -> Self { +// world.mutate_component(self.0, ref_count(), |x| *x += 1); +// Self(self.0) +// } +// } +// impl Drop for AnimationNode { +// fn drop(&mut self) { +// world.mutate_component(self.0, ref_count(), |x| *x -= 1); +// } +// } /// Play clip from url animation node. /// This is an animation node which can be plugged into an animation player or other animation nodes. @@ -87,48 +86,48 @@ impl Drop for AnimationNode { pub struct PlayClipFromUrlNode(pub AnimationNode); impl PlayClipFromUrlNode { /// Create a new node. - pub fn new(url: impl Into) -> Self { + pub fn new(world: &mut dyn World, url: impl Into) -> Self { let node = Entity::new() .with(play_clip_from_url(), url.into()) .with(name(), "Play clip from URL".to_string()) .with(looping(), true) - .with(start_time(), epoch_time()) + .with(start_time(), epoch_time(world)) .with(ref_count(), 1) - .spawn(); + .spawn(world); Self(AnimationNode(node)) } /// Use an existing node - pub fn from_entity(entity: EntityId) -> Self { - Self(AnimationNode::from_entity(entity)) + pub fn from_entity(world: &mut dyn World, entity: EntityId) -> Self { + Self(AnimationNode::from_entity(world, entity)) } /// Set if the animation should loop or not - pub fn looping(&self, value: bool) { - entity::add_component(self.0 .0, looping(), value); + pub fn looping(&self, world: &mut dyn World, value: bool) { + world.add_component(self.0 .0, looping(), value); } /// Freeze the animation at time - pub fn freeze_at_time(&self, time: f32) { - entity::add_component(self.0 .0, freeze_at_time(), time); + pub fn freeze_at_time(&self, world: &mut dyn World, time: f32) { + world.add_component(self.0 .0, freeze_at_time(), time); } /// Freeze the animation at time = percentage * duration - pub fn freeze_at_percentage(&self, percentage: f32) { - entity::add_component(self.0 .0, freeze_at_percentage(), percentage); + pub fn freeze_at_percentage(&self, world: &mut dyn World, percentage: f32) { + world.add_component(self.0 .0, freeze_at_percentage(), percentage); } /// Set up retargeting - pub fn set_retargeting(&self, retargeting: AnimationRetargeting) { + pub fn set_retargeting(&self, world: &mut dyn World, retargeting: AnimationRetargeting) { match retargeting { AnimationRetargeting::None => { - entity::remove_component(self.0 .0, retarget_model_from_url()); + world.remove_component(self.0 .0, retarget_model_from_url()); } AnimationRetargeting::Skeleton { model_url } => { - entity::remove_component(self.0 .0, retarget_animation_scaled()); - entity::add_component(self.0 .0, retarget_model_from_url(), model_url); + world.remove_component(self.0 .0, retarget_animation_scaled()); + world.add_component(self.0 .0, retarget_model_from_url(), model_url); } AnimationRetargeting::AnimationScaled { normalize_hip, model_url, } => { - entity::add_component(self.0 .0, retarget_animation_scaled(), normalize_hip); - entity::add_component(self.0 .0, retarget_model_from_url(), model_url); + world.add_component(self.0 .0, retarget_animation_scaled(), normalize_hip); + world.add_component(self.0 .0, retarget_model_from_url(), model_url); } } } @@ -142,36 +141,38 @@ impl PlayClipFromUrlNode { /// character models which don't have the same pre-rotations we need to make sure they're up to sync /// /// I.e. this is mostly relevant for retargeting - pub fn apply_base_pose(&self, value: bool) { + pub fn apply_base_pose(&self, world: &mut dyn World, value: bool) { if value { - entity::add_component(self.0 .0, apply_base_pose(), ()); + world.add_component(self.0 .0, apply_base_pose(), ()); } else { - entity::remove_component(self.0 .0, apply_base_pose()); + world.remove_component(self.0 .0, apply_base_pose()); } } /// Returns None if the duration hasn't been loaded yet - pub fn peek_clip_duration(&self) -> Option { - entity::get_component(self.0 .0, clip_duration()) + pub fn peek_clip_duration(&self, world: &dyn World) -> Option { + world.get_component(self.0 .0, clip_duration()) } /// Returns the duration of this clip. This is async because it needs to wait for the clip to load before the duration can be returned. - pub async fn clip_duration(&self) -> f32 { - entity::wait_for_component(self.0 .0, clip_duration()) + pub async fn clip_duration(&self, world: &dyn World) -> f32 { + world + .wait_for_component(self.0 .0, clip_duration()) .await .unwrap_or_default() } /// Returns None if the clip hasn't been loaded yet - pub fn peek_bind_ids(&self) -> Option> { - entity::get_component(self.0 .0, bind_ids()) + pub fn peek_bind_ids(&self, world: &dyn World) -> Option> { + world.get_component(self.0 .0, bind_ids()) } /// Returns the bind ids of this clip. This is async because it needs to wait for the clip to load before the bind ids can be returned. - pub async fn bind_ids(&self) -> Vec { - entity::wait_for_component(self.0 .0, bind_ids()) + pub async fn bind_ids(&self, world: &dyn World) -> Vec { + world + .wait_for_component(self.0 .0, bind_ids()) .await .unwrap_or_default() } /// Wait until the clip has been loaded - pub async fn wait_for_load(&self) { - self.clip_duration().await; + pub async fn wait_for_load(&self, world: &dyn World) { + self.clip_duration(world).await; } } impl AsRef for PlayClipFromUrlNode { @@ -182,7 +183,7 @@ impl AsRef for PlayClipFromUrlNode { /// Blend animation node. /// This is an animation node which can be plugged into an animation player or other animation nodes. -#[derive(Debug, Clone)] +#[derive(Debug /*, Clone*/)] pub struct BlendNode(pub AnimationNode); impl BlendNode { /// Create a new blend animation node. @@ -191,6 +192,7 @@ impl BlendNode { /// If the weight is 1, only the right animation will play. /// Values in between blend between the two animations. pub fn new( + world: &mut dyn World, left: impl AsRef, right: impl AsRef, weight: f32, @@ -202,38 +204,39 @@ impl BlendNode { .with(name(), "Blend".to_string()) .with(children(), vec![left.0, right.0]) .with(ref_count(), 1) - .spawn(); - entity::add_component(left.0, parent(), node); - entity::add_component(right.0, parent(), node); + .spawn(world); + world.add_component(left.0, parent(), node); + world.add_component(right.0, parent(), node); Self(AnimationNode(node)) } /// Use an existing node - pub fn from_entity(entity: EntityId) -> Self { - Self(AnimationNode::from_entity(entity)) + pub fn from_entity(world: &mut dyn World, entity: EntityId) -> Self { + Self(AnimationNode::from_entity(world, entity)) } /// Set the weight of this blend node. /// /// If the weight is 0, only the left animation will play. /// If the weight is 1, only the right animation will play. /// Values in between blend between the two animations. - pub fn set_weight(&self, weight: f32) { - entity::set_component(self.0 .0, blend(), weight); + pub fn set_weight(&self, world: &mut dyn World, weight: f32) { + world.set_component(self.0 .0, blend(), weight); } /// Sets the mask of this blend node. /// /// For example `blend_node.set_mask(vec![("LeftLeg".to_string(), 1.)])` means /// that the LeftLeg is always controlled by the right animation. - pub fn set_mask(&self, weights: Vec<(BindId, f32)>) { + pub fn set_mask(&self, world: &mut dyn World, weights: Vec<(BindId, f32)>) { let (bind_ids, weights): (Vec<_>, Vec<_>) = weights .into_iter() .map(|(a, b)| (a.as_str().to_string(), b)) .unzip(); - entity::add_component(self.0 .0, mask_bind_ids(), bind_ids); - entity::add_component(self.0 .0, mask_weights(), weights); + world.add_component(self.0 .0, mask_bind_ids(), bind_ids); + world.add_component(self.0 .0, mask_weights(), weights); } /// Sets a mask value to all bones of a humanoids lower body - pub fn set_mask_humanoid_lower_body(&self, weight: f32) { + pub fn set_mask_humanoid_lower_body(&self, world: &mut dyn World, weight: f32) { self.set_mask( + world, BindId::HUMANOID_LOWER_BODY .iter() .map(|x| ((*x).clone(), weight)) @@ -241,8 +244,9 @@ impl BlendNode { ); } /// Sets a mask value to all bones of a humanoids upper body - pub fn set_mask_humanoid_upper_body(&self, weight: f32) { + pub fn set_mask_humanoid_upper_body(&self, world: &mut dyn World, weight: f32) { self.set_mask( + world, BindId::HUMANOID_UPPER_BODY .iter() .map(|x| ((*x).clone(), weight)) @@ -283,15 +287,19 @@ impl Default for AnimationRetargeting { } /// Get the bone entity from the bind_id; for example "LeftFoot" -pub fn get_bone_by_bind_id(entity: EntityId, bind_id: &BindId) -> Option { - if let Some(bid) = entity::get_component(entity, self::bind_id()) { +pub fn get_bone_by_bind_id( + world: &dyn World, + entity: EntityId, + bind_id: &BindId, +) -> Option { + if let Some(bid) = world.get_component(entity, self::bind_id()) { if bid == bind_id.as_str() { return Some(entity); } } - if let Some(childs) = entity::get_component(entity, children()) { + if let Some(childs) = world.get_component(entity, children()) { for c in childs { - if let Some(bid) = get_bone_by_bind_id(c, bind_id) { + if let Some(bid) = get_bone_by_bind_id(world, c, bind_id) { return Some(bid); } } diff --git a/guest/rust/api_core/src/client/audio.rs b/guest/rust/api_core/src/client/audio.rs index 5c48a1a74a..7cc4f1f49b 100644 --- a/guest/rust/api_core/src/client/audio.rs +++ b/guest/rust/api_core/src/client/audio.rs @@ -4,14 +4,13 @@ use crate::{ audio::components::*, ecs::components::{children, parent}, }, - entity, - prelude::{Entity, EntityId}, + prelude::{Entity, EntityId, World, WorldExt}, }; /// stop the audio on the given entity -pub fn stop(entity: EntityId) { - if entity::exists(entity) { - entity::add_component(entity, stop_now(), ()); +pub fn stop(world: &mut dyn World, entity: EntityId) { + if world.exists(entity) { + world.add_component(entity, stop_now(), ()); } else { eprintln!("Tried to stop audio on non-existent entity {}", entity); } @@ -23,42 +22,35 @@ pub struct SpatialAudioPlayer { /// the entity that represents the spatial audio player pub player: EntityId, } - -impl Default for SpatialAudioPlayer { - fn default() -> Self { - Self::new() - } -} - impl SpatialAudioPlayer { - pub fn new() -> Self { + pub fn new(world: &mut dyn World) -> Self { let player = Entity::new() .with(is_spatial_audio_player(), ()) .with(name(), "Spatial audio player".to_string()) - .spawn(); + .spawn(world); Self { player } } - pub fn set_listener(&self, listener: EntityId) { - entity::add_component(self.player, spatial_audio_listener(), listener); + pub fn set_listener(&self, world: &mut dyn World, listener: EntityId) { + world.add_component(self.player, spatial_audio_listener(), listener); } - pub fn set_emitter(&self, emitter: EntityId) { - entity::add_component(self.player, spatial_audio_emitter(), emitter); + pub fn set_emitter(&self, world: &mut dyn World, emitter: EntityId) { + world.add_component(self.player, spatial_audio_emitter(), emitter); } - pub fn set_amplitude(&self, amp: f32) { - entity::add_component(self.player, amplitude(), amp); + pub fn set_amplitude(&self, world: &mut dyn World, amp: f32) { + world.add_component(self.player, amplitude(), amp); } - pub fn set_looping(&self, val: bool) { - entity::add_component(self.player, looping(), val); + pub fn set_looping(&self, world: &mut dyn World, val: bool) { + world.add_component(self.player, looping(), val); } - pub fn play_sound_on_entity(&self, url: String, emitter: EntityId) { - entity::add_component(self.player, spatial_audio_emitter(), emitter); - entity::add_component(self.player, audio_url(), url); - entity::add_component(self.player, play_now(), ()); + pub fn play_sound_on_entity(&self, world: &mut dyn World, url: String, emitter: EntityId) { + world.add_component(self.player, spatial_audio_emitter(), emitter); + world.add_component(self.player, audio_url(), url); + world.add_component(self.player, play_now(), ()); } } @@ -68,51 +60,44 @@ pub struct AudioPlayer { /// The entity that represents the audio player pub entity: EntityId, } - -impl Default for AudioPlayer { - fn default() -> Self { - Self::new() - } -} - impl AudioPlayer { /// Create new audio player from URL - pub fn new() -> Self { + pub fn new(world: &mut dyn World) -> Self { let player = Entity::new() .with(is_audio_player(), ()) .with(name(), "Audio player".to_string()) .with(children(), vec![]) - .spawn(); + .spawn(world); Self { entity: player } } /// Set the sound looping or not - pub fn set_looping(&self, val: bool) { - entity::add_component(self.entity, looping(), val); + pub fn set_looping(&self, world: &mut dyn World, val: bool) { + world.add_component(self.entity, looping(), val); } /// Add a simple onepole lowpass filter to the sound with one param: roll off frequency - pub fn add_one_pole_lpf(&self, rolloff_freq: f32) { - entity::add_component(self.entity, onepole_lpf(), rolloff_freq); + pub fn add_one_pole_lpf(&self, world: &mut dyn World, rolloff_freq: f32) { + world.add_component(self.entity, onepole_lpf(), rolloff_freq); } /// Set the amp/volume of the sound 0.0 is 0%, 1.0 is 100% - pub fn set_amplitude(&self, amp: f32) { - entity::add_component(self.entity, amplitude(), amp); + pub fn set_amplitude(&self, world: &mut dyn World, amp: f32) { + world.add_component(self.entity, amplitude(), amp); } /// Set the panning of the sound -1.0 is 100% left, 1.0 is 100% right. - pub fn set_panning(&self, pan: f32) { - entity::add_component(self.entity, panning(), pan); + pub fn set_panning(&self, world: &mut dyn World, pan: f32) { + world.add_component(self.entity, panning(), pan); } /// Play the sound, this will generate a new entity that represents the playing sound. - pub fn play(&self, url: String) -> EntityId { - entity::add_component(self.entity, audio_url(), url); - entity::add_component(self.entity, play_now(), ()); + pub fn play(&self, world: &mut dyn World, url: String) -> EntityId { + world.add_component(self.entity, audio_url(), url); + world.add_component(self.entity, play_now(), ()); let id = Entity::new() .with(playing_sound(), ()) .with(name(), "Playing sound".to_string()) .with(parent(), self.entity) - .spawn(); - entity::mutate_component(self.entity, children(), |val| { + .spawn(world); + world.mutate_component(self.entity, children(), |val| { val.push(id); }); id diff --git a/guest/rust/api_core/src/ecs.rs b/guest/rust/api_core/src/ecs.rs index 4528803ed0..46ae130cfe 100644 --- a/guest/rust/api_core/src/ecs.rs +++ b/guest/rust/api_core/src/ecs.rs @@ -1,3 +1,6 @@ +use async_trait::async_trait; +use glam::{Mat4, Vec3}; + pub use crate::internal::component::{ query::{ change_query, despawn_query, query, spawn_query, ChangeQuery, EventQuery, GeneralQuery, @@ -9,3 +12,256 @@ pub use crate::internal::component::{ #[doc(hidden)] pub use crate::internal::wit::component::Value as WitComponentValue; +use crate::{ + core::ecs::components::{children, parent}, + prelude::{block_until, EntityId}, +}; + +/// Implemented by all things that can be used as an ECS world. +pub trait World: Sync + Send { + /// Spawns an entity containing the `components`. + /// + /// Returns `spawned_entity_uid`. + fn spawn(&mut self, components: &Entity) -> EntityId; + + /// Despawns `entity` from the world. `entity` will not work with any other functions afterwards. + /// + /// Returns the data of the despawned entity, if it existed. + fn despawn(&mut self, entity: EntityId) -> Option; + + /// Gets a list of world transforms relative to origin entity + /// Origin can be null entity for a list of world transforms + fn get_transforms_relative_to(&self, list: &[EntityId], origin: EntityId) -> Vec; + + /// Checks if the `entity` exists. + fn exists(&self, entity: EntityId) -> bool; + + /// Gets all of the entities that have the given `component`. + #[doc(hidden)] + fn get_all_untyped(&self, component: &dyn UntypedComponent) -> Vec; + + /// Gets all of the entities within `radius` of `position`. + fn in_area(&self, position: Vec3, radius: f32) -> Vec; + + /// Retrieves the component `component` for `entity` if it exists, or `None` if it doesn't. + #[doc(hidden)] + fn get_component_untyped( + &self, + entity: EntityId, + component: &dyn UntypedComponent, + ) -> Option; + + /// Retrieves the components `components` for `entity`. Will return an empty `Entity` if no components are found. + fn get_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> Entity; + + /// Retrieves all guest-visible components for `entity`. Will return an empty `Entity` if no components are found. + /// + /// Note that this may not be all of the components on the entity, as some components are not visible to the guest. + fn get_all_components(&self, entity: EntityId) -> Entity; + + /// Adds the component `component` for `entity` with `value`. Will replace an existing component if present. + #[doc(hidden)] + fn add_component_untyped( + &mut self, + entity: EntityId, + component: &dyn UntypedComponent, + value: ComponentValue, + ); + + /// Adds the components `components` for `entity` with `value`. Will replace any existing components specified in `components`. + fn add_components(&mut self, entity: EntityId, components: Entity); + + /// Sets the component `component` for `entity` with `value`. + #[doc(hidden)] + fn set_component_untyped( + &mut self, + entity: EntityId, + component: &dyn UntypedComponent, + value: ComponentValue, + ); + + /// Sets the components `components` for `entity` with `value`. + fn set_components(&mut self, entity: EntityId, components: Entity); + + /// Checks if the `entity` has a `component`. + #[doc(hidden)] + fn has_component_untyped(&self, entity: EntityId, component: &dyn UntypedComponent) -> bool; + + /// Checks if the `entity` has `components`. + fn has_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> bool; + + /// Removes the `component` from `entity`. + /// + /// Does nothing if the component does not exist. + #[doc(hidden)] + fn remove_component_untyped(&mut self, entity: EntityId, component: &dyn UntypedComponent); + /// Removes the `components` from `entity`. + /// + /// Does nothing if the component does not exist. + fn remove_components(&mut self, entity: EntityId, components: &[&dyn UntypedComponent]); + + /// Gets the resource entity. The components of this entity contain global state for this ECS world. + /// + /// Components with the `Resource` attribute can be found here. + fn resources(&self) -> EntityId; + + /// Gets the synchronized resource entity. The components of this entity contain global state that should be networked, but not persisted. + fn synchronized_resources(&self) -> EntityId; + + /// Gets the persisted resource entity. The components of this entity contain global state that should be networked and persisted. + fn persisted_resources(&self) -> EntityId; +} + +#[async_trait] +/// Extension methods for [`Worldlike`]. +pub trait WorldExt: World { + /// Gets all of the entities that have the given `component`. + fn get_all(&self, component: Component) -> Vec { + self.get_all_untyped(&component) + } + + /// Retrieves the component `component` for `entity` if it exists, or `None` if it doesn't. + fn get_component( + &self, + entity: EntityId, + component: Component, + ) -> Option { + self.get_component_untyped(entity, &component) + .and_then(|x| T::from_value(x)) + } + /// Retrieves the component `component` for `entity` if it exists, or `None` if it doesn't. + fn get(&self, entity: EntityId, component: Component) -> Option { + self.get_component(entity, component) + } + #[doc(hidden)] + fn get_cloned( + &self, + entity: EntityId, + component: Component, + ) -> Option { + self.get_component(entity, component) + } + + /// Retrieves the `resource` if it exists and panics if it doesn't. + fn resource(&self, component: Component) -> T { + self.get_component(self.resources(), component).unwrap() + } + + /// Adds the component `component` for `entity` with `value`. Will replace an existing component if present. + fn add_component( + &mut self, + entity: EntityId, + component: Component, + value: T, + ) { + self.add_component_untyped(entity, &component, value.into_value()) + } + + /// Sets the component `component` for `entity` with `value`. + fn set_component( + &mut self, + entity: EntityId, + component: Component, + value: T, + ) { + self.set_component_untyped(entity, &component, value.into_value()) + } + /// Sets the component `component` for `entity` with `value`. + fn set(&mut self, entity: EntityId, component: Component, value: T) { + self.set_component(entity, component, value) + } + + /// Checks if the `entity` has a `component`. + fn has_component(&self, entity: EntityId, component: Component) -> bool { + self.has_component_untyped(entity, &component) + } + + /// Removes the `component` from `entity`. + /// + /// Does nothing if the component does not exist. + fn remove_component(&mut self, entity: EntityId, component: Component) { + self.remove_component_untyped(entity, &component) + } + + /// Waits until `id` has the `component`. If the entity was deleted the method returns None. + async fn wait_for_component( + &self, + entity: EntityId, + component: Component, + ) -> Option { + block_until(move || !self.exists(entity) || self.has_component(entity, component)).await; + self.get_component(entity, component) + } + + /// Despawns `entity` and all of its children. + fn despawn_recursive(&mut self, entity: EntityId) { + if let Some(res) = self.despawn(entity) { + if let Some(children) = res.get_ref(children()) { + for c in children { + self.despawn_recursive(*c); + } + } + } + } + + /// Mutates the component `component` for `entity` using the passed in `mutator`, and returns its value. + /// + /// This will not set the component if the value is the same, which will prevent change events from + /// being unnecessarily fired. + fn mutate_component( + &mut self, + entity: EntityId, + component: Component, + mutator: impl FnOnce(&mut T), + ) -> Option { + let mut value: T = self.get_component(entity, component)?; + let orig_value = value.clone(); + mutator(&mut value); + if value != orig_value { + self.set_component(entity, component, value.clone()); + } + Some(value) + } + + /// Mutates the component `component` for `entity` using the passed in `mutator`, or sets it + /// to `default` if it doesn't exist, and returns its value. + /// + /// This will not set the component if the value is the same, which will prevent change events from + /// being unnecessarily fired. + fn mutate_component_with_default( + &mut self, + entity: EntityId, + component: Component, + default: T, + mutator: impl FnOnce(&mut T), + ) -> T { + let value = self.mutate_component(entity, component, mutator); + if let Some(value) = value { + value + } else { + self.add_component(entity, component, default.clone()); + default + } + } + + /// Adds `child` as a child to `entity`. + fn add_child(&mut self, entity: EntityId, child: EntityId) { + if self.has_component(entity, children()) { + self.mutate_component(entity, children(), |children| children.push(child)); + } else { + self.add_component(entity, children(), vec![child]); + } + self.add_component(child, parent(), entity); + } + + /// Removes `child` as a child to `entity`. + fn remove_child(&mut self, entity: EntityId, child: EntityId) { + if self.has_component(entity, children()) { + self.mutate_component(entity, children(), |children| { + children.retain(|x| *x != child) + }); + } + self.remove_component(child, parent()); + } +} +impl WorldExt for T {} diff --git a/guest/rust/api_core/src/entity.rs b/guest/rust/api_core/src/entity.rs deleted file mode 100644 index 01678220b3..0000000000 --- a/guest/rust/api_core/src/entity.rs +++ /dev/null @@ -1,245 +0,0 @@ -use crate::{ - core::ecs::components::{children, parent}, - global::{EntityId, Vec3}, - internal::{ - component::{Component, Entity, SupportedValue, UntypedComponent}, - conversion::{FromBindgen, IntoBindgen}, - wit, - }, - prelude::block_until, -}; - -/// Spawns an entity containing the `components`. -/// -/// Returns `spawned_entity_uid`. -pub fn spawn(components: &Entity) -> EntityId { - wit::entity::spawn(&components.clone().into_bindgen()).from_bindgen() -} - -/// Waits until `id` has the `component`. If the entity was deleted the method returns None. -pub async fn wait_for_component( - entity: EntityId, - component: Component, -) -> Option { - block_until(move || !exists(entity) || has_component(entity, component)).await; - get_component(entity, component) -} - -/// Despawns `entity` from the world. `entity` will not work with any other functions afterwards. -/// -/// Returns the data of the despawned entity, if it existed. -pub fn despawn(entity: EntityId) -> Option { - wit::entity::despawn(entity.into_bindgen()).from_bindgen() -} -/// Despawns `entity` and all of its children. -pub fn despawn_recursive(entity: EntityId) { - if let Some(res) = despawn(entity) { - if let Some(children) = res.get_ref(children()) { - for c in children { - despawn_recursive(*c); - } - } - } -} - -/// Unconverted bindgen transforms -pub struct RawTransforms { - transforms: Vec, -} - -impl RawTransforms { - /// Convert transforms into a list of Mat4 - pub fn into_mat4(self) -> Vec { - self.transforms.from_bindgen() - } - - /// Convert transforms into mat4 as an iterator - pub fn iter_mat4(&self) -> impl ExactSizeIterator + '_ { - self.transforms.iter().map(|&x| x.from_bindgen()) - } -} - -/// Gets a list of world transforms relative to origin entity -/// Origin can be null entity for a list of world transforms -pub fn get_transforms_relative_to(list: &[EntityId], origin: EntityId) -> RawTransforms { - let entities: Vec = list.iter().map(|x| x.into_bindgen()).collect(); - RawTransforms { - transforms: wit::entity::get_transforms_relative_to(&entities, origin.into_bindgen()), - } -} - -/// Checks if the `entity` exists. -pub fn exists(entity: EntityId) -> bool { - wit::entity::exists(entity.into_bindgen()) -} - -/// Gets all of the entities that have the given `component`. -pub fn get_all(component: Component) -> Vec { - wit::entity::get_all(component.index()).from_bindgen() -} - -/// Gets all of the entities within `radius` of `position`. -pub fn in_area(position: Vec3, radius: f32) -> Vec { - wit::entity::in_area(position.into_bindgen(), radius).from_bindgen() -} - -/// Retrieves the component `component` for `entity` if it exists, or `None` if it doesn't. -pub fn get_component(entity: EntityId, component: Component) -> Option { - T::from_result(wit::component::get_component( - entity.into_bindgen(), - component.index(), - )?) -} - -/// Retrieves the components `components` for `entity`. Will return an empty `Entity` if no components are found. -pub fn get_components(entity: EntityId, components: &[&dyn UntypedComponent]) -> Entity { - let components: Vec<_> = components.iter().map(|c| c.index()).collect(); - wit::component::get_components(entity.into_bindgen(), &components).from_bindgen() -} - -/// Retrieves all guest-visible components for `entity`. Will return an empty `Entity` if no components are found. -/// -/// Note that this may not be all of the components on the entity, as some components are not visible to the guest. -pub fn get_all_components(entity: EntityId) -> Entity { - wit::component::get_all_components(entity.into_bindgen()).from_bindgen() -} - -/// Adds the component `component` for `entity` with `value`. Will replace an existing component if present. -pub fn add_component(entity: EntityId, component: Component, value: T) { - wit::component::add_component( - entity.into_bindgen(), - component.index(), - &value.into_result(), - ) -} - -/// Adds the components `components` for `entity` with `value`. Will replace any existing components specified in `components`. -pub fn add_components(entity: EntityId, components: Entity) { - wit::component::add_components(entity.into_bindgen(), &components.into_bindgen()) -} - -/// Sets the component `component` for `entity` with `value`. -pub fn set_component(entity: EntityId, component: Component, value: T) { - wit::component::set_component( - entity.into_bindgen(), - component.index(), - &value.into_result(), - ) -} - -/// Sets the components `components` for `entity` with `value`. -pub fn set_components(entity: EntityId, components: Entity) { - wit::component::set_components(entity.into_bindgen(), &components.into_bindgen()) -} - -/// Checks if the `entity` has a `component`. -pub fn has_component(entity: EntityId, component: Component) -> bool { - wit::component::has_component(entity.into_bindgen(), component.index()) -} - -/// Checks if the `entity` has `components`. -pub fn has_components(entity: EntityId, components: &[&dyn UntypedComponent]) -> bool { - let components: Vec<_> = components.iter().map(|c| c.index()).collect(); - wit::component::has_components(entity.into_bindgen(), &components) -} - -/// Adds the `component` with `value` to `entity` if `entity` does not already have that component. -pub fn add_component_if_required( - entity: EntityId, - component: Component, - value: T, -) { - if !has_component(entity, component) { - add_component(entity, component, value) - } -} - -/// Removes the `component` from `entity`. -/// -/// Does nothing if the component does not exist. -pub fn remove_component(entity: EntityId, component: Component) { - wit::component::remove_component(entity.into_bindgen(), component.index()) -} - -/// Removes the `components` from `entity`. -/// -/// Does nothing if the component does not exist. -pub fn remove_components(entity: EntityId, components: &[&dyn UntypedComponent]) { - let components: Vec<_> = components.iter().map(|c| c.index()).collect(); - wit::component::remove_components(entity.into_bindgen(), &components) -} - -/// Mutates the component `component` for `entity` using the passed in `mutator`, and returns its value. -/// -/// This will not set the component if the value is the same, which will prevent change events from -/// being unnecessarily fired. -pub fn mutate_component( - entity: EntityId, - component: Component, - mutator: impl FnOnce(&mut T), -) -> Option { - let mut value: T = get_component(entity, component)?; - let orig_value = value.clone(); - mutator(&mut value); - if value != orig_value { - set_component(entity, component, value.clone()); - } - Some(value) -} - -/// Mutates the component `component` for `entity` using the passed in `mutator`, or sets it -/// to `default` if it doesn't exist, and returns its value. -/// -/// This will not set the component if the value is the same, which will prevent change events from -/// being unnecessarily fired. -pub fn mutate_component_with_default( - entity: EntityId, - component: Component, - default: T, - mutator: impl FnOnce(&mut T), -) -> T { - let value = mutate_component(entity, component, mutator); - if let Some(value) = value { - value - } else { - add_component(entity, component, default.clone()); - default - } -} - -/// Adds `child` as a child to `entity`. -pub fn add_child(entity: EntityId, child: EntityId) { - if has_component(entity, children()) { - mutate_component(entity, children(), |children| children.push(child)); - } else { - add_component(entity, children(), vec![child]); - } - add_component(child, parent(), entity); -} - -/// Removes `child` as a child to `entity`. -pub fn remove_child(entity: EntityId, child: EntityId) { - if has_component(entity, children()) { - mutate_component(entity, children(), |children| { - children.retain(|x| *x != child) - }); - } - remove_component(child, parent()); -} - -/// Gets the resource entity. The components of this entity contain global state for this ECS world. -/// -/// Components with the `Resource` attribute can be found here. -pub fn resources() -> EntityId { - EntityId::resources() -} - -/// Gets the synchronized resource entity. The components of this entity contain global state that should be networked, but not persisted. -pub fn synchronized_resources() -> EntityId { - wit::entity::synchronized_resources().from_bindgen() -} - -/// Gets the persisted resource entity. The components of this entity contain global state that should be networked and persisted. -pub fn persisted_resources() -> EntityId { - wit::entity::persisted_resources().from_bindgen() -} diff --git a/guest/rust/api_core/src/global/runtime.rs b/guest/rust/api_core/src/global/runtime.rs index d1aadef3a2..6ce8a13646 100644 --- a/guest/rust/api_core/src/global/runtime.rs +++ b/guest/rust/api_core/src/global/runtime.rs @@ -8,25 +8,30 @@ use std::{ use crate::{ core::app, - entity, global::{OkEmpty, ResultEmpty}, internal::executor::EXECUTOR, - prelude::RuntimeMessage, + prelude::{RuntimeMessage, World, WorldExt}, }; /// The time, relative to the start of the game. Guaranteed to be monotonic. -pub fn game_time() -> Duration { - entity::get_component(entity::resources(), app::components::game_time()).unwrap() +pub fn game_time(world: &dyn World) -> Duration { + world + .get_component(world.resources(), app::components::game_time()) + .unwrap() } /// The time, relative to Jan 1, 1970. Not guaranteed to be monotonic. Use [game_time] for most applications. -pub fn epoch_time() -> Duration { - entity::get_component(entity::resources(), app::components::epoch_time()).unwrap() +pub fn epoch_time(world: &dyn World) -> Duration { + world + .get_component(world.resources(), app::components::epoch_time()) + .unwrap() } /// The length of the previous frame, in seconds. -pub fn delta_time() -> f32 { - entity::get_component(entity::resources(), app::components::delta_time()).unwrap() +pub fn delta_time(world: &dyn World) -> f32 { + world + .get_component(world.resources(), app::components::delta_time()) + .unwrap() } /// A trait that abstracts over return types so that you can return an [ResultEmpty] or nothing. diff --git a/guest/rust/api_core/src/global/shapes.rs b/guest/rust/api_core/src/global/shapes.rs index 0a750e08a0..7d14f2ef0b 100644 --- a/guest/rust/api_core/src/global/shapes.rs +++ b/guest/rust/api_core/src/global/shapes.rs @@ -1,8 +1,8 @@ use super::EntityId; use crate::{ core::transform::components::local_to_world, - entity::get_component, internal::{conversion::FromBindgen, wit}, + prelude::{World, WorldExt}, }; use glam::{vec3, Vec3}; @@ -16,8 +16,8 @@ pub struct Ray { } impl Ray { /// This creates a ray from a cameras view matrix (i.e. from `local_to_world` of a camera entity). - pub fn from_camera_view_matrix(camera: EntityId) -> Option { - let mat4 = get_component(camera, local_to_world())?; + pub fn from_camera_view_matrix(world: &dyn World, camera: EntityId) -> Option { + let mat4 = world.get_component(camera, local_to_world())?; let origin = mat4.project_point3(Vec3::ZERO); let end = mat4.project_point3(vec3(0., 0., 1.)); let dir = (end - origin).normalize(); diff --git a/guest/rust/api_core/src/internal/component/entity.rs b/guest/rust/api_core/src/internal/component/entity.rs index d53e7497ee..d33ca6545d 100644 --- a/guest/rust/api_core/src/internal/component/entity.rs +++ b/guest/rust/api_core/src/internal/component/entity.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; -use crate::internal::{ - conversion::{FromBindgen, IntoBindgen}, - wit, +use crate::{ + internal::{ + conversion::{FromBindgen, IntoBindgen}, + wit, + }, + prelude::World, }; use super::{Component, ComponentValue, SupportedValue, SupportedValueRef, UntypedComponent}; @@ -67,9 +70,9 @@ impl Entity { /// Spawns an entity with these components. /// - /// Returns `spawned_entity_uid`. - pub fn spawn(&self) -> crate::prelude::EntityId { - crate::entity::spawn(self) + /// Returns `spawned_entity_id`. + pub fn spawn(&self, world: &mut dyn World) -> crate::prelude::EntityId { + world.spawn(self) } } impl FromBindgen for wit::component::Entity { diff --git a/guest/rust/api_core/src/internal/component/mod.rs b/guest/rust/api_core/src/internal/component/mod.rs index d672e87319..6722226a18 100644 --- a/guest/rust/api_core/src/internal/component/mod.rs +++ b/guest/rust/api_core/src/internal/component/mod.rs @@ -44,6 +44,8 @@ impl UntypedComponent for Component { self.index } } +unsafe impl Send for Component {} +unsafe impl Sync for Component {} /// A tuple of [Component]s. pub trait ComponentsTuple { diff --git a/guest/rust/api_core/src/internal/generated.rs b/guest/rust/api_core/src/internal/generated.rs index fbf8291c07..ed33febaee 100644 --- a/guest/rust/api_core/src/internal/generated.rs +++ b/guest/rust/api_core/src/internal/generated.rs @@ -531,9 +531,9 @@ mod raw { ) } #[doc = "Checks if the entity is a *Camera*.\n\nBase components for a camera. You will need other components to make a fully-functioning camera.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n}\n```\n"] - pub fn is_camera(id: EntityId) -> bool { - crate::ambient_core::transform::concepts::is_transformable(id) - && entity::has_components( + pub fn is_camera(world: &dyn crate::prelude::World, id: EntityId) -> bool { + crate::ambient_core::transform::concepts::is_transformable(world, id) + && world.has_components( id, &[ &crate::ambient_core::camera::components::near(), @@ -576,9 +576,12 @@ mod raw { ) } #[doc = "Checks if the entity is a *Perspective Common Camera*.\n\nBase components for a perspective camera. Consider `perspective_camera` or `perspective_infinite_reverse_camera`.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::fovy\": f32 = 1.0,\n \"ambient_core::camera::aspect_ratio\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n}\n```\n"] - pub fn is_perspective_common_camera(id: EntityId) -> bool { - crate::ambient_core::camera::concepts::is_camera(id) - && entity::has_components( + pub fn is_perspective_common_camera( + world: &dyn crate::prelude::World, + id: EntityId, + ) -> bool { + crate::ambient_core::camera::concepts::is_camera(world, id) + && world.has_components( id, &[ &crate::ambient_core::camera::components::fovy(), @@ -605,9 +608,12 @@ mod raw { .with(crate::ambient_core::camera::components::far(), 1000f32) } #[doc = "Checks if the entity is a *Perspective Camera*.\n\nA perspective camera.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::perspective\": () = (),\n \"ambient_core::camera::far\": f32 = 1000.0,\n \"ambient_core::camera::perspective_common_camera\": { // Concept.\n \"ambient_core::camera::fovy\": f32 = 1.0,\n \"ambient_core::camera::aspect_ratio\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n },\n}\n```\n"] - pub fn is_perspective_camera(id: EntityId) -> bool { - crate::ambient_core::camera::concepts::is_perspective_common_camera(id) - && entity::has_components( + pub fn is_perspective_camera( + world: &dyn crate::prelude::World, + id: EntityId, + ) -> bool { + crate::ambient_core::camera::concepts::is_perspective_common_camera(world, id) + && world.has_components( id, &[ &crate::ambient_core::camera::components::perspective(), @@ -636,8 +642,11 @@ mod raw { ) } #[doc = "Checks if the entity is a *Perspective-Infinite-Reverse Camera*.\n\nA perspective-infinite-reverse camera. This is recommended for most use-cases.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::perspective_infinite_reverse\": () = (),\n \"ambient_core::camera::perspective_common_camera\": { // Concept.\n \"ambient_core::camera::fovy\": f32 = 1.0,\n \"ambient_core::camera::aspect_ratio\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n },\n}\n```\n"] - pub fn is_perspective_infinite_reverse_camera(id: EntityId) -> bool { - crate :: ambient_core :: camera :: concepts :: is_perspective_common_camera (id) && entity :: has_components (id , & [& crate :: ambient_core :: camera :: components :: perspective_infinite_reverse ()]) + pub fn is_perspective_infinite_reverse_camera( + world: &dyn crate::prelude::World, + id: EntityId, + ) -> bool { + crate :: ambient_core :: camera :: concepts :: is_perspective_common_camera (world , id) && world . has_components (id , & [& crate :: ambient_core :: camera :: components :: perspective_infinite_reverse ()]) } #[doc = "Returns the components that comprise *Perspective-Infinite-Reverse Camera* as a tuple.\n\nA perspective-infinite-reverse camera. This is recommended for most use-cases.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::perspective_infinite_reverse\": () = (),\n \"ambient_core::camera::perspective_common_camera\": { // Concept.\n \"ambient_core::camera::fovy\": f32 = 1.0,\n \"ambient_core::camera::aspect_ratio\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n },\n}\n```\n"] #[allow(clippy::type_complexity)] @@ -670,9 +679,12 @@ mod raw { .with(crate::ambient_core::camera::components::far(), 1f32) } #[doc = "Checks if the entity is a *Orthographic Camera*.\n\nAn orthographic camera.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::orthographic\": () = (),\n \"ambient_core::camera::orthographic_left\": f32 = -1.0,\n \"ambient_core::camera::orthographic_right\": f32 = 1.0,\n \"ambient_core::camera::orthographic_top\": f32 = 1.0,\n \"ambient_core::camera::orthographic_bottom\": f32 = -1.0,\n \"ambient_core::camera::near\": f32 = -1.0,\n \"ambient_core::camera::far\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n}\n```\n"] - pub fn is_orthographic_camera(id: EntityId) -> bool { - crate::ambient_core::camera::concepts::is_camera(id) - && entity::has_components( + pub fn is_orthographic_camera( + world: &dyn crate::prelude::World, + id: EntityId, + ) -> bool { + crate::ambient_core::camera::concepts::is_camera(world, id) + && world.has_components( id, &[ &crate::ambient_core::camera::components::orthographic(), @@ -1794,8 +1806,8 @@ mod raw { ) } #[doc = "Checks if the entity is a *Sphere*.\n\nA primitive sphere.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::primitives::sphere\": () = (),\n \"ambient_core::primitives::sphere_radius\": f32 = 0.5,\n \"ambient_core::primitives::sphere_sectors\": u32 = 36,\n \"ambient_core::primitives::sphere_stacks\": u32 = 18,\n}\n```\n"] - pub fn is_sphere(id: EntityId) -> bool { - entity::has_components( + pub fn is_sphere(world: &dyn crate::prelude::World, id: EntityId) -> bool { + world.has_components( id, &[ &crate::ambient_core::primitives::components::sphere(), @@ -1847,8 +1859,8 @@ mod raw { ) } #[doc = "Checks if the entity is a *Capsule*.\n\nA primitive capsule. Defined as a cylinder capped by hemispheres.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::primitives::capsule\": () = (),\n \"ambient_core::primitives::capsule_radius\": f32 = 0.5,\n \"ambient_core::primitives::capsule_half_height\": f32 = 0.5,\n \"ambient_core::primitives::capsule_rings\": u32 = 0,\n \"ambient_core::primitives::capsule_latitudes\": u32 = 16,\n \"ambient_core::primitives::capsule_longitudes\": u32 = 32,\n}\n```\n"] - pub fn is_capsule(id: EntityId) -> bool { - entity::has_components( + pub fn is_capsule(world: &dyn crate::prelude::World, id: EntityId) -> bool { + world.has_components( id, &[ &crate::ambient_core::primitives::components::capsule(), @@ -1902,8 +1914,8 @@ mod raw { ) } #[doc = "Checks if the entity is a *Torus*.\n\nA primitive Torus, surface of revolution generated by revolving a circle in three-dimensional space one full revolution.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::primitives::torus\": () = (),\n \"ambient_core::primitives::torus_inner_radius\": f32 = 0.25,\n \"ambient_core::primitives::torus_outer_radius\": f32 = 0.35,\n \"ambient_core::primitives::torus_slices\": u32 = 32,\n \"ambient_core::primitives::torus_loops\": u32 = 16,\n}\n```\n"] - pub fn is_torus(id: EntityId) -> bool { - entity::has_components( + pub fn is_torus(world: &dyn crate::prelude::World, id: EntityId) -> bool { + world.has_components( id, &[ &crate::ambient_core::primitives::components::torus(), @@ -2465,8 +2477,8 @@ mod raw { ) } #[doc = "Checks if the entity is a *Transformable*.\n\nCan be translated, rotated and scaled.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n}\n```\n"] - pub fn is_transformable(id: EntityId) -> bool { - entity::has_components( + pub fn is_transformable(world: &dyn crate::prelude::World, id: EntityId) -> bool { + world.has_components( id, &[ &crate::ambient_core::transform::components::translation(), diff --git a/guest/rust/api_core/src/internal/mod.rs b/guest/rust/api_core/src/internal/mod.rs index 475be17aec..23965920e1 100644 --- a/guest/rust/api_core/src/internal/mod.rs +++ b/guest/rust/api_core/src/internal/mod.rs @@ -6,20 +6,22 @@ pub(crate) mod generated; #[allow(missing_docs)] pub(crate) mod wit; -use crate::internal::executor::EXECUTOR; +mod world; + +use crate::{ecs::World, internal::executor::EXECUTOR}; use wit::{__link_section, exports, guest}; wit::export_bindings!(Guest); extern "Rust" { - fn main(); + fn main(worldlike: &mut dyn World); } struct Guest; impl guest::Guest for Guest { fn init() { once_cell::sync::Lazy::force(&EXECUTOR); - unsafe { main() }; + unsafe { main(&mut world::DefaultWorld) }; } fn exec(source: guest::Source, message_name: String, message_data: Vec) { diff --git a/guest/rust/api_core/src/internal/world.rs b/guest/rust/api_core/src/internal/world.rs new file mode 100644 index 0000000000..b99852f72a --- /dev/null +++ b/guest/rust/api_core/src/internal/world.rs @@ -0,0 +1,124 @@ +use glam::Mat4; + +use crate::{ + ecs::{ComponentValue, World}, + global::{EntityId, Vec3}, + internal::{ + component::{Entity, UntypedComponent}, + conversion::{FromBindgen, IntoBindgen}, + wit, + }, +}; + +pub struct DefaultWorld; + +impl World for DefaultWorld { + fn spawn(&mut self, components: &Entity) -> EntityId { + wit::entity::spawn(&components.clone().into_bindgen()).from_bindgen() + } + + fn despawn(&mut self, entity: EntityId) -> Option { + wit::entity::despawn(entity.into_bindgen()).from_bindgen() + } + + fn get_transforms_relative_to(&self, list: &[EntityId], origin: EntityId) -> Vec { + wit::entity::get_transforms_relative_to( + &list.iter().map(|x| x.into_bindgen()).collect::>(), + origin.into_bindgen(), + ) + .from_bindgen() + } + + fn exists(&self, entity: EntityId) -> bool { + wit::entity::exists(entity.into_bindgen()) + } + + fn get_all_untyped(&self, component: &dyn UntypedComponent) -> Vec { + wit::entity::get_all(component.index()).from_bindgen() + } + + fn in_area(&self, position: Vec3, radius: f32) -> Vec { + wit::entity::in_area(position.into_bindgen(), radius).from_bindgen() + } + + fn get_component_untyped( + &self, + entity: EntityId, + component: &dyn UntypedComponent, + ) -> Option { + wit::component::get_component(entity.into_bindgen(), component.index()).from_bindgen() + } + + fn get_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> Entity { + let components: Vec<_> = components.iter().map(|c| c.index()).collect(); + wit::component::get_components(entity.into_bindgen(), &components).from_bindgen() + } + + fn get_all_components(&self, entity: EntityId) -> Entity { + wit::component::get_all_components(entity.into_bindgen()).from_bindgen() + } + + fn add_component_untyped( + &mut self, + entity: EntityId, + component: &dyn UntypedComponent, + value: ComponentValue, + ) { + wit::component::add_component( + entity.into_bindgen(), + component.index(), + &value.into_bindgen(), + ) + } + + fn add_components(&mut self, entity: EntityId, components: Entity) { + wit::component::add_components(entity.into_bindgen(), &components.into_bindgen()) + } + + fn set_component_untyped( + &mut self, + entity: EntityId, + component: &dyn UntypedComponent, + value: ComponentValue, + ) { + wit::component::set_component( + entity.into_bindgen(), + component.index(), + &value.into_bindgen(), + ) + } + + fn set_components(&mut self, entity: EntityId, components: Entity) { + wit::component::set_components(entity.into_bindgen(), &components.into_bindgen()) + } + + fn has_component_untyped(&self, entity: EntityId, component: &dyn UntypedComponent) -> bool { + wit::component::has_component(entity.into_bindgen(), component.index()) + } + + fn has_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> bool { + let components: Vec<_> = components.iter().map(|c| c.index()).collect(); + wit::component::has_components(entity.into_bindgen(), &components) + } + + fn remove_component_untyped(&mut self, entity: EntityId, component: &dyn UntypedComponent) { + wit::component::remove_component(entity.into_bindgen(), component.index()) + } + + fn remove_components(&mut self, entity: EntityId, components: &[&dyn UntypedComponent]) { + let components: Vec<_> = components.iter().map(|c| c.index()).collect(); + wit::component::remove_components(entity.into_bindgen(), &components) + } + + fn resources(&self) -> EntityId { + EntityId::resources() + } + + fn synchronized_resources(&self) -> EntityId { + wit::entity::synchronized_resources().from_bindgen() + } + + fn persisted_resources(&self) -> EntityId { + wit::entity::persisted_resources().from_bindgen() + } +} diff --git a/guest/rust/api_core/src/lib.rs b/guest/rust/api_core/src/lib.rs index 7c50b41a6a..1c36533615 100644 --- a/guest/rust/api_core/src/lib.rs +++ b/guest/rust/api_core/src/lib.rs @@ -19,8 +19,6 @@ pub mod server; pub mod asset; /// ECS-related functionality not directly related to entities. pub mod ecs; -/// Manipulation, creation, removal, search and more for entities. -pub mod entity; /// Global functions and types for your convenience. pub mod global; /// Messaging to other packages and to the other side of the network boundary. diff --git a/guest/rust/api_core/src/prelude.rs b/guest/rust/api_core/src/prelude.rs index d19829a823..f6fa8d97c9 100644 --- a/guest/rust/api_core/src/prelude.rs +++ b/guest/rust/api_core/src/prelude.rs @@ -1,7 +1,9 @@ pub use crate::{ asset, - ecs::{change_query, despawn_query, query, spawn_query, Component, Entity, QueryEvent}, - entity, + ecs::{ + change_query, despawn_query, query, spawn_query, Component, Entity, QueryEvent, World, + WorldExt, + }, global::*, main, message, message::{Message, ModuleMessage, RuntimeMessage}, diff --git a/guest/rust/examples/basics/asset_loading/src/client.rs b/guest/rust/examples/basics/asset_loading/src/client.rs index 79dd69a3c1..f477571245 100644 --- a/guest/rust/examples/basics/asset_loading/src/client.rs +++ b/guest/rust/examples/basics/asset_loading/src/client.rs @@ -1,7 +1,7 @@ use ambient_api::prelude::*; #[main] -fn main() { +fn main(_world: &mut dyn World) { // Load the asset println!( "asset url can be accessed from client: {}", diff --git a/guest/rust/examples/basics/asset_loading/src/server.rs b/guest/rust/examples/basics/asset_loading/src/server.rs index 56c2c959db..589f3babf2 100644 --- a/guest/rust/examples/basics/asset_loading/src/server.rs +++ b/guest/rust/examples/basics/asset_loading/src/server.rs @@ -23,20 +23,20 @@ use ambient_api::{ use packages::this::{assets, components::is_the_best}; #[main] -pub async fn main() { +pub async fn main(world: &mut dyn World) { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with(aspect_ratio_from_window(), EntityId::resources()) .with(main_scene(), ()) .with(translation(), vec3(2., 2., 1.)) .with(lookat_target(), vec3(0., 0., 0.)) - .spawn(); + .spawn(world); Entity::new() .with_merge(make_transformable()) .with(quad(), ()) .with(scale(), Vec3::ONE * 2.0) - .spawn(); + .spawn(world); println!("Hello, Ambient!"); @@ -47,22 +47,22 @@ pub async fn main() { .with(main_scene(), ()) .with(light_diffuse(), Vec3::ONE * 5.0) .with(light_ambient(), Vec3::ZERO) - .spawn(); + .spawn(world); let model = Entity::new() .with_merge(make_transformable()) .with(cast_shadows(), ()) .with(prefab_from_url(), assets::url("Teapot.glb")) .with(is_the_best(), true) - .spawn(); + .spawn(world); - entity::wait_for_component(model, spawned()).await; + world.wait_for_component(model, spawned()).await; - println!("Entity components: {:?}", entity::get_all_components(model)); + println!("Entity components: {:?}", world.get_all_components(model)); Frame::subscribe(move |_| { - let t = game_time().as_secs_f32(); - entity::set_component( + let t = game_time(world).as_secs_f32(); + world.set_component( model, rotation(), Quat::from_euler(EulerRot::ZXY, t % TAU, (t * 2.0).sin() * 0.5, 0.0), diff --git a/guest/rust/examples/basics/physics/src/client.rs b/guest/rust/examples/basics/physics/src/client.rs index 7f1ba8a5bc..6ea9c0fee1 100644 --- a/guest/rust/examples/basics/physics/src/client.rs +++ b/guest/rust/examples/basics/physics/src/client.rs @@ -1,8 +1,8 @@ -use ambient_api::prelude::*; +use ambient_api::{ecs::World, prelude::*}; use packages::this::{assets, messages::Bonk}; #[main] -pub fn main() { +pub fn main(world: &mut dyn World) { let spatial_audio_player = audio::SpatialAudioPlayer::new(); Bonk::subscribe(move |_source, data| { diff --git a/shared_crates/guest_bridge/src/guest.rs b/shared_crates/guest_bridge/src/guest.rs index 5e4c9d884a..436668e168 100644 --- a/shared_crates/guest_bridge/src/guest.rs +++ b/shared_crates/guest_bridge/src/guest.rs @@ -31,77 +31,10 @@ pub async fn sleep(seconds: f32) { pub mod ecs { use super::api; pub use api::{ - ecs::{Component, SupportedValue as ComponentValue, UntypedComponent}, + ecs::{Component, SupportedValue as ComponentValue, UntypedComponent, World, WorldExt}, prelude::{Entity, EntityId}, }; - #[derive(Clone, Copy)] - pub struct World; - impl World { - pub fn spawn(&self, entity: Entity) -> EntityId { - api::entity::spawn(&entity) - } - pub fn despawn(&self, entity_id: EntityId) -> Option { - api::entity::despawn(entity_id) - } - pub fn exists(&self, entity_id: EntityId) -> bool { - api::entity::exists(entity_id) - } - pub fn set( - &self, - entity_id: EntityId, - component: Component, - value: T, - ) -> Result<(), ECSError> { - // TODO: set_component needs to return errors - api::entity::set_component(entity_id, component, value); - Ok(()) - } - pub fn add_component( - &self, - entity_id: EntityId, - component: Component, - value: T, - ) -> Result<(), ECSError> { - // TODO: add_component needs to return errors - api::entity::add_component(entity_id, component, value); - Ok(()) - } - pub fn add_components( - &self, - entity_id: EntityId, - components: Entity, - ) -> Result<(), ECSError> { - // TODO: add_components needs to return errors - api::entity::add_components(entity_id, components); - Ok(()) - } - pub fn get( - &self, - entity_id: EntityId, - component: Component, - ) -> Result { - api::entity::get_component(entity_id, component) - .ok_or_else(|| ECSError::EntityDoesntHaveComponent) - } - pub fn get_cloned( - &self, - entity_id: EntityId, - component: Component, - ) -> Result { - self.get(entity_id, component) - } - pub fn has_component( - &self, - entity_id: EntityId, - component: Component, - ) -> bool { - api::entity::has_component(entity_id, component) - } - pub fn resource(&self, component: Component) -> T { - api::entity::get_component(api::entity::resources(), component).unwrap() - } - } #[derive(Debug)] pub enum ECSError { EntityDoesntHaveComponent, diff --git a/shared_crates/package_macro_common/src/concepts.rs b/shared_crates/package_macro_common/src/concepts.rs index 0af4c01f2b..7db471d62d 100644 --- a/shared_crates/package_macro_common/src/concepts.rs +++ b/shared_crates/package_macro_common/src/concepts.rs @@ -146,14 +146,17 @@ fn generate_is( }) } }, - Context::GuestApi | Context::GuestUser => quote! { - #[doc = #is_comment] - pub fn #is_ident(id: EntityId) -> bool { - #(#extends(id) && )* entity::has_components(id, &[ - #(&#components),* - ]) + Context::GuestApi | Context::GuestUser => { + let api_path = context.guest_api_path().unwrap(); + quote! { + #[doc = #is_comment] + pub fn #is_ident(world: &dyn #api_path::prelude::World, id: EntityId) -> bool { + #(#extends(world, id) && )* world.has_components(id, &[ + #(&#components),* + ]) + } } - }, + } }) } From a9f8ca78328dc67ea6619696c31ee14c5a01c593 Mon Sep 17 00:00:00 2001 From: Philpax Date: Thu, 31 Aug 2023 15:35:34 +0200 Subject: [PATCH 2/2] wip(guest): struct instead of a trait for World --- Cargo.lock | 1 - guest/rust/Cargo.lock | 12 - guest/rust/Cargo.toml | 1 - guest/rust/api_core/Cargo.toml | 1 - guest/rust/api_core/src/animation.rs | 63 +++-- guest/rust/api_core/src/client/audio.rs | 28 +-- guest/rust/api_core/src/ecs.rs | 224 +++++++++++++----- guest/rust/api_core/src/global/runtime.rs | 8 +- guest/rust/api_core/src/global/shapes.rs | 7 +- .../api_core/src/internal/component/entity.rs | 4 +- guest/rust/api_core/src/internal/generated.rs | 24 +- guest/rust/api_core/src/internal/mod.rs | 6 +- guest/rust/api_core/src/internal/world.rs | 74 +++--- guest/rust/api_core/src/prelude.rs | 4 +- .../basics/asset_loading/src/client.rs | 2 +- .../basics/asset_loading/src/server.rs | 2 +- .../examples/basics/physics/src/client.rs | 2 +- shared_crates/element/src/hooks.rs | 27 ++- shared_crates/element/src/lib.rs | 15 +- shared_crates/guest_bridge/src/guest.rs | 8 +- .../package_macro_common/src/concepts.rs | 2 +- 21 files changed, 303 insertions(+), 212 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f38b1f9c50..9297fe3d55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,7 +210,6 @@ dependencies = [ "ambient_package_rt", "ambient_shared_types", "anyhow", - "async-trait", "byteorder", "data-encoding", "futures", diff --git a/guest/rust/Cargo.lock b/guest/rust/Cargo.lock index 8156817d45..4ba93bbfd7 100644 --- a/guest/rust/Cargo.lock +++ b/guest/rust/Cargo.lock @@ -130,7 +130,6 @@ dependencies = [ "ambient_package_rt", "ambient_shared_types", "anyhow", - "async-trait", "byteorder", "data-encoding", "futures", @@ -703,17 +702,6 @@ dependencies = [ "syn 2.0.28", ] -[[package]] -name = "async-trait" -version = "0.1.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - [[package]] name = "atomic_refcell" version = "0.1.10" diff --git a/guest/rust/Cargo.toml b/guest/rust/Cargo.toml index 6edfbffcd5..37757dd4b0 100644 --- a/guest/rust/Cargo.toml +++ b/guest/rust/Cargo.toml @@ -110,4 +110,3 @@ wit-bindgen = { version = "0.9.0", features = ["realloc"] } ulid = { version = "1.0.0", features = ["serde"] } indexmap = { version = "2.0", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } -async-trait = "0.1" diff --git a/guest/rust/api_core/Cargo.toml b/guest/rust/api_core/Cargo.toml index b0002931ec..fefc516487 100644 --- a/guest/rust/api_core/Cargo.toml +++ b/guest/rust/api_core/Cargo.toml @@ -33,7 +33,6 @@ wit-bindgen = { workspace = true } ulid = { workspace = true } indexmap = { workspace = true } serde = { workspace = true } -async-trait = { workspace = true } [features] client = [] diff --git a/guest/rust/api_core/src/animation.rs b/guest/rust/api_core/src/animation.rs index 9c8543bb59..2d2ce80c13 100644 --- a/guest/rust/api_core/src/animation.rs +++ b/guest/rust/api_core/src/animation.rs @@ -8,7 +8,8 @@ use crate::{ app::components::{name, ref_count}, ecs::components::{children, parent}, }, - prelude::{epoch_time, Entity, EntityId, World, WorldExt}, + ecs, + prelude::{epoch_time, Entity, EntityId, World}, }; /// This plays animations, and can handle blending and masking of animations together to create @@ -17,7 +18,7 @@ use crate::{ pub struct AnimationPlayer(pub EntityId); impl AnimationPlayer { /// Create a new animation player, with `root` as the currently playing node - pub fn new(world: &mut dyn World, root: impl AsRef) -> Self { + pub fn new(world: &mut World, root: impl AsRef) -> Self { let root: &AnimationNode = root.as_ref(); let player = Entity::new() .with(is_animation_player(), ()) @@ -27,20 +28,20 @@ impl AnimationPlayer { world.add_component(root.0, parent(), player); Self(player) } - fn root(&self, world: &dyn World) -> Option { - if let Some(children) = world.get_component(self.0, children()) { + fn root(&self, world: &World) -> Option { + if let Ok(children) = world.get_component(self.0, children()) { children.get(0).copied() } else { None } } - fn free_root(&self, world: &mut dyn World) { + fn free_root(&self, world: &mut World) { if let Some(root) = self.root(world) { world.remove_component(root, parent()); } } /// Replaces the current root node of the animation player with a new node - pub fn play(&self, world: &mut dyn World, node: impl AsRef) { + pub fn play(&self, world: &mut World, node: impl AsRef) { self.free_root(world); let new_root: &AnimationNode = node.as_ref(); world.add_component(self.0, children(), vec![new_root.0]); @@ -48,7 +49,7 @@ impl AnimationPlayer { } /// Despawn this animation player. /// Note that dropping this player won't despawn it automatically; only call this method will despawn it. - pub fn despawn(self, world: &mut dyn World) { + pub fn despawn(self, world: &mut World) { self.free_root(world); world.despawn(self.0); } @@ -63,7 +64,7 @@ impl AnimationNode { self.0 } /// Use an existing node - pub fn from_entity(world: &mut dyn World, entity: EntityId) -> Self { + pub fn from_entity(world: &mut World, entity: EntityId) -> Self { world.mutate_component(entity, ref_count(), |x| *x += 1); Self(entity) } @@ -86,7 +87,7 @@ impl AnimationNode { pub struct PlayClipFromUrlNode(pub AnimationNode); impl PlayClipFromUrlNode { /// Create a new node. - pub fn new(world: &mut dyn World, url: impl Into) -> Self { + pub fn new(world: &mut World, url: impl Into) -> Self { let node = Entity::new() .with(play_clip_from_url(), url.into()) .with(name(), "Play clip from URL".to_string()) @@ -97,23 +98,23 @@ impl PlayClipFromUrlNode { Self(AnimationNode(node)) } /// Use an existing node - pub fn from_entity(world: &mut dyn World, entity: EntityId) -> Self { + pub fn from_entity(world: &mut World, entity: EntityId) -> Self { Self(AnimationNode::from_entity(world, entity)) } /// Set if the animation should loop or not - pub fn looping(&self, world: &mut dyn World, value: bool) { + pub fn looping(&self, world: &mut World, value: bool) { world.add_component(self.0 .0, looping(), value); } /// Freeze the animation at time - pub fn freeze_at_time(&self, world: &mut dyn World, time: f32) { + pub fn freeze_at_time(&self, world: &mut World, time: f32) { world.add_component(self.0 .0, freeze_at_time(), time); } /// Freeze the animation at time = percentage * duration - pub fn freeze_at_percentage(&self, world: &mut dyn World, percentage: f32) { + pub fn freeze_at_percentage(&self, world: &mut World, percentage: f32) { world.add_component(self.0 .0, freeze_at_percentage(), percentage); } /// Set up retargeting - pub fn set_retargeting(&self, world: &mut dyn World, retargeting: AnimationRetargeting) { + pub fn set_retargeting(&self, world: &mut World, retargeting: AnimationRetargeting) { match retargeting { AnimationRetargeting::None => { world.remove_component(self.0 .0, retarget_model_from_url()); @@ -141,7 +142,7 @@ impl PlayClipFromUrlNode { /// character models which don't have the same pre-rotations we need to make sure they're up to sync /// /// I.e. this is mostly relevant for retargeting - pub fn apply_base_pose(&self, world: &mut dyn World, value: bool) { + pub fn apply_base_pose(&self, world: &mut World, value: bool) { if value { world.add_component(self.0 .0, apply_base_pose(), ()); } else { @@ -149,29 +150,29 @@ impl PlayClipFromUrlNode { } } /// Returns None if the duration hasn't been loaded yet - pub fn peek_clip_duration(&self, world: &dyn World) -> Option { + pub fn peek_clip_duration(&self, world: &World) -> ecs::Result { world.get_component(self.0 .0, clip_duration()) } /// Returns the duration of this clip. This is async because it needs to wait for the clip to load before the duration can be returned. - pub async fn clip_duration(&self, world: &dyn World) -> f32 { + pub async fn clip_duration(&self, world: &World) -> f32 { world .wait_for_component(self.0 .0, clip_duration()) .await .unwrap_or_default() } /// Returns None if the clip hasn't been loaded yet - pub fn peek_bind_ids(&self, world: &dyn World) -> Option> { + pub fn peek_bind_ids(&self, world: &World) -> ecs::Result> { world.get_component(self.0 .0, bind_ids()) } /// Returns the bind ids of this clip. This is async because it needs to wait for the clip to load before the bind ids can be returned. - pub async fn bind_ids(&self, world: &dyn World) -> Vec { + pub async fn bind_ids(&self, world: &World) -> Vec { world .wait_for_component(self.0 .0, bind_ids()) .await .unwrap_or_default() } /// Wait until the clip has been loaded - pub async fn wait_for_load(&self, world: &dyn World) { + pub async fn wait_for_load(&self, world: &World) { self.clip_duration(world).await; } } @@ -192,7 +193,7 @@ impl BlendNode { /// If the weight is 1, only the right animation will play. /// Values in between blend between the two animations. pub fn new( - world: &mut dyn World, + world: &mut World, left: impl AsRef, right: impl AsRef, weight: f32, @@ -210,7 +211,7 @@ impl BlendNode { Self(AnimationNode(node)) } /// Use an existing node - pub fn from_entity(world: &mut dyn World, entity: EntityId) -> Self { + pub fn from_entity(world: &mut World, entity: EntityId) -> Self { Self(AnimationNode::from_entity(world, entity)) } /// Set the weight of this blend node. @@ -218,14 +219,14 @@ impl BlendNode { /// If the weight is 0, only the left animation will play. /// If the weight is 1, only the right animation will play. /// Values in between blend between the two animations. - pub fn set_weight(&self, world: &mut dyn World, weight: f32) { + pub fn set_weight(&self, world: &mut World, weight: f32) { world.set_component(self.0 .0, blend(), weight); } /// Sets the mask of this blend node. /// /// For example `blend_node.set_mask(vec![("LeftLeg".to_string(), 1.)])` means /// that the LeftLeg is always controlled by the right animation. - pub fn set_mask(&self, world: &mut dyn World, weights: Vec<(BindId, f32)>) { + pub fn set_mask(&self, world: &mut World, weights: Vec<(BindId, f32)>) { let (bind_ids, weights): (Vec<_>, Vec<_>) = weights .into_iter() .map(|(a, b)| (a.as_str().to_string(), b)) @@ -234,7 +235,7 @@ impl BlendNode { world.add_component(self.0 .0, mask_weights(), weights); } /// Sets a mask value to all bones of a humanoids lower body - pub fn set_mask_humanoid_lower_body(&self, world: &mut dyn World, weight: f32) { + pub fn set_mask_humanoid_lower_body(&self, world: &mut World, weight: f32) { self.set_mask( world, BindId::HUMANOID_LOWER_BODY @@ -244,7 +245,7 @@ impl BlendNode { ); } /// Sets a mask value to all bones of a humanoids upper body - pub fn set_mask_humanoid_upper_body(&self, world: &mut dyn World, weight: f32) { + pub fn set_mask_humanoid_upper_body(&self, world: &mut World, weight: f32) { self.set_mask( world, BindId::HUMANOID_UPPER_BODY @@ -287,17 +288,13 @@ impl Default for AnimationRetargeting { } /// Get the bone entity from the bind_id; for example "LeftFoot" -pub fn get_bone_by_bind_id( - world: &dyn World, - entity: EntityId, - bind_id: &BindId, -) -> Option { - if let Some(bid) = world.get_component(entity, self::bind_id()) { +pub fn get_bone_by_bind_id(world: &World, entity: EntityId, bind_id: &BindId) -> Option { + if let Ok(bid) = world.get_component(entity, self::bind_id()) { if bid == bind_id.as_str() { return Some(entity); } } - if let Some(childs) = world.get_component(entity, children()) { + if let Ok(childs) = world.get_component(entity, children()) { for c in childs { if let Some(bid) = get_bone_by_bind_id(world, c, bind_id) { return Some(bid); diff --git a/guest/rust/api_core/src/client/audio.rs b/guest/rust/api_core/src/client/audio.rs index 7cc4f1f49b..20d95a4c49 100644 --- a/guest/rust/api_core/src/client/audio.rs +++ b/guest/rust/api_core/src/client/audio.rs @@ -4,11 +4,11 @@ use crate::{ audio::components::*, ecs::components::{children, parent}, }, - prelude::{Entity, EntityId, World, WorldExt}, + prelude::{Entity, EntityId, World}, }; /// stop the audio on the given entity -pub fn stop(world: &mut dyn World, entity: EntityId) { +pub fn stop(world: &mut World, entity: EntityId) { if world.exists(entity) { world.add_component(entity, stop_now(), ()); } else { @@ -23,7 +23,7 @@ pub struct SpatialAudioPlayer { pub player: EntityId, } impl SpatialAudioPlayer { - pub fn new(world: &mut dyn World) -> Self { + pub fn new(world: &mut World) -> Self { let player = Entity::new() .with(is_spatial_audio_player(), ()) .with(name(), "Spatial audio player".to_string()) @@ -31,23 +31,23 @@ impl SpatialAudioPlayer { Self { player } } - pub fn set_listener(&self, world: &mut dyn World, listener: EntityId) { + pub fn set_listener(&self, world: &mut World, listener: EntityId) { world.add_component(self.player, spatial_audio_listener(), listener); } - pub fn set_emitter(&self, world: &mut dyn World, emitter: EntityId) { + pub fn set_emitter(&self, world: &mut World, emitter: EntityId) { world.add_component(self.player, spatial_audio_emitter(), emitter); } - pub fn set_amplitude(&self, world: &mut dyn World, amp: f32) { + pub fn set_amplitude(&self, world: &mut World, amp: f32) { world.add_component(self.player, amplitude(), amp); } - pub fn set_looping(&self, world: &mut dyn World, val: bool) { + pub fn set_looping(&self, world: &mut World, val: bool) { world.add_component(self.player, looping(), val); } - pub fn play_sound_on_entity(&self, world: &mut dyn World, url: String, emitter: EntityId) { + pub fn play_sound_on_entity(&self, world: &mut World, url: String, emitter: EntityId) { world.add_component(self.player, spatial_audio_emitter(), emitter); world.add_component(self.player, audio_url(), url); world.add_component(self.player, play_now(), ()); @@ -62,7 +62,7 @@ pub struct AudioPlayer { } impl AudioPlayer { /// Create new audio player from URL - pub fn new(world: &mut dyn World) -> Self { + pub fn new(world: &mut World) -> Self { let player = Entity::new() .with(is_audio_player(), ()) .with(name(), "Audio player".to_string()) @@ -71,25 +71,25 @@ impl AudioPlayer { Self { entity: player } } /// Set the sound looping or not - pub fn set_looping(&self, world: &mut dyn World, val: bool) { + pub fn set_looping(&self, world: &mut World, val: bool) { world.add_component(self.entity, looping(), val); } /// Add a simple onepole lowpass filter to the sound with one param: roll off frequency - pub fn add_one_pole_lpf(&self, world: &mut dyn World, rolloff_freq: f32) { + pub fn add_one_pole_lpf(&self, world: &mut World, rolloff_freq: f32) { world.add_component(self.entity, onepole_lpf(), rolloff_freq); } /// Set the amp/volume of the sound 0.0 is 0%, 1.0 is 100% - pub fn set_amplitude(&self, world: &mut dyn World, amp: f32) { + pub fn set_amplitude(&self, world: &mut World, amp: f32) { world.add_component(self.entity, amplitude(), amp); } /// Set the panning of the sound -1.0 is 100% left, 1.0 is 100% right. - pub fn set_panning(&self, world: &mut dyn World, pan: f32) { + pub fn set_panning(&self, world: &mut World, pan: f32) { world.add_component(self.entity, panning(), pan); } /// Play the sound, this will generate a new entity that represents the playing sound. - pub fn play(&self, world: &mut dyn World, url: String) -> EntityId { + pub fn play(&self, world: &mut World, url: String) -> EntityId { world.add_component(self.entity, audio_url(), url); world.add_component(self.entity, play_now(), ()); let id = Entity::new() diff --git a/guest/rust/api_core/src/ecs.rs b/guest/rust/api_core/src/ecs.rs index 46ae130cfe..170caae3df 100644 --- a/guest/rust/api_core/src/ecs.rs +++ b/guest/rust/api_core/src/ecs.rs @@ -1,4 +1,4 @@ -use async_trait::async_trait; +#![allow(clippy::single_match)] use glam::{Mat4, Vec3}; pub use crate::internal::component::{ @@ -14,187 +14,300 @@ pub use crate::internal::component::{ pub use crate::internal::wit::component::Value as WitComponentValue; use crate::{ core::ecs::components::{children, parent}, + internal::HostWorld, prelude::{block_until, EntityId}, }; -/// Implemented by all things that can be used as an ECS world. -pub trait World: Sync + Send { +#[derive(Debug)] +/// An error that can occur when interacting with the ECS. +pub enum ECSError { + /// The entity does not have the component. + EntityDoesntHaveComponent, + /// The entity does not exist. + NoSuchEntity, +} + +/// A result that can occur when interacting with the ECS. +pub type Result = core::result::Result; + +/// An ECS world. +pub enum World { + #[doc(hidden)] + Host(HostWorld), +} + +impl World { /// Spawns an entity containing the `components`. /// - /// Returns `spawned_entity_uid`. - fn spawn(&mut self, components: &Entity) -> EntityId; + /// Returns `spawned_entity_id`. + pub fn spawn(&mut self, components: Entity) -> EntityId { + match self { + World::Host(w) => w.spawn(components), + } + } /// Despawns `entity` from the world. `entity` will not work with any other functions afterwards. /// /// Returns the data of the despawned entity, if it existed. - fn despawn(&mut self, entity: EntityId) -> Option; + pub fn despawn(&mut self, entity: EntityId) -> Option { + match self { + World::Host(w) => w.despawn(entity), + } + } /// Gets a list of world transforms relative to origin entity /// Origin can be null entity for a list of world transforms - fn get_transforms_relative_to(&self, list: &[EntityId], origin: EntityId) -> Vec; + pub fn get_transforms_relative_to(&self, list: &[EntityId], origin: EntityId) -> Vec { + match self { + World::Host(w) => w.get_transforms_relative_to(list, origin), + } + } /// Checks if the `entity` exists. - fn exists(&self, entity: EntityId) -> bool; + pub fn exists(&self, entity: EntityId) -> bool { + match self { + World::Host(w) => w.exists(entity), + } + } /// Gets all of the entities that have the given `component`. #[doc(hidden)] - fn get_all_untyped(&self, component: &dyn UntypedComponent) -> Vec; + pub fn get_all_untyped(&self, component: &dyn UntypedComponent) -> Vec { + match self { + World::Host(w) => w.get_all_untyped(component), + } + } /// Gets all of the entities within `radius` of `position`. - fn in_area(&self, position: Vec3, radius: f32) -> Vec; + pub fn in_area(&self, position: Vec3, radius: f32) -> Vec { + match self { + World::Host(w) => w.in_area(position, radius), + } + } /// Retrieves the component `component` for `entity` if it exists, or `None` if it doesn't. #[doc(hidden)] - fn get_component_untyped( + pub fn get_component_untyped( &self, entity: EntityId, component: &dyn UntypedComponent, - ) -> Option; + ) -> Result { + match self { + World::Host(w) => w.get_component_untyped(entity, component), + } + } /// Retrieves the components `components` for `entity`. Will return an empty `Entity` if no components are found. - fn get_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> Entity; + pub fn get_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> Entity { + match self { + World::Host(w) => w.get_components(entity, components), + } + } /// Retrieves all guest-visible components for `entity`. Will return an empty `Entity` if no components are found. /// /// Note that this may not be all of the components on the entity, as some components are not visible to the guest. - fn get_all_components(&self, entity: EntityId) -> Entity; + pub fn get_all_components(&self, entity: EntityId) -> Entity { + match self { + World::Host(w) => w.get_all_components(entity), + } + } /// Adds the component `component` for `entity` with `value`. Will replace an existing component if present. #[doc(hidden)] - fn add_component_untyped( + pub fn add_component_untyped( &mut self, entity: EntityId, component: &dyn UntypedComponent, value: ComponentValue, - ); + ) -> Result<()> { + match self { + World::Host(w) => w.add_component_untyped(entity, component, value), + } + } /// Adds the components `components` for `entity` with `value`. Will replace any existing components specified in `components`. - fn add_components(&mut self, entity: EntityId, components: Entity); + pub fn add_components(&mut self, entity: EntityId, components: Entity) -> Result<()> { + match self { + World::Host(w) => w.add_components(entity, components), + } + } /// Sets the component `component` for `entity` with `value`. #[doc(hidden)] - fn set_component_untyped( + pub fn set_component_untyped( &mut self, entity: EntityId, component: &dyn UntypedComponent, value: ComponentValue, - ); + ) -> Result<()> { + match self { + World::Host(w) => w.set_component_untyped(entity, component, value), + } + } /// Sets the components `components` for `entity` with `value`. - fn set_components(&mut self, entity: EntityId, components: Entity); + pub fn set_components(&mut self, entity: EntityId, components: Entity) { + match self { + World::Host(w) => w.set_components(entity, components), + } + } /// Checks if the `entity` has a `component`. #[doc(hidden)] - fn has_component_untyped(&self, entity: EntityId, component: &dyn UntypedComponent) -> bool; + pub fn has_component_untyped( + &self, + entity: EntityId, + component: &dyn UntypedComponent, + ) -> bool { + match self { + World::Host(w) => w.has_component_untyped(entity, component), + } + } /// Checks if the `entity` has `components`. - fn has_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> bool; + pub fn has_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> bool { + match self { + World::Host(w) => w.has_components(entity, components), + } + } /// Removes the `component` from `entity`. /// /// Does nothing if the component does not exist. #[doc(hidden)] - fn remove_component_untyped(&mut self, entity: EntityId, component: &dyn UntypedComponent); + pub fn remove_component_untyped(&mut self, entity: EntityId, component: &dyn UntypedComponent) { + match self { + World::Host(w) => w.remove_component_untyped(entity, component), + } + } /// Removes the `components` from `entity`. /// /// Does nothing if the component does not exist. - fn remove_components(&mut self, entity: EntityId, components: &[&dyn UntypedComponent]); + pub fn remove_components(&mut self, entity: EntityId, components: &[&dyn UntypedComponent]) { + match self { + World::Host(w) => w.remove_components(entity, components), + } + } /// Gets the resource entity. The components of this entity contain global state for this ECS world. /// /// Components with the `Resource` attribute can be found here. - fn resources(&self) -> EntityId; + pub fn resources(&self) -> EntityId { + match self { + World::Host(w) => w.resources(), + } + } /// Gets the synchronized resource entity. The components of this entity contain global state that should be networked, but not persisted. - fn synchronized_resources(&self) -> EntityId; + pub fn synchronized_resources(&self) -> EntityId { + match self { + World::Host(w) => w.synchronized_resources(), + } + } /// Gets the persisted resource entity. The components of this entity contain global state that should be networked and persisted. - fn persisted_resources(&self) -> EntityId; + pub fn persisted_resources(&self) -> EntityId { + match self { + World::Host(w) => w.persisted_resources(), + } + } } -#[async_trait] -/// Extension methods for [`Worldlike`]. -pub trait WorldExt: World { +impl World { /// Gets all of the entities that have the given `component`. - fn get_all(&self, component: Component) -> Vec { + pub fn get_all(&self, component: Component) -> Vec { self.get_all_untyped(&component) } /// Retrieves the component `component` for `entity` if it exists, or `None` if it doesn't. - fn get_component( + pub fn get_component( &self, entity: EntityId, component: Component, - ) -> Option { + ) -> Result { self.get_component_untyped(entity, &component) - .and_then(|x| T::from_value(x)) + .map(|x| T::from_value(x).unwrap()) } /// Retrieves the component `component` for `entity` if it exists, or `None` if it doesn't. - fn get(&self, entity: EntityId, component: Component) -> Option { + pub fn get(&self, entity: EntityId, component: Component) -> Result { self.get_component(entity, component) } #[doc(hidden)] - fn get_cloned( + pub fn get_cloned( &self, entity: EntityId, component: Component, - ) -> Option { + ) -> Result { self.get_component(entity, component) } /// Retrieves the `resource` if it exists and panics if it doesn't. - fn resource(&self, component: Component) -> T { + pub fn resource(&self, component: Component) -> T { self.get_component(self.resources(), component).unwrap() } /// Adds the component `component` for `entity` with `value`. Will replace an existing component if present. - fn add_component( + pub fn add_component( &mut self, entity: EntityId, component: Component, value: T, - ) { + ) -> Result<()> { self.add_component_untyped(entity, &component, value.into_value()) } /// Sets the component `component` for `entity` with `value`. - fn set_component( + pub fn set_component( &mut self, entity: EntityId, component: Component, value: T, - ) { + ) -> Result<()> { self.set_component_untyped(entity, &component, value.into_value()) } /// Sets the component `component` for `entity` with `value`. - fn set(&mut self, entity: EntityId, component: Component, value: T) { + pub fn set( + &mut self, + entity: EntityId, + component: Component, + value: T, + ) -> Result<()> { self.set_component(entity, component, value) } /// Checks if the `entity` has a `component`. - fn has_component(&self, entity: EntityId, component: Component) -> bool { + pub fn has_component( + &self, + entity: EntityId, + component: Component, + ) -> bool { self.has_component_untyped(entity, &component) } /// Removes the `component` from `entity`. /// /// Does nothing if the component does not exist. - fn remove_component(&mut self, entity: EntityId, component: Component) { + pub fn remove_component( + &mut self, + entity: EntityId, + component: Component, + ) { self.remove_component_untyped(entity, &component) } /// Waits until `id` has the `component`. If the entity was deleted the method returns None. - async fn wait_for_component( + pub async fn wait_for_component( &self, entity: EntityId, component: Component, - ) -> Option { + ) -> Result { block_until(move || !self.exists(entity) || self.has_component(entity, component)).await; self.get_component(entity, component) } /// Despawns `entity` and all of its children. - fn despawn_recursive(&mut self, entity: EntityId) { + pub fn despawn_recursive(&mut self, entity: EntityId) { if let Some(res) = self.despawn(entity) { if let Some(children) = res.get_ref(children()) { for c in children { @@ -208,19 +321,19 @@ pub trait WorldExt: World { /// /// This will not set the component if the value is the same, which will prevent change events from /// being unnecessarily fired. - fn mutate_component( + pub fn mutate_component( &mut self, entity: EntityId, component: Component, mutator: impl FnOnce(&mut T), - ) -> Option { + ) -> Result { let mut value: T = self.get_component(entity, component)?; let orig_value = value.clone(); mutator(&mut value); if value != orig_value { self.set_component(entity, component, value.clone()); } - Some(value) + Ok(value) } /// Mutates the component `component` for `entity` using the passed in `mutator`, or sets it @@ -228,7 +341,7 @@ pub trait WorldExt: World { /// /// This will not set the component if the value is the same, which will prevent change events from /// being unnecessarily fired. - fn mutate_component_with_default( + pub fn mutate_component_with_default( &mut self, entity: EntityId, component: Component, @@ -236,7 +349,7 @@ pub trait WorldExt: World { mutator: impl FnOnce(&mut T), ) -> T { let value = self.mutate_component(entity, component, mutator); - if let Some(value) = value { + if let Ok(value) = value { value } else { self.add_component(entity, component, default.clone()); @@ -245,7 +358,7 @@ pub trait WorldExt: World { } /// Adds `child` as a child to `entity`. - fn add_child(&mut self, entity: EntityId, child: EntityId) { + pub fn add_child(&mut self, entity: EntityId, child: EntityId) { if self.has_component(entity, children()) { self.mutate_component(entity, children(), |children| children.push(child)); } else { @@ -255,7 +368,7 @@ pub trait WorldExt: World { } /// Removes `child` as a child to `entity`. - fn remove_child(&mut self, entity: EntityId, child: EntityId) { + pub fn remove_child(&mut self, entity: EntityId, child: EntityId) { if self.has_component(entity, children()) { self.mutate_component(entity, children(), |children| { children.retain(|x| *x != child) @@ -264,4 +377,3 @@ pub trait WorldExt: World { self.remove_component(child, parent()); } } -impl WorldExt for T {} diff --git a/guest/rust/api_core/src/global/runtime.rs b/guest/rust/api_core/src/global/runtime.rs index 6ce8a13646..f144141145 100644 --- a/guest/rust/api_core/src/global/runtime.rs +++ b/guest/rust/api_core/src/global/runtime.rs @@ -10,25 +10,25 @@ use crate::{ core::app, global::{OkEmpty, ResultEmpty}, internal::executor::EXECUTOR, - prelude::{RuntimeMessage, World, WorldExt}, + prelude::{RuntimeMessage, World}, }; /// The time, relative to the start of the game. Guaranteed to be monotonic. -pub fn game_time(world: &dyn World) -> Duration { +pub fn game_time(world: &World) -> Duration { world .get_component(world.resources(), app::components::game_time()) .unwrap() } /// The time, relative to Jan 1, 1970. Not guaranteed to be monotonic. Use [game_time] for most applications. -pub fn epoch_time(world: &dyn World) -> Duration { +pub fn epoch_time(world: &World) -> Duration { world .get_component(world.resources(), app::components::epoch_time()) .unwrap() } /// The length of the previous frame, in seconds. -pub fn delta_time(world: &dyn World) -> f32 { +pub fn delta_time(world: &World) -> f32 { world .get_component(world.resources(), app::components::delta_time()) .unwrap() diff --git a/guest/rust/api_core/src/global/shapes.rs b/guest/rust/api_core/src/global/shapes.rs index 7d14f2ef0b..6a11670a81 100644 --- a/guest/rust/api_core/src/global/shapes.rs +++ b/guest/rust/api_core/src/global/shapes.rs @@ -1,8 +1,9 @@ use super::EntityId; use crate::{ core::transform::components::local_to_world, + ecs, internal::{conversion::FromBindgen, wit}, - prelude::{World, WorldExt}, + prelude::World, }; use glam::{vec3, Vec3}; @@ -16,12 +17,12 @@ pub struct Ray { } impl Ray { /// This creates a ray from a cameras view matrix (i.e. from `local_to_world` of a camera entity). - pub fn from_camera_view_matrix(world: &dyn World, camera: EntityId) -> Option { + pub fn from_camera_view_matrix(world: &World, camera: EntityId) -> ecs::Result { let mat4 = world.get_component(camera, local_to_world())?; let origin = mat4.project_point3(Vec3::ZERO); let end = mat4.project_point3(vec3(0., 0., 1.)); let dir = (end - origin).normalize(); - Some(Ray { origin, dir }) + Ok(Ray { origin, dir }) } } diff --git a/guest/rust/api_core/src/internal/component/entity.rs b/guest/rust/api_core/src/internal/component/entity.rs index d33ca6545d..56047cb64f 100644 --- a/guest/rust/api_core/src/internal/component/entity.rs +++ b/guest/rust/api_core/src/internal/component/entity.rs @@ -71,8 +71,8 @@ impl Entity { /// Spawns an entity with these components. /// /// Returns `spawned_entity_id`. - pub fn spawn(&self, world: &mut dyn World) -> crate::prelude::EntityId { - world.spawn(self) + pub fn spawn(&self, world: &mut World) -> crate::prelude::EntityId { + world.spawn(self.clone()) } } impl FromBindgen for wit::component::Entity { diff --git a/guest/rust/api_core/src/internal/generated.rs b/guest/rust/api_core/src/internal/generated.rs index ed33febaee..f837189167 100644 --- a/guest/rust/api_core/src/internal/generated.rs +++ b/guest/rust/api_core/src/internal/generated.rs @@ -531,7 +531,7 @@ mod raw { ) } #[doc = "Checks if the entity is a *Camera*.\n\nBase components for a camera. You will need other components to make a fully-functioning camera.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n}\n```\n"] - pub fn is_camera(world: &dyn crate::prelude::World, id: EntityId) -> bool { + pub fn is_camera(world: &crate::prelude::World, id: EntityId) -> bool { crate::ambient_core::transform::concepts::is_transformable(world, id) && world.has_components( id, @@ -577,7 +577,7 @@ mod raw { } #[doc = "Checks if the entity is a *Perspective Common Camera*.\n\nBase components for a perspective camera. Consider `perspective_camera` or `perspective_infinite_reverse_camera`.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::fovy\": f32 = 1.0,\n \"ambient_core::camera::aspect_ratio\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n}\n```\n"] pub fn is_perspective_common_camera( - world: &dyn crate::prelude::World, + world: &crate::prelude::World, id: EntityId, ) -> bool { crate::ambient_core::camera::concepts::is_camera(world, id) @@ -608,10 +608,7 @@ mod raw { .with(crate::ambient_core::camera::components::far(), 1000f32) } #[doc = "Checks if the entity is a *Perspective Camera*.\n\nA perspective camera.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::perspective\": () = (),\n \"ambient_core::camera::far\": f32 = 1000.0,\n \"ambient_core::camera::perspective_common_camera\": { // Concept.\n \"ambient_core::camera::fovy\": f32 = 1.0,\n \"ambient_core::camera::aspect_ratio\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n },\n}\n```\n"] - pub fn is_perspective_camera( - world: &dyn crate::prelude::World, - id: EntityId, - ) -> bool { + pub fn is_perspective_camera(world: &crate::prelude::World, id: EntityId) -> bool { crate::ambient_core::camera::concepts::is_perspective_common_camera(world, id) && world.has_components( id, @@ -643,7 +640,7 @@ mod raw { } #[doc = "Checks if the entity is a *Perspective-Infinite-Reverse Camera*.\n\nA perspective-infinite-reverse camera. This is recommended for most use-cases.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::perspective_infinite_reverse\": () = (),\n \"ambient_core::camera::perspective_common_camera\": { // Concept.\n \"ambient_core::camera::fovy\": f32 = 1.0,\n \"ambient_core::camera::aspect_ratio\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n },\n}\n```\n"] pub fn is_perspective_infinite_reverse_camera( - world: &dyn crate::prelude::World, + world: &crate::prelude::World, id: EntityId, ) -> bool { crate :: ambient_core :: camera :: concepts :: is_perspective_common_camera (world , id) && world . has_components (id , & [& crate :: ambient_core :: camera :: components :: perspective_infinite_reverse ()]) @@ -679,10 +676,7 @@ mod raw { .with(crate::ambient_core::camera::components::far(), 1f32) } #[doc = "Checks if the entity is a *Orthographic Camera*.\n\nAn orthographic camera.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::camera::orthographic\": () = (),\n \"ambient_core::camera::orthographic_left\": f32 = -1.0,\n \"ambient_core::camera::orthographic_right\": f32 = 1.0,\n \"ambient_core::camera::orthographic_top\": f32 = 1.0,\n \"ambient_core::camera::orthographic_bottom\": f32 = -1.0,\n \"ambient_core::camera::near\": f32 = -1.0,\n \"ambient_core::camera::far\": f32 = 1.0,\n \"ambient_core::camera::camera\": { // Concept.\n \"ambient_core::camera::near\": f32 = 0.1,\n \"ambient_core::camera::projection\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::projection_view\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::camera::active_camera\": f32 = 0.0,\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::inv_local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n \"ambient_core::transform::transformable\": { // Concept.\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n },\n },\n}\n```\n"] - pub fn is_orthographic_camera( - world: &dyn crate::prelude::World, - id: EntityId, - ) -> bool { + pub fn is_orthographic_camera(world: &crate::prelude::World, id: EntityId) -> bool { crate::ambient_core::camera::concepts::is_camera(world, id) && world.has_components( id, @@ -1806,7 +1800,7 @@ mod raw { ) } #[doc = "Checks if the entity is a *Sphere*.\n\nA primitive sphere.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::primitives::sphere\": () = (),\n \"ambient_core::primitives::sphere_radius\": f32 = 0.5,\n \"ambient_core::primitives::sphere_sectors\": u32 = 36,\n \"ambient_core::primitives::sphere_stacks\": u32 = 18,\n}\n```\n"] - pub fn is_sphere(world: &dyn crate::prelude::World, id: EntityId) -> bool { + pub fn is_sphere(world: &crate::prelude::World, id: EntityId) -> bool { world.has_components( id, &[ @@ -1859,7 +1853,7 @@ mod raw { ) } #[doc = "Checks if the entity is a *Capsule*.\n\nA primitive capsule. Defined as a cylinder capped by hemispheres.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::primitives::capsule\": () = (),\n \"ambient_core::primitives::capsule_radius\": f32 = 0.5,\n \"ambient_core::primitives::capsule_half_height\": f32 = 0.5,\n \"ambient_core::primitives::capsule_rings\": u32 = 0,\n \"ambient_core::primitives::capsule_latitudes\": u32 = 16,\n \"ambient_core::primitives::capsule_longitudes\": u32 = 32,\n}\n```\n"] - pub fn is_capsule(world: &dyn crate::prelude::World, id: EntityId) -> bool { + pub fn is_capsule(world: &crate::prelude::World, id: EntityId) -> bool { world.has_components( id, &[ @@ -1914,7 +1908,7 @@ mod raw { ) } #[doc = "Checks if the entity is a *Torus*.\n\nA primitive Torus, surface of revolution generated by revolving a circle in three-dimensional space one full revolution.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::primitives::torus\": () = (),\n \"ambient_core::primitives::torus_inner_radius\": f32 = 0.25,\n \"ambient_core::primitives::torus_outer_radius\": f32 = 0.35,\n \"ambient_core::primitives::torus_slices\": u32 = 32,\n \"ambient_core::primitives::torus_loops\": u32 = 16,\n}\n```\n"] - pub fn is_torus(world: &dyn crate::prelude::World, id: EntityId) -> bool { + pub fn is_torus(world: &crate::prelude::World, id: EntityId) -> bool { world.has_components( id, &[ @@ -2477,7 +2471,7 @@ mod raw { ) } #[doc = "Checks if the entity is a *Transformable*.\n\nCan be translated, rotated and scaled.\n\n*Definition*:\n\n```ignore\n{\n \"ambient_core::transform::translation\": Vec3 = Vec3(0.0, 0.0, 0.0),\n \"ambient_core::transform::rotation\": Quat = Quat(0.0, 0.0, 0.0, 1.0),\n \"ambient_core::transform::scale\": Vec3 = Vec3(1.0, 1.0, 1.0),\n \"ambient_core::transform::local_to_world\": Mat4 = Mat4 { x_axis: Vec4(1.0, 0.0, 0.0, 0.0), y_axis: Vec4(0.0, 1.0, 0.0, 0.0), z_axis: Vec4(0.0, 0.0, 1.0, 0.0), w_axis: Vec4(0.0, 0.0, 0.0, 1.0) },\n}\n```\n"] - pub fn is_transformable(world: &dyn crate::prelude::World, id: EntityId) -> bool { + pub fn is_transformable(world: &crate::prelude::World, id: EntityId) -> bool { world.has_components( id, &[ diff --git a/guest/rust/api_core/src/internal/mod.rs b/guest/rust/api_core/src/internal/mod.rs index 23965920e1..a1e16f2fab 100644 --- a/guest/rust/api_core/src/internal/mod.rs +++ b/guest/rust/api_core/src/internal/mod.rs @@ -11,17 +11,19 @@ mod world; use crate::{ecs::World, internal::executor::EXECUTOR}; use wit::{__link_section, exports, guest}; + +pub(crate) use self::world::HostWorld; wit::export_bindings!(Guest); extern "Rust" { - fn main(worldlike: &mut dyn World); + fn main(world: &mut World); } struct Guest; impl guest::Guest for Guest { fn init() { once_cell::sync::Lazy::force(&EXECUTOR); - unsafe { main(&mut world::DefaultWorld) }; + unsafe { main(&mut World::Host(HostWorld)) }; } fn exec(source: guest::Source, message_name: String, message_data: Vec) { diff --git a/guest/rust/api_core/src/internal/world.rs b/guest/rust/api_core/src/internal/world.rs index b99852f72a..82c9a0a225 100644 --- a/guest/rust/api_core/src/internal/world.rs +++ b/guest/rust/api_core/src/internal/world.rs @@ -1,27 +1,27 @@ use glam::Mat4; use crate::{ - ecs::{ComponentValue, World}, + ecs::{ComponentValue, Result}, global::{EntityId, Vec3}, internal::{ component::{Entity, UntypedComponent}, conversion::{FromBindgen, IntoBindgen}, wit, }, + prelude::ECSError, }; -pub struct DefaultWorld; - -impl World for DefaultWorld { - fn spawn(&mut self, components: &Entity) -> EntityId { - wit::entity::spawn(&components.clone().into_bindgen()).from_bindgen() +pub struct HostWorld; +impl HostWorld { + pub fn spawn(&mut self, components: Entity) -> EntityId { + wit::entity::spawn(&components.into_bindgen()).from_bindgen() } - fn despawn(&mut self, entity: EntityId) -> Option { + pub fn despawn(&mut self, entity: EntityId) -> Option { wit::entity::despawn(entity.into_bindgen()).from_bindgen() } - fn get_transforms_relative_to(&self, list: &[EntityId], origin: EntityId) -> Vec { + pub fn get_transforms_relative_to(&self, list: &[EntityId], origin: EntityId) -> Vec { wit::entity::get_transforms_relative_to( &list.iter().map(|x| x.into_bindgen()).collect::>(), origin.into_bindgen(), @@ -29,96 +29,106 @@ impl World for DefaultWorld { .from_bindgen() } - fn exists(&self, entity: EntityId) -> bool { + pub fn exists(&self, entity: EntityId) -> bool { wit::entity::exists(entity.into_bindgen()) } - fn get_all_untyped(&self, component: &dyn UntypedComponent) -> Vec { + pub fn get_all_untyped(&self, component: &dyn UntypedComponent) -> Vec { wit::entity::get_all(component.index()).from_bindgen() } - fn in_area(&self, position: Vec3, radius: f32) -> Vec { + pub fn in_area(&self, position: Vec3, radius: f32) -> Vec { wit::entity::in_area(position.into_bindgen(), radius).from_bindgen() } - fn get_component_untyped( + pub fn get_component_untyped( &self, entity: EntityId, component: &dyn UntypedComponent, - ) -> Option { - wit::component::get_component(entity.into_bindgen(), component.index()).from_bindgen() + ) -> Result { + // TODO: Need to actually get a meaningful error + wit::component::get_component(entity.into_bindgen(), component.index()) + .from_bindgen() + .ok_or(ECSError::NoSuchEntity) } - fn get_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> Entity { + pub fn get_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> Entity { let components: Vec<_> = components.iter().map(|c| c.index()).collect(); wit::component::get_components(entity.into_bindgen(), &components).from_bindgen() } - fn get_all_components(&self, entity: EntityId) -> Entity { + pub fn get_all_components(&self, entity: EntityId) -> Entity { wit::component::get_all_components(entity.into_bindgen()).from_bindgen() } - fn add_component_untyped( + pub fn add_component_untyped( &mut self, entity: EntityId, component: &dyn UntypedComponent, value: ComponentValue, - ) { + ) -> Result<()> { wit::component::add_component( entity.into_bindgen(), component.index(), &value.into_bindgen(), - ) + ); + Ok(()) } - fn add_components(&mut self, entity: EntityId, components: Entity) { - wit::component::add_components(entity.into_bindgen(), &components.into_bindgen()) + pub fn add_components(&mut self, entity: EntityId, components: Entity) -> Result<()> { + wit::component::add_components(entity.into_bindgen(), &components.into_bindgen()); + Ok(()) } - fn set_component_untyped( + pub fn set_component_untyped( &mut self, entity: EntityId, component: &dyn UntypedComponent, value: ComponentValue, - ) { + ) -> Result<()> { wit::component::set_component( entity.into_bindgen(), component.index(), &value.into_bindgen(), - ) + ); + Ok(()) } - fn set_components(&mut self, entity: EntityId, components: Entity) { + pub fn set_components(&mut self, entity: EntityId, components: Entity) { wit::component::set_components(entity.into_bindgen(), &components.into_bindgen()) } - fn has_component_untyped(&self, entity: EntityId, component: &dyn UntypedComponent) -> bool { + pub fn has_component_untyped( + &self, + entity: EntityId, + component: &dyn UntypedComponent, + ) -> bool { wit::component::has_component(entity.into_bindgen(), component.index()) } - fn has_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> bool { + pub fn has_components(&self, entity: EntityId, components: &[&dyn UntypedComponent]) -> bool { let components: Vec<_> = components.iter().map(|c| c.index()).collect(); wit::component::has_components(entity.into_bindgen(), &components) } - fn remove_component_untyped(&mut self, entity: EntityId, component: &dyn UntypedComponent) { + pub fn remove_component_untyped(&mut self, entity: EntityId, component: &dyn UntypedComponent) { wit::component::remove_component(entity.into_bindgen(), component.index()) } - fn remove_components(&mut self, entity: EntityId, components: &[&dyn UntypedComponent]) { + pub fn remove_components(&mut self, entity: EntityId, components: &[&dyn UntypedComponent]) { let components: Vec<_> = components.iter().map(|c| c.index()).collect(); wit::component::remove_components(entity.into_bindgen(), &components) } - fn resources(&self) -> EntityId { + pub fn resources(&self) -> EntityId { EntityId::resources() } - fn synchronized_resources(&self) -> EntityId { + pub fn synchronized_resources(&self) -> EntityId { wit::entity::synchronized_resources().from_bindgen() } - fn persisted_resources(&self) -> EntityId { + pub fn persisted_resources(&self) -> EntityId { wit::entity::persisted_resources().from_bindgen() } } diff --git a/guest/rust/api_core/src/prelude.rs b/guest/rust/api_core/src/prelude.rs index f6fa8d97c9..47b3a46ab6 100644 --- a/guest/rust/api_core/src/prelude.rs +++ b/guest/rust/api_core/src/prelude.rs @@ -1,8 +1,8 @@ pub use crate::{ asset, ecs::{ - change_query, despawn_query, query, spawn_query, Component, Entity, QueryEvent, World, - WorldExt, + change_query, despawn_query, query, spawn_query, Component, ECSError, Entity, QueryEvent, + World, }, global::*, main, message, diff --git a/guest/rust/examples/basics/asset_loading/src/client.rs b/guest/rust/examples/basics/asset_loading/src/client.rs index f477571245..667b3121fa 100644 --- a/guest/rust/examples/basics/asset_loading/src/client.rs +++ b/guest/rust/examples/basics/asset_loading/src/client.rs @@ -1,7 +1,7 @@ use ambient_api::prelude::*; #[main] -fn main(_world: &mut dyn World) { +fn main(_world: &mut World) { // Load the asset println!( "asset url can be accessed from client: {}", diff --git a/guest/rust/examples/basics/asset_loading/src/server.rs b/guest/rust/examples/basics/asset_loading/src/server.rs index 589f3babf2..2525c6189d 100644 --- a/guest/rust/examples/basics/asset_loading/src/server.rs +++ b/guest/rust/examples/basics/asset_loading/src/server.rs @@ -23,7 +23,7 @@ use ambient_api::{ use packages::this::{assets, components::is_the_best}; #[main] -pub async fn main(world: &mut dyn World) { +pub async fn main(world: &mut World) { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with(aspect_ratio_from_window(), EntityId::resources()) diff --git a/guest/rust/examples/basics/physics/src/client.rs b/guest/rust/examples/basics/physics/src/client.rs index 6ea9c0fee1..3541410618 100644 --- a/guest/rust/examples/basics/physics/src/client.rs +++ b/guest/rust/examples/basics/physics/src/client.rs @@ -2,7 +2,7 @@ use ambient_api::{ecs::World, prelude::*}; use packages::this::{assets, messages::Bonk}; #[main] -pub fn main(world: &mut dyn World) { +pub fn main(world: &mut World) { let spatial_audio_player = audio::SpatialAudioPlayer::new(); Bonk::subscribe(move |_source, data| { diff --git a/shared_crates/element/src/hooks.rs b/shared_crates/element/src/hooks.rs index f78157a0c7..1cbf2f529c 100644 --- a/shared_crates/element/src/hooks.rs +++ b/shared_crates/element/src/hooks.rs @@ -34,6 +34,9 @@ use crate::{AnyCloneable, ElementTree, HookContext, InstanceId}; /// Helper type for a callback that sets some value. pub type Setter = Cb; +/// Helper type for a callback that sets some value using the world. +#[cfg(feature = "guest")] +pub type SetterWithWorld = Cb; type SpawnFn = Box DespawnFn + Sync + Send>; /// The return type of a function passed to [Hooks::use_spawn]. This function is called @@ -230,9 +233,9 @@ impl<'a> Hooks<'a> { { let handler = self.use_ref_with(|_| None); *handler.lock() = Some(cb(func)); - self.use_effect((), move |_, _| { + self.use_effect((), move |world, _| { let listener = T::subscribe(move |event| { - (handler.lock().as_ref().unwrap())(&mut World, &event); + (handler.lock().as_ref().unwrap())(world, &event); }); |_| listener.stop() }); @@ -249,9 +252,9 @@ impl<'a> Hooks<'a> { ) { let handler = self.use_ref_with(|_| None); *handler.lock() = Some(cb(func)); - self.use_effect((), move |_, _| { + self.use_effect((), move |world, _| { let listener = T::subscribe(move |source, event| { - (handler.lock().as_ref().unwrap())(&mut World, source, &event); + (handler.lock().as_ref().unwrap())(world, source, &event); }); |_| listener.stop() }); @@ -600,8 +603,8 @@ impl<'a> Hooks<'a> { &mut self, id: EntityId, component: ambient_guest_bridge::api::ecs::Component, - ) -> (Option, Setter) { - use ambient_guest_bridge::api::prelude::{change_query, entity}; + ) -> (Option, SetterWithWorld) { + use ambient_guest_bridge::api::prelude::change_query; let refresh = self.use_rerender_signal(); self.use_spawn(move |_| { @@ -615,8 +618,10 @@ impl<'a> Hooks<'a> { }); ( - entity::get_component(id, component), - cb(move |value| entity::add_component(id, component, value)), + self.world.get(id, component).ok(), + cb(move |world, value| { + world.add_component(id, component, value); + }), ) } @@ -636,10 +641,8 @@ impl<'a> Hooks<'a> { >( &mut self, component: ambient_guest_bridge::api::ecs::Component, - ) -> (Option, Setter) { - use ambient_guest_bridge::api::entity; - - self.use_entity_component(entity::resources(), component) + ) -> (Option, SetterWithWorld) { + self.use_entity_component(self.world.resources(), component) } /// Run `cb` every `seconds` seconds. diff --git a/shared_crates/element/src/lib.rs b/shared_crates/element/src/lib.rs index a060b8a31d..f9369dbe88 100644 --- a/shared_crates/element/src/lib.rs +++ b/shared_crates/element/src/lib.rs @@ -274,18 +274,11 @@ impl Element { ); world.spawn(entity) } - /// This spawns the elemet tree and returns it. The tree won't be automatically updated, but can manually be updated + /// This spawns the element tree and returns it. The tree won't be automatically updated, but can manually be updated /// by calling the `update` method. - #[cfg(feature = "native")] pub fn spawn_tree(self, world: &mut World) -> ElementTree { ElementTree::new(world, self) } - /// This spawns the elemet tree and returns it. The tree won't be automatically updated, but can manually be updated - /// by calling the `update` method. - #[cfg(feature = "guest")] - pub fn spawn_tree(self) -> ElementTree { - ElementTree::new(&mut World, self) - } /// This spawns the element tree and sets up listeners to automatically update it. /// /// This is equivalent to calling [Self::spawn_tree] and then calling [ElementTree::update] on the tree each frame. @@ -301,14 +294,14 @@ impl Element { /// }); /// ``` #[cfg(feature = "guest")] - pub fn spawn_interactive(self) { + pub fn spawn_interactive(self, world: &mut World) { use ambient_guest_bridge::api::{ core::messages::Frame, message::RuntimeMessage, prelude::OkEmpty, }; - let mut tree = self.spawn_tree(); + let mut tree = self.spawn_tree(world); Frame::subscribe(move |_| { - tree.update(&mut World); + tree.update(world); OkEmpty }); } diff --git a/shared_crates/guest_bridge/src/guest.rs b/shared_crates/guest_bridge/src/guest.rs index 436668e168..e0a95eb876 100644 --- a/shared_crates/guest_bridge/src/guest.rs +++ b/shared_crates/guest_bridge/src/guest.rs @@ -31,16 +31,10 @@ pub async fn sleep(seconds: f32) { pub mod ecs { use super::api; pub use api::{ - ecs::{Component, SupportedValue as ComponentValue, UntypedComponent, World, WorldExt}, + ecs::{Component, ECSError, SupportedValue as ComponentValue, UntypedComponent, World}, prelude::{Entity, EntityId}, }; - #[derive(Debug)] - pub enum ECSError { - EntityDoesntHaveComponent, - NoSuchEntity, - } - pub struct ComponentDesc(Box); impl ComponentDesc { pub fn index(&self) -> u32 { diff --git a/shared_crates/package_macro_common/src/concepts.rs b/shared_crates/package_macro_common/src/concepts.rs index 7db471d62d..fa8d392272 100644 --- a/shared_crates/package_macro_common/src/concepts.rs +++ b/shared_crates/package_macro_common/src/concepts.rs @@ -150,7 +150,7 @@ fn generate_is( let api_path = context.guest_api_path().unwrap(); quote! { #[doc = #is_comment] - pub fn #is_ident(world: &dyn #api_path::prelude::World, id: EntityId) -> bool { + pub fn #is_ident(world: &#api_path::prelude::World, id: EntityId) -> bool { #(#extends(world, id) && )* world.has_components(id, &[ #(&#components),* ])