| File: System\Drawing\Internal\SystemColorTracker.cs | Web Access |
| Project: src\winforms\src\System.Drawing.Common\src\System.Drawing.Common.csproj (System.Drawing.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Win32; namespace System.Drawing.Internal; // Keeps track of objects that need to be notified of system color change events. // Mostly this means maintaining a list of weak references. internal static class SystemColorTracker { // when I tried the self host, it went over 500 but never over 1000. private const int INITIAL_SIZE = 200; // If it gets this big, I seriously miscalculated the performance of this object. private const int WARNING_SIZE = 100000; private const float EXPAND_THRESHOLD = 0.75f; private const int EXPAND_FACTOR = 2; private static WeakReference<ISystemColorTracker>?[] s_list = new WeakReference<ISystemColorTracker>?[INITIAL_SIZE]; private static int s_count; private static bool s_addedTracker; private static readonly Lock s_lockObject = new(); internal static void Add(ISystemColorTracker obj) { lock (s_lockObject) { Debug.Assert(s_list is not null, "List is null"); Debug.Assert(s_list.Length > 0, "INITIAL_SIZE was initialized after list"); if (s_list.Length == s_count) { GarbageCollectList(); } if (!s_addedTracker) { s_addedTracker = true; SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged; } // Strictly speaking, we should grab a lock on this class. But since the chances // of a problem are so low, the consequences so minimal (something will get accidentally dropped // from the list), and the performance of locking so lousy, we'll risk it. int index = s_count; s_count++; // COM+ takes forever to Finalize() weak references, so it pays to reuse them. if (s_list[index] is not WeakReference<ISystemColorTracker> reference) { s_list[index] = new(obj); } else { Debug.Assert(!reference.TryGetTarget(out _), $"Trying to reuse a weak reference that isn't broken yet: list[{index}], length = {s_list.Length}"); reference.SetTarget(obj); } } } private static void CleanOutBrokenLinks() { // Partition the list -- valid references in the low indices, broken references in the high indices. // This is taken straight out of Sedgewick (p. 118 on quicksort). // Basic idea is to find a broken reference on the left side of the list, and swap it with // a valid reference on the right int right = s_list.Length - 1; int left = 0; int length = s_list.Length; // Loop invariant: everything to the left of "left" is a valid reference, // and anything to the right of "right" is broken. while (true) { while (left < length && s_list[left]?.TryGetTarget(out _) == true) left++; while (right >= 0 && s_list[right]?.TryGetTarget(out _) != true) right--; if (left >= right) { s_count = left; break; } (s_list[right], s_list[left]) = (s_list[left], s_list[right]); left++; right--; } Debug.Assert(s_count >= 0 && s_count <= s_list.Length, "count not a legal index into list"); #if DEBUG // Check loop invariant. // We'd like to assert that any index < count contains a valid pointer, // but since garbage collection can happen at any time, it may have been broken // after we partitioned it. // // for (int i = 0; i < count; i++) { // Debug.Assert(list[i].Target is not null, "Null found on the left side of the list"); // } for (int i = s_count; i < s_list.Length; i++) { Debug.Assert(s_list[i]?.TryGetTarget(out _) == false, "Partitioning didn't work"); } #endif } private static void GarbageCollectList() { CleanOutBrokenLinks(); if (s_count / (float)s_list.Length > EXPAND_THRESHOLD) { Array.Resize(ref s_list, s_list.Length * EXPAND_FACTOR); Debug.Assert(s_list.Length < WARNING_SIZE, "SystemColorTracker is using way more memory than expected."); } } private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { // Update pens and brushes if (e.Category == UserPreferenceCategory.Color) { for (int i = 0; i < s_count; i++) { Debug.Assert(s_list[i] is not null, "null value in active part of list"); if (s_list[i]?.TryGetTarget(out ISystemColorTracker? target) == true) { target.OnSystemColorChanged(); } } } } }