Skip to content

Commit

Permalink
feat(QueryAttribute): now uses source generation instead of reflection
Browse files Browse the repository at this point in the history
BREAKING CHANGE: QueryAttribute uses source generation now. Classes that use it must be partial.
  • Loading branch information
jonisavo committed Oct 16, 2022
1 parent a8d5e01 commit a43e019
Show file tree
Hide file tree
Showing 22 changed files with 809 additions and 134 deletions.
10 changes: 5 additions & 5 deletions Assets/Samples/Query/QueryExampleComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ namespace UIComponents.Samples.Query
{
[Layout("QueryExampleComponent")]
[Stylesheet("QueryExampleComponent.styles")]
public class QueryExampleComponent : UIComponent
public partial class QueryExampleComponent : UIComponent
{
[Query("my-label")]
private readonly Label MyLabel;
private Label MyLabel;

[Query(Name = "my-foldout")]
private readonly Foldout MyFoldout;
private Foldout MyFoldout;

[Query(Class = "description")]
private readonly Label[] DescriptionLabels;
private Label[] DescriptionLabels;

[Query]
private readonly VisualElement[] Everything;
private VisualElement[] Everything;

public override void OnInit()
{
Expand Down
24 changes: 10 additions & 14 deletions Assets/UIComponents.Benchmarks/UIComponentFieldCacheBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace UIComponents.Benchmarks
{
public class UIComponentFieldCacheBenchmarks
public partial class UIComponentFieldCacheBenchmarks
{
private class EmptyComponent : UIComponent {}

Expand All @@ -27,34 +27,30 @@ public void InitializeEmptyComponent()
MeasureFieldCache<EmptyComponent>();
}

private class ComponentWithFields : UIComponent
private partial class ComponentWithFields : UIComponent
{
[Query("hello-world-label")]
public readonly Label HelloWorldLabel;
public Label HelloWorldLabel;

[Query(Name = "test-foldout")]
public readonly Foldout TestFoldout;
public Foldout TestFoldout;

[Query]
public readonly Label FirstLabel;
public Label FirstLabel;

[Query(Class = "text")]
public readonly Label[] LabelsWithTextClass;
public Label[] LabelsWithTextClass;

[Query(Name = "hello-world-label", Class = "text")]
public readonly Label HelloWorldLabelWithTextClass;
public Label HelloWorldLabelWithTextClass;

[Query]
public readonly List<Label> AllLabelsImplicit;
public List<Label> AllLabels;

[Query(Name = "hello-world-label")]
[Query(Name = "foldout-content")]
public readonly List<Label> AllLabelsExplicit;

[Provide]
public readonly IList StringProperty;
public IList StringProperty;
[Provide]
public readonly IList FloatProperty;
public IList FloatProperty;
}

[Test, Performance, Version(BenchmarkUtils.Version)]
Expand Down
36 changes: 14 additions & 22 deletions Assets/UIComponents.Tests/QueryAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,28 @@
namespace UIComponents.Tests
{
[TestFixture]
public class QueryAttributeTests
public partial class QueryAttributeTests
{
[Layout("UIComponentTests/LayoutAttributeTests")]
private class ComponentWithQueryAttribute : UIComponent
private partial class ComponentWithQueryAttribute : UIComponent
{
[Query("hello-world-label")]
public readonly Label HelloWorldLabel;
public Label HelloWorldLabel;

[Query(Name = "test-foldout")]
public readonly Foldout TestFoldout;
public Foldout TestFoldout;

[Query]
public readonly Label FirstLabel;
public Label FirstLabel;

[Query(Class = "text")]
public readonly Label[] LabelsWithTextClass;
public Label[] LabelsWithTextClass;

[Query(Name = "hello-world-label", Class = "text")]
public readonly Label HelloWorldLabelWithTextClass;
public Label HelloWorldLabelWithTextClass;

[Query]
public readonly List<Label> AllLabelsImplicit;

[Query(Name = "hello-world-label")]
[Query(Name = "foldout-content")]
public readonly List<Label> AllLabelsExplicit;
public List<Label> AllLabels;
}

private TestBed _testBed;
Expand Down Expand Up @@ -63,19 +59,15 @@ public IEnumerator Should_Populate_Fields()

Assert.That(component.HelloWorldLabelWithTextClass, Is.SameAs(component.HelloWorldLabel));

Assert.That(component.AllLabelsImplicit, Is.InstanceOf<List<Label>>());
Assert.That(component.AllLabelsImplicit.Count, Is.EqualTo(2));
Assert.That(component.AllLabelsImplicit[1].text, Is.EqualTo("Foldout content"));

Assert.That(component.AllLabelsExplicit, Is.InstanceOf<List<Label>>());
Assert.That(component.AllLabelsExplicit.Count, Is.EqualTo(2));
Assert.That(component.AllLabelsExplicit[1].text, Is.EqualTo("Foldout content"));
Assert.That(component.AllLabels, Is.InstanceOf<List<Label>>());
Assert.That(component.AllLabels.Count, Is.EqualTo(2));
Assert.That(component.AllLabels[1].text, Is.EqualTo("Foldout content"));
}

private class ChildComponentWithQueryAttribute : ComponentWithQueryAttribute
private partial class ChildComponentWithQueryAttribute : ComponentWithQueryAttribute
{
[Query("foldout-content")]
public readonly Label FoldoutContent;
public Label FoldoutContent;
}

[UnityTest]
Expand All @@ -92,7 +84,7 @@ public IEnumerator Should_Populate_Inherited_Fields()
}

[Layout("UIComponentTests/LayoutAttributeTests")]
private class ComponentWithInvalidQueryAttribute : UIComponent
private partial class ComponentWithInvalidQueryAttribute : UIComponent
{
[Query]
public object InvalidField;
Expand Down
40 changes: 6 additions & 34 deletions Assets/UIComponents.Tests/QueryClassAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
namespace UIComponents.Tests
{
[TestFixture]
public class QueryClassAttributeTests
public partial class QueryClassAttributeTests
{
[Layout("UIComponentTests/QueryClassAttributeTest")]
private class QueryClassTestComponent : UIComponent
private partial class QueryClassTestComponent : UIComponent
{
[Query(Class = "class1")]
public VisualElement[] AllClassOneElementsArray;
Expand All @@ -28,16 +28,6 @@ private class QueryClassTestComponent : UIComponent
[Query(Class = "class1")]
public List<Label> ClassOneLabelList;

[Query(Class = "class1")]
[Query(Class = "class2")]
[Query(Class = "class3")]
public VisualElement[] AllElements;

[Query(Class = "class1")]
[Query(Class = "class2")]
[Query(Class = "class3")]
public Label[] AllLabels;

[Query(Class = "no-such-class")]
public Label[] EmptyLabelArray;

Expand Down Expand Up @@ -97,22 +87,6 @@ public void Populates_Typed_Array_And_List_With_Elements_Of_Type_And_Class()
Assert.That(_queryClassTestComponent.ClassOneLabelList.Count, Is.EqualTo(3));
AssertAllElementsAreOfType(_queryClassTestComponent.ClassOneLabelList);
}


[Test]
public void Populates_Array_With_All_Elements_With_Multiple_Classes()
{
Assert.That(_queryClassTestComponent.AllElements, Is.Not.Null);
Assert.That(_queryClassTestComponent.AllElements.Length, Is.EqualTo(7));
}

[Test]
public void Populates_Typed_Array_With_Multiple_Classes()
{
Assert.That(_queryClassTestComponent.AllLabels, Is.Not.Null);
Assert.That(_queryClassTestComponent.AllLabels.Length, Is.EqualTo(5));
AssertAllElementsAreOfType(_queryClassTestComponent.AllLabels);
}

[Test]
public void Creates_Empty_List_And_Array_If_No_Elements_Are_Found()
Expand All @@ -130,12 +104,10 @@ public void Leaves_Field_Null_If_No_Element_Is_Found()
}

[Layout("UIComponentTests/QueryClassAttributeTest")]
private class UIComponentWithNameAndClassQuery : UIComponent
private partial class UIComponentWithNameAndClassQuery : UIComponent
{
[Query("class1-first")]
[Query(Class = "class2")]
[Query(Class = "class3")]
public readonly Label[] Labels;
[Query("class3-second", Class = "class3")]
public Label[] Labels;
}

[UnityTest]
Expand All @@ -145,7 +117,7 @@ public IEnumerator Works_With_Both_Name_And_Class_Query()
yield return component.WaitForInitializationEnumerator();

Assert.That(component.Labels, Is.Not.Null);
Assert.That(component.Labels.Length, Is.EqualTo(3));
Assert.That(component.Labels.Length, Is.EqualTo(1));
}
}
}
14 changes: 1 addition & 13 deletions Assets/UIComponents/Core/Cache/FieldCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace UIComponents.Cache
/// </summary>
public readonly struct FieldCache
{
public readonly Dictionary<FieldInfo, QueryAttribute[]> QueryAttributes;
public readonly Dictionary<FieldInfo, ProvideAttribute> ProvideAttributes;

private static readonly Type VisualElementType = typeof(VisualElement);
Expand All @@ -21,7 +20,6 @@ public FieldCache(Type type)
var fieldInfos = type.GetFields(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

QueryAttributes = new Dictionary<FieldInfo, QueryAttribute[]>();
ProvideAttributes = new Dictionary<FieldInfo, ProvideAttribute>();

for (var i = 0; i < fieldInfos.Length; i++)
Expand All @@ -31,21 +29,11 @@ public FieldCache(Type type)

var fieldIsVisualElement = VisualElementType.IsAssignableFrom(concreteFieldType);

if (fieldIsVisualElement)
CheckForQueryAttributes(fieldInfo);
else
if (!fieldIsVisualElement)
CheckForProvideAttribute(fieldInfo);
}
}

private void CheckForQueryAttributes(FieldInfo fieldInfo)
{
var queryAttributes = (QueryAttribute[]) fieldInfo.GetCustomAttributes<QueryAttribute>();

if (queryAttributes.Length > 0)
QueryAttributes[fieldInfo] = queryAttributes;
}

private void CheckForProvideAttribute(FieldInfo fieldInfo)
{
var provideAttribute = fieldInfo.GetCustomAttribute<ProvideAttribute>();
Expand Down
8 changes: 5 additions & 3 deletions Assets/UIComponents/Core/QueryAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using System.Diagnostics;
using UnityEngine.TestTools;
using UnityEngine.UIElements;

namespace UIComponents
Expand All @@ -27,8 +28,9 @@ namespace UIComponents
/// public readonly Label[] AllLabels;
/// }
/// </example>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
[Conditional("UICOMPONENTS_INCLUDE_ATTRIBUTES")]
[ExcludeFromCoverage]
public sealed class QueryAttribute : Attribute
{
/// <summary>
Expand Down
45 changes: 2 additions & 43 deletions Assets/UIComponents/Core/UIComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,50 +302,9 @@ private void ApplyEffects()
effectAttributes[i].Apply(this);
}

private void PopulateQueryFields()
{
var fieldCache = CacheDictionary[_componentType].FieldCache;
var queryAttributeDictionary = fieldCache.QueryAttributes;

foreach (var queryAttributeKeyPair in queryAttributeDictionary)
{
var fieldInfo = queryAttributeKeyPair.Key;
var queryAttributes = queryAttributeKeyPair.Value;

var fieldType = fieldInfo.FieldType;
var concreteType = TypeUtils.GetConcreteType(fieldType);

var results = new List<VisualElement>();

for (var i = 0; i < queryAttributes.Length; i++)
{
#if !UNITY_2020_3_OR_NEWER
if (queryAttributes[i].Name == null && queryAttributes[i].Class == null)
{
Unity2019CompatibilityUtils.QueryByDesiredType(queryAttributes[i], this, concreteType, results);
continue;
}
#endif
queryAttributes[i].Query(this, results);
}

results.RemoveAll(result => !concreteType.IsInstanceOfType(result));

object value = null;

if (fieldType.IsArray)
value = CollectionUtils.CreateArrayOfType(concreteType, results);
else if (CollectionUtils.TypeQualifiesAsList(fieldType))
value = CollectionUtils.CreateListOfType(concreteType, results);
else if (results.Count > 0)
value = results[0];

if (value != null)
fieldInfo.SetValue(this, value);
}
}
protected virtual void PopulateQueryFields() {}

private void PopulateProvideFields()
protected virtual void PopulateProvideFields()
{
var fieldCache = CacheDictionary[_componentType].FieldCache;
var provideAttributeDictionary = fieldCache.ProvideAttributes;
Expand Down

0 comments on commit a43e019

Please sign in to comment.