Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unity support #100

Open
gaozhou opened this issue May 5, 2023 · 16 comments
Open

Unity support #100

gaozhou opened this issue May 5, 2023 · 16 comments
Labels
question Further information is requested

Comments

@gaozhou
Copy link

gaozhou commented May 5, 2023

Can you provide a unity example?

@genaray genaray added the enhancement New feature or request label May 5, 2023
@genaray
Copy link
Owner

genaray commented May 5, 2023

I will do that once i have some time :) Pretty busy rn with my thesis.
Just out of curiosity, have you tried integrating it via a nuget unity plugin?

Arch sources can not be used, nugets however should be usable even if they make use of higher language features.

@mikhail-dcl
Copy link
Contributor

In Decentraland we are already doing a PoC using Arch as an ECS framework. it's totally possible to use Arch DLLs compiled for netstandard2.1. There is one limitation though, Marshal.SizeOf in the static ctor of ComponentType does not work on IL2CPP for non-blittable (e.g. with reference type fields) and generic structures. @genaray It would be great if you could give advice if it's possible to replace Marshal.SizeOf with something else.

@genaray
Copy link
Owner

genaray commented May 9, 2023

First of all, it's probably the coolest thing I've heard in a long time and makes myself kind of proud too :) Also saw that you created a SystemGroups framework, I'll link it on the github page, maybe others are interested in it too ^^

To the second... there we, unfortunately, come across a limitation of C#. As an alternative there would be Unsafe.SizeOf, but this does not provide a type overload and is therefore only limited usable.

The easiest option here is to never use managed structs. Or... and here comes your solution... register them yourself beforehand. ComponentRegistry has methods to pre-register components and so this hurdle is avoided :)

public struct ManagedStruct{ public List<int> SomeInts{ get; set; } }

// Register
var componentType = new ComponentType(ComponentRegistry.Size - 1, typeof(ManagedStruct), 8, false); // 8 = Size in bytes of the managed struct.
ComponentRegistry.Add(componentType);

// Use
var entity = world.Create(new ManagedStruct());

@mikhail-dcl
Copy link
Contributor

// Register
var componentType = new ComponentType(ComponentRegistry.Size - 1, typeof(ManagedStruct), 8, false); // 8 = Size in bytes of the managed struct.
ComponentRegistry.Add(componentType);

Great, it may work out, I will give it a try. Thank you

Also saw that you created a SystemGroups framework, I'll link it on the github page, maybe others are interested in it too

Sure, feel free

@genaray
Copy link
Owner

genaray commented May 9, 2023

// Register
var componentType = new ComponentType(ComponentRegistry.Size - 1, typeof(ManagedStruct), 8, false); // 8 = Size in bytes of the managed struct.
ComponentRegistry.Add(componentType);

Great, it may work out, I will give it a try. Thank you

Also saw that you created a SystemGroups framework, I'll link it on the github page, maybe others are interested in it too

Sure, feel free

Perfect! Lemme know if it works for your case :)

@mikhail-dcl
Copy link
Contributor

I can confirm that the proposed solution works nicely with Unity IL2CPP with both a manually calculated size
and Unsafe.SizeOf

var componentType = new ComponentType(ComponentRegistry.Size - 1, typeof(ManagedStruct), 8, false); // 8 = Size in bytes of the managed struct.
ComponentRegistry.Add(componentType);

var componentType = new ComponentType(ComponentRegistry.Size - 1, typeof(ManagedStruct), Unsafe.SizeOf<ManagedStruct>(), false); 
ComponentRegistry.Add(componentType);

@genaray
Copy link
Owner

genaray commented May 17, 2023

I can confirm that the proposed solution works nicely with Unity IL2CPP with both a manually calculated size and Unsafe.SizeOf

var componentType = new ComponentType(ComponentRegistry.Size - 1, typeof(ManagedStruct), 8, false); // 8 = Size in bytes of the managed struct.
ComponentRegistry.Add(componentType);

var componentType = new ComponentType(ComponentRegistry.Size - 1, typeof(ManagedStruct), Unsafe.SizeOf<ManagedStruct>(), false); 
ComponentRegistry.Add(componentType);

Great to see that it works! :) 33013b9 recently added automatic size detection for managed structs. That makes use of reflection during the component registration. Should also work on unity and will reduce the boilerplate a bit in the future ^^

@mikhail-dcl
Copy link
Contributor

That's actually great so there will be no boilerplate.

IL2CPP though will not generate the generic version of Unsafe for structures if it was not mentioned explicitly anywhere in conjunction with Unsafe so

private static int SizeOf(Type type)
    {
        if (type.IsValueType)
        {
            return (int) typeof(Unsafe)
                .GetMethod(nameof(Unsafe.SizeOf))!
                .MakeGenericMethod(type)
                .Invoke(null, null)!;
        }

        return IntPtr.Size;
    }

may not work in Unity. It should not be critical as all usual paths I saw so far were via an explicitly mentioned generic Component

@genaray
Copy link
Owner

genaray commented May 19, 2023

