Browse Source

Reduced array allocations.

pull/2917/head
José Pedro 7 years ago
parent
commit
19f9729e86
No known key found for this signature in database GPG Key ID: B8247B9301707B83
  1. 2
      build/System.Memory.props
  2. 6
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  3. 23
      src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
  4. 2
      src/Avalonia.Controls/TreeView.cs
  5. 37
      src/Avalonia.FreeDesktop/NativeMethods.cs
  6. 5
      src/Avalonia.X11/X11CursorFactory.cs
  7. 40
      src/Avalonia.X11/XI2Manager.cs
  8. 7
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  9. 1
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  10. 25
      src/Windows/Avalonia.Win32/DataObject.cs
  11. 7
      src/Windows/Avalonia.Win32/DragSource.cs
  12. 6
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  13. 43
      src/Windows/Avalonia.Win32/OleDataObject.cs
  14. 12
      src/Windows/Avalonia.Win32/WindowImpl.cs
  15. 9
      src/iOS/Avalonia.iOS/TopLevelImpl.cs

2
build/System.Memory.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.1" />
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>
</Project>

6
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -15,10 +15,10 @@ using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, IFramebufferPlatformSurface
{
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
private ViewImpl _view;
public TopLevelImpl(Context context, bool placeOnTop = false)
@ -28,6 +28,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
p => GetAvaloniaPointFromEvent(p));
Surfaces = new object[] { this };
MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels);
}
@ -82,7 +84,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces => new object[] {this};
public IEnumerable<object> Surfaces { get; }
public IRenderer CreateRenderer(IRenderRoot root)
{

23
src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Text;
@ -6,7 +7,7 @@ namespace Avalonia.Platform.Interop
{
public class Utf8Buffer : SafeHandle
{
private GCHandle _gchandle;
private GCHandle _gcHandle;
private byte[] _data;
public Utf8Buffer(string s) : base(IntPtr.Zero, true)
@ -14,8 +15,8 @@ namespace Avalonia.Platform.Interop
if (s == null)
return;
_data = Encoding.UTF8.GetBytes(s);
_gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
handle = _gchandle.AddrOfPinnedObject();
_gcHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
handle = _gcHandle.AddrOfPinnedObject();
}
public int ByteLen => _data.Length;
@ -26,7 +27,7 @@ namespace Avalonia.Platform.Interop
{
handle = IntPtr.Zero;
_data = null;
_gchandle.Free();
_gcHandle.Free();
}
return true;
}
@ -40,10 +41,18 @@ namespace Avalonia.Platform.Interop
return null;
int len;
for (len = 0; pstr[len] != 0; len++) ;
var bytes = new byte[len];
Marshal.Copy(s, bytes, 0, len);
return Encoding.UTF8.GetString(bytes, 0, len);
var bytes = ArrayPool<byte>.Shared.Rent(len);
try
{
Marshal.Copy(s, bytes, 0, len);
return Encoding.UTF8.GetString(bytes, 0, len);
}
finally
{
ArrayPool<byte>.Shared.Return(bytes);
}
}
}
}

2
src/Avalonia.Controls/TreeView.cs

@ -50,7 +50,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<SelectionMode> SelectionModeProperty =
ListBox.SelectionModeProperty.AddOwner<TreeView>();
private static readonly IList Empty = new object[0];
private static readonly IList Empty = Array.Empty<object>();
private object _selectedItem;
private IList _selectedItems;

37
src/Avalonia.FreeDesktop/NativeMethods.cs

@ -1,12 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Avalonia.Controls.Platform;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
@ -21,11 +14,27 @@ namespace Avalonia.FreeDesktop
public static string ReadLink(string path)
{
var symlink = Encoding.UTF8.GetBytes(path);
var result = new byte[4095];
readlink(symlink, result, result.Length);
var rawstr = Encoding.UTF8.GetString(result);
return rawstr.Substring(0, rawstr.IndexOf('\0'));
var symlinkMaxSize = Encoding.ASCII.GetMaxByteCount(path.Length);
var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated
var symlink = ArrayPool<byte>.Shared.Rent(symlinkMaxSize + 1);
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
var symlinkSize = Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0);
symlink[symlinkSize] = 0;
var size = readlink(symlink, buffer, bufferSize);
Debug.Assert(size < bufferSize); // if this fails, we need to increase the buffer size (dynamically?)
return Encoding.UTF8.GetString(buffer, 0, (int)size);
}
finally
{
ArrayPool<byte>.Shared.Return(symlink);
ArrayPool<byte>.Shared.Return(buffer);
}
}
}
}

5
src/Avalonia.X11/X11CursorFactory.cs

