Browse Source

Initial working version of remoting

pull/1105/head
Nikita Tsukanov 9 years ago
parent
commit
646db5b914
  1. 3
      samples/ControlCatalog/MainWindow.xaml.cs
  2. 43
      samples/RemoteTest/Program.cs
  3. 2
      samples/RemoteTest/RemoteTest.csproj
  4. 1
      src/Avalonia.Base/AvaloniaObject.cs
  5. 13
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  6. 26
      src/Avalonia.Controls/Remote/RemoteServer.cs
  7. 79
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  8. 107
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  9. 19
      src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj
  10. 157
      src/Avalonia.Remote.Protocol/BsonStreamTransport.cs
  11. 27
      src/Avalonia.Remote.Protocol/BsonTcpTransport.cs
  12. 72
      src/Avalonia.Remote.Protocol/EventStash.cs
  13. 2
      src/Avalonia.Remote.Protocol/ITransport.cs
  14. 84
      src/Avalonia.Remote.Protocol/TcpTransportBase.cs
  15. 98
      src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs
  16. 5
      src/Avalonia.Remote.Protocol/ViewportMessages.cs

3
samples/ControlCatalog/MainWindow.xaml.cs

@ -6,11 +6,12 @@ namespace ControlCatalog
{
public class MainWindow : Window
{
public static bool DebugMode = false;
public MainWindow()
{
this.InitializeComponent();
this.AttachDevTools();
Renderer.DrawDirtyRects = Renderer.DrawFps = true;
Renderer.DrawDirtyRects = Renderer.DrawFps = DebugMode;
}
private void InitializeComponent()

43
samples/RemoteTest/Program.cs

@ -1,4 +1,13 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Remote;
using Avalonia.Remote.Protocol;
using Avalonia.Threading;
using ControlCatalog;
namespace RemoteTest
{
@ -6,7 +15,39 @@ namespace RemoteTest
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
AppBuilder.Configure<App>().UsePlatformDetect().SetupWithoutStarting();
var l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
var port = ((IPEndPoint) l.LocalEndpoint).Port;
l.Stop();
var transport = new BsonTcpTransport();
transport.Listen(IPAddress.Loopback, port, sc =>
{
Dispatcher.UIThread.InvokeAsync(() =>
{
new RemoteServer(sc).Content = new MainView();
});
});
var cts = new CancellationTokenSource();
transport.Connect(IPAddress.Loopback, port).ContinueWith(t =>
{
Dispatcher.UIThread.InvokeAsync(() =>
{
var window = new Window()
{
Content = new RemoteWidget(t.Result)
};
window.Closed += delegate { cts.Cancel(); };
window.Show();
});
});
Dispatcher.UIThread.MainLoop(cts.Token);
}
}
}

2
samples/RemoteTest/RemoteTest.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>

1
src/Avalonia.Base/AvaloniaObject.cs

