2017-01-06 18:57:58 +00:00
|
|
|
|
using System;
|
2017-01-06 20:45:35 +00:00
|
|
|
|
using System.Collections.Generic;
|
2017-01-06 21:14:31 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2017-01-07 17:35:56 +00:00
|
|
|
|
using System.IO;
|
2017-04-15 15:40:25 +00:00
|
|
|
|
using System.Runtime.Serialization.Formatters.Binary;
|
|
|
|
|
using System.Runtime.Serialization;
|
2017-03-04 21:49:51 +00:00
|
|
|
|
|
2017-01-06 18:57:58 +00:00
|
|
|
|
namespace Nibriboard.RippleSpace
|
|
|
|
|
{
|
2017-01-06 19:05:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represents an infinite plane.
|
|
|
|
|
/// </summary>
|
2017-01-06 18:57:58 +00:00
|
|
|
|
public class Plane
|
|
|
|
|
{
|
2017-01-06 21:14:31 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The name of this plane.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly string Name;
|
|
|
|
|
|
2017-01-06 20:45:35 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The size of the chunks on this plane.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly int ChunkSize;
|
|
|
|
|
|
2017-01-07 17:35:56 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The path to the directory that the plane's information will be stored in.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly string StorageDirectory;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The number of milliseconds that should pass since a chunk's last
|
|
|
|
|
/// access in order for it to be considered inactive.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int InactiveMillisecs = 60 * 1000;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The number of chunks in a square around (0, 0) that should always be
|
|
|
|
|
/// loaded.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int PrimaryChunkAreaSize = 10;
|
|
|
|
|
|
2017-04-15 15:40:25 +00:00
|
|
|
|
/// <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;
|
|
|
|
|
|
2017-01-06 20:45:35 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The chunkspace that holds the currently loaded and active chunks.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected Dictionary<ChunkReference, Chunk> loadedChunkspace = new Dictionary<ChunkReference, Chunk>();
|
|
|
|
|
|
2017-04-15 15:40:25 +00:00
|
|
|
|
/// <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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-06 21:14:31 +00:00
|
|
|
|
public Plane(string inName, int inChunkSize)
|
2017-01-06 18:57:58 +00:00
|
|
|
|
{
|
2017-01-06 21:14:31 +00:00
|
|
|
|
Name = inName;
|
2017-01-06 20:45:35 +00:00
|
|
|
|
ChunkSize = inChunkSize;
|
2017-01-07 17:35:56 +00:00
|
|
|
|
|
2017-04-15 15:40:25 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
2017-01-07 17:35:56 +00:00
|
|
|
|
StorageDirectory = $"./Planes/{Name}";
|
2017-01-06 18:57:58 +00:00
|
|
|
|
}
|
2017-03-25 19:49:44 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Fetches a list of chunks by a list of chunk refererences.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="chunkRefs">The chunk references to fetch the attached chunks for.</param>
|
|
|
|
|
/// <returns>The chunks attached to the specified chunk references.</returns>
|
|
|
|
|
public async Task<List<Chunk>> FetchChunks(List<ChunkReference> chunkRefs)
|
|
|
|
|
{
|
|
|
|
|
List<Chunk> chunks = new List<Chunk>();
|
|
|
|
|
foreach(ChunkReference chunkRef in chunkRefs)
|
|
|
|
|
chunks.Add(await FetchChunk(chunkRef));
|
|
|
|
|
return chunks;
|
|
|
|
|
}
|
2017-01-06 21:14:31 +00:00
|
|
|
|
|
|
|
|
|
public async Task<Chunk> FetchChunk(ChunkReference chunkLocation)
|
|
|
|
|
{
|
|
|
|
|
// If the chunk is in the loaded chunk-space, then return it immediately
|
|
|
|
|
if(loadedChunkspace.ContainsKey(chunkLocation))
|
|
|
|
|
{
|
|
|
|
|
return loadedChunkspace[chunkLocation];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Uh-oh! The chunk isn't loaded at moment. Load it quick & then
|
|
|
|
|
// return it fast.
|
2017-01-07 20:19:21 +00:00
|
|
|
|
string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilename());
|
|
|
|
|
Chunk loadedChunk = await Chunk.FromFile(this, chunkFilePath);
|
2017-04-23 16:41:15 +00:00
|
|
|
|
loadedChunk.OnChunkUpdate += handleChunkUpdate;
|
2017-01-06 21:14:31 +00:00
|
|
|
|
loadedChunkspace.Add(chunkLocation, loadedChunk);
|
|
|
|
|
|
|
|
|
|
return loadedChunk;
|
|
|
|
|
}
|
2017-01-08 14:45:48 +00:00
|
|
|
|
|
|
|
|
|
public async Task AddLine(DrawnLine newLine)
|
|
|
|
|
{
|
|
|
|
|
List<DrawnLine> chunkedLine;
|
|
|
|
|
// Split the line up into chunked pieces if neccessary
|
|
|
|
|
if(newLine.SpansMultipleChunks)
|
|
|
|
|
chunkedLine = newLine.SplitOnChunks(ChunkSize);
|
|
|
|
|
else
|
|
|
|
|
chunkedLine = new List<DrawnLine>() { newLine };
|
|
|
|
|
|
|
|
|
|
// Add each segment to the appropriate chunk
|
|
|
|
|
foreach(DrawnLine newLineSegment in chunkedLine)
|
|
|
|
|
{
|
|
|
|
|
Chunk containingChunk = await FetchChunk(newLineSegment.ContainingChunk);
|
|
|
|
|
containingChunk.Add(newLineSegment);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-19 13:13:35 +00:00
|
|
|
|
|
2017-04-15 15:40:25 +00:00
|
|
|
|
public void PerformMaintenance()
|
|
|
|
|
{
|
|
|
|
|
// 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>
|
2017-04-23 16:41:15 +00:00
|
|
|
|
protected void handleChunkUpdate(object sender, ChunkUpdateEventArgs eventArgs)
|
2017-01-19 13:13:35 +00:00
|
|
|
|
{
|
2017-04-23 17:06:58 +00:00
|
|
|
|
Chunk updatingChunk = sender as Chunk;
|
|
|
|
|
if(updatingChunk == null)
|
|
|
|
|
{
|
|
|
|
|
Log.WriteLine("[Plane {0}] Invalid chunk update event captured - ignoring.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.WriteLine("[Plane {0}] Chunk at {1} {2} updated", Name, updatingChunk.Location, eventArgs.UpdateType);
|
|
|
|
|
|
2017-04-15 15:40:25 +00:00
|
|
|
|
// Make the chunk update bubble up to plane-level
|
|
|
|
|
OnChunkUpdate(sender, eventArgs);
|
2017-01-19 13:13:35 +00:00
|
|
|
|
}
|
2017-01-06 18:57:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|