Skip to content

Commit

Permalink
Websocket protocol upgrade (#5)
Browse files Browse the repository at this point in the history
* Started upgrading websocket protocol

* Got socket naming working. Also some responsive improvements.

* Fixed non-lazy messages and added responsive connection state tracking.

* Messaging and metrics

* Finished up message UI and fixed issues with client state.
  • Loading branch information
axle-h committed Nov 6, 2017
1 parent 69e86d9 commit 70a9483
Show file tree
Hide file tree
Showing 44 changed files with 1,110 additions and 187 deletions.
12 changes: 6 additions & 6 deletions GameBoy.Net/Graphics/GpuMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ public class GpuMetrics
/// <summary>
/// Initializes a new instance of the <see cref="GpuMetrics"/> class.
/// </summary>
/// <param name="framsPerSecond">The frams per second.</param>
/// <param name="framesPerSecond">The frams per second.</param>
/// <param name="skippedFrames">The skipped frames.</param>
public GpuMetrics(int framsPerSecond, int skippedFrames)
public GpuMetrics(int framesPerSecond, int skippedFrames)
{
FramsPerSecond = framsPerSecond;
FramesPerSecond = framesPerSecond;
SkippedFrames = skippedFrames;
}

/// <summary>
/// Gets or sets the frams per second.
/// Gets or sets the frames per second.
/// </summary>
/// <value>
/// The frams per second.
/// The frames per second.
/// </value>
public int FramsPerSecond { get; set; }
public int FramesPerSecond { get; set; }

/// <summary>
/// Gets or sets the skipped frames.
Expand Down
4 changes: 2 additions & 2 deletions GameBoy.Net/Graphics/IRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface IRenderer
/// Updates the rendering metrics.
/// The renderer can choose to display this if required.
/// </summary>
/// <param name="metrics">The metrics.</param>
void UpdateMetrics(GpuMetrics metrics);
/// <param name="gpuMetrics">The metrics.</param>
void UpdateMetrics(GpuMetrics gpuMetrics);
}
}
4 changes: 2 additions & 2 deletions GameBoy.Net/Graphics/NullRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public void Paint(Frame frame)
/// <summary>
/// Updates the metrics.
/// </summary>
/// <param name="metrics">The metrics.</param>
public void UpdateMetrics(GpuMetrics metrics)
/// <param name="gpuMetrics">The metrics.</param>
public void UpdateMetrics(GpuMetrics gpuMetrics)
{
}
}
Expand Down
7 changes: 7 additions & 0 deletions Retro.Net.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Web;