@ -56,6 +56,7 @@ namespace Avalonia
/// </summary>
public AvaloniaObject()
{
CheckAccess();
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
{
object value = property.IsDirect ?

13
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -6,12 +6,13 @@ using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Controls.Embedding.Offscreen
{
abstract class OffscreenTopLevelImplBase : ITopLevelImpl
public abstract class OffscreenTopLevelImplBase : ITopLevelImpl
{
private double _scaling;
private double _scaling = 1;
private Size _clientSize;
public IInputRoot InputRoot { get; private set; }
@ -20,6 +21,8 @@ namespace Avalonia.Controls.Embedding.Offscreen
//No-op
}
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
public abstract void Invalidate(Rect rect);
public abstract IEnumerable<object> Surfaces { get; }
@ -51,15 +54,13 @@ namespace Avalonia.Controls.Embedding.Offscreen
public virtual Point PointToClient(Point point) => point;
public Point PointToScreen(Point point)
{
throw new NotImplementedException();
}
public virtual Point PointToScreen(Point point) => point;
public virtual void SetCursor(IPlatformHandle cursor)
{
}
public Action Closed { get; set; }
public abstract IMouseDevice MouseDevice { get; }
}
}

26
src/Avalonia.Controls/Remote/RemoteServer.cs

@ -3,19 +3,37 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Remote.Server;
using Avalonia.Platform;
using Avalonia.Remote.Protocol;
namespace Avalonia.Controls.Remote
{
public class RemoteServer
{
private readonly IAvaloniaRemoteTransport _transport;
private EmbeddableControlRoot _topLevel;
public RemoteServer(IAvaloniaRemoteTransport transport)
class TopLevelImpl : RemoteServerTopLevelImpl, IEmbeddableWindowImpl
{
_transport = transport;
public TopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport)
{
}
public event Action LostFocus;
}
public RemoteServer(IAvaloniaRemoteTransportConnection transport)
{
_topLevel = new EmbeddableControlRoot(new TopLevelImpl(transport));
_topLevel.Prepare();
//TODO: Somehow react on closed connection?
}
public object Content { get; set; }
public object Content
{
get => _topLevel.Content;
set => _topLevel.Content = value;
}
}
}

79
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -0,0 +1,79 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading;
using PixelFormat = Avalonia.Platform.PixelFormat;
namespace Avalonia.Controls.Remote
{
public class RemoteWidget : Control
{
private readonly IAvaloniaRemoteTransportConnection _connection;
private FrameMessage _lastFrame;
private WritableBitmap _bitmap;
public RemoteWidget(IAvaloniaRemoteTransportConnection connection)
{
_connection = connection;
_connection.OnMessage += msg => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg));
_connection.Send(new ClientSupportedPixelFormatsMessage
{
Formats = new[]
{
Avalonia.Remote.Protocol.Viewport.PixelFormat.Bgra8888,
Avalonia.Remote.Protocol.Viewport.PixelFormat.Rgba8888,
}
});
}
private void OnMessage(object msg)
{
if (msg is FrameMessage frame)
{
_connection.Send(new FrameReceivedMessage
{
SequenceId = frame.SequenceId
});
_lastFrame = frame;
InvalidateVisual();
}
}
protected override void ArrangeCore(Rect finalRect)
{
_connection.Send(new ClientViewportAllocatedMessage
{
Width = finalRect.Width,
Height = finalRect.Height,
DpiX = 96,
DpiY = 96 //TODO: Somehow detect the actual DPI
});
base.ArrangeCore(finalRect);
}
public override void Render(DrawingContext context)
{
if (_lastFrame != null)
{
var fmt = (PixelFormat) _lastFrame.Format;
if (_bitmap == null || _bitmap.PixelWidth != _lastFrame.Width ||
_bitmap.PixelHeight != _lastFrame.Height)
_bitmap = new WritableBitmap(_lastFrame.Width, _lastFrame.Height, fmt);
using (var l = _bitmap.Lock())
{
var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width;
for (var y = 0; y < _lastFrame.Height; y++)
Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride,
new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen);
}
context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight),
new Rect(Bounds.Size));
}
base.Render(context);
}
}
}

