jeffcampbellmakesgames / Genesis

A general purpose code generator library for Unity
MIT License
74 stars 8 forks source link

[BUG] Add try-catch to ReflectionTools to suppress ReflectionTypeLoadException #21

Open laicasaane opened 2 years ago

laicasaane commented 2 years ago

Describe the bug Encounter this error when click on a GenesisSettings asset

ReflectionTypeLoadException: Exception of type 'System.Reflection.ReflectionTypeLoadException' was thrown.
System.Reflection.Assembly.GetTypes () (at <695d1cc93cca45069c528c15c9fdd749>:0)
Genesis.Shared.ReflectionTools.GetAllImplementingInstancesOfInterface[T] () (at /home/runner/work/Genesis/Genesis/Genesis/ExternalApp/Genesis.Shared/Tools/ReflectionTools.cs:319)
JCMG.Genesis.Editor.Inspectors.GenesisSettingsInspector..cctor () (at Library/PackageCache/com.jeffcampbellmakesgames.genesis@2.3.2/Scripts/Editor/Inspectors/GenesisSettingsInspector.cs:43)
Rethrow as TypeInitializationException: The type initializer for 'JCMG.Genesis.Editor.Inspectors.GenesisSettingsInspector' threw an exception.
UnityEditor.UIElements.InspectorElement+<>c__DisplayClass59_0.<CreateIMGUIInspectorFromEditor>b__0 () (at <1ada6c7052bb42378c5ec1bd01fc4723>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)

Unity Version: Unity 2020.3.20f1 Genesis is installed via OpenUPM

To Reproduce This is currently a private project so I cannot share steps to reproduce, but I can describe a bit:

  1. I created a plugin to generate some "union" structs defined by this attribute [Union(typeof((<...tuple syntax...>)))]
  2. The user codes in Unity are placed inside a folder with an asmdef (Game.Runtime.asmdef) For example:

    namespace Game.Runtime
    {
    public partial class TestBehaviour : MonoBehaviour
    {
        public partial class Container
        {
            [Union(typeof((Vector2, MySpecialStruct)))]
            public readonly partial struct ExampleUnion
            {
            }
        }
    }
    
    public struct MySpecialStruct
    {
        public event Action OnClick;
    
        public float X { get; }
    
        public int Y;
    }
    }
  3. Then press "Generate" button on the GenesisSettings and we will have this code:

    namespace Game.Runtime
    {
    partial class TestBehaviour {
        partial class Container {
    
            [StructLayout(LayoutKind.Explicit, Pack = 1)]
            partial struct ExampleUnion
            {
                [FieldOffset(0)]
                private readonly UnityEngine.Vector2 _Item1;
    
                [FieldOffset(0)]
                private readonly Game.Runtime.MySpecialStruct _Item2;
             }
        }
    }
    }
  4. After step 3, whenever I clicked on a GenesisSettings asset, it would throw ReflectionTypeLoadException.
  5. If I replace MySpecialStruct in the Union attribute with float or Vector3, no exception will occur.

Expected behavior ReflectionTypeLoadException should be suppress in ReflectionTools.cs

jeffcampbellmakesgames commented 2 years ago

Hi @laicasaane , I just wanted to give you a heads up that I did see this and will be following up this weekend to go through this and the associated fix.

jeffcampbellmakesgames commented 2 years ago

