173 lines
5.8 KiB
ObjectPascal
173 lines
5.8 KiB
ObjectPascal
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.
|