From e71ff31ef2b76006e526b5a8b2fd823835eef6de Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Sun, 5 Feb 2017 14:46:40 +0000
Subject: [PATCH] Finish fixing bugs in the core message handling system. It
 works, more or less! :D

---
 .../Client/Messages/CursorPositionMessage.cs  |   5 +-
 .../Messages/HandshakeRequestMessage.cs       |  13 +-
 Nibriboard/Client/NibriClient.cs              |  33 +++--
 Nibriboard/Client/NibriClientManager.cs       |  17 ++-
 Nibriboard/ClientFiles/NibriClient.js         |   4 +
 Nibriboard/ClientFiles/RippleLink.js          |   4 +
 Nibriboard/Nibriboard.csproj                  |   4 +-
 Nibriboard/RippleSpace/ClientState.cs         |   7 +-
 .../JsonConverters/PointConverter.cs          |   2 +-
 ...Converter.cs => RectangleJsonConverter.cs} |   4 +-
 Nibriboard/Utilities/Rectangle.cs             | 115 ++++++++++++++++++
 Nibriboard/Utilities/Vector2.cs               |  63 ++++++++++
 12 files changed, 244 insertions(+), 27 deletions(-)
 rename Nibriboard/Utilities/JsonConverters/{RectangleConverter.cs => RectangleJsonConverter.cs} (89%)
 create mode 100644 Nibriboard/Utilities/Rectangle.cs
 create mode 100644 Nibriboard/Utilities/Vector2.cs

diff --git a/Nibriboard/Client/Messages/CursorPositionMessage.cs b/Nibriboard/Client/Messages/CursorPositionMessage.cs
index 4dfc768..3af999f 100644
--- a/Nibriboard/Client/Messages/CursorPositionMessage.cs
+++ b/Nibriboard/Client/Messages/CursorPositionMessage.cs
@@ -1,5 +1,6 @@
 ﻿using System;
