Browse Source

Merge pull request #7372 from AvaloniaUI/feature/new-weak-events-for-visual

AffectsRender subscription optimization
pull/8023/head
Dariusz Komosiński 4 years ago
committed by GitHub
parent
commit
ad2ae75187
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      src/Avalonia.Base/Media/Pen.cs
  2. 29
      src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs
  3. 138
      src/Avalonia.Base/Utilities/WeakEvent.cs
  4. 236
      src/Avalonia.Base/Utilities/WeakHashList.cs
  5. 21
      src/Avalonia.Base/Visual.cs
  6. 30
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  7. 17
      src/Avalonia.Controls/TopLevel.cs
  8. 45
      tests/Avalonia.Benchmarks/Visuals/VisualAffectsRenderBenchmarks.cs

26
src/Avalonia.Base/Media/Pen.cs

@ -7,7 +7,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Describes how a stroke is drawn. /// Describes how a stroke is drawn.
/// </summary> /// </summary>
public sealed class Pen : AvaloniaObject, IPen, IWeakEventSubscriber<EventArgs> public sealed class Pen : AvaloniaObject, IPen
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Brush"/> property. /// Defines the <see cref="Brush"/> property.
@ -48,7 +48,8 @@ namespace Avalonia.Media
private EventHandler? _invalidated; private EventHandler? _invalidated;
private IAffectsRender? _subscribedToBrush; private IAffectsRender? _subscribedToBrush;
private IAffectsRender? _subscribedToDashes; private IAffectsRender? _subscribedToDashes;
private TargetWeakEventSubscriber<Pen, EventArgs>? _weakSubscriber;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class. /// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary> /// </summary>
@ -207,13 +208,24 @@ namespace Avalonia.Media
{ {
if ((_invalidated == null || field != value) && field != null) if ((_invalidated == null || field != value) && field != null)
{ {
InvalidatedWeakEvent.Unsubscribe(field, this); if (_weakSubscriber != null)
InvalidatedWeakEvent.Unsubscribe(field, _weakSubscriber);
field = null; field = null;
} }
if (_invalidated != null && field != value && value is IAffectsRender affectsRender) if (_invalidated != null && field != value && value is IAffectsRender affectsRender)
{ {
InvalidatedWeakEvent.Subscribe(affectsRender, this); if (_weakSubscriber == null)
{
_weakSubscriber = new TargetWeakEventSubscriber<Pen, EventArgs>(
this, static (target, _, ev, _) =>
{
if (ev == InvalidatedWeakEvent)
target._invalidated?.Invoke(target, EventArgs.Empty);
});
}
InvalidatedWeakEvent.Subscribe(affectsRender, _weakSubscriber);
field = affectsRender; field = affectsRender;
} }
} }
@ -223,11 +235,5 @@ namespace Avalonia.Media
UpdateSubscription(ref _subscribedToBrush, Brush); UpdateSubscription(ref _subscribedToBrush, Brush);
UpdateSubscription(ref _subscribedToDashes, DashStyle); UpdateSubscription(ref _subscribedToDashes, DashStyle);
} }
void IWeakEventSubscriber<EventArgs>.OnEvent(object? sender, WeakEvent ev, EventArgs e)
{
if (ev == InvalidatedWeakEvent)
_invalidated?.Invoke(this, EventArgs.Empty);
}
} }
} }

29
src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs

@ -9,4 +9,31 @@ namespace Avalonia.Utilities;
public interface IWeakEventSubscriber<in TEventArgs> where TEventArgs : EventArgs public interface IWeakEventSubscriber<in TEventArgs> where TEventArgs : EventArgs
{ {
void OnEvent(object? sender, WeakEvent ev, TEventArgs e); void OnEvent(object? sender, WeakEvent ev, TEventArgs e);
} }
public sealed class WeakEventSubscriber<TEventArgs> : IWeakEventSubscriber<TEventArgs> where TEventArgs : EventArgs
{
public event Action<object?, WeakEvent, TEventArgs>? Event;
void IWeakEventSubscriber<TEventArgs>.OnEvent(object? sender, WeakEvent ev, TEventArgs e)
{
Event?.Invoke(sender, ev, e);
}
}
public sealed class TargetWeakEventSubscriber<TTarget, TEventArgs> : IWeakEventSubscriber<TEventArgs> where TEventArgs : EventArgs
{
private readonly TTarget _target;
private readonly Action<TTarget, object?, WeakEvent, TEventArgs> _dispatchFunc;
public TargetWeakEventSubscriber(TTarget target, Action<TTarget, object?, WeakEvent, TEventArgs> dispatchFunc)
{
_target = target;
_dispatchFunc = dispatchFunc;
}
void IWeakEventSubscriber<TEventArgs>.OnEvent(object? sender, WeakEvent ev, TEventArgs e)
{
_dispatchFunc(_target, sender, ev, e);
}
}

138
src/Avalonia.Base/Utilities/WeakEvent.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -36,7 +37,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
{ {
if (!_subscriptions.TryGetValue(target, out var subscription)) if (!_subscriptions.TryGetValue(target, out var subscription))
_subscriptions.Add(target, subscription = new Subscription(this, target)); _subscriptions.Add(target, subscription = new Subscription(this, target));
subscription.Add(new WeakReference<IWeakEventSubscriber<TEventArgs>>(subscriber)); subscription.Add(subscriber);
} }
public void Unsubscribe(TSender target, IWeakEventSubscriber<TEventArgs> subscriber) public void Unsubscribe(TSender target, IWeakEventSubscriber<TEventArgs> subscriber)
@ -51,11 +52,59 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
private readonly TSender _target; private readonly TSender _target;
private readonly Action _compact; private readonly Action _compact;
private WeakReference<IWeakEventSubscriber<TEventArgs>>?[] _data = struct Entry
new WeakReference<IWeakEventSubscriber<TEventArgs>>[16]; {
private int _count; WeakReference<IWeakEventSubscriber<TEventArgs>>? _reference;
int _hashCode;
public Entry(IWeakEventSubscriber<TEventArgs> r)
{
if (r == null)
{
_reference = null;
_hashCode = 0;
return;
}
_hashCode = r.GetHashCode();
_reference = new WeakReference<IWeakEventSubscriber<TEventArgs>>(r);
}
public bool IsEmpty
{
get
{
if (_reference == null)
return true;
if (_reference.TryGetTarget(out _))
return false;
_reference = null;
return true;
}
}
public bool TryGetTarget([MaybeNullWhen(false)]out IWeakEventSubscriber<TEventArgs> target)
{
if (_reference == null)
{
target = null!;
return false;
}
return _reference.TryGetTarget(out target);
}
public bool Equals(IWeakEventSubscriber<TEventArgs> r)
{
if (_reference == null || r.GetHashCode() != _hashCode)
return false;
return _reference.TryGetTarget(out var target) && target == r;
}
}
private readonly Action _unsubscribe; private readonly Action _unsubscribe;
private readonly WeakHashList<IWeakEventSubscriber<TEventArgs>> _list = new();
private bool _compactScheduled; private bool _compactScheduled;
private bool _destroyed;
public Subscription(WeakEvent<TSender, TEventArgs> ev, TSender target) public Subscription(WeakEvent<TSender, TEventArgs> ev, TSender target)
{ {
@ -67,48 +116,27 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
void Destroy() void Destroy()
{ {
if(_destroyed)
return;
_destroyed = true;
_unsubscribe(); _unsubscribe();
_ev._subscriptions.Remove(_target); _ev._subscriptions.Remove(_target);
} }
public void Add(WeakReference<IWeakEventSubscriber<TEventArgs>> s) public void Add(IWeakEventSubscriber<TEventArgs> s) => _list.Add(s);
{
if (_count == _data.Length)
{
//Extend capacity
var extendedData = new WeakReference<IWeakEventSubscriber<TEventArgs>>?[_data.Length * 2];
Array.Copy(_data, extendedData, _data.Length);
_data = extendedData;
}
_data[_count] = s;
_count++;
}
public void Remove(IWeakEventSubscriber<TEventArgs> s) public void Remove(IWeakEventSubscriber<TEventArgs> s)
{ {
var removed = false; _list.Remove(s);
if(_list.IsEmpty)
for (int c = 0; c < _count; ++c) Destroy();
{ else if(_list.NeedCompact && _compactScheduled)
var reference = _data[c];
if (reference != null && reference.TryGetTarget(out var instance) && instance == s)
{
_data[c] = null;
removed = true;
}
}
if (removed)
{
ScheduleCompact(); ScheduleCompact();
}
} }
void ScheduleCompact() void ScheduleCompact()
{ {
if(_compactScheduled) if(_compactScheduled || _destroyed)
return; return;
_compactScheduled = true; _compactScheduled = true;
Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background); Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background);
@ -116,43 +144,27 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
void Compact() void Compact()
{ {
if(!_compactScheduled)
return;
_compactScheduled = false; _compactScheduled = false;
int empty = -1; _list.Compact();
for (var c = 0; c < _count; c++) if (_list.IsEmpty)
{
var r = _data[c];
//Mark current index as first empty
if (r == null && empty == -1)
empty = c;
//If current element isn't null and we have an empty one
if (r != null && empty != -1)
{
_data[c] = null;
_data[empty] = r;
empty++;
}
}
if (empty != -1)
_count = empty;
if (_count == 0)
Destroy(); Destroy();
} }
void OnEvent(object? sender, TEventArgs eventArgs) void OnEvent(object? sender, TEventArgs eventArgs)
{ {
var needCompact = false; var alive = _list.GetAlive();
for (var c = 0; c < _count; c++) if(alive == null)
Destroy();
else
{ {
var r = _data[c]; foreach(var item in alive.Span)
if (r?.TryGetTarget(out var sub) == true) item.OnEvent(_target, _ev, eventArgs);
sub!.OnEvent(_target, _ev, eventArgs); WeakHashList<IWeakEventSubscriber<TEventArgs>>.ReturnToSharedPool(alive);
else if(_list.NeedCompact && !_compactScheduled)
needCompact = true; ScheduleCompact();
} }
if (needCompact)
ScheduleCompact();
} }
} }