@ -8,6 +8,8 @@ namespace Avalonia.X11
{
class X11CursorFactory : IStandardCursorFactory
{
private static readonly byte[] NullCursorData = new byte[] { 0 };
private static IntPtr _nullCursor;
private readonly IntPtr _display;
@ -68,9 +70,8 @@ namespace Avalonia.X11
private static IntPtr GetNullCursor(IntPtr display)
{
XColor color = new XColor();
byte[] data = new byte[] { 0 };
IntPtr window = XLib.XRootWindow(display, 0);
IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, data, 1, 1);
IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, NullCursorData, 1, 1);
return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0);
}
}

40
src/Avalonia.X11/XI2Manager.cs

@ -1,18 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Input;
using Avalonia.Input.Raw;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
unsafe class XI2Manager
{
private static readonly XiEventType[] DefaultEventTypes = new XiEventType[]
{
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease
};
private static readonly XiEventType[] MultiTouchEventTypes = new XiEventType[]
{
XiEventType.XI_TouchBegin,
XiEventType.XI_TouchUpdate,
XiEventType.XI_TouchEnd
};
private X11Info _x11;
private bool _multitouch;
private Dictionary<IntPtr, IXI2Client> _clients = new Dictionary<IntPtr, IXI2Client>();
class DeviceInfo
{
public int Id { get; }
@ -125,20 +139,18 @@ namespace Avalonia.X11
public XEventMask AddWindow(IntPtr xid, IXI2Client window)
{
_clients[xid] = window;
var events = new List<XiEventType>
{
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease
};
var eventsLength = DefaultEventTypes.Length;
if (_multitouch)
events.AddRange(new[]
{
XiEventType.XI_TouchBegin,
XiEventType.XI_TouchUpdate,
XiEventType.XI_TouchEnd
});
eventsLength += MultiTouchEventTypes.Length;
var events = new List<XiEventType>(eventsLength);
events.AddRange(DefaultEventTypes);
if (_multitouch)
events.AddRange(MultiTouchEventTypes);
XiSelectEvents(_x11.Display, xid,
new Dictionary<int, List<XiEventType>> {[_pointerDevice.Id] = events});

7
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -6,7 +6,6 @@ using Avalonia.LinuxFramebuffer.Input;
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer
{
@ -14,6 +13,7 @@ namespace Avalonia.LinuxFramebuffer
{
private readonly IOutputBackend _outputBackend;
private readonly IInputBackend _inputBackend;
private bool _renderQueued;
public IInputRoot InputRoot { get; private set; }
@ -21,6 +21,9 @@ namespace Avalonia.LinuxFramebuffer
{
_outputBackend = outputBackend;
_inputBackend = inputBackend;
Surfaces = new object[] { _outputBackend };
Invalidate(default(Rect));
_inputBackend.Initialize(this, e => Input?.Invoke(e));
}
@ -62,7 +65,7 @@ namespace Avalonia.LinuxFramebuffer
public IPopupImpl CreatePopup() => null;
public double Scaling => _outputBackend.Scaling;
public IEnumerable<object> Surfaces => new object[] {_outputBackend};
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }

1
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.Direct2D1</PackageId>
</PropertyGroup>
<ItemGroup>

25
src/Windows/Avalonia.Win32/DataObject.cs

@ -1,5 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@ -254,9 +256,18 @@ namespace Avalonia.Win32
return WriteFileListToHGlobal(ref hGlobal, files);
if (data is Stream stream)
{
byte[] buffer = new byte[stream.Length - stream.Position];
stream.Read(buffer, 0, buffer.Length);
return WriteBytesToHGlobal(ref hGlobal, buffer);
var length = (int)(stream.Length - stream.Position);
var buffer = ArrayPool<byte>.Shared.Rent(length);
try
{
stream.Read(buffer, 0, length);
return WriteBytesToHGlobal(ref hGlobal, buffer.AsSpan(0, length));
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
if (data is IEnumerable<byte> bytes)
{
@ -277,7 +288,7 @@ namespace Avalonia.Win32
}
}
private int WriteBytesToHGlobal(ref IntPtr hGlobal, byte[] data)
private unsafe int WriteBytesToHGlobal(ref IntPtr hGlobal, ReadOnlySpan<byte> data)
{
int required = data.Length;
if (hGlobal == IntPtr.Zero)
@ -287,10 +298,12 @@ namespace Avalonia.Win32
if (required > available)
return STG_E_MEDIUMFULL;
IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
var ptr = UnmanagedMethods.GlobalLock(hGlobal);
Debug.Assert(ptr == hGlobal);
try
{
Marshal.Copy(data, 0, ptr, data.Length);
data.CopyTo(new Span<byte>((void*)ptr, data.Length));
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
finally

7
src/Windows/Avalonia.Win32/DragSource.cs

@ -17,9 +17,8 @@ namespace Avalonia.Win32
DataObject dataObject = new DataObject(data);
int allowed = (int)OleDropTarget.ConvertDropEffect(allowedEffects);
int[] finalEffect = new int[1];
UnmanagedMethods.DoDragDrop(dataObject, src, allowed, finalEffect);
return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect[0]));}
UnmanagedMethods.DoDragDrop(dataObject, src, allowed, out var finalEffect);
return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect));
}
}
}

