diff --git a/.vscode/launch.json b/.vscode/launch.json index a8adeb3752..f50dc2b807 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,7 +20,8 @@ }, "args": [ "run", - "guest/rust/examples/image" + "guest/rust/examples/text", + "--debug" ], "cwd": "${workspaceFolder}", "sourceLanguages": [ diff --git a/Cargo.lock b/Cargo.lock index f48daa7340..e251d29935 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,34 @@ dependencies = [ "serde", ] +[[package]] +name = "ambient_api" +version = "0.1.1" +dependencies = [ + "ambient_api_macros", + "anyhow", + "bincode", + "data-encoding", + "glam 0.22.0", + "once_cell", + "paste", + "rand 0.8.5", + "serde", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ambient_api_macros" +version = "0.1.1" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "serde", + "syn", + "toml 0.7.2", +] + [[package]] name = "ambient_app" version = "0.1.1" @@ -549,14 +577,15 @@ name = "ambient_element" version = "0.1.1" dependencies = [ "ambient_core", - "ambient_ecs", "ambient_element_component", - "ambient_std", + "ambient_guest_bridge", "ambient_sys", "as-any", "atomic_refcell", + "cb", "derivative", "dyn-clonable", + "friendly_id", "futures", "itertools", "parking_lot", @@ -624,6 +653,18 @@ dependencies = [ "winit", ] +[[package]] +name = "ambient_guest_bridge" +version = "0.1.0" +dependencies = [ + "ambient_api", + "ambient_core", + "ambient_ecs", + "ambient_layout", + "ambient_renderer", + "ambient_text", +] + [[package]] name = "ambient_input" version = "0.1.1" @@ -659,6 +700,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "ambient_layout" +version = "0.1.0" +dependencies = [ + "ambient_core", + "ambient_ecs", + "ambient_input", + "glam 0.22.0", + "itertools", + "log", +] + [[package]] name = "ambient_meshes" version = "0.1.1" @@ -980,9 +1033,11 @@ dependencies = [ "async-trait", "bincode", "bytemuck", + "cb", "chrono", "convert_case 0.6.0", "data-encoding", + "friendly_id", "futures", "glam 0.22.0", "hex", @@ -1076,6 +1131,26 @@ dependencies = [ "wgpu 0.14.2", ] +[[package]] +name = "ambient_text" +version = "0.1.0" +dependencies = [ + "ambient_core", + "ambient_ecs", + "ambient_gpu", + "ambient_input", + "ambient_layout", + "ambient_renderer", + "ambient_std", + "anyhow", + "async-trait", + "glam 0.22.0", + "glyph_brush", + "log", + "parking_lot", + "wgpu 0.14.2", +] + [[package]] name = "ambient_ui" version = "0.1.1" @@ -1088,10 +1163,13 @@ dependencies = [ "ambient_element", "ambient_gpu", "ambient_input", + "ambient_layout", "ambient_meshes", "ambient_renderer", "ambient_std", "ambient_sys", + "ambient_text", + "ambient_ui_components", "anyhow", "arboard", "async-trait", @@ -1102,7 +1180,6 @@ dependencies = [ "fixed-vec-deque", "futures", "glam 0.22.0", - "glyph_brush", "indexmap", "itertools", "log", @@ -1118,6 +1195,15 @@ dependencies = [ "winit", ] +[[package]] +name = "ambient_ui_components" +version = "0.1.0" +dependencies = [ + "ambient_element", + "ambient_guest_bridge", + "glam 0.22.0", +] + [[package]] name = "ambient_wasm" version = "0.1.1" @@ -1757,6 +1843,10 @@ dependencies = [ "toml 0.7.2", ] +[[package]] +name = "cb" +version = "0.1.0" + [[package]] name = "cc" version = "1.0.79" @@ -2763,6 +2853,13 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "friendly_id" +version = "0.1.0" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "fs-set-times" version = "0.17.1" diff --git a/app/src/client/mod.rs b/app/src/client/mod.rs index 7942aae44f..7691f23202 100644 --- a/app/src/client/mod.rs +++ b/app/src/client/mod.rs @@ -44,8 +44,8 @@ fn MainApp( ) -> Element { let resolution = use_window_physical_resolution(hooks); - hooks.provide_context(GameClientNetworkStats::default); - hooks.provide_context(GameClientServerStats::default); + let update_network_stats = hooks.provide_context(GameClientNetworkStats::default); + let update_server_stats = hooks.provide_context(GameClientServerStats::default); *hooks.world.resource_mut(window_title()) = "Ambient".to_string(); @@ -65,6 +65,8 @@ fn MainApp( }))), on_loaded: cb(move |_game_state, _game_client| Ok(Box::new(|| {}))), error_view: cb(move |error| Dock(vec![Text::el("Error").header_style(), Text::el(error)]).el()), + on_network_stats: cb(move |stats| update_network_stats(stats)), + on_server_stats: cb(move |stats| update_server_stats(stats)), systems_and_resources: cb(|| (systems(), Entity::new())), create_rpc_registry: cb(shared::create_rpc_registry), on_in_entities: None, diff --git a/app/src/shared/player.rs b/app/src/shared/player.rs index 23216f9e9e..d851887dff 100644 --- a/app/src/shared/player.rs +++ b/app/src/shared/player.rs @@ -3,7 +3,7 @@ use std::{io::Write, sync::Arc}; use ambient_audio::AudioListener; use ambient_core::{ camera::{active_camera, aspect_ratio_from_window}, - main_scene, runtime, + runtime, }; use ambient_ecs::{query, query_mut, Entity, SystemGroup}; use ambient_element::{element_component, Element, Hooks}; @@ -106,7 +106,6 @@ pub fn client_systems() -> SystemGroup { id, Entity::new() .with(active_camera(), 0.) - .with(main_scene(), ()) .with(audio_listener(), Arc::new(Mutex::new(AudioListener::new(Mat4::IDENTITY, Vec3::X * 0.2)))) .with(aspect_ratio_from_window(), ()), ) diff --git a/crates/cameras/src/lib.rs b/crates/cameras/src/lib.rs index c3b6e41501..b04f345356 100644 --- a/crates/cameras/src/lib.rs +++ b/crates/cameras/src/lib.rs @@ -1,8 +1,8 @@ -use ambient_core::{camera::*, transform::*, ui_scene, window_logical_size}; -use ambient_ecs::{components, query_mut, Description, Name, Networked, Store, SystemGroup}; +use ambient_core::{camera::*, transform::*, ui_scene}; +use ambient_ecs::{components, Networked, Store, SystemGroup}; use ambient_element::{element_component, Element, Hooks}; use ambient_std::shapes::BoundingBox; -use glam::{Mat4, Quat, Vec3}; +use glam::{Quat, Vec3}; use winit::event::Event; use crate::{free::free_camera_system, spherical::spherical_camera_system}; @@ -13,8 +13,6 @@ pub mod spherical; components!("camera", { @[Networked, Store] camera_movement_speed: f32, - @[Networked, Store, Name["UI camera"], Description["This entity is a camera that is used to render UI.\nEnsure that you have the remaining camera components."]] - ui_camera: (), }); pub fn init_all_components() { @@ -24,26 +22,7 @@ pub fn init_all_components() { } pub fn assets_camera_systems() -> SystemGroup> { - SystemGroup::new( - "assets_camera_systems", - vec![Box::new(free_camera_system()), Box::new(spherical_camera_system()), Box::new(ui_camera_system())], - ) -} - -pub fn ui_camera_system() -> SystemGroup> { - SystemGroup::new( - "ui_camera_system", - vec![query_mut((orthographic_rect(), local_to_world()), (ui_camera(),)).to_system(|q, world, qs, _| { - let window_size = world.resource(window_logical_size()).as_vec2(); - for (_, (orth, ltw), (_,)) in q.iter(world, qs) { - *ltw = Mat4::from_translation((window_size / 2.).extend(0.)); - orth.left = -window_size.x / 2.; - orth.right = window_size.x / 2.; - orth.top = -window_size.y / 2.; - orth.bottom = window_size.y / 2.; - } - })], - ) + SystemGroup::new("assets_camera_systems", vec![Box::new(free_camera_system()), Box::new(spherical_camera_system())]) } #[element_component] @@ -53,12 +32,17 @@ pub fn UICamera(_: &mut Hooks) -> Element { .init_default(inv_local_to_world()) .init(near(), -1.) .init(far(), 1.0) + .init_default(orthographic()) + .init(orthographic_left(), 0.) + .init(orthographic_right(), 100.) + .init(orthographic_top(), 0.) + .init(orthographic_bottom(), 100.) .init(orthographic_rect(), OrthographicRect { left: 0.0, right: 100., top: 0., bottom: 100. }) .init_default(projection()) .init_default(projection_view()) .init_default(translation()) .init_default(rotation()) - .init_default(ui_camera()) + .init_default(orthographic_from_window()) .init_default(ui_scene()) } diff --git a/crates/core/src/camera.rs b/crates/core/src/camera.rs index a50dfa1e9b..4849700a54 100644 --- a/crates/core/src/camera.rs +++ b/crates/core/src/camera.rs @@ -1,6 +1,6 @@ use ambient_ecs::{ - components, query, query_mut, Component, Concept, Description, ECSError, Entity, EntityId, Name, Networked, RefConcept, Store, - SystemGroup, World, + components, query, query_mut, Component, Concept, Debuggable, Description, ECSError, Entity, EntityId, Name, Networked, RefConcept, + Store, SystemGroup, World, }; use ambient_std::{ math::Line, @@ -12,7 +12,7 @@ use ordered_float::OrderedFloat; use crate::{ transform::{inv_local_to_world, local_to_world}, - window_physical_size, + window_logical_size, window_physical_size, }; #[derive(Clone, Copy, Debug)] @@ -27,101 +27,108 @@ components!("camera", { // Orthographic orthographic_rect: OrthographicRect, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Orthographic projection"], Description["If attached, this camera will use a standard orthographic projection matrix.\nEnsure that the `orthographic_` components are set, including `left`, right`, `top` and `bottom`, as well as `near` and `far`."] ] orthographic: (), @[ - Networked, Store, + Networked, Store, Debuggable, Name["Orthographic left"], Description["The left bound for this `orthographic` camera."] ] orthographic_left: f32, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Orthographic right"], Description["The right bound for this `orthographic` camera."] ] orthographic_right: f32, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Orthographic top"], Description["The top bound for this `orthographic` camera."] ] orthographic_top: f32, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Orthographic bottom"], Description["The bottom bound for this `orthographic` camera."] ] orthographic_bottom: f32, + @[ + Networked, Store, Debuggable, + Name["Orthographic from window"], + Description["The bounds of this orthographic camera will be updated to match the window automatically."] + ] + orthographic_from_window: (), + // Perspective @[ - Networked, Store, + Networked, Store, Debuggable, Name["Perspective-infinite-reverse projection"], Description["If attached, this camera will use a perspective-infinite-reverse projection matrix.\nThis is well-suited for rendering large worlds as it has no far plane. Ensure `near` is set."] ] perspective_infinite_reverse: (), @[ - Networked, Store, + Networked, Store, Debuggable, Name["Perspective projection"], Description["If attached, this camera will use a standard perspective projection matrix.\nEnsure that `near` and `far` are set."] ] perspective: (), + + // Properties @[ - Networked, Store, + Networked, Store, Debuggable, Name["Near plane"], Description["The near plane of this camera, measured in meters."] ] - - // Properties near: f32, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Far plane"], Description["The far plane of this camera, measured in meters."] ] far: f32, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Field of View Y"], Description["The field of view of this camera in the Y/vertical direction, measured in radians."] ] fovy: f32, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Aspect ratio"], Description["The aspect ratio of this camera.\nIf `aspect_ratio_from_window` is set, this will be automatically updated to match the window."] ] aspect_ratio: f32, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Aspect ratio from window"], Description["If attached, the `aspect_ratio` component will be automatically updated to match the aspect ratio of the window."] ] aspect_ratio_from_window: (), @[ - Networked, Store, + Networked, Store, Debuggable, Name["Projection"], Description["The projection matrix of this camera.\nThis can be driven by other components, including `perspective` and `perspective_infinite_reverse`."] ] projection: glam::Mat4, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Projection-view"], Description["The composition of the projection and view (inverse-local-to-world) matrices."] ] projection_view: glam::Mat4, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Active camera"], Description["The camera with the highest `active_camera` value will be used for rendering."] ] active_camera: f32, @[ - Networked, Store, + Networked, Store, Debuggable, Name["Fog"], Description["If attached, this camera will see/render fog."] ] @@ -129,7 +136,7 @@ components!("camera", { // Shadows @[ - Networked, Store, + Networked, Store, Debuggable, Name["Shadows far plane"], Description["The far plane for the shadow camera, measured in meters."] ] @@ -143,7 +150,12 @@ pub fn concepts() -> Vec { name: "Camera", description: "Base components for a camera. You will need other components to make a fully-functioning camera.", extends: &["transformable"], - data: Entity::new().with(projection(), glam::Mat4::IDENTITY).with(projection_view(), glam::Mat4::IDENTITY).with(near(), 0.1), + data: Entity::new() + .with(projection(), glam::Mat4::IDENTITY) + .with(projection_view(), glam::Mat4::IDENTITY) + .with(near(), 0.1) + .with_default(local_to_world()) + .with_default(inv_local_to_world()), } .to_owned(), RefConcept { @@ -182,7 +194,8 @@ pub fn concepts() -> Vec { .with(orthographic_right(), 1.0) .with(orthographic_top(), 1.0) .with(orthographic_bottom(), -1.0) - .with(far(), 1_000.0), + .with(near(), -1.) + .with(far(), 1.0), } .to_owned(), ] @@ -213,6 +226,23 @@ pub fn camera_systems() -> SystemGroup { *projection = perspective_reverse(fovy, aspect_ratio, near, far); } }), + query(()) + .incl(orthographic_from_window()) + .incl(orthographic_left()) + .incl(orthographic_right()) + .incl(orthographic_top()) + .incl(orthographic_bottom()) + .incl(local_to_world()) + .to_system(|q, world, qs, _| { + let window_size = world.resource(window_logical_size()).as_vec2(); + for (id, _) in q.collect_cloned(world, qs) { + world.set_if_changed(id, local_to_world(), Mat4::from_translation((window_size / 2.).extend(0.))).unwrap(); + world.set_if_changed(id, orthographic_left(), -window_size.x / 2.).unwrap(); + world.set_if_changed(id, orthographic_right(), window_size.x / 2.).unwrap(); + world.set_if_changed(id, orthographic_top(), -window_size.y / 2.).unwrap(); + world.set_if_changed(id, orthographic_bottom(), window_size.y / 2.).unwrap(); + } + }), query(( orthographic_left().changed(), orthographic_right().changed(), diff --git a/crates/element/Cargo.toml b/crates/element/Cargo.toml index de9f52cde0..b30dc4ad78 100644 --- a/crates/element/Cargo.toml +++ b/crates/element/Cargo.toml @@ -8,21 +8,29 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ambient_ecs = { path = "../ecs" } -ambient_sys = { path = "../sys" } -ambient_std = { path = "../std" } -ambient_core = { path = "../core" } +ambient_guest_bridge = { path = "../guest_bridge" } ambient_element_component = { path = "../element_component" } +cb = { path = "../../libs/cb" } +friendly_id = { path = "../../libs/friendly_id" } + +ambient_sys = { path = "../sys", optional = true } +ambient_core = { path = "../core", optional = true } +tokio = { workspace = true, optional = true } + +parking_lot = { workspace = true } itertools = { workspace = true } as-any = { workspace = true } dyn-clonable = { workspace = true } derivative = { workspace = true } -parking_lot = { workspace = true } tracing = { workspace = true } profiling = { workspace = true } atomic_refcell = { workspace = true } -tokio = { workspace = true } futures = { workspace = true } [dev-dependencies] tokio = { workspace = true } + +[features] +default = ["native"] +native = ["ambient_sys", "ambient_core", "ambient_guest_bridge/native", "tokio"] +guest = ["ambient_guest_bridge/guest"] diff --git a/crates/element/src/element_config.rs b/crates/element/src/element_config.rs index 201797b66b..50f7899ecb 100644 --- a/crates/element/src/element_config.rs +++ b/crates/element/src/element_config.rs @@ -1,6 +1,8 @@ use std::{collections::HashMap, sync::Arc}; -use ambient_ecs::{Component, ComponentDesc, ComponentValue, Entity, EntityId, World}; +#[cfg(feature = "guest")] +use ambient_guest_bridge::ecs::UntypedComponent; +use ambient_guest_bridge::ecs::{Component, ComponentDesc, ComponentValue, Entity, EntityId, World}; use crate::ElementComponent; @@ -27,7 +29,7 @@ impl ElementConfig { part: None, components: ElementComponents::new(), init_components: ElementComponents::new(), - spawner: Arc::new(|world, props| props.spawn(world)), + spawner: Arc::new(|world, props| world.spawn(props)), despawner: Arc::new(|world, entity| { world.despawn(entity); }), @@ -57,7 +59,7 @@ impl ElementComponents { pub fn new() -> Self { Self(HashMap::new()) } - pub fn set(&mut self, component: Component, value: T) { + pub fn set(&mut self, component: Component, value: T) { self.set_writer(component, Arc::new(move |_, ed| ed.set(component, value.clone()))); } pub fn set_writer(&mut self, component: impl Into, writer: Arc) { diff --git a/crates/element/src/hooks.rs b/crates/element/src/hooks.rs index 67972ee244..ecef254fd3 100644 --- a/crates/element/src/hooks.rs +++ b/crates/element/src/hooks.rs @@ -6,12 +6,16 @@ use std::{ sync::Arc, }; +#[cfg(feature = "native")] use ambient_core::runtime; -use ambient_ecs::{world_events, ComponentQuery, ComponentValue, Entity, FrameEvent, QueryState, TypedReadQuery, World, WorldEventReader}; -use ambient_std::{cb, Cb}; +#[cfg(feature = "native")] +use ambient_guest_bridge::ecs::{world_events, ComponentQuery, FrameEvent, QueryState, TypedReadQuery, WorldEventReader}; +use ambient_guest_bridge::ecs::{ComponentValue, Entity, World}; +#[cfg(feature = "native")] use ambient_sys::task; use as_any::Downcast; use atomic_refcell::AtomicRefCell; +use cb::{cb, Cb}; use parking_lot::Mutex; use tracing::info_span; @@ -33,7 +37,7 @@ pub struct Hooks<'a> { } impl<'a> Hooks<'a> { - pub fn use_state(&mut self, init: T) -> (T, Setter) { + pub fn use_state(&mut self, init: T) -> (T, Setter) { self.use_state_with(|_| init) } @@ -79,7 +83,10 @@ impl<'a> Hooks<'a> { /// /// **Note**: Does not rely on order, and is therefore safe to use inside /// conditionals. - pub fn provide_context(&mut self, default_value: impl FnOnce() -> T) -> Setter { + pub fn provide_context( + &mut self, + default_value: impl FnOnce() -> T, + ) -> Setter { let instance = self.tree.instances.get_mut(&self.element).unwrap(); let type_id = TypeId::of::(); instance @@ -98,7 +105,7 @@ impl<'a> Hooks<'a> { }) } #[allow(clippy::type_complexity)] - pub fn consume_context(&mut self) -> Option<(T, Setter)> { + pub fn consume_context(&mut self) -> Option<(T, Setter)> { let type_id = TypeId::of::(); if let Some(provider) = self.tree.get_context_provider(&self.element, type_id) { let value = { @@ -137,6 +144,7 @@ impl<'a> Hooks<'a> { } } + #[cfg(feature = "native")] pub fn use_world_event(&mut self, func: impl Fn(&mut World, &Entity) + Sync + Send + 'static) { let reader = self.use_ref_with(|world| world.resource(world_events()).reader()); self.use_frame(move |world| { @@ -150,6 +158,7 @@ impl<'a> Hooks<'a> { /// Spawns the provided future as a task, and aborts the task when the /// entity is despawned. + #[cfg(feature = "native")] #[tracing::instrument(level = "debug", skip_all)] pub fn use_task + Send + 'static>(&mut self, task: impl FnOnce(&mut World) -> T + Send + Sync + 'static) { if let Some(ref mut on_spawn) = self.on_spawn { @@ -164,6 +173,7 @@ impl<'a> Hooks<'a> { } /// Use state dependent on a future + #[cfg(feature = "native")] pub fn use_async(&mut self, future: impl FnOnce(&mut World) -> T + Send + Sync + 'static) -> Option where T: Future + Send + 'static, @@ -187,6 +197,7 @@ impl<'a> Hooks<'a> { } /// Use memoized state dependent on a future + #[cfg(feature = "native")] pub fn use_memo_async(&mut self, deps: D, init: F) -> Option where F: FnOnce(&mut World, D) -> U, @@ -256,7 +267,10 @@ impl<'a> Hooks<'a> { } #[profiling::function] - pub fn use_memo_with( + pub fn use_memo_with< + T: Clone + ComponentValue + Debug + Sync + Send + 'static, + D: PartialEq + Clone + Sync + Send + Debug + 'static, + >( &mut self, dependencies: D, create: impl FnOnce(&mut World, &D) -> T, @@ -282,7 +296,7 @@ impl<'a> Hooks<'a> { /// The provided functions returns a function which is run when the part is /// removed or `use_effect` is run again. /// - pub fn use_effect( + pub fn use_effect( &mut self, dependencies: D, run: impl FnOnce(&mut World, &D) -> Box + Sync + Send, @@ -320,6 +334,7 @@ impl<'a> Hooks<'a> { } } + #[cfg(feature = "native")] pub fn use_system< 'b, R: ComponentQuery<'b> + Clone + 'static, diff --git a/crates/element/src/lib.rs b/crates/element/src/lib.rs index ffe35949a0..5b01ca2948 100644 --- a/crates/element/src/lib.rs +++ b/crates/element/src/lib.rs @@ -3,7 +3,9 @@ extern crate derivative; use std::{any::Any, sync::Arc}; -use ambient_ecs::{components, Component, ComponentDesc, ComponentValue, Entity, EntityId, SystemGroup, World}; +#[cfg(feature = "native")] +use ambient_guest_bridge::ecs::{components, Debuggable, Description, Name, Networked, SystemGroup}; +use ambient_guest_bridge::ecs::{Component, ComponentDesc, ComponentValue, Entity, EntityId, World}; use as_any::AsAny; use dyn_clonable::clonable; use parking_lot::Mutex; @@ -18,12 +20,24 @@ pub use hooks::*; pub use standard::*; pub use tree::*; -components!("element", { +#[cfg(feature = "native")] +components!("app", { + @[ + Networked, Debuggable, + Name["Element"], + Description["The id of the Element which controls this Entity."] + ] element: String, element_tree: ShareableElementTree, - // If this is set, the user is expected to manage the children of the element themselves + @[ + Networked, Debuggable, + Name["Element unmanaged children"], + Description["If this is set, the user is expected to manage the children of the element themselves."] + ] element_unmanaged_children: (), }); +#[cfg(feature = "guest")] +pub use ambient_guest_bridge::api::components::core::app::{element, element_unmanaged_children}; #[clonable] pub trait AnyCloneable: AsAny + Clone + std::fmt::Debug {} @@ -79,40 +93,50 @@ impl Element { pub fn vec_of(self) -> Vec { vec![self] } - pub fn set(mut self, component: Component, value: T) -> Self { + pub fn set(mut self, component: Component, value: T) -> Self { self.config.components.set(component, value); self } - pub fn set_with T + ComponentValue>(mut self, component: Component, value: F) -> Self { + pub fn set_with T + ComponentValue + Sync + Send + 'static>( + mut self, + component: Component, + value: F, + ) -> Self { self.config.components.set_writer(component, Arc::new(move |world, ed| ed.set(component, value(world)))); self } - pub fn set_default(mut self, component: Component) -> Self { + pub fn set_default(mut self, component: Component) -> Self { self.config.components.set(component, T::default()); self } /// Sets the component of the element component instantiation - pub fn init(mut self, component: Component, value: T) -> Self { + pub fn init(mut self, component: Component, value: T) -> Self { self.config.init_components.set(component, value); self } /// See [`Element::init`] - pub fn init_with T + ComponentValue>(mut self, component: Component, value: F) -> Self { + pub fn init_with T + ComponentValue + Sync + Send + 'static>( + mut self, + component: Component, + value: F, + ) -> Self { self.config.init_components.set_writer(component, Arc::new(move |world, ed| ed.set(component, value(world)))); self } /// See [`Element::init`] - pub fn init_default(mut self, component: Component) -> Self { + pub fn init_default(mut self, component: Component) -> Self { self.config.init_components.set(component, T::default()); self } + #[cfg(feature = "native")] pub fn extend(mut self, entity_data: Entity) -> Self { for unit in entity_data.into_iter() { self.config.components.set_writer(unit.desc(), Arc::new(move |_, ed| ed.set_entry(unit.clone()))); } self } - /// See [`Element::init`]; adds each entry in the EntityData to init + /// See [`Element::init`]; adds each entry in the Entity to init + #[cfg(feature = "native")] pub fn init_extend(mut self, entity_data: Entity) -> Self { for unit in entity_data.into_iter() { self.config.init_components.set_writer(unit.desc(), Arc::new(move |_, ed| ed.set_entry(unit.clone()))); @@ -132,19 +156,19 @@ impl Element { self.children = children; self } - pub fn spawner EntityId + ComponentValue>(mut self, handler: F) -> Self { + pub fn spawner EntityId + ComponentValue + Sync + Send + 'static>(mut self, handler: F) -> Self { self.config.spawner = Arc::new(handler); self } - pub fn despawner(mut self, handler: F) -> Self { + pub fn despawner(mut self, handler: F) -> Self { self.config.despawner = Arc::new(handler); self } - pub fn on_spawned(mut self, handler: F) -> Self { + pub fn on_spawned(mut self, handler: F) -> Self { self.config.on_spawned = Some(Arc::new(handler)); self } - pub fn on_despawn(mut self, handler: F) -> Self { + pub fn on_despawn(mut self, handler: F) -> Self { self.config.on_despawn = Some(Arc::new(handler)); self } @@ -162,20 +186,30 @@ impl Element { self.config.components.0.contains_key(&index) || self.config.init_components.0.contains_key(&index) } /// This spawns the element tree as a number of entities, but they won't react to changes. Returns the root entity + #[cfg(feature = "native")] pub fn spawn_static(self, world: &mut World) -> EntityId { ElementTree::new(world, self).root_entity().unwrap() } /// This spawns the element tree plus a handle entity which will have an `element_tree` component on it. All /// `element_tree` components get updated each frame so this entity tree will be updated + #[cfg(feature = "native")] pub fn spawn_interactive(self, world: &mut World) -> EntityId { let tree = self.spawn_tree(world); - Entity::new().with(self::element_tree(), ShareableElementTree(Arc::new(Mutex::new(tree)))).spawn(world) + let entity = Entity::new().with(self::element_tree(), ShareableElementTree(Arc::new(Mutex::new(tree)))); + world.spawn(entity) } /// 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 = "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) + } } impl Default for Element { @@ -184,6 +218,7 @@ impl Default for Element { } } +#[cfg(feature = "native")] pub fn ambient_system() -> SystemGroup { ElementTree::systems_for_component(element_tree()) } @@ -199,6 +234,7 @@ macro_rules! define_el_function_for_vec_element_newtype { }; } +#[cfg(feature = "native")] pub fn render_parented_with_component(world: &mut World, id: EntityId, handle: Component, mut element: Element) { use ambient_core::{ hierarchy::{children, parent}, diff --git a/crates/element/src/tree.rs b/crates/element/src/tree.rs index 93db2e3a29..eddb7e176b 100644 --- a/crates/element/src/tree.rs +++ b/crates/element/src/tree.rs @@ -5,18 +5,22 @@ use std::{ sync::Arc, }; +#[cfg(feature = "native")] +use crate::element_tree; +use crate::element_unmanaged_children; +use crate::{AnyCloneable, ContextUpdate, DespawnFn, Element, ElementConfig, Hooks, HooksEnvironment, InstanceId, StateUpdate}; +#[cfg(feature = "native")] use ambient_core::hierarchy::{children, parent}; -use ambient_ecs::{query, Component, Entity, EntityId, SystemGroup, World}; -use ambient_std::friendly_id; +#[cfg(feature = "guest")] +use ambient_guest_bridge::api::components::core::ecs::{children, parent}; +#[cfg(feature = "native")] +use ambient_guest_bridge::ecs::{query, SystemGroup}; +use ambient_guest_bridge::ecs::{Component, Entity, EntityId, World}; +use friendly_id::friendly_id; use itertools::Itertools; use parking_lot::Mutex; use tracing::debug_span; -use crate::{ - element_tree, element_unmanaged_children, AnyCloneable, ContextUpdate, DespawnFn, Element, ElementConfig, Hooks, HooksEnvironment, - InstanceId, StateUpdate, -}; - #[derive(Debug)] pub(crate) struct HookContext { pub value: Box, @@ -98,6 +102,7 @@ impl ElementTree { }) } + #[cfg(feature = "native")] pub fn render_with_component(world: &mut World, id: EntityId, handle: Component, element: Element) { if let Ok(tree) = world.get_ref(id, handle).map(|x| x.clone()) { tree.0.lock().migrate_root(world, element); @@ -106,9 +111,11 @@ impl ElementTree { world.add_component(id, handle, tree).unwrap(); } } + #[cfg(feature = "native")] pub fn render(world: &mut World, id: EntityId, element: Element) { Self::render_with_component(world, id, element_tree(), element) } + #[cfg(feature = "native")] pub fn systems_for_component(component: Component) -> SystemGroup { SystemGroup::new( "ElementTree::systems_for_component", @@ -262,12 +269,13 @@ impl ElementTree { let instance = self.instances.get_mut(instance_id).unwrap(); if instance.entity != old_entity { if let Some(parent) = instance.parent_entity { - let children = world.get_mut(parent, children()).unwrap(); - for c in children.iter_mut() { + let mut childs = world.get_ref(parent, children()).unwrap().clone(); + for c in childs.iter_mut() { if *c == old_entity { *c = instance.entity; } } + world.set(parent, children(), childs).unwrap(); } } } diff --git a/crates/element/tests/common/mod.rs b/crates/element/tests/common/mod.rs index 30b4f55a39..f47d81bdbb 100644 --- a/crates/element/tests/common/mod.rs +++ b/crates/element/tests/common/mod.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use ambient_ecs::{components, query_mut, Resource, World}; -use ambient_std::Cb; +use ambient_guest_bridge::ecs::{components, query_mut, Resource, World}; +use cb::Cb; use itertools::Itertools; components!("test", { diff --git a/crates/element/tests/context.rs b/crates/element/tests/context.rs index 869da8eb6a..89650cf7df 100644 --- a/crates/element/tests/context.rs +++ b/crates/element/tests/context.rs @@ -1,7 +1,7 @@ use ambient_element::{Element, ElementComponent, ElementComponentExt, Hooks}; mod common; -use ambient_std::cb; +use cb::cb; use common::*; #[test] diff --git a/crates/element/tests/events.rs b/crates/element/tests/events.rs index e89438f467..2c8b74a47c 100644 --- a/crates/element/tests/events.rs +++ b/crates/element/tests/events.rs @@ -1,7 +1,7 @@ -use ambient_ecs::query_mut; use ambient_element::{Element, ElementComponent, ElementComponentExt, Hooks}; +use ambient_guest_bridge::ecs::query_mut; mod common; -use ambient_std::cb; +use cb::cb; use common::*; #[test] diff --git a/crates/element/tests/hooks.rs b/crates/element/tests/hooks.rs index 11d1865e48..0612a7e6f5 100644 --- a/crates/element/tests/hooks.rs +++ b/crates/element/tests/hooks.rs @@ -5,7 +5,7 @@ use std::sync::{ use ambient_element::{Element, ElementComponent, ElementComponentExt, ElementTree, Hooks}; mod common; -use ambient_std::cb; +use cb::cb; use common::*; #[test] diff --git a/crates/element/tests/standard.rs b/crates/element/tests/standard.rs index c6630fceb5..a8d5fac89c 100644 --- a/crates/element/tests/standard.rs +++ b/crates/element/tests/standard.rs @@ -1,5 +1,5 @@ -use ambient_ecs::query; use ambient_element::{Element, ElementComponent, ElementComponentExt, Hooks, Memo}; +use ambient_guest_bridge::ecs::query; mod common; use common::*; diff --git a/crates/guest_bridge/Cargo.toml b/crates/guest_bridge/Cargo.toml new file mode 100644 index 0000000000..6359cf2065 --- /dev/null +++ b/crates/guest_bridge/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ambient_guest_bridge" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ambient_ecs = { path = "../ecs", optional = true } +ambient_core = { path = "../core", optional = true } +ambient_layout = { path = "../layout", optional = true } +ambient_renderer = { path = "../renderer", optional = true } +ambient_text = { path = "../text", optional = true } +ambient_api = { path = "../../guest/rust/api", optional = true } + +[features] +native = ["ambient_ecs", "ambient_core", "ambient_layout", "ambient_renderer", "ambient_text"] +guest = ["ambient_api"] diff --git a/crates/guest_bridge/src/lib.rs b/crates/guest_bridge/src/lib.rs new file mode 100644 index 0000000000..dc6881dc09 --- /dev/null +++ b/crates/guest_bridge/src/lib.rs @@ -0,0 +1,98 @@ +#[cfg(feature = "native")] +pub use ambient_ecs as ecs; + +#[cfg(feature = "native")] +pub mod components { + pub mod app { + pub use ambient_core::{name, ui_scene}; + } + pub mod ecs { + pub use ambient_core::hierarchy::{children, parent}; + } + pub mod transform { + pub use ambient_core::transform::{local_to_parent, local_to_world, mesh_to_local, mesh_to_world, rotation, scale, translation}; + } + pub mod ui { + pub use ambient_layout::{height, min_height, min_width, width}; + pub use ambient_text::{font_size, text}; + } + pub mod rendering { + pub use ambient_renderer::color; + } +} + +#[cfg(feature = "guest")] +pub use ambient_api as api; + +#[cfg(feature = "guest")] +pub use ambient_api::components::core as components; + +#[cfg(feature = "guest")] +pub mod ecs { + use ambient_api::ecs::SupportedComponentTypeGet; + pub use ambient_api::{ + ecs::{Component, SupportedComponentTypeSet as ComponentValue, UntypedComponent}, + prelude::{Entity, EntityId}, + }; + + #[derive(Clone, Copy)] + pub struct World; + impl World { + pub fn spawn(&self, entity: Entity) -> EntityId { + ambient_api::entity::spawn(&entity) + } + pub fn despawn(&self, entity_id: EntityId) -> bool { + ambient_api::entity::despawn(entity_id) + } + pub fn set(&self, entity_id: EntityId, component: Component, value: T) -> Result<(), ECSError> { + // TODO: set_component needs to return errors + ambient_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 + ambient_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 + ambient_api::entity::add_components(entity_id, components); + Ok(()) + } + pub fn get( + &self, + entity_id: EntityId, + component: Component, + ) -> Result { + ambient_api::entity::get_component(entity_id, component).ok_or_else(|| ECSError::EntityDoesntHaveComponent) + } + // TODO: This should actually return &T + pub fn get_ref( + &self, + entity_id: EntityId, + component: Component, + ) -> Result { + self.get(entity_id, component) + } + pub fn has_component(&self, entity_id: EntityId, component: Component) -> bool { + ambient_api::entity::has_component(entity_id, component) + } + } + #[derive(Debug)] + pub enum ECSError { + EntityDoesntHaveComponent, + NoSuchEntity, + } + + pub struct ComponentDesc(Box); + impl ComponentDesc { + pub fn index(&self) -> u32 { + self.0.index() + } + } + impl From> for ComponentDesc { + fn from(value: Component) -> Self { + Self(Box::new(value)) + } + } +} diff --git a/crates/layout/Cargo.toml b/crates/layout/Cargo.toml new file mode 100644 index 0000000000..efd62ca9b5 --- /dev/null +++ b/crates/layout/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ambient_layout" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ambient_ecs = { path = "../ecs" } +ambient_core = { path = "../core" } +ambient_input = { path = "../input" } +glam = { workspace = true } +itertools = { workspace = true } +log = { workspace = true } diff --git a/crates/ui/src/layout.rs b/crates/layout/src/lib.rs similarity index 100% rename from crates/ui/src/layout.rs rename to crates/layout/src/lib.rs diff --git a/crates/network/src/client.rs b/crates/network/src/client.rs index 4ea058aa10..2b3f83a27f 100644 --- a/crates/network/src/client.rs +++ b/crates/network/src/client.rs @@ -143,6 +143,8 @@ pub struct GameClientView { pub on_in_entities: Option>, pub on_disconnect: Cb, pub create_rpc_registry: Cb RpcRegistry + Sync + Send>, + pub on_network_stats: Cb, + pub on_server_stats: Cb, pub ui: Element, } @@ -159,6 +161,8 @@ impl Clone for GameClientView { on_in_entities: self.on_in_entities.clone(), on_disconnect: self.on_disconnect.clone(), create_rpc_registry: self.create_rpc_registry.clone(), + on_network_stats: self.on_network_stats.clone(), + on_server_stats: self.on_server_stats.clone(), ui: self.ui.clone(), } } @@ -178,11 +182,10 @@ impl ElementComponent for GameClientView { on_in_entities, ui, on_disconnect, + on_network_stats, + on_server_stats, } = *self; - let (_, client_stats_ctx) = hooks.consume_context::().unwrap(); - let (_, server_stats_ctx) = hooks.consume_context::().unwrap(); - let gpu = hooks.world.resource(gpu()).clone(); let render_target = hooks.use_memo_with(resolution, |_, &resolution| Arc::new(RenderTarget::new(gpu.clone(), resolution, None))); @@ -263,11 +266,11 @@ impl ElementComponent for GameClientView { }; let mut on_server_stats = |stats| { - server_stats_ctx(stats); + on_server_stats(stats); }; - let mut on_client_stats = |stats| { - client_stats_ctx(stats); + let mut on_network_stats = |stats| { + on_network_stats(stats); }; let client_loop = ClientInstance { @@ -277,7 +280,7 @@ impl ElementComponent for GameClientView { on_init: &mut on_init, on_diff: &mut on_diff, on_server_stats: &mut on_server_stats, - on_client_stats: &mut on_client_stats, + on_client_stats: &mut on_network_stats, on_event: &mut on_event, on_disconnect, init_destructor: None, diff --git a/crates/network/src/client_game_state.rs b/crates/network/src/client_game_state.rs index e5da836067..fd4b815427 100644 --- a/crates/network/src/client_game_state.rs +++ b/crates/network/src/client_game_state.rs @@ -6,7 +6,7 @@ use ambient_core::{ gpu_ecs::GpuWorldSyncEvent, main_scene, transform::local_to_world, - window_physical_size, + ui_scene, window_physical_size, }; use ambient_ecs::{components, query, Entity, FrameEvent, System, SystemGroup, World}; use ambient_gizmos::render::GizmoRenderer; @@ -34,6 +34,7 @@ pub struct ClientGameState { temporary_systems: Vec, gpu_world_sync_systems: SystemGroup, pub renderer: Renderer, + pub ui_renderer: Renderer, assets: AssetCache, user_id: String, } @@ -65,12 +66,15 @@ impl ClientGameState { Renderer::new(world, assets.clone(), RendererConfig { scene: main_scene(), shadows: true, ..Default::default() }); renderer.post_transparent = Some(Box::new(GizmoRenderer::new(&assets))); + let ui_renderer = Renderer::new(world, assets.clone(), RendererConfig { scene: ui_scene(), shadows: false, ..Default::default() }); + Self { world: game_world, systems, temporary_systems: Default::default(), gpu_world_sync_systems: gpu_world_sync_systems(), renderer, + ui_renderer, assets, user_id: player_id, } @@ -91,6 +95,7 @@ impl ClientGameState { RendererTarget::Target(target), Some(Color::rgba(0., 0., 0., 1.)), ); + self.ui_renderer.render(&mut self.world, &mut encoder, &mut post_submit, RendererTarget::Target(target), None); gpu.queue.submit(Some(encoder.finish())); for action in post_submit { action(); diff --git a/crates/std/Cargo.toml b/crates/std/Cargo.toml index b7d670f9a1..ed4c795870 100644 --- a/crates/std/Cargo.toml +++ b/crates/std/Cargo.toml @@ -9,6 +9,10 @@ edition = "2021" [dependencies] ambient_asset_cache = { path = "../asset_cache/", optional = true } ambient_sys = { path = "../sys" } +cb = { path = "../../libs/cb" } +friendly_id = { path = "../../libs/friendly_id" } +anyhow = { workspace = true } +tracing = { workspace = true } wgpu = { workspace = true, optional = true } serde = { workspace = true, optional = true } @@ -26,12 +30,10 @@ log = { workspace = true, optional = true } reqwest = { workspace = true, optional = true } thiserror = { workspace = true, optional = true } yaml-rust = { workspace = true, optional = true } -anyhow = { workspace = true, optional = true } sentry-anyhow = { workspace = true, optional = true } parking_lot = { workspace = true, optional = true } hex = { workspace = true, optional = true } profiling = { workspace = true, optional = true } -tracing = { workspace = true, optional = true } ring = { workspace = true, optional = true } data-encoding = { workspace = true, optional = true } chrono = { workspace = true, optional = true } @@ -49,8 +51,7 @@ percent-encoding = { workspace = true, optional = true } # without dragging in all of its dependencies. If you have some free time, try # splitting these up into finer-grained features! default = ["all"] -all = ["cb", "uncategorized"] -cb = ["dep:anyhow", "dep:tracing"] +all = ["uncategorized"] uncategorized = [ "dep:ambient_asset_cache", "dep:wgpu", @@ -68,12 +69,10 @@ uncategorized = [ "dep:log", "dep:reqwest", "dep:thiserror", - "dep:anyhow", "dep:sentry-anyhow", "dep:parking_lot", "dep:hex", "dep:profiling", - "dep:tracing", "dep:ring", "dep:data-encoding", "dep:chrono", diff --git a/crates/std/README.md b/crates/std/README.md index 1f67a302ab..1f16a8ce48 100644 --- a/crates/std/README.md +++ b/crates/std/README.md @@ -2,4 +2,4 @@ Ambient standard library is a collection of low level functions and types; think of it as a gamedev complement to the rust-std library. -This is the "lowest level" library and shouldn't depend on any other wiwi crate. +This is the "lowest level" library and shouldn't depend on any other Ambient crate. diff --git a/crates/std/src/lib.rs b/crates/std/src/lib.rs index c01bff3ff6..1a7841da61 100644 --- a/crates/std/src/lib.rs +++ b/crates/std/src/lib.rs @@ -5,15 +5,11 @@ mod uncategorized; #[cfg(feature = "uncategorized")] pub use uncategorized::*; -#[cfg(feature = "cb")] -mod cb; -#[cfg(feature = "cb")] -pub use cb::*; - pub mod colorspace; pub mod events; pub mod line_hash; pub mod path; +pub use cb::*; /// Read a file as a string during debug at runtime, or use include_str at release /// # Panics @@ -59,6 +55,13 @@ macro_rules! include_file_bytes { }}; } +pub fn log_error(err: &anyhow::Error) { + #[cfg(feature = "sentry")] + sentry_anyhow::capture_anyhow(err); + #[cfg(not(feature = "sentry"))] + tracing::error!("{:?}", err); +} + #[macro_export] /// Consumes and logs the error variant. /// diff --git a/crates/std/src/uncategorized/mod.rs b/crates/std/src/uncategorized/mod.rs index c95676d4f3..f5ca3829a7 100644 --- a/crates/std/src/uncategorized/mod.rs +++ b/crates/std/src/uncategorized/mod.rs @@ -6,7 +6,6 @@ pub mod disk_cache; pub mod download_asset; pub mod encode; pub mod fps_counter; -pub mod id; pub mod math; pub mod mesh; pub mod ordered_glam; @@ -15,7 +14,7 @@ pub mod sparse_vec; pub mod time; pub use encode::sha256_digest; -pub use id::friendly_id; +pub use friendly_id::friendly_id; pub use time::{pretty_duration, FromDuration, IntoDuration}; #[cfg(not(target_os = "unknown"))] diff --git a/crates/text/Cargo.toml b/crates/text/Cargo.toml new file mode 100644 index 0000000000..37deb9d03f --- /dev/null +++ b/crates/text/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ambient_text" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ambient_std = { path = "../std" } +ambient_ecs = { path = "../ecs" } +ambient_gpu = { path = "../gpu" } +ambient_core = { path = "../core" } +ambient_input = { path = "../input" } +ambient_renderer = { path = "../renderer" } +ambient_layout = { path = "../layout" } +glyph_brush = { workspace = true } +anyhow = { workspace = true } +async-trait = { workspace = true } +glam = { workspace = true } +log = { workspace = true } +parking_lot = { workspace = true } +wgpu = { workspace = true } diff --git a/crates/ui/src/text.rs b/crates/text/src/lib.rs similarity index 92% rename from crates/ui/src/text.rs rename to crates/text/src/lib.rs index f32d20a0f8..8ca4f40246 100644 --- a/crates/ui/src/text.rs +++ b/crates/text/src/lib.rs @@ -1,10 +1,9 @@ use std::{num::NonZeroU32, ops::Deref, sync::Arc}; -use ambient_core::{asset_cache, async_ecs::async_run, gpu, mesh, name, runtime, transform::*, ui_scene, window_scale_factor}; +use ambient_core::{asset_cache, async_ecs::async_run, gpu, mesh, runtime, transform::*, window_scale_factor}; use ambient_ecs::{components, query, query_mut, Debuggable, Description, Entity, Name, Networked, Store, SystemGroup}; -use ambient_element::{element_component, Element, ElementComponentExt, Hooks}; use ambient_gpu::{mesh_buffer::GpuMesh, texture::Texture}; -use ambient_renderer::{color, gpu_primitives, material, primitives, renderer_shader, SharedMaterial}; +use ambient_renderer::{gpu_primitives, material, primitives, renderer_shader, SharedMaterial}; use ambient_std::{ asset_cache::{AssetCache, AsyncAssetKey, AsyncAssetKeyExt}, asset_url::AbsAssetUrl, @@ -23,11 +22,10 @@ use glyph_brush::{ use log::info; use parking_lot::Mutex; -use crate::{ - layout::*, - text_material::{get_text_shader, TextMaterial}, - UIBase, UIElement, -}; +use crate::text_material::{get_text_shader, TextMaterial}; +use ambient_layout::{height, min_height, min_width, width}; + +mod text_material; components!("ui", { @[Debuggable, Networked, Store, Name["Text"], Description["Create a text mesh on this entity."]] @@ -43,45 +41,6 @@ components!("ui", { text_texture: Arc, }); -/// A text element. Use the `text`, `font_size`, `font` and `color` components to set its state -#[element_component(without_el)] -pub fn Text(hooks: &mut Hooks) -> Element { - let scale_factor = *hooks.world.resource(window_scale_factor()) as f32; - - UIBase - .el() - .init(width(), 1.) - .init(height(), 1.) - .init(mesh_to_local(), Mat4::from_scale(Vec3::ONE / scale_factor)) - .init(color(), vec4(0.6, 0.6, 0.6, 1.)) - .init(name(), "Text".to_string()) - .init(ui_scene(), ()) - .init_default(font_family()) - .init_default(font_style()) - .init(font_size(), 12.) - .init(text(), "".to_string()) -} -impl Text { - pub fn el(value: impl Into) -> Element { - Text.el().set(text(), value.into()) - } -} -impl From<&str> for UIElement { - fn from(value: &str) -> Self { - UIElement(Text.el().set(text(), value.to_string())) - } -} -impl From for UIElement { - fn from(value: String) -> Self { - UIElement(Text.el().set(text(), value)) - } -} -impl From<&String> for UIElement { - fn from(value: &String) -> Self { - UIElement(Text.el().set(text(), value.to_string())) - } -} - #[derive(Debug, Clone, Copy)] pub enum TextCase { AsTyped, @@ -366,6 +325,13 @@ pub fn systems() -> SystemGroup { } } }), + query(window_scale_factor().changed()).to_system(|q, world, qs, _| { + if let Some((_, scale_factor)) = q.collect_cloned(world, qs).iter().next() { + for (id, _) in query(()).incl(text()).collect_cloned(world, None) { + world.add_component(id, mesh_to_local(), Mat4::from_scale(Vec3::ONE / (*scale_factor as f32))).unwrap(); + } + } + }), ], ) } diff --git a/crates/ui/src/text_material.rs b/crates/text/src/text_material.rs similarity index 100% rename from crates/ui/src/text_material.rs rename to crates/text/src/text_material.rs diff --git a/crates/ui/src/text_material.wgsl b/crates/text/src/text_material.wgsl similarity index 100% rename from crates/ui/src/text_material.wgsl rename to crates/text/src/text_material.wgsl diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml index 394604dfe6..cddfdd0906 100644 --- a/crates/ui/Cargo.toml +++ b/crates/ui/Cargo.toml @@ -16,8 +16,10 @@ ambient_meshes = { path = "../meshes" } ambient_element = { path = "../element" } ambient_input = { path = "../input" } ambient_renderer = { path = "../renderer" } +ambient_layout = { path = "../layout" } +ambient_text = { path = "../text" } +ambient_ui_components = { path = "../ui_components" } ambient_editor_derive = { path = "../editor_derive" } -glyph_brush = { workspace = true } glam = { workspace = true } winit = { workspace = true } itertools = { workspace = true } diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 2d1f3b1a10..9df488c7dd 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -37,7 +37,6 @@ pub mod graph; mod hooks; mod image; mod input; -pub mod layout; mod loadable; mod prompt; mod rect; @@ -45,11 +44,14 @@ mod screens; mod select; mod style_constants; mod tabs; -mod text; mod text_input; -mod text_material; mod throbber; +pub use ambient_layout as layout; +use ambient_text as text; +pub use ambient_text::*; +pub use ambient_ui_components::text::*; +pub use ambient_ui_components::*; pub use asset_url::*; pub use button::*; pub use collections::*; @@ -66,7 +68,6 @@ pub use screens::*; pub use select::*; pub use style_constants::*; pub use tabs::*; -pub use text::*; pub use text_input::*; pub use throbber::*; @@ -87,26 +88,6 @@ pub fn systems() -> SystemGroup { ) } -/// This only exists so that we can implement From for Text, and then use it in -/// for instance Button -pub struct UIElement(pub Element); -impl From for UIElement { - fn from(el: Element) -> Self { - Self(el) - } -} - -#[element_component] -pub fn UIBase(_: &mut Hooks) -> Element { - Element::new() - .init(translation(), vec3(0., 0., -0.001)) - .init_default(local_to_world()) - .init_default(local_to_parent()) - .init_default(mesh_to_world()) - .init(width(), 0.) - .init(height(), 0.) -} - pub fn use_window_physical_resolution(hooks: &mut Hooks) -> UVec2 { let (res, set_res) = hooks.use_state(*hooks.world.resource(window_physical_size())); hooks.use_frame(move |world| { diff --git a/crates/ui_components/Cargo.toml b/crates/ui_components/Cargo.toml new file mode 100644 index 0000000000..5e8821cc6c --- /dev/null +++ b/crates/ui_components/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ambient_ui_components" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ambient_element = { path = "../element", default-features = false } +ambient_guest_bridge = { path = "../guest_bridge", default-features = false } +glam = { workspace = true } diff --git a/crates/ui_components/src/lib.rs b/crates/ui_components/src/lib.rs new file mode 100644 index 0000000000..b5f0f58c4e --- /dev/null +++ b/crates/ui_components/src/lib.rs @@ -0,0 +1,28 @@ +use ambient_element::{element_component, Element, Hooks}; +use ambient_guest_bridge::components::{ + transform::{local_to_parent, local_to_world, mesh_to_world, translation}, + ui::{height, width}, +}; +use glam::vec3; + +pub mod text; + +#[element_component] +pub fn UIBase(_: &mut Hooks) -> Element { + Element::new() + .init(translation(), vec3(0., 0., -0.001)) + .init_default(local_to_world()) + .init_default(local_to_parent()) + .init_default(mesh_to_world()) + .init(width(), 0.) + .init(height(), 0.) +} + +/// This only exists so that we can implement From for Text, and then use it in +/// for instance Button +pub struct UIElement(pub Element); +impl From for UIElement { + fn from(el: Element) -> Self { + Self(el) + } +} diff --git a/crates/ui_components/src/text.rs b/crates/ui_components/src/text.rs new file mode 100644 index 0000000000..5014bc1671 --- /dev/null +++ b/crates/ui_components/src/text.rs @@ -0,0 +1,51 @@ +use crate::{UIBase, UIElement}; +use ambient_element::{element_component, Element, ElementComponentExt, Hooks}; +use ambient_guest_bridge::{ + components::{ + app::{name, ui_scene}, + rendering::color, + ui::{font_size, text}, + }, + components::{ + transform::mesh_to_local, + ui::{height, width}, + }, +}; +use glam::{vec4, Mat4}; + +/// A text element. Use the `text`, `font_size`, `font` and `color` components to set its state +#[element_component(without_el)] +pub fn Text(_hooks: &mut Hooks) -> Element { + UIBase + .el() + .init(width(), 1.) + .init(height(), 1.) + .init(mesh_to_local(), Mat4::IDENTITY) + .init(color(), vec4(0.6, 0.6, 0.6, 1.)) + .init(name(), "Text".to_string()) + .init(ui_scene(), ()) + // .init_default(font_family()) + // .init_default(font_style()) + .init(font_size(), 12.) + .init(text(), "".to_string()) +} +impl Text { + pub fn el(value: impl Into) -> Element { + Text.el().set(text(), value.into()) + } +} +impl From<&str> for UIElement { + fn from(value: &str) -> Self { + UIElement(Text.el().set(text(), value.to_string())) + } +} +impl From for UIElement { + fn from(value: String) -> Self { + UIElement(Text.el().set(text(), value)) + } +} +impl From<&String> for UIElement { + fn from(value: &String) -> Self { + UIElement(Text.el().set(text(), value.to_string())) + } +} diff --git a/guest/rust/Cargo.lock b/guest/rust/Cargo.lock index 1b04f5d39d..8194a4b926 100644 --- a/guest/rust/Cargo.lock +++ b/guest/rust/Cargo.lock @@ -31,6 +31,52 @@ dependencies = [ "toml 0.7.2", ] +[[package]] +name = "ambient_element" +version = "0.1.1" +dependencies = [ + "ambient_element_component", + "ambient_guest_bridge", + "as-any", + "atomic_refcell", + "cb", + "derivative", + "dyn-clonable", + "friendly_id", + "futures", + "itertools", + "parking_lot", + "profiling", + "tracing", +] + +[[package]] +name = "ambient_element_component" +version = "0.1.1" +dependencies = [ + "itertools", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ambient_guest_bridge" +version = "0.1.0" +dependencies = [ + "ambient_api", +] + +[[package]] +name = "ambient_ui_components" +version = "0.1.0" +dependencies = [ + "ambient_element", + "ambient_guest_bridge", + "glam", +] + [[package]] name = "anyhow" version = "1.0.69" @@ -46,6 +92,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "as-any" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088ccb346677e658e7ccd9627c62576fba881f4db7fab71fa9e21bf31c0aa4cb" + [[package]] name = "asset_loading" version = "0.1.1" @@ -60,6 +112,12 @@ dependencies = [ "ambient_api", ] +[[package]] +name = "atomic_refcell" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857253367827bd9d0fd973f0ef15506a96e79e41b0ad7aa691203a4e3214f6c8" + [[package]] name = "autocfg" version = "1.1.0" @@ -75,6 +133,28 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cb" +version = "0.1.0" + [[package]] name = "cfg-if" version = "1.0.0" @@ -87,6 +167,50 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "find-crate" version = "0.6.3" @@ -96,6 +220,78 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "friendly_id" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "futures" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -113,7 +309,9 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" dependencies = [ + "bytemuck", "rand", + "serde", ] [[package]] @@ -152,12 +350,41 @@ dependencies = [ "ambient_api", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", + "serde", +] + [[package]] name = "memchr" version = "2.5.0" @@ -227,6 +454,29 @@ dependencies = [ "syn", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "paste" version = "1.0.11" @@ -282,6 +532,18 @@ dependencies = [ "ambient_api", ] +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -295,6 +557,16 @@ dependencies = [ "ambient_api", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.51" @@ -304,6 +576,35 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.7" +source = "git+https://github.com/philpax/profiling.git#0167b1da12e301b2a19c90a02684527a0f54f935" +dependencies = [ + "profiling-procmacros", + "puffin", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.7" +source = "git+https://github.com/philpax/profiling.git#0167b1da12e301b2a19c90a02684527a0f54f935" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "puffin" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b2c7a01f569fb03e2ff1f5376537f294001447bd23ce75ca51054fcd223fe4" +dependencies = [ + "byteorder", + "instant", + "once_cell", +] + [[package]] name = "quote" version = "1.0.23" @@ -343,6 +644,28 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw_text" +version = "0.1.1" +dependencies = [ + "ambient_api", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "serde" version = "1.0.152" @@ -385,6 +708,21 @@ dependencies = [ "ambient_api", ] +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "sun" version = "0.1.1" @@ -408,6 +746,9 @@ name = "text" version = "0.1.1" dependencies = [ "ambient_api", + "ambient_element", + "ambient_guest_bridge", + "ambient_ui_components", ] [[package]] @@ -468,6 +809,38 @@ dependencies = [ "toml_datetime", ] +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + [[package]] name = "unicode-ident" version = "1.0.6" @@ -479,3 +852,69 @@ name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/guest/rust/api/api_macros/ambient.toml b/guest/rust/api/api_macros/ambient.toml index 1d5bf83361..128e524d42 100644 --- a/guest/rust/api/api_macros/ambient.toml +++ b/guest/rust/api/api_macros/ambient.toml @@ -68,6 +68,18 @@ How long the previous tick took in seconds. Also known as frametime.""" attributes = ["Debuggable", "Resource"] +[components."core::app::element"] +type = "String" +name = "Element" +description = "The id of the Element which controls this Entity." +attributes = ["Debuggable", "Networked"] + +[components."core::app::element_unmanaged_children"] +type = "Empty" +name = "Element unmanaged children" +description = "If this is set, the user is expected to manage the children of the element themselves." +attributes = ["Debuggable", "Networked"] + [components."core::app::main_scene"] type = "Empty" name = "Main scene" @@ -124,7 +136,7 @@ attributes = ["Debuggable", "Networked", "Store"] type = "F32" name = "Active camera" description = "The camera with the highest `active_camera` value will be used for rendering." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::aspect_ratio"] type = "F32" @@ -132,37 +144,37 @@ name = "Aspect ratio" description = """ The aspect ratio of this camera. If `aspect_ratio_from_window` is set, this will be automatically updated to match the window.""" -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::aspect_ratio_from_window"] type = "Empty" name = "Aspect ratio from window" description = "If attached, the `aspect_ratio` component will be automatically updated to match the aspect ratio of the window." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::far"] type = "F32" name = "Far plane" description = "The far plane of this camera, measured in meters." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::fog"] type = "Empty" name = "Fog" description = "If attached, this camera will see/render fog." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::fovy"] type = "F32" name = "Field of View Y" description = "The field of view of this camera in the Y/vertical direction, measured in radians." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::near"] type = "F32" name = "Near plane" description = "The near plane of this camera, measured in meters." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::orthographic"] type = "Empty" @@ -170,31 +182,37 @@ name = "Orthographic projection" description = """ If attached, this camera will use a standard orthographic projection matrix. Ensure that the `orthographic_` components are set, including `left`, right`, `top` and `bottom`, as well as `near` and `far`.""" -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::orthographic_bottom"] type = "F32" name = "Orthographic bottom" description = "The bottom bound for this `orthographic` camera." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] + +[components."core::camera::orthographic_from_window"] +type = "Empty" +name = "Orthographic from window" +description = "The bounds of this orthographic camera will be updated to match the window automatically." +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::orthographic_left"] type = "F32" name = "Orthographic left" description = "The left bound for this `orthographic` camera." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::orthographic_right"] type = "F32" name = "Orthographic right" description = "The right bound for this `orthographic` camera." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::orthographic_top"] type = "F32" name = "Orthographic top" description = "The top bound for this `orthographic` camera." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::perspective"] type = "Empty" @@ -202,7 +220,7 @@ name = "Perspective projection" description = """ If attached, this camera will use a standard perspective projection matrix. Ensure that `near` and `far` are set.""" -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::perspective_infinite_reverse"] type = "Empty" @@ -210,7 +228,7 @@ name = "Perspective-infinite-reverse projection" description = """ If attached, this camera will use a perspective-infinite-reverse projection matrix. This is well-suited for rendering large worlds as it has no far plane. Ensure `near` is set.""" -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::projection"] type = "Mat4" @@ -218,27 +236,19 @@ name = "Projection" description = """ The projection matrix of this camera. This can be driven by other components, including `perspective` and `perspective_infinite_reverse`.""" -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::projection_view"] type = "Mat4" name = "Projection-view" description = "The composition of the projection and view (inverse-local-to-world) matrices." -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::camera::shadows_far"] type = "F32" name = "Shadows far plane" description = "The far plane for the shadow camera, measured in meters." -attributes = ["Networked", "Store"] - -[components."core::camera::ui_camera"] -type = "Empty" -name = "UI camera" -description = """ -This entity is a camera that is used to render UI. -Ensure that you have the remaining camera components.""" -attributes = ["Networked", "Store"] +attributes = ["Debuggable", "Networked", "Store"] [components."core::ecs::children"] type = { type = "Vec", element_type = "EntityId" } @@ -818,6 +828,8 @@ extends = ["transformable"] "core::camera::near" = 0.10000000149011612 "core::camera::projection" = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] "core::camera::projection_view" = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] +"core::transform::local_to_world" = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] +"core::transform::inv_local_to_world" = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] [concepts.perspective_common_camera] name = "Perspective Common Camera" @@ -856,4 +868,5 @@ extends = ["camera"] "core::camera::orthographic_right" = 1.0 "core::camera::orthographic_top" = 1.0 "core::camera::orthographic_bottom" = -1.0 -"core::camera::far" = 1000.0 +"core::camera::near" = -1.0 +"core::camera::far" = 1.0 diff --git a/guest/rust/api/src/ecs.rs b/guest/rust/api/src/ecs.rs index 817908abf2..db7ee0542e 100644 --- a/guest/rust/api/src/ecs.rs +++ b/guest/rust/api/src/ecs.rs @@ -4,5 +4,5 @@ pub use crate::internal::component::{ GeneralQueryBuilder, QueryEvent, }, Component, ComponentsTuple, Entity, SupportedComponentTypeGet, SupportedComponentTypeSet, - __internal_get_component, + UntypedComponent, __internal_get_component, }; diff --git a/guest/rust/api/src/global/entity_id.rs b/guest/rust/api/src/global/entity_id.rs index 509d7d61a2..a4cdaa7c3b 100644 --- a/guest/rust/api/src/global/entity_id.rs +++ b/guest/rust/api/src/global/entity_id.rs @@ -27,6 +27,14 @@ impl EntityId { id1: u64::from_le_bytes(bytes[8..].try_into().unwrap()), } } + /// Return a null EntityId + pub fn null() -> Self { + Self { id0: 0, id1: 0 } + } + /// Returns true if this is a null EntityId + pub fn is_null(&self) -> bool { + self.id0 == 0 && self.id1 == 0 + } } impl std::fmt::Display for EntityId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/guest/rust/api/src/global/runtime.rs b/guest/rust/api/src/global/runtime.rs index 561e664e84..bd01a841f6 100644 --- a/guest/rust/api/src/global/runtime.rs +++ b/guest/rust/api/src/global/runtime.rs @@ -22,7 +22,7 @@ pub fn frametime() -> f32 { /// If you only want to be notified once, use [once]. /// /// The `callback` is a `fn`. This can be a closure (e.g. `|args| { ... }`). -pub fn on(event: &str, callback: impl Fn(&Entity) -> EventResult + 'static) { +pub fn on(event: &str, mut callback: impl FnMut(&Entity) -> EventResult + 'static) { on_async(event, move |args| std::future::ready(callback(args))) } @@ -33,7 +33,7 @@ pub fn on(event: &str, callback: impl Fn(&Entity) -> EventResult + 'static) { /// The `callback` is a `async fn`. This can be a closure (e.g. `|args| async move { ... }`). pub fn on_async + 'static>( event: &str, - callback: impl Fn(&Entity) -> R + 'static, + mut callback: impl FnMut(&Entity) -> R + 'static, ) { host::event_subscribe(event); EXECUTOR.register_callback( diff --git a/guest/rust/api/src/internal/executor.rs b/guest/rust/api/src/internal/executor.rs index 4f0ccc6fa1..48dd6c3e1a 100644 --- a/guest/rust/api/src/internal/executor.rs +++ b/guest/rust/api/src/internal/executor.rs @@ -11,7 +11,7 @@ use once_cell::sync::Lazy; use crate::{global::EventResult, internal::component::Entity}; type EventFuture = Pin>>; -type EventCallbackFn = Box EventFuture>; +type EventCallbackFn = Box EventFuture>; type EventCallbackFnOnce = Box EventFuture>; // the function is too general to be passed in directly @@ -77,7 +77,7 @@ impl Executor { { let mut new_futures = vec![]; let mut callbacks = self.current_callbacks.borrow_mut(); - if let Some(callbacks) = callbacks.on.get(event_name) { + if let Some(callbacks) = callbacks.on.get_mut(event_name) { for callback in callbacks { new_futures.push(callback(components)); } diff --git a/guest/rust/examples/asset_loading/src/lib.rs b/guest/rust/examples/asset_loading/src/lib.rs index 711cb5f7d1..1e2dd5f6f8 100644 --- a/guest/rust/examples/asset_loading/src/lib.rs +++ b/guest/rust/examples/asset_loading/src/lib.rs @@ -1,5 +1,6 @@ use ambient_api::{ components::core::{ + app::main_scene, game_objects::player_camera, prefab::{prefab_from_url, spawned}, transform::{lookat_center, rotation, translation}, @@ -13,6 +14,7 @@ pub async fn main() -> EventResult { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with_default(player_camera()) + .with_default(main_scene()) .with(translation(), vec3(5., 5., 4.)) .with(lookat_center(), vec3(0., 0., 0.)) .spawn(); diff --git a/guest/rust/examples/minigolf/src/lib.rs b/guest/rust/examples/minigolf/src/lib.rs index ddb25053a4..72b5322730 100644 --- a/guest/rust/examples/minigolf/src/lib.rs +++ b/guest/rust/examples/minigolf/src/lib.rs @@ -119,6 +119,7 @@ pub async fn main() -> EventResult { .with(user_id(), player_user_id.clone()) .with(player_camera_state(), camera_state) .with_default(player_camera()) + .with_default(main_scene()) .with_default(local_to_world()) .with_default(inv_local_to_world()) .with_default(translation()) diff --git a/guest/rust/examples/multiplayer/src/lib.rs b/guest/rust/examples/multiplayer/src/lib.rs index a19a443cb4..fbfff60d9c 100644 --- a/guest/rust/examples/multiplayer/src/lib.rs +++ b/guest/rust/examples/multiplayer/src/lib.rs @@ -1,5 +1,6 @@ use ambient_api::{ components::core::{ + app::main_scene, game_objects::player_camera, player::player, primitives::cube, @@ -15,6 +16,7 @@ pub async fn main() -> EventResult { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with_default(player_camera()) + .with_default(main_scene()) .with(translation(), Vec3::ONE * 5.) .with(lookat_center(), vec3(0., 0., 0.)) .spawn(); diff --git a/guest/rust/examples/physics/src/lib.rs b/guest/rust/examples/physics/src/lib.rs index 12c4d074f8..c252a5506d 100644 --- a/guest/rust/examples/physics/src/lib.rs +++ b/guest/rust/examples/physics/src/lib.rs @@ -1,5 +1,6 @@ use ambient_api::{ components::core::{ + app::main_scene, ecs::ids, game_objects::player_camera, physics::{ @@ -21,6 +22,7 @@ pub async fn main() -> EventResult { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with_default(player_camera()) + .with_default(main_scene()) .with(translation(), vec3(5., 5., 4.)) .with(lookat_center(), vec3(0., 0., 0.)) .spawn(); diff --git a/guest/rust/examples/primitives/src/lib.rs b/guest/rust/examples/primitives/src/lib.rs index f2b6ecab7d..e1cd61e59a 100644 --- a/guest/rust/examples/primitives/src/lib.rs +++ b/guest/rust/examples/primitives/src/lib.rs @@ -1,5 +1,6 @@ use ambient_api::{ components::core::{ + app::main_scene, game_objects::player_camera, primitives::{cube, quad, sphere_radius, sphere_sectors, sphere_stacks}, rendering::color, @@ -14,6 +15,7 @@ pub async fn main() -> EventResult { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with_default(player_camera()) + .with_default(main_scene()) .with(translation(), vec3(5., 5., 6.)) .with(lookat_center(), vec3(0., 0., 2.)) .spawn(); diff --git a/guest/rust/examples/raw_text/Cargo.toml b/guest/rust/examples/raw_text/Cargo.toml new file mode 100644 index 0000000000..5b27b208a2 --- /dev/null +++ b/guest/rust/examples/raw_text/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "raw_text" +edition = "2021" +version = { workspace = true } +publish = false + +[dependencies] +ambient_api = { path = "../../api" } + +[lib] +crate-type = ["cdylib"] +required-features = [] diff --git a/guest/rust/examples/raw_text/ambient.toml b/guest/rust/examples/raw_text/ambient.toml new file mode 100644 index 0000000000..d60216cd9c --- /dev/null +++ b/guest/rust/examples/raw_text/ambient.toml @@ -0,0 +1,4 @@ +[project] +id = "raw_text" +name = "Raw Text" +version = "0.0.1" diff --git a/guest/rust/examples/raw_text/src/lib.rs b/guest/rust/examples/raw_text/src/lib.rs new file mode 100644 index 0000000000..56cd9b59fb --- /dev/null +++ b/guest/rust/examples/raw_text/src/lib.rs @@ -0,0 +1,38 @@ +use ambient_api::{ + components::core::{ + app::main_scene, + game_objects::player_camera, + rendering::color, + transform::{ + local_to_world, lookat_center, mesh_to_local, mesh_to_world, scale, translation, + }, + ui::text, + }, + concepts::{make_perspective_infinite_reverse_camera, make_transformable}, + prelude::*, +}; + +#[main] +pub async fn main() -> EventResult { + Entity::new() + .with_merge(make_perspective_infinite_reverse_camera()) + .with_default(player_camera()) + .with_default(main_scene()) + .with(translation(), vec3(5., 5., 4.)) + .with(lookat_center(), vec3(0., 0., 0.)) + .spawn(); + + Entity::new() + .with_merge(make_transformable()) + .with(text(), "Hello world".to_string()) + .with(color(), vec4(1., 1., 1., 1.)) + .with(translation(), vec3(0., 0., 0.01)) + .with(scale(), Vec3::ONE * 0.05) + .with_default(local_to_world()) + .with_default(mesh_to_local()) + .with_default(mesh_to_world()) + .with_default(main_scene()) + .spawn(); + + EventOk +} diff --git a/guest/rust/examples/skinmesh/src/lib.rs b/guest/rust/examples/skinmesh/src/lib.rs index 4b50810d50..a1be174f74 100644 --- a/guest/rust/examples/skinmesh/src/lib.rs +++ b/guest/rust/examples/skinmesh/src/lib.rs @@ -1,5 +1,6 @@ use ambient_api::{ components::core::{ + app::main_scene, game_objects::player_camera, player::player, prefab::prefab_from_url, @@ -18,6 +19,7 @@ pub async fn main() -> EventResult { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with_default(player_camera()) + .with_default(main_scene()) .with(translation(), vec3(2., 2., 3.0)) .with(lookat_center(), vec3(0., 0., 1.)) .spawn(); diff --git a/guest/rust/examples/sun/src/lib.rs b/guest/rust/examples/sun/src/lib.rs index 23a6cd95b3..ccdd0ec13f 100644 --- a/guest/rust/examples/sun/src/lib.rs +++ b/guest/rust/examples/sun/src/lib.rs @@ -15,6 +15,7 @@ pub async fn main() -> EventResult { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with_default(player_camera()) + .with_default(main_scene()) .with(translation(), vec3(5., 5., 2.)) .with(lookat_center(), vec3(0., 0., 1.)) .spawn(); diff --git a/guest/rust/examples/text/Cargo.toml b/guest/rust/examples/text/Cargo.toml index 4d962042e2..f344e439de 100644 --- a/guest/rust/examples/text/Cargo.toml +++ b/guest/rust/examples/text/Cargo.toml @@ -7,6 +7,13 @@ publish = false [dependencies] ambient_api = { path = "../../api" } +ambient_element = { path = "../../../../crates/element", default-features = false, features = [ + "guest", +] } +ambient_ui_components = { path = "../../../../crates/ui_components" } +ambient_guest_bridge = { path = "../../../../crates/guest_bridge", default-features = false, features = [ + "guest", +] } [lib] crate-type = ["cdylib"] diff --git a/guest/rust/examples/text/src/lib.rs b/guest/rust/examples/text/src/lib.rs index 3659f251ed..01132a27b7 100644 --- a/guest/rust/examples/text/src/lib.rs +++ b/guest/rust/examples/text/src/lib.rs @@ -1,37 +1,51 @@ use ambient_api::{ components::core::{ - app::main_scene, + app::ui_scene, + camera::{orthographic_bottom, orthographic_left, orthographic_right, orthographic_top}, game_objects::player_camera, - rendering::color, - transform::{ - local_to_world, lookat_center, mesh_to_local, mesh_to_world, scale, translation, - }, - ui::text, }, - concepts::{make_perspective_infinite_reverse_camera, make_transformable}, + concepts::make_orthographic_camera, prelude::*, }; +use ambient_element::{element_component, Element, ElementComponentExt, Hooks}; +use ambient_guest_bridge::ecs::World; +use ambient_ui_components::text::Text; + +#[element_component] +fn App(hooks: &mut Hooks) -> Element { + let (count, set_count) = hooks.use_state(0); + hooks.use_spawn(move |_| { + run_async(async move { + let mut count = 0; + loop { + sleep(0.5).await; + count += 1; + set_count(count); + } + }); + Box::new(|_| {}) + }); + println!("{}", count); + Text::el(format!("Hello world: {}", count)) +} #[main] pub async fn main() -> EventResult { Entity::new() - .with_merge(make_perspective_infinite_reverse_camera()) + .with_merge(make_orthographic_camera()) + .with(orthographic_left(), -300.) + .with(orthographic_right(), 300.) + .with(orthographic_top(), -300.) + .with(orthographic_bottom(), 300.) .with_default(player_camera()) - .with(translation(), vec3(5., 5., 4.)) - .with(lookat_center(), vec3(0., 0., 0.)) + .with_default(ui_scene()) .spawn(); - Entity::new() - .with_merge(make_transformable()) - .with(text(), "Hello world".to_string()) - .with(color(), vec4(1., 1., 1., 1.)) - .with(translation(), vec3(0., 0., 0.01)) - .with(scale(), Vec3::ONE * 0.05) - .with_default(local_to_world()) - .with_default(mesh_to_local()) - .with_default(mesh_to_world()) - .with_default(main_scene()) - .spawn(); + let mut tree = App.el().spawn_tree(); + on(ambient_api::event::FRAME, move |_| { + tree.update(&mut World); + EventOk + }); EventOk } diff --git a/guest/rust/examples/third_person_camera/src/lib.rs b/guest/rust/examples/third_person_camera/src/lib.rs index 05aef2c5e0..f1cf897b6a 100644 --- a/guest/rust/examples/third_person_camera/src/lib.rs +++ b/guest/rust/examples/third_person_camera/src/lib.rs @@ -8,7 +8,7 @@ use ambient_api::{ player::{player, user_id}, primitives::{cube, quad}, rendering::color, - transform::{lookat_center, rotation, scale, translation}, + transform::{lookat_center, rotation, scale, translation}, app::main_scene, }, concepts::{make_perspective_infinite_reverse_camera, make_sphere, make_transformable}, player::KeyCode, @@ -40,6 +40,7 @@ pub async fn main() -> EventResult { let camera = Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with_default(player_camera()) + .with_default(main_scene()) .with(user_id(), user) .with(translation(), Vec3::ONE * 5.) .with(lookat_center(), vec3(0., 0., 0.)) diff --git a/guest/rust/examples/tictactoe/src/lib.rs b/guest/rust/examples/tictactoe/src/lib.rs index a037a95b51..002ce48e52 100644 --- a/guest/rust/examples/tictactoe/src/lib.rs +++ b/guest/rust/examples/tictactoe/src/lib.rs @@ -1,6 +1,7 @@ use ambient_api::{ components::core::{ self, + app::main_scene, game_objects::player_camera, player::player, primitives::cube, @@ -18,6 +19,7 @@ pub async fn main() -> EventResult { Entity::new() .with_merge(make_perspective_infinite_reverse_camera()) .with_default(player_camera()) + .with_default(main_scene()) .with(translation(), vec3(3., 3., 2.5)) .with(lookat_center(), vec3(1.5, 1.5, 0.)) .spawn(); diff --git a/libs/cb/Cargo.toml b/libs/cb/Cargo.toml new file mode 100644 index 0000000000..e0842abdac --- /dev/null +++ b/libs/cb/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cb" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/crates/std/src/cb.rs b/libs/cb/src/lib.rs similarity index 85% rename from crates/std/src/cb.rs rename to libs/cb/src/lib.rs index b29a535aba..4f40192dfc 100644 --- a/crates/std/src/cb.rs +++ b/libs/cb/src/lib.rs @@ -28,13 +28,6 @@ pub fn cb(f: T) -> Cb { Arc::new(CbDebuggable(f)) } -pub fn log_error(err: &anyhow::Error) { - #[cfg(feature = "sentry")] - sentry_anyhow::capture_anyhow(err); - #[cfg(not(feature = "sentry"))] - tracing::error!("{:?}", err); -} - pub type CallbackFn = Cb U + Sync + Send + 'static>; pub type CallbackBox = Box U + Sync + Send + 'static>; diff --git a/libs/friendly_id/Cargo.toml b/libs/friendly_id/Cargo.toml new file mode 100644 index 0000000000..bf585ddd90 --- /dev/null +++ b/libs/friendly_id/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "friendly_id" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = { workspace = true } diff --git a/crates/std/src/uncategorized/id.rs b/libs/friendly_id/src/lib.rs similarity index 100% rename from crates/std/src/uncategorized/id.rs rename to libs/friendly_id/src/lib.rs