236
src/Avalonia.Base/Utilities/WeakHashList.cs

@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections.Pooled;
namespace Avalonia.Utilities;
internal class WeakHashList<T> where T : class
{
private struct Key
{
public WeakReference<T>? Weak;
public T? Strong;
public int HashCode;
public static Key MakeStrong(T r) => new()
{
HashCode = r.GetHashCode(),
Strong = r
};
public static Key MakeWeak(T r) => new()
{
HashCode = r.GetHashCode(),
Weak = new WeakReference<T>(r)
};
public override int GetHashCode() => HashCode;
}
class KeyComparer : IEqualityComparer<Key>
{
public bool Equals(Key x, Key y)
{
if (x.HashCode != y.HashCode)
return false;
if (x.Strong != null)
{
if (y.Strong != null)
return x.Strong == y.Strong;
if (y.Weak == null)
return false;
return y.Weak.TryGetTarget(out var weakTarget) && weakTarget == x.Strong;
}
else if (y.Strong != null)
{
if (x.Weak == null)
return false;
return x.Weak.TryGetTarget(out var weakTarget) && weakTarget == y.Strong;
}
else
{
if (x.Weak == null || x.Weak.TryGetTarget(out var xTarget) == false)
return y.Weak?.TryGetTarget(out _) != true;
return y.Weak?.TryGetTarget(out var yTarget) == true && xTarget == yTarget;
}
}
public int GetHashCode(Key obj) => obj.HashCode;
public static KeyComparer Instance = new();
}
Dictionary<Key, int>? _dic;
WeakReference<T>?[]? _arr;
int _arrCount;
public bool IsEmpty => _dic == null || _dic.Count == 0;
public bool NeedCompact { get; private set; }
public void Add(T item)
{
if (_dic != null)
{
var strongKey = Key.MakeStrong(item);
if (_dic.TryGetValue(strongKey, out var cnt))
_dic[strongKey] = cnt + 1;
else
_dic[Key.MakeWeak(item)] = 1;
return;
}
if (_arr == null)
_arr = new WeakReference<T>[8];
if (_arrCount < _arr.Length)
{
_arr[_arrCount] = new WeakReference<T>(item);
_arrCount++;
return;
}
// Check if something is dead
for (var c = 0; c < _arrCount; c++)
{
if (_arr[c]!.TryGetTarget(out _) == false)
{
_arr[c] = new WeakReference<T>(item);
return;
}
}
_dic = new Dictionary<Key, int>(KeyComparer.Instance);
foreach (var existing in _arr)
{
if (existing!.TryGetTarget(out var target))
Add(target);
}
_arr = null;
}
public void Remove(T item)
{
if (_arr != null)
{
for (var c = 0; c < _arr.Length; c++)
{
if (_arr[c]?.TryGetTarget(out var target) == true && target == item)
{
_arr[c] = null;
Compact();
return;
}
}
}
else if (_dic != null)
{
var strongKey = Key.MakeStrong(item);
if (_dic.TryGetValue(strongKey, out var cnt))
{
if (cnt > 1)
{
_dic[strongKey] = cnt - 1;
return;
}
}
_dic.Remove(strongKey);
}
}
private void ArrCompact()
{
if (_arr != null)
{
int empty = -1;
for (var c = 0; c < _arrCount; c++)
{
var r = _arr[c];
//Mark current index as first empty
if (r == null && empty == -1)
empty = c;
//If current element isn't null and we have an empty one
if (r != null && empty != -1)
{
_arr[c] = null;
_arr[empty] = r;
empty++;
}
}
if (empty != -1)
_arrCount = empty;
}
}
public void Compact()
{
if (_dic != null)
{
PooledList<Key>? toRemove = null;
foreach (var kvp in _dic)
{
if (kvp.Key.Weak?.TryGetTarget(out _) != true)
(toRemove ??= new PooledList<Key>()).Add(kvp.Key);
}
if (toRemove != null)
{
foreach (var k in toRemove)
_dic.Remove(k);
toRemove.Dispose();
}
}
}
private static readonly Stack<PooledList<T>> s_listPool = new();
public static void ReturnToSharedPool(PooledList<T> list)
{
list.Clear();
s_listPool.Push(list);
}
public PooledList<T>? GetAlive(Func<PooledList<T>>? factory = null)
{
PooledList<T>? pooled = null;
if (_arr != null)
{
bool needCompact = false;
for (var c = 0; c < _arrCount; c++)
{
if (_arr[c]?.TryGetTarget(out var target) == true)
(pooled ??= factory?.Invoke()
?? (s_listPool.Count > 0
? s_listPool.Pop()
: new PooledList<T>())).Add(target!);
else
{
_arr[c] = null;
needCompact = true;
}
}
if(needCompact)
ArrCompact();
return pooled;
}
if (_dic != null)
{
foreach (var kvp in _dic)
{
if (kvp.Key.Weak?.TryGetTarget(out var target) == true)
(pooled ??= factory?.Invoke()
?? (s_listPool.Count > 0
? s_listPool.Pop()
: new PooledList<T>()))
.Add(target!);
else
NeedCompact = true;
}
}
return pooled;
}
}

