Browse Source

Reduce allocations while loading deferred resources (#15070)

* Reduce allocations in XamlIlRuntimeHelpers

* Iterate XAML parents without allocations

* Use IDeferredContent for XAML deferred content to reduce allocations

* Use function pointer in DeferredTransformationFactory

* Reuse parent resource nodes if possible for deferred content

* Fix function pointer usage with SRE
pull/15506/head
Julien Lebosquain 2 years ago
committed by GitHub
parent
commit
a7e102676f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 16
      src/Avalonia.Base/Controls/IDeferredContent.cs
  2. 32
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  3. 69
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  4. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  5. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  6. 47
      src/Markup/Avalonia.Markup.Xaml/EagerParentStackEnumerator.cs
  7. 47
      src/Markup/Avalonia.Markup.Xaml/Extensions.cs
  8. 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  9. 63
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  10. 30
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  11. 7
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlParentStackProvider.cs
  12. 325
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  13. 7
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

16
src/Avalonia.Base/Controls/IDeferredContent.cs

@ -0,0 +1,16 @@
using System;
namespace Avalonia.Controls;
/// <summary>
/// Represents a deferred content.
/// </summary>
public interface IDeferredContent
{
/// <summary>
/// Builds the deferred content using the specified service provider.
/// </summary>
/// <param name="serviceProvider">The service provider to use.</param>
/// <returns>The built content.</returns>
object? Build(IServiceProvider? serviceProvider);
}

32
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -1,12 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Templates;
using Avalonia.Media;
using Avalonia.Styling;
namespace Avalonia.Controls
@ -16,7 +13,7 @@ namespace Avalonia.Controls
/// </summary>
public class ResourceDictionary : ResourceProvider, IResourceDictionary, IThemeVariantProvider
{
private object? lastDeferredItemKey;
private object? _lastDeferredItemKey;
private Dictionary<object, object?>? _inner;
private AvaloniaList<IResourceProvider>? _mergedDictionaries;
private AvaloniaDictionary<ThemeVariant, IThemeVariantProvider>? _themeDictionary;
@ -145,10 +142,10 @@ namespace Avalonia.Controls
}
public void AddDeferred(object key, Func<IServiceProvider?, object?> factory)
{
Inner.Add(key, new DeferredItem(factory));
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
=> Add(key, new DeferredItem(factory));
public void AddDeferred(object key, IDeferredContent deferredContent)
=> Add(key, deferredContent);
public void Clear()
{
@ -227,10 +224,10 @@ namespace Avalonia.Controls
{
if (_inner is not null && _inner.TryGetValue(key, out value))
{
if (value is DeferredItem deffered)
if (value is IDeferredContent deferred)
{
// Avoid simple reentrancy, which could commonly occur on redefining the resource.
if (lastDeferredItemKey == key)
if (_lastDeferredItemKey == key)
{
value = null;
return false;
@ -238,8 +235,8 @@ namespace Avalonia.Controls
try
{
lastDeferredItemKey = key;
_inner[key] = value = deffered.Factory(null) switch
_lastDeferredItemKey = key;
_inner[key] = value = deferred.Build(null) switch
{
ITemplateResult t => t.Result,
{ } v => v,
@ -248,7 +245,7 @@ namespace Avalonia.Controls
}
finally
{
lastDeferredItemKey = null;
_lastDeferredItemKey = null;
}
}
return true;
@ -313,7 +310,7 @@ namespace Avalonia.Controls
{
if (_inner is not null && _inner.TryGetValue(key, out var result))
{
return result is DeferredItem;
return result is IDeferredContent;
}
return false;
@ -373,10 +370,11 @@ namespace Avalonia.Controls
}
}
private class DeferredItem
private sealed class DeferredItem : IDeferredContent
{
public DeferredItem(Func<IServiceProvider?, object?> factory) => Factory = factory;
public Func<IServiceProvider?, object?> Factory { get; }
private readonly Func<IServiceProvider?,object?> _factory;
public DeferredItem(Func<IServiceProvider?, object?> factory) => _factory = factory;
public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider);
}
}
}

69
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -1,13 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Media;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
@ -64,7 +58,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
"TemplateResultType"
},
DeferredContentExecutorCustomization =
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV2"),
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV3"),
UsableDuringInitializationAttributes =
{
typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"),
@ -79,18 +73,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var emit = new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>
{
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.EmitProvideValueTarget,
ContextTypeBuilderCallback = definition => EmitNameScopeField(rv, typeSystem, definition)
ContextTypeBuilderCallback = definition =>
{
EmitNameScopeField(rv, typeSystem, definition);
EmitEagerParentStackProvider(rv, typeSystem, definition);
}
};
return (rv, emit);
}
public const string ContextNameScopeFieldName = "AvaloniaNameScope";
private static void EmitNameScopeField(XamlLanguageTypeMappings mappings,
private static void EmitNameScopeField(
XamlLanguageTypeMappings mappings,
IXamlTypeSystem typeSystem,
IXamlILContextDefinition<IXamlILEmitter> definition)
{
var nameScopeType = typeSystem.FindType("Avalonia.Controls.INameScope");
var field = definition.TypeBuilder.DefineField(nameScopeType,
ContextNameScopeFieldName, XamlVisibility.Public, false);
@ -102,7 +100,56 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
typeSystem.FindType("System.Object"), typeSystem.FindType("System.Type"))))
.Stfld(field);
}
private static void EmitEagerParentStackProvider(
XamlLanguageTypeMappings mappings,
IXamlTypeSystem typeSystem,
IXamlILContextDefinition<IXamlILEmitter> definition)
{
var interfaceType = typeSystem.FindType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlEagerParentStackProvider");
definition.TypeBuilder.AddInterfaceImplementation(interfaceType);
// IReadOnlyList<object> DirectParents => (IReadOnlyList<object>)ParentsStack;
var directParentsGetter = ImplementInterfacePropertyGetter("DirectParents");
directParentsGetter.Generator
.LdThisFld(definition.ParentListField)
.Castclass(directParentsGetter.ReturnType)
.Ret();
var serviceProviderGetServiceMethod = mappings.ServiceProvider.GetMethod(new FindMethodMethodSignature(
"GetService",
typeSystem.FindType("System.Object"),
typeSystem.FindType("System.Type")));
// IAvaloniaXamlIlEagerParentStackProvider? ParentProvider
// => (IAvaloniaXamlIlEagerParentStackProvider)_serviceProvider.GetService(typeof(IAvaloniaXamlIlParentStackProvider))
var parentProviderGetter = ImplementInterfacePropertyGetter("ParentProvider");
parentProviderGetter.Generator
.LdThisFld(definition.ParentServiceProviderField)
.Ldtype(mappings.ParentStackProvider)
.EmitCall(serviceProviderGetServiceMethod)
.Castclass(interfaceType)
.Ret();
IXamlMethodBuilder<IXamlILEmitter> ImplementInterfacePropertyGetter(string propertyName)
{
var interfaceGetter = interfaceType.FindMethod(m => m.Name == "get_" + propertyName);
var getter = definition.TypeBuilder.DefineMethod(
interfaceGetter.ReturnType,
Array.Empty<IXamlType>(),
"get_" + propertyName,
XamlVisibility.Private,
false,
true,
interfaceGetter);
definition.TypeBuilder.DefineProperty(interfaceGetter.ReturnType, propertyName, null, getter);
return getter;
}
}
class AttributeResolver : IXamlCustomAttributeResolver
{

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -261,9 +261,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary");
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent"));
ResourceDictionaryEnsureCapacity = ResourceDictionary.FindMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32);
ResourceDictionaryGetCount = ResourceDictionary.FindMethod("get_Count", XamlIlTypes.Int32, true);
IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider");

2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -7,6 +7,7 @@
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<EnableDefaultItems>false</EnableDefaultItems>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="AvaloniaXamlLoader.cs" />
@ -15,6 +16,7 @@
<Compile Include="Converters\FontFamilyTypeConverter.cs" />
<Compile Include="Converters\TimeSpanTypeConverter.cs" />
<Compile Include="Data\DynamicResourceExpression.cs" />
<Compile Include="EagerParentStackEnumerator.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="MarkupExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindingExtension.cs" />

47
src/Markup/Avalonia.Markup.Xaml/EagerParentStackEnumerator.cs

@ -0,0 +1,47 @@
using System.Collections.Generic;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
namespace Avalonia.Markup.Xaml;
internal struct EagerParentStackEnumerator
{
private IAvaloniaXamlIlEagerParentStackProvider? _provider;
private IReadOnlyList<object>? _currentParents;
private int _currentIndex; // only valid when _currentParents isn't null
public EagerParentStackEnumerator(IAvaloniaXamlIlEagerParentStackProvider? provider)
=> _provider = provider;
public object? TryGetNext()
{
while (_provider is not null)
{
if (_currentParents is null)
{
_currentParents = _provider.DirectParents;
_currentIndex = _currentParents.Count;
}
--_currentIndex;
if (_currentIndex >= 0)
return _currentParents[_currentIndex];
_currentParents = null;
_provider = _provider.ParentProvider;
}
return null;
}
public T? TryGetNextOfType<T>() where T : class
{
while (TryGetNext() is { } parent)
{
if (parent is T typedParent)
return typedParent;
}
return null;
}
}

47
src/Markup/Avalonia.Markup.Xaml/Extensions.cs

@ -18,13 +18,52 @@ namespace Avalonia.Markup.Xaml
public static Uri? GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>()?.BaseUri;
public static T? GetFirstParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>().FirstOrDefault();
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>() switch
{
null => null,
IAvaloniaXamlIlEagerParentStackProvider eager => new EagerParentStackEnumerator(eager).TryGetNextOfType<T>(),
{ } provider => provider.Parents.OfType<T>().FirstOrDefault()
};
public static T? GetLastParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>().LastOrDefault();
{
switch (ctx.GetService<IAvaloniaXamlIlParentStackProvider>())
{
case null:
return null;
case IAvaloniaXamlIlEagerParentStackProvider eager:
var enumerator = new EagerParentStackEnumerator(eager);
T? lastTypedParent = null;
while (enumerator.TryGetNextOfType<T>() is { } typedParent)
lastTypedParent = typedParent;
return lastTypedParent;
case { } provider:
return provider.Parents.OfType<T>().LastOrDefault();
}
}
public static IEnumerable<T> GetParents<T>(this IServiceProvider sp) where T : class
{
return sp.GetService<IAvaloniaXamlIlParentStackProvider>() switch
{
null => Enumerable.Empty<T>(),
IAvaloniaXamlIlEagerParentStackProvider eager => EnumerateEagerProvider(eager),
{ } provider => provider.Parents.OfType<T>()
};
public static IEnumerable<T> GetParents<T>(this IServiceProvider sp)
=> sp.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>() ?? Enumerable.Empty<T>();
// allocates the final iterator, but still avoids the intermediate ones
static IEnumerable<T> EnumerateEagerProvider(IAvaloniaXamlIlEagerParentStackProvider eagerProvider)
{
var enumerator = new EagerParentStackEnumerator(eagerProvider);
while (enumerator.TryGetNextOfType<T>() is { } typedParent)
yield return typedParent;
}
}
public static bool IsInControlTemplate(this IServiceProvider sp) => sp.GetService<IAvaloniaXamlIlControlTemplateProvider>() != null;

4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions
@ -37,7 +38,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
(object?)serviceProvider.GetFirstParent<IResourceHost>();
}
_themeVariant = StaticResourceExtension.GetDictionaryVariant(serviceProvider);
_themeVariant = StaticResourceExtension.GetDictionaryVariant(
serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>());
return this;
}

