Compare commits
10 Commits
4deed47b3d
...
dcb48e9acf
Author | SHA1 | Date |
---|---|---|
SeanOMik | dcb48e9acf | |
SeanOMik | fcf4f54e34 | |
SeanOMik | faa5387f93 | |
SeanOMik | 01bb7d0bc8 | |
SeanOMik | 8f926b3b58 | |
SeanOMik | a45f642d4c | |
SeanOMik | a36c1e221e | |
SeanOMik | 587f65cbe4 | |
SeanOMik | e289cd83a6 | |
SeanOMik | a2e1522af2 |
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
|
@ -1,4 +1,4 @@
|
||||||
workspace = { members = [ "common-api", "witguest"] }
|
workspace = { members = [ "./guests/rust/common-api", "./guests/rust/witguest"] }
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "wittest"
|
name = "wittest"
|
||||||
|
@ -8,13 +8,13 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1.80"
|
async-trait = "0.1.83"
|
||||||
tokio = { version = "1.37.0", features = ["full"] }
|
tokio = { version = "1.41.1", features = ["full"] }
|
||||||
wasmtime = "23.0.1"
|
wasmtime = "26.0.1"
|
||||||
wasmtime-wasi = "23.0.1"
|
wasmtime-wasi = "26.0.1"
|
||||||
lyra-ecs = { path = "./lyra-engine/lyra-ecs" }
|
lyra-ecs = { path = "./lyra-engine/crates/lyra-ecs" }
|
||||||
slab = "0.4.9"
|
slab = "0.4.9"
|
||||||
thiserror = "1.0.58"
|
thiserror = "2.0.0"
|
||||||
|
|
||||||
common-api = { path = "./common-api" }
|
common-api = { path = "./guests/rust/common-api" }
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.93"
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
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}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A4003DF0-C8FF-4A50-A66C-9777E900ED21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4003DF0-C8FF-4A50-A66C-9777E900ED21}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A4003DF0-C8FF-4A50-A66C-9777E900ED21}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A4003DF0-C8FF-4A50-A66C-9777E900ED21}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{68F96E6F-472F-409E-B36E-9C5E7206CCDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{68F96E6F-472F-409E-B36E-9C5E7206CCDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{68F96E6F-472F-409E-B36E-9C5E7206CCDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{68F96E6F-472F-409E-B36E-9C5E7206CCDE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,2 @@
|
||||||
|
bin
|
||||||
|
obj
|
|
@ -0,0 +1,56 @@
|
||||||
|
namespace ExampleWorld;
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using ExampleWorld.wit.imports.lyra.api;
|
||||||
|
using LyraApi;
|
||||||
|
using LyraApi.Ecs;
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExampleWorldImpl : IExampleWorld
|
||||||
|
{
|
||||||
|
public static void OnInit(IEcs.EcsWorld gameWorld, IEcs.Entity owningEntity)
|
||||||
|
{
|
||||||
|
var world = new World(gameWorld);
|
||||||
|
Entity entity = world.Spawn(new Vec3(7.0f, 30.0f, 18.0f));
|
||||||
|
Console.WriteLine("C#: Spawned entity with id {0}", entity.Id);
|
||||||
|
|
||||||
|
foreach ((Entity en, Vec3 comp) in world.View<Vec3>())
|
||||||
|
{
|
||||||
|
Console.WriteLine("C#: Found entity at ({0}, {1}, {2})", comp.X, comp.Y, comp.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnUpdate(IEcs.EcsWorld gameWorld, IEcs.Entity owningEntity)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using ExampleWorld.wit.imports.lyra.api;
|
||||||
|
using LyraApi.Ecs;
|
||||||
|
|
||||||
|
public class Component(string hostName, ulong hostSize, ulong hostAlignment, ulong typeId)
|
||||||
|
{
|
||||||
|
public string HostName { get; } = hostName;
|
||||||
|
public ulong HostSize { get; } = hostSize;
|
||||||
|
public ulong HostAlignment { get; } = hostAlignment;
|
||||||
|
public ulong TypeId { get; } = typeId;
|
||||||
|
|
||||||
|
public static Component FromComponent<T>() where T : IComponent
|
||||||
|
{
|
||||||
|
return new Component(T.HostName, T.HostSize, T.HostAlignment, T.TypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentInfo GetComponentInfo()
|
||||||
|
{
|
||||||
|
return new ComponentInfo(HostName, HostSize, HostAlignment, TypeId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
using ExampleWorld.wit.imports.lyra.api;
|
||||||
|
|
||||||
|
namespace LyraApi.Ecs;
|
||||||
|
|
||||||
|
public readonly struct ComponentInfo
|
||||||
|
{
|
||||||
|
internal readonly IEcs.ComponentInfo info;
|
||||||
|
|
||||||
|
public readonly string? HostName => info.hostName;
|
||||||
|
public readonly ulong Size => info.size;
|
||||||
|
public readonly ulong Alignment => info.alignment;
|
||||||
|
public readonly ulong TypeId => info.typeId.inner.Item1;
|
||||||
|
|
||||||
|
public ComponentInfo(IEcs.ComponentInfo info)
|
||||||
|
{
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentInfo(string hostName, ulong size, ulong alignment, ulong typeId)
|
||||||
|
{
|
||||||
|
var wasmTypeId = new IEcs.WasmTypeId((typeId, 0));
|
||||||
|
this.info = new IEcs.ComponentInfo(hostName, size, alignment, wasmTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ComponentInfo FromType<T>() where T : IComponent
|
||||||
|
{
|
||||||
|
var typeId = new IEcs.WasmTypeId((T.TypeId, 0));
|
||||||
|
var info = new IEcs.ComponentInfo(T.HostName, T.HostSize, T.HostAlignment, typeId);
|
||||||
|
return new ComponentInfo(info);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
namespace LyraApi.Ecs;
|
||||||
|
using ExampleWorld.wit.imports.lyra.api;
|
||||||
|
|
||||||
|
public class Entity(IEcs.Entity entity)
|
||||||
|
{
|
||||||
|
internal IEcs.Entity Inner { get; set; } = entity;
|
||||||
|
|
||||||
|
public ulong Id { get => Inner.id.id; }
|
||||||
|
public ulong Generation { get => Inner.generation; }
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Data.SqlTypes;
|
||||||
|
using ExampleWorld.wit.imports.lyra.api;
|
||||||
|
|
||||||
|
namespace LyraApi.Ecs;
|
||||||
|
|
||||||
|
public interface IComponent
|
||||||
|
{
|
||||||
|
public abstract static string HostName { get; }
|
||||||
|
public abstract static ulong HostSize { get; }
|
||||||
|
public abstract static ulong HostAlignment { get; }
|
||||||
|
public abstract static ulong TypeId { get; }
|
||||||
|
|
||||||
|
public abstract static object? TakeFromBytes(byte[] bytes);
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using ExampleWorld;
|
||||||
|
|
||||||
|
namespace LyraApi.Ecs;
|
||||||
|
|
||||||
|
public class View
|
||||||
|
{
|
||||||
|
internal List<ComponentInfo> infos;
|
||||||
|
|
||||||
|
private View(List<ComponentInfo> infos)
|
||||||
|
{
|
||||||
|
this.infos = infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static View Create<T1>() where T1 : IComponent
|
||||||
|
{
|
||||||
|
List<ComponentInfo> infos = [ComponentInfo.FromType<T1>()];
|
||||||
|
return new View(infos);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Runtime.InteropServices.Swift;
|
||||||
|
using ExampleWorld.wit.imports.lyra.api;
|
||||||
|
|
||||||
|
namespace LyraApi.Ecs;
|
||||||
|
|
||||||
|
public class ViewResult
|
||||||
|
{
|
||||||
|
private List<Component> components;
|
||||||
|
private IEcs.EcsDynamicView inner;
|
||||||
|
|
||||||
|
internal ViewResult(List<Component> components, IEcs.EcsDynamicView inner)
|
||||||
|
{
|
||||||
|
this.components = components;
|
||||||
|
this.inner = inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(Entity, T1)> Get<T1>() where T1 : IComponent
|
||||||
|
{
|
||||||
|
(IEcs.Entity, byte[])? row = inner.Next();
|
||||||
|
|
||||||
|
if (row is (var entity, byte[] bytes))
|
||||||
|
{
|
||||||
|
byte[] compBytes = bytes.Take((int)T1.HostSize).ToArray();
|
||||||
|
var t1 = (T1?)T1.TakeFromBytes(compBytes);
|
||||||
|
|
||||||
|
if (t1 != null)
|
||||||
|
{
|
||||||
|
yield return (new Entity(entity), t1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
namespace LyraApi.Ecs;
|
||||||
|
|
||||||
|
using ExampleWorld.wit.imports.lyra.api;
|
||||||
|
using LyraApi;
|
||||||
|
|
||||||
|
public class World(IEcs.EcsWorld world)
|
||||||
|
{
|
||||||
|
private IEcs.EcsWorld Inner { get; set; } = world;
|
||||||
|
|
||||||
|
private Entity Spawn(List<IEcs.ComponentInfo> infos, params object[] comps)
|
||||||
|
{
|
||||||
|
byte[] bytes = comps.SelectMany(c => MarshalUtils.GetBytes(c)).ToArray();
|
||||||
|
return new Entity(Inner.Spawn(bytes, infos));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity Spawn<T1>(T1 c1) where T1 : IComponent
|
||||||
|
{
|
||||||
|
List<IEcs.ComponentInfo> infos = [ComponentInfo.FromType<T1>().info];
|
||||||
|
return Spawn(infos, c1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(Entity, T1)> View<T1>() where T1 : IComponent
|
||||||
|
{
|
||||||
|
List<Component> comps = [Component.FromComponent<T1>()];
|
||||||
|
List<IEcs.ComponentInfo> infos = comps.Select(c => c.GetComponentInfo().info).ToList();
|
||||||
|
|
||||||
|
IEcs.EcsDynamicView dynamicView = Inner.View(infos);
|
||||||
|
return new ViewResult(comps, dynamicView).Get<T1>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
namespace LyraApi;
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
public static class MarshalUtils
|
||||||
|
{
|
||||||
|
internal struct AlignmentHelper<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value 0
|
||||||
|
public byte Padding;
|
||||||
|
public T Target;
|
||||||
|
#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int AlignmentOf<T>() where T : unmanaged
|
||||||
|
{
|
||||||
|
return (int)Marshal.OffsetOf<AlignmentHelper<T>>(nameof(AlignmentHelper<T>.Target));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] GetBytes<T>([DisallowNull] T data)
|
||||||
|
{
|
||||||
|
int size = Marshal.SizeOf(data);
|
||||||
|
byte[] arr = new byte[size];
|
||||||
|
|
||||||
|
IntPtr ptr = IntPtr.Zero;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ptr = Marshal.AllocHGlobal(size);
|
||||||
|
Marshal.StructureToPtr(data, ptr, true);
|
||||||
|
Marshal.Copy(ptr, arr, 0, size);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(ptr);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T? FromBytes<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(byte[] data)
|
||||||
|
{
|
||||||
|
var ptr = GCHandle.Alloc(data, GCHandleType.Pinned);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//var res = (T?)Marshal.PtrToStructure(ptr.AddrOfPinnedObject(), typeof(T));
|
||||||
|
var res = Marshal.PtrToStructure<T>(ptr.AddrOfPinnedObject());
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ptr.Free();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
|
||||||
|
<UseAppHost>false</UseAppHost>
|
||||||
|
<PublishTrimmed>true</PublishTrimmed>
|
||||||
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<IlcExportUnmanagedEntrypoints>true</IlcExportUnmanagedEntrypoints>
|
||||||
|
|
||||||
|
<!-- Ignore warning of multiple `PackageReference` items for the `ILCompiler.LLVM` package. -->
|
||||||
|
<NoWarn>$(NoWarn);NU1504</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Copy WIT files from LyraApi to lyra-api wit folder. -->
|
||||||
|
<!-- <Target Name="CopyFolderOnBuild" BeforeTargets="WitCompile_InvokeTool">
|
||||||
|
<ItemGroup>
|
||||||
|
<MyFiles Include="..\LyraApi\wit\**\*.wit" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Copy SourceFiles="@(MyFiles)" DestinationFolder="wit\deps\lyraapi\%(RecursiveDir)" />
|
||||||
|
</Target> -->
|
||||||
|
|
||||||
|
<!-- Remove bindgen of LyraApi WIT .-->
|
||||||
|
<!-- <Target Name="RemoveBindgenLyraApi" AfterTargets="WitCompile_InvokeTool">
|
||||||
|
<ItemGroup>
|
||||||
|
<FilesToDelete Include="obj\Debug\net*\wasi-wasm\wit_bindgen\*.lyra.api.*.cs"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<Delete Files="@(FilesToDelete)" />
|
||||||
|
</Target> -->
|
||||||
|
|
||||||
|
<!-- Reuse bindgen from LyraApi package instead of using local bindgen. -->
|
||||||
|
<!-- <Target Name="UpdateLyraApiReferences" AfterTargets="WitCompile_InvokeTool; RemoveBindgenLyraApi">
|
||||||
|
<Exec Condition="'$(OS)' == 'Unix'" Command="find obj/Debug -type f -wholename '*wit_bindgen/*.cs' -exec sed -i 's/ExampleWorld\.wit\.imports\.lyra\.api/ImportsWorld.wit.imports.lyra.api/g' {} \;" />
|
||||||
|
<Exec Condition="'$(OS)' == 'Windows_NT'" Command="powershell -Command "Get-ChildItem -Path 'obj\Debug' -Recurse -Filter '*.cs' ^
|
||||||
|
| Where-Object { $_.FullName -match 'wit_bindgen' } ^
|
||||||
|
| ForEach-Object { (Get-Content -Path $_.FullName) -replace 'ExampleWorld\.wit\.imports\.lyra\.api', 'ImportsWorld.wit.imports.lyra.api' ^
|
||||||
|
| Set-Content -Path $_.FullName }"" />
|
||||||
|
</Target> -->
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BytecodeAlliance.Componentize.DotNet.Wasm.SDK" Version="0.4.0-preview00007" />
|
||||||
|
<PackageReference Include="runtime.linux-x64.Microsoft.DotNet.ILCompiler.LLVM" Version="10.0.0-alpha.1.24531.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Wit Remove="**\*.wit" />
|
||||||
|
<Wit Include="wit" World="example" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- <ItemGroup>
|
||||||
|
<ProjectReference Include="..\LyraApi\LyraApi.csproj" />
|
||||||
|
</ItemGroup> -->
|
||||||
|
</Project>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
|
||||||
|
<clear />
|
||||||
|
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
|
||||||
|
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
|
@ -34,7 +34,7 @@ interface ecs {
|
||||||
/// Get the bytes of the next row in the view.
|
/// Get the bytes of the next row in the view.
|
||||||
///
|
///
|
||||||
/// A row contains multiple component serialized as bytes. The buffer is tighly packed.
|
/// A row contains multiple component serialized as bytes. The buffer is tighly packed.
|
||||||
next: func() -> option<list<u8>>;
|
next: func() -> option<tuple<entity, list<u8>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
resource ecs-world {
|
resource ecs-world {
|
||||||
|
@ -49,6 +49,16 @@ interface ecs {
|
||||||
/// and specify the layouts of them.
|
/// and specify the layouts of them.
|
||||||
spawn: func(components: list<u8>, component-infos: list<component-info>) -> entity;
|
spawn: func(components: list<u8>, component-infos: list<component-info>) -> entity;
|
||||||
|
|
||||||
|
/// Insert components into an existing entity.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `en`: The entity to insert into.
|
||||||
|
/// * `components`: A tightly packed byte buffer containing the components to spawn
|
||||||
|
/// with the entity. This expects the components in the same order of `component-infos`.
|
||||||
|
/// * `component-infos`: A list of `component-infos` uses to identify the components
|
||||||
|
/// and specify the layouts of them.
|
||||||
|
insert: func(en: entity, components: list<u8>, component-infos: list<component-info>);
|
||||||
|
|
||||||
/// Query for a list of entities and their components.
|
/// Query for a list of entities and their components.
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
|
@ -57,6 +67,15 @@ interface ecs {
|
||||||
/// Returns: an iterator that returns the byte buffers of each row.
|
/// Returns: an iterator that returns the byte buffers of each row.
|
||||||
view: func(component-infos: list<component-info>) -> ecs-dynamic-view;
|
view: func(component-infos: list<component-info>) -> ecs-dynamic-view;
|
||||||
|
|
||||||
|
/// Query for components from a single entity.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `en`: The entity to query components from.
|
||||||
|
/// * `component-infos`: The `component-info`'s of the components that you are querying.
|
||||||
|
///
|
||||||
|
/// 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>>;
|
||||||
|
|
||||||
//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>));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,7 +26,7 @@ use lyra::api::ecs::{EcsDynamicView, EcsWorld, Entity, WasmTypeId};
|
||||||
impl WasmTypeId {
|
impl WasmTypeId {
|
||||||
pub fn of<T: 'static>() -> Self {
|
pub fn of<T: 'static>() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: unsafe { mem::transmute(TypeId::of::<T>()) }
|
inner: unsafe { mem::transmute(TypeId::of::<T>()) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,16 +74,53 @@ impl<'a> World<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new [`Entity`] in the world with a component [`Bundle`].
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// ```nobuild
|
||||||
|
/// let new_entity = world.spawn((Vec3::new(10.0, 1.0, 10.0), PlayerMovement::new()));
|
||||||
|
/// ```
|
||||||
pub fn spawn<B: Bundle>(&self, bundle: B) -> Result<Entity, ComponentSerializationError> {
|
pub fn spawn<B: Bundle>(&self, bundle: B) -> Result<Entity, ComponentSerializationError> {
|
||||||
let infos = B::component_info();
|
let infos = B::component_info();
|
||||||
let bundle = bundle.to_bytes()?;
|
let bundle = bundle.to_bytes()?;
|
||||||
Ok(self.inner.spawn(&bundle, &infos))
|
Ok(self.inner.spawn(&bundle, &infos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a bundle into an existing entity.
|
||||||
|
///
|
||||||
|
/// If the components already exist on the entity, they will be updated, else the entity will
|
||||||
|
/// be moved to a different Archetype that can store the entity. That may involve creating
|
||||||
|
/// a new Archetype.
|
||||||
|
pub fn insert<B: Bundle>(&self, en: Entity, bundle: B) -> Result<(), ComponentSerializationError> {
|
||||||
|
let infos = B::component_info();
|
||||||
|
let bundle = bundle.to_bytes()?;
|
||||||
|
Ok(self.inner.insert(en, &bundle, &infos))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query components from entities.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use common_api::{math::{Vec3, Vec4}, World};
|
||||||
|
/// # let world = World::new();
|
||||||
|
///
|
||||||
|
/// let view = world.view::<(Vec3, Vec4)>();
|
||||||
|
/// for row in view {
|
||||||
|
/// let (en, (p3, p4)) = row.unwrap();
|
||||||
|
/// println!("Entity: {}", en.id.id);
|
||||||
|
/// println!(" vec3: {:?}", p3);
|
||||||
|
/// println!(" vec4: {:?}", p4);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn view<B: Bundle>(&self) -> View<B> {
|
pub fn view<B: Bundle>(&self) -> View<B> {
|
||||||
let infos = B::component_info();
|
let infos = B::component_info();
|
||||||
View::from(self.inner.view(&infos))
|
View::from(self.inner.view(&infos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn view_one<B: Bundle>(&self, en: Entity) -> Option<Result<B, ComponentSerializationError>> {
|
||||||
|
let infos = B::component_info();
|
||||||
|
self.inner.view_one(en, &infos)
|
||||||
|
.map(|bytes| B::from_bytes(bytes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct View<B: Bundle> {
|
pub struct View<B: Bundle> {
|
||||||
|
@ -95,7 +132,7 @@ impl<B: Bundle> From<EcsDynamicView> for View<B> {
|
||||||
fn from(value: EcsDynamicView) -> Self {
|
fn from(value: EcsDynamicView) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: value,
|
inner: value,
|
||||||
_marker: PhantomData
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,11 +148,11 @@ impl<B: Bundle> View<B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Bundle> Iterator for View<B> {
|
impl<B: Bundle> Iterator for View<B> {
|
||||||
type Item = Result<B, ComponentSerializationError>;
|
type Item = Result<(Entity, B), ComponentSerializationError>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if let Some(row) = self.inner.next() {
|
if let Some((en, row)) = self.inner.next() {
|
||||||
Some(B::from_bytes(row))
|
Some(B::from_bytes(row).map(|b| (en, b)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
|
@ -34,7 +34,7 @@ interface ecs {
|
||||||
/// Get the bytes of the next row in the view.
|
/// Get the bytes of the next row in the view.
|
||||||
///
|
///
|
||||||
/// A row contains multiple component serialized as bytes. The buffer is tighly packed.
|
/// A row contains multiple component serialized as bytes. The buffer is tighly packed.
|
||||||
next: func() -> option<list<u8>>;
|
next: func() -> option<tuple<entity, list<u8>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
resource ecs-world {
|
resource ecs-world {
|
||||||
|
@ -49,6 +49,16 @@ interface ecs {
|
||||||
/// and specify the layouts of them.
|
/// and specify the layouts of them.
|
||||||
spawn: func(components: list<u8>, component-infos: list<component-info>) -> entity;
|
spawn: func(components: list<u8>, component-infos: list<component-info>) -> entity;
|
||||||
|
|
||||||
|
/// Insert components into an existing entity.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `en`: The entity to insert into.
|
||||||
|
/// * `components`: A tightly packed byte buffer containing the components to spawn
|
||||||
|
/// with the entity. This expects the components in the same order of `component-infos`.
|
||||||
|
/// * `component-infos`: A list of `component-infos` uses to identify the components
|
||||||
|
/// and specify the layouts of them.
|
||||||
|
insert: func(en: entity, components: list<u8>, component-infos: list<component-info>);
|
||||||
|
|
||||||
/// Query for a list of entities and their components.
|
/// Query for a list of entities and their components.
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
|
@ -57,6 +67,15 @@ interface ecs {
|
||||||
/// Returns: an iterator that returns the byte buffers of each row.
|
/// Returns: an iterator that returns the byte buffers of each row.
|
||||||
view: func(component-infos: list<component-info>) -> ecs-dynamic-view;
|
view: func(component-infos: list<component-info>) -> ecs-dynamic-view;
|
||||||
|
|
||||||
|
/// Query for components from a single entity.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `en`: The entity to query components from.
|
||||||
|
/// * `component-infos`: The `component-info`'s of the components that you are querying.
|
||||||
|
///
|
||||||
|
/// 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>>;
|
||||||
|
|
||||||
//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>));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,9 +13,14 @@ struct Component;
|
||||||
impl Guest for Component {
|
impl Guest for Component {
|
||||||
fn on_init(world: &EcsWorld, _owning_entity: Entity) -> Result<(), ()> {
|
fn on_init(world: &EcsWorld, _owning_entity: Entity) -> Result<(), ()> {
|
||||||
let world = World::from(world);
|
let world = World::from(world);
|
||||||
let en = world.spawn((Vec3::new(7.0, 30.0, 18.0), Vec4::new(10.0, 332.35, 329.0, 95.0)));
|
let en = world.spawn((Vec3::new(7.0, 30.0, 18.0), Vec4::new(10.0, 332.35, 329.0, 95.0)))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
println!("guest spawned {:?}", en);
|
println!("Spawned {:?}", en);
|
||||||
|
|
||||||
|
let pos3 = world.view_one::<Vec3>(en)
|
||||||
|
.unwrap().unwrap();
|
||||||
|
println!("Found entity at {pos3:?}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -25,9 +30,10 @@ impl Guest for Component {
|
||||||
|
|
||||||
let view = world.view::<(Vec3, Vec4)>();
|
let view = world.view::<(Vec3, Vec4)>();
|
||||||
for pos in view {
|
for pos in view {
|
||||||
let (p3, p4) = pos.unwrap();
|
let (en, (p3, p4)) = pos.unwrap();
|
||||||
println!("Retrieved vec3: {:?}", p3);
|
println!("Entity: {}", en.id.id);
|
||||||
println!("Retrieved vec4: {:?}", p4);
|
println!(" vec3: {:?}", p3);
|
||||||
|
println!(" vec4: {:?}", p4);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
|
@ -0,0 +1,81 @@
|
||||||
|
package lyra:api;
|
||||||
|
|
||||||
|
interface ecs {
|
||||||
|
record entity-id {
|
||||||
|
id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
record entity {
|
||||||
|
id: entity-id,
|
||||||
|
generation: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
record wasm-type-id {
|
||||||
|
// represents a u128, can be converted into that with mem::transmute
|
||||||
|
inner: tuple<u64, u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
record component-info {
|
||||||
|
host-name: option<string>,
|
||||||
|
/// The size of the component in memory.
|
||||||
|
size: u64,
|
||||||
|
/// The alignment of the component in memory.
|
||||||
|
alignment: u64,
|
||||||
|
/// The type id of the component.
|
||||||
|
///
|
||||||
|
/// This must be unique between component types since its used to identify the components
|
||||||
|
/// in spawning and querying.
|
||||||
|
type-id: wasm-type-id,
|
||||||
|
}
|
||||||
|
|
||||||
|
resource ecs-dynamic-view {
|
||||||
|
constructor(wrld: borrow<ecs-world>, component-infos: list<component-info>);
|
||||||
|
|
||||||
|
/// Get the bytes of the next row in the view.
|
||||||
|
///
|
||||||
|
/// A row contains multiple component serialized as bytes. The buffer is tighly packed.
|
||||||
|
next: func() -> option<tuple<entity, list<u8>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
resource ecs-world {
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
/// Spawn an entity.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `components`: A tightly packed byte buffer containing the components to spawn
|
||||||
|
/// with the entity. This expects the components in the same order of `component-infos`.
|
||||||
|
/// * `component-infos`: A list of `component-infos` uses to identify the components
|
||||||
|
/// and specify the layouts of them.
|
||||||
|
spawn: func(components: list<u8>, component-infos: list<component-info>) -> entity;
|
||||||
|
|
||||||
|
/// Insert components into an existing entity.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `en`: The entity to insert into.
|
||||||
|
/// * `components`: A tightly packed byte buffer containing the components to spawn
|
||||||
|
/// with the entity. This expects the components in the same order of `component-infos`.
|
||||||
|
/// * `component-infos`: A list of `component-infos` uses to identify the components
|
||||||
|
/// and specify the layouts of them.
|
||||||
|
insert: func(en: entity, components: list<u8>, component-infos: list<component-info>);
|
||||||
|
|
||||||
|
/// Query for a list of entities and their components.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `component-infos`: The `component-info`'s of the components that you are querying.
|
||||||
|
///
|
||||||
|
/// Returns: an iterator that returns the byte buffers of each row.
|
||||||
|
view: func(component-infos: list<component-info>) -> ecs-dynamic-view;
|
||||||
|
|
||||||
|
/// Query for components from a single entity.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `en`: The entity to query components from.
|
||||||
|
/// * `component-infos`: The `component-info`'s of the components that you are querying.
|
||||||
|
///
|
||||||
|
/// 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>>;
|
||||||
|
|
||||||
|
//with_system: func(stage: string, component-infos: list<component-info>, system: func(components: list<u8>));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package lyra:api;
|
||||||
|
|
||||||
|
world imports {
|
||||||
|
import ecs;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package component:witguest;
|
||||||
|
|
||||||
|
interface math {
|
||||||
|
record vec3 {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
z: f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An example world for the component to target.
|
||||||
|
world example {
|
||||||
|
import math;
|
||||||
|
use lyra:api/ecs.{ecs-world, entity};
|
||||||
|
|
||||||
|
import host-print: func(msg: string);
|
||||||
|
|
||||||
|
export on-init: func(game-world: borrow<ecs-world>, owning-entity: entity) -> result;
|
||||||
|
export on-update: func(game-world: borrow<ecs-world>, owning-entity: entity) -> result;
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
Subproject commit 12c8ece4183f117864bac362d181ea5906141c6f
|
Subproject commit 62adcf2b50a458314f573425061530d3dc3d25da
|
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
321
src/main.rs
321
src/main.rs
|
@ -1,12 +1,13 @@
|
||||||
use std::alloc::Layout;
|
use std::alloc::Layout;
|
||||||
use std::borrow::Borrow;
|
use std::collections::HashMap;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
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::{Component, World};
|
use lyra_ecs::{query::dynamic::DynamicViewOne, DynTypeId, World};
|
||||||
|
|
||||||
use lyra_ecs as ecs;
|
use lyra_ecs as ecs;
|
||||||
|
|
||||||
|
@ -14,50 +15,16 @@ 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, ResourceAny};
|
use wasmtime::component::Resource as WasmResource;
|
||||||
|
|
||||||
use wasmtime::component::Val as ComponentVal;
|
|
||||||
|
|
||||||
#[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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* mod bindings {
|
|
||||||
wasmtime::component::bindgen!({
|
|
||||||
world: "imports",
|
|
||||||
path: "common-api/wit",
|
|
||||||
//tracing: true,
|
|
||||||
async: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
mod component {
|
|
||||||
wasmtime::component::bindgen!({
|
|
||||||
world: "example",
|
|
||||||
path: "witguest/wit",
|
|
||||||
//tracing: true,
|
|
||||||
async: true,
|
|
||||||
|
|
||||||
with: {
|
|
||||||
"lyra:api": super::lyra::api,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use bindings::lyra::api::ecs as wasm_ecs; */
|
|
||||||
|
|
||||||
/* wasmtime::component::bindgen!({
|
|
||||||
world: "imports",
|
|
||||||
path: "common-api/wit",
|
|
||||||
//tracing: true,
|
|
||||||
async: true,
|
|
||||||
}); */
|
|
||||||
|
|
||||||
wasmtime::component::bindgen!({
|
wasmtime::component::bindgen!({
|
||||||
world: "example",
|
world: "example",
|
||||||
path: "witguest/wit",
|
path: "guests/rust/witguest/wit",
|
||||||
//tracing: true,
|
|
||||||
async: true,
|
async: true,
|
||||||
|
|
||||||
with: {
|
with: {
|
||||||
|
@ -96,14 +63,6 @@ impl Into<ecs::Entity> for wasm_ecs::Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Component)]
|
|
||||||
struct Vec3 {
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
z: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WorldEntry {
|
struct WorldEntry {
|
||||||
world: lyra_ecs::World,
|
world: lyra_ecs::World,
|
||||||
/// Indices of dynamic views that are still alive
|
/// Indices of dynamic views that are still alive
|
||||||
|
@ -172,6 +131,41 @@ impl wasm_ecs::HostEcsWorld for Imports {
|
||||||
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))
|
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let world = &mut world_entry.world;
|
let world = &mut world_entry.world;
|
||||||
|
let lookup = world.get_resource::<GuestTypeLookup>().unwrap();
|
||||||
|
|
||||||
|
// add the components in the tightly packed `components` buffer into the dynamic bundle
|
||||||
|
let mut bundle = ecs::DynamicBundle::new();
|
||||||
|
let mut offset = 0;
|
||||||
|
for info in infos {
|
||||||
|
// Try to get the Native type's ComponentInfo.
|
||||||
|
let info = lookup.lookup_info(info.into());
|
||||||
|
|
||||||
|
// SAFETY: The components are tightly packed. Adding the offset to the pointer will
|
||||||
|
// get the next component in the buffer.
|
||||||
|
let data_ptr = unsafe { NonNull::new_unchecked(components.as_mut_ptr().add(offset)) };
|
||||||
|
bundle.push_unknown(data_ptr, info.clone().into());
|
||||||
|
|
||||||
|
offset += info.layout().size() as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(lookup);
|
||||||
|
let en = world.spawn(bundle);
|
||||||
|
en.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert(
|
||||||
|
&mut self,
|
||||||
|
this: WasmResource<wasm_ecs::EcsWorld>,
|
||||||
|
entity: Entity,
|
||||||
|
mut components: Vec<u8>,
|
||||||
|
infos: Vec<wasm_ecs::ComponentInfo>,
|
||||||
|
) {
|
||||||
|
let world_entry = self
|
||||||
|
.world_slab
|
||||||
|
.get_mut(this.rep() as _)
|
||||||
|
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))
|
||||||
|
.unwrap();
|
||||||
|
let world = &mut world_entry.world;
|
||||||
|
|
||||||
// add the components in the tightly packed `components` buffer into the dynamic bundle
|
// add the components in the tightly packed `components` buffer into the dynamic bundle
|
||||||
let mut bundle = ecs::DynamicBundle::new();
|
let mut bundle = ecs::DynamicBundle::new();
|
||||||
|
@ -185,8 +179,7 @@ impl wasm_ecs::HostEcsWorld for Imports {
|
||||||
offset += info.size as usize;
|
offset += info.size as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
let en = world.spawn(bundle);
|
world.insert(entity.into(), bundle);
|
||||||
en.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn view(
|
async fn view(
|
||||||
|
@ -197,64 +190,27 @@ impl wasm_ecs::HostEcsWorld for Imports {
|
||||||
<Self as wasm_ecs::HostEcsDynamicView>::new(self, this, infos).await
|
<Self as wasm_ecs::HostEcsDynamicView>::new(self, this, infos).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop(
|
async fn view_one(
|
||||||
&mut self,
|
&mut self,
|
||||||
this: wasmtime::component::Resource<wasm_ecs::EcsWorld>,
|
this: wasmtime::component::Resource<wasm_ecs::EcsWorld>,
|
||||||
) -> wasmtime::Result<()> {
|
entity: Entity,
|
||||||
let mut world_entry = self
|
|
||||||
.world_slab
|
|
||||||
.try_remove(this.rep() as _)
|
|
||||||
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))?;
|
|
||||||
|
|
||||||
// make sure that all the world views are now dead
|
|
||||||
for view in world_entry.views.drain() {
|
|
||||||
self.world_views_slab.remove(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl math::Host for Imports {}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl wasm_ecs::HostEcsDynamicView for Imports {
|
|
||||||
async fn new(
|
|
||||||
&mut self,
|
|
||||||
world: WasmResource<wasm_ecs::EcsWorld>,
|
|
||||||
infos: Vec<wasm_ecs::ComponentInfo>,
|
infos: Vec<wasm_ecs::ComponentInfo>,
|
||||||
) -> wasmtime::component::Resource<wasm_ecs::EcsDynamicView> {
|
) -> Option<Vec<u8>> {
|
||||||
// Create a dynamic view for querying for the components
|
|
||||||
let mut view = DynamicViewState::new();
|
|
||||||
for info in infos.clone() {
|
|
||||||
view.push(QueryDynamicType::from_info(info.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let world_rep = world.rep();
|
|
||||||
let entry = DynamicViewEntry {
|
|
||||||
world_index: world_rep as _,
|
|
||||||
view: view.into_iter(),
|
|
||||||
};
|
|
||||||
let view_index = self.world_views_slab.insert(entry);
|
|
||||||
|
|
||||||
// link the dynamic view to the world that its a part of.
|
|
||||||
let world_entry = self
|
let world_entry = self
|
||||||
.world_slab
|
.world_slab
|
||||||
.get_mut(world.rep() as _)
|
.get_mut(this.rep() as _)
|
||||||
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))
|
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
world_entry.views.insert(view_index);
|
let world = &mut world_entry.world;
|
||||||
|
|
||||||
WasmResource::new_own(view_index as _)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn next(&mut self, view: WasmResource<wasm_ecs::EcsDynamicView>) -> Option<Vec<u8>> {
|
//world.view_one(entity.into());
|
||||||
let view_entry = self.world_views_slab.get_mut(view.rep() as _).unwrap();
|
let queries = infos.into_iter()
|
||||||
let world_entry = self.world_slab.get(view_entry.world_index).unwrap();
|
.map(|i| QueryDynamicType::from_info(i.into()))
|
||||||
let view = &mut view_entry.view;
|
.collect();
|
||||||
|
let v = DynamicViewOne::new_with(&world, entity.into(), queries);
|
||||||
|
|
||||||
// get the next row in the view and copy the returned components into a tightly packed
|
if let Some(row) = v.get() {
|
||||||
if let Some(row) = view.next(&world_entry.world) {
|
|
||||||
let row_len = row.iter().map(|d| d.info.layout().size()).sum();
|
let row_len = row.iter().map(|d| d.info.layout().size()).sum();
|
||||||
let mut row_buf = Vec::<u8>::with_capacity(row_len);
|
let mut row_buf = Vec::<u8>::with_capacity(row_len);
|
||||||
|
|
||||||
|
@ -285,7 +241,110 @@ impl wasm_ecs::HostEcsDynamicView for Imports {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop(
|
|
||||||
|
async fn drop(
|
||||||
|
&mut self,
|
||||||
|
this: wasmtime::component::Resource<EcsWorld>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let mut world_entry = self
|
||||||
|
.world_slab
|
||||||
|
.try_remove(this.rep() as _)
|
||||||
|
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))?;
|
||||||
|
|
||||||
|
// make sure that all the world views are now dead
|
||||||
|
for view in world_entry.views.drain() {
|
||||||
|
self.world_views_slab.remove(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl math::Host for Imports {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl wasm_ecs::HostEcsDynamicView for Imports {
|
||||||
|
async fn new(
|
||||||
|
&mut self,
|
||||||
|
world: WasmResource<wasm_ecs::EcsWorld>,
|
||||||
|
infos: Vec<wasm_ecs::ComponentInfo>,
|
||||||
|
) -> wasmtime::component::Resource<wasm_ecs::EcsDynamicView> {
|
||||||
|
|
||||||
|
// Create the view state
|
||||||
|
let view = {
|
||||||
|
let world_entry = self
|
||||||
|
.world_slab
|
||||||
|
.get_mut(world.rep() as _)
|
||||||
|
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))
|
||||||
|
.unwrap();
|
||||||
|
let lookup = world_entry.world.get_resource::<GuestTypeLookup>().unwrap();
|
||||||
|
|
||||||
|
// Create a dynamic view for querying for the components
|
||||||
|
let mut view = DynamicViewState::new();
|
||||||
|
for info in infos.clone() {
|
||||||
|
let info = lookup.lookup_info(info.into());
|
||||||
|
view.push(QueryDynamicType::from_info(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
view
|
||||||
|
};
|
||||||
|
|
||||||
|
let world_rep = world.rep();
|
||||||
|
let entry = DynamicViewEntry {
|
||||||
|
world_index: world_rep as _,
|
||||||
|
view: view.into_iter(),
|
||||||
|
};
|
||||||
|
let view_index = self.world_views_slab.insert(entry);
|
||||||
|
|
||||||
|
// link the dynamic view to the world that its a part of.
|
||||||
|
let world_entry = self
|
||||||
|
.world_slab
|
||||||
|
.get_mut(world.rep() as _)
|
||||||
|
.ok_or(WasmError::InvalidResourceHandle("EcsWorld"))
|
||||||
|
.unwrap();
|
||||||
|
world_entry.views.insert(view_index);
|
||||||
|
|
||||||
|
WasmResource::new_own(view_index as _)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn next(&mut self, view: WasmResource<wasm_ecs::EcsDynamicView>) -> Option<(Entity, Vec<u8>)> {
|
||||||
|
let view_entry = self.world_views_slab.get_mut(view.rep() as _).unwrap();
|
||||||
|
let world_entry = self.world_slab.get(view_entry.world_index).unwrap();
|
||||||
|
let view = &mut view_entry.view;
|
||||||
|
|
||||||
|
// get the next row in the view and copy the returned components into a tightly packed
|
||||||
|
if let Some((en, row)) = view.next(&world_entry.world) {
|
||||||
|
let row_len = row.iter().map(|d| d.info.layout().size()).sum();
|
||||||
|
let mut row_buf = Vec::<u8>::with_capacity(row_len);
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
for comp in row {
|
||||||
|
let comp_size = comp.info.layout().size();
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: the vec has the capacity to store this component because it was
|
||||||
|
// created with Vec::with_capacity.
|
||||||
|
let dst = row_buf.as_mut_ptr().add(offset);
|
||||||
|
|
||||||
|
std::ptr::copy_nonoverlapping(
|
||||||
|
comp.ptr.as_ptr(),
|
||||||
|
dst,
|
||||||
|
comp.info.layout().size(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// make sure to tell the vec that it now has the bytes of this component
|
||||||
|
row_buf.set_len(row_buf.len() + comp_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += comp_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((en.into(), row_buf))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn drop(
|
||||||
&mut self,
|
&mut self,
|
||||||
this: wasmtime::component::Resource<wasm_ecs::EcsDynamicView>,
|
this: wasmtime::component::Resource<wasm_ecs::EcsDynamicView>,
|
||||||
) -> wasmtime::Result<()> {
|
) -> wasmtime::Result<()> {
|
||||||
|
@ -303,6 +362,32 @@ impl wasm_ecs::HostEcsDynamicView for Imports {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct GuestTypeLookup(HashMap<u128, lyra_ecs::ComponentInfo>);
|
||||||
|
|
||||||
|
impl GuestTypeLookup {
|
||||||
|
/// Try to find the native type's component info.
|
||||||
|
///
|
||||||
|
/// If the wasm type id does not correspond to a native type's info, the provided `ComponentInfo`
|
||||||
|
/// 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.
|
||||||
|
fn lookup_info(&self, info: lyra_ecs::ComponentInfo) -> lyra_ecs::ComponentInfo {
|
||||||
|
match self.0.get(&info.type_id().as_unknown().unwrap()) {
|
||||||
|
Some(native) => {
|
||||||
|
let native_align = native.layout().align() as usize;
|
||||||
|
let native_size = native.layout().size() as usize;
|
||||||
|
|
||||||
|
let layout = info.layout();
|
||||||
|
assert_eq!(native_align, layout.align(), "Native type alignment is different then scripting type alignment!");
|
||||||
|
assert_eq!(native_size, layout.size(), "Native type size is different then scripting type size!");
|
||||||
|
|
||||||
|
native.clone()
|
||||||
|
},
|
||||||
|
None => info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl wasm_ecs::Host for Imports {}
|
impl wasm_ecs::Host for Imports {}
|
||||||
|
|
||||||
|
@ -340,11 +425,17 @@ 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 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();
|
||||||
|
|
||||||
|
let mut lookup = GuestTypeLookup::default();
|
||||||
|
lookup.0.insert(4124409524, lyra_ecs::ComponentInfo::new::<Vec3>());
|
||||||
|
world.add_resource(lookup);
|
||||||
|
|
||||||
let script_en = world.spawn(());
|
let script_en = world.spawn(());
|
||||||
|
|
||||||
let data = store.data_mut();
|
let data = store.data_mut();
|
||||||
|
@ -359,13 +450,41 @@ async fn main() -> wasmtime::Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Instantiate the component
|
// Instantiate the component
|
||||||
println!("creating example component");
|
println!("RUST: Creating example component");
|
||||||
let instance = linker.instantiate_async(&mut store, &component).await?;
|
let instance = linker.instantiate_async(&mut store, &component).await?;
|
||||||
|
|
||||||
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!("Guest is done");
|
let rep = world_res_b.rep();
|
||||||
|
let w = store.data().world_slab.get(rep as _).unwrap();
|
||||||
|
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(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue