From 8b256c15be1236b3ed1d8d7c52b9fcbc0ba6095b Mon Sep 17 00:00:00 2001 From: roubachof Date: Fri, 4 Aug 2023 20:42:03 +0200 Subject: [PATCH 1/2] feat(ShadowContainer): cache cleanup based on shadows stats --- .../Controls/Shadows/ShadowsCache.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowsCache.cs b/src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowsCache.cs index 0cc4ccd04..08bec807f 100644 --- a/src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowsCache.cs +++ b/src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowsCache.cs @@ -1,5 +1,9 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + using Microsoft.Extensions.Logging; using SkiaSharp; using Uno.Extensions; @@ -8,10 +12,20 @@ namespace Uno.Toolkit.UI; public class ShadowsCache { + private const int CleanupInterval = 30; + private static readonly ILogger _logger = typeof(ShadowsCache).Log(); + private static readonly Predicate OneHitSinceAShortTime = + (bucket) => bucket.Hit == 1 && (DateTime.UtcNow - bucket.LastHit).TotalMinutes > 1; + + private static readonly Predicate SeveralHitsSinceALongTime = + (bucket) => bucket.Hit > 1 && (DateTime.UtcNow - bucket.LastHit).TotalMinutes > 3; + private readonly ConcurrentDictionary _shadowsCache = new ConcurrentDictionary(); + private DateTime _lastCleanupTime = DateTime.UtcNow; + public void AddOrUpdate(string key, SKImage image) { if (_logger.IsEnabled(LogLevel.Trace)) @@ -38,6 +52,8 @@ public void AddOrUpdate(string key, SKImage image) } return existing; }); + + CleanupIfNeeded(); } public bool TryGetValue(string key, out SKImage? image) @@ -101,4 +117,46 @@ public void AddHit() LastHit = DateTime.UtcNow; } } + + public async void CleanupIfNeeded() + { + if ((DateTime.UtcNow - _lastCleanupTime).TotalSeconds > CleanupInterval) + { + try + { + await CleanupAsync(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[ShadowsCache] Cleanup failed: {ex}"); + } + } + } + + /// + /// Removing old shadows based on their stats. + /// + private Task CleanupAsync() + { + _lastCleanupTime = DateTime.UtcNow; + + return Task.Run(() => + { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + System.Diagnostics.Debug.WriteLine($"[ShadowsCache] Cleanup starting"); + int removedShadows = 0; + foreach (var expiredBucket in _shadowsCache + .Where(x => OneHitSinceAShortTime(x.Value) || SeveralHitsSinceALongTime(x.Value))) + { + if (_shadowsCache.TryRemove(expiredBucket.Key, out var _)) + { + removedShadows++; + } + } + + stopwatch.Stop(); + System.Diagnostics.Debug.WriteLine($"[ShadowsCache] Cleanup done in {stopwatch.ElapsedMilliseconds} ms ({removedShadows} shadows removed)"); + }); + } } From c63faa0975cb5daf06b36a44e162e7bf9767be0b Mon Sep 17 00:00:00 2001 From: roubachof Date: Thu, 21 Sep 2023 14:10:43 +0200 Subject: [PATCH 2/2] chore(ShadowContainer): changes after PR review --- .../Controls/Shadows/ShadowsCache.cs | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowsCache.cs b/src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowsCache.cs index 08bec807f..41338f058 100644 --- a/src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowsCache.cs +++ b/src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowsCache.cs @@ -10,21 +10,22 @@ using Uno.Logging; namespace Uno.Toolkit.UI; + public class ShadowsCache { private const int CleanupInterval = 30; private static readonly ILogger _logger = typeof(ShadowsCache).Log(); - private static readonly Predicate OneHitSinceAShortTime = - (bucket) => bucket.Hit == 1 && (DateTime.UtcNow - bucket.LastHit).TotalMinutes > 1; + private static readonly Func OneHitSinceAShortTime = + (bucket, time) => bucket.Hit == 1 && (time - bucket.LastHit).TotalMinutes > 1; - private static readonly Predicate SeveralHitsSinceALongTime = - (bucket) => bucket.Hit > 1 && (DateTime.UtcNow - bucket.LastHit).TotalMinutes > 3; + private static readonly Func SeveralHitsSinceALongTime = + (bucket, time) => bucket.Hit > 1 && (time - bucket.LastHit).TotalMinutes > 3; - private readonly ConcurrentDictionary _shadowsCache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _shadowsCache = new(); - private DateTime _lastCleanupTime = DateTime.UtcNow; + private DateTime _lastCleanupUtcTime = DateTime.UtcNow; public void AddOrUpdate(string key, SKImage image) { @@ -33,9 +34,9 @@ public void AddOrUpdate(string key, SKImage image) _logger.Trace($"[ShadowsCache] AddOrUpdate => key: {key}"); } - var bucket = _shadowsCache.AddOrUpdate( + _shadowsCache.AddOrUpdate( key, - (key) => + _ => { if (_logger.IsEnabled(LogLevel.Trace)) { @@ -43,7 +44,7 @@ public void AddOrUpdate(string key, SKImage image) } return new CacheBucket(image); }, - (key, existing) => + (_, existing) => { existing.AddHit(); if (_logger.IsEnabled(LogLevel.Trace)) @@ -62,6 +63,7 @@ public bool TryGetValue(string key, out SKImage? image) { _logger.Trace($"[ShadowsCache] TryGet => key: {key}"); } + if (_shadowsCache.TryGetValue(key, out var bucket)) { bucket.AddHit(); @@ -120,15 +122,15 @@ public void AddHit() public async void CleanupIfNeeded() { - if ((DateTime.UtcNow - _lastCleanupTime).TotalSeconds > CleanupInterval) + if (!_shadowsCache.IsEmpty && (DateTime.UtcNow - _lastCleanupUtcTime).TotalSeconds > CleanupInterval) { try { - await CleanupAsync(); + await CleanupAsync().ConfigureAwait(false); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"[ShadowsCache] Cleanup failed: {ex}"); + _logger.Warn($"[ShadowsCache] Cleanup failed: {ex}"); } } } @@ -138,25 +140,36 @@ public async void CleanupIfNeeded() /// private Task CleanupAsync() { - _lastCleanupTime = DateTime.UtcNow; + _lastCleanupUtcTime = DateTime.UtcNow; return Task.Run(() => { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - System.Diagnostics.Debug.WriteLine($"[ShadowsCache] Cleanup starting"); + var stopwatch = new Stopwatch(); + if (_logger.IsEnabled(LogLevel.Trace)) + { + stopwatch.Start(); + _logger.Trace($"[ShadowsCache] Cleanup starting"); + } + + DateTime utcNow = DateTime.UtcNow; int removedShadows = 0; foreach (var expiredBucket in _shadowsCache - .Where(x => OneHitSinceAShortTime(x.Value) || SeveralHitsSinceALongTime(x.Value))) + .Where(x => OneHitSinceAShortTime(x.Value, utcNow) || SeveralHitsSinceALongTime(x.Value, utcNow))) { - if (_shadowsCache.TryRemove(expiredBucket.Key, out var _)) + if (_shadowsCache.TryRemove(expiredBucket.Key, out _)) { removedShadows++; } } - stopwatch.Stop(); - System.Diagnostics.Debug.WriteLine($"[ShadowsCache] Cleanup done in {stopwatch.ElapsedMilliseconds} ms ({removedShadows} shadows removed)"); + if (_logger.IsEnabled(LogLevel.Trace)) + { + stopwatch.Stop(); + if (_logger.IsEnabled(LogLevel.Trace)) + { + _logger.Trace($"[ShadowsCache] Cleanup done in {stopwatch.ElapsedMilliseconds} ms ({removedShadows} shadows removed)"); + } + } }); } }