diff --git a/guests/csharp/dotnet-guest-test/ExampleWorldImpl.cs b/guests/csharp/dotnet-guest-test/ExampleWorldImpl.cs index 0d72826..6115ad2 100644 --- a/guests/csharp/dotnet-guest-test/ExampleWorldImpl.cs +++ b/guests/csharp/dotnet-guest-test/ExampleWorldImpl.cs @@ -24,9 +24,8 @@ public class ExampleWorldImpl : IExampleWorld Console.WriteLine($"C#: Delta time: {dt?.Seconds:0.##}"); } */ - AssetManager? man = world.GetAssetManager(); - if (man != null) - { + AssetManager? man = world.GetResource(); + if (man != null) { Handle han = man.Request("test_assets/white.png"); Console.WriteLine($"C#: Asset uuid: {han.Uuid}"); diff --git a/guests/csharp/dotnet-guest-test/LyraApi/Asset/AssetManager.cs b/guests/csharp/dotnet-guest-test/LyraApi/Asset/AssetManager.cs index 171a482..e74d95d 100644 --- a/guests/csharp/dotnet-guest-test/LyraApi/Asset/AssetManager.cs +++ b/guests/csharp/dotnet-guest-test/LyraApi/Asset/AssetManager.cs @@ -1,5 +1,6 @@ using LyraApi.Ecs; using ExampleWorld.wit.imports.lyra.api; +using System.Numerics; namespace LyraApi.Asset; @@ -15,11 +16,9 @@ public class AssetManager(IAsset.AssetManager assetManager) : IResource { case IEcs.WorldResourceResult.NONE: return null; - case IEcs.WorldResourceResult.WASM_RESOURCE_REP: - Console.WriteLine($"Got the resource rep: {result.AsWasmResourceRep}"); - var handle = new IAsset.AssetManager.THandle((int)result.AsWasmResourceRep); - var inner = new IAsset.AssetManager(handle); - return new AssetManager(inner); + case IEcs.WorldResourceResult.WASM_RESOURCE: + var handle = IAsset.AssetManager.FromEcsResource(result.AsWasmResource) ?? throw new MismatchedResourceResultException(); + return new AssetManager(handle); case IEcs.WorldResourceResult.BYTES: return null; default: diff --git a/guests/csharp/dotnet-guest-test/LyraApi/Asset/Handle.cs b/guests/csharp/dotnet-guest-test/LyraApi/Asset/Handle.cs index 7cdbe58..35e4466 100644 --- a/guests/csharp/dotnet-guest-test/LyraApi/Asset/Handle.cs +++ b/guests/csharp/dotnet-guest-test/LyraApi/Asset/Handle.cs @@ -4,10 +4,10 @@ public class Handle(UntypedHandle handle) where T : IAssetHandle { internal UntypedHandle inner = handle; - public bool Watched { get => inner.Watched; set => inner.Watched = value; } + public bool Watched { get => inner.Watched; } public ulong Version { get => inner.Version; } public Guid Uuid { get => inner.Uuid; } - public string Path { get => inner.Path; } + public string? Path { get => inner.Path; } public bool IsLoaded { get => inner.IsLoaded; } public void WaitForLoad() diff --git a/guests/csharp/dotnet-guest-test/LyraApi/Asset/UntypedHandle.cs b/guests/csharp/dotnet-guest-test/LyraApi/Asset/UntypedHandle.cs index ae49dc7..133baf2 100644 --- a/guests/csharp/dotnet-guest-test/LyraApi/Asset/UntypedHandle.cs +++ b/guests/csharp/dotnet-guest-test/LyraApi/Asset/UntypedHandle.cs @@ -6,10 +6,10 @@ public class UntypedHandle(IAsset.AssetHandle handle) { internal IAsset.AssetHandle inner = handle; - public bool Watched { get => inner.IsWatched(); set => inner.SetWatched(value); } + public bool Watched { get => inner.IsWatched(); } public ulong Version { get => inner.Version(); } public Guid Uuid { get => Guid.Parse(inner.Uuid()); } - public string Path { get => inner.Path(); } + public string? Path { get => inner.Path(); } public bool IsLoaded { get => inner.IsLoaded(); } public void WaitForLoad() diff --git a/guests/csharp/dotnet-guest-test/LyraApi/Ecs/IResource.cs b/guests/csharp/dotnet-guest-test/LyraApi/Ecs/IResource.cs index 7cc910c..5eeb3a8 100644 --- a/guests/csharp/dotnet-guest-test/LyraApi/Ecs/IResource.cs +++ b/guests/csharp/dotnet-guest-test/LyraApi/Ecs/IResource.cs @@ -7,4 +7,6 @@ public interface IResource public abstract static ulong TypeId { get; } public abstract static object? FromWasmResult(WorldResourceResult result); -} \ No newline at end of file +} + +public class MismatchedResourceResultException : Exception {} \ No newline at end of file diff --git a/guests/csharp/dotnet-guest-test/LyraApi/Ecs/World.cs b/guests/csharp/dotnet-guest-test/LyraApi/Ecs/World.cs index d6600b5..e594132 100644 --- a/guests/csharp/dotnet-guest-test/LyraApi/Ecs/World.cs +++ b/guests/csharp/dotnet-guest-test/LyraApi/Ecs/World.cs @@ -35,7 +35,7 @@ public class World(IEcs.EcsWorld world) return (T?)T.FromWasmResult(result); } - public AssetManager? GetAssetManager() + /* public AssetManager? GetAssetManager() { IAsset.AssetManager? assetManager = IAsset.AssetManager.FromWorld(inner); @@ -47,5 +47,5 @@ public class World(IEcs.EcsWorld world) { return null; } - } + } */ } \ No newline at end of file diff --git a/guests/csharp/dotnet-guest-test/LyraApi/Engine/DeltaTime.cs b/guests/csharp/dotnet-guest-test/LyraApi/Engine/DeltaTime.cs index 461f275..dc4836b 100644 --- a/guests/csharp/dotnet-guest-test/LyraApi/Engine/DeltaTime.cs +++ b/guests/csharp/dotnet-guest-test/LyraApi/Engine/DeltaTime.cs @@ -15,7 +15,7 @@ public struct DeltaTime : IResource { { return result.Tag switch { - WorldResourceResult.NONE | WorldResourceResult.WASM_RESOURCE_REP => null, + WorldResourceResult.NONE | WorldResourceResult.WASM_RESOURCE => null, WorldResourceResult.BYTES => MarshalUtils.FromBytes(result.AsBytes), _ => null, }; diff --git a/guests/csharp/dotnet-guest-test/wit/deps/lyraapi/asset.wit b/guests/csharp/dotnet-guest-test/wit/deps/lyraapi/asset.wit index 69ec15c..87a8e3f 100644 --- a/guests/csharp/dotnet-guest-test/wit/deps/lyraapi/asset.wit +++ b/guests/csharp/dotnet-guest-test/wit/deps/lyraapi/asset.wit @@ -1,6 +1,7 @@ interface asset { + use ecs.{ecs-world, ecs-resource}; + resource asset-handle { - set-watched: func(watched: bool); version: func() -> u64; uuid: func() -> string; path: func() -> option; @@ -18,10 +19,8 @@ interface asset { get-bytes: func() -> option>; } - use ecs.{ecs-world}; - resource asset-manager { - from-world: static func(w: borrow) -> option; + from-ecs-resource: static func(w: borrow) -> option; request: func(path: string) -> asset-handle; } } \ No newline at end of file diff --git a/guests/csharp/dotnet-guest-test/wit/deps/lyraapi/ecs.wit b/guests/csharp/dotnet-guest-test/wit/deps/lyraapi/ecs.wit index 39d993b..4b3585b 100644 --- a/guests/csharp/dotnet-guest-test/wit/deps/lyraapi/ecs.wit +++ b/guests/csharp/dotnet-guest-test/wit/deps/lyraapi/ecs.wit @@ -37,9 +37,16 @@ interface ecs { next: func() -> option>>; } + /// An resource that can be "downcasted" to a specific type. + /// + /// You can use `T.from-ecs-resource` to "downcast" the type + resource ecs-resource { + + } + variant world-resource-result { none, - wasm-resource-rep(u32), + wasm-resource(ecs-resource), bytes(list), } diff --git a/guests/rust/common-api/src/resource/dt.rs b/guests/rust/common-api/src/resource/dt.rs index 6eb842c..2001a89 100644 --- a/guests/rust/common-api/src/resource/dt.rs +++ b/guests/rust/common-api/src/resource/dt.rs @@ -20,7 +20,7 @@ impl EcsResource for DeltaTime { fn from_wasm_result(result: &WorldResourceResult) -> Option { match result { WorldResourceResult::None => None, - WorldResourceResult::WasmResourceRep(_) => None, + WorldResourceResult::WasmResource(_) => None, WorldResourceResult::Bytes(vec) => Some(Self(*bytemuck::from_bytes::(&vec))), } } diff --git a/guests/rust/common-api/wit/asset.wit b/guests/rust/common-api/wit/asset.wit index 69ec15c..87a8e3f 100644 --- a/guests/rust/common-api/wit/asset.wit +++ b/guests/rust/common-api/wit/asset.wit @@ -1,6 +1,7 @@ interface asset { + use ecs.{ecs-world, ecs-resource}; + resource asset-handle { - set-watched: func(watched: bool); version: func() -> u64; uuid: func() -> string; path: func() -> option; @@ -18,10 +19,8 @@ interface asset { get-bytes: func() -> option>; } - use ecs.{ecs-world}; - resource asset-manager { - from-world: static func(w: borrow) -> option; + from-ecs-resource: static func(w: borrow) -> option; request: func(path: string) -> asset-handle; } } \ No newline at end of file diff --git a/guests/rust/common-api/wit/ecs.wit b/guests/rust/common-api/wit/ecs.wit index 39d993b..4b3585b 100644 --- a/guests/rust/common-api/wit/ecs.wit +++ b/guests/rust/common-api/wit/ecs.wit @@ -37,9 +37,16 @@ interface ecs { next: func() -> option>>; } + /// An resource that can be "downcasted" to a specific type. + /// + /// You can use `T.from-ecs-resource` to "downcast" the type + resource ecs-resource { + + } + variant world-resource-result { none, - wasm-resource-rep(u32), + wasm-resource(ecs-resource), bytes(list), } diff --git a/guests/rust/witguest/wit/deps/lyraapi/asset.wit b/guests/rust/witguest/wit/deps/lyraapi/asset.wit index 69ec15c..87a8e3f 100644 --- a/guests/rust/witguest/wit/deps/lyraapi/asset.wit +++ b/guests/rust/witguest/wit/deps/lyraapi/asset.wit @@ -1,6 +1,7 @@ interface asset { + use ecs.{ecs-world, ecs-resource}; + resource asset-handle { - set-watched: func(watched: bool); version: func() -> u64; uuid: func() -> string; path: func() -> option; @@ -18,10 +19,8 @@ interface asset { get-bytes: func() -> option>; } - use ecs.{ecs-world}; - resource asset-manager { - from-world: static func(w: borrow) -> option; + from-ecs-resource: static func(w: borrow) -> option; request: func(path: string) -> asset-handle; } } \ No newline at end of file diff --git a/guests/rust/witguest/wit/deps/lyraapi/ecs.wit b/guests/rust/witguest/wit/deps/lyraapi/ecs.wit index 39d993b..4b3585b 100644 --- a/guests/rust/witguest/wit/deps/lyraapi/ecs.wit +++ b/guests/rust/witguest/wit/deps/lyraapi/ecs.wit @@ -37,9 +37,16 @@ interface ecs { next: func() -> option>>; } + /// An resource that can be "downcasted" to a specific type. + /// + /// You can use `T.from-ecs-resource` to "downcast" the type + resource ecs-resource { + + } + variant world-resource-result { none, - wasm-resource-rep(u32), + wasm-resource(ecs-resource), bytes(list), } diff --git a/src/asset.rs b/src/asset.rs index 42e2953..b6618a2 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -1,10 +1,6 @@ -use std::any::TypeId; - use async_trait::async_trait; -use tokio::task; use wasmtime::component::Resource as WasmResource; -use crate::WasmError; use crate::{lyra, Imports}; use lyra::api::asset as wasm_asset; @@ -14,17 +10,17 @@ impl wasm_asset::Host for Imports {} #[async_trait] impl wasm_asset::HostAssetManager for Imports { - async fn from_world(&mut self, world: WasmResource) -> Option> { - let world_entry = self - .world_slab - .get(world.rep() as _) - .ok_or(WasmError::InvalidResourceHandle("EcsWorld")).unwrap(); - let world = &world_entry.world; + async fn from_ecs_resource(&mut self, res: WasmResource) -> Option> { + let res_rep = res.rep() as u32; + let res = self.resource_data_slab.get_mut(res_rep as _)?; + res.borrow += 1; - let man = world.get_resource_data::()?; - let man_rep = self.asset_manager_slab.insert(man); - - Some(WasmResource::new_own(man_rep as _)) + // ensure that the + if !res.data.is::() { + return None; + } + + Some(WasmResource::new_own(res_rep as _)) } async fn request( @@ -32,33 +28,22 @@ impl wasm_asset::HostAssetManager for Imports { this: WasmResource, path: String, ) -> WasmResource { - let man = self.asset_manager_slab.get(this.rep() as _) + let man = self.resource_data_slab.get(this.rep() as _) .unwrap() - .get::(); + .data.get::(); let res = man.request_raw(&path).unwrap(); let rep = self.asset_handles_slab.insert(res); WasmResource::new_own(rep as _) } async fn drop(&mut self, this: WasmResource) -> wasmtime::Result<()> { - let rep = this.rep() as usize; - if self.asset_handles_slab.contains(rep) { - self.asset_manager_slab.remove(rep); - } - + self.resource_data_slab.drop_ref(this.rep() as _); Ok(()) } } #[async_trait] impl wasm_asset::HostAssetHandle for Imports { - async fn set_watched(&mut self, this: WasmResource, watched: bool) { - let han = self.asset_handles_slab.get(this.rep() as _) - .unwrap(); - - todo!() - } - async fn version(&mut self, this: WasmResource) -> u64 { let han = self.asset_handles_slab.get(this.rep() as _) .unwrap(); @@ -111,7 +96,8 @@ impl wasm_asset::HostAssetHandle for Imports { } async fn drop(&mut self, this: WasmResource) -> wasmtime::Result<()> { - todo!() + self.asset_handles_slab.drop_ref(this.rep() as _); + Ok(()) } } @@ -122,7 +108,10 @@ impl wasm_asset::HostImageHandle for Imports { // image will be none if the asset is not an image asset let image = untyped.as_typed::(); if image.is_some() { - Some(WasmResource::new_own(raw_handle.rep())) + let rep = raw_handle.rep(); + self.asset_handles_slab.increment_ref(rep as _); + + Some(WasmResource::new_own(rep)) } else { None } @@ -150,6 +139,7 @@ impl wasm_asset::HostImageHandle for Imports { } async fn drop(&mut self, this: WasmResource) -> wasmtime::Result<()> { - todo!() + self.asset_handles_slab.drop_ref(this.rep() as _); + Ok(()) } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 951c28c..8024c3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,22 +9,23 @@ use async_trait::async_trait; use common_api::math::Vec3; use component::witguest::math; use ecs::query::dynamic::{DynamicViewState, DynamicViewStateIter, QueryDynamicType}; -use lyra_ecs::{query::dynamic::DynamicViewOne, DynTypeId, World}; +use lyra_ecs::{query::dynamic::DynamicViewOne, World}; use lyra_ecs as ecs; use lyra_engine::DeltaTime; -use lyra_reflect::{FromType, ReflectedResource, RegisteredType, TypeRegistry}; +use lyra_reflect::{ReflectedResource, TypeRegistry}; use slab::Slab; use thiserror::Error; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; -use wasmtime::component::{Resource as WasmResource, ResourceAny}; +use wasmtime::component::Resource as WasmResource; mod proxy; pub use proxy::*; mod asset; +#[allow(unused_imports)] pub use asset::*; /* #[allow(unused_imports)] @@ -85,13 +86,84 @@ struct DynamicViewEntry { view: DynamicViewStateIter, } +#[derive(Clone)] +pub(crate) struct RefCountedData { + pub borrow: u64, + pub data: T, +} + +impl std::ops::Deref for RefCountedData { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl std::ops::DerefMut for RefCountedData { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +/// A Slab of reference counted data. +pub(crate) struct SlabRcData(Slab>); + +impl From>> for SlabRcData { + fn from(value: Slab>) -> Self { + Self(value) + } +} + +impl std::ops::Deref for SlabRcData { + type Target = Slab>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for SlabRcData { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl SlabRcData { + pub fn with_capacity(capacity: usize) -> Self { + Self(Slab::with_capacity(capacity)) + } + + pub fn increment_ref(&mut self, r: usize) { + if let Some(count) = self.0.get_mut(r) { + count.borrow += 1; + } + } + + pub fn drop_ref(&mut self, r: usize) { + if let Some(count) = self.0.get_mut(r) { + // only remove the data from the slab if this is the only borrow + if count.borrow == 1 { + self.0.remove(r); + } else { + count.borrow -= 1; + } + } + } + + /// Insert a new reference counted element into the slab. + pub fn insert(&mut self, data: T) -> usize { + self.0.insert(RefCountedData { borrow: 1, data, }) + } +} + unsafe impl Send for DynamicViewEntry {} -pub(crate) struct Imports { +pub struct Imports { pub(crate) world_slab: Slab, pub(crate) world_views_slab: Slab, - pub(crate) asset_manager_slab: Slab, - pub(crate) asset_handles_slab: Slab, + pub(crate) resource_data_slab: SlabRcData, + pub(crate) asset_handles_slab: SlabRcData, pub(crate) ctx: WasiCtx, pub(crate) table: ResourceTable, @@ -279,9 +351,16 @@ impl wasm_ecs::HostEcsWorld for Imports { let w = NonNull::from(world); let world = unsafe { w.as_ref() }; let refl = refl.reflect(world).unwrap(); - let res = proxy.marshal_to_bytes(self, refl.deref()); + let res = proxy.marshal_to_bytes(self, this.rep(), refl.deref()); - res + match res { + ProxyResult::None => wasm_ecs::WorldResourceResult::None, + ProxyResult::Bytes(bytes) => wasm_ecs::WorldResourceResult::Bytes(bytes), + ProxyResult::WasmResource(data) => { + let rep = self.resource_data_slab.insert(data); + wasm_ecs::WorldResourceResult::WasmResource(WasmResource::new_own(rep as _)) + } + } } async fn drop( @@ -439,6 +518,18 @@ impl GuestTypeLookup { } } +#[async_trait] +impl wasm_ecs::HostEcsResource for Imports { + async fn drop( + &mut self, + this: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + self.resource_data_slab.drop_ref(this.rep() as _); + + Ok(()) + } +} + #[async_trait] impl wasm_ecs::Host for Imports {} @@ -466,8 +557,8 @@ async fn main() -> wasmtime::Result<()> { // wants another world for some reason. world_slab: Slab::with_capacity(1), world_views_slab: Slab::with_capacity(10), - asset_manager_slab: Slab::with_capacity(1), - asset_handles_slab: Slab::with_capacity(25), + resource_data_slab: SlabRcData::with_capacity(25), + asset_handles_slab: SlabRcData::with_capacity(25), ctx: builder.build(), table: ResourceTable::new(), }, @@ -478,7 +569,7 @@ async fn main() -> wasmtime::Result<()> { 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 (script_en, (world_res_a, world_res_b)) = { + let (script_en, (world_res_a, _)) = { let mut world = World::new(); world.add_resource(DeltaTime::from(100.00)); @@ -492,12 +583,8 @@ async fn main() -> wasmtime::Result<()> { { let mut reg = world.get_resource_or_default::(); - - let mut dt_type = RegisteredType::new(); - dt_type.add_data(>::from_type()); - dt_type.add_data(>::from_type()); - - reg.add_registered_type(TypeId::of::(), dt_type); + add_proxy_type_data::(&mut reg); + add_proxy_type_data::(&mut reg); } let script_en = world.spawn(()); diff --git a/src/proxy.rs b/src/proxy.rs index a6842e7..7245175 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -1,20 +1,28 @@ -use crate::{lyra::api::ecs as wasm_ecs, Imports}; +use std::any::TypeId; + +use crate::Imports; use common_api::bytemuck; use lyra_engine::DeltaTime; -use lyra_reflect::{FromType, Reflect}; +use lyra_reflect::{FromType, Reflect, ReflectedResource, TypeRegistry}; + +pub enum ProxyResult { + None, + WasmResource(lyra_ecs::ResourceData), + Bytes(Vec) +} pub trait WasmProxy { - fn marshal_to_bytes(&self, imports: &mut Imports) -> wasm_ecs::WorldResourceResult; + fn marshal_to_bytes(&self, imports: &mut Imports, world_idx: u32) -> ProxyResult; } #[derive(Clone)] pub struct WasmProxied { - fn_marshal: for<'a> fn (&mut Imports, &'a dyn Reflect) -> wasm_ecs::WorldResourceResult, + fn_marshal: for<'a> fn (&mut Imports, world_idx: u32, &'a dyn Reflect) -> ProxyResult, } impl WasmProxied { - pub fn marshal_to_bytes(&self, imports: &mut Imports, reflected: &dyn Reflect) -> wasm_ecs::WorldResourceResult { - (self.fn_marshal)(imports, reflected) + pub fn marshal_to_bytes(&self, imports: &mut Imports, world_idx: u32, reflected: &dyn Reflect) -> ProxyResult { + (self.fn_marshal)(imports, world_idx, reflected) } } @@ -24,25 +32,33 @@ where { fn from_type() -> Self { WasmProxied { - fn_marshal: |imports, reflect| { + fn_marshal: |imports, world_idx: u32, reflect| { let this: &T = reflect.as_any().downcast_ref().unwrap(); - this.marshal_to_bytes(imports) + this.marshal_to_bytes(imports, world_idx) } } } } +pub fn add_proxy_type_data(reg: &mut TypeRegistry) { + let ty = reg.get_type_or_default(TypeId::of::()); + ty.add_data(>::from_type()); + ty.add_data(>::from_type()); +} + impl WasmProxy for DeltaTime { - fn marshal_to_bytes(&self, _: &mut Imports) -> wasm_ecs::WorldResourceResult { - wasm_ecs::WorldResourceResult::Bytes(bytemuck::bytes_of(&**self).to_vec()) + fn marshal_to_bytes(&self, _: &mut Imports, _: u32) -> ProxyResult { + ProxyResult::Bytes(bytemuck::bytes_of(&**self).to_vec()) } } impl WasmProxy for lyra_engine::assets::ResourceManager { - fn marshal_to_bytes(&self, imports: &mut Imports) -> wasm_ecs::WorldResourceResult { - //imports. - - todo!() - //imports.asset_manager_slab.insert(val) + fn marshal_to_bytes(&self, imports: &mut Imports, world_idx: u32) -> ProxyResult { + let world = &imports.world_slab.get(world_idx as _) + .unwrap().world; + match world.get_resource_data::() { + Some(data) => ProxyResult::WasmResource(data), + None => ProxyResult::None, + } } } \ No newline at end of file