Skip to content

Add source generator for UnbindAllBindables #5764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
24 changes: 24 additions & 0 deletions osu.Framework.Benchmarks/BenchmarkUnbindAllBindables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using BenchmarkDotNet.Attributes;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;

Expand Down Expand Up @@ -49,6 +50,15 @@ public void TestTypeNestedComposite()
_.UnbindAllBindables();
}

[Test]
[Benchmark]
public void TestComplexComposite()
{
var _ = new ComplexComposite();
_.Load(clock, dependencies);
_.UnbindAllBindables();
}

public partial class SimpleComposite3 : SimpleComposite2
{
}
Expand All @@ -64,5 +74,19 @@ public partial class SimpleComposite1 : SimpleComposite
public partial class SimpleComposite : CompositeDrawable
{
}

public partial class ComplexComposite : CompositeDrawable
{
private readonly Bindable<int> bindable1 = new Bindable<int>();
private readonly Bindable<int> bindable2 = new Bindable<int>();
private readonly Bindable<int> bindable3 = new Bindable<int>();
private readonly Bindable<int> bindable4 = new Bindable<int>();
private readonly Bindable<int> bindable5 = new Bindable<int>();
private readonly Bindable<int> bindable6 = new Bindable<int>();
private readonly Bindable<int> bindable7 = new Bindable<int>();
private readonly Bindable<int> bindable8 = new Bindable<int>();
private readonly Bindable<int> bindable9 = new Bindable<int>();
private readonly Bindable<int> bindable10 = new Bindable<int>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// <auto-generated/>
#nullable enable
#pragma warning disable CS4014

partial class AutoProperty : global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables
{
global::System.Type global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables.KnownType => typeof(global::AutoProperty);
public override void InternalUnbindAllBindables()
{
base.InternalUnbindAllBindables();
global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::AutoProperty), ((global::AutoProperty)this).Bindable1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public partial class AutoProperty : osu.Framework.Graphics.Drawable
{
private osu.Framework.Bindables.Bindable<int> Bindable1 { get; set; } = new osu.Framework.Bindables.Bindable<int>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// <auto-generated/>
#nullable enable
#pragma warning disable CS4014

partial class BaseType : global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables
{
global::System.Type global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables.KnownType => typeof(global::BaseType);
public override void InternalUnbindAllBindables()
{
base.InternalUnbindAllBindables();
global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::BaseType), ((global::BaseType)this).Bindable1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// <auto-generated/>
#nullable enable
#pragma warning disable CS4014

partial class DerivedType : global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables
{
global::System.Type global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables.KnownType => typeof(global::DerivedType);
public override void InternalUnbindAllBindables()
{
base.InternalUnbindAllBindables();
global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::DerivedType), ((global::DerivedType)this).Bindable2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public partial class BaseType : osu.Framework.Graphics.Drawable
{
private readonly osu.Framework.Bindables.Bindable<int> Bindable1 = new osu.Framework.Bindables.Bindable<int>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public partial class DerivedType : BaseType
{
private readonly osu.Framework.Bindables.Bindable<int> Bindable2 = new osu.Framework.Bindables.Bindable<int>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// <auto-generated/>
#nullable enable
#pragma warning disable CS4014

namespace osu.Framework.Graphics
{
partial class Drawable : global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables
{
global::System.Type global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables.KnownType => typeof(global::osu.Framework.Graphics.Drawable);
public virtual void InternalUnbindAllBindables()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace osu.Framework.Bindables
{
public interface IUnbindable
{
void UnbindAll();
}

public interface IBindable : IUnbindable
{
}

public interface IBindable<T> : IBindable
{
}

public class Bindable<T> : IBindable<T>
{
public void UnbindAll()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace osu.Framework.Graphics
{
public partial class Drawable
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace osu.Framework.Graphics
{
public interface ISourceGeneratedUnbindAllBindables
{
System.Type KnownType { get; }

void InternalUnbindAllBindables();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#nullable enable

namespace osu.Framework.Utils
{
public static class SourceGeneratorUtils
{
public static void UnbindBindable(System.Type containingType, osu.Framework.Bindables.IUnbindable? bindable)
{
}
}
}

#nullable disable
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// <auto-generated/>
#nullable enable
#pragma warning disable CS4014

partial class ExplicitImplementation : global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables
{
global::System.Type global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables.KnownType => typeof(global::ExplicitImplementation);
public override void InternalUnbindAllBindables()
{
base.InternalUnbindAllBindables();
global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::ExplicitImplementation), ((ITestInterface)this).Bindable1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public partial class ExplicitImplementation : osu.Framework.Graphics.Drawable, ITestInterface
{
osu.Framework.Bindables.Bindable<int> ITestInterface.Bindable1 { get; } = new osu.Framework.Bindables.Bindable<int>();
}

public interface ITestInterface
{
osu.Framework.Bindables.Bindable<int> Bindable1 { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// <auto-generated/>
#nullable enable
#pragma warning disable CS4014

partial class Nullable : global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables
{
global::System.Type global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables.KnownType => typeof(global::Nullable);
public override void InternalUnbindAllBindables()
{
base.InternalUnbindAllBindables();
global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::Nullable), ((global::Nullable)this).Bindable1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public partial class Nullable : osu.Framework.Graphics.Drawable
{
public osu.Framework.Bindables.Bindable<int>? Bindable1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// <auto-generated/>
#nullable enable
#pragma warning disable CS4014

partial class Static : global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables
{
global::System.Type global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables.KnownType => typeof(global::Static);
public override void InternalUnbindAllBindables()
{
base.InternalUnbindAllBindables();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public partial class Static : osu.Framework.Graphics.Drawable
{
public static readonly osu.Framework.Bindables.Bindable<int> Bindable1 = new osu.Framework.Bindables.Bindable<int>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Threading.Tasks;
using Xunit;
using VerifyCS = osu.Framework.SourceGeneration.Tests.Verifiers.CSharpSourceGeneratorVerifier<osu.Framework.SourceGeneration.Generators.UnbindAllBindables.UnbindAllBindablesSourceGenerator>;

namespace osu.Framework.SourceGeneration.Tests.UnbindAllBindables
{
public class UnbindAllBindablesSourceGeneratorTests : AbstractGeneratorTests
{
protected override string ResourceNamespace => "UnbindAllBindables";

[Theory]
[InlineData("Basic")]
[InlineData("AutoProperty")]
[InlineData("ExplicitImplementation")]
[InlineData("Static")]
[InlineData("Nullable")]
public Task Check(string name)
{
GetTestSources(name,
out (string filename, string content)[] commonSourceFiles,
out (string filename, string content)[] sourceFiles,
out (string filename, string content)[] commonGeneratedFiles,
out (string filename, string content)[] generatedFiles
);

return VerifyCS.VerifyAsync(commonSourceFiles, sourceFiles, commonGeneratedFiles, generatedFiles);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

namespace osu.Framework.SourceGeneration.Generators.UnbindAllBindables
{
public readonly struct BindableDefinition
{
public readonly string Name;
public readonly string FullyQualifiedContainingType;
public readonly bool IsNullable;

public BindableDefinition(string name, string fullyQualifiedContainingType, bool isNullable)
{
Name = name;
FullyQualifiedContainingType = fullyQualifiedContainingType;
IsNullable = isNullable;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace osu.Framework.SourceGeneration.Generators.UnbindAllBindables
{
public class UnbindAllBindablesSemanticTarget : IncrementalSemanticTarget
{
public readonly List<BindableDefinition> Bindables = new List<BindableDefinition>();

public UnbindAllBindablesSemanticTarget(ClassDeclarationSyntax classSyntax, SemanticModel semanticModel)
: base(classSyntax, semanticModel)
{
}

protected override bool CheckValid(INamedTypeSymbol symbol) => isDrawableSubType(symbol);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that the generator is going to emit code for every drawable subtype, even if it doesn't introduce any new bindables to unbind? Do we care, and if we do, how much of a pain is it to have it only emit when necessary?


protected override bool CheckNeedsOverride(INamedTypeSymbol symbol) => !isDrawableType(symbol);

protected override void Process(INamedTypeSymbol symbol)
{
foreach (IFieldSymbol bindableField in enumerateBindables(symbol))
{
bool isNullable = bindableField.NullableAnnotation == NullableAnnotation.Annotated;

if (bindableField.IsImplicitlyDeclared)
{
// Compiler generated names are of the form "<Name>k__BackingField".
string name = bindableField.Name.Substring(1, bindableField.Name.IndexOf('>') - 1);
string containingType = GlobalPrefixedTypeName;

// In the case of explicit auto-property implementations, the name will contain the containing type (interface name).
int containingTypeIndex = name.LastIndexOf('.');

if (containingTypeIndex != -1)
{
containingType = name.Substring(0, containingTypeIndex);
name = name.Substring(containingTypeIndex + 1);
}

Bindables.Add(new BindableDefinition(name, containingType, isNullable));
}
else
Bindables.Add(new BindableDefinition(bindableField.Name, GlobalPrefixedTypeName, isNullable));
}
}

private IEnumerable<IFieldSymbol> enumerateBindables(INamedTypeSymbol symbol)
{
return symbol.GetMembers()
.OfType<IFieldSymbol>()
.Where(m => !m.IsStatic)
.Where(m => m.Type.AllInterfaces.Any(SyntaxHelpers.IsIUnbindableInterface));
}

private bool isDrawableSubType(INamedTypeSymbol type)
{
INamedTypeSymbol? s = type;

while (s != null)
{
if (isDrawableType(s))
return true;

s = s.BaseType;
}

return false;
}

private bool isDrawableType(INamedTypeSymbol type)
=> type.Name == "Drawable" && SyntaxHelpers.GetFullyQualifiedTypeName(type) == "osu.Framework.Graphics.Drawable";
Comment on lines +75 to +76
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeat of #5761 (comment). Should probably be moved out to SyntaxHelpers at this stage.

}
}
Loading