diff --git a/RBG_Server.Core/CommunicationHandler.cs b/RBG_Server.Core/CommunicationHandler.cs index b46f04a..c25cc28 100644 --- a/RBG_Server.Core/CommunicationHandler.cs +++ b/RBG_Server.Core/CommunicationHandler.cs @@ -24,28 +24,39 @@ namespace RBG_Server /// Image data is stored in memory in a dictionary collection. Each byte[] represents an image file, compressed according to its file type; /// which helps save space in memory. Uses a CachedByteArray; essentially a normal byte array but automatically stored on disk if it is larger /// than a set size. - /// This limit can be changed at any time + /// This limit can be changed at any time, if memory is required to be freed /// public ConcurrentDictionary ImageCollection { get; } = new(); public List Players { get; } = new List(); + public List ImageList { get; } = new(); + public string BoardName { get; set; } public IPAddress IpAddress { get; set; } + public short Port { get; set; } private TcpClient stateRetriever { get; set; } + private TcpClient dataRetriever { get; set; } public int ColumnCount { get;private set; } + public int RowCount { get;private set; } + public int ColumnZoomStart { get;private set; } + public int RowZoomStart { get;private set; } + public int ColumnZoomSpan { get;private set; } + public int RowZoomSpan { get;private set; } + public int StartingColumn { get;private set; } + public int StartingRow { get;private set; } public void InitialiseServer() @@ -73,13 +84,11 @@ namespace RBG_Server AcceptConnections(client); }); } - // At this point, the ImageCollection will have already been initialised. - // The name of the board will also be set to {0}.. - // Server logic, such as accepting players and maintaining communication goes in here } private void AcceptConnections(TcpClient client) { + // Handle actual connections here throw new NotImplementedException(); } @@ -89,55 +98,7 @@ namespace RBG_Server NetworkStream stateStream = stateRetriever.GetStream(); Task stateLoader = new Task(async () => { - // Get game board details - byte[] buffer = new byte[] { 1, 0, 0, 0, 1 }; - stateStream.Write(buffer, 0, buffer.Length); - // Response size - var stateResponse = await GetResponse(stateStream, 4); - // Response data - stateResponse = await GetResponse(stateStream, GetInt32(stateResponse)); // Get the full response data - // Board state data - int playerID = GetInt32(stateResponse[..4]); - ColumnCount = GetInt32(stateResponse[..8]); - RowCount = GetInt32(stateResponse[8..12]); - ColumnZoomStart = GetInt32(stateResponse[12..16]); - RowZoomStart = GetInt32(stateResponse[16..20]); - ColumnZoomSpan = GetInt32(stateResponse[20..24]); - RowZoomSpan = GetInt32(stateResponse[24..28]); - StartingColumn = GetInt32(stateResponse[28..32]); - StartingRow = GetInt32(stateResponse[32..36]); - // Basic board data loaded; fetch players - buffer = new byte[] { 1, 0, 0, 0, 2}; - stateStream.Write(buffer, 0, buffer.Length); - stateResponse = await GetResponse(stateStream, 4); - stateResponse = await GetResponse(stateStream, GetInt32(stateResponse)); - // state response contains a player list; - // Player ID (Int32) - // Player Name (null-terminated string) - // Player Sprite (null-terminated string) - // Player Column (Int32) - // Player Row (Int32) - int start = 0; - while (start < stateResponse.Length) - { - int pos = start + 4; - int responsePlayerID = GetInt32(stateResponse[start..pos]); - start = pos; - while (stateResponse[pos++] != 0); // skip the bytes that aren't null - string responsePlayerName = Encoding.UTF8.GetString(stateResponse[start..pos]); - start = pos; - while (stateResponse[pos++] != 0); // skip the bytes that aren't null - string responsePlayerSprite = Encoding.UTF8.GetString(stateResponse[start..pos]); - start = pos; - pos += 4; - int playerColumn = GetInt32(stateResponse[start..pos]); - start = pos; - pos += 4; - int playerRow = GetInt32(stateResponse[start..pos]); - start = pos; - Player player = new Player(responsePlayerName, responsePlayerSprite, playerRow, playerColumn); - Players.Add(player); - } + }); @@ -145,41 +106,13 @@ namespace RBG_Server NetworkStream dataStream = dataRetriever.GetStream(); Task dataLoader = new Task(async () => { - byte[] buffer = new byte[] { 2, 0, 0, 0, 128, 0 }; // Get image collection names - dataStream.Write(buffer, 0, buffer.Length); - var dataResponse = await GetResponse(stateStream, 4); - - dataResponse = await GetResponse(dataStream, GetInt32(dataResponse)); - List data = new List(); - int start = 0; - while (start < dataResponse.Length) - { - int pos = start; - while (dataResponse[pos++] != 0); - ImageList.Add(Encoding.UTF8.GetString(dataResponse[start..pos])); - start = pos; - } - // Load all low-resolution images - foreach (string item in ImageList) - { - byte[] strBytes = Encoding.ASCII.GetBytes(item); - buffer = new byte[strBytes.Length + 6]; - byte[] lenBytes = GetBytes(strBytes.Length + 6); - lenBytes.CopyTo(buffer, 0); - buffer[4] = 129; // Download image - buffer[5] = 0; // mip_low - strBytes.CopyTo(buffer, 6); - dataStream.Write(buffer, 0, buffer.Length); - // Read the length, then the data - dataResponse = await GetResponse(dataStream); - dataResponse = await GetResponse(dataStream, GetInt32(dataResponse)); - ImageCollection.TryAdd(item + "_mip_low", (CachedByteArray)dataResponse); - - } + // At this point, the minimal amount of work required by the data thread has been done (load all thumbs) // When an asset is needed from here, queue a load }); + // start the tasks; they should have + dataLoader.Start(); stateLoader.Start(); @@ -202,6 +135,141 @@ namespace RBG_Server // Then load the low of each unused image (for quick retrieval) } + struct ProgressData + { + /// + /// Current activity being processed + /// + public enum Activity + { + Idle, + MessageSent, + MessageReceived, + ProcessingMessage, + MessageProcessed, + CollectionRecieved, + // ----- Image stuffs + ImageDownloaded, + // ----- Gameplay stuffs + Finished, + } + public Activity CurrentActivity; + + public int Progress; + } + + /// + /// Initialises data loader + /// + /// + /// + /// + private async Task InitDataLoader(NetworkStream dataStream, IProgress progressUpdates) + { + byte[] buffer = new byte[] { 2, 0, 0, 0, 128, 0 }; // Get image collection names + dataStream.Write(buffer, 0, buffer.Length); + // Notify Data was sent + progressUpdates.Report(new ProgressData() + { + CurrentActivity = ProgressData.Activity.MessageSent, + Progress = 0, + }); + var dataResponse = await GetResponse(dataStream, 4); + // Notify Data was recieved + progressUpdates.Report(new ProgressData() + { + CurrentActivity = ProgressData.Activity.MessageReceived, + Progress = 1, + }); + + dataResponse = await GetResponse(dataStream, GetInt32(dataResponse)); + List data = new List(); + int start = 0; + while (start < dataResponse.Length) + { + int pos = start; + while (dataResponse[pos++] != 0) ; + ImageList.Add(Encoding.UTF8.GetString(dataResponse[start..pos])); + start = pos; + } + // Load all low-resolution images + for (int i = 0; i < ImageList.Count; i++) + { + string item = ImageList[i]; + byte[] strBytes = Encoding.ASCII.GetBytes(item); + buffer = new byte[strBytes.Length + 6]; + byte[] lenBytes = GetBytes(strBytes.Length + 6); + lenBytes.CopyTo(buffer, 0); + buffer[4] = 129; // Download image + buffer[5] = 0; // mip_low + strBytes.CopyTo(buffer, 6); + dataStream.Write(buffer, 0, buffer.Length); + // Read the length, then the data + dataResponse = await GetResponse(dataStream); + dataResponse = await GetResponse(dataStream, GetInt32(dataResponse)); + ImageCollection.TryAdd(item + "_mip_low", (CachedByteArray)dataResponse); + progressUpdates.Report(new ProgressData() + { + CurrentActivity = ProgressData.Activity.MessageReceived, + Progress = i, + }); + } + } + + private async Task InitGameLoader(NetworkStream stateStream, IProgress progressUpdates) + { + // Get game board details + byte[] buffer = new byte[] { 1, 0, 0, 0, 1 }; + stateStream.Write(buffer, 0, buffer.Length); + // Response size + var stateResponse = await GetResponse(stateStream, 4); + // Response data + stateResponse = await GetResponse(stateStream, GetInt32(stateResponse)); // Get the full response data + // Board state data + int playerID = GetInt32(stateResponse[..4]); + ColumnCount = GetInt32(stateResponse[..8]); + RowCount = GetInt32(stateResponse[8..12]); + ColumnZoomStart = GetInt32(stateResponse[12..16]); + RowZoomStart = GetInt32(stateResponse[16..20]); + ColumnZoomSpan = GetInt32(stateResponse[20..24]); + RowZoomSpan = GetInt32(stateResponse[24..28]); + StartingColumn = GetInt32(stateResponse[28..32]); + StartingRow = GetInt32(stateResponse[32..36]); + // Basic board data loaded; fetch players + buffer = new byte[] { 1, 0, 0, 0, 2 }; + stateStream.Write(buffer, 0, buffer.Length); + stateResponse = await GetResponse(stateStream, 4); + stateResponse = await GetResponse(stateStream, GetInt32(stateResponse)); + // state response contains a player list; + // Player ID (Int32) + // Player Name (null-terminated string) + // Player Sprite (null-terminated string) + // Player Column (Int32) + // Player Row (Int32) + int start = 0; + while (start < stateResponse.Length) + { + + int pos = start + 4; + int responsePlayerID = GetInt32(stateResponse[start..pos]); + start = pos; + while (stateResponse[pos++] != 0) ; // skip the bytes that aren't null + string responsePlayerName = Encoding.UTF8.GetString(stateResponse[start..pos]); + start = pos; + while (stateResponse[pos++] != 0) ; // skip the bytes that aren't null + string responsePlayerSprite = Encoding.UTF8.GetString(stateResponse[start..pos]); + start = pos; + pos += 4; + int playerColumn = GetInt32(stateResponse[start..pos]); + start = pos; + pos += 4; + int playerRow = GetInt32(stateResponse[start..pos]); + start = pos; + Player player = new Player(responsePlayerName, responsePlayerSprite, playerRow, playerColumn); + Players.Add(player); + } + } + /// /// Idea is that each response is prefaced by a 4 byte stream length specifier. /// This requires a busy wait to achieve, if not all recieved at once. diff --git a/RBG_Server.Core/Player.cs b/RBG_Server.Core/Player.cs index c062072..426a26e 100644 --- a/RBG_Server.Core/Player.cs +++ b/RBG_Server.Core/Player.cs @@ -54,9 +54,10 @@ namespace RBG_Server // public Image sprite; // Sprite is now set as the implementation of this class - public Player(string name, string sprite, int row, int column) : base() // Call the base constructor at the same time; inits a Grid() + public Player(string name, string sprite, byte[] identifier, int row, int column) : base() // Call the base constructor at the same time; inits a Grid() { PlayerName = name; + hashCode = identifier; Sprite = sprite; Row = row; Column = column; @@ -71,20 +72,19 @@ namespace RBG_Server public new bool Equals(object obj) { - return (obj as Player).GetHashCode() == GetHashCode(); + return (obj as Player).hashCode == hashCode; } + + private byte[] hashCode; /// /// TODO: - /// Replace this with something that is connection-agnostic - /// I.e. the combo of name, position, linked sprite etc. should - /// allow us to reconnect disconnected players + /// The simplest way to auth a PC is using the computer name. + /// We don't need the textual version; just using the hash-code of the PC name is fine /// /// public new int GetHashCode() { - int res = (base.GetHashCode() + PlayerName.GetHashCode()) >> 8; // Right-shift the original hashcode by one byte & use our row & column - int rcByte = ((Row & 0xF) << 4) | (Column & 0xF); - return (rcByte << 24) | res; + return hashCode.GetHashCode(); } } }