That's actually great so there will be no boilerplate.

IL2CPP though will not generate the generic version of Unsafe for structures if it was not mentioned explicitly anywhere in conjunction with Unsafe so

private static int SizeOf(Type type)
    {
        if (type.IsValueType)
        {
            return (int) typeof(Unsafe)
                .GetMethod(nameof(Unsafe.SizeOf))!
                .MakeGenericMethod(type)
                .Invoke(null, null)!;
        }

        return IntPtr.Size;
    }

may not work in Unity. It should not be critical as all usual paths I saw so far were via an explicitly mentioned generic Component

I assume that's because Unitys IL2CPP strips out most reflection code, especially if not mentioned before... correct?
Well that's actually a pitty, but quite understandable. Unity really has a advantage here, since their implement their own "Runtime" they have access to all type data needed without reflection.

For the future i could look into unity specific way. If i saw that correctly, unity has their own SizeOf API.

@mikhail-dcl
Copy link
Contributor

mikhail-dcl commented May 20, 2023

You can read more about IL2CPP Code stripping here. Essentially, the most relevant part is the "Annotate roots using a Link XML file" section with generics examples. This is how the limitation can be circumvented.

When it comes to generics with value types it's not like they are stripped out, it's rather the AOT compiler does not generate them at all. For every value type, the size needed to allocate the type itself and the values on the stack (for methods invocations for instance) is different. Unlike the ref type when it can be always treated as an object and then casted to the required type (this is exactly how IL2CPP produces c++ code).

For example, Unsafe.SizeOf<T> returns a compile-time constant:

[NonVersionable]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int SizeOf<T>() => sizeof (T);

It's a perfect example of the necessity for the AOT compiler to know about the generic type ahead. If the code is not generated (and without JIT it can't be generated at runtime) the runtime has no idea about the size of the type

@mikhail-dcl
Copy link
Contributor

When I have time I will create a Unity preprocessor to generate a link.xml with every Arch-related component. I don't know how I will find them through reflection at the moment though 😄 , so if you have any ideas you are very welcome to share 😉

@genaray
Copy link
Owner

genaray commented May 21, 2023

You can read more about IL2CPP Code stripping here. Essentially, the most relevant part is the "Annotate roots using a Link XML file" section with generics examples. This is how the limitation can be circumvented.

When it comes to generics with value types it's not like they are stripped out, it's rather the AOT compiler does not generate them at all. For every value type, the size needed to allocate the type itself and the values on the stack (for methods invocations for instance) is different. Unlike the ref type when it can be always treated as an object and then casted to the required type (this is exactly how IL2CPP produces c++ code).

For example, Unsafe.SizeOf<T> returns a compile-time constant:

[NonVersionable]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int SizeOf<T>() => sizeof (T);

It's a perfect example of the necessity for the AOT compiler to know about the generic type ahead. If the code is not generated (and without JIT it can't be generated at runtime) the runtime has no idea about the size of the type

Ah i see. However a link.xml or the [Preserve] attribute. Have you ever tried the [Preserve] attribute?
If understood correctly it could also be used any might be a bit easier compared to the link.xml.

In the example with the Unsafe API, we could theoretically create a Unsafe.SizeOf wrapper, add that attribute and call it. Wouldnt that work?

Might be easier ^^ But haven't tried it yet.

@mikhail-dcl
Copy link
Contributor

mikhail-dcl commented May 22, 2023

[Preserve] will not work with generics as it still does not hint the AOT compiler with the type arguments. It will not instruct it to generate the annotated generic for every possible value type

@Sorrowful-free
Copy link

@mikhail-dcl i've copied minimal struct of your project ( plugins folder with arch, and meta files, and copy dll of source generators and their meta files ) but faced exception (maybe problem with source generator)

using System.Runtime.CompilerServices;
using Arch.Core;
using Arch.System;

public partial class TestTagSystem : BaseSystem<World, float>
{
public TestTagSystem(World world) : base(world) { }

[Query]
[All(typeof(TestTag))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void TestUpdate(in Entity entity, ref TestTag testTag)
{
    testTag.IntValue += 3;
}

}

Arch.System.SourceGenerator\Arch.System.SourceGenerator.QueryGenerator\TestTagSystem.g.cs(10,34): error CS0115: 'TestTagSystem.Update(in float)': no suitable method found to override,

maybe you faced something like that?

@Sorrowful-free
Copy link

sorry guys i found the answer
if my system class doesn't have namespace it will be appear

@genaray genaray added question Further information is requested and removed enhancement New feature or request labels Aug 23, 2023
@AnnulusGames
Copy link

I have developed Arch.Unity as a library to integrate Arch into Unity. This includes support for Conversion workflows (GameObject to Entity), fast queries integrated with the C# Job System, Hierarchy Windows and Inspectors that can track Entity, and a unique layer to integrate Arch.System with PlayerLoop.

I hope this helps Arch support Unity. (Sorry if this is not what I should be posting here...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
Status: No status
Development

No branches or pull requests

5 participants