107
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -6,6 +6,8 @@ using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls.Embedding.Offscreen;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Viewport;
@ -15,17 +17,20 @@ using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
namespace Avalonia.Controls.Remote.Server
{
class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface
public class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface
{
private readonly IAvaloniaRemoteTransport _transport;
private readonly IAvaloniaRemoteTransportConnection _transport;
private LockedFramebuffer _framebuffer;
private object _lock = new object();
private long _lastSentFrame;
private long _lastSentFrame = -1;
private long _lastReceivedFrame = -1;
private long _nextFrameNumber = 1;
private ClientViewportAllocatedMessage _pendingAllocation;
private bool _invalidated;
private Vector _dpi = new Vector(96, 96);
private ProtocolPixelFormat[] _supportedFormats;
public RemoteServerTopLevelImpl(IAvaloniaRemoteTransport transport)
public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport)
{
_transport = transport;
_transport.OnMessage += OnMessage;
@ -35,24 +40,64 @@ namespace Avalonia.Controls.Remote.Server
{
lock (_lock)
{
var lastFrame = obj as FrameReceivedMessage;
if (lastFrame != null)
if (obj is FrameReceivedMessage lastFrame)
{
lock (_lock)
{
_lastReceivedFrame = lastFrame.SequenceId;
}
Dispatcher.UIThread.InvokeAsync(CheckNeedsRender);
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded);
}
if (obj is ClientSupportedPixelFormatsMessage supportedFormats)
{
lock (_lock)
_supportedFormats = supportedFormats.Formats;
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded);
}
if (obj is MeasureViewportMessage measure)
Dispatcher.UIThread.InvokeAsync(() =>
{
var m = Measure(new Size(measure.Width, measure.Height));
_transport.Send(new MeasureViewportMessage
{
Width = m.Width,
Height = m.Height
});
});
if (obj is ClientViewportAllocatedMessage allocated)
{
lock (_lock)
{
if (_pendingAllocation == null)
Dispatcher.UIThread.InvokeAsync(() =>
{
ClientViewportAllocatedMessage allocation;
lock (_lock)
{
allocation = _pendingAllocation;
_pendingAllocation = null;
}
_dpi = new Vector(allocation.DpiX, allocation.DpiY);
ClientSize = new Size(allocation.Width, allocation.Height);
RenderIfNeeded();
});
_pendingAllocation = allocated;
}
}
var supportedFormats = obj as ClientSupportedPixelFormatsMessage;
if (supportedFormats != null)
_supportedFormats = supportedFormats.Formats;
}
}
protected virtual Size Measure(Size constaint)
{
var l = (ILayoutable) InputRoot;
l.Measure(constaint);
return l.DesiredSize;
}
public override IEnumerable<object> Surfaces => new[] { this };
FrameMessage RenderFrame(int width, int height, Size dpi, ProtocolPixelFormat? format)
FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format)
{
var fmt = format ?? ProtocolPixelFormat.Rgba8888;
var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4;
@ -60,7 +105,7 @@ namespace Avalonia.Controls.Remote.Server
var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
try
{
_framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, dpi, (PixelFormat)fmt,
_framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, _dpi, (PixelFormat)fmt,
null);
Paint?.Invoke(new Rect(0, 0, width, height));
}
@ -69,7 +114,14 @@ namespace Avalonia.Controls.Remote.Server
_framebuffer = null;
handle.Free();
}
return new FrameMessage();
return new FrameMessage
{
Data = data,
Format = (ProtocolPixelFormat) format,
Width = width,
Height = height,
Stride = width * bpp,
};
}
public ILockedFramebuffer Lock()
@ -79,23 +131,40 @@ namespace Avalonia.Controls.Remote.Server
return _framebuffer;
}
void CheckNeedsRender()
void RenderIfNeeded()
{
ProtocolPixelFormat[] formats;
lock (_lock)
{
if (_lastReceivedFrame != _lastSentFrame && !_invalidated)
if (_lastReceivedFrame != _lastSentFrame || !_invalidated || _supportedFormats == null)
return;
formats = _supportedFormats;
}
if (ClientSize.Width < 1 || ClientSize.Height < 1)
return;
var format = ProtocolPixelFormat.Rgba8888;
foreach(var fmt in _supportedFormats)
if (fmt <= ProtocolPixelFormat.MaxValue)
{
format = fmt;
break;
}
//var frame = RenderFrame()
var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format);
lock (_lock)
{
_lastSentFrame = _nextFrameNumber++;
frame.SequenceId = _lastSentFrame;
_invalidated = false;
}
_transport.Send(frame);
}
public override void Invalidate(Rect rect)
{
_invalidated = true;
Dispatcher.UIThread.InvokeAsync(CheckNeedsRender);
Dispatcher.UIThread.InvokeAsync(RenderIfNeeded);
}
public override IMouseDevice MouseDevice { get; } = new MouseDevice();
}
}

