From 1557d48d15c1400774598f2660979b9c95bdd5f9 Mon Sep 17 00:00:00 2001 From: Marius Ungureanu Date: Tue, 21 Feb 2023 02:27:16 +0200 Subject: [PATCH] Remove unused tags string and improvements to caching We cache the ImageTagSet directly now. That should allow us to create fewer of these instances overall. For the app I'm testing on, we cover 97% of the image tags combinations In practice, cache hits will be better overall. The other improvement is to not have to sort/create imagetagsets every time, instead opting to use more specialized APIs. --- Xwt/Xwt.Drawing/Image.cs | 96 ++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/Xwt/Xwt.Drawing/Image.cs b/Xwt/Xwt.Drawing/Image.cs index 75427f0a7..838cf541b 100644 --- a/Xwt/Xwt.Drawing/Image.cs +++ b/Xwt/Xwt.Drawing/Image.cs @@ -31,6 +31,7 @@ using System.Reflection; using System.IO; using System.Collections.Generic; +using System.Diagnostics; namespace Xwt.Drawing { @@ -268,7 +269,7 @@ static bool ParseImageHints (string baseName, string fileName, string ext, out i return false; } else i2 = fileName.Length; - tags = new ImageTagSet (fileName.Substring (i, i2 - i)); + tags = ImageTagSet.Parse (fileName.Substring (i, i2 - i)); return true; } else { @@ -288,7 +289,7 @@ static bool ParseImageHints (string baseName, string fileName, string ext, out i return false; } if (i2 + 2 < fileName.Length) - tags = new ImageTagSet (fileName.Substring (i2 + 2)); + tags = ImageTagSet.Parse (fileName.Substring (i2 + 2)); return true; } } @@ -307,18 +308,23 @@ public static Image CreateMultiSizeIcon (IEnumerable images) // If one of the images is themed, then the whole resulting image will be themed. // To create the new image, we group images with the same theme but different size, and we create a multi-size icon for those. // The resulting image is the combination of those multi-size icons. - var allThemes = allImages.OfType ().SelectMany (i => i.Images).Select (i => new ImageTagSet (i.Item2)).Distinct ().ToArray (); + var allThemes = allImages + .OfType () + .SelectMany (i => i.Images) + .Select(i => i.Item2) + .Distinct (TagSetEqualityComparer.Instance) + .ToArray (); List> newImages = new List> (); foreach (var ts in allThemes) { List multiSizeImages = new List (); foreach (var i in allImages) { if (i is ThemedImage) - multiSizeImages.Add (((ThemedImage)i).GetImage (ts.AsArray)); + multiSizeImages.Add (((ThemedImage)i).GetImage (ts)); else multiSizeImages.Add (i); } var img = CreateMultiSizeIcon (multiSizeImages); - newImages.Add (new Tuple (img, ts.AsArray)); + newImages.Add (new Tuple (img, ts)); } return new ThemedImage (newImages); } else { @@ -1000,6 +1006,9 @@ 2 active~dark 2 active~contrast~dark 2 active~contrast 2 active + + Keep in sync with knownTagArrays. + These tag items amount for 97% of the image tags found in images. */ readonly string[] knownTags = new[] { "~dark", @@ -1013,65 +1022,74 @@ 2 active "~contrast~dark~disabled", }; - readonly string[][] knownTagArrays = new[] { - new[] { "dark", }, - new[] { "contrast", }, - new[] { "contrast", "dark", }, - new[] { "sel", }, - new[] { "dark", "sel", }, - new[] { "disabled", }, - new[] { "dark", "disabled", }, - new[] { "contrast", "disabled", }, - new[] { "contrast", "dark", "disabled", }, + readonly ImageTagSet[] knownTagArrays = new[] { + new ImageTagSet(new[] { "dark", }), + new ImageTagSet(new[] { "contrast", }), + new ImageTagSet(new[] { "contrast", "dark", }), + new ImageTagSet(new[] { "sel", }), + new ImageTagSet(new[] { "dark", "sel", }), + new ImageTagSet(new[] { "disabled", }), + new ImageTagSet(new[] { "dark", "disabled", }), + new ImageTagSet(new[] { "contrast", "disabled", }), + new ImageTagSet(new[] { "contrast", "dark", "disabled", }), }; - static readonly char[] tagSeparators = { '~' }; - public bool GetTagArray(string tags, out string[] tagArray) + public ImageTagSet TryGetTagSet(string tags) { var index = Array.IndexOf(knownTags, tags); - tagArray = index >= 0 ? knownTagArrays[index] : SplitTags(tags); - return index >= 0; + return index >= 0 ? knownTagArrays[index] : null; } + } - static string[] SplitTags(string tags) - { - var array = tags.Split(tagSeparators, StringSplitOptions.RemoveEmptyEntries); - Array.Sort(array); + // As much as I don't like the duplication, it's simpler than accessing a static instance every time. + class TagSetEqualityComparer : IEqualityComparer + { + public static TagSetEqualityComparer Instance { get; } = new TagSetEqualityComparer(); + + public bool Equals(string[] x, string[] y) => x.SequenceEqual(y); - return array; + public int GetHashCode(string[] obj) + { + unchecked + { + int c = 0; + foreach (var s in obj) + c %= s.GetHashCode(); + return c; + } } } + [DebuggerDisplay("{DebuggerDisplay,nq}")] sealed class ImageTagSet { - string tags; string[] tagsArray; public static readonly ImageTagSet Empty = new ImageTagSet (new string[0]); static readonly ImageTagCache imageTagCache; + static readonly char[] tagSeparators = { '~' }; - public ImageTagSet (string [] tagsArray) + public static ImageTagSet Parse(string tags) { - this.tagsArray = tagsArray; - Array.Sort (tagsArray); + return imageTagCache.TryGetTagSet(tags) ?? Create(tags); } - public bool IsEmpty { - get { - return tagsArray.Length == 0; - } + static ImageTagSet Create(string tags) + { + var tagArray = tags.Split(tagSeparators, StringSplitOptions.RemoveEmptyEntries); + Array.Sort(tagArray); + + return new ImageTagSet(tagArray); } - public ImageTagSet (string tags) + public ImageTagSet (string [] tagsArray) { - imageTagCache.GetTagArray(tags, out tagsArray); + this.tagsArray = tagsArray; } - public string AsString { + public bool IsEmpty { get { - if (tags == null) - tags = string.Join ("~", tagsArray); - return tags; + return tagsArray.Length == 0; } } @@ -1096,6 +1114,8 @@ public override int GetHashCode () return c; } } + + string DebuggerDisplay => string.Join("~", tagsArray); } abstract class ImageLoader