Genesis icon indicating copy to clipboard operation
Genesis copied to clipboard

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

Open laicasaane opened this issue 4 years ago • 3 comments
trafficstars

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/[email protected]/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;
    }
}
  1. 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;
             }
        }
    }
}
  1. After step 3, whenever I clicked on a GenesisSettings asset, it would throw ReflectionTypeLoadException.
  2. 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

laicasaane avatar Oct 11 '21 08:10 laicasaane

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 avatar Oct 13 '21 14:10 jeffcampbellmakesgames

@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.

jeffcampbellmakesgames avatar Apr 18 '22 10:04 jeffcampbellmakesgames

@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;
    }
}
}

laicasaane avatar Apr 19 '22 04:04 laicasaane