19
src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj

@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<DefineConstants>AVALONIA_REMOTE_PROTOCOL;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Input\Key.cs" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<DefineConstants>AVALONIA_REMOTE_PROTOCOL;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<Compile Include="..\Avalonia.Input\Key.cs" />
</ItemGroup>
</Project>

157
src/Avalonia.Remote.Protocol/BsonStreamTransport.cs

@ -1,7 +1,160 @@
namespace Avalonia.Remote.Protocol
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Newtonsoft.Json;
using System.Threading.Tasks;
using Newtonsoft.Json.Bson;
namespace Avalonia.Remote.Protocol
{
public class BsonStreamTransport
public class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection
{
private readonly IMessageTypeResolver _resolver;
private readonly Stream _inputStream;
private readonly Stream _outputStream;
private readonly Action _disposeCallback;
private readonly CancellationToken _cancel;
private readonly CancellationTokenSource _cancelSource;
private readonly MemoryStream _outputBlock = new MemoryStream();
private readonly object _lock = new object();
private bool _writeOperationPending;
private bool _readingAlreadyStarted;
private bool _writerIsBroken;
static readonly JsonSerializer Serializer = new JsonSerializer();
private static readonly byte[] ZeroLength = new byte[4];
public BsonStreamTransportConnection(IMessageTypeResolver resolver, Stream inputStream, Stream outputStream, Action disposeCallback)
{
_resolver = resolver;
_inputStream = inputStream;
_outputStream = outputStream;
_disposeCallback = disposeCallback;
_cancelSource = new CancellationTokenSource();
_cancel = _cancelSource.Token;
}
public void Dispose()
{
_cancelSource.Cancel();
_disposeCallback?.Invoke();
}
public void StartReading()
{
lock (_lock)
{
if(_readingAlreadyStarted)
throw new InvalidOperationException("Reading has already started");
_readingAlreadyStarted = true;
Task.Run(Reader, _cancel);
}
}
async Task ReadExact(byte[] buffer)
{
int read = 0;
while (read != buffer.Length)
{
var readNow = await _inputStream.ReadAsync(buffer, read, buffer.Length - read, _cancel)
.ConfigureAwait(false);
if (readNow == 0)
throw new EndOfStreamException();
read += readNow;
}
}
async Task Reader()
{
Task.Yield();
try
{
while (true)
{
var infoBlock = new byte[20];
await ReadExact(infoBlock).ConfigureAwait(false);
var length = BitConverter.ToInt32(infoBlock, 0);
var guidBytes = new byte[16];
Buffer.BlockCopy(infoBlock, 4, guidBytes, 0, 16);
var guid = new Guid(guidBytes);
var buffer = new byte[length];
await ReadExact(buffer).ConfigureAwait(false);
if (Environment.GetEnvironmentVariable("WTF") == "WTF")
{
using (var f = System.IO.File.Create("/tmp/wtf2.bin"))
{
f.Write(infoBlock, 0, infoBlock.Length);
f.Write(buffer, 0, buffer.Length);
}
}
var message = Serializer.Deserialize(new BsonReader(new MemoryStream(buffer)), _resolver.GetByGuid(guid));
OnMessage?.Invoke(message);
}
}
catch (Exception e)
{
FireException(e);
}
}
public async Task Send(object data)
{
lock (_lock)
{
if(_writerIsBroken) //Ignore further calls, since there is no point of writing to "broken" stream
return;
if (_writeOperationPending)
throw new InvalidOperationException("Previous send operation was not finished");
_writeOperationPending = true;
}
try
{
var guid = _resolver.GetGuid(data.GetType()).ToByteArray();
_outputBlock.Seek(0, SeekOrigin.Begin);
_outputBlock.SetLength(0);
_outputBlock.Write(ZeroLength, 0, 4);
_outputBlock.Write(guid, 0, guid.Length);
var writer = new BsonWriter(_outputBlock);
Serializer.Serialize(writer, data);
_outputBlock.Seek(0, SeekOrigin.Begin);
var length = BitConverter.GetBytes((int)_outputBlock.Length - 20);
_outputBlock.Write(length, 0, length.Length);
_outputBlock.Seek(0, SeekOrigin.Begin);
try
{
await _outputBlock.CopyToAsync(_outputStream, 0x1000, _cancel).ConfigureAwait(false);
}
catch (Exception e) //We are only catching "network"-related exceptions here
{
lock (_lock)
{
_writerIsBroken = true;
}
FireException(e);
}
}
finally
{
lock (_lock)
{
_writeOperationPending = false;
}
}
}
void FireException(Exception e)
{
var cancel = e as OperationCanceledException;
if (cancel?.CancellationToken == _cancel)
return;
OnException?.Invoke(e);
}
public event Action<object> OnMessage;
public event Action<Exception> OnException;
}
}

