From 884ef84cd85577887553ba1326307c2d734cf2e6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 19:16:56 +0900 Subject: [PATCH 1/9] Add source generator for UnbindAllBindables --- ...k.Graphics.Drawable_UnbindAllBindables.txt | 14 +++ .../CommonSources/Bindable.txt | 22 +++++ .../CommonSources/Drawable.txt | 6 ++ .../ISourceGeneratedUnbindAllBindables.txt | 9 ++ .../g_BaseType_UnbindAllBindables.txt | 13 +++ .../g_DerivedType_UnbindAllBindables.txt | 13 +++ .../Test/Sources/BaseType.txt | 4 + .../Test/Sources/DerivedType.txt | 4 + .../UnbindAllBindablesSourceGeneratorTests.cs | 28 ++++++ .../UnbindAllBindablesSemanticTarget.cs | 56 +++++++++++ .../UnbindAllBindablesSourceEmitter.cs | 96 +++++++++++++++++++ .../UnbindAllBindablesSourceGenerator.cs | 18 ++++ .../SyntaxHelpers.cs | 6 ++ osu.Framework/Graphics/Drawable.cs | 45 +-------- .../Graphics/Drawable_BindableUnbinding.cs | 63 ++++++++++++ .../ISourceGeneratedUnbindAllBindables.cs | 14 +++ 16 files changed, 367 insertions(+), 44 deletions(-) create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonGenerated/g_osu.Framework.Graphics.Drawable_UnbindAllBindables.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/Bindable.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/Drawable.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/ISourceGeneratedUnbindAllBindables.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_BaseType_UnbindAllBindables.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_DerivedType_UnbindAllBindables.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/BaseType.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/DerivedType.txt create mode 100644 osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs create mode 100644 osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs create mode 100644 osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs create mode 100644 osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceGenerator.cs create mode 100644 osu.Framework/Graphics/Drawable_BindableUnbinding.cs create mode 100644 osu.Framework/Graphics/ISourceGeneratedUnbindAllBindables.cs diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonGenerated/g_osu.Framework.Graphics.Drawable_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonGenerated/g_osu.Framework.Graphics.Drawable_UnbindAllBindables.txt new file mode 100644 index 0000000000..6799afdf52 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonGenerated/g_osu.Framework.Graphics.Drawable_UnbindAllBindables.txt @@ -0,0 +1,14 @@ +// +#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() + { + } + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/Bindable.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/Bindable.txt new file mode 100644 index 0000000000..072f55c561 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/Bindable.txt @@ -0,0 +1,22 @@ +namespace osu.Framework.Bindables +{ + public interface IUnbindable + { + void UnbindAll(); + } + + public interface IBindable : IUnbindable + { + } + + public interface IBindable : IBindable + { + } + + public class Bindable : IBindable + { + public void UnbindAll() + { + } + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/Drawable.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/Drawable.txt new file mode 100644 index 0000000000..e5cde97c34 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/Drawable.txt @@ -0,0 +1,6 @@ +namespace osu.Framework.Graphics +{ + public partial class Drawable + { + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/ISourceGeneratedUnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/ISourceGeneratedUnbindAllBindables.txt new file mode 100644 index 0000000000..4ef69c3bed --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/ISourceGeneratedUnbindAllBindables.txt @@ -0,0 +1,9 @@ +namespace osu.Framework.Graphics +{ + public interface ISourceGeneratedUnbindAllBindables + { + System.Type KnownType { get; } + + void InternalUnbindAllBindables(); + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_BaseType_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_BaseType_UnbindAllBindables.txt new file mode 100644 index 0000000000..3e5326c048 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_BaseType_UnbindAllBindables.txt @@ -0,0 +1,13 @@ +// +#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(); + ((osu.Framework.Bindables.IUnbindable)Bindable1).UnbindAll(); + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_DerivedType_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_DerivedType_UnbindAllBindables.txt new file mode 100644 index 0000000000..7c48edc0c4 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_DerivedType_UnbindAllBindables.txt @@ -0,0 +1,13 @@ +// +#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(); + ((osu.Framework.Bindables.IUnbindable)Bindable2).UnbindAll(); + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/BaseType.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/BaseType.txt new file mode 100644 index 0000000000..23e448abb8 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/BaseType.txt @@ -0,0 +1,4 @@ +public partial class BaseType : osu.Framework.Graphics.Drawable +{ + private readonly osu.Framework.Bindables.Bindable Bindable1 = new osu.Framework.Bindables.Bindable(); +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/DerivedType.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/DerivedType.txt new file mode 100644 index 0000000000..a3f2d619b2 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/DerivedType.txt @@ -0,0 +1,4 @@ +public partial class DerivedType : BaseType +{ + private readonly osu.Framework.Bindables.Bindable Bindable2 = new osu.Framework.Bindables.Bindable(); +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs b/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs new file mode 100644 index 0000000000..10a97b9d7e --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . 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; + +namespace osu.Framework.SourceGeneration.Tests.UnbindAllBindables +{ + public class UnbindAllBindablesSourceGeneratorTests : AbstractGeneratorTests + { + protected override string ResourceNamespace => "UnbindAllBindables"; + + [Theory] + [InlineData("Test")] + 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); + } + } +} diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs new file mode 100644 index 0000000000..deb227de63 --- /dev/null +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . 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 BindableFieldNames = new List(); + + public UnbindAllBindablesSemanticTarget(ClassDeclarationSyntax classSyntax, SemanticModel semanticModel) + : base(classSyntax, semanticModel) + { + } + + protected override bool CheckValid(INamedTypeSymbol symbol) + { + if (!isDrawableSubType(symbol)) + return false; + + return isDrawableType(symbol) + || symbol.GetMembers().OfType().Any(m => m.Type.AllInterfaces.Any(SyntaxHelpers.IsIUnbindableInterface)); + } + + protected override bool CheckNeedsOverride(INamedTypeSymbol symbol) => !isDrawableType(symbol); + + protected override void Process(INamedTypeSymbol symbol) + { + BindableFieldNames.AddRange(symbol.GetMembers().OfType() + .Where(m => m.Type.AllInterfaces.Any(SyntaxHelpers.IsIUnbindableInterface)) + .Select(m => m.Name)); + } + + 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"; + } +} diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs new file mode 100644 index 0000000000..c7f182e85b --- /dev/null +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace osu.Framework.SourceGeneration.Generators.UnbindAllBindables +{ + public class UnbindAllBindablesSourceEmitter : IncrementalSourceEmitter + { + private const string unbind_all_bindables_method_name = "InternalUnbindAllBindables"; + private const string interface_name = "global::osu.Framework.Graphics.ISourceGeneratedUnbindAllBindables"; + + protected override string FileSuffix => "UnbindAllBindables"; + + public new UnbindAllBindablesSemanticTarget Target => (UnbindAllBindablesSemanticTarget)base.Target; + + public UnbindAllBindablesSourceEmitter(IncrementalSemanticTarget target) + : base(target) + { + } + + protected override ClassDeclarationSyntax ConstructClass(ClassDeclarationSyntax initialClass) + { + return initialClass.WithBaseList( + SyntaxFactory.BaseList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.SimpleBaseType( + SyntaxFactory.ParseTypeName(interface_name))))) + .WithMembers( + SyntaxFactory.List(new[] + { + emitKnownType(), + emitUnbindAllBindables() + })); + } + + private MemberDeclarationSyntax emitKnownType() + { + return SyntaxFactory.PropertyDeclaration( + SyntaxFactory.ParseTypeName("global::System.Type"), + SyntaxFactory.Identifier("KnownType")) + .WithExplicitInterfaceSpecifier( + SyntaxFactory.ExplicitInterfaceSpecifier( + SyntaxFactory.IdentifierName(interface_name))) + .WithExpressionBody( + SyntaxFactory.ArrowExpressionClause( + SyntaxFactory.TypeOfExpression( + SyntaxFactory.ParseTypeName(Target.GlobalPrefixedTypeName)))) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + } + + private MemberDeclarationSyntax emitUnbindAllBindables() + { + return SyntaxFactory.MethodDeclaration( + SyntaxFactory.PredefinedType( + SyntaxFactory.Token(SyntaxKind.VoidKeyword)), + unbind_all_bindables_method_name) + .WithModifiers( + SyntaxFactory.TokenList( + SyntaxFactory.Token(SyntaxKind.PublicKeyword), + Target.NeedsOverride + ? SyntaxFactory.Token(SyntaxKind.OverrideKeyword) + : SyntaxFactory.Token(SyntaxKind.VirtualKeyword))) + .WithBody( + SyntaxFactory.Block(emitStatements())); + + IEnumerable emitStatements() + { + if (Target.NeedsOverride) + { + yield return SyntaxFactory.ExpressionStatement( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.BaseExpression(), + SyntaxFactory.IdentifierName(unbind_all_bindables_method_name)))); + } + + foreach (string name in Target.BindableFieldNames) + { + yield return SyntaxFactory.ExpressionStatement( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ParenthesizedExpression( + SyntaxFactory.CastExpression( + SyntaxFactory.IdentifierName("osu.Framework.Bindables.IUnbindable"), + SyntaxFactory.IdentifierName(name))), + SyntaxFactory.IdentifierName("UnbindAll")))); + } + } + } + } +} diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceGenerator.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceGenerator.cs new file mode 100644 index 0000000000..65a9e148c7 --- /dev/null +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceGenerator.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace osu.Framework.SourceGeneration.Generators.UnbindAllBindables +{ + [Generator] + public class UnbindAllBindablesSourceGenerator : AbstractIncrementalGenerator + { + protected override IncrementalSemanticTarget CreateSemanticTarget(ClassDeclarationSyntax node, SemanticModel semanticModel) + => new UnbindAllBindablesSemanticTarget(node, semanticModel); + + protected override IncrementalSourceEmitter CreateSourceEmitter(IncrementalSemanticTarget target) + => new UnbindAllBindablesSourceEmitter(target); + } +} diff --git a/osu.Framework.SourceGeneration/SyntaxHelpers.cs b/osu.Framework.SourceGeneration/SyntaxHelpers.cs index 3a3e488164..ddcf4159ef 100644 --- a/osu.Framework.SourceGeneration/SyntaxHelpers.cs +++ b/osu.Framework.SourceGeneration/SyntaxHelpers.cs @@ -112,6 +112,12 @@ public static bool IsCachedAttribute(ITypeSymbol? type) public static bool IsIDependencyInjectionCandidateInterface(ITypeSymbol? type) => type?.Name == "IDependencyInjectionCandidate"; + public static bool IsISourceGeneratedUnbindAllBindablesInterface(ITypeSymbol? type) + => type?.Name == "ISourceGeneratedUnbindAllBindables"; + + public static bool IsIUnbindableInterface(ITypeSymbol? type) + => type?.Name == "IUnbindable"; + public static string GetFullyQualifiedTypeName(INamedTypeSymbol type) => type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); diff --git a/osu.Framework/Graphics/Drawable.cs b/osu.Framework/Graphics/Drawable.cs index 634d574631..7e37ac0227 100644 --- a/osu.Framework/Graphics/Drawable.cs +++ b/osu.Framework/Graphics/Drawable.cs @@ -22,7 +22,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.Linq; using System.Reflection; using System.Threading; using JetBrains.Annotations; @@ -132,45 +131,6 @@ protected virtual void Dispose(bool isDisposing) /// internal virtual void UnbindAllBindablesSubTree() => UnbindAllBindables(); - private Action getUnbindAction() - { - Type ourType = GetType(); - return unbind_action_cache.TryGetValue(ourType, out var action) ? action : cacheUnbindAction(ourType); - - // Extracted to a separate method to prevent .NET from pre-allocating some objects (saves ~150B per call to this method, even if already cached). - static Action cacheUnbindAction(Type ourType) - { - List> actions = new List>(); - - foreach (var type in ourType.EnumerateBaseTypes()) - { - // Generate delegates to unbind fields - actions.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) - .Where(f => typeof(IUnbindable).IsAssignableFrom(f.FieldType)) - .Select(f => new Action(target => ((IUnbindable)f.GetValue(target))?.UnbindAll()))); - } - - // Delegates to unbind properties are intentionally not generated. - // Properties with backing fields (including automatic properties) will be picked up by the field unbind delegate generation, - // while ones without backing fields (like get-only properties that delegate to another drawable's bindable) should not be unbound here. - - return unbind_action_cache[ourType] = target => - { - foreach (var a in actions) - { - try - { - a(target); - } - catch (Exception e) - { - Logger.Error(e, $"Failed to unbind a local bindable in {ourType.ReadableName()}"); - } - } - }; - } - } - private bool unbindComplete; /// @@ -183,7 +143,7 @@ internal virtual void UnbindAllBindables() unbindComplete = true; - getUnbindAction().Invoke(this); + unbindAllBindables(); OnUnbindAllBindables?.Invoke(); } @@ -267,9 +227,6 @@ private void load(IFrameBasedClock clock, IReadOnlyDependencyContainer dependenc { LoadThread = Thread.CurrentThread; - // Cache eagerly during load to hopefully defer the reflection overhead to an async pathway. - getUnbindAction(); - UpdateClock(clock); double timeBefore = DebugUtils.LogPerformanceIssues ? perf_clock.CurrentTime : 0; diff --git a/osu.Framework/Graphics/Drawable_BindableUnbinding.cs b/osu.Framework/Graphics/Drawable_BindableUnbinding.cs new file mode 100644 index 0000000000..b8edbc5ebb --- /dev/null +++ b/osu.Framework/Graphics/Drawable_BindableUnbinding.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Logging; + +namespace osu.Framework.Graphics +{ + public partial class Drawable + { + private void unbindAllBindables() + { + if (this is ISourceGeneratedUnbindAllBindables sg && sg.KnownType == GetType()) + sg.InternalUnbindAllBindables(); + + getUnbindAction().Invoke(this); + } + + private Action getUnbindAction() + { + Type ourType = GetType(); + return unbind_action_cache.TryGetValue(ourType, out var action) ? action : addToCache(ourType); + + // Extracted to a separate method to prevent .NET from pre-allocating some objects (saves ~150B per call to this method, even if already cached). + static Action addToCache(Type ourType) + { + List> actions = new List>(); + + foreach (var type in ourType.EnumerateBaseTypes()) + { + // Generate delegates to unbind fields + actions.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(f => typeof(IUnbindable).IsAssignableFrom(f.FieldType)) + .Select(f => new Action(target => ((IUnbindable?)f.GetValue(target))?.UnbindAll()))); + } + + // Delegates to unbind properties are intentionally not generated. + // Properties with backing fields (including automatic properties) will be picked up by the field unbind delegate generation, + // while ones without backing fields (like get-only properties that delegate to another drawable's bindable) should not be unbound here. + + return unbind_action_cache[ourType] = target => + { + foreach (var a in actions) + { + try + { + a(target); + } + catch (Exception e) + { + Logger.Error(e, $"Failed to unbind a local bindable in {ourType.ReadableName()}"); + } + } + }; + } + } + } +} diff --git a/osu.Framework/Graphics/ISourceGeneratedUnbindAllBindables.cs b/osu.Framework/Graphics/ISourceGeneratedUnbindAllBindables.cs new file mode 100644 index 0000000000..7bb1f8c912 --- /dev/null +++ b/osu.Framework/Graphics/ISourceGeneratedUnbindAllBindables.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Framework.Graphics +{ + public interface ISourceGeneratedUnbindAllBindables + { + protected internal Type KnownType { get; } + + void InternalUnbindAllBindables(); + } +} From 28beadc1abafc22d341c12b35b04d46423482057 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 20:33:55 +0900 Subject: [PATCH 2/9] Support auto properties and explicit implementations --- .../g_AutoProperty_UnbindAllBindables.txt | 13 +++++++++ .../AutoProperty/Sources/AutoProperty.txt | 4 +++ .../g_BaseType_UnbindAllBindables.txt | 2 +- .../g_DerivedType_UnbindAllBindables.txt | 2 +- .../{Test => Basic}/Sources/BaseType.txt | 0 .../{Test => Basic}/Sources/DerivedType.txt | 0 ...licitImplementation_UnbindAllBindables.txt | 13 +++++++++ .../Sources/ExplicitImplementation.txt | 9 +++++++ .../UnbindAllBindablesSourceGeneratorTests.cs | 4 ++- .../UnbindAllBindables/BindableDefinition.cs | 17 ++++++++++++ .../UnbindAllBindablesSemanticTarget.cs | 27 ++++++++++++++++--- .../UnbindAllBindablesSourceEmitter.cs | 12 ++++++--- 12 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Generated/g_AutoProperty_UnbindAllBindables.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Sources/AutoProperty.txt rename osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/{Test => Basic}/Generated/g_BaseType_UnbindAllBindables.txt (79%) rename osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/{Test => Basic}/Generated/g_DerivedType_UnbindAllBindables.txt (79%) rename osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/{Test => Basic}/Sources/BaseType.txt (100%) rename osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/{Test => Basic}/Sources/DerivedType.txt (100%) create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Generated/g_ExplicitImplementation_UnbindAllBindables.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Sources/ExplicitImplementation.txt create mode 100644 osu.Framework.SourceGeneration/Generators/UnbindAllBindables/BindableDefinition.cs diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Generated/g_AutoProperty_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Generated/g_AutoProperty_UnbindAllBindables.txt new file mode 100644 index 0000000000..ba7098a03b --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Generated/g_AutoProperty_UnbindAllBindables.txt @@ -0,0 +1,13 @@ +// +#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.Bindables.IUnbindable)((global::AutoProperty)this).Bindable1).UnbindAll(); + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Sources/AutoProperty.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Sources/AutoProperty.txt new file mode 100644 index 0000000000..9cedc66c44 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Sources/AutoProperty.txt @@ -0,0 +1,4 @@ +public partial class AutoProperty : osu.Framework.Graphics.Drawable +{ + private osu.Framework.Bindables.Bindable Bindable1 { get; set; } = new osu.Framework.Bindables.Bindable(); +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_BaseType_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_BaseType_UnbindAllBindables.txt similarity index 79% rename from osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_BaseType_UnbindAllBindables.txt rename to osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_BaseType_UnbindAllBindables.txt index 3e5326c048..4733e3c471 100644 --- a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_BaseType_UnbindAllBindables.txt +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_BaseType_UnbindAllBindables.txt @@ -8,6 +8,6 @@ partial class BaseType : global::osu.Framework.Graphics.ISourceGeneratedUnbindAl public override void InternalUnbindAllBindables() { base.InternalUnbindAllBindables(); - ((osu.Framework.Bindables.IUnbindable)Bindable1).UnbindAll(); + ((global::osu.Framework.Bindables.IUnbindable)((global::BaseType)this).Bindable1).UnbindAll(); } } \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_DerivedType_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_DerivedType_UnbindAllBindables.txt similarity index 79% rename from osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_DerivedType_UnbindAllBindables.txt rename to osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_DerivedType_UnbindAllBindables.txt index 7c48edc0c4..a94f3a5c5e 100644 --- a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Generated/g_DerivedType_UnbindAllBindables.txt +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_DerivedType_UnbindAllBindables.txt @@ -8,6 +8,6 @@ partial class DerivedType : global::osu.Framework.Graphics.ISourceGeneratedUnbin public override void InternalUnbindAllBindables() { base.InternalUnbindAllBindables(); - ((osu.Framework.Bindables.IUnbindable)Bindable2).UnbindAll(); + ((global::osu.Framework.Bindables.IUnbindable)((global::DerivedType)this).Bindable2).UnbindAll(); } } \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/BaseType.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Sources/BaseType.txt similarity index 100% rename from osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/BaseType.txt rename to osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Sources/BaseType.txt diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/DerivedType.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Sources/DerivedType.txt similarity index 100% rename from osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Test/Sources/DerivedType.txt rename to osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Sources/DerivedType.txt diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Generated/g_ExplicitImplementation_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Generated/g_ExplicitImplementation_UnbindAllBindables.txt new file mode 100644 index 0000000000..23f9e9462d --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Generated/g_ExplicitImplementation_UnbindAllBindables.txt @@ -0,0 +1,13 @@ +// +#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.Bindables.IUnbindable)((ITestInterface)this).Bindable1).UnbindAll(); + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Sources/ExplicitImplementation.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Sources/ExplicitImplementation.txt new file mode 100644 index 0000000000..4845c119ed --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Sources/ExplicitImplementation.txt @@ -0,0 +1,9 @@ +public partial class ExplicitImplementation : osu.Framework.Graphics.Drawable, ITestInterface +{ + osu.Framework.Bindables.Bindable ITestInterface.Bindable1 { get; } = new osu.Framework.Bindables.Bindable(); +} + +public interface ITestInterface +{ + osu.Framework.Bindables.Bindable Bindable1 { get; } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs b/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs index 10a97b9d7e..cbaebf5b41 100644 --- a/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs +++ b/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs @@ -12,7 +12,9 @@ public class UnbindAllBindablesSourceGeneratorTests : AbstractGeneratorTests protected override string ResourceNamespace => "UnbindAllBindables"; [Theory] - [InlineData("Test")] + [InlineData("Basic")] + [InlineData("AutoProperty")] + [InlineData("ExplicitImplementation")] public Task Check(string name) { GetTestSources(name, diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/BindableDefinition.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/BindableDefinition.cs new file mode 100644 index 0000000000..85e1688cf0 --- /dev/null +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/BindableDefinition.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . 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 BindableDefinition(string name, string fullyQualifiedContainingType) + { + Name = name; + FullyQualifiedContainingType = fullyQualifiedContainingType; + } + } +} diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs index deb227de63..68a55b146d 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs @@ -10,7 +10,7 @@ namespace osu.Framework.SourceGeneration.Generators.UnbindAllBindables { public class UnbindAllBindablesSemanticTarget : IncrementalSemanticTarget { - public readonly List BindableFieldNames = new List(); + public readonly List Bindables = new List(); public UnbindAllBindablesSemanticTarget(ClassDeclarationSyntax classSyntax, SemanticModel semanticModel) : base(classSyntax, semanticModel) @@ -30,9 +30,28 @@ protected override bool CheckValid(INamedTypeSymbol symbol) protected override void Process(INamedTypeSymbol symbol) { - BindableFieldNames.AddRange(symbol.GetMembers().OfType() - .Where(m => m.Type.AllInterfaces.Any(SyntaxHelpers.IsIUnbindableInterface)) - .Select(m => m.Name)); + foreach (IFieldSymbol bindableField in symbol.GetMembers().OfType().Where(m => m.Type.AllInterfaces.Any(SyntaxHelpers.IsIUnbindableInterface))) + { + if (bindableField.IsImplicitlyDeclared) + { + // Compiler generated names are of the form "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)); + } + else + Bindables.Add(new BindableDefinition(bindableField.Name, GlobalPrefixedTypeName)); + } } private bool isDrawableSubType(INamedTypeSymbol type) diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs index c7f182e85b..39aa0a23af 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs @@ -78,7 +78,7 @@ IEnumerable emitStatements() SyntaxFactory.IdentifierName(unbind_all_bindables_method_name)))); } - foreach (string name in Target.BindableFieldNames) + foreach (BindableDefinition bindable in Target.Bindables) { yield return SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( @@ -86,8 +86,14 @@ IEnumerable emitStatements() SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ParenthesizedExpression( SyntaxFactory.CastExpression( - SyntaxFactory.IdentifierName("osu.Framework.Bindables.IUnbindable"), - SyntaxFactory.IdentifierName(name))), + SyntaxFactory.IdentifierName("global::osu.Framework.Bindables.IUnbindable"), + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ParenthesizedExpression( + SyntaxFactory.CastExpression( + SyntaxFactory.ParseTypeName(bindable.FullyQualifiedContainingType), + SyntaxFactory.ThisExpression())), + SyntaxFactory.IdentifierName(bindable.Name)))), SyntaxFactory.IdentifierName("UnbindAll")))); } } From a0d3b3b8ff1725de95886c86ab1a1dc87137793a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 20:40:29 +0900 Subject: [PATCH 3/9] Exclude statics --- .../UnbindAllBindables/Static/Sources/Static.txt | 4 ++++ .../UnbindAllBindablesSourceGeneratorTests.cs | 1 + .../UnbindAllBindablesSemanticTarget.cs | 13 ++++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Static/Sources/Static.txt diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Static/Sources/Static.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Static/Sources/Static.txt new file mode 100644 index 0000000000..3ff28626e1 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Static/Sources/Static.txt @@ -0,0 +1,4 @@ +public partial class Static : osu.Framework.Graphics.Drawable +{ + public static readonly osu.Framework.Bindables.Bindable Bindable1 = new osu.Framework.Bindables.Bindable(); +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs b/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs index cbaebf5b41..159c256c64 100644 --- a/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs +++ b/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs @@ -15,6 +15,7 @@ public class UnbindAllBindablesSourceGeneratorTests : AbstractGeneratorTests [InlineData("Basic")] [InlineData("AutoProperty")] [InlineData("ExplicitImplementation")] + [InlineData("Static")] public Task Check(string name) { GetTestSources(name, diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs index 68a55b146d..d76a119df0 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs @@ -22,15 +22,14 @@ protected override bool CheckValid(INamedTypeSymbol symbol) if (!isDrawableSubType(symbol)) return false; - return isDrawableType(symbol) - || symbol.GetMembers().OfType().Any(m => m.Type.AllInterfaces.Any(SyntaxHelpers.IsIUnbindableInterface)); + return isDrawableType(symbol) || enumerateBindables(symbol).Any(); } protected override bool CheckNeedsOverride(INamedTypeSymbol symbol) => !isDrawableType(symbol); protected override void Process(INamedTypeSymbol symbol) { - foreach (IFieldSymbol bindableField in symbol.GetMembers().OfType().Where(m => m.Type.AllInterfaces.Any(SyntaxHelpers.IsIUnbindableInterface))) + foreach (IFieldSymbol bindableField in enumerateBindables(symbol)) { if (bindableField.IsImplicitlyDeclared) { @@ -54,6 +53,14 @@ protected override void Process(INamedTypeSymbol symbol) } } + private IEnumerable enumerateBindables(INamedTypeSymbol symbol) + { + return symbol.GetMembers() + .OfType() + .Where(m => !m.IsStatic) + .Where(m => m.Type.AllInterfaces.Any(SyntaxHelpers.IsIUnbindableInterface)); + } + private bool isDrawableSubType(INamedTypeSymbol type) { INamedTypeSymbol? s = type; From 9ac6e5ca2ac84417c7bcce64bf6c49079fad3208 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 20:48:37 +0900 Subject: [PATCH 4/9] Support nullability --- .../g_Nullable_UnbindAllBindables.txt | 13 +++++++ .../Nullable/Sources/Nullable.txt | 4 +++ .../UnbindAllBindablesSourceGeneratorTests.cs | 1 + .../UnbindAllBindables/BindableDefinition.cs | 4 ++- .../UnbindAllBindablesSemanticTarget.cs | 6 ++-- .../UnbindAllBindablesSourceEmitter.cs | 36 +++++++++++++------ 6 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Generated/g_Nullable_UnbindAllBindables.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Sources/Nullable.txt diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Generated/g_Nullable_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Generated/g_Nullable_UnbindAllBindables.txt new file mode 100644 index 0000000000..fdcd464131 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Generated/g_Nullable_UnbindAllBindables.txt @@ -0,0 +1,13 @@ +// +#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.Bindables.IUnbindable)((global::Nullable)this).Bindable1)?.UnbindAll(); + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Sources/Nullable.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Sources/Nullable.txt new file mode 100644 index 0000000000..88c0cb71ff --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Sources/Nullable.txt @@ -0,0 +1,4 @@ +public partial class Nullable : osu.Framework.Graphics.Drawable +{ + public osu.Framework.Bindables.Bindable? Bindable1; +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs b/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs index 159c256c64..cdfe355588 100644 --- a/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs +++ b/osu.Framework.SourceGeneration.Tests/UnbindAllBindables/UnbindAllBindablesSourceGeneratorTests.cs @@ -16,6 +16,7 @@ public class UnbindAllBindablesSourceGeneratorTests : AbstractGeneratorTests [InlineData("AutoProperty")] [InlineData("ExplicitImplementation")] [InlineData("Static")] + [InlineData("Nullable")] public Task Check(string name) { GetTestSources(name, diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/BindableDefinition.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/BindableDefinition.cs index 85e1688cf0..b81ef3a509 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/BindableDefinition.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/BindableDefinition.cs @@ -7,11 +7,13 @@ public readonly struct BindableDefinition { public readonly string Name; public readonly string FullyQualifiedContainingType; + public readonly bool IsNullable; - public BindableDefinition(string name, string fullyQualifiedContainingType) + public BindableDefinition(string name, string fullyQualifiedContainingType, bool isNullable) { Name = name; FullyQualifiedContainingType = fullyQualifiedContainingType; + IsNullable = isNullable; } } } diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs index d76a119df0..98f42f3db0 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs @@ -31,6 +31,8 @@ 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 "k__BackingField". @@ -46,10 +48,10 @@ protected override void Process(INamedTypeSymbol symbol) name = name.Substring(containingTypeIndex + 1); } - Bindables.Add(new BindableDefinition(name, containingType)); + Bindables.Add(new BindableDefinition(name, containingType, isNullable)); } else - Bindables.Add(new BindableDefinition(bindableField.Name, GlobalPrefixedTypeName)); + Bindables.Add(new BindableDefinition(bindableField.Name, GlobalPrefixedTypeName, isNullable)); } } diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs index 39aa0a23af..2ece823743 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs @@ -80,21 +80,35 @@ IEnumerable emitStatements() foreach (BindableDefinition bindable in Target.Bindables) { - yield return SyntaxFactory.ExpressionStatement( - SyntaxFactory.InvocationExpression( + ExpressionSyntax unbindableSyntax = SyntaxFactory.ParenthesizedExpression( + SyntaxFactory.CastExpression( + SyntaxFactory.IdentifierName("global::osu.Framework.Bindables.IUnbindable"), SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ParenthesizedExpression( SyntaxFactory.CastExpression( - SyntaxFactory.IdentifierName("global::osu.Framework.Bindables.IUnbindable"), - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.ParenthesizedExpression( - SyntaxFactory.CastExpression( - SyntaxFactory.ParseTypeName(bindable.FullyQualifiedContainingType), - SyntaxFactory.ThisExpression())), - SyntaxFactory.IdentifierName(bindable.Name)))), - SyntaxFactory.IdentifierName("UnbindAll")))); + SyntaxFactory.ParseTypeName(bindable.FullyQualifiedContainingType), + SyntaxFactory.ThisExpression())), + SyntaxFactory.IdentifierName(bindable.Name)))); + + if (bindable.IsNullable) + { + yield return SyntaxFactory.ExpressionStatement( + SyntaxFactory.ConditionalAccessExpression( + unbindableSyntax, + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberBindingExpression( + SyntaxFactory.IdentifierName("UnbindAll"))))); + } + else + { + yield return SyntaxFactory.ExpressionStatement( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + unbindableSyntax, + SyntaxFactory.IdentifierName("UnbindAll")))); + } } } } From 5cf556b3bf4849885e3fc50e0a1be093d1488b8f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 21:09:19 +0900 Subject: [PATCH 5/9] Add helper method --- .../g_AutoProperty_UnbindAllBindables.txt | 2 +- .../g_BaseType_UnbindAllBindables.txt | 2 +- .../g_DerivedType_UnbindAllBindables.txt | 2 +- .../CommonSources/SourceGeneratorUtils.txt | 13 +++++ ...licitImplementation_UnbindAllBindables.txt | 2 +- .../g_Nullable_UnbindAllBindables.txt | 2 +- .../Generated/g_Static_UnbindAllBindables.txt | 12 +++++ .../UnbindAllBindablesSemanticTarget.cs | 8 +-- .../UnbindAllBindablesSourceEmitter.cs | 51 ++++++++----------- osu.Framework/Utils/SourceGeneratorUtils.cs | 13 +++++ 10 files changed, 66 insertions(+), 41 deletions(-) create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/SourceGeneratorUtils.txt create mode 100644 osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Static/Generated/g_Static_UnbindAllBindables.txt diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Generated/g_AutoProperty_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Generated/g_AutoProperty_UnbindAllBindables.txt index ba7098a03b..ed3e905347 100644 --- a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Generated/g_AutoProperty_UnbindAllBindables.txt +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/AutoProperty/Generated/g_AutoProperty_UnbindAllBindables.txt @@ -8,6 +8,6 @@ partial class AutoProperty : global::osu.Framework.Graphics.ISourceGeneratedUnbi public override void InternalUnbindAllBindables() { base.InternalUnbindAllBindables(); - ((global::osu.Framework.Bindables.IUnbindable)((global::AutoProperty)this).Bindable1).UnbindAll(); + global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::AutoProperty), ((global::AutoProperty)this).Bindable1); } } \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_BaseType_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_BaseType_UnbindAllBindables.txt index 4733e3c471..8535eae87e 100644 --- a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_BaseType_UnbindAllBindables.txt +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_BaseType_UnbindAllBindables.txt @@ -8,6 +8,6 @@ partial class BaseType : global::osu.Framework.Graphics.ISourceGeneratedUnbindAl public override void InternalUnbindAllBindables() { base.InternalUnbindAllBindables(); - ((global::osu.Framework.Bindables.IUnbindable)((global::BaseType)this).Bindable1).UnbindAll(); + global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::BaseType), ((global::BaseType)this).Bindable1); } } \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_DerivedType_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_DerivedType_UnbindAllBindables.txt index a94f3a5c5e..358c471502 100644 --- a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_DerivedType_UnbindAllBindables.txt +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Basic/Generated/g_DerivedType_UnbindAllBindables.txt @@ -8,6 +8,6 @@ partial class DerivedType : global::osu.Framework.Graphics.ISourceGeneratedUnbin public override void InternalUnbindAllBindables() { base.InternalUnbindAllBindables(); - ((global::osu.Framework.Bindables.IUnbindable)((global::DerivedType)this).Bindable2).UnbindAll(); + global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::DerivedType), ((global::DerivedType)this).Bindable2); } } \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/SourceGeneratorUtils.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/SourceGeneratorUtils.txt new file mode 100644 index 0000000000..b526653d69 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/CommonSources/SourceGeneratorUtils.txt @@ -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 \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Generated/g_ExplicitImplementation_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Generated/g_ExplicitImplementation_UnbindAllBindables.txt index 23f9e9462d..1e5e750d11 100644 --- a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Generated/g_ExplicitImplementation_UnbindAllBindables.txt +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/ExplicitImplementation/Generated/g_ExplicitImplementation_UnbindAllBindables.txt @@ -8,6 +8,6 @@ partial class ExplicitImplementation : global::osu.Framework.Graphics.ISourceGen public override void InternalUnbindAllBindables() { base.InternalUnbindAllBindables(); - ((global::osu.Framework.Bindables.IUnbindable)((ITestInterface)this).Bindable1).UnbindAll(); + global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::ExplicitImplementation), ((ITestInterface)this).Bindable1); } } \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Generated/g_Nullable_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Generated/g_Nullable_UnbindAllBindables.txt index fdcd464131..d4de87c09d 100644 --- a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Generated/g_Nullable_UnbindAllBindables.txt +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Nullable/Generated/g_Nullable_UnbindAllBindables.txt @@ -8,6 +8,6 @@ partial class Nullable : global::osu.Framework.Graphics.ISourceGeneratedUnbindAl public override void InternalUnbindAllBindables() { base.InternalUnbindAllBindables(); - ((global::osu.Framework.Bindables.IUnbindable)((global::Nullable)this).Bindable1)?.UnbindAll(); + global::osu.Framework.Utils.SourceGeneratorUtils.UnbindBindable(typeof(global::Nullable), ((global::Nullable)this).Bindable1); } } \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Static/Generated/g_Static_UnbindAllBindables.txt b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Static/Generated/g_Static_UnbindAllBindables.txt new file mode 100644 index 0000000000..f1f063ef73 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/UnbindAllBindables/Static/Generated/g_Static_UnbindAllBindables.txt @@ -0,0 +1,12 @@ +// +#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(); + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs index 98f42f3db0..eb1d0d2415 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSemanticTarget.cs @@ -17,13 +17,7 @@ public UnbindAllBindablesSemanticTarget(ClassDeclarationSyntax classSyntax, Sema { } - protected override bool CheckValid(INamedTypeSymbol symbol) - { - if (!isDrawableSubType(symbol)) - return false; - - return isDrawableType(symbol) || enumerateBindables(symbol).Any(); - } + protected override bool CheckValid(INamedTypeSymbol symbol) => isDrawableSubType(symbol); protected override bool CheckNeedsOverride(INamedTypeSymbol symbol) => !isDrawableType(symbol); diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs index 2ece823743..5a8670a05e 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -80,35 +81,27 @@ IEnumerable emitStatements() foreach (BindableDefinition bindable in Target.Bindables) { - ExpressionSyntax unbindableSyntax = SyntaxFactory.ParenthesizedExpression( - SyntaxFactory.CastExpression( - SyntaxFactory.IdentifierName("global::osu.Framework.Bindables.IUnbindable"), - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.ParenthesizedExpression( - SyntaxFactory.CastExpression( - SyntaxFactory.ParseTypeName(bindable.FullyQualifiedContainingType), - SyntaxFactory.ThisExpression())), - SyntaxFactory.IdentifierName(bindable.Name)))); - - if (bindable.IsNullable) - { - yield return SyntaxFactory.ExpressionStatement( - SyntaxFactory.ConditionalAccessExpression( - unbindableSyntax, - SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberBindingExpression( - SyntaxFactory.IdentifierName("UnbindAll"))))); - } - else - { - yield return SyntaxFactory.ExpressionStatement( - SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - unbindableSyntax, - SyntaxFactory.IdentifierName("UnbindAll")))); - } + yield return SyntaxFactory.ExpressionStatement( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ParseTypeName("global::osu.Framework.Utils.SourceGeneratorUtils"), + SyntaxFactory.IdentifierName("UnbindBindable"))) + .WithArgumentList( + SyntaxFactory.ArgumentList( + SyntaxFactory.SeparatedList(new[] + { + SyntaxFactory.Argument( + SyntaxHelpers.TypeOf(Target.GlobalPrefixedTypeName)), + SyntaxFactory.Argument( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ParenthesizedExpression( + SyntaxFactory.CastExpression( + SyntaxFactory.IdentifierName(bindable.FullyQualifiedContainingType), + SyntaxFactory.ThisExpression())), + SyntaxFactory.IdentifierName(bindable.Name))) + })))); } } } diff --git a/osu.Framework/Utils/SourceGeneratorUtils.cs b/osu.Framework/Utils/SourceGeneratorUtils.cs index a0126b2758..797cd80b50 100644 --- a/osu.Framework/Utils/SourceGeneratorUtils.cs +++ b/osu.Framework/Utils/SourceGeneratorUtils.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Logging; namespace osu.Framework.Utils { @@ -73,5 +74,17 @@ public static T GetDependency(IReadOnlyDependencyContainer container, Type ca // `(int)(object)null` throws a NRE, so `default` is used instead. return val == null ? default! : (T)val; } + + public static void UnbindBindable(Type containingType, IUnbindable? bindable) + { + try + { + bindable?.UnbindAll(); + } + catch (Exception e) + { + Logger.Error(e, $"Failed to unbind a local bindable in {containingType.ReadableName()}"); + } + } } } From 89e821ad85c7b8ed70c193f691d603c85e7630f5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 21:55:44 +0900 Subject: [PATCH 6/9] Extract common name to const --- .../UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs | 3 +-- osu.Framework.SourceGeneration/SyntaxHelpers.cs | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs index 5a8670a05e..0effb1b1c6 100644 --- a/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs +++ b/osu.Framework.SourceGeneration/Generators/UnbindAllBindables/UnbindAllBindablesSourceEmitter.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -85,7 +84,7 @@ IEnumerable emitStatements() SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.ParseTypeName("global::osu.Framework.Utils.SourceGeneratorUtils"), + SyntaxFactory.ParseTypeName(SyntaxHelpers.UTILS_TYPE_NAME), SyntaxFactory.IdentifierName("UnbindBindable"))) .WithArgumentList( SyntaxFactory.ArgumentList( diff --git a/osu.Framework.SourceGeneration/SyntaxHelpers.cs b/osu.Framework.SourceGeneration/SyntaxHelpers.cs index ddcf4159ef..38448e97a7 100644 --- a/osu.Framework.SourceGeneration/SyntaxHelpers.cs +++ b/osu.Framework.SourceGeneration/SyntaxHelpers.cs @@ -13,6 +13,8 @@ namespace osu.Framework.SourceGeneration { public static class SyntaxHelpers { + public const string UTILS_TYPE_NAME = "global::osu.Framework.Utils.SourceGeneratorUtils"; + public static InvocationExpressionSyntax CacheDependencyInvocation(string callerType, ExpressionSyntax objSyntax, string? asType, string? cachedName, string? propertyName) { ExpressionSyntax asTypeSyntax = asType == null @@ -30,7 +32,7 @@ public static InvocationExpressionSyntax CacheDependencyInvocation(string caller return SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.ParseTypeName("global::osu.Framework.Utils.SourceGeneratorUtils"), + SyntaxFactory.ParseTypeName(UTILS_TYPE_NAME), SyntaxFactory.IdentifierName("CacheDependency"))) .WithArgumentList( SyntaxFactory.ArgumentList( @@ -67,7 +69,7 @@ public static InvocationExpressionSyntax GetDependencyInvocation(string callerTy return SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.ParseTypeName("global::osu.Framework.Utils.SourceGeneratorUtils"), + SyntaxFactory.ParseTypeName(UTILS_TYPE_NAME), SyntaxFactory.GenericName("GetDependency") .WithTypeArgumentList( SyntaxFactory.TypeArgumentList( From 72d5f3235b3cc4e22827cc82da6c0b3e03f51a0a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 21:57:04 +0900 Subject: [PATCH 7/9] Rename method --- osu.Framework/Graphics/Drawable_BindableUnbinding.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Framework/Graphics/Drawable_BindableUnbinding.cs b/osu.Framework/Graphics/Drawable_BindableUnbinding.cs index b8edbc5ebb..070c195e5e 100644 --- a/osu.Framework/Graphics/Drawable_BindableUnbinding.cs +++ b/osu.Framework/Graphics/Drawable_BindableUnbinding.cs @@ -18,10 +18,10 @@ private void unbindAllBindables() if (this is ISourceGeneratedUnbindAllBindables sg && sg.KnownType == GetType()) sg.InternalUnbindAllBindables(); - getUnbindAction().Invoke(this); + reflectUnbindAction().Invoke(this); } - private Action getUnbindAction() + private Action reflectUnbindAction() { Type ourType = GetType(); return unbind_action_cache.TryGetValue(ourType, out var action) ? action : addToCache(ourType); From 42b43fb0b8885984c19bd2000a46bb9a494caf0c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 21:58:30 +0900 Subject: [PATCH 8/9] Actually elide reflection pathway --- osu.Framework/Graphics/Drawable_BindableUnbinding.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Framework/Graphics/Drawable_BindableUnbinding.cs b/osu.Framework/Graphics/Drawable_BindableUnbinding.cs index 070c195e5e..d75c012ebc 100644 --- a/osu.Framework/Graphics/Drawable_BindableUnbinding.cs +++ b/osu.Framework/Graphics/Drawable_BindableUnbinding.cs @@ -16,7 +16,10 @@ public partial class Drawable private void unbindAllBindables() { if (this is ISourceGeneratedUnbindAllBindables sg && sg.KnownType == GetType()) + { sg.InternalUnbindAllBindables(); + return; + } reflectUnbindAction().Invoke(this); } From a5c7c73ddd18493c53a5cb93b2a62e1dcbce6048 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 29 Apr 2023 22:09:10 +0900 Subject: [PATCH 9/9] Add complex unbind benchmark --- .../BenchmarkUnbindAllBindables.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Framework.Benchmarks/BenchmarkUnbindAllBindables.cs b/osu.Framework.Benchmarks/BenchmarkUnbindAllBindables.cs index 64ec23396c..fbab7df171 100644 --- a/osu.Framework.Benchmarks/BenchmarkUnbindAllBindables.cs +++ b/osu.Framework.Benchmarks/BenchmarkUnbindAllBindables.cs @@ -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; @@ -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 { } @@ -64,5 +74,19 @@ public partial class SimpleComposite1 : SimpleComposite public partial class SimpleComposite : CompositeDrawable { } + + public partial class ComplexComposite : CompositeDrawable + { + private readonly Bindable bindable1 = new Bindable(); + private readonly Bindable bindable2 = new Bindable(); + private readonly Bindable bindable3 = new Bindable(); + private readonly Bindable bindable4 = new Bindable(); + private readonly Bindable bindable5 = new Bindable(); + private readonly Bindable bindable6 = new Bindable(); + private readonly Bindable bindable7 = new Bindable(); + private readonly Bindable bindable8 = new Bindable(); + private readonly Bindable bindable9 = new Bindable(); + private readonly Bindable bindable10 = new Bindable(); + } } }