Browse Source

WIP: Lazy load resources.

experiment/lazy-load-styles-resources
Steven Kirk 5 years ago
parent
commit
4003cab406
  1. 17
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  2. 9
      src/Avalonia.Build.Tasks/Properties/launchSettings.json
  3. 18
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  4. 50
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  5. 20
      src/Avalonia.Styling/Controls/Templates/TemplateResult.cs
  6. 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  7. 83
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlLazyResourceTransformer.cs
  8. 15
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  9. 22
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

17
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@ -67,11 +67,7 @@ namespace Avalonia.Collections
/// <returns>The resource, or null if not found.</returns> /// <returns>The resource, or null if not found.</returns>
public TValue this[TKey key] public TValue this[TKey key]
{ {
get get => GetItemCore(key);
{
return _inner[key];
}
set set
{ {
TValue old; TValue old;
@ -98,7 +94,11 @@ namespace Avalonia.Collections
} }
} }
object IDictionary.this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; } object IDictionary.this[object key]
{
get => GetItemCore((TKey)key);
set => this[(TKey)key] = (TValue)value;
}
/// <inheritdoc/> /// <inheritdoc/>
public void Add(TKey key, TValue value) public void Add(TKey key, TValue value)
@ -166,7 +166,7 @@ namespace Avalonia.Collections
} }
/// <inheritdoc/> /// <inheritdoc/>
public bool TryGetValue(TKey key, out TValue value) => _inner.TryGetValue(key, out value); public bool TryGetValue(TKey key, out TValue value) => TryGetValueCore(key, out value);
/// <inheritdoc/> /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
@ -204,6 +204,9 @@ namespace Avalonia.Collections
/// <inheritdoc/> /// <inheritdoc/>
void IDictionary.Remove(object key) => Remove((TKey)key); void IDictionary.Remove(object key) => Remove((TKey)key);
protected virtual TValue GetItemCore(TKey key) => _inner[key];
protected virtual bool TryGetValueCore(TKey key, out TValue value) => _inner.TryGetValue(key, out value);
private void NotifyAdd(TKey key, TValue value) private void NotifyAdd(TKey key, TValue value)
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));

9
src/Avalonia.Build.Tasks/Properties/launchSettings.json

@ -0,0 +1,9 @@
{
"profiles": {
"Avalonia.Build.Tasks": {
"commandName": "Executable",
"executablePath": "$(SolutionDir)\\src\\Avalonia.Build.Tasks\\bin\\Debug\\netcoreapp3.1\\Avalonia.Build.Tasks.exe",
"commandLineArgs": "D:\\projects\\AvaloniaUI\\Avalonia\\src\\Avalonia.Themes.Fluent\\obj\\Debug\\netstandard2.0\\Avalonia\\original.dll \"D:\\projects\\AvaloniaUI\\Avalonia\\src\\Avalonia.Themes.Fluent\\obj\\Debug\\netstandard2.0\\Avalonia\\references\" $(SolutionDir)\\out.dll"
}
}
}

18
src/Avalonia.Controls/Templates/IControlTemplate.cs

@ -9,22 +9,4 @@ namespace Avalonia.Controls.Templates
public interface IControlTemplate : ITemplate<ITemplatedControl, TemplateResult<IControl>> public interface IControlTemplate : ITemplate<ITemplatedControl, TemplateResult<IControl>>
{ {
} }
public class TemplateResult<T>
{
public T Result { get; }
public INameScope NameScope { get; }
public TemplateResult(T result, INameScope nameScope)
{
Result = result;
NameScope = nameScope;
}
public void Deconstruct(out T result, out INameScope scope)
{
result = Result;
scope = NameScope;
}
}
} }

50
src/Avalonia.Styling/Controls/ResourceDictionary.cs

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Metadata; using Avalonia.Controls.Templates;
#nullable enable #nullable enable
@ -101,6 +101,11 @@ namespace Avalonia.Controls
public event EventHandler? OwnerChanged; public event EventHandler? OwnerChanged;
public void Add(object key, Func<IServiceProvider?, object> loader)
{
Add(key, new LazyItem(loader));
}
public bool TryGetResource(object key, out object? value) public bool TryGetResource(object key, out object? value)
{ {
if (TryGetValue(key, out value)) if (TryGetValue(key, out value))
@ -176,9 +181,52 @@ namespace Avalonia.Controls
} }
} }
protected override object? GetItemCore(object key)
{
var item = base.GetItemCore(key);
if (item is LazyItem lazy)
{
var value = lazy.Load();
this[key] = value;
return value;
}
else
{
return item;
}
}
protected override bool TryGetValueCore(object key, out object? value)
{
if (base.TryGetValueCore(key, out value))
{
if (value is LazyItem lazy)
{
value = lazy.Load();
this[key] = value;
}
return true;
}
return false;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
} }
private class LazyItem
{
private Func<IServiceProvider?, object?> _load;
public LazyItem(Func<IServiceProvider?, object?> load) => _load = load;
public object? Load()
{
var result = _load(null);
return result is TemplateResult<object> t ? t.Result : result;
}
}
} }
} }

