Compare commits

..

3 Commits

31 changed files with 704 additions and 116 deletions

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59 VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-guest-test", "guests/csharp/dotnet-guest-test\dotnet-guest-test.csproj", "{68F96E6F-472F-409E-B36E-9C5E7206CCDE}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-guest-test", "guests\csharp\dotnet-guest-test\dotnet-guest-test.csproj", "{68F96E6F-472F-409E-B36E-9C5E7206CCDE}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -1,39 +1,10 @@
namespace ExampleWorld; namespace ExampleWorld;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ExampleWorld.wit.imports.lyra.api; using ExampleWorld.wit.imports.lyra.api;
using LyraApi; using LyraApi.Asset;
using LyraApi.Ecs; using LyraApi.Ecs;
using LyraApi.Engine;
[StructLayout(LayoutKind.Sequential)] using LyraApi.Math;
struct Vec3(float x, float y, float z) : IComponent
{
/// <summary>
/// The X component.
/// </summary>
public float X { get; set; } = x;
/// <summary>
/// The Y component
/// </summary>
public float Y { get; set; } = y;
/// <summary>
/// The Z component
/// </summary>
public float Z { get; set; } = z;
public static string HostName => "Vec3";
public static ulong HostSize => (ulong)Marshal.SizeOf<Vec3>();
public static ulong HostAlignment => (ulong)MarshalUtils.AlignmentOf<Vec3>();
public static ulong TypeId => 4124409524;
public static object? TakeFromBytes(byte[] bytes)
{
byte[] taken = bytes.Take((int)HostSize).ToArray();
return MarshalUtils.FromBytes<Vec3>(taken);
}
}
public class ExampleWorldImpl : IExampleWorld public class ExampleWorldImpl : IExampleWorld
{ {
@ -47,6 +18,32 @@ public class ExampleWorldImpl : IExampleWorld
{ {
Console.WriteLine("C#: Found entity at ({0}, {1}, {2})", comp.X, comp.Y, comp.Z); Console.WriteLine("C#: Found entity at ({0}, {1}, {2})", comp.X, comp.Y, comp.Z);
} }
/* DeltaTime? dt = world.GetResource<DeltaTime>();
if (dt != null) {
Console.WriteLine($"C#: Delta time: {dt?.Seconds:0.##}");
} */
AssetManager? man = world.GetResource<AssetManager>();
if (man != null) {
Handle<ImageHandle> han = man.Request<ImageHandle>("test_assets/white.png");
Console.WriteLine($"C#: Asset uuid: {han.Uuid}");
// wait for asset to load before trying to get the data
han.WaitForLoadRecursive();
if (!han.IsLoaded)
{
Console.WriteLine("C#: Asset is still not loaded, even after waiting!");
}
// will be null if the image hasn't loaded yet
ImageHandle? img = han.GetData();
Console.WriteLine($"C#: Size of image: ({img?.Width}, {img?.Height})");
}
else
{
Console.WriteLine("C#: No manager found");
}
} }
public static void OnUpdate(IEcs.EcsWorld gameWorld, IEcs.Entity owningEntity) public static void OnUpdate(IEcs.EcsWorld gameWorld, IEcs.Entity owningEntity)

View File

@ -0,0 +1,38 @@
using LyraApi.Ecs;
using ExampleWorld.wit.imports.lyra.api;
using System.Numerics;
namespace LyraApi.Asset;
public class AssetManager(IAsset.AssetManager assetManager) : IResource
{
internal IAsset.AssetManager inner = assetManager;
public static ulong TypeId => 567234789345;
public static object? FromWasmResult(IEcs.WorldResourceResult result)
{
switch (result.Tag)
{
case IEcs.WorldResourceResult.NONE:
return null;
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:
return null;
}
}
public UntypedHandle Request(string path)
{
return new UntypedHandle(inner.Request(path));
}
public Handle<T> Request<T>(string path) where T : IAssetHandle
{
return Request(path).AsHandle<T>();
}
}

View File

@ -0,0 +1,34 @@
namespace LyraApi.Asset;
public class Handle<T>(UntypedHandle handle) where T : IAssetHandle
{
internal UntypedHandle inner = handle;
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 bool IsLoaded { get => inner.IsLoaded; }
public void WaitForLoad()
{
inner.WaitForLoad();
}
public void WaitForLoadRecursive()
{
inner.WaitForLoadRecursive();
}
public T? GetData()
{
if (IsLoaded)
{
return (T?)T.FromRawHandle(inner);
}
else
{
return default;
}
}
}