21
src/Avalonia.Base/Visual.cs

@ -97,12 +97,18 @@ namespace Avalonia
/// </summary> /// </summary>
public static readonly StyledProperty<int> ZIndexProperty = public static readonly StyledProperty<int> ZIndexProperty =
AvaloniaProperty.Register<Visual, int>(nameof(ZIndex)); AvaloniaProperty.Register<Visual, int>(nameof(ZIndex));
private static readonly WeakEvent<IAffectsRender, EventArgs> InvalidatedWeakEvent =
WeakEvent.Register<IAffectsRender>(
(s, h) => s.Invalidated += h,
(s, h) => s.Invalidated -= h);
private Rect _bounds; private Rect _bounds;
private TransformedBounds? _transformedBounds; private TransformedBounds? _transformedBounds;
private IRenderRoot? _visualRoot; private IRenderRoot? _visualRoot;
private IVisual? _visualParent; private IVisual? _visualParent;
private bool _hasMirrorTransform; private bool _hasMirrorTransform;
private TargetWeakEventSubscriber<Visual, EventArgs>? _affectsRenderWeakSubscriber;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Visual"/> class. /// Initializes static members of the <see cref="Visual"/> class.
@ -369,12 +375,21 @@ namespace Avalonia
{ {
if (e.OldValue is IAffectsRender oldValue) if (e.OldValue is IAffectsRender oldValue)
{ {
WeakEventHandlerManager.Unsubscribe<EventArgs, T>(oldValue, nameof(oldValue.Invalidated), sender.AffectsRenderInvalidated); if (sender._affectsRenderWeakSubscriber != null)
InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber);
} }
if (e.NewValue is IAffectsRender newValue) if (e.NewValue is IAffectsRender newValue)
{ {
WeakEventHandlerManager.Subscribe<IAffectsRender, EventArgs, T>(newValue, nameof(newValue.Invalidated), sender.AffectsRenderInvalidated); if (sender._affectsRenderWeakSubscriber == null)
{
sender._affectsRenderWeakSubscriber = new TargetWeakEventSubscriber<Visual, EventArgs>(
sender, static (target, _, _, _) =>
{
target.InvalidateVisual();
});
}
InvalidatedWeakEvent.Subscribe(newValue, sender._affectsRenderWeakSubscriber);
} }
sender.InvalidateVisual(); sender.InvalidateVisual();
@ -625,8 +640,6 @@ namespace Avalonia
OnVisualParentChanged(old, value); OnVisualParentChanged(old, value);
} }
private void AffectsRenderInvalidated(object? sender, EventArgs e) => InvalidateVisual();
/// <summary> /// <summary>
/// Called when the <see cref="VisualChildren"/> collection changes. /// Called when the <see cref="VisualChildren"/> collection changes.
/// </summary> /// </summary>