27
src/Avalonia.Remote.Protocol/BsonTcpTransport.cs

@ -0,0 +1,27 @@
using System;
using System.IO;
using System.Reflection;
namespace Avalonia.Remote.Protocol
{
public class BsonTcpTransport : TcpTransportBase
{
public BsonTcpTransport(IMessageTypeResolver resolver) : base(resolver)
{
}
public BsonTcpTransport() : this(new DefaultMessageTypeResolver(typeof(BsonTcpTransport).GetTypeInfo().Assembly))
{
}
protected override IAvaloniaRemoteTransportConnection CreateTransport(IMessageTypeResolver resolver,
Stream stream, Action dispose)
{
var t = new BsonStreamTransportConnection(resolver, stream, stream, dispose);
var wrap = new TransportConnectionWrapper(t);
t.StartReading();
return wrap;
}
}
}

72
src/Avalonia.Remote.Protocol/EventStash.cs

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Remote.Protocol
{
public class EventStash<T>
{
private readonly Action<Exception> _exceptionHandler;
private List<T> _stash;
private Action<T> _delegate;
public EventStash(Action<Exception> exceptionHandler = null)
{
_exceptionHandler = exceptionHandler;
}
public void Add(Action<T> handler)
{
List<T> stash;
lock (this)
{
var needsReplay = _delegate == null;
_delegate += handler;
if(!needsReplay)
return;
lock (this)
{
stash = _stash;
if(_stash == null)
return;
_stash = null;
}
}
foreach (var m in stash)
{
if (_exceptionHandler != null)
try
{
_delegate?.Invoke(m);
}
catch (Exception e)
{
_exceptionHandler(e);
}
else
_delegate?.Invoke(m);
}
}
public void Remove(Action<T> handler)
{
lock (this)
_delegate -= handler;
}
public void Fire(T ev)
{
if (_delegate == null)
{
lock (this)
{
_stash = _stash ?? new List<T>();
_stash.Add(ev);
}
}
else
_delegate?.Invoke(ev);
}
}
}