View File

@ -0,0 +1,6 @@
namespace LyraApi.Asset;
public interface IAssetHandle
{
public abstract static object? FromRawHandle(UntypedHandle untypedHandle);
}

View File

@ -0,0 +1,28 @@
using ExampleWorld.wit.imports.lyra.api;
namespace LyraApi.Asset;
public class ImageHandle(IAsset.ImageHandle handle) : IAssetHandle
{
internal IAsset.ImageHandle inner = handle;
public uint? Height => inner.Height();
public uint? Width => inner.Width();
public static object? FromRawHandle(UntypedHandle untypedHandle)
{
var handle = IAsset.ImageHandle.FromRawHandle(untypedHandle.inner);
return handle != null ? new ImageHandle(handle) : null;
}
/// <summary>
/// Get the image's pixels as native endian bytes.
/// </summary>
///
/// Keep in mind that this does copy the image's pixels from the host.
/// This is pretty slow.
public byte[]? GetImageBytes()
{
return inner.GetBytes();
}
}

View File

@ -0,0 +1,29 @@
using ExampleWorld.wit.imports.lyra.api;
namespace LyraApi.Asset;
public class UntypedHandle(IAsset.AssetHandle handle)
{
internal IAsset.AssetHandle inner = handle;
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 bool IsLoaded { get => inner.IsLoaded(); }
public void WaitForLoad()
{
inner.WaitForLoad();
}
public void WaitForLoadRecursive()
{
inner.WaitRecurseDependenciesLoad();
}
public Handle<T> AsHandle<T>() where T : IAssetHandle
{
return new Handle<T>(this);
}
}

View File

@ -0,0 +1,12 @@
using static ExampleWorld.wit.imports.lyra.api.IEcs;
namespace LyraApi.Ecs;
public interface IResource
{
public abstract static ulong TypeId { get; }
public abstract static object? FromWasmResult(WorldResourceResult result);
}
public class MismatchedResourceResultException : Exception {}

View File

@ -2,15 +2,16 @@ namespace LyraApi.Ecs;
using ExampleWorld.wit.imports.lyra.api; using ExampleWorld.wit.imports.lyra.api;
using LyraApi; using LyraApi;
using LyraApi.Asset;
public class World(IEcs.EcsWorld world) public class World(IEcs.EcsWorld world)
{ {
private IEcs.EcsWorld Inner { get; set; } = world; internal IEcs.EcsWorld inner = world;
private Entity Spawn(List<IEcs.ComponentInfo> infos, params object[] comps) private Entity Spawn(List<IEcs.ComponentInfo> infos, params object[] comps)
{ {
byte[] bytes = comps.SelectMany(c => MarshalUtils.GetBytes(c)).ToArray(); byte[] bytes = comps.SelectMany(c => MarshalUtils.GetBytes(c)).ToArray();
return new Entity(Inner.Spawn(bytes, infos)); return new Entity(inner.Spawn(bytes, infos));
} }
public Entity Spawn<T1>(T1 c1) where T1 : IComponent public Entity Spawn<T1>(T1 c1) where T1 : IComponent
@ -24,7 +25,27 @@ public class World(IEcs.EcsWorld world)
List<Component> comps = [Component.FromComponent<T1>()]; List<Component> comps = [Component.FromComponent<T1>()];
List<IEcs.ComponentInfo> infos = comps.Select(c => c.GetComponentInfo().info).ToList(); List<IEcs.ComponentInfo> infos = comps.Select(c => c.GetComponentInfo().info).ToList();
IEcs.EcsDynamicView dynamicView = Inner.View(infos); IEcs.EcsDynamicView dynamicView = inner.View(infos);
return new ViewResult(comps, dynamicView).Get<T1>(); return new ViewResult(comps, dynamicView).Get<T1>();
} }
public T? GetResource<T>() where T : IResource
{
IEcs.WorldResourceResult result = inner.GetResource(Utils.ToWasmTypeId(T.TypeId));
return (T?)T.FromWasmResult(result);
}
/* public AssetManager? GetAssetManager()
{
IAsset.AssetManager? assetManager = IAsset.AssetManager.FromWorld(inner);
if (assetManager != null)
{
return new AssetManager(assetManager);
}
else
{
return null;
}
} */
} }

