Create a common-api crate to make it easier to write a wasm guest
This commit is contained in:
parent
27c8f93611
commit
a58dcfb53d
|
@ -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<u8>,
|
||||
pub(crate) infos: Vec<ComponentInfo>,
|
||||
}
|
||||
|
||||
impl ComponentBundle {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn push<C: Component>(&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<ComponentInfo>;
|
||||
fn to_bytes(self) -> Result<Vec<u8>, ComponentSerializationError>;
|
||||
fn from_bytes(bytes: Vec<u8>) -> Result<Self, ComponentSerializationError>;
|
||||
}
|
||||
|
||||
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<ComponentInfo> {
|
||||
vec![$($name::component_info(),)+ $last::component_info()]
|
||||
}
|
||||
|
||||
fn to_bytes(self) -> Result<Vec<u8>, 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<u8>) -> Result<Self, ComponentSerializationError> {
|
||||
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<ComponentInfo> {
|
||||
vec![$only::component_info()]
|
||||
}
|
||||
|
||||
fn to_bytes(self) -> Result<Vec<u8>, ComponentSerializationError> {
|
||||
let mut bytes = vec![];
|
||||
bytes.extend(self.0.to_bytes()?.into_iter());
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: Vec<u8>) -> Result<Self, ComponentSerializationError> {
|
||||
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<ComponentInfo> {
|
||||
vec![$only::component_info()]
|
||||
}
|
||||
|
||||
fn to_bytes(self) -> Result<Vec<u8>, ComponentSerializationError> {
|
||||
let mut bytes = vec![];
|
||||
bytes.extend(<Self as Component>::to_bytes(&self)?.into_iter());
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: Vec<u8>) -> Result<Self, ComponentSerializationError> {
|
||||
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);
|
|
@ -0,0 +1,70 @@
|
|||
use std::alloc::Layout;
|
||||
|
||||
use crate::lyra::api::ecs::{ComponentInfo, WasmTypeId};
|
||||
|
||||
impl ComponentInfo {
|
||||
pub fn of<C: Component>() -> Self {
|
||||
let layout = Layout::new::<C>();
|
||||
Self {
|
||||
size: layout.size() as _,
|
||||
alignment: layout.align() as _,
|
||||
type_id: WasmTypeId::of::<C>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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<Vec<u8>, ComponentSerializationError>;
|
||||
fn to_bytes_into(&self, target: &mut Vec<u8>) -> Result<(), ComponentSerializationError>;
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, ComponentSerializationError>;
|
||||
}
|
||||
|
||||
#[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<Vec<u8>, common_api::ComponentSerializationError> {
|
||||
Ok(common_api::bytemuck::bytes_of(self).to_vec())
|
||||
}
|
||||
|
||||
fn to_bytes_into(&self, target: &mut Vec<u8>) -> Result<(), common_api::ComponentSerializationError> {
|
||||
let bytes = self.to_bytes()?;
|
||||
target.extend(bytes.into_iter());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, common_api::ComponentSerializationError> {
|
||||
common_api::bytemuck::try_from_bytes::<Self>(&bytes)
|
||||
.map_err(|e| common_api::ComponentSerializationError::Bytemuck(e))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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<T: 'static>() -> Self {
|
||||
|
@ -28,95 +27,6 @@ impl WasmTypeId {
|
|||
}
|
||||
}
|
||||
|
||||
impl ComponentInfo {
|
||||
pub fn of<C: Component>() -> Self {
|
||||
let layout = Layout::new::<C>();
|
||||
Self {
|
||||
size: layout.size() as _,
|
||||
alignment: layout.align() as _,
|
||||
type_id: WasmTypeId::of::<C>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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<Vec<u8>, ComponentSerializationError>;
|
||||
fn to_bytes_into(&self, target: &mut Vec<u8>) -> Result<(), ComponentSerializationError>;
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, ComponentSerializationError>;
|
||||
}
|
||||
|
||||
#[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<Vec<u8>, common_api::ComponentSerializationError> {
|
||||
Ok(common_api::bytemuck::bytes_of(self).to_vec())
|
||||
}
|
||||
|
||||
fn to_bytes_into(&self, target: &mut Vec<u8>) -> Result<(), common_api::ComponentSerializationError> {
|
||||
let bytes = self.to_bytes()?;
|
||||
target.extend(bytes.into_iter());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, common_api::ComponentSerializationError> {
|
||||
common_api::bytemuck::try_from_bytes::<Self>(&bytes)
|
||||
.map_err(|e| common_api::ComponentSerializationError::Bytemuck(e))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ComponentBundle {
|
||||
bundle: Vec<u8>,
|
||||
infos: Vec<ComponentInfo>,
|
||||
}
|
||||
|
||||
impl ComponentBundle {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn push<C: Component>(&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<B: Bundle>(&self, bundle: B) -> Result<Entity, ComponentSerializationError> {
|
||||
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<B: Bundle>(&self) -> View<B> {
|
||||
let infos = B::component_info();
|
||||
View::from(self.inner.view(&infos))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
pub struct View<B: Bundle> {
|
||||
inner: EcsDynamicView,
|
||||
_marker: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl From<EcsDynamicView> for View {
|
||||
impl<B: Bundle> From<EcsDynamicView> for View<B> {
|
||||
fn from(value: EcsDynamicView) -> Self {
|
||||
Self {
|
||||
inner: value
|
||||
inner: value,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn next<C: Component>(&mut self) -> Option<C> {
|
||||
let row = self.inner.next();
|
||||
impl<B: Bundle> View<B> {
|
||||
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<B: Bundle> Iterator for View<B> {
|
||||
type Item = Result<B, ComponentSerializationError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(row) = self.inner.next() {
|
||||
Some(B::from_bytes(row))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -11,26 +11,49 @@ interface ecs {
|
|||
}
|
||||
|
||||
record wasm-type-id {
|
||||
// represents a u128, can be converted into that with mem::transmute
|
||||
inner: tuple<u64, u64>,
|
||||
}
|
||||
|
||||
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<ecs-world>, component-infos: list<component-info>);
|
||||
|
||||
/// 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<list<u8>>;
|
||||
}
|
||||
|
||||
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<u8>, component-infos: list<component-info>) -> 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<component-info>) -> ecs-dynamic-view;
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ impl From<ecs::Entity> for wasm_ecs::Entity {
|
|||
|
||||
impl Into<ecs::Entity> 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<Vec3>() {
|
||||
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(())
|
||||
|
|
Loading…
Reference in New Issue