Browse Source

Merge remote-tracking branch 'origin/master' into ios-ime-responder

pull/8963/head
Dan Walmsley 4 years ago
parent
commit
0f5145a193
  1. 2
      src/Avalonia.Base/Avalonia.Base.csproj
  2. 4
      src/Avalonia.Base/Input/KeyGesture.cs
  3. 6
      src/Avalonia.Base/Logging/TraceLogSink.cs
  4. 4
      src/Avalonia.Base/Media/BoxShadow.cs
  5. 6
      src/Avalonia.Base/Media/BoxShadows.cs
  6. 4
      src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs
  7. 4
      src/Avalonia.Base/Media/HslColor.cs
  8. 4
      src/Avalonia.Base/Media/HsvColor.cs
  9. 31
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  10. 2
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  11. 32
      src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
  12. 134
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  13. 10
      src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs
  14. 7
      src/Avalonia.Base/Styling/NthChildSelector.cs
  15. 6
      src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
  16. 6
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  17. 11
      src/Avalonia.Base/Threading/DispatcherPriority.cs
  18. 68
      src/Avalonia.Base/Utilities/StringBuilderCache.cs
  19. 1
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  20. 6
      src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs
  21. 8
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  22. 9
      src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs
  23. 6
      src/Avalonia.Controls/Documents/InlineCollection.cs
  24. 5
      src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs
  25. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  26. 6
      src/Windows/Avalonia.Win32/ClipboardFormats.cs
  27. 6
      src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
  28. 6
      src/Windows/Avalonia.Win32/OleDataObject.cs
  29. 1
      src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

2
src/Avalonia.Base/Avalonia.Base.csproj

@ -35,8 +35,10 @@
<InternalsVisibleTo Include="Avalonia.Skia.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)"/>
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup>

4
src/Avalonia.Base/Input/KeyGesture.cs

@ -96,7 +96,7 @@ namespace Avalonia.Input
public override string ToString()
{
var s = new StringBuilder();
var s = StringBuilderCache.Acquire();
static void Plus(StringBuilder s)
{
@ -132,7 +132,7 @@ namespace Avalonia.Input
Plus(s);
s.Append(Key);
return s.ToString();
return StringBuilderCache.GetStringAndRelease(s);
}
public bool Matches(KeyEventArgs keyEvent) =>

6
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;

4
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)

6
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
{
@ -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);
}

4
src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs

@ -77,7 +77,7 @@ namespace Avalonia.Media.Fonts
/// </returns>
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);
}
/// <summary>

4
src/Avalonia.Base/Media/HslColor.cs

@ -202,7 +202,7 @@ namespace Avalonia.Media
/// <inheritdoc/>
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);
}
/// <summary>

4
src/Avalonia.Base/Media/HsvColor.cs

@ -202,7 +202,7 @@ namespace Avalonia.Media
/// <inheritdoc/>
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);
}
/// <summary>

31
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<Visual> _dirty = new();
private HashSet<Visual> _recalculateChildren = new();
private bool _queuedUpdate;
private Action _update;
private Action _invalidateScene;
private Action<Task> _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;
}
/// <inheritdoc/>
@ -72,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate)
return;
_queuedUpdate = true;
_compositor.InvokeWhenReadyForNextCommit(_update);
_compositor.InvokeBeforeNextCommit(_update);
}
/// <inheritdoc/>
@ -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();
}
/// <summary>

2
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -129,7 +129,7 @@ namespace Avalonia.Rendering.Composition
/// </summary>
internal void ImmediateUIThreadRender()
{
Compositor.RequestCommitAsync();
Compositor.Commit();
Compositor.Server.Render();
}
}

32
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
{
/// <summary>
/// Creates a new CompositionTarget
/// </summary>
/// <param name="renderTargetFactory">A factory method to create IRenderTarget to be called from the render thread</param>
/// <returns></returns>
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> 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);
}

134
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 <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
@ -24,16 +25,18 @@ namespace Avalonia.Rendering.Composition
{
internal IRenderLoop Loop { get; }
private ServerCompositor _server;
private bool _implicitBatchCommitQueued;
private Action _implicitBatchCommit;
private TaskCompletionSource<int>? _nextCommit;
private Action _commit;
private BatchStreamObjectPool<object?> _batchObjectPool = new();
private BatchStreamMemoryPool _batchMemoryPool = new();
private List<CompositionObject> _objectsForSerialization = new();
private Queue<Action<Task>> _invokeBeforeCommit = new();
internal ServerCompositor Server => _server;
private Task? _pendingBatch;
private readonly object _pendingBatchLock = new();
internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new();
private Task? _lastBatchCompleted;
/// <summary>
/// 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));
}
/// <summary>
/// Creates a new CompositionTarget
/// </summary>
/// <param name="renderTargetFactory">A factory method to create IRenderTarget to be called from the render thread</param>
/// <returns></returns>
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory)
{
return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory));
}
/// <summary>
/// Requests pending changes in the composition objects to be serialized and sent to the render thread
/// </summary>
@ -66,7 +63,35 @@ namespace Avalonia.Rendering.Composition
public Task RequestCommitAsync()
{
Dispatcher.UIThread.VerifyAccess();
var batch = new Batch();
if (_nextCommit == null)
{
_nextCommit = new TaskCompletionSource<int>();
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<int>();
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<Task> 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();
}
}
}

