mirror of
https://github.com/sbrl/Nibriboard.git
synced 2018-01-10 21:33:49 +00:00
[server] Start handling line part and line complete udpates correctly. Also add more logic to the chunk/plane system ready for later.
This commit is contained in:
parent
b6f50a48ba
commit
5dc1a2d9e5
6 changed files with 145 additions and 10 deletions
|
@ -21,7 +21,7 @@ namespace Nibriboard.Client
|
||||||
public int LinesCompleted { get; private set; } = 0;
|
public int LinesCompleted { get; private set; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of liens that are still incubating and haven't been completed yet.
|
/// The number of lines that are still incubating and haven't been completed yet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int IncompletedLines {
|
public int IncompletedLines {
|
||||||
get {
|
get {
|
||||||
|
|
|
@ -55,7 +55,8 @@ namespace Nibriboard.Client
|
||||||
["CursorPosition"] = typeof(CursorPositionMessage),
|
["CursorPosition"] = typeof(CursorPositionMessage),
|
||||||
["PlaneChange"] = typeof(PlaneChangeMessage),
|
["PlaneChange"] = typeof(PlaneChangeMessage),
|
||||||
["ChunkUpdateRequest"] = typeof(ChunkUpdateRequestMessage),
|
["ChunkUpdateRequest"] = typeof(ChunkUpdateRequestMessage),
|
||||||
["LinePartMessage"] = typeof(LinePartMessage)
|
["LinePartMessage"] = typeof(LinePartMessage),
|
||||||
|
["LineCompleteMessage"] = typeof(LineCompleteMessage)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -327,16 +328,37 @@ namespace Nibriboard.Client
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles messages containing a fragment of a line from the client.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to process.</param>
|
||||||
protected Task handleLinePartMessage(LinePartMessage message)
|
protected Task handleLinePartMessage(LinePartMessage message)
|
||||||
{
|
{
|
||||||
// Forward the line part to everyone on this plane
|
// Forward the line part to everyone on this plane
|
||||||
manager.BroadcastPlane(this, message);
|
manager.BroadcastPlane(this, message);
|
||||||
|
|
||||||
throw new NotImplementedException();
|
List<LocationReference> linePoints = new List<LocationReference>(message.Points.Count);
|
||||||
|
foreach(Vector2 point in message.Points)
|
||||||
|
linePoints.Add(new LocationReference(CurrentPlane, point.X, point.Y));
|
||||||
|
|
||||||
|
manager.LineIncubator.AddBit(message.LineId, linePoints);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles notifications from clients telling us that they've finished drawing a line.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to handle.</param>
|
||||||
|
protected async Task handleLineCompleteMessage(LineCompleteMessage message)
|
||||||
|
{
|
||||||
|
DrawnLine line = manager.LineIncubator.CompleteLine(message.LineId);
|
||||||
|
line.LineWidth = message.LineWidth;
|
||||||
|
line.Colour = message.LineColour;
|
||||||
|
|
||||||
|
await CurrentPlane.AddLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ namespace Nibriboard.Client
|
||||||
private ClientSettings clientSettings;
|
private ClientSettings clientSettings;
|
||||||
public List<NibriClient> Clients = new List<NibriClient>();
|
public List<NibriClient> Clients = new List<NibriClient>();
|
||||||
|
|
||||||
|
public LineIncubator LineIncubator = new LineIncubator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The cancellation token that's used by the main server to tell us when we should shut down.
|
/// The cancellation token that's used by the main server to tell us when we should shut down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -7,6 +7,32 @@ using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace Nibriboard.RippleSpace
|
namespace Nibriboard.RippleSpace
|
||||||
{
|
{
|
||||||
|
public enum ChunkUpdateType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Something was added to the chunk.
|
||||||
|
/// </summary>
|
||||||
|
Addition,
|
||||||
|
/// <summary>
|
||||||
|
/// Something was deleted form the chunk.
|
||||||
|
/// </summary>
|
||||||
|
Deletion,
|
||||||
|
/// <summary>
|
||||||
|
/// A combination of additions and deletions were made to the chunk's contents.
|
||||||
|
/// </summary>
|
||||||
|
Combination
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChunkUpdateEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of update made to the chunk
|
||||||
|
/// </summary>
|
||||||
|
public ChunkUpdateType UpdateType { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void ChunkUpdateEvent(object sender, ChunkUpdateEventArgs eventArgs);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a single chunk of an infinite <see cref="NibriboardServer.RippleSpace.Plane" />.
|
/// Represents a single chunk of an infinite <see cref="NibriboardServer.RippleSpace.Plane" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -33,7 +59,10 @@ namespace Nibriboard.RippleSpace
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ChunkReference Location;
|
public readonly ChunkReference Location;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when this chunk is updated.
|
||||||
|
/// </summary>
|
||||||
|
public event ChunkUpdateEvent ChunkUpdateEvent;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which this chunk was loaded.
|
/// The time at which this chunk was loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -83,9 +112,8 @@ namespace Nibriboard.RippleSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this chunk could, theorectically, be unloaded. Of course,
|
/// Whether this chunk could, theorectically, be unloaded.
|
||||||
/// the server may decide it doesn't need to unload us even if we're
|
/// This method takes into account whether this is a primary chunk or not.
|
||||||
/// inactive.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CouldUnload
|
public bool CouldUnload
|
||||||
{
|
{
|
||||||
|
@ -148,6 +176,8 @@ namespace Nibriboard.RippleSpace
|
||||||
lines.Add(newLine);
|
lines.Add(newLine);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChunkUpdateEvent(this, new ChunkUpdateEventArgs() { UpdateType = ChunkUpdateType.Addition });
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<DrawnLine> GetEnumerator()
|
public IEnumerator<DrawnLine> GetEnumerator()
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace Nibriboard.RippleSpace
|
namespace Nibriboard.RippleSpace
|
||||||
{
|
{
|
||||||
|
@ -37,16 +39,60 @@ namespace Nibriboard.RippleSpace
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PrimaryChunkAreaSize = 10;
|
public int PrimaryChunkAreaSize = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum number of potentially unloadable chunks that we should have
|
||||||
|
/// before considering unloading some chunks
|
||||||
|
/// </summary>
|
||||||
|
public int MinUnloadeableChunks = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The soft limit on the number of chunks we can have loaded before we start
|
||||||
|
/// bothering to try and unload any chunks
|
||||||
|
/// </summary>
|
||||||
|
public int SoftLoadedChunkLimit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when one of the chunks on this plane updates.
|
||||||
|
/// </summary>
|
||||||
|
public event ChunkUpdateEvent OnChunkUpdate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chunkspace that holds the currently loaded and active chunks.
|
/// The chunkspace that holds the currently loaded and active chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Dictionary<ChunkReference, Chunk> loadedChunkspace = new Dictionary<ChunkReference, Chunk>();
|
protected Dictionary<ChunkReference, Chunk> loadedChunkspace = new Dictionary<ChunkReference, Chunk>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of chunks that this plane currently has laoded into active memory.
|
||||||
|
/// </summary>
|
||||||
|
public int LoadedChunks {
|
||||||
|
get {
|
||||||
|
return loadedChunkspace.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// The number of potentially unloadable chunks this plane currently has.
|
||||||
|
/// </summary>
|
||||||
|
public int UnloadableChunks {
|
||||||
|
get {
|
||||||
|
int result = 0;
|
||||||
|
foreach(KeyValuePair<ChunkReference, Chunk> chunkEntry in loadedChunkspace) {
|
||||||
|
if(chunkEntry.Value.CouldUnload)
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Plane(string inName, int inChunkSize)
|
public Plane(string inName, int inChunkSize)
|
||||||
{
|
{
|
||||||
Name = inName;
|
Name = inName;
|
||||||
ChunkSize = inChunkSize;
|
ChunkSize = inChunkSize;
|
||||||
|
|
||||||
|
// Set the soft loaded chunk limit to double the number of chunks in the
|
||||||
|
// primary chunks area
|
||||||
|
// Note that the primary chunk area is a radius around (0, 0) - not the diameter
|
||||||
|
SoftLoadedChunkLimit = PrimaryChunkAreaSize * PrimaryChunkAreaSize * 4;
|
||||||
|
|
||||||
StorageDirectory = $"./Planes/{Name}";
|
StorageDirectory = $"./Planes/{Name}";
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -74,6 +120,7 @@ namespace Nibriboard.RippleSpace
|
||||||
// return it fast.
|
// return it fast.
|
||||||
string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilename());
|
string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilename());
|
||||||
Chunk loadedChunk = await Chunk.FromFile(this, chunkFilePath);
|
Chunk loadedChunk = await Chunk.FromFile(this, chunkFilePath);
|
||||||
|
loadedChunk.OnChunkUpdate += HandleChunkUpdate;
|
||||||
loadedChunkspace.Add(chunkLocation, loadedChunk);
|
loadedChunkspace.Add(chunkLocation, loadedChunk);
|
||||||
|
|
||||||
return loadedChunk;
|
return loadedChunk;
|
||||||
|
@ -96,9 +143,43 @@ namespace Nibriboard.RippleSpace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PerformMaintenance()
|
public void PerformMaintenance()
|
||||||
{
|
{
|
||||||
// TODO: Perform maintenance here
|
// Be lazy and don't bother to perform maintenance if it's not needed
|
||||||
|
if(LoadedChunks < SoftLoadedChunkLimit ||
|
||||||
|
UnloadableChunks < MinUnloadeableChunks)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach(KeyValuePair<ChunkReference, Chunk> chunkEntry in loadedChunkspace)
|
||||||
|
{
|
||||||
|
if(!chunkEntry.Value.CouldUnload)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// This chunk has been inactive for a while - let's serialise it and save it to disk
|
||||||
|
Stream chunkSerializationSink = new FileStream(
|
||||||
|
Path.Combine(StorageDirectory, chunkEntry.Key.AsFilename()),
|
||||||
|
FileMode.Create,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.None
|
||||||
|
);
|
||||||
|
IFormatter binaryFormatter = new BinaryFormatter();
|
||||||
|
binaryFormatter.Serialize(chunkSerializationSink, chunkEntry.Value);
|
||||||
|
|
||||||
|
// Remove the chunk from the loaded chunkspace
|
||||||
|
loadedChunkspace.Remove(chunkEntry.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles chunk updates from the individual loaded chunks on this plane.
|
||||||
|
/// Re-emits chunk updates it catches wind of at plane-level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The chunk responsible for the update.</param>
|
||||||
|
/// <param name="eventArgs">The event arguments associated with the chunk update.</param>
|
||||||
|
protected void HandleChunkUpdate(object sender, ChunkUpdateEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
// Make the chunk update bubble up to plane-level
|
||||||
|
OnChunkUpdate(sender, eventArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ namespace Nibriboard.RippleSpace
|
||||||
Stopwatch maintenanceStopwatch = Stopwatch.StartNew();
|
Stopwatch maintenanceStopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
foreach (Plane plane in Planes)
|
foreach (Plane plane in Planes)
|
||||||
await plane.PerformMaintenance();
|
plane.PerformMaintenance();
|
||||||
|
|
||||||
LastMaintenanceDuration = maintenanceStopwatch.ElapsedMilliseconds;
|
LastMaintenanceDuration = maintenanceStopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue