Browse Source

Set capacity of XAML resource dictionaries (#15069)

pull/15124/head
Julien Lebosquain 3 years ago
committed by GitHub
parent
commit
bb6005b324
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 18
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  2. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  3. 13
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
  4. 142
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer.cs
  5. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

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

@ -258,6 +258,24 @@ namespace Avalonia.Controls
return false;
}
/// <summary>
/// Ensures that the resource dictionary can hold up to <paramref name="capacity"/> entries without
/// any further expansion of its backing storage.
/// </summary>
/// <remarks>This method may have no effect when targeting .NET Standard 2.0.</remarks>
public void EnsureCapacity(int capacity)
{
if (_inner is null)
{
_inner = new(capacity);
return;
}
#if !NETSTANDARD2_0
Inner.EnsureCapacity(capacity);
#endif
}
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator()
{
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator();

1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -88,6 +88,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer());
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
Transformers.Add(new AvaloniaXamlIlRootObjectScope());
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());

13
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs

@ -162,16 +162,27 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
&& ThemeVariantNodeEquals(context, key, sameKeyPrevAssignmentNode.Values[0]))
{
sameKeyPrevValueGroup.Children.AddRange(valueGroup.Children);
FixEnsureCapacityNodes(context, sameKeyPrevValueGroup);
children.RemoveAt(i);
break;
}
}
}
}
FixEnsureCapacityNodes(context, resourceDictionaryManipulation);
return node;
}
// // Removes all existing EnsureCapacityNode (from the merged dictionaries) and adds a new one.
private static void FixEnsureCapacityNodes(AstGroupTransformationContext context, XamlManipulationGroupNode manipulation)
{
var children = manipulation.Children;
children.RemoveAll(c => c is AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer.EnsureCapacityNode);
new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer().Apply(context, manipulation);
}
public static bool ThemeVariantNodeEquals(AstGroupTransformationContext context, IXamlAstValueNode left, IXamlAstValueNode right)
{
if (left is XamlConstantNode leftConst

142
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer.cs

@ -0,0 +1,142 @@
#nullable enable
using System.Collections.Generic;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
/// <summary>
/// Adds a call to EnsureCapacity before adding items to a ResourceDictionary.
/// </summary>
internal sealed class AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer : IXamlAstTransformer
{
private readonly HashSet<XamlManipulationGroupNode> _processedGroups = new();
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlManipulationGroupNode group)
Apply(context, group);
return node;
}
public void Apply(AstTransformationContext context, XamlManipulationGroupNode group)
{
var info = GetResourcesInfo(group, context.GetAvaloniaTypes());
if (info.Mode != ResourcesMode.None && info.Count >= 2)
group.Children.Insert(0, new EnsureCapacityNode(group, info.Count, info.ResourcesGetter));
}
private ResourcesInfo GetResourcesInfo(IXamlAstManipulationNode node, AvaloniaXamlIlWellKnownTypes types)
=> node switch
{
XamlPropertyAssignmentNode propertyAssignment => GetResourcesInfo(propertyAssignment, types),
XamlManipulationGroupNode group => GetResourcesInfo(group, types),
_ => default
};
private ResourcesInfo GetResourcesInfo(XamlManipulationGroupNode node, AvaloniaXamlIlWellKnownTypes types)
{
if (!_processedGroups.Add(node))
return default;
ResourcesInfo groupInfo = default;
foreach (var child in node.Children)
{
var childInfo = GetResourcesInfo(child, types);
if (childInfo.Mode == ResourcesMode.None)
continue;
if (groupInfo.Mode == ResourcesMode.None)
groupInfo = new ResourcesInfo(childInfo.Mode, childInfo.ResourcesGetter);
else if (groupInfo.Mode != childInfo.Mode || groupInfo.ResourcesGetter != childInfo.ResourcesGetter)
return default;
groupInfo.Count += childInfo.Count;
}
return groupInfo;
}
private static ResourcesInfo GetResourcesInfo(XamlPropertyAssignmentNode node, AvaloniaXamlIlWellKnownTypes types)
=> node.Property.Name switch
{
"Content" when node.Property.DeclaringType == types.ResourceDictionary
=> new ResourcesInfo(ResourcesMode.ResourceDictionaryContent, null) { Count = 1 },
"Resources" when types.IResourceDictionary.IsAssignableFrom(node.Property.Getter.ReturnType)
=> new ResourcesInfo(ResourcesMode.ElementResources, node.Property.Getter) { Count = 1 },
_
=> default
};
private struct ResourcesInfo
{
public ResourcesMode Mode { get; }
public IXamlMethod? ResourcesGetter { get; }
public int Count { get; set; }
public ResourcesInfo(ResourcesMode mode, IXamlMethod? resourcesGetter)
{
Mode = mode;
ResourcesGetter = resourcesGetter;
}
}
private enum ResourcesMode
{
None,
ResourceDictionaryContent,
ElementResources
}
public sealed class EnsureCapacityNode : XamlAstNode, IXamlAstManipulationNode, IXamlAstILEmitableNode
{
private readonly int _capacity;
private readonly IXamlMethod? _resourcesGetter;
public EnsureCapacityNode(IXamlLineInfo lineInfo, int capacity, IXamlMethod? resourcesGetter)
: base(lineInfo)
{
_capacity = capacity;
_resourcesGetter = resourcesGetter;
}
public XamlILNodeEmitResult Emit(
XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
var types = context.GetAvaloniaTypes();
var exitLabel = codeGen.DefineLabel();
using var local = codeGen.LocalsPool.GetLocal(types.ResourceDictionary);
if (_resourcesGetter is not null)
codeGen.EmitCall(_resourcesGetter);
codeGen
// if (value is not ResourceDictionary local) goto exit;
.Isinst(types.ResourceDictionary)
.Stloc(local.Local)
.Ldloc(local.Local)
.Brfalse(exitLabel)
// local.EnsureCapacity(local.Count + `_capacity`);
.Ldloc(local.Local)
.Dup()
.EmitCall(types.ResourceDictionaryGetCount)
.Ldc_I4(_capacity)
.Add()
.EmitCall(types.ResourceDictionaryEnsureCapacity)
// exit:
.MarkLabel(exitLabel);
return XamlILNodeEmitResult.Void(1);
}
}
}

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

@ -114,6 +114,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public IXamlMethod ResourceDictionaryEnsureCapacity { get; }
public IXamlMethod ResourceDictionaryGetCount { get; }
public IXamlType IThemeVariantProvider { get; }
public IXamlType UriKind { get; }
public IXamlConstructor UriConstructor { get; }
@ -262,6 +264,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
ResourceDictionaryEnsureCapacity = ResourceDictionary.FindMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32);
ResourceDictionaryGetCount = ResourceDictionary.FindMethod("get_Count", XamlIlTypes.Int32, true);
IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider");
UriKind = cfg.TypeSystem.GetType("System.UriKind");
UriConstructor = Uri.GetConstructor(new List<IXamlType>() { cfg.WellKnownTypes.String, UriKind });

Loading…
Cancel
Save