@laicasaane What is the definition of the Union attribute in this case? Attempting to reproduce this locally has been a bit difficult without knowing what the constructor param typeof((Vector2, MySpecialStruct) resolves to.

laicasaane commented 2 years ago

@jeffcampbellmakesgames This is the usage of Union attribute

Usage:

using UnityEngine;
using ZBase.Common.Unions;

namespace Examples
{
    public partial class ExampleBehaviour : MonoBehaviour
    {
        [Union(typeof((int, float)))]
        public readonly partial struct ExampleUnionA { }
    }
}

Generated code:

//------------------------------------------------------------------------------
// <auto-generated>
//      This code was generated by a tool (Genesis v2.3.2.0).
//
//
//      Changes to this file may cause incorrect behavior and will be lost if
//      the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Runtime.InteropServices;
using ZBase.Common.Unions;

namespace Examples
{
partial class ExampleBehaviour {

    [StructLayout(LayoutKind.Explicit, Pack = 1)]
    partial struct ExampleUnionA : IUnion, IEquatable<ExampleUnionA>
    {
        public enum Fields : byte
        {
            Item1,
            Item2,
        }

        [FieldOffset(0)]
        public readonly Fields Field;

        [FieldOffset(1)]
        public readonly int Item1;

        [FieldOffset(1)]
        public readonly float Item2;

        public ExampleUnionA(int value)
        {
            Field = Fields.Item1;

            Item2 = default;

            Item1 = value;
        }

        public ExampleUnionA(in int value)
        {
            Field = Fields.Item1;

            Item2 = default;

            Item1 = value;
        }

        public ExampleUnionA(float value)
        {
            Field = Fields.Item2;

            Item1 = default;

            Item2 = value;
        }

        public ExampleUnionA(in float value)
        {
            Field = Fields.Item2;

            Item1 = default;

            Item2 = value;
        }

        public ExampleUnionA(Fields type)
        {
            Field = type;

            Item1 = default;
            Item2 = default;
        }

        public bool TryGet(out int value)
        {
            if (Field != Fields.Item1)
            {
                value = default;
                return false;
            }

            value = Item1;
            return true;
        }

        public bool TryGet(out float value)
        {
            if (Field != Fields.Item2)
            {
                value = default;
                return false;
            }

            value = Item2;
            return true;
        }

        public System.Type GetUnderlyingType()
        {
            if (Field == Fields.Item1)
                return Item1.GetType();

            if (Field == Fields.Item2)
                return Item2.GetType();

            return GetType();
        }

        public override int GetHashCode()
        {
            var hash = new HashCode();
            hash.Add(Field);

            if (Field == Fields.Item1)
                hash.Add(Item1);

            if (Field == Fields.Item2)
                hash.Add(Item2);

            return hash.ToHashCode();
        }

        public override bool Equals(object obj)
            => obj is ExampleUnionA other && Equals(this, other);

        public bool Equals(ExampleUnionA other)
            => Equals(this, other);

        public static bool Equals(ExampleUnionA a, ExampleUnionA b)
        {
            if (a.Field != b.Field)
                return false;

            if (a.Field == Fields.Item1)
                return a.Item1 == b.Item1;

            if (a.Field == Fields.Item2)
                return a.Item2 == b.Item2;

            return false;
        }

        public static bool operator ==(ExampleUnionA left, ExampleUnionA right)
            => Equals(left, right);

        public static bool operator !=(ExampleUnionA left, ExampleUnionA right)
            => !Equals(left, right);

        public bool Equals(in ExampleUnionA other)
            => Equals(in this, in other);

        public static bool Equals(in ExampleUnionA a, in ExampleUnionA b)
        {
            if (a.Field != b.Field)
                return false;

            if (a.Field == Fields.Item1)
                return a.Item1 == b.Item1;

            if (a.Field == Fields.Item2)
                return a.Item2 == b.Item2;

            return false;
        }

        public static bool operator ==(in ExampleUnionA left, in ExampleUnionA right)
            => Equals(in left, in right);

        public static bool operator !=(in ExampleUnionA left, in ExampleUnionA right)
            => !Equals(in left, in right);

        public override string ToString()
        {
            if (Field == Fields.Item1)
                return Item1.ToString();

            if (Field == Fields.Item2)
                return Item2.ToString();

            return string.Empty;
        }

        public static implicit operator ExampleUnionA(int value)
            => new ExampleUnionA(value);

        public static implicit operator int(ExampleUnionA value)
            => value.Item1;

        public static implicit operator ExampleUnionA(float value)
            => new ExampleUnionA(value);

        public static implicit operator float(ExampleUnionA value)
            => value.Item2;
    }
}
}