-using System.Drawing;
+
+using SBRL.Utilities;
 
 namespace Nibriboard.Client.Messages
 {
@@ -11,7 +12,7 @@ namespace Nibriboard.Client.Messages
 		/// <summary>
 		/// The absolute cursor position.
 		/// </summary>
-		public Point AbsCursorPosition;
+		public Vector2 AbsCursorPosition;
 
 		public CursorPositionMessage()
 		{
diff --git a/Nibriboard/Client/Messages/HandshakeRequestMessage.cs b/Nibriboard/Client/Messages/HandshakeRequestMessage.cs
index b673fc5..b94d0ba 100644
--- a/Nibriboard/Client/Messages/HandshakeRequestMessage.cs
+++ b/Nibriboard/Client/Messages/HandshakeRequestMessage.cs
@@ -1,9 +1,9 @@
 ﻿using System;
-using System.Drawing;
 
 using Newtonsoft.Json;
-// TODO: In C# you can either have namespaces or types in a namespace - not both.
-using Nibriboard.Utilities.JsonConverters;
+
+using SBRL.Utilities;
+using SBRL.Utilities.JsonConverters;
 
 namespace Nibriboard.Client.Messages
 {
@@ -13,13 +13,12 @@ namespace Nibriboard.Client.Messages
 		/// The initial visible area on the client's screen.
 		/// Very useful for determining which chunks we should send a client when they first connect.
 		/// </summary>
-		[JsonConverter(typeof(RectangleConverter))]
-		public Rectangle InitialViewport = Rectangle.Empty;
+		public Rectangle InitialViewport = Rectangle.Zero;
+
 		/// <summary>
 		/// The initial position of the user's cursor.
 		/// </summary>
-		[JsonConverter(typeof(RectangleConverter))]
-		public Point InitialAbsCursorPosition = Point.Empty;
+		public Vector2 InitialAbsCursorPosition = Vector2.Zero;
 
 		public HandshakeRequestMessage()
 		{
diff --git a/Nibriboard/Client/NibriClient.cs b/Nibriboard/Client/NibriClient.cs
index 5950d66..7fc669f 100644
--- a/Nibriboard/Client/NibriClient.cs
+++ b/Nibriboard/Client/NibriClient.cs
@@ -1,19 +1,23 @@
 ﻿using System;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 using System.Text;
-using System.Collections.Generic;
-using System.Drawing;
 using System.Linq;
+using System.Reflection;
 
 using IotWeb.Common.Http;
+using Newtonsoft.Json;
 using SBRL.Utilities;
 using Nibriboard.Client.Messages;
-using Newtonsoft.Json;
-using System.Reflection;
 using RippleSpace;
 
 namespace Nibriboard.Client
 {
+	/// <summary>
+	/// A delegate that is used in the event that is fired when a nibri client disconnects.
+	/// </summary>
+	public delegate void NibriDisconnectedEvent(NibriClient disconnectedClient);
+
 	/// <summary>
 	/// Represents a single client connected to the ripple-space on this Nibriboard server.
 	/// </summary>
@@ -41,6 +45,12 @@ namespace Nibriboard.Client
 			["handshakeRequest"] = typeof(HandshakeRequestMessage)
 		};
 
+		/// <summary>
+		/// Whether this nibri client is still connected.
+		/// </summary>
+		public bool Connected = true;
+		public event NibriDisconnectedEvent Disconnected;
+
 		/// <summary>
 		/// Whether this client has completed the handshake yet or not.
 		/// </summary>
@@ -54,12 +64,12 @@ namespace Nibriboard.Client
 		/// The current area that this client is looking at.
 		/// </summary>
 		/// <value>The current view port.</value>
-		public Rectangle CurrentViewPort { get; private set; } = Rectangle.Empty;
+		public Rectangle CurrentViewPort { get; private set; } = Rectangle.Zero;
 		/// <summary>
 		/// The absolute position in plane-space of this client's cursor.
 		/// </summary>
 		/// <value>The absolute cursor position.</value>
-		public Point AbsoluteCursorPosition { get; private set; } = Point.Empty;
+		public Vector2 AbsoluteCursorPosition { get; private set; } = Vector2.Zero;
 
 		#region Core Setup & Message Routing Logic
 
@@ -81,8 +91,8 @@ namespace Nibriboard.Client
 
 				//Task.Run(async () => await onMessage(frame)).Wait();
 			};
-
-			
+			// Store whether this NibriClient is still connected or not
+			client.ConnectionClosed += (WebSocket socket) => Connected = false;
 		}
 
 		private async Task handleMessage(string frame)
@@ -107,7 +117,7 @@ namespace Nibriboard.Client
 
 			string handlerMethodName = "handle" + decodedMessage.GetType().Name;
 			Type clientType = this.GetType();
-			MethodInfo handlerInfo = clientType.GetMethod(handlerMethodName);
+			MethodInfo handlerInfo = clientType.GetMethod(handlerMethodName, BindingFlags.Instance | BindingFlags.NonPublic);
 			await (Task)handlerInfo.Invoke(this, new object[] { decodedMessage });
 		}
 
@@ -129,7 +139,10 @@ namespace Nibriboard.Client
 		/// <param name="message">The message to send.</param>
 		public void SendRaw(string message)
 		{
-			client.Send(Encoding.UTF8.GetBytes(message));
+			if (!Connected)
+				throw new InvalidOperationException($"[NibriClient]{Id}] Can't send a message as the client has disconnected.");
+			
+			client.Send(message);
 		}
 
 		/// <summary>
diff --git a/Nibriboard/Client/NibriClientManager.cs b/Nibriboard/Client/NibriClientManager.cs
index ebb1fe8..bcb42e8 100644
--- a/Nibriboard/Client/NibriClientManager.cs
+++ b/Nibriboard/Client/NibriClientManager.cs
@@ -49,10 +49,16 @@ namespace Nibriboard.Client
 		public void Connected(WebSocket newSocket)
 		{
 			NibriClient client = new NibriClient(this, newSocket);
+			client.Disconnected += handleDisconnection; // Clean up when the client disconnects
+
 			Clients.Add(client);
 		}
 
-
+		/// <summary>
+		/// Sends a message to all the connected clients, except the one who's sending it.
+		/// </summary>
+		/// <param name="sendingClient">The client sending the message.</param>
+		/// <param name="message">The message that is to bee sent.</param>
 		public void Broadcast(NibriClient sendingClient, Message message)
 		{
 			foreach(NibriClient client in Clients)
@@ -64,5 +70,14 @@ namespace Nibriboard.Client
 				client.Send(message);
 			}
 		}
+
+		/// <summary>
+		/// Clean up after a client disconnects from the server.
+		/// </summary>
+		/// <param name="disconnectedClient">The client that has disconnected.</param>
+		private void handleDisconnection(NibriClient disconnectedClient)
+		{
+			Clients.Remove(disconnectedClient);
+		}
 	}
 }
diff --git a/Nibriboard/ClientFiles/NibriClient.js b/Nibriboard/ClientFiles/NibriClient.js
index 22ea971..65985c5 100644
--- a/Nibriboard/ClientFiles/NibriClient.js
+++ b/Nibriboard/ClientFiles/NibriClient.js
@@ -46,6 +46,10 @@ class RippleLink extends EventEmitter$1
 		this.websocket.addEventListener("message", this.handleMessage.bind(this));
 		this.websocket.addEventListener("close", this.handleDisconnection.bind(this));
 		
