diff --git a/Nibriboard/Client/Messages/HandshakeRequestMessage.cs b/Nibriboard/Client/Messages/HandshakeRequestMessage.cs index 6c0eb6e..26c9641 100644 --- a/Nibriboard/Client/Messages/HandshakeRequestMessage.cs +++ b/Nibriboard/Client/Messages/HandshakeRequestMessage.cs @@ -9,10 +9,11 @@ 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. /// - public Rectangle InitialViewport = new Rectangle( - 0, 0, - 1366, 768 - ); + public Rectangle InitialViewport = Rectangle.Empty; + /// + /// The initial position of the user's cursor. + /// + public Point InitialAbsCursorPosition = Point.Empty; public HandshakeRequestMessage() { diff --git a/Nibriboard/Client/NibriClient.cs b/Nibriboard/Client/NibriClient.cs index 191ed2d..c573b23 100644 --- a/Nibriboard/Client/NibriClient.cs +++ b/Nibriboard/Client/NibriClient.cs @@ -2,22 +2,63 @@ using System.Threading.Tasks; using System.Text; using System.Collections.Generic; +using System.Drawing; using IotWeb.Common.Http; using SBRL.Utilities; +using Nibriboard.Client.Messages; +using Newtonsoft.Json; +using System.Reflection; +using RippleSpace; namespace Nibriboard.Client { public class NibriClient { + private static int nextId = 1; + private static int getNextId() { return nextId++; } + + /// + /// This client's unique id. + /// + public readonly int Id = getNextId(); + /// + /// The nibri client manager + /// private readonly NibriClientManager manager; + /// + /// The underlying websocket connection to the client. + /// Please try not to call the send method on here - use the NibriClient Send() method instead. + /// private readonly WebSocket client; - private Dictionary messageEventTypes = new Dictionary() + private static readonly Dictionary messageEventTypes = new Dictionary() { - ["handshake"] = + ["handshakeRequest"] = typeof(HandshakeRequestMessage) }; + /// + /// Whether this client has completed the handshake yet or not. + /// + public bool HandshakeCompleted = false; + /// + /// The name this client has assignedto themselves. + /// + /// The name. + public string Name { get; private set; } + /// + /// The current area that this client is looking at. + /// + /// The current view port. + public Rectangle CurrentViewPort { get; private set; } = Rectangle.Empty; + /// + /// The absolute position in plane-space of this client's cursor. + /// + /// The absolute cursor position. + public Point AbsoluteCursorPosition { get; private set; } = Point.Empty; + + #region Core Setup & Message Routing Logic + public NibriClient(NibriClientManager inManager, WebSocket inClient) { manager = inManager; @@ -40,16 +81,101 @@ namespace Nibriboard.Client } - public void Send(string message) + private async Task handleMessage(string frame) + { + string eventName = JsonUtilities.DeserializeProperty(frame, "event"); + + if (!messageEventTypes.ContainsKey(eventName)) + Log.WriteLine("Received message with invalid event {1} from Client #{0}", Id, eventName); + + Type messageType = messageEventTypes[eventName]; + Type jsonNet = typeof(JsonConvert); + MethodInfo deserialiserInfo = jsonNet.GetMethod("DeserailizeObject"); + MethodInfo genericInfo = deserialiserInfo.MakeGenericMethod(messageType); + var decodedMessage = genericInfo.Invoke(null, new object[] { frame }); + + string handlerMethodName = "handle" + decodedMessage.GetType().Name; + Type clientType = this.GetType(); + MethodInfo handlerInfo = clientType.GetMethod(handlerMethodName); + await (Task)handlerInfo.Invoke(this, new object[] { decodedMessage }); + } + + #endregion + + /// + /// Sends a to the client. + /// If you *really* need to send a raw message to the client, you can do so with the SendRawa() method. + /// + /// The message to send. + public void Send(Message message) + { + SendRaw(JsonConvert.SerializeObject(message)); + } + /// + /// Sends a raw string to the client. Don't use unnless you know what you're doing! + /// Use the regular Send() method if you can possibly help it. + /// + /// The message to send. + public void SendRaw(string message) { client.Send(Encoding.UTF8.GetBytes(message)); } - private async Task handleMessage(string frame) + /// + /// Generates a new ClientState object representing this client's state at the current time. + /// + public ClientState GenerateStateSnapshot() { - string eventName = JsonUtilities.DeserializeProperty(frame, "event"); + ClientState result = new ClientState(); + result.Id = Id; + result.Name = Name; + result.AbsCursorPosition = AbsoluteCursorPosition; + result.Viewport = CurrentViewPort; + return result; + } + #region Message Handlers + /// + /// Handles an incoming handshake request. We should only receive one of these! + /// + protected Task handleHandshakeRequestMessage(HandshakeRequestMessage message) + { + CurrentViewPort = message.InitialViewport; + AbsoluteCursorPosition = message.InitialAbsCursorPosition; + + Send(GenerateClientStateUpdate()); + + return Task.CompletedTask; + } + + /// + /// Handles an incoming cursor position message from the client.. + /// + protected Task handleCursorPositionMessage(CursorPositionMessage message) { + AbsoluteCursorPosition = message.AbsCursorPosition; + + return Task.CompletedTask; + } + #endregion + + /// + /// Generates an update message that contains information about the locations and states of all connected clients. + /// Automatically omits information about the current client. + /// + /// The client state update message. + protected ClientStateMessage GenerateClientStateUpdate() + { + ClientStateMessage result = new ClientStateMessage(); + foreach (NibriClient client in manager.Clients) + { + // Don't include ourselves in the update message! + if (client == this) + continue; + + result.ClientStates.Add(client.GenerateStateSnapshot()); + } + return result; } } } diff --git a/Nibriboard/Client/NibriClientManager.cs b/Nibriboard/Client/NibriClientManager.cs index a7ee868..82dafae 100644 --- a/Nibriboard/Client/NibriClientManager.cs +++ b/Nibriboard/Client/NibriClientManager.cs @@ -9,14 +9,14 @@ namespace Nibriboard.Client public class NibriClientManager : IWebSocketRequestHandler { private ClientSettings clientSettings; - private List clients = new List(); + public List Clients = new List(); /// /// The number of clients currently connected to this Nibriboard. /// public int ClientCount { get { - return clients.Count; + return Clients.Count; } } @@ -45,18 +45,18 @@ namespace Nibriboard.Client public void Connected(WebSocket newSocket) { NibriClient client = new NibriClient(this, newSocket); - clients.Add(client); + Clients.Add(client); } public void Broadcast(NibriClient sendingClient, string message) { - foreach(NibriClient client in clients) + foreach(NibriClient client in Clients) { // Don't send the message to the sender if (client == sendingClient) continue; - client.Send(message); + client.SendRaw(message); } } } diff --git a/Nibriboard/Nibriboard.csproj b/Nibriboard/Nibriboard.csproj index 5157dad..033eff5 100644 --- a/Nibriboard/Nibriboard.csproj +++ b/Nibriboard/Nibriboard.csproj @@ -7,7 +7,7 @@ Exe Nibriboard Nibriboard - v4.5 + v4.6.1 true @@ -74,6 +74,9 @@ + + + @@ -97,7 +100,7 @@ - +