Split tasks and implemented an ID

This commit is contained in:
Brychan Dempsey 2021-10-17 12:46:12 +13:00
parent f0c7980e9f
commit c8ac4ef579
2 changed files with 160 additions and 92 deletions

View File

@ -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; /// 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 /// 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. /// 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
/// </summary> /// </summary>
public ConcurrentDictionary<string, CachedByteArray> ImageCollection { get; } = new(); public ConcurrentDictionary<string, CachedByteArray> ImageCollection { get; } = new();
public List<Player> Players { get; } = new List<Player>(); public List<Player> Players { get; } = new List<Player>();
public List<string> ImageList { get; } = new(); public List<string> ImageList { get; } = new();
public string BoardName { get; set; } public string BoardName { get; set; }
public IPAddress IpAddress { get; set; } public IPAddress IpAddress { get; set; }
public short Port { get; set; } public short Port { get; set; }
private TcpClient stateRetriever { get; set; } private TcpClient stateRetriever { get; set; }
private TcpClient dataRetriever { get; set; } private TcpClient dataRetriever { get; set; }
public int ColumnCount { get;private set; } public int ColumnCount { get;private set; }
public int RowCount { get;private set; } public int RowCount { get;private set; }
public int ColumnZoomStart { get;private set; } public int ColumnZoomStart { get;private set; }
public int RowZoomStart { get;private set; } public int RowZoomStart { get;private set; }
public int ColumnZoomSpan { get;private set; } public int ColumnZoomSpan { get;private set; }
public int RowZoomSpan { get;private set; } public int RowZoomSpan { get;private set; }
public int StartingColumn { get;private set; } public int StartingColumn { get;private set; }
public int StartingRow { get;private set; } public int StartingRow { get;private set; }
public void InitialiseServer() public void InitialiseServer()
@ -73,13 +84,11 @@ namespace RBG_Server
AcceptConnections(client); AcceptConnections(client);
}); });
} }
// At this point, the ImageCollection will have already been initialised.
// The name of the board will also be set to <boardname>{0}.<extension>.
// Server logic, such as accepting players and maintaining communication goes in here
} }
private void AcceptConnections(TcpClient client) private void AcceptConnections(TcpClient client)
{ {
// Handle actual connections here
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -89,55 +98,7 @@ namespace RBG_Server
NetworkStream stateStream = stateRetriever.GetStream(); NetworkStream stateStream = stateRetriever.GetStream();
Task stateLoader = new Task(async () => 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(); NetworkStream dataStream = dataRetriever.GetStream();
Task dataLoader = new Task(async () => 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<byte> data = new List<byte>();
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) // 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 // When an asset is needed from here, queue a load
}); });
// start the tasks; they should have
dataLoader.Start(); dataLoader.Start();
stateLoader.Start(); stateLoader.Start();
@ -202,6 +135,141 @@ namespace RBG_Server
// Then load the low of each unused image (for quick retrieval) // Then load the low of each unused image (for quick retrieval)
} }
struct ProgressData
{
/// <summary>
/// Current activity being processed
/// </summary>
public enum Activity
{
Idle,
MessageSent,
MessageReceived,
ProcessingMessage,
MessageProcessed,
CollectionRecieved,
// ----- Image stuffs
ImageDownloaded,
// ----- Gameplay stuffs
Finished,
}
public Activity CurrentActivity;
public int Progress;
}
/// <summary>
/// Initialises data loader
/// </summary>
/// <param name="dataStream"></param>
/// <param name="progressUpdates"></param>
/// <returns></returns>
private async Task InitDataLoader(NetworkStream dataStream, IProgress<ProgressData> 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<byte> data = new List<byte>();
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<ProgressData> 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);
}
}
/// <summary> /// <summary>
/// Idea is that each response is prefaced by a 4 byte stream length specifier. /// 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. /// This requires a busy wait to achieve, if not all recieved at once.

View File

@ -54,9 +54,10 @@ namespace RBG_Server
// public Image sprite; // Sprite is now set as the implementation of this class // 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; PlayerName = name;
hashCode = identifier;
Sprite = sprite; Sprite = sprite;
Row = row; Row = row;
Column = column; Column = column;
@ -71,20 +72,19 @@ namespace RBG_Server
public new bool Equals(object obj) public new bool Equals(object obj)
{ {
return (obj as Player).GetHashCode() == GetHashCode(); return (obj as Player).hashCode == hashCode;
} }
private byte[] hashCode;
/// <summary> /// <summary>
/// TODO: /// TODO:
/// Replace this with something that is connection-agnostic /// The simplest way to auth a PC is using the computer name.
/// I.e. the combo of name, position, linked sprite etc. should /// We don't need the textual version; just using the hash-code of the PC name is fine
/// allow us to reconnect disconnected players
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public new int GetHashCode() public new int GetHashCode()
{ {
int res = (base.GetHashCode() + PlayerName.GetHashCode()) >> 8; // Right-shift the original hashcode by one byte & use our row & column return hashCode.GetHashCode();
int rcByte = ((Row & 0xF) << 4) | (Column & 0xF);
return (rcByte << 24) | res;
} }
} }
} }