namespace Retro.Net.Api
{
Expand All @@ -10,6 +12,11 @@ public static void Main(string[] args)
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureLogging((h, builder) =>
{
h.HostingEnvironment.ConfigureNLog("nlog.xml");
builder.SetMinimumLevel(LogLevel.Trace);
})
.UseStartup<Startup>()
.UseApplicationInsights()
.UseSetting(WebHostDefaults.PreventHostingStartupKey, bool.TrueString)
Expand Down
16 changes: 13 additions & 3 deletions Retro.Net.Api/RealTime/FramedMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using MessagePack;
using MessagePack.Resolvers;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Retro.Net.Api.RealTime.Interfaces;

Expand All @@ -16,14 +17,15 @@ namespace Retro.Net.Api.RealTime
public class FramedMessageHandler<TMessage> : IFramedMessageHandler<TMessage>
{
private readonly ConcurrentQueue<(Guid id, TMessage message)> _messages;

private readonly ILogger _logger;

/// <summary>
/// Initializes a new instance of the <see cref="FramedMessageHandler{TMessage}"/> class.
/// </summary>
public FramedMessageHandler()
public FramedMessageHandler(ILoggerFactory loggerFactory)
{
_messages = new ConcurrentQueue<(Guid, TMessage)>();
_logger = loggerFactory.CreateLogger<FramedMessageHandler<TMessage>>();
}

/// <inheritdoc />
Expand Down Expand Up @@ -59,7 +61,15 @@ public void OnReceive(Guid socketId, ArraySegment<byte> message) =>
{
if (_messages.TryDequeue(out var msg))
{
yield return msg;
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(msg.ToString());
}

if (!Equals(msg, default(TMessage)))
{
yield return msg;
}
}
else
{
Expand Down
20 changes: 0 additions & 20 deletions Retro.Net.Api/RealTime/GameBoySocketMessage.cs

This file was deleted.

39 changes: 28 additions & 11 deletions Retro.Net.Api/RealTime/LockingWebSocket.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Retro.Net.Api.RealTime.Interfaces;
using Retro.Net.Api.RealTime.Models;

namespace Retro.Net.Api.RealTime
{
Expand Down Expand Up @@ -48,7 +50,7 @@ public LockingWebSocket(Guid id, WebSocket socket, IWebSocketMessageHandler hand
Task.Run(HeartbeatAsync, _disposing.Token);

// this is a hack... linked token sources don't seem to work here. dunno if it's because the token is from MVC?
token.Register(() => _lifetime.SetResult(true));
token.Register(Abort);
}

/// <summary>
Expand All @@ -62,16 +64,19 @@ public LockingWebSocket(Guid id, WebSocket socket, IWebSocketMessageHandler hand

public bool IsClosed { get; private set; }

public async Task SendAsync(params ArraySegment<byte>[] messages) =>
public async Task SendAsync(IWebSocketMessage message) =>
await WithSocketAsync(async s =>
{
var messages = message.Serialize().ToArray();
for (var i = 0; i < messages.Length; i++)
{
var isLast = i == messages.Length - 1;
await s.SendAsync(messages[i], WebSocketMessageType.Binary, isLast, _disposing.Token).ConfigureAwait(false);
}
}).ConfigureAwait(false);

private void Abort() => Task.Run(() => _lifetime.TrySetResult(true));

private async Task HeartbeatAsync()
{
while (!_disposing.IsCancellationRequested)
Expand Down Expand Up @@ -128,17 +133,28 @@ private async Task ReceiveAsync()
catch (Exception e)
{
_logger.LogError(0, e, "Receive message failed");
Abort();
}
}
}

private async Task CloseNowAsync() => await WithSocketAsync(async s =>
{
await s.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, CancellationToken.None).ConfigureAwait(false);
IsClosed = true;
}).ConfigureAwait(false);

private async Task WithSocketAsync(Func<WebSocket, Task> f)
private async Task CloseNowAsync()
{
await _semaphore.WaitAsync(_disposing.Token).ConfigureAwait(false);
try
{
_logger.LogDebug("Closing web socket");
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
IsClosed = true;
Abort();
}
finally
{
_semaphore.Release();
}
}

private async Task WithSocketAsync(Func<WebSocket, Task> socketFunc)
{
if (_disposing.IsCancellationRequested)
{
Expand All @@ -153,13 +169,14 @@ private async Task WithSocketAsync(Func<WebSocket, Task> f)
{
if (!IsClosed)
{
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, CancellationToken.None).ConfigureAwait(false);
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
IsClosed = true;
}
Abort();
}
else
{
await f(_socket).ConfigureAwait(false);
await socketFunc(_socket).ConfigureAwait(false);
}

}
Expand Down
21 changes: 21 additions & 0 deletions Retro.Net.Api/RealTime/Models/ErrorMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using MessagePack;

namespace Retro.Net.Api.RealTime.Models
{
/// <summary>
/// An error message.
/// </summary>
[MessagePackObject]
public class ErrorMessage
{
/// <summary>
/// Gets or sets the reason.
/// </summary>
/// <value>
/// The reason.
/// </value>
[Key("reasons")]
public ICollection<string> Reasons { get; set; }
}
}
41 changes: 41 additions & 0 deletions Retro.Net.Api/RealTime/Models/GameBoyClientContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Retro.Net.Api.RealTime.Models
{
internal class GameBoyClientContext
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public Guid Id { get; set; }

/// <summary>
/// Gets or sets the socket.
/// </summary>
/// <value>
/// The socket.
/// </value>
public LockingWebSocket Socket { get; set; }

/// <summary>
/// Gets or sets the state.
/// </summary>
/// <value>
/// The state.
/// </value>
public GameBoySocketClientState State { get; set; }

/// <summary>
/// Gets or sets the message queue.
/// </summary>
/// <value>
/// The message queue.
/// </value>
public ConcurrentQueue<ICollection<GameBoyClientMessage>> MessageQueue { get; set; }
}
}
38 changes: 38 additions & 0 deletions Retro.Net.Api/RealTime/Models/GameBoyClientMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using MessagePack;

namespace Retro.Net.Api.RealTime.Models
{
/// <summary>
/// A message to a client GameBoy.
/// </summary>
[MessagePackObject]
public class GameBoyClientMessage
{
/// <summary>
/// Gets or sets the user associated with this message.
/// </summary>
/// <value>
/// The user associated with this message.
/// </value>
[Key("user")]
public string User { get; set; }

/// <summary>
/// Gets or sets the date.
/// </summary>
/// <value>
/// The date.
/// </value>
[Key("date")]
public string Date { get; set; }

/// <summary>
/// Gets or sets the message.
/// </summary>
/// <value>
/// The message.
/// </value>
[Key("message")]
public string Message { get; set; }
}
}
40 changes: 40 additions & 0 deletions Retro.Net.Api/RealTime/Models/GameBoyMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using GameBoy.Net.Graphics;
using MessagePack;

namespace Retro.Net.Api.RealTime.Models
{
/// <summary>
/// Metrics for a GameBoy socket client.
/// </summary>
[MessagePackObject]
public class GameBoyMetrics
{
/// <summary>
/// Gets or sets the frames per second.
/// </summary>
/// <value>
/// The frames per second.
/// </value>
[Key("framesPerSecond")]
public int FramesPerSecond { get; set; }

/// <summary>
/// Gets or sets the skipped frames.
/// </summary>
/// <value>
/// The skipped frames.
/// </value>
[Key("skippedFrames")]
public int SkippedFrames { get; set; }

/// <summary>
/// Gets or sets the messages.
/// </summary>
/// <value>
/// The messages.
/// </value>
[Key("messages")]
public ICollection<GameBoyClientMessage> Messages { get; set; }
}
}
Loading

0 comments on commit 70a9483

Please sign in to comment.