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.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.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.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)"/>
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" /> <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup> </ItemGroup>

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

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

6
src/Avalonia.Base/Logging/TraceLogSink.cs

@ -46,7 +46,7 @@ namespace Avalonia.Logging
object? source, object? source,
object?[]? values) object?[]? values)
{ {
var result = new StringBuilder(template.Length); var result = StringBuilderCache.Acquire(template.Length);
var r = new CharacterReader(template.AsSpan()); var r = new CharacterReader(template.AsSpan());
var i = 0; var i = 0;
@ -89,7 +89,7 @@ namespace Avalonia.Logging
result.Append(')'); result.Append(')');
} }
return result.ToString(); return StringBuilderCache.GetStringAndRelease(result);
} }
private static string Format( private static string Format(
@ -98,7 +98,7 @@ namespace Avalonia.Logging
object? source, object? source,
object?[] v) object?[] v)
{ {
var result = new StringBuilder(template.Length); var result = StringBuilderCache.Acquire(template.Length);
var r = new CharacterReader(template.AsSpan()); var r = new CharacterReader(template.AsSpan());
var i = 0; var i = 0;

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

@ -80,7 +80,7 @@ namespace Avalonia.Media
public override string ToString() public override string ToString()
{ {
var sb = new StringBuilder(); var sb = StringBuilderCache.Acquire();
if (IsEmpty) if (IsEmpty)
{ {
@ -114,7 +114,7 @@ namespace Avalonia.Media
sb.AppendFormat(" {0}", Color.ToString()); sb.AppendFormat(" {0}", Color.ToString());
return sb.ToString(); return StringBuilderCache.GetStringAndRelease(sb);
} }
public static unsafe BoxShadow Parse(string s) public static unsafe BoxShadow Parse(string s)

6
src/Avalonia.Base/Media/BoxShadows.cs

@ -1,7 +1,7 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Text;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using Avalonia.Utilities;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -45,7 +45,7 @@ namespace Avalonia.Media
public override string ToString() public override string ToString()
{ {
var sb = new StringBuilder(); var sb = StringBuilderCache.Acquire();
if (Count == 0) if (Count == 0)
{ {
@ -57,7 +57,7 @@ namespace Avalonia.Media
sb.AppendFormat("{0} ", boxShadow.ToString()); 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> /// </returns>
public override string ToString() public override string ToString()
{ {
var builder = new StringBuilder(); var builder = StringBuilderCache.Acquire();
for (var index = 0; index < Names.Count; index++) for (var index = 0; index < Names.Count; index++)
{ {
@ -91,7 +91,7 @@ namespace Avalonia.Media.Fonts
builder.Append(", "); builder.Append(", ");
} }
return builder.ToString(); return StringBuilderCache.GetStringAndRelease(builder);
} }
/// <summary> /// <summary>

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

@ -202,7 +202,7 @@ namespace Avalonia.Media
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
var sb = new StringBuilder(); var sb = StringBuilderCache.Acquire();
// Use a format similar to CSS. However: // Use a format similar to CSS. However:
// - To ensure precision is never lost, allow decimal places. // - To ensure precision is never lost, allow decimal places.
@ -225,7 +225,7 @@ namespace Avalonia.Media
sb.Append(A.ToString(CultureInfo.InvariantCulture)); sb.Append(A.ToString(CultureInfo.InvariantCulture));
sb.Append(')'); sb.Append(')');
return sb.ToString(); return StringBuilderCache.GetStringAndRelease(sb);
} }
/// <summary> /// <summary>

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

@ -202,7 +202,7 @@ namespace Avalonia.Media
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
var sb = new StringBuilder(); var sb = StringBuilderCache.Acquire();
// Use a format similar to CSS. However: // Use a format similar to CSS. However:
// - To ensure precision is never lost, allow decimal places. // - To ensure precision is never lost, allow decimal places.
@ -225,7 +225,7 @@ namespace Avalonia.Media
sb.Append(A.ToString(CultureInfo.InvariantCulture)); sb.Append(A.ToString(CultureInfo.InvariantCulture));
sb.Append(')'); sb.Append(')');
return sb.ToString(); return StringBuilderCache.GetStringAndRelease(sb);
} }
/// <summary> /// <summary>

31
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Collections.Pooled; using Avalonia.Collections.Pooled;
using Avalonia.Media; using Avalonia.Media;
@ -27,8 +28,7 @@ public class CompositingRenderer : IRendererWithCompositor
private HashSet<Visual> _dirty = new(); private HashSet<Visual> _dirty = new();
private HashSet<Visual> _recalculateChildren = new(); private HashSet<Visual> _recalculateChildren = new();
private bool _queuedUpdate; private bool _queuedUpdate;
private Action _update; private Action<Task> _update;
private Action _invalidateScene;
private bool _updating; private bool _updating;
internal CompositionTarget CompositionTarget; internal CompositionTarget CompositionTarget;
@ -47,7 +47,6 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget); CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget);
CompositionTarget.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); CompositionTarget.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor);
_update = Update; _update = Update;
_invalidateScene = InvalidateScene;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -72,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate) if(_queuedUpdate)
return; return;
_queuedUpdate = true; _queuedUpdate = true;
_compositor.InvokeWhenReadyForNextCommit(_update); _compositor.InvokeBeforeNextCommit(_update);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -151,12 +150,6 @@ public class CompositingRenderer : IRendererWithCompositor
if (compositionChildren.Count == visualChildren.Count) if (compositionChildren.Count == visualChildren.Count)
{ {
bool mismatch = false; bool mismatch = false;
if (v.HasNonUniformZIndexChildren)
{
}
if (sortedChildren != null) if (sortedChildren != null)
for (var c = 0; c < visualChildren.Count; c++) 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() private void UpdateCore()
{ {
_queuedUpdate = false; _queuedUpdate = false;
@ -252,10 +242,15 @@ public class CompositingRenderer : IRendererWithCompositor
_recalculateChildren.Clear(); _recalculateChildren.Clear();
CompositionTarget.Size = _root.ClientSize; CompositionTarget.Size = _root.ClientSize;
CompositionTarget.Scaling = _root.RenderScaling; 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) if(_updating)
return; return;
@ -276,10 +271,10 @@ public class CompositingRenderer : IRendererWithCompositor
public void Paint(Rect rect) public void Paint(Rect rect)
{ {
Update(); QueueUpdate();
CompositionTarget.RequestRedraw(); CompositionTarget.RequestRedraw();
if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground) if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground)
Compositor.RequestCommitAsync().Wait(); Compositor.Commit().Wait();
else else
CompositionTarget.ImmediateUIThreadRender(); CompositionTarget.ImmediateUIThreadRender();
} }
@ -299,7 +294,7 @@ public class CompositingRenderer : IRendererWithCompositor
// Wait for the composition batch to be applied and rendered to guarantee that // Wait for the composition batch to be applied and rendered to guarantee that
// render target is not used anymore and can be safely disposed // render target is not used anymore and can be safely disposed
if (Compositor.Loop.RunsInBackground) if (Compositor.Loop.RunsInBackground)
_compositor.RequestCommitAsync().Wait(); _compositor.Commit().Wait();
} }
/// <summary> /// <summary>

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

