using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Hjg.Pngcs;
using Hjg.Pngcs.Chunks;
namespace Automap
{
public class Snapshotter
{
public readonly int chunkSize;
private const string customTimeFormat = @"yyyy.MM.dd.HH.mm.ssZzz";
public string fileName;
public string chunkPath;
public ColumnsMetadata cols;
//TODO: Refactor - so Edges are set at construction time, as ColumnsMetadata is async updating in real time!
public int NorthEdge => cols.North_mostChunk;
public int WestEdge => cols.West_mostChunk;
public int Width => (cols.East_mostChunk - WestEdge + 1);
public int Height => (cols.South_mostChunk - NorthEdge + 1);
public Snapshotter(string path, ColumnsMetadata cols, int chunkSize, int worldSeed)
{
this.fileName = Path.Combine(path, $"snapshot_{worldSeed}_{DateTime.Now.ToString(customTimeFormat)}.png");
this.chunkPath = Path.Combine(path, AutomapSystem._chunkPath);
this.cols = cols;
this.chunkSize = chunkSize;
}
///
/// takes a snapshot. this should be called from an extra thread.
///
/// path to the map dir
/// name of the chunks dir part thing
///
///
public async void Take()
{
var t = new Stopwatch();
t.Start();
Console.WriteLine("snapshot started");
ImageInfo info = new ImageInfo(Width * chunkSize, Height * chunkSize, 8, false);
PngWriter snapWriter = FileHelper.CreatePngWriter(fileName, info, true);
PngMetadata meta = snapWriter.GetMetadata( );
meta.SetTimeNow( );
var transparencyChunk = meta.CreateTRNSChunk( );
transparencyChunk.SetRGB(0, 0, 0);//CHECK: This is pure black.
meta.SetText("Northmost", NorthEdge.ToString("D"));
meta.SetText("Eastmost", WestEdge.ToString("D"));
meta.SetText("Width", Width.ToString("D"));
meta.SetText("Height", Height.ToString("D"));
/*
Red: 2 bytes, range 0 .. (2^bitdepth)-1
Green: 2 bytes, range 0 .. (2^bitdepth)-1
Blue: 2 bytes, range 0 .. (2^bitdepth)-1
*/
snapWriter.CompLevel = 5;
snapWriter.CompressionStrategy = Hjg.Pngcs.Zlib.EDeflateCompressStrategy.Filtered;
var orderedList =
from ch in cols
group ch by ch.Location.Y into g
orderby g.Key
select g;
// that sorts things in ascending order so we can only create (chunkSize) lines at once
int row = 0, lastPosY = 0, posY = 0;
foreach (var chunkGroup in orderedList)
{
// oh god here we go...
posY = chunkGroup.Key - NorthEdge;
var delta = posY - lastPosY;
lastPosY = posY;
// if there is more than 1 blank step then the gap needs to be filled.
for (int i = 1; i < delta; i++)
{
byte[] blankLine = new byte[info.BytesPerRow];
for (int j = 0; j < chunkSize; j++)
snapWriter.WriteRowByte(blankLine, row++);
}
var inGroup = (await ReadAllInGroup(chunkGroup)).ToArray();
for (int r = 0; r < chunkSize; r++)
{
var line = new byte[info.BytesPerRow];
for (int chunk = 0; chunk < inGroup.Length; chunk++)
{
var posX = inGroup[chunk].Key * chunkSize * info.BytesPixel;
inGroup[chunk].Value?[r].CopyTo(line, posX);
}
snapWriter.WriteRowByte(line, row++);
}
}
snapWriter.ShouldCloseStream = true;
try
{
snapWriter.End();
}
catch (Exception)
{
Console.WriteLine("Snapshot exception!");
}
Console.WriteLine($"snapshot finished in {t.ElapsedMilliseconds}");
}
private async Task> ReadAllInGroup(IGrouping group)
{
var taskGroup = new Dictionary(group.Count());
foreach (var shardMeta in group)
{
var shardPath = Path.Combine(chunkPath, $"{shardMeta.Location.X}_{shardMeta.Location.Y}.png");
taskGroup.Add(shardMeta.Location.X - WestEdge, await ReadNoThrow(shardPath));
}
return taskGroup;
}
private async Task ReadNoThrow(string readPath)
{
try
{
return await Task.Run(() => FileHelper.CreatePngReader(readPath).ReadRowsByte().ScanlinesB);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return null;
} // do nothing on error
}
}
}