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 = 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>; // 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. TDataCache = class strict private FCachedFiles: TDictionary>; FLoadGate: TLatch; FIsMemoryLow: TFunc; 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); 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): Boolean; // Adds a new or updates an existing entry in the cache. procedure AddOrUpdate(const FileName: string; const AAge: TDateTime; const AData: TFuture>); // 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 } constructor TDataCache.Create(const ALoadGate: TLatch; const AIsMemoryLow: TFunc); begin inherited Create; FCachedFiles := TObjectDictionary>.Create([doOwnsValues]); FLoadGate := ALoadGate; FIsMemoryLow := AIsMemoryLow; end; destructor TDataCache.Destroy; begin FCachedFiles.Free; inherited; end; procedure TDataCache.Clear; begin FCachedFiles.Clear; end; procedure TDataCache.AddOrUpdate( const FileName: string; const AAge: TDateTime; const AData: TFuture> ); var cachedFile: TCachedFile; 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.Create; cachedFile.Name := FileName; cachedFile.Age := AAge; cachedFile.Data := AData; cachedFile.LastUsed := Now; FCachedFiles.Add(FileName, cachedFile); end; end; function TDataCache.GetLoadGate: TLatch; begin Result := FLoadGate; end; procedure TDataCache.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>( fL, TComparer>.Construct( function(const Left, Right: TCachedFile): 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.TryGetValue(const FileName: string; out ACachedFile: TCachedFile): 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.