1
0
Fork 0

[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:
Starbeamrainbowlabs 2017-04-15 16:40:25 +01:00
parent b6f50a48ba
commit 5dc1a2d9e5
6 changed files with 145 additions and 10 deletions

View File

@ -21,7 +21,7 @@ namespace Nibriboard.Client
public int LinesCompleted { get; private set; } = 0;
/// <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>
public int IncompletedLines {
get {

View File

@ -55,7 +55,8 @@ namespace Nibriboard.Client
["CursorPosition"] = typeof(CursorPositionMessage),
["PlaneChange"] = typeof(PlaneChangeMessage),
["ChunkUpdateRequest"] = typeof(ChunkUpdateRequestMessage),
["LinePartMessage"] = typeof(LinePartMessage)
["LinePartMessage"] = typeof(LinePartMessage),
["LineCompleteMessage"] = typeof(LineCompleteMessage)
};
/// <summary>
@ -327,16 +328,37 @@ namespace Nibriboard.Client
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)
{
// Forward the line part to everyone on this plane
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;
}
/// <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

View File

@ -22,6 +22,8 @@ namespace Nibriboard.Client
private ClientSettings clientSettings;
public List<NibriClient> Clients = new List<NibriClient>();
public LineIncubator LineIncubator = new LineIncubator();
/// <summary>
/// The cancellation token that's used by the main server to tell us when we should shut down.
/// </summary>

View File

@ -7,6 +7,32 @@ using System.Runtime.Serialization;
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>
/// Represents a single chunk of an infinite <see cref="NibriboardServer.RippleSpace.Plane" />.
/// </summary>
@ -33,7 +59,10 @@ namespace Nibriboard.RippleSpace
/// </summary>
public readonly ChunkReference Location;
/// <summary>
/// Fired when this chunk is updated.
/// </summary>
public event ChunkUpdateEvent ChunkUpdateEvent;
/// <summary>
/// The time at which this chunk was loaded.
/// </summary>
@ -83,9 +112,8 @@ namespace Nibriboard.RippleSpace
}
/// <summary>
/// Whether this chunk could, theorectically, be unloaded. Of course,
/// the server may decide it doesn't need to unload us even if we're
/// inactive.
/// Whether this chunk could, theorectically, be unloaded.
/// This method takes into account whether this is a primary chunk or not.
/// </summary>
public bool CouldUnload
{
@ -148,6 +176,8 @@ namespace Nibriboard.RippleSpace
lines.Add(newLine);
i++;
}
ChunkUpdateEvent(this, new ChunkUpdateEventArgs() { UpdateType = ChunkUpdateType.Addition });
}
public IEnumerator<DrawnLine> GetEnumerator()

View File

@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
namespace Nibriboard.RippleSpace
{
@ -37,16 +39,60 @@ namespace Nibriboard.RippleSpace
/// </summary>
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>
/// The chunkspace that holds the currently loaded and active chunks.
/// </summary>
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)
{
Name = inName;
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}";
}
/// <summary>
@ -74,6 +120,7 @@ namespace Nibriboard.RippleSpace
// return it fast.
string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilename());
Chunk loadedChunk = await Chunk.FromFile(this, chunkFilePath);
loadedChunk.OnChunkUpdate += HandleChunkUpdate;
loadedChunkspace.Add(chunkLocation, 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);
}
}
}

View File

@ -62,7 +62,7 @@ namespace Nibriboard.RippleSpace
Stopwatch maintenanceStopwatch = Stopwatch.StartNew();
foreach (Plane plane in Planes)
await plane.PerformMaintenance();
plane.PerformMaintenance();
LastMaintenanceDuration = maintenanceStopwatch.ElapsedMilliseconds;