Skip to content

Commit

Permalink
refactor: shape perf
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoy312 committed Dec 6, 2024
1 parent 6383649 commit 76d93ed
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 7 deletions.
14 changes: 14 additions & 0 deletions src/Uno.UI/FeatureConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -842,5 +842,19 @@ public static class DependencyProperty
/// </summary>
public static bool DisableThreadingCheck { get; set; }
}

public static class Shape
{
#if __WASM__
public static bool BlockNativeUpdateUntil1stArrange { get; set; } = true;

public static int BBoxCacheSize
{
get => Microsoft.UI.Xaml.Shapes.Shape.BBoxCacheSize;
set => Microsoft.UI.Xaml.Shapes.Shape.BBoxCacheSize = value;
}
public static bool CacheBBoxCalculationResult { get; set; } = true;
#endif
}
}
}
60 changes: 53 additions & 7 deletions src/Uno.UI/UI/Xaml/Shapes/Shape.wasm.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Uno;
using Uno.Disposables;
using Uno.Extensions;
using System.Numerics;
using Windows.Foundation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Wasm;
using Uno;
using Uno.Collections;
using Uno.Disposables;
using Uno.Extensions;
using Uno.UI;
using Uno.UI.Xaml;

using RadialGradientBrush = Microsoft/* UWP don't rename */.UI.Xaml.Media.RadialGradientBrush;
using System.Numerics;
using System.Diagnostics;
using Uno.UI.Xaml;

namespace Microsoft.UI.Xaml.Shapes
{
partial class Shape

{
private static readonly LruCache<string, Rect> _bboxCache = new(500);

internal static int BBoxCacheSize
{
get => _bboxCache.Capacity;
set => _bboxCache.Capacity = value;
}
}

partial class Shape
{
private readonly SerialDisposable _fillBrushSubscription = new SerialDisposable();
private readonly SerialDisposable _strokeBrushSubscription = new SerialDisposable();

private DefsSvgElement _defs;
private protected readonly SvgElement _mainSvgElement;
private protected bool _shouldUpdateNative = !FeatureConfiguration.Shape.BlockNativeUpdateUntil1stArrange; // used to block updates to native element until 1st Arrange

protected Shape() : base("svg", isSvg: true)
{
Expand All @@ -37,6 +53,8 @@ private protected Shape(string mainSvgElementTag) : base("svg", isSvg: true)

private protected void UpdateRender()
{
_shouldUpdateNative = true;

OnFillBrushChanged();
OnStrokeBrushChanged();
UpdateStrokeThickness();
Expand All @@ -45,6 +63,8 @@ private protected void UpdateRender()

private void OnFillBrushChanged()
{
if (!_shouldUpdateNative) return;

// We don't request an update of the HitTest (UpdateHitTest()) since this element is never expected to be hit testable.
// Note: We also enforce that the default hit test == false is not altered in the OnHitTestVisibilityChanged.

Expand Down Expand Up @@ -111,6 +131,8 @@ private void OnFillBrushChanged()

private void OnStrokeBrushChanged()
{
if (!_shouldUpdateNative) return;

var svgElement = _mainSvgElement;
var stroke = Stroke;

Expand Down Expand Up @@ -159,6 +181,8 @@ private void OnStrokeBrushChanged()

private void UpdateStrokeThickness()
{
if (!_shouldUpdateNative) return;

Check failure on line 184 in src/Uno.UI/UI/Xaml/Shapes/Shape.wasm.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI/UI/Xaml/Shapes/Shape.wasm.cs#L184

Add curly braces around the nested statement(s) in this 'if' block.

var svgElement = _mainSvgElement;
var strokeThickness = ActualStrokeThickness;

Expand All @@ -174,6 +198,8 @@ private void UpdateStrokeThickness()

private void UpdateStrokeDashArray()
{
if (!_shouldUpdateNative) return;

var svgElement = _mainSvgElement;

if (StrokeDashArray is not { } strokeDashArray)
Expand Down Expand Up @@ -203,7 +229,27 @@ private UIElementCollection GetDefs()

private static Rect GetPathBoundingBox(Shape shape)
{
return shape._mainSvgElement.GetBBox();
if (FeatureConfiguration.Shape.CacheBBoxCalculationResult)
{
var key = shape switch
{
Path path => (path.Data as GeometryData)?.Data,
_ => null,
};

if (!string.IsNullOrEmpty(key))
{
if (!_bboxCache.TryGetValue(key, out var rect))
{
_bboxCache[key] = rect = shape._mainSvgElement.GetBBox();
}

return rect;
}
}

var result = shape._mainSvgElement.GetBBox();
return result;
}

private protected void Render(Shape shape, Size? size = null, double scaleX = 1d, double scaleY = 1d, double renderOriginX = 0d, double renderOriginY = 0d)
Expand Down
113 changes: 113 additions & 0 deletions src/Uno.UWP/Collections/LruCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Cache;
using System.Text;
using System.Threading.Tasks;

namespace Uno.Collections;

public partial class LruCache<TKey, TValue>
{
private class KeyedItem
{
public TKey Key { get; init; }
public TValue Value { get; set; }

public KeyedItem(TKey key, TValue value)
{
this.Key = key;
this.Value = value;
}
}
}
public partial class LruCache<TKey, TValue> where TKey : notnull
{
private readonly Dictionary<TKey, LinkedListNode<KeyedItem>> _map = new();
private readonly LinkedList<KeyedItem> _list = new();
private int _capacity;

public int Count => _map.Count;
public int Capacity
{
get => _capacity;
set => UpdateCapacity(value);
}

public TValue this[TKey key]
{
get => Get(key);
set => Put(key, value);
}

public LruCache(int capacity)
{
if (capacity < 0) throw new ArgumentOutOfRangeException("capacity must be positive or zero.");

Check failure on line 45 in src/Uno.UWP/Collections/LruCache.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UWP/Collections/LruCache.cs#L45

Add curly braces around the nested statement(s) in this 'if' block.

this._capacity = capacity;
}

public TValue Get(TKey key)
{
return TryGetValue(key, out var value) ? value : default;
}
public bool TryGetValue(TKey key, out TValue value)
{
if (_map.TryGetValue(key, out var node))
{
_list.Remove(node);
_list.AddFirst(node);

value = node.Value.Value;
return true;
}
else
{
value = default;
return false;
}
}
public void Put(TKey key, TValue value)
{
if (_capacity == 0) return;

Check failure on line 72 in src/Uno.UWP/Collections/LruCache.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UWP/Collections/LruCache.cs#L72

Add curly braces around the nested statement(s) in this 'if' block.

if (_map.TryGetValue(key, out var node))
{
node.Value.Value = value;

_list.Remove(node);
_list.AddFirst(node);
}
else
{
while (_map.Count >= _capacity)
{
_map.Remove(_list.Last.Value.Key);
_list.RemoveLast();
}

_map.Add(key, _list.AddFirst(new KeyedItem(key, value)));
}
}

public void UpdateCapacity(int capacity)
{
if (capacity < 0) throw new ArgumentOutOfRangeException("capacity must be positive or zero.");

_capacity = capacity;
if (_capacity == 0)
{
_map.Clear();
_list.Clear();
}
else
{
while (_map.Count > _capacity)
{
_map.Remove(_list.Last.Value.Key);
_list.RemoveLast();
}
}
}
}

0 comments on commit 76d93ed

Please sign in to comment.