10
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<BatchStreamData> _pool = new();
private readonly TaskCompletionSource<int>? _tcs;
public long SequenceId { get; }
public Batch()
public Batch(TaskCompletionSource<int>? tcs)
{
_tcs = tcs;
SequenceId = Interlocked.Increment(ref _nextSequenceId);
if (!_pool.TryTake(out var lst))
lst = new BatchStreamData();
Changes = lst;
}
private TaskCompletionSource<int> _tcs = new TaskCompletionSource<int>();
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;
}
}

7
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
{
@ -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);
}
}
}

6
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
@ -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;

6
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
@ -145,7 +145,7 @@ namespace Avalonia.Styling
private string BuildSelectorString()
{
var builder = new StringBuilder();
var builder = StringBuilderCache.Acquire();
if (_previous != null)
{
@ -185,7 +185,7 @@ namespace Avalonia.Styling
}
}
return builder.ToString();
return StringBuilderCache.GetStringAndRelease(builder);
}
}
}

11
src/Avalonia.Base/Threading/DispatcherPriority.cs

@ -61,21 +61,16 @@ namespace Avalonia.Threading
/// The job will be processed with the same priority as render.
/// </summary>
public static readonly DispatcherPriority Render = new(5);
/// <summary>
/// The job will be processed with the same priority as composition batch commit.
/// </summary>
public static readonly DispatcherPriority CompositionBatch = new(6);
/// <summary>
/// The job will be processed with the same priority as composition updates.
/// </summary>
public static readonly DispatcherPriority Composition = new(7);
public static readonly DispatcherPriority Composition = new(6);
/// <summary>
/// The job will be processed with the same priority as render.
/// </summary>
public static readonly DispatcherPriority Layout = new(8);
public static readonly DispatcherPriority Layout = new(7);
/// <summary>
/// The job will be processed with the same priority as data binding.
@ -85,7 +80,7 @@ namespace Avalonia.Threading
/// <summary>
/// The job will be processed before other asynchronous operations.
/// </summary>
public static readonly DispatcherPriority Send = new(9);
public static readonly DispatcherPriority Send = new(8);
/// <summary>
/// Maximum possible priority

68
src/Avalonia.Base/Utilities/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.Utilities;
// <summary>Provide a cached reusable instance of stringbuilder per thread.</summary>
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;
/// <summary>Get a StringBuilder for the specified capacity.</summary>
/// <remarks>If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.</remarks>
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);
}
/// <summary>Place the specified builder in the cache if it is not too big.</summary>
public static void Release(StringBuilder sb)
{
if (sb.Capacity <= MaxBuilderSize)
{
t_cachedInstance = sb;
}
}
/// <summary>ToString() the stringbuilder, Release it to the cache, and return the resulting string.</summary>
public static string GetStringAndRelease(StringBuilder sb)
{
string result = sb.ToString();
Release(sb);
return result;
}
}

1
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -50,6 +50,7 @@
<Compile Include="../Avalonia.Base/Utilities/MathUtilities.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
<Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>

6
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
{
@ -113,7 +113,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
@ -128,7 +128,7 @@ namespace Avalonia.Controls.Primitives
sb.Append(name[i]);
}
string displayName = sb.ToString();
string displayName = StringBuilderCache.GetStringAndRelease(sb);
lock (cacheMutex)
{

8
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -6020,7 +6020,7 @@ namespace Avalonia.Controls
/// <returns>The formatted string.</returns>
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++)
@ -6037,7 +6037,7 @@ namespace Avalonia.Controls
text.Append('\n');
}
}
return text.ToString();
return StringBuilderCache.GetStringAndRelease(text);
}
/// <summary>
@ -6052,7 +6052,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)
{
@ -6078,7 +6078,7 @@ namespace Avalonia.Controls
textBuilder.Append(FormatClipboardContent(itemArgs));
}
string text = textBuilder.ToString();
string text = StringBuilderCache.GetStringAndRelease(textBuilder);
if (!string.IsNullOrEmpty(text))
{

9
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
{
@ -62,7 +63,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 +99,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 +128,7 @@ namespace Avalonia.Controls.Converters
s.Append(ToOSXString(gesture.Key));
return s.ToString();
return StringBuilderCache.GetStringAndRelease(s);
}
private static string ToString(Key key)

6
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
{
@ -70,14 +70,14 @@ namespace Avalonia.Controls.Documents
{
get
{
var builder = new StringBuilder();
var builder = StringBuilderCache.Acquire();
foreach (var inline in this)
{
inline.AppendText(builder);
}
return builder.ToString();
return StringBuilderCache.GetStringAndRelease(builder);
}
}

5
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)
{
StringBuilder 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)

3
src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj

@ -7,6 +7,9 @@
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
</PropertyGroup>
<Import Project="IncludeXamlIlSre.props" />
<ItemGroup>
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
</ItemGroup>

6
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
{
@ -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;
}

6
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
@ -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()

6
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;
@ -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));
}
}
}

1
src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

@ -16,6 +16,7 @@
<ItemGroup>
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerExtensions\**\*.cs" />
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\AvaloniaXamlIlRuntimeCompiler.cs" />
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />

Loading…
Cancel
Save