friflo / Friflo.Json.Fliox

C# ECS for high performance DoD. C# ORM for .NET with Messaging and Pub-Sub.
GNU Lesser General Public License v3.0
150 stars 11 forks source link

AreAllMembersBlittable throws TypeLoadException in NativeAOT #51

Open cNoNim opened 2 weeks ago

cNoNim commented 2 weeks ago

If a registered component has an enum, AreAllMembersBlittable throws a TypeLoadException in NativeAOT.

public enum StatusEffect
{
    Spawn,
    Dead,
    Alive
}

public struct Health : IComponent
{
    public int Hp;

    public int MaxHp;

    public StatusEffect Status;
}
System.TypeLoadException: The type 'Benchmark.Tests.Components.StatusEffect' cannot be found in assembly 'Benchmark.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
   at System.Reflection.Runtime.General.TypeResolver.Resolve(Handle, MetadataReader, TypeContext) + 0x29
   at System.Reflection.Runtime.FieldInfos.NativeFormat.NativeFormatRuntimeFieldInfo.get_FieldRuntimeType() + 0x51
   at System.Reflection.Runtime.FieldInfos.RuntimeFieldInfo.get_FieldType() + 0x1e
   at Friflo.Engine.ECS.SchemaType.AreAllMembersBlittable(Type) + 0x77
   at Friflo.Engine.ECS.SchemaType.GetBlittableType(Type) + 0xa3
   at Friflo.Engine.ECS.ComponentType`1..ctor(String, Int32, TypeStore) + 0x41
   at Friflo.Engine.ECS.SchemaUtils.CreateComponentType[T](TypeStore typeStore, Int32 structIndex) + 0x40
   at Friflo.Engine.ECS.NativeAOT.RegisterComponent[T]() + 0x5e
   at Benchmark.Tests.Mix.MixFrifloContext..ctor(Int32) + 0x8d
   at Benchmark.Tests.Mix.Mix.FrifloSetup() + 0x23
   at BenchmarkDotNet.Engines.EngineFactory.CreateReadyToRun(EngineParameters) + 0x7e
   at BenchmarkDotNet.Autogenerated.Runnable_103.Run(IHost, String) + 0x576
   at BenchmarkDotNet.Autogenerated.UniqueProgramName.AfterAssemblyLoadingAttached(String[]) + 0x9b4
friflo commented 2 weeks ago

hi,

It tried to reproduce the exception without success in a small test. One question.

Did you register the Health component with

aot.RegisterComponent<Health>();

as mentioned at Examples - General > Native AOT?

edit: Ah, you did. NativeAOT.RegisterComponent[T] is part of the stack trace.

cNoNim commented 2 weeks ago

Yes. I fixed the component and the error is gone. But it's a crutch.

public struct Health : IComponent, IComponentData
{
    public int Hp;

    public int MaxHp;

    private int _status;

    public StatusEffect Status
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => (StatusEffect)_status;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        set => _status = (int)value;
    }
}
friflo commented 2 weeks ago

I assume you are using .NET 8? I am using: \Program Files\dotnet\sdk\8.0.202

friflo commented 2 weeks ago

According to this post: https://stackoverflow.com/questions/77776702/net8-nativeaot-type-getproperties-doesnt-work-for-some-types-but-does-for-ot

Can you try adding the attribute [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] to the struct.

I assume StatusEffect Status is not used anywhere in your assembly. right?

cNoNim commented 2 weeks ago

Used, of course.

And DynamicallyAccessedMembers didn't help.

I can try to make a reproducible project, but it will take time.

friflo commented 2 weeks ago

I can try to make a reproducible project, but it will take time.

That would be very helpful!

The workaround using a property - like public StatusEffect Status { get; set; } is a crutch.

cNoNim commented 1 week ago

I've tried to find the problem, but it's not in the framework. BenchmarkDotNet doesn't compile the NativeAOT build correctly if you run benchmarks selectively. The problem is definitely a floating issue.

cNoNim commented 1 week ago

Another question came up. Should we create an open issue?

I'm not comfortable adding the IComponent/ITag interface to all components and tags in my tests.

I tried to bypass the system as follows:

public struct Comp<T>(T v) : IComponent
{
    public T V = v;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static implicit operator Comp<T>(in T value) =>
        new() { V = value };
}

public struct Tag<T> : ITag
{
    public T Value;
}

But it wasn't good.

Test method Benchmark.Mix.Tests.HashTests.Test threw exception: 
System.TypeInitializationException: The type initializer for 'Friflo.Engine.ECS.StructInfo`1' threw an exception. ---> System.Collections.Generic.KeyNotFoundException: The given key 'Benchmark.Core.Friflo.Comp`1[Benchmark.Core.Components.Velocity]' was not present in the dictionary.
    at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Friflo.Engine.ECS.SchemaTypeUtils.GetStructIndex(Type type)
   at Friflo.Engine.ECS.StructInfo`1..cctor()
--- End of inner exception stack trace ---
    at Friflo.Engine.ECS.ComponentTypes.Get[T1,T2,T3,T4]()
   at Friflo.Engine.ECS.Systems.QuerySystem`4..ctor()
   at Benchmark.Mix.MixContextFriflo.UpdateVelocitySystem..ctor()
   at Benchmark.Mix.MixContextFriflo.DoSetup() in E:\Projects\ECS.Net\Benchmark.Mix\MixContextFriflo.cs:line 77
   at Benchmark.Mix.MixContextBase.Setup(Int32 entityCount, Int32 ticks) in E:\Projects\ECS.Net\Benchmark.Mix\MixContextBase.cs:line 165
   at Benchmark.Mix.Tests.HashTests.TestContext(IMixContext context, Nullable`1& hash, Int32 entityCount, Int32 ticks) in E:\Projects\ECS.Net\Benchmark.Mix.Tests\HashTests.cs:line 62
   at Benchmark.Mix.Tests.HashTests.TestContexts(IMixContext[] contexts, Int32 entityCount, Int32 ticks) in E:\Projects\ECS.Net\Benchmark.Mix.Tests\HashTests.cs:line 43
   at Benchmark.Mix.Tests.HashTests.Test(Int32 entityCount, Int32 ticks) in E:\Projects\ECS.Net\Benchmark.Mix.Tests\HashTests.cs:line 22
   at InvokeStub_HashTests.Test(Object, Span`1)
   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)

The problem doesn't involve NativeAot. Friflo.Engine.ECS.AssemblyLoader.GetComponentTypes doesn't bypass generic types. It can't be done automatically, but maybe I can add types manually?

friflo commented 1 week ago

Another question came up. Should we create an open issue?

Thx for reporting this. I created an new issue: https://github.com/friflo/Friflo.Json.Fliox/issues/53