@ -129,7 +129,7 @@ namespace Avalonia.Rendering.Composition
/// </summary> /// </summary>
internal void ImmediateUIThreadRender() internal void ImmediateUIThreadRender()
{ {
Compositor.RequestCommitAsync(); Compositor.Commit();
Compositor.Server.Render(); 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.Server;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
using Avalonia.Threading; 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> // 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; } internal IRenderLoop Loop { get; }
private ServerCompositor _server; private ServerCompositor _server;
private bool _implicitBatchCommitQueued; private TaskCompletionSource<int>? _nextCommit;
private Action _implicitBatchCommit; private Action _commit;
private BatchStreamObjectPool<object?> _batchObjectPool = new(); private BatchStreamObjectPool<object?> _batchObjectPool = new();
private BatchStreamMemoryPool _batchMemoryPool = new(); private BatchStreamMemoryPool _batchMemoryPool = new();
private List<CompositionObject> _objectsForSerialization = new(); private List<CompositionObject> _objectsForSerialization = new();
private Queue<Action<Task>> _invokeBeforeCommit = new();
internal ServerCompositor Server => _server; internal ServerCompositor Server => _server;
private Task? _pendingBatch;
private readonly object _pendingBatchLock = new();
internal IEasing DefaultEasing { get; } internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new();
private Task? _lastBatchCompleted;
/// <summary> /// <summary>
/// Creates a new compositor on a specified render loop that would use a particular GPU /// 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; Loop = loop;
_server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool); _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)); 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> /// <summary>
/// Requests pending changes in the composition objects to be serialized and sent to the render thread /// Requests pending changes in the composition objects to be serialized and sent to the render thread
/// </summary> /// </summary>
@ -66,7 +63,35 @@ namespace Avalonia.Rendering.Composition
public Task RequestCommitAsync() public Task RequestCommitAsync()
{ {
Dispatcher.UIThread.VerifyAccess(); 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)) using (var writer = new BatchStreamWriter(batch.Changes, _batchMemoryPool, _batchObjectPool))
{ {
@ -84,71 +109,36 @@ namespace Avalonia.Rendering.Composition
batch.CommitedAt = Server.Clock.Elapsed; batch.CommitedAt = Server.Clock.Elapsed;
_server.EnqueueBatch(batch); _server.EnqueueBatch(batch);
if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed);
return _lastBatchCompleted = batch.Completed; lock (_pendingBatchLock)
} {
_pendingBatch = _nextCommit.Task;
async void ScheduleCommitCallbacks(Task task) _pendingBatch.ContinueWith(t =>
{ {
var list = _invokeOnNextCommit; lock (_pendingBatchLock)
_invokeOnNextCommit = null; {
await task; if (_pendingBatch == t)
foreach (var i in list!) _pendingBatch = null;
i(); }
list.Clear(); }, TaskContinuationOptions.ExecuteSynchronously);
_invokeListPool.Push(list); _nextCommit = null;
}
return _pendingBatch;
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();
} }
internal void RegisterForSerialization(CompositionObject compositionObject) internal void RegisterForSerialization(CompositionObject compositionObject)
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_objectsForSerialization.Add(compositionObject); _objectsForSerialization.Add(compositionObject);
QueueImplicitBatchCommit(); RequestCommitAsync();
}
internal void InvokeOnNextCommit(Action action)
{
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_invokeOnNextCommit.Add(action);
} }
public void InvokeWhenReadyForNextCommit(Action action) internal void InvokeBeforeNextCommit(Action<Task> action)
{ {
if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted) Dispatcher.UIThread.VerifyAccess();
Dispatcher.UIThread.Post(action, DispatcherPriority.Composition); _invokeBeforeCommit.Enqueue(action);
else RequestCommitAsync();
_lastBatchCompleted.ContinueWith(
static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition),
action);
} }
} }
} }

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 long _nextSequenceId = 1;
private static ConcurrentBag<BatchStreamData> _pool = new(); private static ConcurrentBag<BatchStreamData> _pool = new();
private readonly TaskCompletionSource<int>? _tcs;
public long SequenceId { get; } public long SequenceId { get; }
public Batch() public Batch(TaskCompletionSource<int>? tcs)
{ {
_tcs = tcs;
SequenceId = Interlocked.Increment(ref _nextSequenceId); SequenceId = Interlocked.Increment(ref _nextSequenceId);
if (!_pool.TryTake(out var lst)) if (!_pool.TryTake(out var lst))
lst = new BatchStreamData(); lst = new BatchStreamData();
Changes = lst; Changes = lst;
} }
private TaskCompletionSource<int> _tcs = new TaskCompletionSource<int>();
public BatchStreamData Changes { get; private set; } public BatchStreamData Changes { get; private set; }
public TimeSpan CommitedAt { get; set; } public TimeSpan CommitedAt { get; set; }
@ -33,9 +36,8 @@ namespace Avalonia.Rendering.Composition.Transport
_pool.Add(Changes); _pool.Add(Changes);
Changes = null!; 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 #nullable enable
using System; using System;
using System.Text;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling.Activators; using Avalonia.Styling.Activators;
using Avalonia.Utilities;
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
@ -110,7 +110,8 @@ namespace Avalonia.Styling
public override string ToString() public override string ToString()
{ {
var expectedCapacity = NthLastChildSelectorName.Length + 8; 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(':');
stringBuilder.Append(_reversed ? NthLastChildSelectorName : NthChildSelectorName); stringBuilder.Append(_reversed ? NthLastChildSelectorName : NthChildSelectorName);
@ -140,7 +141,7 @@ namespace Avalonia.Styling
stringBuilder.Append(')'); stringBuilder.Append(')');
return stringBuilder.ToString(); return StringBuilderCache.GetStringAndRelease(stringBuilder);
} }
} }
} }

