diff --git a/common-api/src/bundle.rs b/common-api/src/bundle.rs new file mode 100644 index 0000000..2a0ffc4 --- /dev/null +++ b/common-api/src/bundle.rs @@ -0,0 +1,147 @@ +use crate::{lyra::api::ecs::ComponentInfo, Component, ComponentSerializationError}; + +use std::collections::VecDeque; +use std::io::Read; + +#[derive(Default)] +pub struct ComponentBundle { + pub(crate) bundle: Vec, + pub(crate) infos: Vec, +} + +impl ComponentBundle { + pub fn new() -> Self { + Self::default() + } + + pub fn push(&mut self, c: C) { + let info = C::component_info(); + self.infos.push(info); + + c.to_bytes_into(&mut self.bundle).unwrap(); + } +} + +pub trait Bundle: Sized { + fn component_info() -> Vec; + fn to_bytes(self) -> Result, ComponentSerializationError>; + fn from_bytes(bytes: Vec) -> Result; +} + +macro_rules! impl_into_bundle_tuple { + ($last: tt, $( $name: tt ),+) => { + + #[allow(non_snake_case)] + impl<$($name: Component,)* $last: Component> Bundle for ($($name,)* $last,) { + fn component_info() -> Vec { + vec![$($name::component_info(),)+ $last::component_info()] + } + + fn to_bytes(self) -> Result, ComponentSerializationError> { + let ($($name,)+ $last,) = self; + + let mut bytes = vec![]; + $(bytes.extend($name.to_bytes()?.into_iter());)+ + bytes.extend($last.to_bytes()?.into_iter()); + + Ok(bytes) + } + + fn from_bytes(bytes: Vec) -> Result { + let len = { + let mut acc = 0; + $(acc += $name::component_info().size;)+ + acc + }; + if bytes.len() < len as _ { + return Err(ComponentSerializationError::ExpectedEndOfBuffer); + } + + let mut bytes = VecDeque::from(bytes); + + $( + let info = $name::component_info(); + let mut part_bytes = Vec::new(); + part_bytes.resize(info.size as _, 0u8); + bytes.read_exact(&mut part_bytes) + .map_err(|_| ComponentSerializationError::ExpectedEndOfBuffer)?; + + let $name = $name::from_bytes(&part_bytes)?; + )+ + + let info = $last::component_info(); + let mut part_bytes = Vec::new(); + part_bytes.resize(info.size as _, 0u8); + bytes.read_exact(&mut part_bytes) + .map_err(|_| ComponentSerializationError::ExpectedEndOfBuffer)?; + + let $last = $last::from_bytes(&part_bytes)?; + + Ok(($($name,)+ $last,)) + } + } + + impl_into_bundle_tuple!($( $name ),+); + }; + + ($only: tt) => { + #[allow(non_snake_case)] + impl<$only: Component> Bundle for ($only,) { + fn component_info() -> Vec { + vec![$only::component_info()] + } + + fn to_bytes(self) -> Result, ComponentSerializationError> { + let mut bytes = vec![]; + bytes.extend(self.0.to_bytes()?.into_iter()); + + Ok(bytes) + } + + fn from_bytes(bytes: Vec) -> Result { + let mut bytes = VecDeque::from(bytes); + + let info = $only::component_info(); + let mut part_bytes = Vec::new(); + part_bytes.resize(info.size as _, 0u8); + bytes.read_exact(&mut part_bytes) + .map_err(|_| ComponentSerializationError::ExpectedEndOfBuffer)?; + + let $only = $only::from_bytes(&part_bytes)?; + + Ok(($only,)) + } + } + + #[allow(non_snake_case)] + impl<$only: Component> Bundle for $only { + fn component_info() -> Vec { + vec![$only::component_info()] + } + + fn to_bytes(self) -> Result, ComponentSerializationError> { + let mut bytes = vec![]; + bytes.extend(::to_bytes(&self)?.into_iter()); + + Ok(bytes) + } + + fn from_bytes(bytes: Vec) -> Result { + let mut bytes = VecDeque::from(bytes); + + let info = $only::component_info(); + let mut part_bytes = Vec::new(); + part_bytes.resize(info.size as _, 0u8); + bytes.read_exact(&mut part_bytes) + .map_err(|_| ComponentSerializationError::ExpectedEndOfBuffer)?; + + let $only = $only::from_bytes(&part_bytes)?; + + Ok($only) + } + } + }; +} + +//impl_into_bundle_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16 ); +impl_into_bundle_tuple!(C1, C2); \ No newline at end of file diff --git a/common-api/src/component.rs b/common-api/src/component.rs new file mode 100644 index 0000000..ae68f2b --- /dev/null +++ b/common-api/src/component.rs @@ -0,0 +1,70 @@ +use std::alloc::Layout; + +use crate::lyra::api::ecs::{ComponentInfo, WasmTypeId}; + +impl ComponentInfo { + pub fn of() -> Self { + let layout = Layout::new::(); + Self { + size: layout.size() as _, + alignment: layout.align() as _, + type_id: WasmTypeId::of::(), + } + } +} + +#[derive(Debug)] +pub enum ComponentSerializationMethod { + /// Serializes the component into bytes using [`bytemuck::bytes_of`]. + Bytemuck, + /// Serializes the component into bytes using + /// [bincode](https://github.com/bincode-org/bincode). + Bincode +} + +#[derive(Debug, thiserror::Error)] +pub enum ComponentSerializationError { + #[error("{0}")] + Bytemuck(bytemuck::PodCastError), + #[error("unexpected end of buffer")] + ExpectedEndOfBuffer, +} + +pub trait Component: Sized + 'static { + const SERIALIZATION: ComponentSerializationMethod; + + fn component_info() -> ComponentInfo; + fn to_bytes(&self) -> Result, ComponentSerializationError>; + fn to_bytes_into(&self, target: &mut Vec) -> Result<(), ComponentSerializationError>; + + fn from_bytes(bytes: &[u8]) -> Result; +} + +#[macro_export] +macro_rules! bytemuck_component_impl { + ($type:ident) => { + impl common_api::Component for $type { + const SERIALIZATION: common_api::ComponentSerializationMethod = common_api::ComponentSerializationMethod::Bytemuck; + + fn component_info() -> common_api::bindings::lyra::api::ecs::ComponentInfo { + common_api::bindings::lyra::api::ecs::ComponentInfo::of::<$type>() + } + + fn to_bytes(&self) -> Result, common_api::ComponentSerializationError> { + Ok(common_api::bytemuck::bytes_of(self).to_vec()) + } + + fn to_bytes_into(&self, target: &mut Vec) -> Result<(), common_api::ComponentSerializationError> { + let bytes = self.to_bytes()?; + target.extend(bytes.into_iter()); + Ok(()) + } + + fn from_bytes(bytes: &[u8]) -> Result { + common_api::bytemuck::try_from_bytes::(&bytes) + .map_err(|e| common_api::ComponentSerializationError::Bytemuck(e)) + .cloned() + } + } + }; +} \ No newline at end of file diff --git a/common-api/src/lib.rs b/common-api/src/lib.rs index 985f7dd..6134ae0 100755 --- a/common-api/src/lib.rs +++ b/common-api/src/lib.rs @@ -1,24 +1,23 @@ -/* pub mod bindings { - wasmtime::component::bindgen!({ - world: "example", - path: "../witguest/wit/world.wit", - //tracing: true, - async: true, - }); -} - -use bindings::{component::witguest::ecs::EcsWorld, *}; */ - -use std::{alloc::Layout, any::TypeId, marker::PhantomData, mem}; +use std::{any::TypeId, marker::PhantomData, mem}; pub use bytemuck; -wit_bindgen::generate!({ - world: "api", - path: "wit", -}); +mod component; +pub use component::*; -use lyra::api::ecs::{ComponentInfo, EcsDynamicView, EcsWorld, Entity, WasmTypeId}; +mod bundle; +pub use bundle::*; + +pub mod bindings { + wit_bindgen::generate!({ + world: "api", + path: "wit", + }); +} + +use bindings::*; + +use lyra::api::ecs::{EcsDynamicView, EcsWorld, Entity, WasmTypeId}; impl WasmTypeId { pub fn of() -> Self { @@ -28,95 +27,6 @@ impl WasmTypeId { } } -impl ComponentInfo { - pub fn of() -> Self { - let layout = Layout::new::(); - Self { - size: layout.size() as _, - alignment: layout.align() as _, - type_id: WasmTypeId::of::(), - } - } -} - -#[derive(Debug)] -pub enum ComponentSerializationMethod { - /// Serializes the component into bytes using [`bytemuck::bytes_of`]. - Bytemuck, - /// Serializes the component into bytes using - /// [bincode](https://github.com/bincode-org/bincode). - Bincode -} - -#[derive(Debug, thiserror::Error)] -pub enum ComponentSerializationError { - #[error("{0}")] - Bytemuck(bytemuck::PodCastError), -} - -pub trait Component: Sized + 'static { - const SERIALIZATION: ComponentSerializationMethod; - - fn component_info() -> ComponentInfo; - fn to_bytes(&self) -> Result, ComponentSerializationError>; - fn to_bytes_into(&self, target: &mut Vec) -> Result<(), ComponentSerializationError>; - - fn from_bytes(bytes: &[u8]) -> Result; -} - -#[macro_export] -macro_rules! bytemuck_component_impl { - ($type:ident) => { - impl common_api::Component for $type { - const SERIALIZATION: common_api::ComponentSerializationMethod = common_api::ComponentSerializationMethod::Bytemuck; - - fn component_info() -> common_api::lyra::api::ecs::ComponentInfo { - common_api::lyra::api::ecs::ComponentInfo::of::<$type>() - } - - fn to_bytes(&self) -> Result, common_api::ComponentSerializationError> { - Ok(common_api::bytemuck::bytes_of(self).to_vec()) - } - - fn to_bytes_into(&self, target: &mut Vec) -> Result<(), common_api::ComponentSerializationError> { - let bytes = self.to_bytes()?; - target.extend(bytes.into_iter()); - Ok(()) - } - - fn from_bytes(bytes: &[u8]) -> Result { - common_api::bytemuck::try_from_bytes::(&bytes) - .map_err(|e| common_api::ComponentSerializationError::Bytemuck(e)) - .cloned() - } - } - }; -} - -#[derive(Default)] -pub struct ComponentBundle { - bundle: Vec, - infos: Vec, -} - -impl ComponentBundle { - pub fn new() -> Self { - Self::default() - } - - pub fn push(&mut self, c: C) { - let info = C::component_info(); - self.infos.push(info); - - c.to_bytes_into(&mut self.bundle); - /* self.bundle.reserve(info.size as _); - self.bundle.extend(comp_bytes.into_iter()); */ - - // dont call the destructor on C to avo - //mem::forget(c); - } -} - pub struct World { inner: EcsWorld, } @@ -128,33 +38,48 @@ impl World { } } - pub fn spawn(&self, components: ComponentBundle) -> Entity { - self.inner.spawn(&components.bundle, &components.infos) + pub fn spawn(&self, bundle: B) -> Result { + let infos = B::component_info(); + let bundle = bundle.to_bytes()?; + Ok(self.inner.spawn(&bundle, &infos)) } - pub fn view(&self, components: &[ComponentInfo]) -> View { - View::from(self.inner.view(components)) + pub fn view(&self) -> View { + let infos = B::component_info(); + View::from(self.inner.view(&infos)) } } -pub struct View { +pub struct View { inner: EcsDynamicView, + _marker: PhantomData, } -impl From for View { +impl From for View { fn from(value: EcsDynamicView) -> Self { Self { - inner: value + inner: value, + _marker: PhantomData } } } -impl View { - pub fn next(&mut self) -> Option { - let row = self.inner.next(); +impl View { + pub fn into_dynamic(self) -> EcsDynamicView { + self.inner + } - if let Some(row) = row { - Some(C::from_bytes(&row).unwrap()) + pub fn as_dynamic(&self) -> &EcsDynamicView { + &self.inner + } +} + +impl Iterator for View { + type Item = Result; + + fn next(&mut self) -> Option { + if let Some(row) = self.inner.next() { + Some(B::from_bytes(row)) } else { None } diff --git a/common-api/wit/ecs.wit b/common-api/wit/ecs.wit index c416c83..e547541 100644 --- a/common-api/wit/ecs.wit +++ b/common-api/wit/ecs.wit @@ -11,26 +11,49 @@ interface ecs { } record wasm-type-id { + // represents a u128, can be converted into that with mem::transmute inner: tuple, } record component-info { + /// The size of the component in memory. size: u64, + /// The alignment of the component in memory. alignment: u64, + /// The type id of the component. + /// + /// This must be unique between component types since its used to identify the components + /// in spawning and querying. type-id: wasm-type-id, // a u128 } resource ecs-dynamic-view { constructor(wrld: borrow, component-infos: list); + /// 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>; } resource ecs-world { constructor(); - // expects components to be tightly packed in the same order of component-infos + /// Spawn an entity. + /// + /// Parameters: + /// * `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. spawn: func(components: list, component-infos: list) -> entity; + + /// Query for a list of entities and their components. + /// + /// Parameters: + /// * `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; } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d2a7fde..fce7164 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,7 @@ impl From for wasm_ecs::Entity { impl Into for wasm_ecs::Entity { fn into(self) -> ecs::Entity { - let mut id = ecs::EntityId(self.id.id); + let id = ecs::EntityId(self.id.id); ecs::Entity::new(id, self.generation) } } diff --git a/witguest/src/lib.rs b/witguest/src/lib.rs index 0a419ed..bd01e46 100644 --- a/witguest/src/lib.rs +++ b/witguest/src/lib.rs @@ -2,10 +2,7 @@ mod bindings; use bindings::Guest; -//use bindings::{component::witguest::ecs::{ComponentInfo, EcsWorld, Entity, WasmTypeId}, Guest}; -use common_api::{bytemuck_component_impl, Component as EcsComponent, ComponentBundle, World}; - -use std::{alloc::Layout, any::TypeId, mem}; +use common_api::{bytemuck_component_impl, World}; #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] @@ -25,22 +22,39 @@ impl Vec3 { bytemuck_component_impl!(Vec3); +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +struct Vec4 { + x: f32, + y: f32, + z: f32, + w: f32, +} + +impl Vec4 { + pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Self { + x, y, z, w + } + } +} + +bytemuck_component_impl!(Vec4); + struct Component; impl Guest for Component { fn on_init() -> Result<(), ()> { let world = World::new(); - - let mut bundle = ComponentBundle::new(); - bundle.push(Vec3::new(7.0, 30.0, 18.0)); - let en = world.spawn(bundle); + let en = world.spawn((Vec3::new(7.0, 30.0, 18.0), Vec4::new(10.0, 332.35, 329.0, 95.0))); println!("guest spawned {:?}", en); - - let mut view = world.view(&[Vec3::component_info()]); - while let Some(c) = view.next::() { - println!("Retrieved vec3: {:?}", c); + let view = world.view::<(Vec3, Vec4)>(); + for pos in view { + let (p3, p4) = pos.unwrap(); + println!("Retrieved vec3: {:?}", p3); + println!("Retrieved vec4: {:?}", p4); } Ok(())