6
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1051,10 +1051,10 @@ namespace Avalonia.Win32.Interop
public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi);
[DllImport("user32")]
public static extern bool GetTouchInputInfo(
public static extern unsafe bool GetTouchInputInfo(
IntPtr hTouchInput,
uint cInputs,
[Out]TOUCHINPUT[] pInputs,
TOUCHINPUT* pInputs,
int cbSize
);
@ -1115,7 +1115,7 @@ namespace Avalonia.Win32.Interop
public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, out int finalEffect);

43
src/Windows/Avalonia.Win32/OleDataObject.cs

@ -1,5 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@ -86,15 +88,8 @@ namespace Avalonia.Win32
return null;
}
private bool IsSerializedObject(byte[] data)
{
if (data.Length < DataObject.SerializedObjectGUID.Length)
return false;
for (int i = 0; i < DataObject.SerializedObjectGUID.Length; i++)
if (data[i] != DataObject.SerializedObjectGUID[i])
return false;
return true;
}
private static bool IsSerializedObject(ReadOnlySpan<byte> data) =>
data.StartsWith(DataObject.SerializedObjectGUID);
private static IEnumerable<string> ReadFileNamesFromHGlobal(IntPtr hGlobal)
{
@ -148,21 +143,33 @@ namespace Avalonia.Win32
private IEnumerable<string> GetDataFormatsCore()
{
var enumFormat = _wrapped.EnumFormatEtc(DATADIR.DATADIR_GET);
if (enumFormat != null)
{
enumFormat.Reset();
FORMATETC[] formats = new FORMATETC[1];
int[] fetched = { 1 };
while (fetched[0] > 0)
var formats = ArrayPool<FORMATETC>.Shared.Rent(1);
var fetched = ArrayPool<int>.Shared.Rent(1);
try
{
fetched[0] = 0;
if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0)
do
{
if (formats[0].ptd != IntPtr.Zero)
Marshal.FreeCoTaskMem(formats[0].ptd);
yield return ClipboardFormats.GetFormat(formats[0].cfFormat);
fetched[0] = 0;
if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0)
{
if (formats[0].ptd != IntPtr.Zero)
Marshal.FreeCoTaskMem(formats[0].ptd);
yield return ClipboardFormats.GetFormat(formats[0].cfFormat);
}
}
while (fetched[0] > 0);
}
finally
{
ArrayPool<FORMATETC>.Shared.Return(formats);
ArrayPool<int>.Shared.Return(fetched);
}
}
}

12
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Input;
@ -13,7 +12,6 @@ using Avalonia.Input.Raw;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@ -430,7 +428,7 @@ namespace Avalonia.Win32
}
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
bool unicode = UnmanagedMethods.IsWindowUnicode(hWnd);
@ -637,8 +635,12 @@ namespace Avalonia.Win32
new Point(0, 0), GetMouseModifiers(wParam));
break;
case WindowsMessage.WM_TOUCH:
var touchInputs = new TOUCHINPUT[wParam.ToInt32()];
if (GetTouchInputInfo(lParam, (uint)wParam.ToInt32(), touchInputs, Marshal.SizeOf<TOUCHINPUT>()))
var touchInputCount = wParam.ToInt32();
var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount];
var touchInputs = new Span<TOUCHINPUT>(pTouchInputs, touchInputCount);
if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf<TOUCHINPUT>()))
{
foreach (var touchInput in touchInputs)
{

9
src/iOS/Avalonia.iOS/TopLevelImpl.cs

@ -16,14 +16,17 @@ namespace Avalonia.iOS
[Adopts("UIKeyInput")]
class TopLevelImpl : UIView, ITopLevelImpl, IFramebufferPlatformSurface
{
private IInputRoot _inputRoot;
private readonly KeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private IInputRoot _inputRoot;
public TopLevelImpl()
{
_keyboardHelper = new KeyboardEventsHelper<TopLevelImpl>(this);
AutoresizingMask = UIViewAutoresizing.All;
_keyboardHelper.ActivateAutoShowKeyboard();
Surfaces = new object[] { this };
}
[Export("hasText")]
@ -76,8 +79,8 @@ namespace Avalonia.iOS
{
//Not supported
}
public IEnumerable<object> Surfaces => new object[] { this };
public IEnumerable<object> Surfaces { get; }
public override void TouchesEnded(NSSet touches, UIEvent evt)
{

Loading…
Cancel
Save