View File

@ -0,0 +1,23 @@
using System.Runtime.InteropServices;
using LyraApi;
using LyraApi.Ecs;
using static ExampleWorld.wit.imports.lyra.api.IEcs;
namespace LyraApi.Engine;
[StructLayout(LayoutKind.Sequential)]
public struct DeltaTime : IResource {
public float Seconds;
public static ulong TypeId => 83716348954;
public static object? FromWasmResult(WorldResourceResult result)
{
return result.Tag switch
{
WorldResourceResult.NONE | WorldResourceResult.WASM_RESOURCE => null,
WorldResourceResult.BYTES => MarshalUtils.FromBytes<DeltaTime>(result.AsBytes),
_ => null,
};
}
}

View File

@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
using LyraApi.Ecs;
namespace LyraApi.Math;
[StructLayout(LayoutKind.Sequential)]
struct Vec3(float x, float y, float z) : IComponent
{
/// <summary>
/// The X component.
/// </summary>
public float X { get; set; } = x;
/// <summary>
/// The Y component
/// </summary>
public float Y { get; set; } = y;
/// <summary>
/// The Z component
/// </summary>
public float Z { get; set; } = z;
public static string HostName => "Vec3";
public static ulong HostSize => (ulong)Marshal.SizeOf<Vec3>();
public static ulong HostAlignment => (ulong)MarshalUtils.AlignmentOf<Vec3>();
public static ulong TypeId => 4124409524;
public static object? TakeFromBytes(byte[] bytes)
{
byte[] taken = bytes.Take((int)HostSize).ToArray();
return MarshalUtils.FromBytes<Vec3>(taken);
}
}

View File

@ -0,0 +1,8 @@
using static ExampleWorld.wit.imports.lyra.api.IEcs;
internal static class Utils {
public static WasmTypeId ToWasmTypeId(ulong typeId)
{
return new WasmTypeId((typeId, 0));
}
}

View File

@ -18,12 +18,16 @@
</PropertyGroup> </PropertyGroup>
<!-- Copy WIT files from LyraApi to lyra-api wit folder. --> <!-- Copy WIT files from LyraApi to lyra-api wit folder. -->
<!-- <Target Name="CopyFolderOnBuild" BeforeTargets="WitCompile_InvokeTool"> <Target Name="CopyLyraApiWit" BeforeTargets="WitCompile_InvokeTool">
<ItemGroup> <ItemGroup>
<MyFiles Include="..\LyraApi\wit\**\*.wit" /> <MyFiles Include="..\..\rust\common-api\wit\**\*.wit" />
</ItemGroup> </ItemGroup>
<Copy SourceFiles="@(MyFiles)" DestinationFolder="wit\deps\lyraapi\%(RecursiveDir)" /> <Copy SourceFiles="@(MyFiles)" DestinationFolder="wit\deps\lyraapi\%(RecursiveDir)" />
</Target> --> </Target>
<Target Name="CopyComponentGuestWit" BeforeTargets="WitCompile_InvokeTool">
<Copy SourceFiles="..\..\rust\witguest\wit\world.wit" DestinationFiles="wit\world.wit" />
</Target>
<!-- Remove bindgen of LyraApi WIT .--> <!-- Remove bindgen of LyraApi WIT .-->
<!-- <Target Name="RemoveBindgenLyraApi" AfterTargets="WitCompile_InvokeTool"> <!-- <Target Name="RemoveBindgenLyraApi" AfterTargets="WitCompile_InvokeTool">

View File

@ -0,0 +1,26 @@
interface asset {
use ecs.{ecs-world, ecs-resource};
resource asset-handle {
version: func() -> u64;
uuid: func() -> string;
path: func() -> option<string>;
is-watched: func() -> bool;
is-loaded: func() -> bool;
wait-for-load: func();
wait-recurse-dependencies-load: func();
}
resource image-handle {
from-raw-handle: static func(raw-handle: borrow<asset-handle>) -> option<image-handle>;
height: func() -> option<u32>;
width: func() -> option<u32>;
get-bytes: func() -> option<list<u8>>;
}
resource asset-manager {
from-ecs-resource: static func(w: borrow<ecs-resource>) -> option<asset-manager>;
request: func(path: string) -> asset-handle;
}
}

View File

