From 6bd0d2818c5e0c759d3a2729566ad2c0fced2689 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 29 Jun 2022 13:40:10 +0200 Subject: [PATCH 1/8] Add deferred support to ResourceDictionary. --- .../Controls/ResourceDictionary.cs | 40 +++++++++++++++---- .../Controls/Templates/ITemplateResult.cs | 8 ++++ .../Controls}/Templates/TemplateResult.cs | 3 +- 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 src/Avalonia.Base/Controls/Templates/ITemplateResult.cs rename src/{Avalonia.Controls => Avalonia.Base/Controls}/Templates/TemplateResult.cs (80%) diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index 77863e5101..e3a8bbbcbc 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using Avalonia.Collections; +using Avalonia.Controls.Templates; namespace Avalonia.Controls { @@ -29,7 +30,11 @@ namespace Avalonia.Controls public object? this[object key] { - get => _inner?[key]; + get + { + TryGetValue(key, out var value); + return value; + } set { Inner[key] = value; @@ -119,6 +124,12 @@ namespace Avalonia.Controls Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); } + public void AddDeferred(object key, Func factory) + { + Inner.Add(key, new DeferredItem(factory)); + Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); + } + public void Clear() { if (_inner?.Count > 0) @@ -143,10 +154,8 @@ namespace Avalonia.Controls public bool TryGetResource(object key, out object? value) { - if (_inner is not null && _inner.TryGetValue(key, out value)) - { + if (TryGetValue(key, out value)) return true; - } if (_mergedDictionaries != null) { @@ -165,13 +174,24 @@ namespace Avalonia.Controls public bool TryGetValue(object key, out object? value) { - if (_inner is not null) - return _inner.TryGetValue(key, out value); + if (_inner is not null && _inner.TryGetValue(key, out value)) + { + if (value is DeferredItem deffered) + { + _inner[key] = value = deffered.Factory(null) switch + { + ITemplateResult t => t.Result, + object v => v, + _ => null, + }; + } + return true; + } + value = null; return false; } - void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); @@ -258,5 +278,11 @@ namespace Avalonia.Controls } } } + + private class DeferredItem + { + public DeferredItem(Func factory) => Factory = factory; + public Func Factory { get; } + } } } diff --git a/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs b/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs new file mode 100644 index 0000000000..6bd4d735a7 --- /dev/null +++ b/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Controls.Templates +{ + public interface ITemplateResult + { + public object? Result { get; } + public INameScope NameScope { get; } + } +} diff --git a/src/Avalonia.Controls/Templates/TemplateResult.cs b/src/Avalonia.Base/Controls/Templates/TemplateResult.cs similarity index 80% rename from src/Avalonia.Controls/Templates/TemplateResult.cs rename to src/Avalonia.Base/Controls/Templates/TemplateResult.cs index 770aecc329..0e38c6c0ce 100644 --- a/src/Avalonia.Controls/Templates/TemplateResult.cs +++ b/src/Avalonia.Base/Controls/Templates/TemplateResult.cs @@ -1,9 +1,10 @@ namespace Avalonia.Controls.Templates { - public class TemplateResult + public class TemplateResult : ITemplateResult { public T Result { get; } public INameScope NameScope { get; } + object? ITemplateResult.Result => Result; public TemplateResult(T result, INameScope nameScope) { From 5c6eeed4fae39ec31824c9dedb94183aee143e5d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 29 Jun 2022 14:01:41 +0200 Subject: [PATCH 2/8] Initial support for deferred resources in XAML compiler. --- .../Controls/ResourceDictionary.cs | 18 +++- .../AvaloniaXamlIlCompiler.cs | 4 + ...aloniaXamlIlDeferredResourceTransformer.cs | 80 +++++++++++++++ .../AvaloniaXamlIlWellKnownTypes.cs | 9 ++ .../Xaml/ResourceDictionaryTests.cs | 98 +++++++++++++++++++ 5 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index e3a8bbbcbc..d6197c50c6 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -192,6 +192,11 @@ namespace Avalonia.Controls return false; } + public IEnumerator> GetEnumerator() + { + return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator(); + } + void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); @@ -218,12 +223,17 @@ namespace Avalonia.Controls return false; } - public IEnumerator> GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + internal bool ContainsDeferredKey(object key) { - return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator(); - } + if (_inner is not null && _inner.TryGetValue(key, out var result)) + { + return result is DeferredItem; + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + return false; + } void IResourceProvider.AddOwner(IResourceHost owner) { diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 04a61e5f10..fac053df14 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -59,6 +59,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertAfter( new XDataTypeTransformer()); + InsertBefore( + new AvaloniaXamlIlDeferredResourceTransformer() + ); + // After everything else InsertBefore( new AddNameScopeRegistration(), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs new file mode 100644 index 0000000000..099878df08 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -0,0 +1,80 @@ +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 +{ + internal class AvaloniaXamlIlDeferredResourceTransformer : 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], types.XamlIlTypes.Object, context.Configuration); + pa.PossibleSetters = new List + { + new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd), + }; + } + else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary)) + { + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); + pa.PossibleSetters = new List + { + new AdderSetter(pa.Property.Getter, types.ResourceDictionaryDeferredAdd), + }; + } + + return node; + } + + class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + { + 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 Parameters { get; } + public void Emit(IXamlILEmitter emitter) + { + var locals = new Stack(); + // 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); + } + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 28787d9b84..330d836f49 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -96,6 +96,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType TextDecorations { get; } public IXamlType TextTrimming { get; } public IXamlType ISetter { get; } + public IXamlType IResourceDictionary { get; } + public IXamlType ResourceDictionary { get; } + public IXamlMethod ResourceDictionaryDeferredAdd { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -210,6 +213,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations"); TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); + 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)); } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 578fa888a3..939c5319e4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -66,6 +66,104 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void Item_Is_Added_To_ResourceDictionary_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + Red +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + + [Fact] + public void Item_Is_Added_To_Window_Resources_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + Red + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)window.Resources; + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + + [Fact] + public void Item_Is_Added_To_Window_MergedDictionaries_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + + + + Red + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)window.Resources.MergedDictionaries[0]; + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + + [Fact] + public void Item_Is_Added_To_Style_Resources_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" +"; + var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)style.Resources; + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + + [Fact] + public void Item_Is_Added_To_Styles_Resources_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + Red + +"; + var style = (Styles)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)style.Resources; + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From ccdc11d0accad930a11996546d4cef9af41d191b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 30 Jun 2022 15:34:48 +0200 Subject: [PATCH 3/8] Fix missing subscribe if owner is already set. --- src/Avalonia.Base/Controls/ResourceNodeExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs index 1758c45650..6121646107 100644 --- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs @@ -132,6 +132,11 @@ namespace Avalonia.Controls { _target.OwnerChanged += OwnerChanged; _owner = _target.Owner; + + if (_owner is object) + { + _owner.ResourcesChanged += ResourcesChanged; + } } protected override void Deinitialize() From 705e9065a3b677359a9db1fadd7665dec96bb050 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 02:28:05 -0400 Subject: [PATCH 4/8] Add more tests for lazy resources --- .../Xaml/ResourceDictionaryTests.cs | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 939c5319e4..74b6a1e15d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -74,13 +74,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @" - Red + "; var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); Assert.True(resources.ContainsDeferredKey("Red")); } } + + [Fact] + public void Item_Added_To_ResourceDictionary_Is_UnDeferred_On_Read() + { + using (StyledWindow()) + { + var xaml = @" + + +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.True(resources.ContainsDeferredKey("Red")); + + Assert.IsType(resources["Red"]); + + Assert.False(resources.ContainsDeferredKey("Red")); + } + } [Fact] public void Item_Is_Added_To_Window_Resources_As_Deferred() @@ -91,7 +111,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -113,7 +133,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + @@ -135,7 +155,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml "; var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -154,7 +174,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + "; var style = (Styles)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -164,6 +184,60 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void Item_Can_Be_StaticReferenced_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var windowResources = (ResourceDictionary)window.Resources; + var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources; + + Assert.True(windowResources.ContainsDeferredKey("Red")); + Assert.True(buttonResources.ContainsDeferredKey("Red2")); + } + } + + [Fact] + public void Item_StaticReferenced_Is_UnDeferred_On_Read() + { + using (StyledWindow()) + { + var xaml = @" + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var windowResources = (ResourceDictionary)window.Resources; + var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources; + + Assert.IsType(buttonResources["Red2"]); + + Assert.False(windowResources.ContainsDeferredKey("Red")); + Assert.False(buttonResources.ContainsDeferredKey("Red2")); + } + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From ad518ac6787ab8265adcd1a4eddc70c390590ca6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 3 Aug 2022 15:00:42 +0300 Subject: [PATCH 5/8] Add backpressure for sending batches to the render thread --- .../Rendering/Composition/CompositingRenderer.cs | 2 +- src/Avalonia.Base/Rendering/Composition/Compositor.cs | 11 ++++++++++- .../Rendering/Composition/Server/ServerCompositor.cs | 3 +-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index db773bc43c..9aa3c25425 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -71,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor if(_queuedUpdate) return; _queuedUpdate = true; - Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); + _compositor.InvokeWhenReadyForNextCommit(_update); } /// diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index 45212d0f36..f812acc016 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -33,6 +33,7 @@ namespace Avalonia.Rendering.Composition internal IEasing DefaultEasing { get; } private List? _invokeOnNextCommit; private readonly Stack> _invokeListPool = new(); + private Task? _lastBatchCompleted; /// /// Creates a new compositor on a specified render loop that would use a particular GPU @@ -86,7 +87,7 @@ namespace Avalonia.Rendering.Composition if (_invokeOnNextCommit != null) ScheduleCommitCallbacks(batch.Completed); - return batch.Completed; + return _lastBatchCompleted = batch.Completed; } async void ScheduleCommitCallbacks(Task task) @@ -139,5 +140,13 @@ namespace Avalonia.Rendering.Composition _invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new(); _invokeOnNextCommit.Add(action); } + + public void InvokeWhenReadyForNextCommit(Action action) + { + if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted) + Dispatcher.UIThread.Post(action, DispatcherPriority.Composition); + else + _lastBatchCompleted.ContinueWith(_ => Dispatcher.UIThread.Post(action, DispatcherPriority.Composition)); + } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index 621bc84f4a..bfc2b2d626 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -108,6 +108,7 @@ namespace Avalonia.Rendering.Composition.Server private void RenderCore() { ApplyPendingBatches(); + CompletePendingBatches(); foreach(var animation in _activeAnimations) _animationsToUpdate.Add(animation); @@ -119,8 +120,6 @@ namespace Avalonia.Rendering.Composition.Server foreach (var t in _activeTargets) t.Render(); - - CompletePendingBatches(); } public void AddCompositionTarget(ServerCompositionTarget target) From 0d472a100b6fc191d69675512028c4e770045081 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 3 Aug 2022 15:57:13 +0300 Subject: [PATCH 6/8] Use static callback for ContinueWith --- src/Avalonia.Base/Rendering/Composition/Compositor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index f812acc016..10360f7874 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -146,7 +146,9 @@ namespace Avalonia.Rendering.Composition if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted) Dispatcher.UIThread.Post(action, DispatcherPriority.Composition); else - _lastBatchCompleted.ContinueWith(_ => Dispatcher.UIThread.Post(action, DispatcherPriority.Composition)); + _lastBatchCompleted.ContinueWith( + static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition), + action); } } } From 6520722e5488350433e525232877e139ad57cf53 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 03:20:37 -0400 Subject: [PATCH 7/8] Attempt to not deferr value types --- ...valoniaXamlIlDeferredResourceTransformer.cs | 10 ++++++++++ .../Xaml/ResourceDictionaryTests.cs | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 099878df08..662263e513 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -15,6 +15,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) return node; + if (!ShouldBeDeferred(pa.Values[1])) + return node; + var types = context.GetAvaloniaTypes(); if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") @@ -37,6 +40,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return node; } + private static bool ShouldBeDeferred(IXamlAstValueNode node) + { + // XAML compiler is currently strict about value types, allowing them to be created only through converters. + // At the moment it should be safe to not defer structs. + return !node.Type.GetClrType().IsValueType; + } + class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter { private readonly IXamlMethod _getter; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 74b6a1e15d..19fb88b391 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -237,6 +238,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.False(buttonResources.ContainsDeferredKey("Red2")); } } + + [Fact] + public void Value_Type_With_Parse_Should_Not_Be_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + Red +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.False(resources.ContainsDeferredKey("Red")); + Assert.IsType(resources["Red"]); + } + } private IDisposable StyledWindow(params (string, string)[] assets) { From 41d31d8857dec976aa293d8070ea69296f84c2d1 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 03:53:01 -0400 Subject: [PATCH 8/8] Add thickness test as well --- .../Xaml/ResourceDictionaryTests.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 19fb88b391..5066341bbb 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -240,7 +240,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } [Fact] - public void Value_Type_With_Parse_Should_Not_Be_Deferred() + public void Value_Type_With_Parse_Converter_Should_Not_Be_Deferred() { using (StyledWindow()) { @@ -255,6 +255,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.IsType(resources["Red"]); } } + + [Fact] + public void Value_Type_With_Ctor_Converter_Should_Not_Be_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + 1 1 1 1 +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.False(resources.ContainsDeferredKey("Margin")); + Assert.IsType(resources["Margin"]); + } + } private IDisposable StyledWindow(params (string, string)[] assets) {