aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs
blob: e778b30caf5c2106ca663be7efe2dc1e705de514 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
* Copyright (c) 2023 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;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;

using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;


namespace VNLib.Data.Caching
{
    /// <summary>
    /// A structure that represents an item in cache. It contains the binary content
    /// of a cache entry by its internal memory handle
    /// </summary>
    public readonly record struct CacheEntry : IDisposable
    {
        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<byte> _handle;


        /// <summary>
        /// Creates a new <see cref="CacheEntry"/> and copies the initial data to the internal buffer
        /// </summary>
        /// <param name="data">The initial data to store</param>
        /// <param name="heap">The heap to allocate the buffer from</param>
        /// <returns>The newly initialized and ready to use <see cref="CacheEntry"/></returns>
        public static CacheEntry Create(ReadOnlySpan<byte> data, IUnmangedHeap heap)
        {
            //Calc buffer size
            int bufferSize = GetRequiredHandleSize(data.Length);

            //Alloc buffer
            MemoryHandle<byte> handle = heap.Alloc<byte>(bufferSize);
            
            //Create new entry from handle
            CacheEntry entry = new (handle, data.Length);

            //Get the data segment
            Span<byte> 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<byte> handle, int length)
        {
            _handle = handle;
            //Store data length, assumes the handle is large enough to store it
            SetLength(length);
        }


        ///<inheritdoc/>
        public readonly void Dispose() => _handle?.Dispose();


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private readonly Span<byte> GetTimeSegment() => _handle.AsSpan(0, TIME_SEGMENT_SIZE);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private readonly Span<byte> GetLengthSegment() => _handle.AsSpan(TIME_SEGMENT_SIZE, LENGTH_SEGMENT_SIZE);

        /// <summary>
        /// Gets the size of the block of memory held by the underlying handle
        /// </summary>
        /// <returns>The size of the block held by the current entry</returns>
        /// <exception cref="ObjectDisposedException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public readonly nuint GetMemoryUsage()
        {
            _handle.ThrowIfClosed();
            return _handle.ByteLength;
        }


        /// <summary>
        /// Gets the last set time
        /// </summary>
        /// <returns>The last date stored</returns>
        /// <exception cref="ObjectDisposedException"></exception>
        public readonly DateTime GetTime()
        {
            //Get the time segment and write the value in big endian
            ReadOnlySpan<byte> segment = GetTimeSegment();

            long ticks = BinaryPrimitives.ReadInt64BigEndian(segment);

            //ticks back to 
            return new(ticks);
        }

        /// <summary>
        /// Sets the last modified time
        /// </summary>
        /// <param name="time">The new time to set the handle to</param>
        /// <exception cref="ObjectDisposedException"></exception>
        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<byte> segment = GetTimeSegment();

            BinaryPrimitives.WriteInt64BigEndian(segment, timeData);
        }

        /// <summary>
        /// Gets the length of the data segment
        /// </summary>
        /// <returns>The length of the data segment</returns>
        /// <exception cref="ObjectDisposedException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public readonly int GetLength()
        {
            //Get the length segment
            ReadOnlySpan<byte> segment = GetLengthSegment();
            //Recover the integer
            return BinaryPrimitives.ReadInt32BigEndian(segment);
        }

        private readonly void SetLength(int length)
        {
            //Get the length segment
            Span<byte> segment = GetLengthSegment();
        
            //Update the length value
            BinaryPrimitives.WriteInt32BigEndian(segment, length);
        }

        /// <summary>
        /// Gets the stored data segment
        /// </summary>
        /// <returns>The data segment</returns>
        /// <exception cref="ObjectDisposedException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public readonly Span<byte> 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);
        }

        /// <summary>
        /// Writes the specified segment to the internal buffer and resizes the buffer if necessary.
        /// This operation overwrites any previously stored data
        /// </summary>
        /// <param name="data">The data segment to store</param>
        /// <exception cref="ObjectDisposedException"></exception>
        public readonly void UpdateData(ReadOnlySpan<byte> 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<byte> segment = GetDataSegment();

#if DEBUG
            //Test segment length is equivalent to the requested data length
            System.Diagnostics.Debug.Assert(segment.Length == data.Length);
#endif
            //Copy data segment
            data.CopyTo(segment);
        }

        ///<inheritdoc/>
        public override int GetHashCode() => _handle.GetHashCode();

        /// <summary>
        /// Gets a <see cref="MemoryHandle"/> offset to the start of the 
        /// internal data segment, and avoids calling the fixed keyword.
        /// The handle must be disposed/released to avoid memeory leaks.
        /// </summary>
        /// <remarks>
        /// WARNING: You must respect the <see cref="GetLength"/> return value so 
        /// as no to overrun the valid data segment.
        /// </remarks>
        /// <returns>A handle that points to the begining of the data segment</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public readonly MemoryHandle UnsafeGetDataSegmentHandle()
        {
            //Get the handle offset to the data segment start, the caller must know when the data segment ends
            return _handle.Pin(DATA_SEGMENT_START);
        }
    }
}