From ba2747b897204d3c7389adbb7ddf97928ce1bf7d Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 7 Jul 2022 10:45:37 +0200 Subject: [PATCH 1/5] feat: StringBuilderCache --- src/Avalonia.Base/Avalonia.Base.csproj | 1 + src/Avalonia.Base/Input/KeyGesture.cs | 4 +- src/Avalonia.Base/Logging/TraceLogSink.cs | 6 +- src/Avalonia.Base/Media/BoxShadow.cs | 4 +- src/Avalonia.Base/Media/BoxShadows.cs | 4 +- .../Media/Fonts/FamilyNameCollection.cs | 4 +- src/Avalonia.Base/Media/HslColor.cs | 4 +- src/Avalonia.Base/Media/HsvColor.cs | 4 +- src/Avalonia.Base/StringBuilderCache.cs | 68 +++++++++++++++++++ src/Avalonia.Base/Styling/NthChildSelector.cs | 5 +- .../Styling/PropertyEqualsSelector.cs | 4 +- .../Styling/TypeNameAndClassSelector.cs | 4 +- .../Avalonia.Build.Tasks.csproj | 1 + .../Helpers/ColorHelper.cs | 4 +- src/Avalonia.Controls.DataGrid/DataGrid.cs | 8 +-- .../Converters/PlatformKeyGestureConverter.cs | 8 +-- .../Documents/InlineCollection.cs | 4 +- .../Diagnostics/VisualTreeDebug.cs | 2 +- .../Avalonia.Markup.Xaml.Loader.csproj | 3 + .../Avalonia.Win32/ClipboardFormats.cs | 4 +- .../Input/WindowsKeyboardDevice.cs | 4 +- src/Windows/Avalonia.Win32/OleDataObject.cs | 4 +- .../Avalonia.Designer.HostApp.csproj | 1 + 23 files changed, 115 insertions(+), 40 deletions(-) create mode 100644 src/Avalonia.Base/StringBuilderCache.cs diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index a07e0e3667..0018d40f66 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -31,6 +31,7 @@ + diff --git a/src/Avalonia.Base/Input/KeyGesture.cs b/src/Avalonia.Base/Input/KeyGesture.cs index 3b7a828b86..2123886cb1 100644 --- a/src/Avalonia.Base/Input/KeyGesture.cs +++ b/src/Avalonia.Base/Input/KeyGesture.cs @@ -106,7 +106,7 @@ namespace Avalonia.Input public override string ToString() { - var s = new StringBuilder(); + var s = StringBuilderCache.Acquire(); static void Plus(StringBuilder s) { @@ -142,7 +142,7 @@ namespace Avalonia.Input Plus(s); s.Append(Key); - return s.ToString(); + return StringBuilderCache.GetStringAndRelease(s); } public bool Matches(KeyEventArgs keyEvent) => diff --git a/src/Avalonia.Base/Logging/TraceLogSink.cs b/src/Avalonia.Base/Logging/TraceLogSink.cs index 05e4b8bc5a..fc3897fade 100644 --- a/src/Avalonia.Base/Logging/TraceLogSink.cs +++ b/src/Avalonia.Base/Logging/TraceLogSink.cs @@ -46,7 +46,7 @@ namespace Avalonia.Logging object? source, object?[]? values) { - var result = new StringBuilder(template.Length); + var result = StringBuilderCache.Acquire(template.Length); var r = new CharacterReader(template.AsSpan()); var i = 0; @@ -89,7 +89,7 @@ namespace Avalonia.Logging result.Append(')'); } - return result.ToString(); + return StringBuilderCache.GetStringAndRelease(result); } private static string Format( @@ -98,7 +98,7 @@ namespace Avalonia.Logging object? source, object?[] v) { - var result = new StringBuilder(template.Length); + var result = StringBuilderCache.Acquire(template.Length); var r = new CharacterReader(template.AsSpan()); var i = 0; diff --git a/src/Avalonia.Base/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs index b01f59f5f8..cc97d89cfc 100644 --- a/src/Avalonia.Base/Media/BoxShadow.cs +++ b/src/Avalonia.Base/Media/BoxShadow.cs @@ -80,7 +80,7 @@ namespace Avalonia.Media public override string ToString() { - var sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); if (IsEmpty) { @@ -114,7 +114,7 @@ namespace Avalonia.Media sb.AppendFormat(" {0}", Color.ToString()); - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); } public static unsafe BoxShadow Parse(string s) diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs index 4614ea4e3c..44288d89cf 100644 --- a/src/Avalonia.Base/Media/BoxShadows.cs +++ b/src/Avalonia.Base/Media/BoxShadows.cs @@ -45,7 +45,7 @@ namespace Avalonia.Media public override string ToString() { - var sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); if (Count == 0) { @@ -57,7 +57,7 @@ namespace Avalonia.Media sb.AppendFormat("{0} ", boxShadow.ToString()); } - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); } diff --git a/src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs b/src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs index 99daaf2143..eb42f6443b 100644 --- a/src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs @@ -77,7 +77,7 @@ namespace Avalonia.Media.Fonts /// public override string ToString() { - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(); for (var index = 0; index < Names.Count; index++) { @@ -91,7 +91,7 @@ namespace Avalonia.Media.Fonts builder.Append(", "); } - return builder.ToString(); + return StringBuilderCache.GetStringAndRelease(builder); } /// diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index e8a4d6f94f..485bb1db16 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -202,7 +202,7 @@ namespace Avalonia.Media /// public override string ToString() { - var sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); // Use a format similar to CSS. However: // - To ensure precision is never lost, allow decimal places. @@ -225,7 +225,7 @@ namespace Avalonia.Media sb.Append(A.ToString(CultureInfo.InvariantCulture)); sb.Append(')'); - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); } /// diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 924ef4778b..512e57ae07 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -202,7 +202,7 @@ namespace Avalonia.Media /// public override string ToString() { - var sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); // Use a format similar to CSS. However: // - To ensure precision is never lost, allow decimal places. @@ -225,7 +225,7 @@ namespace Avalonia.Media sb.Append(A.ToString(CultureInfo.InvariantCulture)); sb.Append(')'); - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); } /// diff --git a/src/Avalonia.Base/StringBuilderCache.cs b/src/Avalonia.Base/StringBuilderCache.cs new file mode 100644 index 0000000000..060d76090a --- /dev/null +++ b/src/Avalonia.Base/StringBuilderCache.cs @@ -0,0 +1,68 @@ +// This file is imported from dotnet/runtime +// Source Link: https://github.com/dotnet/runtime/blob/e63d21947e734db2da5093510a6636b5b7fb45b5/src/libraries/Common/src/System/Text/StringBuilderCache.cs +// Commit: a9c5ead on Feb 10, 2021, https://github.com/dotnet/runtime/commit/a9c5eadd951dcba73167f72cc624eb790573663a +// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Text; + +namespace Avalonia; + +// Provide a cached reusable instance of stringbuilder per thread. +internal static class StringBuilderCache +{ + // The value 360 was chosen in discussion with performance experts as a compromise between using + // as little memory per thread as possible and still covering a large part of short-lived + // StringBuilder creations on the startup path of VS designers. + internal const int MaxBuilderSize = 360; + private const int DefaultCapacity = 16; // == StringBuilder.DefaultCapacity + + // WARNING: We allow diagnostic tools to directly inspect this member (t_cachedInstance). + // See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details. + // Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools. + // Get in touch with the diagnostics team if you have questions. + [ThreadStatic] + private static StringBuilder? t_cachedInstance; + + /// Get a StringBuilder for the specified capacity. + /// If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied. + public static StringBuilder Acquire(int capacity = DefaultCapacity) + { + if (capacity <= MaxBuilderSize) + { + StringBuilder? sb = t_cachedInstance; + if (sb != null) + { + // Avoid stringbuilder block fragmentation by getting a new StringBuilder + // when the requested size is larger than the current capacity + if (capacity <= sb.Capacity) + { + t_cachedInstance = null; + sb.Clear(); + return sb; + } + } + } + + return new StringBuilder(capacity); + } + + /// Place the specified builder in the cache if it is not too big. + public static void Release(StringBuilder sb) + { + if (sb.Capacity <= MaxBuilderSize) + { + t_cachedInstance = sb; + } + } + + /// ToString() the stringbuilder, Release it to the cache, and return the resulting string. + public static string GetStringAndRelease(StringBuilder sb) + { + string result = sb.ToString(); + Release(sb); + return result; + } +} diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index 047bf434da..a7af27f4bf 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -110,7 +110,8 @@ namespace Avalonia.Styling public override string ToString() { var expectedCapacity = NthLastChildSelectorName.Length + 8; - var stringBuilder = new StringBuilder(_previous?.ToString(), expectedCapacity); + var stringBuilder = StringBuilderCache.Acquire(expectedCapacity); + stringBuilder.Append(_previous?.ToString()); stringBuilder.Append(':'); stringBuilder.Append(_reversed ? NthLastChildSelectorName : NthChildSelectorName); @@ -140,7 +141,7 @@ namespace Avalonia.Styling stringBuilder.Append(')'); - return stringBuilder.ToString(); + return StringBuilderCache.GetStringAndRelease(stringBuilder); } } } diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 7a37daf087..6663ed8887 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -42,7 +42,7 @@ namespace Avalonia.Styling { if (_selectorString == null) { - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(); if (_previous != null) { @@ -67,7 +67,7 @@ namespace Avalonia.Styling builder.Append(_value ?? string.Empty); builder.Append(']'); - _selectorString = builder.ToString(); + _selectorString = StringBuilderCache.GetStringAndRelease(builder); } return _selectorString; diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 24d5d6bbbf..5f004e91df 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -144,7 +144,7 @@ namespace Avalonia.Styling private string BuildSelectorString() { - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(); if (_previous != null) { @@ -184,7 +184,7 @@ namespace Avalonia.Styling } } - return builder.ToString(); + return StringBuilderCache.GetStringAndRelease(builder); } } } diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index a801d338c3..1d717d5694 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -50,6 +50,7 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) + Markup/%(RecursiveDir)%(FileName)%(Extension) diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs index 32a898ee71..38fa58e7bb 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs @@ -109,7 +109,7 @@ namespace Avalonia.Controls.Primitives // Cache results for next time as well if (closestKnownColor != KnownColor.None) { - StringBuilder sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); string name = closestKnownColor.ToString(); // Add spaces converting PascalCase to human-readable names @@ -124,7 +124,7 @@ namespace Avalonia.Controls.Primitives sb.Append(name[i]); } - string displayName = sb.ToString(); + string displayName = StringBuilderCache.GetStringAndRelease(sb); lock (cacheMutex) { diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index d42468f47e..554b1c371b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -5990,7 +5990,7 @@ namespace Avalonia.Controls /// The formatted string. private string FormatClipboardContent(DataGridRowClipboardEventArgs e) { - var text = new StringBuilder(); + var text = StringBuilderCache.Acquire(); var clipboardRowContent = e.ClipboardRowContent; var numberOfItem = clipboardRowContent.Count; for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++) @@ -6007,7 +6007,7 @@ namespace Avalonia.Controls text.Append('\n'); } } - return text.ToString(); + return StringBuilderCache.GetStringAndRelease(text); } /// @@ -6022,7 +6022,7 @@ namespace Avalonia.Controls if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0) { - StringBuilder textBuilder = new StringBuilder(); + var textBuilder = StringBuilderCache.Acquire(); if (ClipboardCopyMode == DataGridClipboardCopyMode.IncludeHeader) { @@ -6048,7 +6048,7 @@ namespace Avalonia.Controls textBuilder.Append(FormatClipboardContent(itemArgs)); } - string text = textBuilder.ToString(); + string text = StringBuilderCache.GetStringAndRelease(textBuilder); if (!string.IsNullOrEmpty(text)) { diff --git a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs index 9a657cce68..47c2f94e18 100644 --- a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs +++ b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs @@ -62,7 +62,7 @@ namespace Avalonia.Controls.Converters private static string ToString(KeyGesture gesture, string meta) { - var s = new StringBuilder(); + var s = StringBuilderCache.Acquire(); static void Plus(StringBuilder s) { @@ -98,12 +98,12 @@ namespace Avalonia.Controls.Converters Plus(s); s.Append(ToString(gesture.Key)); - return s.ToString(); + return StringBuilderCache.GetStringAndRelease(s); } private static string ToOSXString(KeyGesture gesture) { - var s = new StringBuilder(); + var s = StringBuilderCache.Acquire(); if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control)) { @@ -127,7 +127,7 @@ namespace Avalonia.Controls.Converters s.Append(ToOSXString(gesture.Key)); - return s.ToString(); + return StringBuilderCache.GetStringAndRelease(s); } private static string ToString(Key key) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index dc688fc359..11225a87a1 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -78,14 +78,14 @@ namespace Avalonia.Controls.Documents return _text; } - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(); foreach (var inline in this) { inline.AppendText(builder); } - return builder.ToString(); + return StringBuilderCache.GetStringAndRelease(builder); } set { diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs index 4adcd32302..d1f871d76f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs @@ -10,7 +10,7 @@ namespace Avalonia.Diagnostics { public static string PrintVisualTree(IVisual visual) { - StringBuilder result = new StringBuilder(); + var result = new StringBuilder(); PrintVisualTree(visual, result, 0); return result.ToString(); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj index b89ea8399a..0b6b77e540 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj @@ -7,6 +7,9 @@ $(DefineConstants);XAMLX_INTERNAL + + + diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs index 7538dedfca..f5b8cd6b96 100644 --- a/src/Windows/Avalonia.Win32/ClipboardFormats.cs +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -35,9 +35,9 @@ namespace Avalonia.Win32 private static string QueryFormatName(ushort format) { - StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH); + var sb = StringBuilderCache.Acquire(MAX_FORMAT_NAME_LENGTH); if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0) - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); return null; } diff --git a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs index 1258bb0109..878011b5aa 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs @@ -49,7 +49,7 @@ namespace Avalonia.Win32.Input public string StringFromVirtualKey(uint virtualKey) { - StringBuilder result = new StringBuilder(256); + var result = StringBuilderCache.Acquire(256); int length = UnmanagedMethods.ToUnicode( virtualKey, 0, @@ -57,7 +57,7 @@ namespace Avalonia.Win32.Input result, 256, 0); - return result.ToString(); + return StringBuilderCache.GetStringAndRelease(result); } private void UpdateKeyStates() diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index ba17177473..837b21e34f 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -103,11 +103,11 @@ namespace Avalonia.Win32 for (int i = 0; i < fileCount; i++) { int pathLen = UnmanagedMethods.DragQueryFile(hGlobal, i, null, 0); - StringBuilder sb = new StringBuilder(pathLen+1); + var sb = StringBuilderCache.Acquire(pathLen+1); if (UnmanagedMethods.DragQueryFile(hGlobal, i, sb, sb.Capacity) == pathLen) { - files.Add(sb.ToString()); + files.Add(StringBuilderCache.GetStringAndRelease(sb)); } } } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 1cf68c1605..3dfef234a9 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -16,6 +16,7 @@ + From 01e7bd2a523c2f119d1ccace9975de10e103c5d0 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 1 Aug 2022 09:51:58 +0200 Subject: [PATCH 2/5] fix: move StringBuilderCache to Avalonia.Utilities --- src/Avalonia.Base/Media/BoxShadows.cs | 2 +- src/Avalonia.Base/Styling/NthChildSelector.cs | 2 +- src/Avalonia.Base/Styling/PropertyEqualsSelector.cs | 2 +- src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs | 2 +- src/Avalonia.Base/{ => Utilities}/StringBuilderCache.cs | 2 +- src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj | 2 +- src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs | 2 +- src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs | 1 + src/Avalonia.Controls/Documents/InlineCollection.cs | 2 +- src/Windows/Avalonia.Win32/ClipboardFormats.cs | 2 +- src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs | 2 +- src/Windows/Avalonia.Win32/OleDataObject.cs | 2 +- .../Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) rename src/Avalonia.Base/{ => Utilities}/StringBuilderCache.cs (98%) diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs index 44288d89cf..ab2694389f 100644 --- a/src/Avalonia.Base/Media/BoxShadows.cs +++ b/src/Avalonia.Base/Media/BoxShadows.cs @@ -1,7 +1,7 @@ using System; using System.ComponentModel; -using System.Text; using Avalonia.Animation.Animators; +using Avalonia.Utilities; namespace Avalonia.Media { diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index a7ba79696e..c872a40ad4 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -1,8 +1,8 @@ #nullable enable using System; -using System.Text; using Avalonia.LogicalTree; using Avalonia.Styling.Activators; +using Avalonia.Utilities; namespace Avalonia.Styling { diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 8922b939f7..e98ff3f9c9 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -1,8 +1,8 @@ using System; using System.ComponentModel; using System.Globalization; -using System.Text; using Avalonia.Styling.Activators; +using Avalonia.Utilities; #nullable enable diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 698d96d0aa..1833f0d133 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Text; using Avalonia.Controls; using Avalonia.Styling.Activators; +using Avalonia.Utilities; #nullable enable diff --git a/src/Avalonia.Base/StringBuilderCache.cs b/src/Avalonia.Base/Utilities/StringBuilderCache.cs similarity index 98% rename from src/Avalonia.Base/StringBuilderCache.cs rename to src/Avalonia.Base/Utilities/StringBuilderCache.cs index 060d76090a..be8b24c848 100644 --- a/src/Avalonia.Base/StringBuilderCache.cs +++ b/src/Avalonia.Base/Utilities/StringBuilderCache.cs @@ -8,7 +8,7 @@ using System; using System.Text; -namespace Avalonia; +namespace Avalonia.Utilities; // Provide a cached reusable instance of stringbuilder per thread. internal static class StringBuilderCache diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index a5e38a86a2..7e1cb76911 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -50,7 +50,7 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) - + Markup/%(RecursiveDir)%(FileName)%(Extension) diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs index 68aa30ea7a..c1a03b1b77 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Collections.Generic; using Avalonia.Media; -using System.Text; +using Avalonia.Utilities; namespace Avalonia.Controls.Primitives { diff --git a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs index 47c2f94e18..0fa43809ac 100644 --- a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs +++ b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Text; using Avalonia.Data.Converters; using Avalonia.Input; +using Avalonia.Utilities; namespace Avalonia.Controls.Converters { diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 81ccff4cbd..190373169b 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -1,8 +1,8 @@ using System; -using System.Text; using Avalonia.Collections; using Avalonia.LogicalTree; using Avalonia.Metadata; +using Avalonia.Utilities; namespace Avalonia.Controls.Documents { diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs index f5b8cd6b96..7bd7765f8c 100644 --- a/src/Windows/Avalonia.Win32/ClipboardFormats.cs +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Text; using Avalonia.Input; using Avalonia.Win32.Interop; +using Avalonia.Utilities; namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs index 878011b5aa..7e1e22579b 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs @@ -1,6 +1,6 @@ -using System.Text; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Utilities; using Avalonia.Win32.Interop; namespace Avalonia.Win32.Input diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 837b21e34f..f7345b3ff7 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -6,9 +6,9 @@ using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Serialization.Formatters.Binary; -using System.Text; using Avalonia.Input; using Avalonia.MicroCom; +using Avalonia.Utilities; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Input.IDataObject; diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 3dfef234a9..3293066dd1 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -16,7 +16,7 @@ - + From 3fb4e926430f275c1fd7ac03644b35a7fe0fcb79 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 1 Aug 2022 10:00:24 +0200 Subject: [PATCH 3/5] fix: PrintVisualTree using StringBuilderCache --- src/Avalonia.Base/Avalonia.Base.csproj | 1 + src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index f722330db6..ede79c2943 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs index d1f871d76f..13c8e070e8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs @@ -2,6 +2,7 @@ using System; using System.Text; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia.Diagnostics @@ -10,9 +11,9 @@ namespace Avalonia.Diagnostics { public static string PrintVisualTree(IVisual visual) { - var result = new StringBuilder(); + var result = StringBuilderCache.Acquire(); PrintVisualTree(visual, result, 0); - return result.ToString(); + return StringBuilderCache.GetStringAndRelease(result); } private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent) From 3db08330124ecee997b129dca0e77dbffa173f56 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 1 Aug 2022 10:20:13 +0200 Subject: [PATCH 4/5] fix: Avalonia.Markup.Xaml.Loader --- .../Avalonia.Markup.Xaml.Loader.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj index 0b6b77e540..ce931b9c14 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj @@ -8,7 +8,7 @@ - + From 5ec011f0297b027d5c463b61ec80fca32db55116 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 20 Sep 2022 15:33:52 +0300 Subject: [PATCH 5/5] Moved commit/frame throttling logic to Compositor --- .../Composition/CompositingRenderer.cs | 31 ++-- .../Composition/CompositionTarget.cs | 2 +- .../Composition/Compositor.Factories.cs | 32 +++++ .../Rendering/Composition/Compositor.cs | 134 ++++++++---------- .../Rendering/Composition/Transport/Batch.cs | 10 +- .../Threading/DispatcherPriority.cs | 11 +- 6 files changed, 117 insertions(+), 103 deletions(-) create mode 100644 src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index e47c8e7e31..98a6a3600e 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Collections.Pooled; using Avalonia.Media; @@ -27,8 +28,7 @@ public class CompositingRenderer : IRendererWithCompositor private HashSet _dirty = new(); private HashSet _recalculateChildren = new(); private bool _queuedUpdate; - private Action _update; - private Action _invalidateScene; + private Action _update; private bool _updating; internal CompositionTarget CompositionTarget; @@ -47,7 +47,6 @@ public class CompositingRenderer : IRendererWithCompositor CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget); CompositionTarget.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); _update = Update; - _invalidateScene = InvalidateScene; } /// @@ -72,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor if(_queuedUpdate) return; _queuedUpdate = true; - _compositor.InvokeWhenReadyForNextCommit(_update); + _compositor.InvokeBeforeNextCommit(_update); } /// @@ -151,12 +150,6 @@ public class CompositingRenderer : IRendererWithCompositor if (compositionChildren.Count == visualChildren.Count) { bool mismatch = false; - if (v.HasNonUniformZIndexChildren) - { - - - } - if (sortedChildren != null) for (var c = 0; c < visualChildren.Count; c++) { @@ -202,9 +195,6 @@ public class CompositingRenderer : IRendererWithCompositor } } - private void InvalidateScene() => - SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize))); - private void UpdateCore() { _queuedUpdate = false; @@ -252,10 +242,15 @@ public class CompositingRenderer : IRendererWithCompositor _recalculateChildren.Clear(); CompositionTarget.Size = _root.ClientSize; CompositionTarget.Scaling = _root.RenderScaling; - Compositor.InvokeOnNextCommit(_invalidateScene); } - private void Update() + private async void TriggerSceneInvalidatedOnBatchCompletion(Task batchCompletion) + { + await batchCompletion; + SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize))); + } + + private void Update(Task batchCompletion) { if(_updating) return; @@ -276,10 +271,10 @@ public class CompositingRenderer : IRendererWithCompositor public void Paint(Rect rect) { - Update(); + QueueUpdate(); CompositionTarget.RequestRedraw(); if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground) - Compositor.RequestCommitAsync().Wait(); + Compositor.Commit().Wait(); else CompositionTarget.ImmediateUIThreadRender(); } @@ -299,7 +294,7 @@ public class CompositingRenderer : IRendererWithCompositor // Wait for the composition batch to be applied and rendered to guarantee that // render target is not used anymore and can be safely disposed if (Compositor.Loop.RunsInBackground) - _compositor.RequestCommitAsync().Wait(); + _compositor.Commit().Wait(); } /// diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index eb499604e0..d8a608651b 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -129,7 +129,7 @@ namespace Avalonia.Rendering.Composition /// internal void ImmediateUIThreadRender() { - Compositor.RequestCommitAsync(); + Compositor.Commit(); Compositor.Server.Render(); } } diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs new file mode 100644 index 0000000000..1a13d23acd --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs @@ -0,0 +1,32 @@ +using System; +using Avalonia.Platform; +using Avalonia.Rendering.Composition.Animations; +using Avalonia.Rendering.Composition.Server; + +namespace Avalonia.Rendering.Composition; + +public partial class Compositor +{ + /// + /// Creates a new CompositionTarget + /// + /// A factory method to create IRenderTarget to be called from the render thread + /// + public CompositionTarget CreateCompositionTarget(Func renderTargetFactory) + { + return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory)); + } + + public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server)); + + public ExpressionAnimation CreateExpressionAnimation() => new ExpressionAnimation(this); + + public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this) + { + Expression = expression + }; + + public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this); + + public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this); +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index 10360f7874..21c7dd6bf9 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -10,6 +10,7 @@ using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Transport; using Avalonia.Threading; +using Avalonia.Utilities; // Special license applies License.md @@ -24,16 +25,18 @@ namespace Avalonia.Rendering.Composition { internal IRenderLoop Loop { get; } private ServerCompositor _server; - private bool _implicitBatchCommitQueued; - private Action _implicitBatchCommit; + private TaskCompletionSource? _nextCommit; + private Action _commit; private BatchStreamObjectPool _batchObjectPool = new(); private BatchStreamMemoryPool _batchMemoryPool = new(); private List _objectsForSerialization = new(); + private Queue> _invokeBeforeCommit = new(); internal ServerCompositor Server => _server; + private Task? _pendingBatch; + private readonly object _pendingBatchLock = new(); + internal IEasing DefaultEasing { get; } - private List? _invokeOnNextCommit; - private readonly Stack> _invokeListPool = new(); - private Task? _lastBatchCompleted; + /// /// Creates a new compositor on a specified render loop that would use a particular GPU @@ -44,21 +47,15 @@ namespace Avalonia.Rendering.Composition { Loop = loop; _server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool); - _implicitBatchCommit = ImplicitBatchCommit; + _commit = () => + { + Console.WriteLine("Dispatcher:Commit"); + Commit(); + }; DefaultEasing = new CubicBezierEasing(new Point(0.25f, 0.1f), new Point(0.25f, 1f)); } - /// - /// Creates a new CompositionTarget - /// - /// A factory method to create IRenderTarget to be called from the render thread - /// - public CompositionTarget CreateCompositionTarget(Func renderTargetFactory) - { - return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory)); - } - /// /// Requests pending changes in the composition objects to be serialized and sent to the render thread /// @@ -66,7 +63,35 @@ namespace Avalonia.Rendering.Composition public Task RequestCommitAsync() { Dispatcher.UIThread.VerifyAccess(); - var batch = new Batch(); + if (_nextCommit == null) + { + _nextCommit = new TaskCompletionSource(); + var pending = _pendingBatch; + if (pending != null) + { + pending.ContinueWith(_ => + { + Dispatcher.UIThread.Post(_commit, DispatcherPriority.Composition); + }); + } + else + Dispatcher.UIThread.Post(_commit, DispatcherPriority.Composition); + } + + return _nextCommit.Task; + } + + internal Task Commit() + { + Dispatcher.UIThread.VerifyAccess(); + using var noPump = NonPumpingLockHelper.Use(); + + _nextCommit ??= new TaskCompletionSource(); + + while (_invokeBeforeCommit.Count > 0) + _invokeBeforeCommit.Dequeue()(_nextCommit.Task); + + var batch = new Batch(_nextCommit); using (var writer = new BatchStreamWriter(batch.Changes, _batchMemoryPool, _batchObjectPool)) { @@ -84,71 +109,36 @@ namespace Avalonia.Rendering.Composition batch.CommitedAt = Server.Clock.Elapsed; _server.EnqueueBatch(batch); - if (_invokeOnNextCommit != null) - ScheduleCommitCallbacks(batch.Completed); - return _lastBatchCompleted = batch.Completed; - } - - async void ScheduleCommitCallbacks(Task task) - { - var list = _invokeOnNextCommit; - _invokeOnNextCommit = null; - await task; - foreach (var i in list!) - i(); - list.Clear(); - _invokeListPool.Push(list); - } - - public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server)); - - public ExpressionAnimation CreateExpressionAnimation() => new ExpressionAnimation(this); - - public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this) - { - Expression = expression - }; - - public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this); - - public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this); - - private void QueueImplicitBatchCommit() - { - if(_implicitBatchCommitQueued) - return; - _implicitBatchCommitQueued = true; - Dispatcher.UIThread.Post(_implicitBatchCommit, DispatcherPriority.CompositionBatch); - } - - private void ImplicitBatchCommit() - { - _implicitBatchCommitQueued = false; - RequestCommitAsync(); + lock (_pendingBatchLock) + { + _pendingBatch = _nextCommit.Task; + _pendingBatch.ContinueWith(t => + { + lock (_pendingBatchLock) + { + if (_pendingBatch == t) + _pendingBatch = null; + } + }, TaskContinuationOptions.ExecuteSynchronously); + _nextCommit = null; + + return _pendingBatch; + } } internal void RegisterForSerialization(CompositionObject compositionObject) { Dispatcher.UIThread.VerifyAccess(); _objectsForSerialization.Add(compositionObject); - QueueImplicitBatchCommit(); - } - - internal void InvokeOnNextCommit(Action action) - { - _invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new(); - _invokeOnNextCommit.Add(action); + RequestCommitAsync(); } - public void InvokeWhenReadyForNextCommit(Action action) + internal void InvokeBeforeNextCommit(Action action) { - if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted) - Dispatcher.UIThread.Post(action, DispatcherPriority.Composition); - else - _lastBatchCompleted.ContinueWith( - static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition), - action); + Dispatcher.UIThread.VerifyAccess(); + _invokeBeforeCommit.Enqueue(action); + RequestCommitAsync(); } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs index 0cf1650ccf..d3e3664f84 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs @@ -15,16 +15,19 @@ namespace Avalonia.Rendering.Composition.Transport { private static long _nextSequenceId = 1; private static ConcurrentBag _pool = new(); + private readonly TaskCompletionSource? _tcs; public long SequenceId { get; } - public Batch() + public Batch(TaskCompletionSource? tcs) { + _tcs = tcs; SequenceId = Interlocked.Increment(ref _nextSequenceId); if (!_pool.TryTake(out var lst)) lst = new BatchStreamData(); Changes = lst; } - private TaskCompletionSource _tcs = new TaskCompletionSource(); + + public BatchStreamData Changes { get; private set; } public TimeSpan CommitedAt { get; set; } @@ -33,9 +36,8 @@ namespace Avalonia.Rendering.Composition.Transport _pool.Add(Changes); Changes = null!; - _tcs.TrySetResult(0); + _tcs?.TrySetResult(0); } - public Task Completed => _tcs.Task; } } diff --git a/src/Avalonia.Base/Threading/DispatcherPriority.cs b/src/Avalonia.Base/Threading/DispatcherPriority.cs index b4bf603f74..f041590fc0 100644 --- a/src/Avalonia.Base/Threading/DispatcherPriority.cs +++ b/src/Avalonia.Base/Threading/DispatcherPriority.cs @@ -61,21 +61,16 @@ namespace Avalonia.Threading /// The job will be processed with the same priority as render. /// public static readonly DispatcherPriority Render = new(5); - - /// - /// The job will be processed with the same priority as composition batch commit. - /// - public static readonly DispatcherPriority CompositionBatch = new(6); /// /// The job will be processed with the same priority as composition updates. /// - public static readonly DispatcherPriority Composition = new(7); + public static readonly DispatcherPriority Composition = new(6); /// /// The job will be processed with the same priority as render. /// - public static readonly DispatcherPriority Layout = new(8); + public static readonly DispatcherPriority Layout = new(7); /// /// The job will be processed with the same priority as data binding. @@ -85,7 +80,7 @@ namespace Avalonia.Threading /// /// The job will be processed before other asynchronous operations. /// - public static readonly DispatcherPriority Send = new(9); + public static readonly DispatcherPriority Send = new(8); /// /// Maximum possible priority