63
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -40,7 +40,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
};
var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant
?? GetDictionaryVariant(serviceProvider);
?? GetDictionaryVariant(stack);
var targetType = targetProperty switch
{
@ -58,11 +58,26 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
// which might be able to give us the resource.
if (stack is not null)
{
foreach (var parent in stack.Parents)
// avoid allocations iterating the parents when possible
if (stack is IAvaloniaXamlIlEagerParentStackProvider eagerStack)
{
if (parent is IResourceNode node && node.TryGetResource(resourceKey, themeVariant, out var value))
var enumerator = new EagerParentStackEnumerator(eagerStack);
while (enumerator.TryGetNextOfType<IResourceNode>() is { } node)
{
return ColorToBrushConverter.Convert(value, targetType);
if (node.TryGetResource(resourceKey, themeVariant, out var value))
{
return ColorToBrushConverter.Convert(value, targetType);
}
}
}
else
{
foreach (var parent in stack.Parents)
{
if (parent is IResourceNode node && node.TryGetResource(resourceKey, themeVariant, out var value))
{
return ColorToBrushConverter.Convert(value, targetType);
}
}
}
}
@ -86,23 +101,37 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
return ColorToBrushConverter.Convert(control.FindResource(ResourceKey!), targetType);
}
internal static ThemeVariant? GetDictionaryVariant(IServiceProvider serviceProvider)
internal static ThemeVariant? GetDictionaryVariant(IAvaloniaXamlIlParentStackProvider? stack)
{
var parents = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents;
if (parents is null)
switch (stack)
{
return null;
}
case null:
return null;
foreach (var parent in parents)
{
if (parent is IThemeVariantProvider { Key: { } setKey })
{
return setKey;
}
}
case IAvaloniaXamlIlEagerParentStackProvider eager:
var enumerator = new EagerParentStackEnumerator(eager);
return null;
while (enumerator.TryGetNextOfType<IThemeVariantProvider>() is { } themeVariantProvider)
{
if (themeVariantProvider.Key is { } setKey)
{
return setKey;
}
}
return null;
case { } provider:
foreach (var parent in provider.Parents)
{
if (parent is IThemeVariantProvider { Key: { } setKey })
{
return setKey;
}
}
return null;
}
}
}
}

30
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -7,29 +7,15 @@ namespace Avalonia.Markup.Xaml.Templates
public static class TemplateContent
{
public static TemplateResult<Control>? Load(object? templateContent)
{
if (templateContent is Func<IServiceProvider?, object?> direct)
{
return (TemplateResult<Control>?)direct(null);
}
if (templateContent is null)
{
return null;
}
throw new ArgumentException($"Unexpected content {templateContent.GetType()}", nameof(templateContent));
}
=> Load<Control>(templateContent);
public static TemplateResult<T>? Load<T>(object? templateContent)
{
if (templateContent is Func<IServiceProvider?, object?> direct)
return (TemplateResult<T>?)direct(null);
if (templateContent is null)
return null;
throw new ArgumentException($"Unexpected content {templateContent.GetType()}", nameof(templateContent));
}
=> templateContent switch
{
IDeferredContent deferred => (TemplateResult<T>?)deferred.Build(null),
Func<IServiceProvider?, object?> deferred => (TemplateResult<T>?)deferred(null),
null => null,
_ => throw new ArgumentException($"Unexpected content {templateContent.GetType()}", nameof(templateContent))
};
}
}

7
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlParentStackProvider.cs

@ -6,4 +6,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
IEnumerable<object> Parents { get; }
}
public interface IAvaloniaXamlIlEagerParentStackProvider : IAvaloniaXamlIlParentStackProvider
{
IReadOnlyList<object> DirectParents { get; }
IAvaloniaXamlIlEagerParentStackProvider? ParentProvider { get; }
}
}