2
src/Avalonia.Remote.Protocol/ITransport.cs

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Avalonia.Remote.Protocol
{
public interface IAvaloniaRemoteTransport
public interface IAvaloniaRemoteTransportConnection : IDisposable
{
Task Send(object data);
event Action<object> OnMessage;

84
src/Avalonia.Remote.Protocol/TcpTransportBase.cs

@ -0,0 +1,84 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Avalonia.Remote.Protocol
{
public abstract class TcpTransportBase
{
private readonly IMessageTypeResolver _resolver;
public TcpTransportBase(IMessageTypeResolver resolver)
{
_resolver = resolver;
}
protected abstract IAvaloniaRemoteTransportConnection CreateTransport(IMessageTypeResolver resolver,
Stream stream, Action disposeCallback);
class DisposableServer : IDisposable
{
private readonly TcpListener _l;
public DisposableServer(TcpListener l)
{
_l = l;
}
public void Dispose()
{
try
{
_l.Stop();
}
catch
{
//Ignore
}
}
}
public IDisposable Listen(IPAddress address, int port, Action<IAvaloniaRemoteTransportConnection> cb)
{
var server = new TcpListener(address, port);
async void AcceptNew()
{
try
{
var cl = await server.AcceptTcpClientAsync();
AcceptNew();
Task.Run(async () =>
{
try
{
var tcs = new TaskCompletionSource<int>();
var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0));
cb(t);
await tcs.Task;
}
finally
{
cl.Dispose();
}
});
}
catch
{
//Ignore and stop
}
}
server.Start();
AcceptNew();
return new DisposableServer(server);
}
public async Task<IAvaloniaRemoteTransportConnection> Connect(IPAddress address, int port)
{
var c = new TcpClient();
await c.ConnectAsync(address, port);
return CreateTransport(_resolver, c.GetStream(), c.Dispose);
}
}
}

98
src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs

@ -1,7 +1,101 @@
namespace Avalonia.Remote.Protocol
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Avalonia.Remote.Protocol
{
public class TransportConnectionWrapper
public class TransportConnectionWrapper : IAvaloniaRemoteTransportConnection
{
private readonly IAvaloniaRemoteTransportConnection _conn;
private EventStash<object> _onMessage;
private EventStash<Exception> _onException = new EventStash<Exception>();
private Queue<SendOperation> _sendQueue = new Queue<SendOperation>();
private object _lock =new object();
private TaskCompletionSource<int> _signal;
private bool _workerIsAlive;
public TransportConnectionWrapper(IAvaloniaRemoteTransportConnection conn)
{
_conn = conn;
_onMessage = new EventStash<object>(_onException.Fire);
_conn.OnException +=_onException.Fire;
conn.OnMessage += _onMessage.Fire;
}
class SendOperation
{
public object Message { get; set; }
public TaskCompletionSource<int> Tcs { get; set; }
}
public void Dispose() => _conn.Dispose();
async void Worker()
{
while (true)
{
SendOperation wi = null;
lock (_lock)
{
if (_sendQueue.Count != 0)
wi = _sendQueue.Dequeue();
}
if (wi == null)
{
var signal = new TaskCompletionSource<int>();
lock (_lock)
_signal = signal;
await signal.Task.ConfigureAwait(false);
continue;
}
try
{
await _conn.Send(wi.Message).ConfigureAwait(false);
wi.Tcs.TrySetResult(0);
}
catch (Exception e)
{
wi.Tcs.TrySetException(e);
}
}
}
public Task Send(object data)
{
var tcs = new TaskCompletionSource<int>();
lock (_lock)
{
if (!_workerIsAlive)
{
_workerIsAlive = true;
Worker();
}
_sendQueue.Enqueue(new SendOperation
{
Message = data,
Tcs = tcs
});
if (_signal != null)
{
_signal.SetResult(0);
_signal = null;
}
}
return tcs.Task;
}
public event Action<object> OnMessage
{
add => _onMessage.Add(value);
remove => _onMessage.Remove(value);
}
public event Action<Exception> OnException
{
add => _onException.Add(value);
remove => _onException.Remove(value);
}
}
}

5
src/Avalonia.Remote.Protocol/ViewportMessages.cs

@ -10,7 +10,8 @@ namespace Avalonia.Remote.Protocol.Viewport
{
Rgb565,
Rgba8888,
Bgra8888
Bgra8888,
MaxValue = Bgra8888
}
[AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")]
@ -25,6 +26,8 @@ namespace Avalonia.Remote.Protocol.Viewport
{
public double Width { get; set; }
public double Height { get; set; }
public double DpiX { get; set; }
public double DpiY { get; set; }
}
[AvaloniaRemoteMessageGuid("63481025-7016-43FE-BADC-F2FD0F88609E")]

Loading…
Cancel
Save