@ -37,6 +37,19 @@ interface ecs {
next: func() -> option<tuple<entity, list<u8>>>; next: func() -> option<tuple<entity, list<u8>>>;
} }
/// 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(ecs-resource),
bytes(list<u8>),
}
resource ecs-world { resource ecs-world {
constructor(); constructor();
@ -76,6 +89,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

@ -2,4 +2,5 @@ package lyra:api;
world imports { world imports {
import ecs; import ecs;
import asset;
} }

View File

@ -11,6 +11,7 @@ interface math {
/// An example world for the component to target. /// An example world for the component to target.
world example { world example {
import math; import math;
import lyra:api/asset;
use lyra:api/ecs.{ecs-world, entity}; use lyra:api/ecs.{ecs-world, entity};
import host-print: func(msg: string); import host-print: func(msg: string);

View File

@ -20,7 +20,7 @@ impl EcsResource for DeltaTime {
fn from_wasm_result(result: &WorldResourceResult) -> Option<Self> { fn from_wasm_result(result: &WorldResourceResult) -> Option<Self> {
match result { match result {
WorldResourceResult::None => None, WorldResourceResult::None => None,
WorldResourceResult::WasmResourceRep(_) => None, WorldResourceResult::WasmResource(_) => None,
WorldResourceResult::Bytes(vec) => Some(Self(*bytemuck::from_bytes::<f32>(&vec))), WorldResourceResult::Bytes(vec) => Some(Self(*bytemuck::from_bytes::<f32>(&vec))),
} }
} }

View File

@ -0,0 +1,26 @@
interface asset {
use ecs.{ecs-world, ecs-resource};
resource asset-handle {
version: func() -> u64;
uuid: func() -> string;
path: func() -> option<string>;
is-watched: func() -> bool;
is-loaded: func() -> bool;
wait-for-load: func();
wait-recurse-dependencies-load: func();
}
resource image-handle {
from-raw-handle: static func(raw-handle: borrow<asset-handle>) -> option<image-handle>;
height: func() -> option<u32>;
width: func() -> option<u32>;
get-bytes: func() -> option<list<u8>>;
}
resource asset-manager {
from-ecs-resource: static func(w: borrow<ecs-resource>) -> option<asset-manager>;
request: func(path: string) -> asset-handle;
}
}

View File

@ -37,9 +37,16 @@ interface ecs {
next: func() -> option<tuple<entity, list<u8>>>; next: func() -> option<tuple<entity, list<u8>>>;
} }
/// 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 { variant world-resource-result {
none, none,
wasm-resource-rep(u32), wasm-resource(ecs-resource),
bytes(list<u8>), bytes(list<u8>),
} }

View File

@ -2,4 +2,5 @@ package lyra:api;
world imports { world imports {
import ecs; import ecs;
import asset;
} }

View File

@ -5,6 +5,7 @@ wit_bindgen::generate!({
with: { with: {
"lyra:api/ecs": common_api::bindings::lyra::api::ecs, "lyra:api/ecs": common_api::bindings::lyra::api::ecs,
"lyra:api/asset": common_api::bindings::lyra::api::asset,
}, },
}); });

View File

@ -0,0 +1,26 @@
interface asset {
use ecs.{ecs-world, ecs-resource};
resource asset-handle {
version: func() -> u64;
uuid: func() -> string;
path: func() -> option<string>;
is-watched: func() -> bool;
is-loaded: func() -> bool;
wait-for-load: func();
wait-recurse-dependencies-load: func();
}
resource image-handle {
from-raw-handle: static func(raw-handle: borrow<asset-handle>) -> option<image-handle>;
height: func() -> option<u32>;
width: func() -> option<u32>;
get-bytes: func() -> option<list<u8>>;
}
resource asset-manager {
from-ecs-resource: static func(w: borrow<ecs-resource>) -> option<asset-manager>;
request: func(path: string) -> asset-handle;
}
}

View File

@ -37,9 +37,16 @@ interface ecs {
next: func() -> option<tuple<entity, list<u8>>>; next: func() -> option<tuple<entity, list<u8>>>;
} }
/// 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 { variant world-resource-result {
none, none,
wasm-resource-rep(u32), wasm-resource(ecs-resource),
bytes(list<u8>), bytes(list<u8>),
} }

View File