325
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -1,8 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
@ -15,6 +18,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public static class XamlIlRuntimeHelpers
{
[ThreadStatic] private static List<IResourceNode>? s_resourceNodeBuffer;
[ThreadStatic] private static LastParentStack? s_lastParentStack;
public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
@ -24,36 +30,193 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public static Func<IServiceProvider, object> DeferredTransformationFactoryV2<T>(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
var resourceNodes = provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList();
var resourceNodes = AsResourceNodes(provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>());
var rootObject = provider.GetRequiredService<IRootObjectProvider>().RootObject;
var parentScope = provider.GetService<INameScope>();
return new DelegateDeferredContent<T>(resourceNodes, rootObject, parentScope, builder).Build;
}
// The builder is typed as IntPtr instead of delegate*<IServiceProvider, object> because Reflection.Emit has
// trouble with generic methods containing function pointers. See https://github.com/dotnet/runtime/issues/100020
public static unsafe IDeferredContent DeferredTransformationFactoryV3<T>(
/*delegate*<IServiceProvider, object>*/ IntPtr builder,
IServiceProvider provider)
{
var resourceNodes = AsResourceNodes(provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>());
var rootObject = provider.GetRequiredService<IRootObjectProvider>().RootObject;
var parentScope = provider.GetService<INameScope>();
return sp =>
var typedBuilder = (delegate*<IServiceProvider, object>)builder;
return new PointerDeferredContent<T>(resourceNodes, rootObject, parentScope, typedBuilder);
}
private static IResourceNode[] AsResourceNodes(IAvaloniaXamlIlParentStackProvider provider)
{
var buffer = s_resourceNodeBuffer ??= new List<IResourceNode>(8);
buffer.Clear();
if (provider is IAvaloniaXamlIlEagerParentStackProvider eagerProvider)
{
var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
scope.Complete();
var enumerator = new EagerParentStackEnumerator(eagerProvider);
while (enumerator.TryGetNextOfType<IResourceNode>() is { } node)
buffer.Add(node);
}
else
{
foreach (var item in provider.Parents)
{
if (item is IResourceNode node)
buffer.Add(node);
}
}
var lastParentStack = s_lastParentStack;
if (lastParentStack is null
|| !lastParentStack.IsEquivalentTo(provider, buffer, out var resourceNodes))
{
resourceNodes = buffer.ToArray();
if (lastParentStack is null)
{
lastParentStack = new LastParentStack();
s_lastParentStack = lastParentStack;
}
lastParentStack.Set(provider, resourceNodes);
}
if(typeof(T) == typeof(Control))
return new TemplateResult<Control>((Control)obj, scope);
buffer.Clear();
return resourceNodes;
}
// Parent resource nodes are often the same (e.g. most values in a ResourceDictionary), cache the last ones.
private sealed class LastParentStack
{
private readonly WeakReference<IAvaloniaXamlIlParentStackProvider?> _parentStackProvider = new(null);
private readonly WeakReference<IResourceNode[]?> _resourceNodes = new(null);
public void Set(IAvaloniaXamlIlParentStackProvider parentStackProvider, IResourceNode[] resourceNodes)
{
_parentStackProvider.SetTarget(parentStackProvider);
_resourceNodes.SetTarget(resourceNodes);
}
public bool IsEquivalentTo(
IAvaloniaXamlIlParentStackProvider parentStackProvider,
List<IResourceNode> resourceNodes,
[NotNullWhen(true)] out IResourceNode[]? cachedResourceNodes)
{
if (!_parentStackProvider.TryGetTarget(out var lastParentStackProvider)
|| !_resourceNodes.TryGetTarget(out var lastResourceNodes)
|| parentStackProvider != lastParentStackProvider
|| resourceNodes.Count != lastResourceNodes.Length)
{
cachedResourceNodes = null;
return false;
}
#if NET6_0_OR_GREATER
if (!CollectionsMarshal.AsSpan(resourceNodes).SequenceEqual(lastResourceNodes))
{
cachedResourceNodes = null;
return false;
}
#else
for (var i = 0; i < lastResourceNodes.Length; ++i)
{
if (lastResourceNodes[i] != resourceNodes[i])
{
cachedResourceNodes = null;
return false;
}
}
#endif
cachedResourceNodes = lastResourceNodes;
return true;
}
}
private abstract class DeferredContent<T> : IDeferredContent
{
private readonly INameScope? _parentNameScope;
private readonly object _rootObject;
private readonly IResourceNode[] _parentResourceNodes;
protected DeferredContent(
IResourceNode[] parentResourceNodes,
object rootObject,
INameScope? parentNameScope)
{
_parentNameScope = parentNameScope;
_parentResourceNodes = parentResourceNodes;
_rootObject = rootObject;
}
public object Build(IServiceProvider? serviceProvider)
{
INameScope scope = _parentNameScope is null ? new NameScope() : new ChildNameScope(_parentNameScope);
var obj = InvokeBuilder(new DeferredParentServiceProvider(serviceProvider, _parentResourceNodes, _rootObject, scope));
scope.Complete();
return new TemplateResult<T>((T)obj, scope);
};
}
protected abstract object InvokeBuilder(IServiceProvider serviceProvider);
}
private sealed unsafe class PointerDeferredContent<T> : DeferredContent<T>
{
private readonly delegate*<IServiceProvider, object> _builder;
public PointerDeferredContent(
IResourceNode[] parentResourceNodes,
object rootObject,
INameScope? parentNameScope,
delegate*<IServiceProvider, object> builder)
: base(parentResourceNodes, rootObject, parentNameScope)
=> _builder = builder;
protected override object InvokeBuilder(IServiceProvider serviceProvider)
=> _builder(serviceProvider);
}
private class DeferredParentServiceProvider :
IAvaloniaXamlIlParentStackProvider,
private sealed class DelegateDeferredContent<T> : DeferredContent<T>
{
private readonly Func<IServiceProvider, object> _builder;
public DelegateDeferredContent(
IResourceNode[] parentResourceNodes,
object rootObject,
INameScope? parentNameScope,
Func<IServiceProvider, object> builder)
: base(parentResourceNodes, rootObject, parentNameScope)
=> _builder = builder;
protected override object InvokeBuilder(IServiceProvider serviceProvider)
=> _builder(serviceProvider);
}
private sealed class DeferredParentServiceProvider :
IAvaloniaXamlIlEagerParentStackProvider,
IServiceProvider,
IRootObjectProvider,
IAvaloniaXamlIlControlTemplateProvider
{
private readonly IServiceProvider? _parentProvider;
private readonly List<IResourceNode>? _parentResourceNodes;
private readonly IResourceNode[] _parentResourceNodes;
private readonly INameScope _nameScope;
private IRuntimePlatform? _runtimePlatform;
private Optional<IAvaloniaXamlIlEagerParentStackProvider?> _parentStackProvider;
public DeferredParentServiceProvider(IServiceProvider? parentProvider, List<IResourceNode>? parentResourceNodes,
object rootObject, INameScope nameScope)
public DeferredParentServiceProvider(
IServiceProvider? parentProvider,
IResourceNode[] parentResourceNodes,
object rootObject,
INameScope nameScope)
{
_parentProvider = parentProvider;
_parentResourceNodes = parentResourceNodes;
@ -61,16 +224,35 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
RootObject = rootObject;
}
public IEnumerable<object> Parents => GetParents();
public object RootObject { get; }
public object IntermediateRootObject
=> RootObject;
IEnumerable<object> GetParents()
public IEnumerable<object> Parents
=> _parentResourceNodes;
public IReadOnlyList<object> DirectParents
=> _parentResourceNodes;
public IAvaloniaXamlIlEagerParentStackProvider? ParentProvider
{
if(_parentResourceNodes == null)
yield break;
foreach (var p in _parentResourceNodes)
yield return p;
get
{
if (!_parentStackProvider.HasValue)
{
_parentStackProvider =
new Optional<IAvaloniaXamlIlEagerParentStackProvider?>(GetParentStackProviderFromServices());
}
return _parentStackProvider.GetValueOrDefault();
}
}
private IAvaloniaXamlIlEagerParentStackProvider? GetParentStackProviderFromServices()
=> _parentProvider?.GetService<IAvaloniaXamlIlParentStackProvider>()
as IAvaloniaXamlIlEagerParentStackProvider;
public object? GetService(Type serviceType)
{
if (serviceType == typeof(INameScope))
@ -82,19 +264,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
if (serviceType == typeof(IAvaloniaXamlIlControlTemplateProvider))
return this;
if (serviceType == typeof(IRuntimePlatform))
{
if(_runtimePlatform == null)
_runtimePlatform = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
return _runtimePlatform;
}
return _runtimePlatform ??= AvaloniaLocator.Current.GetService<IRuntimePlatform>();
return _parentProvider?.GetService(serviceType);
}
public object RootObject { get; }
public object IntermediateRootObject => RootObject;
}
public static void ApplyNonMatchingMarkupExtensionV1(object target, object property, IServiceProvider prov,
object value)
{
@ -119,7 +293,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public static IServiceProvider CreateInnerServiceProviderV1(IServiceProvider compiled)
=> new InnerServiceProvider(compiled);
private class InnerServiceProvider : IServiceProvider
private sealed class InnerServiceProvider : IServiceProvider
{
private readonly IServiceProvider _compiledProvider;
private XamlTypeResolver? _resolver;
@ -137,7 +311,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
}
}
private class XamlTypeResolver : IXamlTypeResolver
private sealed class XamlTypeResolver : IXamlTypeResolver
{
private readonly IAvaloniaXamlIlXmlNamespaceInfoProvider _nsInfo;
@ -182,11 +356,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
}
#line default
private class RootServiceProvider : IServiceProvider
private sealed class RootServiceProvider : IServiceProvider
{
private readonly INameScope _nameScope;
private readonly IServiceProvider? _parentServiceProvider;
private readonly IRuntimePlatform? _runtimePlatform;
private IAvaloniaXamlIlParentStackProvider? _parentStackProvider;
public RootServiceProvider(INameScope nameScope, IServiceProvider? parentServiceProvider)
{
@ -200,26 +375,98 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
if (serviceType == typeof(INameScope))
return _nameScope;
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return _parentServiceProvider?.GetService<IAvaloniaXamlIlParentStackProvider>()
?? DefaultAvaloniaXamlIlParentStackProvider.Instance;
return _parentStackProvider ??= CreateParentStackProvider();
if (serviceType == typeof(IRuntimePlatform))
return _runtimePlatform ?? throw new KeyNotFoundException($"{nameof(IRuntimePlatform)} was not registered");
return null;
}
private class DefaultAvaloniaXamlIlParentStackProvider : IAvaloniaXamlIlParentStackProvider
private IAvaloniaXamlIlParentStackProvider CreateParentStackProvider()
=> _parentServiceProvider?.GetService<IAvaloniaXamlIlParentStackProvider>()
?? GetParentStackProviderForApplication(Application.Current);
private static IAvaloniaXamlIlEagerParentStackProvider GetParentStackProviderForApplication(Application? application)
=> application is null ?
EmptyAvaloniaXamlIlParentStackProvider.Instance :
ApplicationAvaloniaXamlIlParentStackProvider.GetForApplication(application);
private sealed class ApplicationAvaloniaXamlIlParentStackProvider :
IAvaloniaXamlIlEagerParentStackProvider,
IReadOnlyList<object>
{
public static DefaultAvaloniaXamlIlParentStackProvider Instance { get; } = new();
private static ApplicationAvaloniaXamlIlParentStackProvider? s_lastProvider;
private static Application? s_lastApplication;
public static ApplicationAvaloniaXamlIlParentStackProvider GetForApplication(Application application)
{
if (application != s_lastApplication)
{
s_lastProvider = new ApplicationAvaloniaXamlIlParentStackProvider(application);
s_lastApplication = application;
}
return s_lastProvider!;
}
private readonly Application _application;
private IEnumerable<object>? _parents;
public ApplicationAvaloniaXamlIlParentStackProvider(Application application)
=> _application = application;
public IEnumerable<object> Parents
=> _parents ??= new object[] { _application };
public IReadOnlyList<object> DirectParents
=> this;
public int Count
=> 1;
public IEnumerator<object> GetEnumerator()
=> Parents.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public object this[int index]
{
get
{
if (Application.Current != null)
yield return Application.Current;
if (index != 0)
ThrowArgumentOutOfRangeException();
return _application;
}
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
[SuppressMessage("ReSharper", "NotResolvedInText")]
private static void ThrowArgumentOutOfRangeException()
=> throw new ArgumentOutOfRangeException("index");
public IAvaloniaXamlIlEagerParentStackProvider? ParentProvider
=> null;
}
private sealed class EmptyAvaloniaXamlIlParentStackProvider : IAvaloniaXamlIlEagerParentStackProvider
{
public static EmptyAvaloniaXamlIlParentStackProvider Instance { get; } = new();
private EmptyAvaloniaXamlIlParentStackProvider()
{
}
public IEnumerable<object> Parents
=> Array.Empty<object>();
public IReadOnlyList<object> DirectParents
=> Array.Empty<object>();
public IAvaloniaXamlIlEagerParentStackProvider? ParentProvider
=> null;
}
}
}

7
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

@ -326,7 +326,10 @@ public class StyleIncludeTests
}
}
public class TestServiceProvider : IServiceProvider, IUriContext, IAvaloniaXamlIlParentStackProvider
public class TestServiceProvider :
IServiceProvider,
IUriContext,
IAvaloniaXamlIlEagerParentStackProvider
{
private IServiceProvider _root = XamlIlRuntimeHelpers.CreateRootServiceProviderV2();
public object GetService(Type serviceType)
@ -345,4 +348,6 @@ public class TestServiceProvider : IServiceProvider, IUriContext, IAvaloniaXamlI
public Uri BaseUri { get; set; }
public List<object> Parents { get; set; } = new List<object> { new ContentControl() };
IEnumerable<object> IAvaloniaXamlIlParentStackProvider.Parents => Parents;
public IReadOnlyList<object> DirectParents => Parents;
public IAvaloniaXamlIlEagerParentStackProvider ParentProvider => null;
}

Loading…
Cancel
Save