+		// Close the socket correctly
+		window.addEventListener("beforeunload", (function(event) {
+			this.websocket.close();
+		}).bind(this));
 	}
 	
 	handleConnection(event) {
diff --git a/Nibriboard/ClientFiles/RippleLink.js b/Nibriboard/ClientFiles/RippleLink.js
index f9fce27..2a34699 100644
--- a/Nibriboard/ClientFiles/RippleLink.js
+++ b/Nibriboard/ClientFiles/RippleLink.js
@@ -20,6 +20,10 @@ class RippleLink extends EventEmitter
 		this.websocket.addEventListener("message", this.handleMessage.bind(this));
 		this.websocket.addEventListener("close", this.handleDisconnection.bind(this));
 		
+		// Close the socket correctly
+		window.addEventListener("beforeunload", (function(event) {
+			this.websocket.close();
+		}).bind(this));
 	}
 	
 	handleConnection(event) {
diff --git a/Nibriboard/Nibriboard.csproj b/Nibriboard/Nibriboard.csproj
index c097082..549e07c 100644
--- a/Nibriboard/Nibriboard.csproj
+++ b/Nibriboard/Nibriboard.csproj
@@ -77,8 +77,10 @@
     <Compile Include="Client\Messages\CursorPositionMessage.cs" />
     <Compile Include="Client\Messages\ClientStateMessage.cs" />
     <Compile Include="RippleSpace\ClientState.cs" />
-    <Compile Include="Utilities\JsonConverters\RectangleConverter.cs" />
     <Compile Include="Utilities\JsonConverters\PointConverter.cs" />
+    <Compile Include="Utilities\JsonConverters\RectangleJsonConverter.cs" />
+    <Compile Include="Utilities\Rectangle.cs" />
+    <Compile Include="Utilities\Vector2.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="ClientFiles\index.html" />
diff --git a/Nibriboard/RippleSpace/ClientState.cs b/Nibriboard/RippleSpace/ClientState.cs
index 426bb01..af62153 100644
--- a/Nibriboard/RippleSpace/ClientState.cs
+++ b/Nibriboard/RippleSpace/ClientState.cs
@@ -1,5 +1,6 @@
 ﻿using System;
-using System.Drawing;
+
+using SBRL.Utilities;
 
 namespace RippleSpace
 {
@@ -26,11 +27,11 @@ namespace RippleSpace
 		/// <summary>
 		/// The size and position of the client's viewport.
 		/// </summary>
-		public Rectangle Viewport = Rectangle.Empty;
+		public Rectangle Viewport = Rectangle.Zero;
 		/// <summary>
 		/// The absolute position of the client's cursor.
 		/// </summary>
-		public Point AbsCursorPosition = Point.Empty;
+		public Vector2 AbsCursorPosition = Vector2.Zero;
 
 		public ClientState()
 		{
diff --git a/Nibriboard/Utilities/JsonConverters/PointConverter.cs b/Nibriboard/Utilities/JsonConverters/PointConverter.cs
index 4ad1b27..01db415 100644
--- a/Nibriboard/Utilities/JsonConverters/PointConverter.cs
+++ b/Nibriboard/Utilities/JsonConverters/PointConverter.cs
@@ -4,7 +4,7 @@ using System.Drawing;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 
-namespace Nibriboard.Utilities.JsonConverters
+namespace SBRL.Utilities.JsonConverters
 {
 	/// <summary>
 	/// Deserialises objects into points from the System.Drawing namespace.
diff --git a/Nibriboard/Utilities/JsonConverters/RectangleConverter.cs b/Nibriboard/Utilities/JsonConverters/RectangleJsonConverter.cs
similarity index 89%
rename from Nibriboard/Utilities/JsonConverters/RectangleConverter.cs
rename to Nibriboard/Utilities/JsonConverters/RectangleJsonConverter.cs
index 5c0d805..c92fa86 100644
--- a/Nibriboard/Utilities/JsonConverters/RectangleConverter.cs
+++ b/Nibriboard/Utilities/JsonConverters/RectangleJsonConverter.cs
@@ -4,12 +4,12 @@ using System.Drawing;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 
-namespace Nibriboard.Utilities.JsonConverters
+namespace SBRL.Utilities.JsonConverters
 {
 	/// <summary>
 	/// Deserialises objects into rectangles from the System.Drawing namespace.
 	/// </summary>
-	public class RectangleConverter : JsonConverter
+	public class RectangleJsonConverter : JsonConverter
 	{
 		public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
 		{
diff --git a/Nibriboard/Utilities/Rectangle.cs b/Nibriboard/Utilities/Rectangle.cs
new file mode 100644
index 0000000..b37a546
--- /dev/null
+++ b/Nibriboard/Utilities/Rectangle.cs
@@ -0,0 +1,115 @@
+﻿using System;
+using System.Drawing;
+
+namespace SBRL.Utilities
+{
+	/// <summary>
+	/// Represents a rectangle in 2D space.
+	/// </summary>
+	public struct Rectangle
+	{
+		/// <summary>
+		/// A rectangle with all it's properties initialised to zero.
+		/// </summary>
+		public static Rectangle Zero = new Rectangle() { X = 0, Y = 0, Width = 0, Height = 0 };
+
+		#region Core Data
+		/// <summary>
+		/// The X coordinate of the rectangle.
+		/// </summary>
+		public int X { get; set; }
+		/// <summary>
+		/// The Ycoordinateof the rectangle.
+		/// </summary>
+		/// <value>The y.</value>
+		public int Y { get; set; }
+		/// <summary>
+		/// The width of the rectangle.
+		/// </summary>
+		public int Width { get; set; }
+		/// <summary>
+		/// The height of the rectangle.
+		/// </summary>
+		public int Height { get; set; }
+		#endregion
+
+		#region Corners
+		/// <summary>
+		/// The top-left corner of the rectangle.
+		/// </summary>
+		public Point TopLeft {
+			get {
+				return new Point(X, Y);
+			}
+		}
+		/// <summary>
+		/// The top-right corner of the rectangle.
+		/// </summary>
+		public Point TopRight {
+			get {
+				return new Point(X + Width, Y);
+			}
+		}
+		/// <summary>
+		/// The bottom-left corner of the rectangle.
+		/// </summary>
+		public Point BottomLeft {
+			get {
+				return new Point(X, Y + Height);
+			}
+		}
+		/// <summary>
+		/// The bottom-right corner of the rectangle.
+		/// </summary>
+		public Point BottomRight {
+			get {
+				return new Point(X + Width, Y + Height);
+			}
+		}
+		#endregion
+
+		#region Edges
+		/// <summary>
+		/// The Y coordinate of the top of the rectangle.
+		/// </summary>
+		public int Top {
+			get {
+				return Y;
+			}
+		}
+		/// <summary>
+		/// The Y coordinate of the bottom of the rectangle.
+		/// </summary>
+		public int Bottom {
+			get {
+				return Y + Width;
+			}
+		}
+		/// <summary>
+		/// The X coordinate of the left side of the rectangle.
+		/// </summary>
+		public int Left {
+			get {
+				return X;
+			}
+		}
+		/// <summary>
+		/// The X coordinate of the right side of the rectangle.
+		/// </summary>
+		public int Right {
+			get {
+				return X + Width;
+			}
+		}
+		#endregion
+
+		public Rectangle(int x, int y, int width, int height)
+		{
+			X = x;
+			Y = y;
+			Width = width;
+			Height = height;
+		}
+	}
+}
+
diff --git a/Nibriboard/Utilities/Vector2.cs b/Nibriboard/Utilities/Vector2.cs
new file mode 100644
index 0000000..7163543
--- /dev/null
+++ b/Nibriboard/Utilities/Vector2.cs
@@ -0,0 +1,63 @@
+﻿using System;
+
+namespace SBRL.Utilities
+{
+	/// <summary>
+	/// Represents a single point in 2D space.
+	/// May also be used to represent a direction with a magnitude.
+	/// </summary>
+	public struct Vector2
+	{
+		/// <summary>
+		/// A Vector 2 with all it's proeprties initialised to zero.
+		/// </summary>
+		public static Vector2 Zero = new Vector2() { X = 0, Y = 0 };
+
+		/// <summary>
+		/// The X coordinate.
+		/// </summary>
+		public int X { get; set; }
+		/// <summary>
+		/// The Y coordinate.
+		/// </summary>
+		public int Y { get; set; }
+
+
+		public Vector2(int x, int y)
+		{
+			X = x;
+			Y = y;
+		}
+
+
+		public Vector2 Add(Vector2 b)
+		{
+			return new Vector2(
+				X + b.X,
+				Y + b.X
+			);
+		}
+		public Vector2 Subtract(Vector2 b)
+		{
+			return new Vector2(
+				X - b.X,
+				Y - b.X
+			);
+		}
+		public Vector2 Divide(int b)
+		{
+			return new Vector2(
+				X / b,
+				Y / b
+			);
+		}
+		public Vector2 Multiply(int b)
+		{
+			return new Vector2(
+				X * b,
+				Y * b
+			);
+		}
+	}
+}
+
