diff --git a/CHANGELOG.md b/CHANGELOG.md index a619f6a..81c85b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All package updates & migration steps will be listed in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.4.0] - 2025-03-11 +### Added +- Support for circular references of structs if using a `IReadOnlyList` +### Changed +- The "Regenerate Code" menu item opens the external code gen project if available to avoid confusion. + ## [4.3.2] - 2025-01-24 - Disable the Unity LTS2020 property drawer recursive property crash workaround. It appears to work in LTS2022. diff --git a/Editor/Common/EditorParameterConstants.cs b/Editor/Common/EditorParameterConstants.cs index 628a7e8..c99ce10 100644 --- a/Editor/Common/EditorParameterConstants.cs +++ b/Editor/Common/EditorParameterConstants.cs @@ -40,6 +40,8 @@ public static class CodeGeneration // external codegen project public static string ExternalProjectDir => Path.Combine(new[] { SanitizedDataPath(), "..", "ParametersCodeGen" }); + public static string ExternalProjectSolutionPath => + Path.Combine(new[] { ExternalProjectDir, "Solutions", "CodeGen.sln" }); // assembly infos public static string AssemblyInfoPath => Path.Combine(new[] { RootDir, AssemblyInfo.FileName }); diff --git a/Editor/Common/Models/ParameterStruct.cs b/Editor/Common/Models/ParameterStruct.cs index 59cf1c7..e429884 100644 --- a/Editor/Common/Models/ParameterStruct.cs +++ b/Editor/Common/Models/ParameterStruct.cs @@ -78,7 +78,9 @@ void DFS(Type t) { var propertyInfo = propertyInfos[j]; - var structType = AttemptToGetStructReference(propertyInfo); + // C# allows circular references in structs if a list is used (not direct field) + const bool includeLists = false; + var structType = AttemptToGetStructReference(propertyInfo, includeLists); if (structType == null) continue; errorString = CheckStructCircularReferences(structType, path); @@ -94,11 +96,11 @@ void DFS(Type t) return errorString; } - private Type AttemptToGetStructReference(PropertyInfo propertyInfo) + private Type AttemptToGetStructReference(PropertyInfo propertyInfo, bool includeLists) { if (ParameterStructReferencePropertyType.IsReferenceType(propertyInfo, out Type genericType)) return genericType; - if (ParameterStructReferenceListPropertyType.IsListReferenceType(propertyInfo, out genericType)) + if (includeLists && ParameterStructReferenceListPropertyType.IsListReferenceType(propertyInfo, out genericType)) return genericType; return null; } diff --git a/Editor/Editor/ParametersWindow.cs b/Editor/Editor/ParametersWindow.cs index bb02389..a6d7e3b 100644 --- a/Editor/Editor/ParametersWindow.cs +++ b/Editor/Editor/ParametersWindow.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using PocketGems.Parameters.CodeGeneration.Operation.Editor; using PocketGems.Parameters.Common.Editor; @@ -8,6 +9,7 @@ using UnityEditor; using UnityEngine; using UnityEngine.TestTools; +using Object = UnityEngine.Object; namespace PocketGems.Parameters.Editor.Editor { @@ -29,7 +31,16 @@ public static void GenerateData() [MenuItem(MenuItemConstants.RegenerateCodePath, false, MenuItemConstants.RegenerateCodePriority)] public static void GenerateCode() { - EditorParameterDataManager.GenerateCodeFiles(GenerateCodeType.Generate, GenerateDataType.All); + bool isUsingExternalCodeGeneration = Directory.Exists(EditorParameterConstants.CodeGeneration.ExternalProjectDir); + if (isUsingExternalCodeGeneration) + { + EditorUtility.DisplayDialog("Code Generation", "Please use the external code generation project that will open in your IDE.", "Okay"); + Application.OpenURL(new Uri(Path.GetFullPath(EditorParameterConstants.CodeGeneration.ExternalProjectSolutionPath)).AbsoluteUri); + } + else + { + EditorParameterDataManager.GenerateCodeFiles(GenerateCodeType.Generate, GenerateDataType.All); + } } [MenuItem(MenuItemConstants.ConfigPanelPath, false, MenuItemConstants.ConfigPanelPriority)] diff --git a/Tests/Editor/Common/Models/ParameterStructTest.cs b/Tests/Editor/Common/Models/ParameterStructTest.cs index 1437330..ed53295 100644 --- a/Tests/Editor/Common/Models/ParameterStructTest.cs +++ b/Tests/Editor/Common/Models/ParameterStructTest.cs @@ -40,29 +40,29 @@ public void InvalidInterface(Type interfaceType) } [Test] - public void ValidWithNoCircularReferences() + [TestCase(typeof(ICircularAStruct))] // references B + [TestCase(typeof(ICircularBStruct))] // references A + [TestCase(typeof(ICircularCStruct))] // subclass of B + [TestCase(typeof(ICircularDStruct))] // references D (self) + + [TestCase(typeof(ICircularEStruct))] // reference F + [TestCase(typeof(ICircularFStruct))] // reference G + [TestCase(typeof(ICircularGStruct))] // reference E + public void InvalidWithCircularReferences(Type interfaceType) { - var parameterStruct = new ParameterStruct(typeof(IReferenceStructStruct)); - Assert.IsTrue(parameterStruct.Validate(out IReadOnlyList errors)); - Assert.IsEmpty(errors); + AssertInvalidInterface(new ParameterStruct(interfaceType)); } [Test] - [TestCase(typeof(ICircularAStruct))] - [TestCase(typeof(ICircularBStruct))] - [TestCase(typeof(ICircularCStruct))] - [TestCase(typeof(ICircularDStruct))] - [TestCase(typeof(ICircularEStruct))] - [TestCase(typeof(ICircularFStruct))] - [TestCase(typeof(ICircularGStruct))] - [TestCase(typeof(ICircularHStruct))] - [TestCase(typeof(ICircularIStruct))] - [TestCase(typeof(ICircularJStruct))] - [TestCase(typeof(ICircularKStruct))] - [TestCase(typeof(ICircularAStruct))] - public void InvalidWithCircularReferences(Type interfaceType) + [TestCase(typeof(IReferenceStructStruct))] + [TestCase(typeof(ICircularIStruct))] // references J with list + [TestCase(typeof(ICircularJStruct))] // references I with list + [TestCase(typeof(ICircularKStruct))] // references K (self) with list + public void ValidListReferences(Type interfaceType) { - AssertInvalidInterface(new ParameterStruct(interfaceType)); + var parameterStruct = new ParameterStruct(interfaceType); + Assert.IsTrue(parameterStruct.Validate(out IReadOnlyList errors)); + Assert.IsEmpty(errors); } } } diff --git a/Tests/Editor/Common/Models/TestCode/TestStructs.cs b/Tests/Editor/Common/Models/TestCode/TestStructs.cs index 9b96f68..3b61af9 100644 --- a/Tests/Editor/Common/Models/TestCode/TestStructs.cs +++ b/Tests/Editor/Common/Models/TestCode/TestStructs.cs @@ -123,55 +123,49 @@ public interface ICircularBStruct : IBaseStruct ParameterStructReference Circular { get; } } -// circular reference in list -public interface ICircularCStruct : IBaseStruct +// circular reference in super class references +public interface ICircularCStruct : ICircularBStruct { - IReadOnlyList> Circular { get; } } -// circular reference in super class references -public interface ICircularDStruct : ICircularBStruct +// circular reference between self (direct reference) +public interface ICircularDStruct : IBaseStruct { + ParameterStructReference Circular { get; } } -// circular reference between list +// deeper circular reference public interface ICircularEStruct : IBaseStruct { - IReadOnlyList> Circular { get; } + ParameterStructReference Circular { get; } } -// circular reference between list +// deeper circular reference public interface ICircularFStruct : IBaseStruct -{ - IReadOnlyList> Circular { get; } -} - -// circular reference between self (direct reference) -public interface ICircularGStruct : IBaseStruct { ParameterStructReference Circular { get; } } -// circular reference between self (list reference) -public interface ICircularHStruct : IBaseStruct +// deeper circular reference +public interface ICircularGStruct : IBaseStruct { - IReadOnlyList> Circular { get; } + ParameterStructReference Circular { get; } } -// deeper circular reference +// circular reference between list public interface ICircularIStruct : IBaseStruct { - ParameterStructReference Circular { get; } + IReadOnlyList> Circular { get; } } -// deeper circular reference +// circular reference between list public interface ICircularJStruct : IBaseStruct { - IReadOnlyList> Circular { get; } + IReadOnlyList> Circular { get; } } -// deeper circular reference +// circular reference between self (list reference) public interface ICircularKStruct : IBaseStruct { - ParameterStructReference Circular { get; } + IReadOnlyList> Circular { get; } } diff --git a/package.json b/package.json index 9d5ae7c..e75d665 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.pocketgems.scriptableobject.flatbuffer", - "version": "4.3.2", + "version": "4.4.0", "displayName": "Scriptable Object - FlatBuffer", "description": "Seamless syncing between Scriptable Objects and CSVs. Scriptable Object data built to Google FlatBuffers for optimal runtime loading & access.", "unity": "2021.3",