/*
* GNU AFFERO GENERAL PUBLIC LICENSE
* Version 3, 19 November 2007
* Copyright (C) 2007 Free Software Foundation, Inc.
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
using Emgu.CV;
using Emgu.CV.CvEnum;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using UVtools.Core.FileFormats;
using UVtools.Core.Operations;
namespace UVtools.Core.Managers;
public class MatCacheManager : IDisposable
{
///
/// Gets the slicer file
///
public FileFormat SlicerFile { get; }
///
/// Gets or sets the cache count of items to keep in memory
///
public ushort CacheCount { get; set; }
///
/// Gets the number of cached elements per a cache entry
///
public byte ElementsPerCache { get; }
///
/// Gets the starting layer index range
///
public uint LayerIndexStart { get; }
///
/// Gets the ending layer index range
///
public uint LayerIndexEnd { get; }
///
/// Gets the size of this collection
///
public uint CollectionSize => LayerIndexEnd - LayerIndexStart + 1;
///
/// Gets the image rotation to cache
///
public RotateDirection Rotate { get; init; } = RotateDirection.None;
///
/// Gets the image flip to cache
///
public FlipDirection Flip { get; init; } = FlipDirection.None;
///
/// Gets if striping anti-aliasing is enabled, cache will be threshold'ed
///
public bool StripAntiAliasing { get; init; }
///
/// Gets or sets the cache direction, false to go backwards, true to go forward
///
public bool Direction { get; set; } = true;
///
/// If enabled it will not calculate the cache index given a layer index, set this to true to use your own indexing without pair them with layers
///
public bool UseRawIndexInsteadOfLayerIndex { get; init; }
///
/// Gets or sets the auto dispose mode, it will dispose all mat's below of above passed index
///
public bool AutoDispose { get; set; } = false;
///
/// Gets or sets the amount of last mats to keep cached when is enabled
///
public ushort AutoDisposeKeepLast { get; set; } = 0;
///
/// Gets the cache mat array
///
private Mat[][] MatCache { get; }
///
/// Gets or sets the action to trigger after cache the initial
///
public Action? AfterCacheAction { get; set; }
public MatCacheManager(FileFormat slicerFile, ushort cacheCount = 0, byte elementsPerCache = 1) : this(slicerFile, 0, slicerFile.LastLayerIndex, cacheCount, elementsPerCache)
{ }
public MatCacheManager(FileFormat slicerFile, uint collectionSize, ushort cacheCount = 0, byte elementsPerCache = 1) : this(slicerFile, 0, collectionSize-1, cacheCount, elementsPerCache)
{ }
public MatCacheManager(Operation operation, ushort cacheCount = 0, byte elementsPerCache = 1) : this(operation.SlicerFile, operation.LayerIndexStart, operation.LayerIndexEnd, cacheCount, elementsPerCache)
{}
public MatCacheManager(FileFormat slicerFile, uint layerIndexStart, uint layerIndexEnd, ushort cacheCount = 0, byte elementsPerCache = 1)
{
if (cacheCount == 0) cacheCount = (ushort)(Environment.ProcessorCount * 5);
if (layerIndexEnd == 0) layerIndexEnd = slicerFile.LayerCount > 0 ? slicerFile.LayerCount - 1 : 0;
SlicerFile = slicerFile;
LayerIndexStart = layerIndexStart;
LayerIndexEnd = layerIndexEnd;
CacheCount = cacheCount;
ElementsPerCache = elementsPerCache;
MatCache = new Mat[CollectionSize][];
if (layerIndexStart == 0) UseRawIndexInsteadOfLayerIndex = true;
}
///
/// Gets the cache index from a layer index, required when layer range is not 0 started
///
///
///
private uint LayerIndexToCacheIndex(uint layerIndex)
{
return UseRawIndexInsteadOfLayerIndex ? layerIndex : layerIndex - LayerIndexStart;
}
///
/// Gets the layer index from the cache index, required when layer range is not 0 started
///
///
///
private uint CacheIndexToLayerIndex(uint index)
{
return UseRawIndexInsteadOfLayerIndex ? index : index + LayerIndexStart;
}
///
/// Gets all cached mat's given an index
///
///
///
public Mat[] Get(uint layerIndex)
{
uint index = LayerIndexToCacheIndex(layerIndex);
if (MatCache[index] is null)
{
if (AutoDispose) // Dispose as go
{
ClearButKeep(layerIndex, AutoDisposeKeepLast);
}
int fromLayerIndex = (int)layerIndex;
int toLayerIndex = (int)Math.Min(LayerIndexEnd+1, layerIndex + CacheCount);
if (!Direction)
{
toLayerIndex = fromLayerIndex + 1;
fromLayerIndex = Math.Max((int)LayerIndexStart, fromLayerIndex - CacheCount);
}
Parallel.For(fromLayerIndex, toLayerIndex, CoreSettings.ParallelOptions,
currentLayerIndex =>
{
var currentCacheIndex = LayerIndexToCacheIndex((uint)currentLayerIndex);
if (MatCache[currentCacheIndex] is not null) return; // Already cached
MatCache[currentCacheIndex] = new Mat[ElementsPerCache];
MatCache[currentCacheIndex][0] = SlicerFile[currentLayerIndex].LayerMat;
if (Flip != FlipDirection.None)
{
CvInvoke.Flip(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], (FlipType)Flip);
}
if (Rotate != RotateDirection.None)
{
CvInvoke.Rotate(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], (RotateFlags) Rotate);
}
if (StripAntiAliasing)
{
CvInvoke.Threshold(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], 127, byte.MaxValue, ThresholdType.Binary);
}
AfterCacheAction?.Invoke(MatCache[currentCacheIndex]);
});
}
return MatCache[index];
}
public Mat Get(uint layerIndex, byte elementIndex)
{
return Get(layerIndex)[elementIndex];
}
public Mat Get1(uint layerIndex)
{
return Get(layerIndex)[0];
}
public (Mat mat1, Mat mat2) Get2(uint layerIndex)
{
var mats = Get(layerIndex);
return (mats[0], mats[1]);
}
public (Mat mat1, Mat mat2, Mat mat3) Get3(uint layerIndex)
{
var mats = Get(layerIndex);
return (mats[0], mats[1], mats[2]);
}
public void GetAndConsumeWork(uint layerIndex, Action action)
{
try
{
action.Invoke(Get(layerIndex));
}
catch (Exception e)
{
Debug.WriteLine(e);
throw;
}
finally
{
Consume(layerIndex);
}
}
public void GetAndConsumeWork(uint layerIndex, Action action)
{
try
{
action.Invoke(Get1(layerIndex));
}
catch (Exception e)
{
Debug.WriteLine(e);
throw;
}
finally
{
Consume(layerIndex);
}
}
public void GetAndConsumeWork(uint layerIndex, byte elementIndex, Action action)
{
try
{
action.Invoke(Get(layerIndex)[elementIndex]);
}
catch (Exception e)
{
Debug.WriteLine(e);
throw;
}
finally
{
Consume(layerIndex);
}
}
///
/// Consume and dispose the cached Mats for given layer index
///
///
public void Consume(uint layerIndex)
{
var index = LayerIndexToCacheIndex(layerIndex);
if(MatCache[index] is null) return;
for (int i = 0; i < MatCache[index].Length; i++)
{
MatCache[index][i]?.Dispose();
}
MatCache[index] = null!;
}
///
/// Clears and dispose the cache but keep the selected index and optionally the last n indexes
///
///
///
public void ClearButKeep(uint layerIndex, ushort keepLast = 0)
{
if (Direction)
{
for (int i = (int)layerIndex - 1 - keepLast; i >= LayerIndexStart; i--) Consume((uint)i);
}
else
{
for (int i = (int)layerIndex + 1 + keepLast; i <= LayerIndexEnd; i++) Consume((uint)i);
}
}
///
/// Clears and dispose all the cache
///
public void Clear()
{
for (uint i = 0; i < MatCache.Length; i++)
{
Consume(CacheIndexToLayerIndex(i));
}
}
///
public void Dispose()
{
Clear();
}
}