DataCache
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
unit Myc.Core.DataCache;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Generics.Defaults,
|
||||
Myc.Futures, // Required for TFuture
|
||||
Myc.Signals; // Required for TLatch
|
||||
|
||||
type
|
||||
// Represents a single cached file entry with its metadata and loaded data future.
|
||||
// TArrayItemType is the type of a single item within the TArray that is loaded by the future.
|
||||
TCachedFile<TArrayItemType> = class
|
||||
Name: String; // The full path and name of the cached file.
|
||||
Age: TDateTime; // The modification timestamp of the file at the time of caching.
|
||||
LastUsed: TDateTime; // The last time this cached entry was accessed.
|
||||
Data: TFuture<TArray<TArrayItemType>>; // A future representing the asynchronously loaded data.
|
||||
end;
|
||||
|
||||
// Generic in-memory cache for data files, supporting asynchronous loading
|
||||
// and eviction based on memory pressure and last usage.
|
||||
// TArrayItemType is the type of a single item within the TArray that is loaded by the future,
|
||||
// e.g., TDataPoint<TAskBidItem>.
|
||||
TDataCache<TArrayItemType> = class
|
||||
strict private
|
||||
FCachedFiles: TDictionary<String, TCachedFile<TArrayItemType>>;
|
||||
FLoadGate: TLatch;
|
||||
FIsMemoryLow: TFunc<Boolean>;
|
||||
|
||||
private
|
||||
// Manages cache size by evicting least recently used items if memory is low.
|
||||
procedure ManageCacheSize(const CurrentFileNameBeingLoaded: string);
|
||||
|
||||
public
|
||||
constructor Create(const ALoadGate: TLatch; const AIsMemoryLow: TFunc<Boolean>);
|
||||
destructor Destroy; override;
|
||||
|
||||
// Clears all entries from the cache.
|
||||
procedure Clear;
|
||||
|
||||
// Attempts to retrieve data for a given file from the cache.
|
||||
// If found and not stale, updates LastUsed and returns true.
|
||||
// Otherwise, returns false.
|
||||
function TryGetValue(const FileName: string; out ACachedFile: TCachedFile<TArrayItemType>): Boolean;
|
||||
|
||||
// Adds a new or updates an existing entry in the cache.
|
||||
procedure AddOrUpdate(const FileName: string; const AAge: TDateTime; const AData: TFuture<TArray<TArrayItemType>>);
|
||||
|
||||
// Returns the TLoadGate associated with this cache, for coordination of load operations.
|
||||
function GetLoadGate: TLatch; inline;
|
||||
|
||||
property LoadGate: TLatch read GetLoadGate;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Math;
|
||||
|
||||
{ TDataCache<TArrayItemType> }
|
||||
|
||||
constructor TDataCache<TArrayItemType>.Create(const ALoadGate: TLatch; const AIsMemoryLow: TFunc<Boolean>);
|
||||
begin
|
||||
inherited Create;
|
||||
FCachedFiles := TObjectDictionary<String, TCachedFile<TArrayItemType>>.Create([doOwnsValues]);
|
||||
FLoadGate := ALoadGate;
|
||||
FIsMemoryLow := AIsMemoryLow;
|
||||
end;
|
||||
|
||||
destructor TDataCache<TArrayItemType>.Destroy;
|
||||
begin
|
||||
FCachedFiles.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TDataCache<TArrayItemType>.Clear;
|
||||
begin
|
||||
FCachedFiles.Clear;
|
||||
end;
|
||||
|
||||
procedure TDataCache<TArrayItemType>.AddOrUpdate(
|
||||
const FileName: string;
|
||||
const AAge: TDateTime;
|
||||
const AData: TFuture<TArray<TArrayItemType>>
|
||||
);
|
||||
var
|
||||
cachedFile: TCachedFile<TArrayItemType>;
|
||||
begin
|
||||
if FCachedFiles.TryGetValue(FileName, cachedFile) then
|
||||
begin
|
||||
// Update existing entry
|
||||
cachedFile.Age := AAge;
|
||||
cachedFile.Data := AData; // This will replace the old future, potentially freeing old data
|
||||
cachedFile.LastUsed := Now;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Add new entry
|
||||
cachedFile := TCachedFile<TArrayItemType>.Create;
|
||||
cachedFile.Name := FileName;
|
||||
cachedFile.Age := AAge;
|
||||
cachedFile.Data := AData;
|
||||
cachedFile.LastUsed := Now;
|
||||
FCachedFiles.Add(FileName, cachedFile);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TDataCache<TArrayItemType>.GetLoadGate: TLatch;
|
||||
begin
|
||||
Result := FLoadGate;
|
||||
end;
|
||||
|
||||
procedure TDataCache<TArrayItemType>.ManageCacheSize(const CurrentFileNameBeingLoaded: string);
|
||||
begin
|
||||
if Assigned(FIsMemoryLow) and FIsMemoryLow() then
|
||||
begin
|
||||
var fL := FCachedFiles.Values.ToArray;
|
||||
// Sorts the cached files by LastUsed in ascending order (least recently used first).
|
||||
TArray.Sort<TCachedFile<TArrayItemType>>(
|
||||
fL,
|
||||
TComparer<TCachedFile<TArrayItemType>>.Construct(
|
||||
function(const Left, Right: TCachedFile<TArrayItemType>): Integer begin Result := Sign(Left.LastUsed - Right.LastUsed) end
|
||||
)
|
||||
);
|
||||
|
||||
for var i := 0 to High(fL) do
|
||||
begin
|
||||
if not FIsMemoryLow() then // Stop evicting if memory pressure is relieved
|
||||
break;
|
||||
// Do not remove the file that is currently being loaded.
|
||||
if fL[i].Name <> CurrentFileNameBeingLoaded then
|
||||
FCachedFiles.Remove(fL[i].Name);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TDataCache<TArrayItemType>.TryGetValue(const FileName: string; out ACachedFile: TCachedFile<TArrayItemType>): Boolean;
|
||||
var
|
||||
age: TDateTime;
|
||||
begin
|
||||
ManageCacheSize( FileName );
|
||||
|
||||
Result := FCachedFiles.TryGetValue(FileName, ACachedFile);
|
||||
if Result then
|
||||
begin
|
||||
// Check if the file on disk has changed since it was cached.
|
||||
if FileAge(FileName, age, true) then
|
||||
begin
|
||||
if age = ACachedFile.Age then
|
||||
begin
|
||||
// Cache hit and not stale, update LastUsed.
|
||||
ACachedFile.LastUsed := Now;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// File is stale, remove from cache.
|
||||
FCachedFiles.Remove(FileName);
|
||||
Result := False;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// File no longer exists, remove from cache.
|
||||
FCachedFiles.Remove(FileName);
|
||||
Result := False;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user