@ -2,4 +2,5 @@ package lyra:api;
world imports { world imports {
import ecs; import ecs;
import asset;
} }

View File

@ -11,6 +11,7 @@ interface math {
/// An example world for the component to target. /// An example world for the component to target.
world example { world example {
import math; import math;
import lyra:api/asset;
use lyra:api/ecs.{ecs-world, entity}; use lyra:api/ecs.{ecs-world, entity};
import host-print: func(msg: string); import host-print: func(msg: string);

2
mise.toml Normal file
View File

@ -0,0 +1,2 @@
[tools]
dotnet = "9.0.100-rc.2.24474.11"

145
src/asset.rs Normal file
View File

@ -0,0 +1,145 @@
use async_trait::async_trait;
use wasmtime::component::Resource as WasmResource;
use crate::{lyra, Imports};
use lyra::api::asset as wasm_asset;
use lyra::api::ecs as wasm_ecs;
impl wasm_asset::Host for Imports {}
#[async_trait]
impl wasm_asset::HostAssetManager for Imports {
async fn from_ecs_resource(&mut self, res: WasmResource<wasm_ecs::EcsResource>) -> Option<WasmResource<wasm_asset::AssetManager>> {
let res_rep = res.rep() as u32;
let res = self.resource_data_slab.get_mut(res_rep as _)?;
res.borrow += 1;
// ensure that the
if !res.data.is::<lyra_engine::assets::ResourceManager>() {
return None;
}
Some(WasmResource::new_own(res_rep as _))
}
async fn request(
&mut self,
this: WasmResource<wasm_asset::AssetManager>,
path: String,
) -> WasmResource<wasm_asset::AssetHandle> {
let man = self.resource_data_slab.get(this.rep() as _)
.unwrap()
.data.get::<lyra_engine::assets::ResourceManager>();
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<wasm_asset::AssetManager>) -> wasmtime::Result<()> {
self.resource_data_slab.drop_ref(this.rep() as _);
Ok(())
}
}
#[async_trait]
impl wasm_asset::HostAssetHandle for Imports {
async fn version(&mut self, this: WasmResource<wasm_asset::AssetHandle>) -> u64 {
let han = self.asset_handles_slab.get(this.rep() as _)
.unwrap();
han.version() as u64
}
async fn uuid(&mut self, this: WasmResource<wasm_asset::AssetHandle>) -> String {
let han = self.asset_handles_slab.get(this.rep() as _)
.unwrap();
han.uuid().to_string()
}
async fn path(&mut self, this: WasmResource<wasm_asset::AssetHandle>) -> Option<String> {
let han = self.asset_handles_slab.get(this.rep() as _)
.unwrap();
han.path()
}
async fn is_watched(&mut self, this: WasmResource<wasm_asset::AssetHandle>) -> bool {
let han = self.asset_handles_slab.get(this.rep() as _)
.unwrap();
han.is_watched()
}
async fn is_loaded(&mut self, this: WasmResource<wasm_asset::AssetHandle>) -> bool {
let han = self.asset_handles_slab.get(this.rep() as _)
.unwrap();
han.is_loaded()
}
async fn wait_for_load(&mut self, this: WasmResource<wasm_asset::AssetHandle>) {
let han = self.asset_handles_slab.get(this.rep() as _)
.unwrap().clone();
tokio::task::spawn_blocking(move || {
han.wait_for_load();
}).await.unwrap();
}
async fn wait_recurse_dependencies_load(
&mut self,
this: WasmResource<wasm_asset::AssetHandle>,
) {
let han = self.asset_handles_slab.get(this.rep() as _)
.unwrap().clone();
tokio::task::spawn_blocking(move || {
han.wait_recurse_dependencies_load();
}).await.unwrap();
}
async fn drop(&mut self, this: WasmResource<wasm_asset::AssetHandle>) -> wasmtime::Result<()> {
self.asset_handles_slab.drop_ref(this.rep() as _);
Ok(())
}
}
#[async_trait]
impl wasm_asset::HostImageHandle for Imports {
async fn from_raw_handle(&mut self, raw_handle: WasmResource<wasm_asset::AssetHandle>) -> Option<WasmResource<wasm_asset::ImageHandle>> {
let untyped = self.asset_handles_slab.get(raw_handle.rep() as _)?;
// image will be none if the asset is not an image asset
let image = untyped.as_typed::<lyra_engine::assets::Image>();
if image.is_some() {
let rep = raw_handle.rep();
self.asset_handles_slab.increment_ref(rep as _);
Some(WasmResource::new_own(rep))
} else {
None
}
}
async fn height(&mut self, this: WasmResource<wasm_asset::ImageHandle>) -> Option<u32> {
let untyped = self.asset_handles_slab.get(this.rep() as _)?;
let image = untyped.as_typed::<lyra_engine::assets::Image>()?;
let image = image.data_ref()?;
Some(image.height())
}
async fn width(&mut self, this: WasmResource<wasm_asset::ImageHandle>) -> Option<u32> {
let untyped = self.asset_handles_slab.get(this.rep() as _)?;
let image = untyped.as_typed::<lyra_engine::assets::Image>()?;
let image = image.data_ref()?;
Some(image.width())
}
async fn get_bytes(&mut self, this: WasmResource<wasm_asset::ImageHandle>) -> Option<Vec<u8>> {
let untyped = self.asset_handles_slab.get(this.rep() as _)?;
let image = untyped.as_typed::<lyra_engine::assets::Image>()?;
let image = image.data_ref()?;
Some(image.as_bytes().to_vec())
}
async fn drop(&mut self, this: WasmResource<wasm_asset::ImageHandle>) -> wasmtime::Result<()> {
self.asset_handles_slab.drop_ref(this.rep() as _);
Ok(())
}
}

View File

@ -9,12 +9,12 @@ use async_trait::async_trait;
use common_api::math::Vec3; use common_api::math::Vec3;
use component::witguest::math; use component::witguest::math;
use ecs::query::dynamic::{DynamicViewState, DynamicViewStateIter, QueryDynamicType}; 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_ecs as ecs;
use ::lyra_engine::DeltaTime; use lyra_engine::DeltaTime;
use lyra_reflect::{FromType, ReflectedResource, RegisteredType, TypeRegistry}; use lyra_reflect::{ReflectedResource, 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};
@ -24,10 +24,14 @@ use wasmtime::component::Resource as WasmResource;
mod proxy; mod proxy;
pub use proxy::*; pub use proxy::*;
mod asset;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use asset::*;
/* #[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;
} } */
wasmtime::component::bindgen!({ wasmtime::component::bindgen!({
world: "example", world: "example",
@ -82,14 +86,87 @@ struct DynamicViewEntry {
view: DynamicViewStateIter, view: DynamicViewStateIter,
} }
#[derive(Clone)]
pub(crate) struct RefCountedData<T> {
pub borrow: u64,
pub data: T,
}
impl<T> std::ops::Deref for RefCountedData<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> std::ops::DerefMut for RefCountedData<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
/// A Slab of reference counted data.
pub(crate) struct SlabRcData<T>(Slab<RefCountedData<T>>);
impl<T> From<Slab<RefCountedData<T>>> for SlabRcData<T> {
fn from(value: Slab<RefCountedData<T>>) -> Self {
Self(value)
}
}
impl<T> std::ops::Deref for SlabRcData<T> {
type Target = Slab<RefCountedData<T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> std::ops::DerefMut for SlabRcData<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> SlabRcData<T> {
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 {} unsafe impl Send for DynamicViewEntry {}
struct Imports { pub struct Imports {
world_slab: Slab<WorldEntry>, pub(crate) world_slab: Slab<WorldEntry>,
world_views_slab: Slab<DynamicViewEntry>, pub(crate) world_views_slab: Slab<DynamicViewEntry>,
pub(crate) resource_data_slab: SlabRcData<lyra_ecs::ResourceData>,
pub(crate) asset_handles_slab: SlabRcData<lyra_engine::assets::UntypedResHandle>,
ctx: WasiCtx, pub(crate) ctx: WasiCtx,
table: ResourceTable, pub(crate) table: ResourceTable,
} }
impl WasiView for Imports { impl WasiView for Imports {
@ -251,23 +328,39 @@ 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 { 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 let world_entry = self
.world_slab .world_slab
.try_remove(this.rep() as _) .get(this.rep() as _)
.ok_or(WasmError::InvalidResourceHandle("EcsWorld")).unwrap(); .ok_or(WasmError::InvalidResourceHandle("EcsWorld")).unwrap();
let world = &world_entry.world; let world = &world_entry.world;
let native_type = world.get_resource::<GuestTypeLookup>().unwrap(); let native_tid = {
let native_tid = native_type.lookup_type_id(type_id) let native_type = world.get_resource::<GuestTypeLookup>().unwrap();
.expect("failed to find native type id of wasm type"); 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 = {
let data = reg.get_type(native_tid).unwrap(); let reg = world.get_resource::<TypeRegistry>().unwrap();
let proxy = data.get_data::<WasmProxied>().unwrap(); reg.get_type(native_tid).unwrap().clone()
};
let refl = data.get_data::<ReflectedResource>().unwrap();
let proxy = data.get_data::<WasmProxied>().unwrap().clone();
let refl = data.get_data::<ReflectedResource>().unwrap().clone();
// SAFETY: reflecting the resource borrows from a different part of the world
// then marshaling would.
let w = NonNull::from(world);
let world = unsafe { w.as_ref() };
let refl = refl.reflect(world).unwrap(); let refl = refl.reflect(world).unwrap();
let res = proxy.marshal_to_bytes(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( async fn drop(
@ -425,6 +518,18 @@ impl GuestTypeLookup {
} }
} }
#[async_trait]
impl wasm_ecs::HostEcsResource for Imports {
async fn drop(
&mut self,
this: wasmtime::component::Resource<wasm_ecs::EcsResource>,
) -> wasmtime::Result<()> {
self.resource_data_slab.drop_ref(this.rep() as _);
Ok(())
}
}
#[async_trait] #[async_trait]
impl wasm_ecs::Host for Imports {} impl wasm_ecs::Host for Imports {}
@ -439,12 +544,8 @@ async fn main() -> wasmtime::Result<()> {
// Configure the linker // Configure the linker
let mut linker = wasmtime::component::Linker::new(&engine); let mut linker = wasmtime::component::Linker::new(&engine);
//wasmtime_wasi::preview0::add_to_linker_sync(&mut linker, |s| s)?;
//wasmtime_wasi::bindings::Imports::add_to_linker(&mut linker, |s| s)?;
wasmtime_wasi::add_to_linker_async(&mut linker)?; wasmtime_wasi::add_to_linker_async(&mut linker)?;
Example::add_to_linker(&mut linker, |s| s)?; Example::add_to_linker(&mut linker, |s| s)?;
//lyra::api::ecs::add_to_linker(&mut linker, |s| s)?;
//Api::add_to_linker(&mut linker, |s| s)?;
let mut builder = WasiCtxBuilder::new(); let mut builder = WasiCtxBuilder::new();
builder.inherit_stdio(); builder.inherit_stdio();
@ -456,34 +557,34 @@ async fn main() -> wasmtime::Result<()> {
// wants another world for some reason. // wants another world for some reason.
world_slab: Slab::with_capacity(1), world_slab: Slab::with_capacity(1),
world_views_slab: Slab::with_capacity(10), world_views_slab: Slab::with_capacity(10),
resource_data_slab: SlabRcData::with_capacity(25),
asset_handles_slab: SlabRcData::with_capacity(25),
ctx: builder.build(), ctx: builder.build(),
table: ResourceTable::new(), table: ResourceTable::new(),
}, },
); );
// 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, _)) = {
let mut world = World::new(); let mut world = World::new();
world.add_resource(DeltaTime::from(100.00)); world.add_resource(DeltaTime::from(100.00));
world.add_resource(lyra_engine::assets::ResourceManager::default());
let mut lookup = GuestTypeLookup::default(); let mut lookup = GuestTypeLookup::default();
lookup.infos.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>()); lookup.type_ids.insert(83716348954, TypeId::of::<lyra_engine::DeltaTime>());
lookup.type_ids.insert(567234789345, TypeId::of::<lyra_engine::assets::ResourceManager>());
world.add_resource(lookup); world.add_resource(lookup);
{ {
let mut reg = world.get_resource_or_default::<TypeRegistry>(); let mut reg = world.get_resource_or_default::<TypeRegistry>();
add_proxy_type_data::<DeltaTime>(&mut reg);
let mut dt_type = RegisteredType::new(); add_proxy_type_data::<lyra_engine::assets::ResourceManager>(&mut reg);
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(());
@ -506,36 +607,6 @@ async fn main() -> wasmtime::Result<()> {
call_script_stage_async(&mut store, &instance, "init", world_res_a, script_en.into()).await?; call_script_stage_async(&mut store, &instance, "init", world_res_a, script_en.into()).await?;
//call_script_stage_async(&mut store, &instance, "update", world_res_b, script_en.into()).await?; //call_script_stage_async(&mut store, &instance, "update", world_res_b, script_en.into()).await?;
println!("RUST: Guest is done"); println!("RUST: Guest is done");
let rep = world_res_b.rep();
if let Some(w) = store.data().world_slab.get(rep as _) {
let w = &w.world;
println!("RUST: Got {} archetypes", w.archetype_count());
for a in w.archetypes.values() {
println!("RUST: Archetype {}", a.id().0);
for col in &a.columns {
println!("RUST: Column type id: {:?}", col.info.type_id());
if col.info.type_id().is_id(DynTypeId::Unknown(4124409524)) {
println!("RUST: Found C# Vec3");
let pos = unsafe { col.get::<Vec3>(0) };
println!("RUST: Entity 0 pos: {:?}", *pos);
}
}
}
let mut dv = w.dynamic_view();
dv.push(QueryDynamicType::from_info(lyra_ecs::ComponentInfo::new::<Vec3>()));
let iter = dv.into_iter();
for (_, comps) in iter {
let first = comps.first().unwrap();
unsafe {
let v: Vec3 = std::ptr::read(first.ptr.as_ptr() as _);
println!("RUST: Found native Vec3! {:?}", v);
}
}
}
Ok(()) Ok(())
} }

View File

@ -1,20 +1,28 @@
use crate::lyra::api::ecs as wasm_ecs; use std::any::TypeId;
use crate::Imports;
use common_api::bytemuck; use common_api::bytemuck;
use lyra_engine::DeltaTime; 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<u8>)
}
pub trait WasmProxy { pub trait WasmProxy {
fn marshal_to_bytes(&self) -> wasm_ecs::WorldResourceResult; fn marshal_to_bytes(&self, imports: &mut Imports, world_idx: u32) -> ProxyResult;
} }
#[derive(Clone)] #[derive(Clone)]
pub struct WasmProxied { pub struct WasmProxied {
fn_marshal: for<'a> fn (&'a dyn Reflect) -> wasm_ecs::WorldResourceResult, fn_marshal: for<'a> fn (&mut Imports, world_idx: u32, &'a dyn Reflect) -> ProxyResult,
} }
impl WasmProxied { impl WasmProxied {
pub fn marshal_to_bytes(&self, reflected: &dyn Reflect) -> wasm_ecs::WorldResourceResult { pub fn marshal_to_bytes(&self, imports: &mut Imports, world_idx: u32, reflected: &dyn Reflect) -> ProxyResult {
(self.fn_marshal)(reflected) (self.fn_marshal)(imports, world_idx, reflected)
} }
} }
@ -24,16 +32,33 @@ where
{ {
fn from_type() -> Self { fn from_type() -> Self {
WasmProxied { WasmProxied {
fn_marshal: |reflect| { fn_marshal: |imports, world_idx: u32, reflect| {
let this: &T = reflect.as_any().downcast_ref().unwrap(); let this: &T = reflect.as_any().downcast_ref().unwrap();
this.marshal_to_bytes() this.marshal_to_bytes(imports, world_idx)
} }
} }
} }
} }
pub fn add_proxy_type_data<T: WasmProxy + lyra_ecs::ResourceObject + lyra_reflect::Reflect>(reg: &mut TypeRegistry) {
let ty = reg.get_type_or_default(TypeId::of::<T>());
ty.add_data(<ReflectedResource as FromType::<T>>::from_type());
ty.add_data(<WasmProxied as FromType::<T>>::from_type());
}
impl WasmProxy for DeltaTime { impl WasmProxy for DeltaTime {
fn marshal_to_bytes(&self) -> wasm_ecs::WorldResourceResult { fn marshal_to_bytes(&self, _: &mut Imports, _: u32) -> ProxyResult {
wasm_ecs::WorldResourceResult::Bytes(bytemuck::bytes_of(&**self).to_vec()) ProxyResult::Bytes(bytemuck::bytes_of(&**self).to_vec())
}
}
impl WasmProxy for lyra_engine::assets::ResourceManager {
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::<Self>() {
Some(data) => ProxyResult::WasmResource(data),
None => ProxyResult::None,
}
} }
} }

BIN
test_assets/white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB