Implement ECS resources
This commit is contained in:
parent
dcb48e9acf
commit
1d97046195
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
}
|
||||||
|
|
|
@ -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>));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>));
|
||||||
}
|
}
|
||||||
}
|
}
|
101
src/main.rs
101
src/main.rs
|
@ -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,31 +508,32 @@ 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() {
|
||||||
println!("RUST: Archetype {}", a.id().0);
|
println!("RUST: Archetype {}", a.id().0);
|
||||||
for col in &a.columns {
|
for col in &a.columns {
|
||||||
println!("RUST: Column type id: {:?}", col.info.type_id());
|
println!("RUST: Column type id: {:?}", col.info.type_id());
|
||||||
|
|
||||||
if col.info.type_id().is_id(DynTypeId::Unknown(4124409524)) {
|
if col.info.type_id().is_id(DynTypeId::Unknown(4124409524)) {
|
||||||
println!("RUST: Found C# Vec3");
|
println!("RUST: Found C# Vec3");
|
||||||
let pos = unsafe { col.get::<Vec3>(0) };
|
let pos = unsafe { col.get::<Vec3>(0) };
|
||||||
println!("RUST: Entity 0 pos: {:?}", *pos);
|
println!("RUST: Entity 0 pos: {:?}", *pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let mut dv = w.dynamic_view();
|
let mut dv = w.dynamic_view();
|
||||||
dv.push(QueryDynamicType::from_info(lyra_ecs::ComponentInfo::new::<Vec3>()));
|
dv.push(QueryDynamicType::from_info(lyra_ecs::ComponentInfo::new::<Vec3>()));
|
||||||
let iter = dv.into_iter();
|
let iter = dv.into_iter();
|
||||||
|
|
||||||
for (_, comps) in iter {
|
for (_, comps) in iter {
|
||||||
let first = comps.first().unwrap();
|
let first = comps.first().unwrap();
|
||||||
unsafe {
|
unsafe {
|
||||||
let v: Vec3 = std::ptr::read(first.ptr.as_ptr() as _);
|
let v: Vec3 = std::ptr::read(first.ptr.as_ptr() as _);
|
||||||
println!("RUST: Found native Vec3! {:?}", v);
|
println!("RUST: Found native Vec3! {:?}", v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue