Add source generator for IsLongRunning property

This commit is contained in:
Dan Balasescu
2023-07-07 20:24:56 +09:00
parent a08e61c58f
commit e4ebfdb828
15 changed files with 317 additions and 2 deletions

View File

@@ -0,0 +1,60 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
}
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.LongRunningLoad.LongRunningLoadSourceGenerator>;
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);
}
}
}

View File

@@ -0,0 +1,12 @@
// <auto-generated/>
#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;
}
}

View File

@@ -0,0 +1,6 @@
namespace osu.Framework.Graphics
{
public partial class Drawable
{
}
}

View File

@@ -0,0 +1,8 @@
namespace osu.Framework.Allocation
{
public interface ISourceGeneratedLongRunningLoadCache
{
protected internal System.Type KnownType { get; }
protected internal bool IsLongRunning { get; }
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace osu.Framework.Allocation
{
[AttributeUsage(AttributeTargets.Class)]
public class LongRunningLoadAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,9 @@
// <auto-generated/>
#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;
}

View File

@@ -0,0 +1,6 @@
using osu.Framework.Allocation;
[LongRunningLoad]
public partial class LongRunningType : osu.Framework.Graphics.Drawable
{
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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";
}
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<BaseTypeSyntax>(
SyntaxFactory.SimpleBaseType(
SyntaxFactory.ParseTypeName(interface_name)))))
.WithMembers(
SyntaxFactory.List(createProperties()));
}
private IEnumerable<MemberDeclarationSyntax> 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));
}
}
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
}
}

View File

@@ -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";

View File

@@ -0,0 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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; }
}
}

View File

@@ -70,8 +70,6 @@ namespace osu.Framework.Graphics
private static readonly GlobalStatistic<int> total_count = GlobalStatistics.Get<int>(nameof(Drawable), "Total constructed");
internal bool IsLongRunning => GetType().GetCustomAttribute<LongRunningLoadAttribute>() != null;
/// <summary>
/// Disposes this drawable.
/// </summary>

View File

@@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<LongRunningLoadAttribute>() != null;
}
}
}
}