mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-15 03:20:30 +00:00
226 lines
10 KiB
C#
226 lines
10 KiB
C#
// 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.
|
|
|
|
#nullable disable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using JetBrains.Annotations;
|
|
using osu.Framework.Extensions.TypeExtensions;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Statistics;
|
|
using osu.Framework.Utils;
|
|
|
|
namespace osu.Framework.Allocation
|
|
{
|
|
/// <summary>
|
|
/// An attribute that may be attached to a class, interface, field, or property definitions of a <see cref="Drawable"/>
|
|
/// to indicate that the value should be cached as a dependency.
|
|
/// Cached values may be resolved through <see cref="BackgroundDependencyLoaderAttribute"/> or <see cref="ResolvedAttribute"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The behaviour of this attribute differs in meaning depending on the type of member it is placed on.
|
|
/// <list type="bullet">
|
|
/// <item>
|
|
/// <para>
|
|
/// In the case of fields and properties, the dependency will be cached for the children of the drawable that contains the field or property.
|
|
/// </para>
|
|
/// <para>
|
|
/// Unless specified differently by <see cref="Type"/>, the dependency will be cached using the field/property value's concrete (most-derived) type.
|
|
/// See the examples section of the <see cref="Type"/> property documentation for further information.
|
|
/// </para>
|
|
/// </item>
|
|
/// <item>
|
|
/// <para>
|
|
/// Instances of classes annotated with <see cref="CachedAttribute"/> will cache themselves for their own children.
|
|
/// Unless specified differently by <see cref="Type"/>, the dependency will be cached using the type <em>at the point where the <see cref="CachedAttribute"/> was declared</em>.
|
|
/// </para>
|
|
/// <para>
|
|
/// Note that while the <see cref="CachedAttribute"/> itself is not inherited, derived class instances will still cache themselves using the base class.
|
|
/// See the examples section of the <see cref="Type"/> property documentation for further information.
|
|
/// </para>
|
|
/// </item>
|
|
/// <item>
|
|
/// If a class implements an interface annotated with <see cref="CachedAttribute"/>, then instances of that class will cache themselves for their own children using the interface type.
|
|
/// As with classes, the <see cref="CachedAttribute"/> is not inherited between interfaces either,
|
|
/// but an instance of a class will cache itself to children using all cacheable interface types that it implements.
|
|
/// </item>
|
|
/// </list>
|
|
/// </remarks>
|
|
[MeansImplicitUse]
|
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
|
|
public class CachedAttribute : Attribute
|
|
{
|
|
private static readonly GlobalStatistic<int> count_reflection_attributes = GlobalStatistics.Get<int>("Dependencies", "Reflected [Cached]s");
|
|
|
|
internal const BindingFlags ACTIVATOR_FLAGS = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
|
|
|
|
/// <summary>
|
|
/// The type to cache the value as. If null, the type depends on the type of member that the attribute is placed on:
|
|
/// <list type="bullet">
|
|
/// <item>In the case of fields and properties, the attribute will use the concrete/most-derived type of the field/property's value.</item>
|
|
/// <item>In the case of classes and interfaces, the attribute will use the class/interface type on which the <see cref="CachedAttribute"/> was <em>directly placed</em>.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
/// <example>
|
|
/// <para>
|
|
/// In the case of fields and properties, if this value is <see langword="null"/> on the following field definition:
|
|
/// <code>
|
|
/// [Cached]
|
|
/// private BaseType obj = new DerivedType();
|
|
/// </code>
|
|
/// then the cached type will be <c>DerivedType</c>.
|
|
/// </para>
|
|
/// <para>
|
|
/// In the case of classes, given the following structure:
|
|
/// <code>
|
|
/// [Cached]
|
|
/// public class A { }
|
|
///
|
|
/// public class B : A { }
|
|
/// </code>
|
|
/// the following things will happen:
|
|
/// <list type="bullet">
|
|
/// <item>Instances of <c>A</c> will cache themselves to children using type <c>A</c>.</item>
|
|
/// <item>Instances of <c>B</c> will cache themselves to children using type <c>A</c>.</item>
|
|
/// <item>
|
|
/// Instances of <c>B</c> will <em>not</em> cache themselves to children using type <c>B</c>.
|
|
/// To achieve that effect, the <see cref="CachedAttribute"/> has to be repeated on class <c>B</c>.
|
|
/// </item>
|
|
/// </list>
|
|
/// <see cref="CachedAttribute"/> placed in interface inheritance hierarchies follows analogous rules to the ones described above for classes.
|
|
/// </para>
|
|
/// </example>
|
|
public Type Type;
|
|
|
|
/// <summary>
|
|
/// The name to identify this member with.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the member is cached with a custom <see cref="CacheInfo"/> that provides a parent, the name is automatically inferred from the field/property.
|
|
/// </remarks>
|
|
public string Name;
|
|
|
|
/// <summary>
|
|
/// Identifies a member to be cached to a <see cref="DependencyContainer"/>.
|
|
/// </summary>
|
|
public CachedAttribute()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Identifies a member to be cached to a <see cref="DependencyContainer"/>.
|
|
/// </summary>
|
|
/// <param name="type">The type to cache the member as.</param>
|
|
/// <param name="name">The name to identify the member as in the cache.</param>
|
|
public CachedAttribute(Type type = null, string name = null)
|
|
{
|
|
Type = type;
|
|
Name = name;
|
|
}
|
|
|
|
internal static CacheDependencyDelegate CreateActivator(Type type)
|
|
{
|
|
count_reflection_attributes.Value++;
|
|
|
|
var additionActivators = new List<Action<object, DependencyContainer, CacheInfo>>();
|
|
|
|
foreach (var iface in type.GetInterfaces())
|
|
{
|
|
foreach (var attribute in iface.GetCustomAttributes<CachedAttribute>())
|
|
additionActivators.Add((target, dc, info) => SourceGeneratorUtils.CacheDependency(dc, type, target, info, attribute.Type ?? iface, attribute.Name, null));
|
|
}
|
|
|
|
foreach (var attribute in type.GetCustomAttributes<CachedAttribute>())
|
|
additionActivators.Add((target, dc, info) => SourceGeneratorUtils.CacheDependency(dc, type, target, info, attribute.Type ?? type, attribute.Name, null));
|
|
|
|
foreach (var property in type.GetProperties(ACTIVATOR_FLAGS).Where(f => f.GetCustomAttributes<CachedAttribute>().Any()))
|
|
additionActivators.AddRange(createMemberActivator(property, type));
|
|
|
|
foreach (var field in type.GetFields(ACTIVATOR_FLAGS).Where(f => f.GetCustomAttributes<CachedAttribute>().Any()))
|
|
additionActivators.AddRange(createMemberActivator(field, type));
|
|
|
|
if (additionActivators.Count == 0)
|
|
return (_, existing, _) => existing;
|
|
|
|
return (target, existing, info) =>
|
|
{
|
|
var dependencies = new DependencyContainer(existing);
|
|
foreach (var a in additionActivators)
|
|
a(target, dependencies, info);
|
|
|
|
return dependencies;
|
|
};
|
|
}
|
|
|
|
private static IEnumerable<Action<object, DependencyContainer, CacheInfo>> createMemberActivator(MemberInfo member, Type type)
|
|
{
|
|
switch (member)
|
|
{
|
|
case PropertyInfo pi:
|
|
{
|
|
var getMethod = pi.GetMethod;
|
|
if (getMethod == null)
|
|
throw new AccessModifierNotAllowedForCachedValueException(AccessModifier.None, pi);
|
|
|
|
if (getMethod.GetCustomAttribute<CompilerGeneratedAttribute>() == null)
|
|
throw new AccessModifierNotAllowedForCachedValueException(AccessModifier.None, pi);
|
|
|
|
var setMethod = pi.SetMethod;
|
|
|
|
if (setMethod != null)
|
|
{
|
|
var modifier = setMethod.GetAccessModifier();
|
|
if (modifier != AccessModifier.Private)
|
|
throw new AccessModifierNotAllowedForCachedValueException(modifier, setMethod);
|
|
|
|
if (setMethod.GetCustomAttribute<CompilerGeneratedAttribute>() == null)
|
|
throw new AccessModifierNotAllowedForCachedValueException(AccessModifier.None, pi);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case FieldInfo fi:
|
|
{
|
|
var modifier = fi.GetAccessModifier();
|
|
if (modifier != AccessModifier.Private && !fi.IsInitOnly)
|
|
throw new AccessModifierNotAllowedForCachedValueException(modifier, fi);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (var attribute in member.GetCustomAttributes<CachedAttribute>())
|
|
{
|
|
yield return (target, dc, info) =>
|
|
{
|
|
object value = null;
|
|
|
|
if (member is PropertyInfo p)
|
|
value = p.GetValue(target);
|
|
|
|
if (member is FieldInfo f)
|
|
value = f.GetValue(target);
|
|
|
|
SourceGeneratorUtils.CacheDependency(dc, type, value, info, attribute.Type, attribute.Name, member.Name);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The exception that is thrown when attempting to cache <see langword="null"/> using <see cref="CachedAttribute"/>.
|
|
/// </summary>
|
|
public sealed class NullDependencyException : InvalidOperationException
|
|
{
|
|
public NullDependencyException(string message)
|
|
: base(message)
|
|
{
|
|
}
|
|
}
|
|
}
|