/*
* Copyright (c) 2022 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Data.Caching.ObjectCache
* File: BlobCache.cs
*
* BlobCache.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Data.Caching.ObjectCache is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
namespace VNLib.Data.Caching
{
///
/// A structure that represents an item in cache
///
public readonly struct CacheEntry : IDisposable, IEquatable
{
private const int TIME_SEGMENT_SIZE = sizeof(long);
private const int LENGTH_SEGMENT_SIZE = sizeof(int);
private const int DATA_SEGMENT_START = TIME_SEGMENT_SIZE + LENGTH_SEGMENT_SIZE;
//Only contain ref to backing handle to keep struct size small
private readonly MemoryHandle _handle;
///
/// Creates a new and copies the initial data to the internal buffer
///
/// The initial data to store
/// The heap to allocate the buffer from
/// The new
public static CacheEntry Create(ReadOnlySpan data, IUnmangedHeap heap)
{
//Calc buffer size
int bufferSize = GetRequiredHandleSize(data.Length);
//Alloc buffer
MemoryHandle handle = heap.Alloc(bufferSize);
//Create new entry from handle
CacheEntry entry = new (handle, data.Length);
//Get the data segment
Span segment = entry.GetDataSegment();
//Copy data segment
data.CopyTo(segment);
return entry;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetRequiredHandleSize(int size)
{
//Caculate the minimum handle size to store all required information, rounded to nearest page
return (int)MemoryUtil.NearestPage(size + DATA_SEGMENT_START);
}
private CacheEntry(MemoryHandle handle, int length)
{
_handle = handle;
//Store data length, assumes the handle is large enough to store it
SetLength(length);
}
///
public readonly void Dispose() => _handle.Dispose();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly Span GetTimeSegment() => _handle.AsSpan(0, TIME_SEGMENT_SIZE);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly Span GetLengthSegment() => _handle.AsSpan(TIME_SEGMENT_SIZE, LENGTH_SEGMENT_SIZE);
///
/// Gets the size of the block of memory held by the underlying handle
///
/// The size of the block held by the current entry
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly nuint GetMemoryUsage()
{
_handle.ThrowIfClosed();
return _handle.ByteLength;
}
///
/// Gets the last set time
///
/// The last date stored
///
public readonly DateTime GetCreatedTime()
{
//Get the time segment and write the value in big endian
ReadOnlySpan segment = GetTimeSegment();
long ticks = BinaryPrimitives.ReadInt64BigEndian(segment);
//ticks back to
return new(ticks);
}
///
/// Sets the last modified time
///
/// The new time to set the handle to
///
public readonly void SetTime(DateTime time)
{
//Get native ticks value
long timeData = time.Ticks;
//Get the time segment and write the value in big endian
Span segment = GetTimeSegment();
BinaryPrimitives.WriteInt64BigEndian(segment, timeData);
}
///
/// Gets the length of the data segment
///
/// The length of the data segment
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetLength()
{
//Get the length segment
ReadOnlySpan segment = GetLengthSegment();
//Recover the integer
return BinaryPrimitives.ReadInt32BigEndian(segment);
}
private readonly void SetLength(int length)
{
//Get the length segment
Span segment = GetLengthSegment();
//Update the length value
BinaryPrimitives.WriteInt32BigEndian(segment, length);
}
///
/// Gets the stored data segment
///
/// The data segment
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span GetDataSegment()
{
//Get the actual length of the segment
int length = GetLength();
//Get the segment from its begining offset and
return _handle.AsSpan(DATA_SEGMENT_START, length);
}
///
/// Writes the specified segment to the internal buffer and resizes the buffer if necessary.
/// This operation overwrites any previously stored data
///
/// The data segment to store
///
public readonly void UpdateData(ReadOnlySpan data)
{
//Calc required buffer size
int bufferSize = GetRequiredHandleSize(data.Length);
//Resize handle if required
_handle.ResizeIfSmaller(bufferSize);
//Reset data length
SetLength(data.Length);
//Get the data segment
Span segment = GetDataSegment();
#if DEBUG
//Test segment length is equvalent to the requested data length
System.Diagnostics.Debug.Assert(segment.Length == data.Length);
#endif
//Copy data segment
data.CopyTo(segment);
}
///
public override bool Equals(object? obj) => obj is CacheEntry entry && Equals(entry);
///
public override int GetHashCode() => _handle.GetHashCode();
///
public static bool operator ==(CacheEntry left, CacheEntry right) => left.Equals(right);
///
public static bool operator !=(CacheEntry left, CacheEntry right) => !(left == right);
///
public bool Equals(CacheEntry other) => other.GetHashCode() == GetHashCode();
}
}