diff --git a/osu.Framework.Benchmarks/BenchmarkLongRunningLoad.cs b/osu.Framework.Benchmarks/BenchmarkLongRunningLoad.cs new file mode 100644 index 000000000..811c02221 --- /dev/null +++ b/osu.Framework.Benchmarks/BenchmarkLongRunningLoad.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using BenchmarkDotNet.Attributes; +using osu.Framework.Allocation; +using osu.Framework.Graphics; + +namespace osu.Framework.Benchmarks +{ + [MemoryDiagnoser] + public partial class BenchmarkLongRunningLoad + { + private Drawable nonLongRunningReflectionDrawable = null!; + private Drawable longRunningReflectionDrawable = null!; + private Drawable nonLongRunningSourceGenerationDrawable = null!; + private Drawable longRunningSourceGenerationDrawable = null!; + + [GlobalSetup] + public void GlobalSetup() + { + nonLongRunningReflectionDrawable = new NonLongRunningReflectionDrawable(); + longRunningReflectionDrawable = new LongRunningReflectionDrawable(); + + nonLongRunningSourceGenerationDrawable = new NonLongRunningSourceGenerationDrawable(); + longRunningSourceGenerationDrawable = new LongRunningSourceGenerationDrawable(); + } + + [Benchmark] + public bool QueryNonLongRunningViaReflection() => nonLongRunningReflectionDrawable.IsLongRunning; + + [Benchmark] + public bool QueryLongRunningViaReflection() => longRunningReflectionDrawable.IsLongRunning; + + [Benchmark] + public bool QueryNonLongRunningViaSourceGeneration() => nonLongRunningSourceGenerationDrawable.IsLongRunning; + + [Benchmark] + public bool QueryLongRunningViaSourceGeneration() => longRunningSourceGenerationDrawable.IsLongRunning; + +#pragma warning disable OFSG001 + private class NonLongRunningReflectionDrawable : Drawable + { + } + + [LongRunningLoad] + private class LongRunningReflectionDrawable : Drawable + { + } +#pragma warning restore OFSG001 + + private partial class NonLongRunningSourceGenerationDrawable : Drawable + { + } + + [LongRunningLoad] + private partial class LongRunningSourceGenerationDrawable : Drawable + { + } + } +} diff --git a/osu.Framework.SourceGeneration.Tests/LongRunningLoad/LongRunningLoadSourceGeneratorTests.cs b/osu.Framework.SourceGeneration.Tests/LongRunningLoad/LongRunningLoadSourceGeneratorTests.cs new file mode 100644 index 000000000..f2a6a6102 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/LongRunningLoad/LongRunningLoadSourceGeneratorTests.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.LongRunningLoad +{ + public class LongRunningLoadSourceGeneratorTests : AbstractGeneratorTests + { + protected override string ResourceNamespace => "LongRunningLoad"; + + [Theory] + [InlineData("LongRunningType")] + 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.Tests/Resources/LongRunningLoad/CommonGenerated/g_osu.Framework.Graphics.Drawable_LongRunningLoad.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonGenerated/g_osu.Framework.Graphics.Drawable_LongRunningLoad.txt new file mode 100644 index 000000000..c3c0ec023 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonGenerated/g_osu.Framework.Graphics.Drawable_LongRunningLoad.txt @@ -0,0 +1,12 @@ +// +#nullable enable +#pragma warning disable CS4014 + +namespace osu.Framework.Graphics +{ + partial class Drawable : global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache + { + global::System.Type global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache.KnownType => typeof(global::osu.Framework.Graphics.Drawable); + bool global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache.IsLongRunning => false; + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/Drawable.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/Drawable.txt new file mode 100644 index 000000000..e5cde97c3 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/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/LongRunningLoad/CommonSources/ISourceGeneratedLongRunningLoadCache.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/ISourceGeneratedLongRunningLoadCache.txt new file mode 100644 index 000000000..04456fa08 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/ISourceGeneratedLongRunningLoadCache.txt @@ -0,0 +1,8 @@ +namespace osu.Framework.Allocation +{ + public interface ISourceGeneratedLongRunningLoadCache + { + protected internal System.Type KnownType { get; } + protected internal bool IsLongRunning { get; } + } +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/LongRunningLoadAttribute.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/LongRunningLoadAttribute.txt new file mode 100644 index 000000000..93215725f --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/CommonSources/LongRunningLoadAttribute.txt @@ -0,0 +1,9 @@ +using System; + +namespace osu.Framework.Allocation +{ + [AttributeUsage(AttributeTargets.Class)] + public class LongRunningLoadAttribute : Attribute + { + } +} diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Generated/g_LongRunningType_LongRunningLoad.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Generated/g_LongRunningType_LongRunningLoad.txt new file mode 100644 index 000000000..65a489d3e --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Generated/g_LongRunningType_LongRunningLoad.txt @@ -0,0 +1,9 @@ +// +#nullable enable +#pragma warning disable CS4014 + +partial class LongRunningType : global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache +{ + global::System.Type global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache.KnownType => typeof(global::LongRunningType); + bool global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache.IsLongRunning => true; +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Sources/LongRunningType.txt b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Sources/LongRunningType.txt new file mode 100644 index 000000000..95c5effa6 --- /dev/null +++ b/osu.Framework.SourceGeneration.Tests/Resources/LongRunningLoad/LongRunningType/Sources/LongRunningType.txt @@ -0,0 +1,6 @@ +using osu.Framework.Allocation; + +[LongRunningLoad] +public partial class LongRunningType : osu.Framework.Graphics.Drawable +{ +} \ No newline at end of file diff --git a/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSemanticTarget.cs b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSemanticTarget.cs new file mode 100644 index 000000000..0835cf74d --- /dev/null +++ b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSemanticTarget.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace osu.Framework.SourceGeneration.Generators.LongRunningLoad +{ + public class LongRunningLoadSemanticTarget : IncrementalSemanticTarget + { + public bool IsLongRunning { get; private set; } + + public LongRunningLoadSemanticTarget(ClassDeclarationSyntax classSyntax, SemanticModel semanticModel) + : base(classSyntax, semanticModel) + { + } + + protected override bool CheckValid(INamedTypeSymbol symbol) + { + INamedTypeSymbol? s = symbol; + + while (s != null) + { + if (isDrawableType(s)) + return true; + + s = s.BaseType; + } + + return false; + } + + // This source generator never overrides. + protected override bool CheckNeedsOverride(INamedTypeSymbol symbol) => false; + + protected override void Process(INamedTypeSymbol symbol) + { + if (FullyQualifiedTypeName == "osu.Framework.Graphics.Drawable") + return; + + IsLongRunning = symbol.GetAttributes().Any(SyntaxHelpers.IsLongRunningLoadAttribute); + } + + private static bool isDrawableType(INamedTypeSymbol type) + => SyntaxHelpers.GetFullyQualifiedTypeName(type) == "osu.Framework.Graphics.Drawable"; + } +} diff --git a/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceEmitter.cs b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceEmitter.cs new file mode 100644 index 000000000..b18d60cae --- /dev/null +++ b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceEmitter.cs @@ -0,0 +1,72 @@ +// 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.LongRunningLoad +{ + public class LongRunningLoadSourceEmitter : IncrementalSourceEmitter + { + private const string interface_name = "global::osu.Framework.Allocation.ISourceGeneratedLongRunningLoadCache"; + + protected override string FileSuffix => "LongRunningLoad"; + + public new LongRunningLoadSemanticTarget Target => (LongRunningLoadSemanticTarget)base.Target; + + public LongRunningLoadSourceEmitter(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(createProperties())); + } + + private IEnumerable createProperties() + { + // Drawable is the base type which always needs to have the members defined. + bool isDrawable = Target.FullyQualifiedTypeName == "osu.Framework.Graphics.Drawable"; + + yield 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)); + + if (Target.IsLongRunning || isDrawable) + { + yield return SyntaxFactory.PropertyDeclaration( + SyntaxFactory.PredefinedType( + SyntaxFactory.Token(SyntaxKind.BoolKeyword)), + SyntaxFactory.Identifier("IsLongRunning")) + .WithExplicitInterfaceSpecifier( + SyntaxFactory.ExplicitInterfaceSpecifier( + SyntaxFactory.IdentifierName(interface_name))) + .WithExpressionBody( + SyntaxFactory.ArrowExpressionClause( + SyntaxFactory.LiteralExpression( + Target.IsLongRunning + ? SyntaxKind.TrueLiteralExpression + : SyntaxKind.FalseLiteralExpression))) + .WithSemicolonToken( + SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + } + } + } +} diff --git a/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceGenerator.cs b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceGenerator.cs new file mode 100644 index 000000000..62ebb782c --- /dev/null +++ b/osu.Framework.SourceGeneration/Generators/LongRunningLoad/LongRunningLoadSourceGenerator.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.LongRunningLoad +{ + [Generator] + public class LongRunningLoadSourceGenerator : AbstractIncrementalGenerator + { + protected override IncrementalSemanticTarget CreateSemanticTarget(ClassDeclarationSyntax node, SemanticModel semanticModel) + => new LongRunningLoadSemanticTarget(node, semanticModel); + + protected override IncrementalSourceEmitter CreateSourceEmitter(IncrementalSemanticTarget target) + => new LongRunningLoadSourceEmitter(target); + } +} diff --git a/osu.Framework.SourceGeneration/SyntaxHelpers.cs b/osu.Framework.SourceGeneration/SyntaxHelpers.cs index 31b22f9f2..3959d9fa6 100644 --- a/osu.Framework.SourceGeneration/SyntaxHelpers.cs +++ b/osu.Framework.SourceGeneration/SyntaxHelpers.cs @@ -100,6 +100,9 @@ namespace osu.Framework.SourceGeneration public static bool IsCachedAttribute(AttributeData? attribute) => IsCachedAttribute(attribute?.AttributeClass); + public static bool IsLongRunningLoadAttribute(AttributeData? attribute) + => IsLongRunningLoadAttribute(attribute?.AttributeClass); + public static bool IsBackgroundDependencyLoaderAttribute(ITypeSymbol? type) => type != null && GetFullyQualifiedTypeName(type) == "osu.Framework.Allocation.BackgroundDependencyLoaderAttribute"; @@ -109,6 +112,9 @@ namespace osu.Framework.SourceGeneration public static bool IsCachedAttribute(ITypeSymbol? type) => type != null && GetFullyQualifiedTypeName(type) == "osu.Framework.Allocation.CachedAttribute"; + public static bool IsLongRunningLoadAttribute(ITypeSymbol? type) + => type != null && GetFullyQualifiedTypeName(type) == "osu.Framework.Allocation.LongRunningLoadAttribute"; + public static bool IsIDependencyInjectionCandidateInterface(ITypeSymbol? type) => type != null && GetFullyQualifiedTypeName(type) == "osu.Framework.Allocation.IDependencyInjectionCandidate"; diff --git a/osu.Framework/Allocation/ISourceGeneratedLongRunningLoadCache.cs b/osu.Framework/Allocation/ISourceGeneratedLongRunningLoadCache.cs new file mode 100644 index 000000000..a70185be9 --- /dev/null +++ b/osu.Framework/Allocation/ISourceGeneratedLongRunningLoadCache.cs @@ -0,0 +1,13 @@ +// 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.Allocation +{ + public interface ISourceGeneratedLongRunningLoadCache + { + protected internal Type KnownType { get; } + protected internal bool IsLongRunning { get; } + } +} diff --git a/osu.Framework/Graphics/Drawable.cs b/osu.Framework/Graphics/Drawable.cs index ca0ab6e20..f8a7c7101 100644 --- a/osu.Framework/Graphics/Drawable.cs +++ b/osu.Framework/Graphics/Drawable.cs @@ -70,8 +70,6 @@ namespace osu.Framework.Graphics private static readonly GlobalStatistic total_count = GlobalStatistics.Get(nameof(Drawable), "Total constructed"); - internal bool IsLongRunning => GetType().GetCustomAttribute() != null; - /// /// Disposes this drawable. /// diff --git a/osu.Framework/Graphics/Drawable_IsLongRunning.cs b/osu.Framework/Graphics/Drawable_IsLongRunning.cs new file mode 100644 index 000000000..5949795b7 --- /dev/null +++ b/osu.Framework/Graphics/Drawable_IsLongRunning.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using osu.Framework.Allocation; + +namespace osu.Framework.Graphics +{ + public partial class Drawable + { + internal bool IsLongRunning + { + get + { + if (this is ISourceGeneratedLongRunningLoadCache sgCache && sgCache.KnownType == GetType()) + return sgCache.IsLongRunning; + + return GetType().GetCustomAttribute() != null; + } + } + } +}