6
src/Avalonia.Base/Styling/PropertyEqualsSelector.cs

@ -1,8 +1,8 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Text;
using Avalonia.Styling.Activators; using Avalonia.Styling.Activators;
using Avalonia.Utilities;
#nullable enable #nullable enable
@ -42,7 +42,7 @@ namespace Avalonia.Styling
{ {
if (_selectorString == null) if (_selectorString == null)
{ {
var builder = new StringBuilder(); var builder = StringBuilderCache.Acquire();
if (_previous != null) if (_previous != null)
{ {
@ -67,7 +67,7 @@ namespace Avalonia.Styling
builder.Append(_value ?? string.Empty); builder.Append(_value ?? string.Empty);
builder.Append(']'); builder.Append(']');
_selectorString = builder.ToString(); _selectorString = StringBuilderCache.GetStringAndRelease(builder);
} }
return _selectorString; return _selectorString;

6
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Styling.Activators; using Avalonia.Styling.Activators;
using Avalonia.Utilities;
#nullable enable #nullable enable
@ -145,7 +145,7 @@ namespace Avalonia.Styling
private string BuildSelectorString() private string BuildSelectorString()
{ {
var builder = new StringBuilder(); var builder = StringBuilderCache.Acquire();
if (_previous != null) 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. /// The job will be processed with the same priority as render.
/// </summary> /// </summary>
public static readonly DispatcherPriority Render = new(5); 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> /// <summary>
/// The job will be processed with the same priority as composition updates. /// The job will be processed with the same priority as composition updates.
/// </summary> /// </summary>
public static readonly DispatcherPriority Composition = new(7); public static readonly DispatcherPriority Composition = new(6);
/// <summary> /// <summary>
/// The job will be processed with the same priority as render. /// The job will be processed with the same priority as render.
/// </summary> /// </summary>
public static readonly DispatcherPriority Layout = new(8); public static readonly DispatcherPriority Layout = new(7);
/// <summary> /// <summary>
/// The job will be processed with the same priority as data binding. /// The job will be processed with the same priority as data binding.
@ -85,7 +80,7 @@ namespace Avalonia.Threading
/// <summary> /// <summary>
/// The job will be processed before other asynchronous operations. /// The job will be processed before other asynchronous operations.
/// </summary> /// </summary>
public static readonly DispatcherPriority Send = new(9); public static readonly DispatcherPriority Send = new(8);
/// <summary> /// <summary>
/// Maximum possible priority /// 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"> <Compile Include="../Avalonia.Base/Utilities/MathUtilities.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>
<Compile Include="..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
<Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs"> <Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>

6
src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs

@ -2,7 +2,7 @@
using System.Globalization; using System.Globalization;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using System.Text; using Avalonia.Utilities;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -113,7 +113,7 @@ namespace Avalonia.Controls.Primitives
// Cache results for next time as well // Cache results for next time as well
if (closestKnownColor != KnownColor.None) if (closestKnownColor != KnownColor.None)
{ {
StringBuilder sb = new StringBuilder(); var sb = StringBuilderCache.Acquire();
string name = closestKnownColor.ToString(); string name = closestKnownColor.ToString();
// Add spaces converting PascalCase to human-readable names // Add spaces converting PascalCase to human-readable names
@ -128,7 +128,7 @@ namespace Avalonia.Controls.Primitives
sb.Append(name[i]); sb.Append(name[i]);
} }
string displayName = sb.ToString(); string displayName = StringBuilderCache.GetStringAndRelease(sb);
lock (cacheMutex) lock (cacheMutex)
{ {

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

@ -6020,7 +6020,7 @@ namespace Avalonia.Controls
/// <returns>The formatted string.</returns> /// <returns>The formatted string.</returns>
private string FormatClipboardContent(DataGridRowClipboardEventArgs e) private string FormatClipboardContent(DataGridRowClipboardEventArgs e)
{ {
var text = new StringBuilder(); var text = StringBuilderCache.Acquire();
var clipboardRowContent = e.ClipboardRowContent; var clipboardRowContent = e.ClipboardRowContent;
var numberOfItem = clipboardRowContent.Count; var numberOfItem = clipboardRowContent.Count;
for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++) for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++)
@ -6037,7 +6037,7 @@ namespace Avalonia.Controls
text.Append('\n'); text.Append('\n');
} }
} }
return text.ToString(); return StringBuilderCache.GetStringAndRelease(text);
} }
/// <summary> /// <summary>
@ -6052,7 +6052,7 @@ namespace Avalonia.Controls
if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0) if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0)
{ {
StringBuilder textBuilder = new StringBuilder(); var textBuilder = StringBuilderCache.Acquire();
if (ClipboardCopyMode == DataGridClipboardCopyMode.IncludeHeader) if (ClipboardCopyMode == DataGridClipboardCopyMode.IncludeHeader)
{ {
@ -6078,7 +6078,7 @@ namespace Avalonia.Controls
textBuilder.Append(FormatClipboardContent(itemArgs)); textBuilder.Append(FormatClipboardContent(itemArgs));
} }
string text = textBuilder.ToString(); string text = StringBuilderCache.GetStringAndRelease(textBuilder);
if (!string.IsNullOrEmpty(text)) if (!string.IsNullOrEmpty(text))
{ {

9
src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs

@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Utilities;
namespace Avalonia.Controls.Converters namespace Avalonia.Controls.Converters
{ {
@ -62,7 +63,7 @@ namespace Avalonia.Controls.Converters
private static string ToString(KeyGesture gesture, string meta) private static string ToString(KeyGesture gesture, string meta)
{ {
var s = new StringBuilder(); var s = StringBuilderCache.Acquire();
static void Plus(StringBuilder s) static void Plus(StringBuilder s)
{ {
@ -98,12 +99,12 @@ namespace Avalonia.Controls.Converters
Plus(s); Plus(s);
s.Append(ToString(gesture.Key)); s.Append(ToString(gesture.Key));
return s.ToString(); return StringBuilderCache.GetStringAndRelease(s);
} }
private static string ToOSXString(KeyGesture gesture) private static string ToOSXString(KeyGesture gesture)
{ {
var s = new StringBuilder(); var s = StringBuilderCache.Acquire();
if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control))
{ {
@ -127,7 +128,7 @@ namespace Avalonia.Controls.Converters
s.Append(ToOSXString(gesture.Key)); s.Append(ToOSXString(gesture.Key));
return s.ToString(); return StringBuilderCache.GetStringAndRelease(s);
} }
private static string ToString(Key key) private static string ToString(Key key)

6
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -1,8 +1,8 @@
using System; using System;
using System.Text;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Utilities;
namespace Avalonia.Controls.Documents namespace Avalonia.Controls.Documents
{ {
@ -70,14 +70,14 @@ namespace Avalonia.Controls.Documents
{ {
get get
{ {
var builder = new StringBuilder(); var builder = StringBuilderCache.Acquire();
foreach (var inline in this) foreach (var inline in this)
{ {
inline.AppendText(builder); 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 System.Text;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Diagnostics namespace Avalonia.Diagnostics
@ -10,9 +11,9 @@ namespace Avalonia.Diagnostics
{ {
public static string PrintVisualTree(IVisual visual) public static string PrintVisualTree(IVisual visual)
{ {
StringBuilder result = new StringBuilder(); var result = StringBuilderCache.Acquire();
PrintVisualTree(visual, result, 0); PrintVisualTree(visual, result, 0);
return result.ToString(); return StringBuilderCache.GetStringAndRelease(result);
} }
private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent) 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> <DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
</PropertyGroup> </PropertyGroup>
<Import Project="IncludeXamlIlSre.props" /> <Import Project="IncludeXamlIlSre.props" />
<ItemGroup>
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
</ItemGroup> </ItemGroup>

6
src/Windows/Avalonia.Win32/ClipboardFormats.cs

@ -2,9 +2,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Text;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using Avalonia.Utilities;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
@ -35,9 +35,9 @@ namespace Avalonia.Win32
private static string QueryFormatName(ushort format) 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) if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0)
return sb.ToString(); return StringBuilderCache.GetStringAndRelease(sb);
return null; return null;
} }

6
src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs

@ -1,6 +1,6 @@
using System.Text;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Utilities;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
namespace Avalonia.Win32.Input namespace Avalonia.Win32.Input
@ -49,7 +49,7 @@ namespace Avalonia.Win32.Input
public string StringFromVirtualKey(uint virtualKey) public string StringFromVirtualKey(uint virtualKey)
{ {
StringBuilder result = new StringBuilder(256); var result = StringBuilderCache.Acquire(256);
int length = UnmanagedMethods.ToUnicode( int length = UnmanagedMethods.ToUnicode(
virtualKey, virtualKey,
0, 0,
@ -57,7 +57,7 @@ namespace Avalonia.Win32.Input
result, result,
256, 256,
0); 0);
return result.ToString(); return StringBuilderCache.GetStringAndRelease(result);
} }
private void UpdateKeyStates() 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;
using System.Runtime.InteropServices.ComTypes; using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.MicroCom; using Avalonia.MicroCom;
using Avalonia.Utilities;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject; using IDataObject = Avalonia.Input.IDataObject;
@ -103,11 +103,11 @@ namespace Avalonia.Win32
for (int i = 0; i < fileCount; i++) for (int i = 0; i < fileCount; i++)
{ {
int pathLen = UnmanagedMethods.DragQueryFile(hGlobal, i, null, 0); 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) 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> <ItemGroup>
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerExtensions\**\*.cs" /> <Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerExtensions\**\*.cs" />
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\AvaloniaXamlIlRuntimeCompiler.cs" /> <Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\AvaloniaXamlIlRuntimeCompiler.cs" />
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />

Loading…
Cancel
Save