diff --git a/Cargo.lock b/Cargo.lock index 381e042..704be4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1266,6 +1266,15 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "slice-group-by" version = "0.3.1" @@ -2271,6 +2280,8 @@ version = "0.1.0" dependencies = [ "async-trait", "lyra-ecs", + "slab", + "thiserror", "tokio", "wasmtime", "wasmtime-wasi", diff --git a/Cargo.toml b/Cargo.toml index 36e2b68..c4603ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,6 @@ async-trait = "0.1.80" tokio = { version = "1.37.0", features = ["full"] } wasmtime = "19.0.2" wasmtime-wasi = "19.0.2" -lyra-ecs = { path = "./lyra-engine/lyra-ecs" } \ No newline at end of file +lyra-ecs = { path = "./lyra-engine/lyra-ecs" } +slab = "0.4.9" +thiserror = "1.0.58" diff --git a/src/main.rs b/src/main.rs index a08205b..2ee33e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,20 +4,20 @@ use std::mem; use std::ptr::NonNull; use async_trait::async_trait; -use component::witguest::{ecs_world, math}; +use component::witguest::ecs::WasmTypeId; +use component::witguest::{ecs as wasm_ecs, math}; use ecs::query::dynamic::QueryDynamicType; -use lyra_ecs::{Component, World}; +use lyra_ecs::Component; use lyra_ecs as ecs; -use lyra_engine::ecs::component::witguest::ecs_world::WasmTypeId; +use slab::Slab; +use thiserror::Error; use wasmtime_wasi::WasiView; #[allow(unused_imports)] pub(crate) mod lyra_engine { - pub(crate) mod ecs { - pub use super::super::*; - } + pub use lyra_ecs as ecs; } wasmtime::component::bindgen!({ @@ -42,7 +42,7 @@ impl Into for WasmTypeId { } } -impl Into for ecs_world::ComponentInfo { +impl Into for wasm_ecs::ComponentInfo { fn into(self) -> ecs::ComponentInfo { ecs::ComponentInfo::new_unknown( None, @@ -52,10 +52,10 @@ impl Into for ecs_world::ComponentInfo { } } -impl From for ecs_world::Entity { +impl From for wasm_ecs::Entity { fn from(value: ecs::Entity) -> Self { Self { - id: ecs_world::EntityId { id: value.id().0 }, + id: wasm_ecs::EntityId { id: value.id().0 }, generation: value.generation(), } } @@ -70,7 +70,7 @@ struct Vec3 { } struct Imports { - world: lyra_ecs::World, + world_slab: Slab, } impl WasiView for Imports { @@ -93,13 +93,23 @@ impl ExampleImports for Imports { impl math::Host for Imports {} +#[derive(Error, Debug)] +pub enum WasmError { + #[error("an invalid '{0}' resource handle was used by the guest")] + InvalidResourceHandle(&'static str) +} + #[async_trait] -impl ecs_world::Host for Imports { - async fn spawn( - &mut self, - mut components: Vec, - infos: Vec, - ) -> wasmtime::Result { +impl wasm_ecs::HostEcsWorld for Imports { + async fn new(&mut self,) -> wasmtime::Result> { + let world = self.world_slab.insert(lyra_ecs::World::new()); + Ok(wasmtime::component::Resource::new_own(world as _)) + } + + async fn spawn(&mut self, this: wasmtime::component::Resource, mut components: Vec, infos: Vec) -> wasmtime::Result { + let world = self.world_slab.get_mut(this.rep() as _) + .ok_or(WasmError::InvalidResourceHandle("EcsWorld"))?; + // add the components in the tightly packed `components` buffer into the dynamic bundle let mut bundle = ecs::DynamicBundle::new(); let mut offset = 0; @@ -112,13 +122,16 @@ impl ecs_world::Host for Imports { offset += info.size as usize; } - let en = self.world.spawn(bundle); + let en = world.spawn(bundle); Ok(en.into()) } - async fn view(&mut self, infos: Vec) -> wasmtime::Result> { + async fn view(&mut self, this: wasmtime::component::Resource, infos: Vec) -> wasmtime::Result> { + let world = self.world_slab.get_mut(this.rep() as _) + .ok_or(WasmError::InvalidResourceHandle("EcsWorld"))?; + // Create a dynamic view for querying for the components - let mut view = self.world.dynamic_view(); + let mut view = world.dynamic_view(); for info in infos.clone() { view.push(QueryDynamicType::from_info(info.into())); } @@ -145,6 +158,18 @@ impl ecs_world::Host for Imports { Ok(components) } + + fn drop(&mut self, this: wasmtime::component::Resource) -> wasmtime::Result<()> { + self.world_slab.try_remove(this.rep() as _) + .ok_or(WasmError::InvalidResourceHandle("EcsWorld"))?; + + Ok(()) + } +} + +#[async_trait] +impl wasm_ecs::Host for Imports { + } #[tokio::main] @@ -164,7 +189,9 @@ async fn main() -> wasmtime::Result<()> { let mut store = wasmtime::Store::new( &engine, Imports { - world: World::new(), + // There will likely be a world, but a slab is used in the case that the guest + // wants another world for some reason. + world_slab: Slab::with_capacity(1), }, ); diff --git a/witguest/src/bindings.rs b/witguest/src/bindings.rs index b479c00..e7b615e 100644 --- a/witguest/src/bindings.rs +++ b/witguest/src/bindings.rs @@ -82,7 +82,7 @@ pub mod component { #[allow(clippy::all)] - pub mod ecs_world { + pub mod ecs { #[used] #[doc(hidden)] #[cfg(target_arch = "wasm32")] @@ -131,72 +131,138 @@ pub mod component { f.debug_struct("ComponentInfo").field("size", &self.size).field("alignment", &self.alignment).field("type-id", &self.type_id).finish() } } - #[allow(unused_unsafe, clippy::all)] - /// expects components to be tightly packed in the same order of component-infos - pub fn spawn(components: &[u8],component_infos: &[ComponentInfo],) -> Entity{ - - #[allow(unused_imports)] - use wit_bindgen::rt::{alloc, vec::Vec, string::String}; - unsafe { - - #[repr(align(8))] - struct RetArea([u8; 16]); - let mut ret_area = ::core::mem::MaybeUninit::::uninit(); - let vec0 = components; - let ptr0 = vec0.as_ptr() as i32; - let len0 = vec0.len() as i32; - let vec1 = component_infos; - let ptr1 = vec1.as_ptr() as i32; - let len1 = vec1.len() as i32; - let ptr2 = ret_area.as_mut_ptr() as i32; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "component:witguest/ecs-world")] - extern "C" { - #[link_name = "spawn"] - fn wit_import(_: i32, _: i32, _: i32, _: i32, _: i32, ); + + #[derive(Debug)] + #[repr(transparent)] + pub struct EcsWorld{ + handle: wit_bindgen::rt::Resource, + } + + impl EcsWorld{ + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + Self { + handle: wit_bindgen::rt::Resource::from_handle(handle), } - + } + + #[doc(hidden)] + pub fn handle(&self) -> u32 { + wit_bindgen::rt::Resource::handle(&self.handle) + } + } + + + unsafe impl wit_bindgen::rt::WasmResource for EcsWorld{ + #[inline] + unsafe fn drop(_handle: u32) { #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i32, _: i32, _: i32, _: i32, _: i32, ){ unreachable!() } - wit_import(ptr0, len0, ptr1, len1, ptr2); - let l3 = *((ptr2 + 0) as *const i64); - let l4 = *((ptr2 + 8) as *const i64); - Entity{ - id: EntityId{ - id: l3 as u64, - }, - generation: l4 as u64, + unreachable!(); + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "component:witguest/ecs")] + extern "C" { + #[link_name = "[resource-drop]ecs-world"] + fn drop(_: u32); + } + + drop(_handle); } } } - #[allow(unused_unsafe, clippy::all)] - pub fn view(component_infos: &[ComponentInfo],) -> wit_bindgen::rt::vec::Vec::{ - - #[allow(unused_imports)] - use wit_bindgen::rt::{alloc, vec::Vec, string::String}; - unsafe { + + impl EcsWorld { + #[allow(unused_unsafe, clippy::all)] + pub fn new() -> Self{ - #[repr(align(4))] - struct RetArea([u8; 8]); - let mut ret_area = ::core::mem::MaybeUninit::::uninit(); - let vec0 = component_infos; - let ptr0 = vec0.as_ptr() as i32; - let len0 = vec0.len() as i32; - let ptr1 = ret_area.as_mut_ptr() as i32; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "component:witguest/ecs-world")] - extern "C" { - #[link_name = "view"] - fn wit_import(_: i32, _: i32, _: i32, ); + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, vec::Vec, string::String}; + unsafe { + + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "component:witguest/ecs")] + extern "C" { + #[link_name = "[constructor]ecs-world"] + fn wit_import() -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import() -> i32{ unreachable!() } + let ret = wit_import(); + EcsWorld::from_handle(ret as u32) } + } + } + impl EcsWorld { + #[allow(unused_unsafe, clippy::all)] + pub fn spawn(&self,components: &[u8],component_infos: &[ComponentInfo],) -> Entity{ - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i32, _: i32, _: i32, ){ unreachable!() } - wit_import(ptr0, len0, ptr1); - let l2 = *((ptr1 + 0) as *const i32); - let l3 = *((ptr1 + 4) as *const i32); - let len4 = l3 as usize; - Vec::from_raw_parts(l2 as *mut _, len4, len4) + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, vec::Vec, string::String}; + unsafe { + + #[repr(align(8))] + struct RetArea([u8; 16]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let vec0 = components; + let ptr0 = vec0.as_ptr() as i32; + let len0 = vec0.len() as i32; + let vec1 = component_infos; + let ptr1 = vec1.as_ptr() as i32; + let len1 = vec1.len() as i32; + let ptr2 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "component:witguest/ecs")] + extern "C" { + #[link_name = "[method]ecs-world.spawn"] + fn wit_import(_: i32, _: i32, _: i32, _: i32, _: i32, _: i32, ); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32, _: i32, _: i32, _: i32, _: i32, _: i32, ){ unreachable!() } + wit_import((self).handle() as i32, ptr0, len0, ptr1, len1, ptr2); + let l3 = *((ptr2 + 0) as *const i64); + let l4 = *((ptr2 + 8) as *const i64); + Entity{ + id: EntityId{ + id: l3 as u64, + }, + generation: l4 as u64, + } + } + } + } + impl EcsWorld { + #[allow(unused_unsafe, clippy::all)] + pub fn view(&self,component_infos: &[ComponentInfo],) -> wit_bindgen::rt::vec::Vec::{ + + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, vec::Vec, string::String}; + unsafe { + + #[repr(align(4))] + struct RetArea([u8; 8]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let vec0 = component_infos; + let ptr0 = vec0.as_ptr() as i32; + let len0 = vec0.len() as i32; + let ptr1 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "component:witguest/ecs")] + extern "C" { + #[link_name = "[method]ecs-world.view"] + fn wit_import(_: i32, _: i32, _: i32, _: i32, ); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32, _: i32, _: i32, _: i32, ){ unreachable!() } + wit_import((self).handle() as i32, ptr0, len0, ptr1); + let l2 = *((ptr1 + 0) as *const i32); + let l3 = *((ptr1 + 4) as *const i32); + let len4 = l3 as usize; + Vec::from_raw_parts(l2 as *mut _, len4, len4) + } } } @@ -208,7 +274,7 @@ pub mod component { #[cfg(target_arch = "wasm32")] #[link_section = "component-type:example"] #[doc(hidden)] -pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 514] = [0, 97, 115, 109, 13, 0, 1, 0, 0, 25, 22, 119, 105, 116, 45, 99, 111, 109, 112, 111, 110, 101, 110, 116, 45, 101, 110, 99, 111, 100, 105, 110, 103, 4, 0, 7, 133, 3, 1, 65, 2, 1, 65, 9, 1, 66, 2, 1, 114, 3, 1, 120, 118, 1, 121, 118, 1, 122, 118, 4, 0, 4, 118, 101, 99, 51, 3, 0, 0, 3, 1, 23, 99, 111, 109, 112, 111, 110, 101, 110, 116, 58, 119, 105, 116, 103, 117, 101, 115, 116, 47, 109, 97, 116, 104, 5, 0, 1, 66, 15, 1, 114, 1, 2, 105, 100, 119, 4, 0, 9, 101, 110, 116, 105, 116, 121, 45, 105, 100, 3, 0, 0, 1, 114, 2, 2, 105, 100, 1, 10, 103, 101, 110, 101, 114, 97, 116, 105, 111, 110, 119, 4, 0, 6, 101, 110, 116, 105, 116, 121, 3, 0, 2, 1, 111, 2, 119, 119, 1, 114, 1, 5, 105, 110, 110, 101, 114, 4, 4, 0, 12, 119, 97, 115, 109, 45, 116, 121, 112, 101, 45, 105, 100, 3, 0, 5, 1, 114, 3, 4, 115, 105, 122, 101, 119, 9, 97, 108, 105, 103, 110, 109, 101, 110, 116, 119, 7, 116, 121, 112, 101, 45, 105, 100, 6, 4, 0, 14, 99, 111, 109, 112, 111, 110, 101, 110, 116, 45, 105, 110, 102, 111, 3, 0, 7, 1, 112, 125, 1, 112, 8, 1, 64, 2, 10, 99, 111, 109, 112, 111, 110, 101, 110, 116, 115, 9, 15, 99, 111, 109, 112, 111, 110, 101, 110, 116, 45, 105, 110, 102, 111, 115, 10, 0, 3, 4, 0, 5, 115, 112, 97, 119, 110, 1, 11, 1, 64, 1, 15, 99, 111, 109, 112, 111, 110, 101, 110, 116, 45, 105, 110, 102, 111, 115, 10, 0, 9, 4, 0, 4, 118, 105, 101, 119, 1, 12, 3, 1, 28, 99, 111, 109, 112, 111, 110, 101, 110, 116, 58, 119, 105, 116, 103, 117, 101, 115, 116, 47, 101, 99, 115, 45, 119, 111, 114, 108, 100, 5, 1, 1, 64, 1, 3, 109, 115, 103, 115, 1, 0, 3, 0, 10, 104, 111, 115, 116, 45, 112, 114, 105, 110, 116, 1, 2, 1, 106, 1, 125, 0, 1, 64, 0, 0, 3, 4, 0, 7, 111, 110, 45, 105, 110, 105, 116, 1, 4, 4, 1, 26, 99, 111, 109, 112, 111, 110, 101, 110, 116, 58, 119, 105, 116, 103, 117, 101, 115, 116, 47, 101, 120, 97, 109, 112, 108, 101, 4, 0, 11, 13, 1, 0, 7, 101, 120, 97, 109, 112, 108, 101, 3, 0, 0, 0, 70, 9, 112, 114, 111, 100, 117, 99, 101, 114, 115, 1, 12, 112, 114, 111, 99, 101, 115, 115, 101, 100, 45, 98, 121, 2, 13, 119, 105, 116, 45, 99, 111, 109, 112, 111, 110, 101, 110, 116, 6, 48, 46, 50, 49, 46, 48, 16, 119, 105, 116, 45, 98, 105, 110, 100, 103, 101, 110, 45, 114, 117, 115, 116, 6, 48, 46, 49, 56, 46, 48]; +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 608] = [0, 97, 115, 109, 13, 0, 1, 0, 0, 25, 22, 119, 105, 116, 45, 99, 111, 109, 112, 111, 110, 101, 110, 116, 45, 101, 110, 99, 111, 100, 105, 110, 103, 4, 0, 7, 227, 3, 1, 65, 2, 1, 65, 9, 1, 66, 2, 1, 114, 3, 1, 120, 118, 1, 121, 118, 1, 122, 118, 4, 0, 4, 118, 101, 99, 51, 3, 0, 0, 3, 1, 23, 99, 111, 109, 112, 111, 110, 101, 110, 116, 58, 119, 105, 116, 103, 117, 101, 115, 116, 47, 109, 97, 116, 104, 5, 0, 1, 66, 20, 1, 114, 1, 2, 105, 100, 119, 4, 0, 9, 101, 110, 116, 105, 116, 121, 45, 105, 100, 3, 0, 0, 1, 114, 2, 2, 105, 100, 1, 10, 103, 101, 110, 101, 114, 97, 116, 105, 111, 110, 119, 4, 0, 6, 101, 110, 116, 105, 116, 121, 3, 0, 2, 1, 111, 2, 119, 119, 1, 114, 1, 5, 105, 110, 110, 101, 114, 4, 4, 0, 12, 119, 97, 115, 109, 45, 116, 121, 112, 101, 45, 105, 100, 3, 0, 5, 1, 114, 3, 4, 115, 105, 122, 101, 119, 9, 97, 108, 105, 103, 110, 109, 101, 110, 116, 119, 7, 116, 121, 112, 101, 45, 105, 100, 6, 4, 0, 14, 99, 111, 109, 112, 111, 110, 101, 110, 116, 45, 105, 110, 102, 111, 3, 0, 7, 4, 0, 9, 101, 99, 115, 45, 119, 111, 114, 108, 100, 3, 1, 1, 105, 9, 1, 64, 0, 0, 10, 4, 0, 22, 91, 99, 111, 110, 115, 116, 114, 117, 99, 116, 111, 114, 93, 101, 99, 115, 45, 119, 111, 114, 108, 100, 1, 11, 1, 104, 9, 1, 112, 125, 1, 112, 8, 1, 64, 3, 4, 115, 101, 108, 102, 12, 10, 99, 111, 109, 112, 111, 110, 101, 110, 116, 115, 13, 15, 99, 111, 109, 112, 111, 110, 101, 110, 116, 45, 105, 110, 102, 111, 115, 14, 0, 3, 4, 0, 23, 91, 109, 101, 116, 104, 111, 100, 93, 101, 99, 115, 45, 119, 111, 114, 108, 100, 46, 115, 112, 97, 119, 110, 1, 15, 1, 64, 2, 4, 115, 101, 108, 102, 12, 15, 99, 111, 109, 112, 111, 110, 101, 110, 116, 45, 105, 110, 102, 111, 115, 14, 0, 13, 4, 0, 22, 91, 109, 101, 116, 104, 111, 100, 93, 101, 99, 115, 45, 119, 111, 114, 108, 100, 46, 118, 105, 101, 119, 1, 16, 3, 1, 22, 99, 111, 109, 112, 111, 110, 101, 110, 116, 58, 119, 105, 116, 103, 117, 101, 115, 116, 47, 101, 99, 115, 5, 1, 1, 64, 1, 3, 109, 115, 103, 115, 1, 0, 3, 0, 10, 104, 111, 115, 116, 45, 112, 114, 105, 110, 116, 1, 2, 1, 106, 1, 125, 0, 1, 64, 0, 0, 3, 4, 0, 7, 111, 110, 45, 105, 110, 105, 116, 1, 4, 4, 1, 26, 99, 111, 109, 112, 111, 110, 101, 110, 116, 58, 119, 105, 116, 103, 117, 101, 115, 116, 47, 101, 120, 97, 109, 112, 108, 101, 4, 0, 11, 13, 1, 0, 7, 101, 120, 97, 109, 112, 108, 101, 3, 0, 0, 0, 70, 9, 112, 114, 111, 100, 117, 99, 101, 114, 115, 1, 12, 112, 114, 111, 99, 101, 115, 115, 101, 100, 45, 98, 121, 2, 13, 119, 105, 116, 45, 99, 111, 109, 112, 111, 110, 101, 110, 116, 6, 48, 46, 50, 49, 46, 48, 16, 119, 105, 116, 45, 98, 105, 110, 100, 103, 101, 110, 45, 114, 117, 115, 116, 6, 48, 46, 49, 56, 46, 48]; #[inline(never)] #[doc(hidden)] diff --git a/witguest/src/lib.rs b/witguest/src/lib.rs index a329767..14651a1 100644 --- a/witguest/src/lib.rs +++ b/witguest/src/lib.rs @@ -1,7 +1,7 @@ #[allow(warnings)] mod bindings; -use bindings::{component::witguest::ecs_world::{self, ComponentInfo, WasmTypeId}, Guest}; +use bindings::{component::witguest::ecs::{ComponentInfo, EcsWorld, WasmTypeId}, Guest}; use std::{alloc::Layout, any::TypeId, mem}; @@ -49,11 +49,14 @@ impl Guest for Component { let comps = comps.as_mut_ptr().cast::(); let comps = unsafe { Vec::from_raw_parts(comps, mem::size_of::(), 1) }; - let en = ecs_world::spawn(&comps, info); + let world = EcsWorld::new(); + let en = world.spawn(&comps, info); + + //let en = ecs_world::spawn(&comps, info); bindings::host_print(&format!("Spawned entity: {}", en.id.id)); - let mut buffer = ecs_world::view(&[Vec3::component_info()]); + let mut buffer = world.view(&[Vec3::component_info()]); bindings::host_print(&format!("Retrieved vec3 of {} bytes long", buffer.len())); let fetched_vec = unsafe { std::ptr::read(buffer.as_mut_ptr().cast::()) }; bindings::host_print(&format!("Retrieved vec3: {:?}", fetched_vec)); diff --git a/witguest/wit/world.wit b/witguest/wit/world.wit index e53a904..578ec1e 100644 --- a/witguest/wit/world.wit +++ b/witguest/wit/world.wit @@ -8,7 +8,7 @@ interface math { } } -interface ecs-world { +interface ecs { //use math.{vec3}; record entity-id { @@ -30,15 +30,19 @@ interface ecs-world { type-id: wasm-type-id, // a u128 } - // expects components to be tightly packed in the same order of component-infos - spawn: func(components: list, component-infos: list) -> entity; - view: func(component-infos: list) -> list; + resource ecs-world { + constructor(); + + // expects components to be tightly packed in the same order of component-infos + spawn: func(components: list, component-infos: list) -> entity; + view: func(component-infos: list) -> list; + } } /// An example world for the component to target. world example { import math; - import ecs-world; + import ecs; import host-print: func(msg: string); export on-init: func() -> result;