30
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -20,7 +20,7 @@ namespace Avalonia.Controls
/// Represents a data-driven collection control that incorporates a flexible layout system, /// Represents a data-driven collection control that incorporates a flexible layout system,
/// custom views, and virtualization. /// custom views, and virtualization.
/// </summary> /// </summary>
public class ItemsRepeater : Panel, IChildIndexProvider, IWeakEventSubscriber<EventArgs> public class ItemsRepeater : Panel, IChildIndexProvider
{ {
/// <summary> /// <summary>
/// Defines the <see cref="HorizontalCacheLength"/> property. /// Defines the <see cref="HorizontalCacheLength"/> property.
@ -60,6 +60,7 @@ namespace Avalonia.Controls
private readonly ViewManager _viewManager; private readonly ViewManager _viewManager;
private readonly ViewportManager _viewportManager; private readonly ViewportManager _viewportManager;
private readonly TargetWeakEventSubscriber<ItemsRepeater, EventArgs> _layoutWeakSubscriber;
private IEnumerable? _items; private IEnumerable? _items;
private VirtualizingLayoutContext? _layoutContext; private VirtualizingLayoutContext? _layoutContext;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged; private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
@ -74,6 +75,15 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ItemsRepeater() public ItemsRepeater()
{ {
_layoutWeakSubscriber = new TargetWeakEventSubscriber<ItemsRepeater, EventArgs>(
this, static (target, _, ev, _) =>
{
if (ev == AttachedLayout.ArrangeInvalidatedWeakEvent)
target.InvalidateArrange();
else if (ev == AttachedLayout.MeasureInvalidatedWeakEvent)
target.InvalidateMeasure();
});
_viewManager = new ViewManager(this); _viewManager = new ViewManager(this);
_viewportManager = new ViewportManager(this); _viewportManager = new ViewportManager(this);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Once); KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Once);
@ -728,8 +738,8 @@ namespace Avalonia.Controls
{ {
oldValue.UninitializeForContext(LayoutContext); oldValue.UninitializeForContext(LayoutContext);
AttachedLayout.MeasureInvalidatedWeakEvent.Unsubscribe(oldValue, this); AttachedLayout.MeasureInvalidatedWeakEvent.Unsubscribe(oldValue, _layoutWeakSubscriber);
AttachedLayout.ArrangeInvalidatedWeakEvent.Unsubscribe(oldValue, this); AttachedLayout.ArrangeInvalidatedWeakEvent.Unsubscribe(oldValue, _layoutWeakSubscriber);
// Walk through all the elements and make sure they are cleared // Walk through all the elements and make sure they are cleared
foreach (var element in Children) foreach (var element in Children)
@ -747,8 +757,8 @@ namespace Avalonia.Controls
{ {
newValue.InitializeForContext(LayoutContext); newValue.InitializeForContext(LayoutContext);
AttachedLayout.MeasureInvalidatedWeakEvent.Subscribe(newValue, this); AttachedLayout.MeasureInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber);
AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, this); AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber);
} }
bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout; bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout;
@ -798,15 +808,7 @@ namespace Avalonia.Controls
{ {
_viewportManager.OnBringIntoViewRequested(e); _viewportManager.OnBringIntoViewRequested(e);
} }
void IWeakEventSubscriber<EventArgs>.OnEvent(object? sender, WeakEvent ev, EventArgs e)
{
if(ev == AttachedLayout.ArrangeInvalidatedWeakEvent)
InvalidateArrange();
else if (ev == AttachedLayout.MeasureInvalidatedWeakEvent)
InvalidateMeasure();
}
private VirtualizingLayoutContext GetLayoutContext() private VirtualizingLayoutContext GetLayoutContext()
{ {
if (_layoutContext == null) if (_layoutContext == null)

17
src/Avalonia.Controls/TopLevel.cs

@ -34,8 +34,7 @@ namespace Avalonia.Controls
ICloseable, ICloseable,
IStyleHost, IStyleHost,
ILogicalRoot, ILogicalRoot,
ITextInputMethodRoot, ITextInputMethodRoot
IWeakEventSubscriber<ResourcesChangedEventArgs>
{ {
/// <summary> /// <summary>
/// Defines the <see cref="ClientSize"/> property. /// Defines the <see cref="ClientSize"/> property.
@ -93,6 +92,7 @@ namespace Avalonia.Controls
private WindowTransparencyLevel _actualTransparencyLevel; private WindowTransparencyLevel _actualTransparencyLevel;
private ILayoutManager? _layoutManager; private ILayoutManager? _layoutManager;
private Border? _transparencyFallbackBorder; private Border? _transparencyFallbackBorder;
private TargetWeakEventSubscriber<TopLevel, ResourcesChangedEventArgs>? _resourcesChangesSubscriber;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TopLevel"/> class. /// Initializes static members of the <see cref="TopLevel"/> class.
@ -192,7 +192,13 @@ namespace Avalonia.Controls
if (((IStyleHost)this).StylingParent is IResourceHost applicationResources) if (((IStyleHost)this).StylingParent is IResourceHost applicationResources)
{ {
ResourcesChangedWeakEvent.Subscribe(applicationResources, this); _resourcesChangesSubscriber = new TargetWeakEventSubscriber<TopLevel, ResourcesChangedEventArgs>(
this, static (target, _, _, e) =>
{
((ILogical)target).NotifyResourcesChanged(e);
});
ResourcesChangedWeakEvent.Subscribe(applicationResources, _resourcesChangesSubscriber);
} }
impl.LostFocus += PlatformImpl_LostFocus; impl.LostFocus += PlatformImpl_LostFocus;
@ -297,11 +303,6 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
IMouseDevice? IInputRoot.MouseDevice => PlatformImpl?.MouseDevice; IMouseDevice? IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
void IWeakEventSubscriber<ResourcesChangedEventArgs>.OnEvent(object? sender, WeakEvent ev, ResourcesChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(e);
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary> /// </summary>

45
tests/Avalonia.Benchmarks/Visuals/VisualAffectsRenderBenchmarks.cs

@ -0,0 +1,45 @@
using Avalonia.Controls;
using Avalonia.Media;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Visuals;
[MemoryDiagnoser]
public class VisualAffectsRenderBenchmarks
{
private readonly TestVisual _target;
private readonly IPen _pen;
public VisualAffectsRenderBenchmarks()
{
_target = new TestVisual();
_pen = new Pen(Brushes.Black);
}
[Benchmark]
public void SetPropertyThatAffectsRender()
{
_target.Pen = _pen;
_target.Pen = null;
}
private class TestVisual : Visual
{
/// <summary>
/// Defines the <see cref="Pen"/> property.
/// </summary>
public static readonly StyledProperty<IPen> PenProperty =
AvaloniaProperty.Register<Border, IPen>(nameof(Pen));
public IPen Pen
{
get => GetValue(PenProperty);
set => SetValue(PenProperty, value);
}
static TestVisual()
{
AffectsRender<TestVisual>(PenProperty);
}
}
}
Loading…
Cancel
Save