20
src/Avalonia.Styling/Controls/Templates/TemplateResult.cs

@ -0,0 +1,20 @@
namespace Avalonia.Controls.Templates
{
public class TemplateResult<T>
{
public T Result { get; }
public INameScope NameScope { get; }
public TemplateResult(T result, INameScope nameScope)
{
Result = result;
NameScope = nameScope;
}
public void Deconstruct(out T result, out INameScope scope)
{
result = Result;
scope = NameScope;
}
}
}

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

@ -57,13 +57,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer() new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
); );
InsertBefore<DeferredContentTransformer>(
new AvaloniaXamlIlLazyResourceTransformer()
);
// After everything else // After everything else
InsertBefore<NewObjectTransformer>( InsertBefore<NewObjectTransformer>(
new AddNameScopeRegistration(), new AddNameScopeRegistration(),
new AvaloniaXamlIlDataContextTypeTransformer(), new AvaloniaXamlIlDataContextTypeTransformer(),
new AvaloniaXamlIlBindingPathTransformer(), new AvaloniaXamlIlBindingPathTransformer(),
new AvaloniaXamlIlCompiledBindingsMetadataRemover() new AvaloniaXamlIlCompiledBindingsMetadataRemover()
); );
Transformers.Add(new AvaloniaXamlIlMetadataRemover()); Transformers.Add(new AvaloniaXamlIlMetadataRemover());
Transformers.Add(new AvaloniaXamlIlRootObjectScope()); Transformers.Add(new AvaloniaXamlIlRootObjectScope());

83
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlLazyResourceTransformer.cs

@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Linq;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlLazyResourceTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
return node;
var types = context.GetAvaloniaTypes();
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content")
{
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new XamlDirectCallPropertySetter(types.ResourceDictionaryLazyAdd),
};
}
else if ((pa.Property.DeclaringType == types.StyledElement ||
pa.Property.DeclaringType == types.Style ||
pa.Property.DeclaringType == types.Styles) &&
pa.Property.Name == "Resources")
{
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new AdderSetter(pa.Property.Getter, types.ResourceDictionaryLazyAdd),
};
}
return node;
}
class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
private readonly IXamlMethod _getter;
private readonly IXamlMethod _adder;
public AdderSetter(IXamlMethod getter, IXamlMethod adder)
{
_getter = getter;
_adder = adder;
TargetType = getter.DeclaringType;
Parameters = adder.ParametersWithThis().Skip(1).ToList();
}
public IXamlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters
{
AllowMultiple = true
};
public IReadOnlyList<IXamlType> Parameters { get; }
public void Emit(IXamlILEmitter emitter)
{
var locals = new Stack<XamlLocalsPool.PooledLocal>();
// Save all "setter" parameters
for (var c = Parameters.Count - 1; c >= 0; c--)
{
var loc = emitter.LocalsPool.GetLocal(Parameters[c]);
locals.Push(loc);
emitter.Stloc(loc.Local);
}
emitter.EmitCall(_getter);
while (locals.Count>0)
using (var loc = locals.Pop())
emitter.Ldloc(loc.Local);
emitter.EmitCall(_adder, true);
}
}
}
}

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

@ -78,6 +78,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ColumnDefinition { get; } public IXamlType ColumnDefinition { get; }
public IXamlType ColumnDefinitions { get; } public IXamlType ColumnDefinitions { get; }
public IXamlType Classes { get; } public IXamlType Classes { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryLazyAdd { get; }
public IXamlType Style { get; }
public IXamlType Styles { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{ {
@ -168,6 +172,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
RowDefinition = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinition"); RowDefinition = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinition");
RowDefinitions = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinitions"); RowDefinitions = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinitions");
Classes = cfg.TypeSystem.GetType("Avalonia.Controls.Classes"); Classes = cfg.TypeSystem.GetType("Avalonia.Controls.Classes");
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryLazyAdd = ResourceDictionary.FindMethod(
"Add",
XamlIlTypes.Void,
true,
XamlIlTypes.Object,
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");
Styles = cfg.TypeSystem.GetType("Avalonia.Styling.Styles");
} }
} }

22
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

@ -29,6 +29,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
} }
} }
[Fact]
public void Foo()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<Color x:Key='Red'>Red</Color>
<SolidColorBrush x:Key='RedBrush' Color='{StaticResource Red}'/>
</Window.Resources>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = window.Resources;
var brush = (SolidColorBrush)resources["RedBrush"];
Assert.Equal(Colors.Red, brush.Color);
}
}
[Fact] [Fact]
public void DynamicResource_Finds_Resource_In_Parent_Dictionary() public void DynamicResource_Finds_Resource_In_Parent_Dictionary()
{ {

Loading…
Cancel
Save