jeffcampbellmakesgames / Genesis

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

[BUG] ScriptableFactory code generator can create bad code output #5

Closed jeffcampbellmakesgames closed 4 years ago

jeffcampbellmakesgames commented 4 years ago

Describe the bug The scriptable factory code generator is capable of creating code that will either fail to compile or worse crash unity and prevent it from starting. An example of where this arose recently was where the [FactoryKeyEnum] attribute was used with the passed type being an array. This caused the array [] brackets to appear in the file and class name.

using JCMG.Genesis;

namespace ExampleContent
{
    [Serializable]
    public class Foo { }

    [FactoryKeyEnumFor(typeof(Foo[]))]
    public enum ExampleCreatureType
    {
        Goblin,
        Orc,
        Knight
    }
}

Example Bad Output

The name of this file is ExampleCreatureTypeToFoo[].cs, which will cause Unity to hang.

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

using System;
using System.Collections.Generic;
using UnityEngine;

namespace Genesis
{
    // Bad asset names
    [CreateAssetMenu(fileName = "DefaultExampleCreatureTypeToFoo[]", menuName = "Genesis/Factory/ExampleCreatureTypeToFoo[]")]
    public sealed partial class ExampleCreatureTypeToFoo[] : ScriptableObject // This class name is invalid and won't compile
    {
        [Serializable]
        private class Mapping
        {
            #pragma warning disable 0649
            public ExampleContent.ExampleCreatureType key;

            public Foo[] value;
            #pragma warning restore 0649
        }

        #pragma warning disable 0649
        [SerializeField]
        private List<Mapping> _mappings;
        #pragma warning restore 0649

        private Dictionary<ExampleContent.ExampleCreatureType, Mapping> MappingLookup
        {
            get
            {
                if(_mappingLookup == null)
                {
                    _mappingLookup = new Dictionary<ExampleContent.ExampleCreatureType, Mapping>();
                    for (var i = 0; i < _mappings.Count; i++)
                    {
                        if(_mappingLookup.ContainsKey(_mappings[i].key))
                        {
                            continue;
                        }

                        _mappingLookup.Add(_mappings[i].key, _mappings[i]);
                    }
                }

                return _mappingLookup;
            }
        }

        private Dictionary<ExampleContent.ExampleCreatureType, Mapping> _mappingLookup;

        private void OnEnable()
        {
            if(_mappings == null)
            {
                _mappings = new List<Mapping>();

                var values = (ExampleContent.ExampleCreatureType[])Enum.GetValues(typeof(ExampleContent.ExampleCreatureType));
                for (var i = 0; i < values.Length; i++)
                {
                    _mappings.Add(new Mapping
                    {
                        key = values[i]
                    });
                }
            }
        }

        /// <summary>
        /// Returns true if a mapping is found for <see cref="ExampleContent.ExampleCreatureType"/> <paramref name="key"/> to a
        /// <see cref="Foo[]"/>, otherwise false.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool TryGetValue(ExampleContent.ExampleCreatureType key, out Foo[] value)
        {
            value = null;

            Mapping mapping;
            if (!MappingLookup.TryGetValue(key, out mapping))
            {
                return false;
            }

            value = mapping.value;

            return true;
        }
    }
}

Unity Version: Unity 2019.3.0f6

To Reproduce Create the example code above where Genesis will discover it and attempt to generate code.

Expected behavior If found that the value type short name contains invalid characters, they should be stripped and/or renamed to reflect a general collection name rather than include these characters.

jeffcampbellmakesgames commented 4 years ago

This has been merged to develop.