diff --git a/common-api/src/lib.rs b/common-api/src/lib.rs index 8d49480..00113b7 100755 --- a/common-api/src/lib.rs +++ b/common-api/src/lib.rs @@ -26,7 +26,7 @@ use lyra::api::ecs::{EcsDynamicView, EcsWorld, Entity, WasmTypeId}; impl WasmTypeId { pub fn of() -> Self { Self { - inner: unsafe { mem::transmute(TypeId::of::()) } + inner: unsafe { mem::transmute(TypeId::of::()) }, } } } @@ -74,16 +74,53 @@ impl<'a> World<'a> { } } + /// Create a new [`Entity`] in the world with a component [`Bundle`]. + /// + /// + /// ```nobuild + /// let new_entity = world.spawn((Vec3::new(10.0, 1.0, 10.0), PlayerMovement::new())); + /// ``` pub fn spawn(&self, bundle: B) -> Result { let infos = B::component_info(); let bundle = bundle.to_bytes()?; Ok(self.inner.spawn(&bundle, &infos)) } + /// Insert a bundle into an existing entity. + /// + /// If the components already exist on the entity, they will be updated, else the entity will + /// be moved to a different Archetype that can store the entity. That may involve creating + /// a new Archetype. + pub fn insert(&self, en: Entity, bundle: B) -> Result<(), ComponentSerializationError> { + let infos = B::component_info(); + let bundle = bundle.to_bytes()?; + Ok(self.inner.insert(en, &bundle, &infos)) + } + + /// Query components from entities. + /// + /// ```rust + /// # use common_api::{math::{Vec3, Vec4}, World}; + /// # let world = World::new(); + /// + /// let view = world.view::<(Vec3, Vec4)>(); + /// for row in view { + /// let (en, (p3, p4)) = row.unwrap(); + /// println!("Entity: {}", en.id.id); + /// println!(" vec3: {:?}", p3); + /// println!(" vec4: {:?}", p4); + /// } + /// ``` pub fn view(&self) -> View { let infos = B::component_info(); View::from(self.inner.view(&infos)) } + + pub fn view_one(&self, en: Entity) -> Option> { + let infos = B::component_info(); + self.inner.view_one(en, &infos) + .map(|bytes| B::from_bytes(bytes)) + } } pub struct View { @@ -95,7 +132,7 @@ impl From for View { fn from(value: EcsDynamicView) -> Self { Self { inner: value, - _marker: PhantomData + _marker: PhantomData, } } } @@ -111,13 +148,13 @@ impl View { } impl Iterator for View { - type Item = Result; + type Item = Result<(Entity, B), ComponentSerializationError>; fn next(&mut self) -> Option { - if let Some(row) = self.inner.next() { - Some(B::from_bytes(row)) + if let Some((en, row)) = self.inner.next() { + Some(B::from_bytes(row).map(|b| (en, b))) } else { None } } -} \ No newline at end of file +} diff --git a/common-api/wit/ecs.wit b/common-api/wit/ecs.wit index 5e717e5..6fd2d71 100644 --- a/common-api/wit/ecs.wit +++ b/common-api/wit/ecs.wit @@ -34,7 +34,7 @@ interface ecs { /// Get the bytes of the next row in the view. /// /// A row contains multiple component serialized as bytes. The buffer is tighly packed. - next: func() -> option>; + next: func() -> option>>; } resource ecs-world { @@ -49,14 +49,33 @@ interface ecs { /// and specify the layouts of them. spawn: func(components: list, component-infos: list) -> entity; + /// Insert components into an existing entity. + /// + /// Parameters: + /// * `en`: The entity to insert into. + /// * `components`: A tightly packed byte buffer containing the components to spawn + /// with the entity. This expects the components in the same order of `component-infos`. + /// * `component-infos`: A list of `component-infos` uses to identify the components + /// and specify the layouts of them. + insert: func(en: entity, components: list, component-infos: list); + /// Query for a list of entities and their components. /// /// Parameters: - /// * `component-infos`: The `component-info`'s of the components that you are querying. + /// * `component-infos`: The `component-info`'s of the components that you are querying. /// /// Returns: an iterator that returns the byte buffers of each row. view: func(component-infos: list) -> ecs-dynamic-view; + /// Query for components from a single entity. + /// + /// Parameters: + /// * `en`: The entity to query components from. + /// * `component-infos`: The `component-info`'s of the components that you are querying. + /// + /// Returns: A row of components serialized as bytes. The buffer is tighly packed. + view-one: func(en: entity, component-infos: list) -> option>; + //with_system: func(stage: string, component-infos: list, system: func(components: list)); } } \ No newline at end of file diff --git a/lyra-engine b/lyra-engine index 12c8ece..60c139f 160000 --- a/lyra-engine +++ b/lyra-engine @@ -1 +1 @@ -Subproject commit 12c8ece4183f117864bac362d181ea5906141c6f +Subproject commit 60c139f9b2563b4c2d809031ecac336badc63965 diff --git a/src/main.rs b/src/main.rs index bd39ef6..751c3e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ use std::alloc::Layout; -use std::borrow::Borrow; use std::ptr::NonNull; use anyhow::{anyhow, Context}; use async_trait::async_trait; use component::witguest::math; use ecs::query::dynamic::{DynamicViewState, DynamicViewStateIter, QueryDynamicType}; -use lyra_ecs::{Component, World}; +use lyra_ecs::query::dynamic::DynamicViewOne; +use lyra_ecs::World; use lyra_ecs as ecs; @@ -14,50 +14,16 @@ use slab::Slab; use thiserror::Error; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; -use wasmtime::component::{Resource as WasmResource, ResourceAny}; - -use wasmtime::component::Val as ComponentVal; +use wasmtime::component::Resource as WasmResource; #[allow(unused_imports)] pub(crate) mod lyra_engine { pub use lyra_ecs as ecs; } -/* mod bindings { - wasmtime::component::bindgen!({ - world: "imports", - path: "common-api/wit", - //tracing: true, - async: true, - }); - - mod component { - wasmtime::component::bindgen!({ - world: "example", - path: "witguest/wit", - //tracing: true, - async: true, - - with: { - "lyra:api": super::lyra::api, - } - }); - } -} - -use bindings::lyra::api::ecs as wasm_ecs; */ - -/* wasmtime::component::bindgen!({ - world: "imports", - path: "common-api/wit", - //tracing: true, - async: true, -}); */ - wasmtime::component::bindgen!({ world: "example", path: "witguest/wit", - //tracing: true, async: true, with: { @@ -96,14 +62,6 @@ impl Into for wasm_ecs::Entity { } } -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Component)] -struct Vec3 { - x: f32, - y: f32, - z: f32, -} - struct WorldEntry { world: lyra_ecs::World, /// Indices of dynamic views that are still alive @@ -189,6 +147,35 @@ impl wasm_ecs::HostEcsWorld for Imports { en.into() } + async fn insert( + &mut self, + this: WasmResource, + entity: Entity, + mut components: Vec, + infos: Vec, + ) { + let world_entry = self + .world_slab + .get_mut(this.rep() as _) + .ok_or(WasmError::InvalidResourceHandle("EcsWorld")) + .unwrap(); + let world = &mut world_entry.world; + + // add the components in the tightly packed `components` buffer into the dynamic bundle + let mut bundle = ecs::DynamicBundle::new(); + let mut offset = 0; + for info in infos { + // SAFETY: The components are tightly packed, adding the offset to the pointer will + // get the next component in the buffer. + let data_ptr = unsafe { NonNull::new_unchecked(components.as_mut_ptr().add(offset)) }; + bundle.push_unknown(data_ptr, info.clone().into()); + + offset += info.size as usize; + } + + world.insert(entity.into(), bundle); + } + async fn view( &mut self, this: wasmtime::component::Resource, @@ -197,6 +184,57 @@ impl wasm_ecs::HostEcsWorld for Imports { ::new(self, this, infos).await } + async fn view_one( + &mut self, + this: wasmtime::component::Resource, + entity: Entity, + infos: Vec, + ) -> Option> { + let world_entry = self + .world_slab + .get_mut(this.rep() as _) + .ok_or(WasmError::InvalidResourceHandle("EcsWorld")) + .unwrap(); + let world = &mut world_entry.world; + + + //world.view_one(entity.into()); + let queries = infos.into_iter() + .map(|i| QueryDynamicType::from_info(i.into())) + .collect(); + let v = DynamicViewOne::new_with(&world, entity.into(), queries); + + if let Some(row) = v.get() { + let row_len = row.iter().map(|d| d.info.layout().size()).sum(); + let mut row_buf = Vec::::with_capacity(row_len); + + let mut offset = 0; + for comp in row { + let comp_size = comp.info.layout().size(); + unsafe { + // SAFETY: the vec has the capacity to store this component because it was + // created with Vec::with_capacity. + let dst = row_buf.as_mut_ptr().add(offset); + + std::ptr::copy_nonoverlapping( + comp.ptr.as_ptr(), + dst, + comp.info.layout().size(), + ); + + // make sure to tell the vec that it now has the bytes of this component + row_buf.set_len(row_buf.len() + comp_size); + } + + offset += comp_size; + } + + Some(row_buf) + } else { + None + } + } + fn drop( &mut self, this: wasmtime::component::Resource, @@ -248,13 +286,13 @@ impl wasm_ecs::HostEcsDynamicView for Imports { WasmResource::new_own(view_index as _) } - async fn next(&mut self, view: WasmResource) -> Option> { + async fn next(&mut self, view: WasmResource) -> Option<(Entity, Vec)> { let view_entry = self.world_views_slab.get_mut(view.rep() as _).unwrap(); let world_entry = self.world_slab.get(view_entry.world_index).unwrap(); let view = &mut view_entry.view; // get the next row in the view and copy the returned components into a tightly packed - if let Some(row) = view.next(&world_entry.world) { + if let Some((en, row)) = view.next(&world_entry.world) { let row_len = row.iter().map(|d| d.info.layout().size()).sum(); let mut row_buf = Vec::::with_capacity(row_len); @@ -279,7 +317,7 @@ impl wasm_ecs::HostEcsDynamicView for Imports { offset += comp_size; } - Some(row_buf) + Some((en.into(), row_buf)) } else { None } @@ -303,6 +341,39 @@ impl wasm_ecs::HostEcsDynamicView for Imports { } } +/* fn dynamic_view_next_impl() -> Option<(Entity, Vec)> { + // get the next row in the view and copy the returned components into a tightly packed + if let Some((en, row)) = view.next(&world_entry.world) { + let row_len = row.iter().map(|d| d.info.layout().size()).sum(); + let mut row_buf = Vec::::with_capacity(row_len); + + let mut offset = 0; + for comp in row { + let comp_size = comp.info.layout().size(); + unsafe { + // SAFETY: the vec has the capacity to store this component because it was + // created with Vec::with_capacity. + let dst = row_buf.as_mut_ptr().add(offset); + + std::ptr::copy_nonoverlapping( + comp.ptr.as_ptr(), + dst, + comp.info.layout().size(), + ); + + // make sure to tell the vec that it now has the bytes of this component + row_buf.set_len(row_buf.len() + comp_size); + } + + offset += comp_size; + } + + Some((en.into(), row_buf)) + } else { + None + } +} */ + #[async_trait] impl wasm_ecs::Host for Imports {} diff --git a/witguest/src/lib.rs b/witguest/src/lib.rs index 8d9d4a8..9a765e7 100644 --- a/witguest/src/lib.rs +++ b/witguest/src/lib.rs @@ -13,9 +13,14 @@ struct Component; impl Guest for Component { fn on_init(world: &EcsWorld, _owning_entity: Entity) -> Result<(), ()> { let world = World::from(world); - let en = world.spawn((Vec3::new(7.0, 30.0, 18.0), Vec4::new(10.0, 332.35, 329.0, 95.0))); + let en = world.spawn((Vec3::new(7.0, 30.0, 18.0), Vec4::new(10.0, 332.35, 329.0, 95.0))) + .unwrap(); - println!("guest spawned {:?}", en); + println!("Spawned {:?}", en); + + let pos3 = world.view_one::(en) + .unwrap().unwrap(); + println!("Found entity at {pos3:?}"); Ok(()) } @@ -25,9 +30,10 @@ impl Guest for Component { let view = world.view::<(Vec3, Vec4)>(); for pos in view { - let (p3, p4) = pos.unwrap(); - println!("Retrieved vec3: {:?}", p3); - println!("Retrieved vec4: {:?}", p4); + let (en, (p3, p4)) = pos.unwrap(); + println!("Entity: {}", en.id.id); + println!(" vec3: {:?}", p3); + println!(" vec4: {:?}", p4); } Ok(()) diff --git a/witguest/wit/deps/lyraapi/ecs.wit b/witguest/wit/deps/lyraapi/ecs.wit index 5e717e5..6fd2d71 100644 --- a/witguest/wit/deps/lyraapi/ecs.wit +++ b/witguest/wit/deps/lyraapi/ecs.wit @@ -34,7 +34,7 @@ interface ecs { /// Get the bytes of the next row in the view. /// /// A row contains multiple component serialized as bytes. The buffer is tighly packed. - next: func() -> option>; + next: func() -> option>>; } resource ecs-world { @@ -49,14 +49,33 @@ interface ecs { /// and specify the layouts of them. spawn: func(components: list, component-infos: list) -> entity; + /// Insert components into an existing entity. + /// + /// Parameters: + /// * `en`: The entity to insert into. + /// * `components`: A tightly packed byte buffer containing the components to spawn + /// with the entity. This expects the components in the same order of `component-infos`. + /// * `component-infos`: A list of `component-infos` uses to identify the components + /// and specify the layouts of them. + insert: func(en: entity, components: list, component-infos: list); + /// Query for a list of entities and their components. /// /// Parameters: - /// * `component-infos`: The `component-info`'s of the components that you are querying. + /// * `component-infos`: The `component-info`'s of the components that you are querying. /// /// Returns: an iterator that returns the byte buffers of each row. view: func(component-infos: list) -> ecs-dynamic-view; + /// Query for components from a single entity. + /// + /// Parameters: + /// * `en`: The entity to query components from. + /// * `component-infos`: The `component-info`'s of the components that you are querying. + /// + /// Returns: A row of components serialized as bytes. The buffer is tighly packed. + view-one: func(en: entity, component-infos: list) -> option>; + //with_system: func(stage: string, component-infos: list, system: func(components: list)); } } \ No newline at end of file