1
0
Fork 0
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:
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; 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 {

View file

@ -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

View file

@ -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>

View file

@ -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()

View file

@ -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);
} }
} }
} }

View file

@ -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;