Implement ECS resources

This commit is contained in:
SeanOMik 2024-11-09 12:12:06 -05:00
parent dcb48e9acf
commit 1d97046195
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
10 changed files with 3620 additions and 100 deletions

3505
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,8 @@ tokio = { version = "1.41.1", features = ["full"] }
wasmtime = "26.0.1" wasmtime = "26.0.1"
wasmtime-wasi = "26.0.1" wasmtime-wasi = "26.0.1"
lyra-ecs = { path = "./lyra-engine/crates/lyra-ecs" } lyra-ecs = { path = "./lyra-engine/crates/lyra-ecs" }
lyra-reflect = { path = "./lyra-engine/crates/lyra-reflect" }
lyra-engine = { path = "./lyra-engine" }
slab = "0.4.9" slab = "0.4.9"
thiserror = "2.0.0" thiserror = "2.0.0"

View File

@ -8,6 +8,9 @@ pub use component::*;
mod bundle; mod bundle;
pub use bundle::*; pub use bundle::*;
pub mod resource;
pub use resource::EcsResource;
pub mod math; pub mod math;
pub use api_derive as macros; pub use api_derive as macros;
@ -29,6 +32,12 @@ impl WasmTypeId {
inner: unsafe { mem::transmute(TypeId::of::<T>()) }, inner: unsafe { mem::transmute(TypeId::of::<T>()) },
} }
} }
pub const fn from_raw(id: u128) -> Self {
Self {
inner: unsafe { mem::transmute(id) },
}
}
} }
enum OwnedBorrow<'a, T> { enum OwnedBorrow<'a, T> {
@ -121,6 +130,11 @@ impl<'a> World<'a> {
self.inner.view_one(en, &infos) self.inner.view_one(en, &infos)
.map(|bytes| B::from_bytes(bytes)) .map(|bytes| B::from_bytes(bytes))
} }
pub fn get_resource<T: EcsResource>(&self) -> Option<T> {
let res = self.inner.get_resource(T::TYPE_ID);
T::from_wasm_result(&res)
}
} }
pub struct View<B: Bundle> { pub struct View<B: Bundle> {

View File

@ -0,0 +1,27 @@
use std::ops::Deref;
use crate::lyra::api::ecs::{WasmTypeId, WorldResourceResult};
use super::EcsResource;
#[derive(Clone, Copy)]
pub struct DeltaTime(f32);
impl Deref for DeltaTime {
type Target = f32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl EcsResource for DeltaTime {
const TYPE_ID: WasmTypeId = WasmTypeId::from_raw(83716348954);
fn from_wasm_result(result: &WorldResourceResult) -> Option<Self> {
match result {
WorldResourceResult::None => None,
WorldResourceResult::WasmResourceRep(_) => None,
WorldResourceResult::Bytes(vec) => Some(Self(*bytemuck::from_bytes::<f32>(&vec))),
}
}
}

View File

@ -0,0 +1,11 @@
use crate::lyra::api::ecs::{WasmTypeId, WorldResourceResult};
mod dt;
pub use dt::*;
pub trait EcsResource: Sized {
const TYPE_ID: WasmTypeId;
fn from_wasm_result(result: &WorldResourceResult) -> Option<Self>;
}

View File

@ -37,6 +37,12 @@ interface ecs {
next: func() -> option<tuple<entity, list<u8>>>; next: func() -> option<tuple<entity, list<u8>>>;
} }
variant world-resource-result {
none,
wasm-resource-rep(u32),
bytes(list<u8>),
}
resource ecs-world { resource ecs-world {
constructor(); constructor();
@ -76,6 +82,8 @@ interface ecs {
/// Returns: A row of components serialized as bytes. The buffer is tighly packed. /// Returns: A row of components serialized as bytes. The buffer is tighly packed.
view-one: func(en: entity, component-infos: list<component-info>) -> option<list<u8>>; view-one: func(en: entity, component-infos: list<component-info>) -> option<list<u8>>;
get-resource: func(type-id: wasm-type-id) -> world-resource-result;
//with_system: func(stage: string, component-infos: list<component-info>, system: func(components: list<u8>)); //with_system: func(stage: string, component-infos: list<component-info>, system: func(components: list<u8>));
} }
} }

View File

@ -1,4 +1,4 @@
use common_api::{math::{Vec3, Vec4}, World}; use common_api::{math::{Vec3, Vec4}, resource::DeltaTime, World};
wit_bindgen::generate!({ wit_bindgen::generate!({
world: "example", world: "example",
@ -22,6 +22,9 @@ impl Guest for Component {
.unwrap().unwrap(); .unwrap().unwrap();
println!("Found entity at {pos3:?}"); println!("Found entity at {pos3:?}");
let dt = world.get_resource::<DeltaTime>().unwrap();
println!("dt is {}", *dt);
Ok(()) Ok(())
} }

View File

@ -37,6 +37,12 @@ interface ecs {
next: func() -> option<tuple<entity, list<u8>>>; next: func() -> option<tuple<entity, list<u8>>>;
} }
variant world-resource-result {
none,
wasm-resource-rep(u32),
bytes(list<u8>),
}
resource ecs-world { resource ecs-world {
constructor(); constructor();
@ -76,6 +82,8 @@ interface ecs {
/// Returns: A row of components serialized as bytes. The buffer is tighly packed. /// Returns: A row of components serialized as bytes. The buffer is tighly packed.
view-one: func(en: entity, component-infos: list<component-info>) -> option<list<u8>>; view-one: func(en: entity, component-infos: list<component-info>) -> option<list<u8>>;
get-resource: func(type-id: wasm-type-id) -> world-resource-result;
//with_system: func(stage: string, component-infos: list<component-info>, system: func(components: list<u8>)); //with_system: func(stage: string, component-infos: list<component-info>, system: func(components: list<u8>));
} }
} }

View File

@ -1,5 +1,7 @@
use std::alloc::Layout; use std::alloc::Layout;
use std::any::TypeId;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref;
use std::ptr::NonNull; use std::ptr::NonNull;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
@ -11,12 +13,17 @@ use lyra_ecs::{query::dynamic::DynamicViewOne, DynTypeId, World};
use lyra_ecs as ecs; use lyra_ecs as ecs;
use ::lyra_engine::DeltaTime;
use lyra_reflect::{FromType, ReflectedResource, RegisteredType, TypeRegistry};
use slab::Slab; use slab::Slab;
use thiserror::Error; use thiserror::Error;
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
use wasmtime::component::Resource as WasmResource; use wasmtime::component::Resource as WasmResource;
mod proxy;
pub use proxy::*;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) mod lyra_engine { pub(crate) mod lyra_engine {
pub use lyra_ecs as ecs; pub use lyra_ecs as ecs;
@ -241,6 +248,27 @@ impl wasm_ecs::HostEcsWorld for Imports {
} }
} }
async fn get_resource(&mut self, this: wasmtime::component::Resource<wasm_ecs::EcsWorld>, type_id: wasm_ecs::WasmTypeId) -> wasm_ecs::WorldResourceResult {
let world_entry = self
.world_slab
.try_remove(this.rep() as _)
.ok_or(WasmError::InvalidResourceHandle("EcsWorld")).unwrap();
let world = &world_entry.world;
let native_type = world.get_resource::<GuestTypeLookup>().unwrap();
let native_tid = native_type.lookup_type_id(type_id)
.expect("failed to find native type id of wasm type");
let reg = world.get_resource::<TypeRegistry>().unwrap();
let data = reg.get_type(native_tid).unwrap();
let proxy = data.get_data::<WasmProxied>().unwrap();
let refl = data.get_data::<ReflectedResource>().unwrap();
let refl = refl.reflect(world).unwrap();
let res = proxy.marshal_to_bytes(refl.deref());
res
}
async fn drop( async fn drop(
&mut self, &mut self,
@ -363,7 +391,10 @@ impl wasm_ecs::HostEcsDynamicView for Imports {
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
struct GuestTypeLookup(HashMap<u128, lyra_ecs::ComponentInfo>); struct GuestTypeLookup {
infos: HashMap<u128, lyra_ecs::ComponentInfo>,
type_ids: HashMap<u128, TypeId>,
}
impl GuestTypeLookup { impl GuestTypeLookup {
/// Try to find the native type's component info. /// Try to find the native type's component info.
@ -372,7 +403,7 @@ impl GuestTypeLookup {
/// will be returned. If info is found, the size and alignment will be verified to match using /// will be returned. If info is found, the size and alignment will be verified to match using
/// asserts and the info of the native type will be returned. /// asserts and the info of the native type will be returned.
fn lookup_info(&self, info: lyra_ecs::ComponentInfo) -> lyra_ecs::ComponentInfo { fn lookup_info(&self, info: lyra_ecs::ComponentInfo) -> lyra_ecs::ComponentInfo {
match self.0.get(&info.type_id().as_unknown().unwrap()) { match self.infos.get(&info.type_id().as_unknown().unwrap()) {
Some(native) => { Some(native) => {
let native_align = native.layout().align() as usize; let native_align = native.layout().align() as usize;
let native_size = native.layout().size() as usize; let native_size = native.layout().size() as usize;
@ -386,6 +417,12 @@ impl GuestTypeLookup {
None => info None => info
} }
} }
fn lookup_type_id(&self, wasm_id: wasm_ecs::WasmTypeId) -> Option<TypeId> {
// SAFETY: a (u64, u64) is the same as a u128
let id: u128 = unsafe { std::mem::transmute(wasm_id) };
self.type_ids.get(&id).cloned()
}
} }
#[async_trait] #[async_trait]
@ -425,17 +462,30 @@ async fn main() -> wasmtime::Result<()> {
); );
// Load the component from disk // Load the component from disk
//let bytes = std::fs::read("target/wasm32-wasip1/debug/witguest-component.wasm")?; let bytes = std::fs::read("target/wasm32-wasip1/debug/witguest-component.wasm")?;
let bytes = std::fs::read("guests/csharp/dotnet-guest-test/bin/Debug/net9.0/wasi-wasm/native/dotnet-guest-test.wasm")?; //let bytes = std::fs::read("guests/csharp/dotnet-guest-test/bin/Debug/net9.0/wasi-wasm/native/dotnet-guest-test.wasm")?;
let component = wasmtime::component::Component::new(&engine, bytes)?; let component = wasmtime::component::Component::new(&engine, bytes)?;
let (script_en, (world_res_a, world_res_b)) = { let (script_en, (world_res_a, world_res_b)) = {
let mut world = World::new(); let mut world = World::new();
world.add_resource(DeltaTime::from(100.00));
let mut lookup = GuestTypeLookup::default(); let mut lookup = GuestTypeLookup::default();
lookup.0.insert(4124409524, lyra_ecs::ComponentInfo::new::<Vec3>()); lookup.infos.insert(4124409524, lyra_ecs::ComponentInfo::new::<Vec3>());
lookup.type_ids.insert(83716348954, TypeId::of::<::lyra_engine::DeltaTime>());
world.add_resource(lookup); world.add_resource(lookup);
{
let mut reg = world.get_resource_or_default::<TypeRegistry>();
let mut dt_type = RegisteredType::new();
dt_type.add_data(<ReflectedResource as FromType::<DeltaTime>>::from_type());
dt_type.add_data(<WasmProxied as FromType::<DeltaTime>>::from_type());
reg.add_registered_type(TypeId::of::<DeltaTime>(), dt_type);
}
let script_en = world.spawn(()); let script_en = world.spawn(());
let data = store.data_mut(); let data = store.data_mut();
@ -458,7 +508,7 @@ async fn main() -> wasmtime::Result<()> {
println!("RUST: Guest is done"); println!("RUST: Guest is done");
let rep = world_res_b.rep(); let rep = world_res_b.rep();
let w = store.data().world_slab.get(rep as _).unwrap(); if let Some(w) = store.data().world_slab.get(rep as _) {
let w = &w.world; let w = &w.world;
println!("RUST: Got {} archetypes", w.archetype_count()); println!("RUST: Got {} archetypes", w.archetype_count());
for a in w.archetypes.values() { for a in w.archetypes.values() {
@ -485,6 +535,7 @@ async fn main() -> wasmtime::Result<()> {
println!("RUST: Found native Vec3! {:?}", v); println!("RUST: Found native Vec3! {:?}", v);
} }
} }
}
Ok(()) Ok(())
} }

39
src/proxy.rs Normal file
View File

@ -0,0 +1,39 @@
use crate::lyra::api::ecs as wasm_ecs;
use common_api::bytemuck;
use lyra_engine::DeltaTime;
use lyra_reflect::{FromType, Reflect};
pub trait WasmProxy {
fn marshal_to_bytes(&self) -> wasm_ecs::WorldResourceResult;
}
#[derive(Clone)]
pub struct WasmProxied {
fn_marshal: for<'a> fn (&'a dyn Reflect) -> wasm_ecs::WorldResourceResult,
}
impl WasmProxied {
pub fn marshal_to_bytes(&self, reflected: &dyn Reflect) -> wasm_ecs::WorldResourceResult {
(self.fn_marshal)(reflected)
}
}
impl<T> FromType<T> for WasmProxied
where
T: WasmProxy + 'static
{
fn from_type() -> Self {
WasmProxied {
fn_marshal: |reflect| {
let this: &T = reflect.as_any().downcast_ref().unwrap();
this.marshal_to_bytes()
}
}
}
}
impl WasmProxy for DeltaTime {
fn marshal_to_bytes(&self) -> wasm_ecs::WorldResourceResult {
wasm_ecs::WorldResourceResult::Bytes(bytemuck::bytes_of(&**self).to_vec())
}
}