mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-06-27 01:28:27 +02:00
335 lines
8.6 KiB
ObjectPascal
335 lines
8.6 KiB
ObjectPascal
{
|
|
$Id$
|
|
|
|
Generic cache class for FCL
|
|
Copyright (C) 2000 by Sebastian Guenther (sg@freepascal.org)
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program 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.
|
|
}
|
|
|
|
|
|
unit CacheCls;
|
|
|
|
interface
|
|
|
|
uses SysUtils;
|
|
|
|
resourcestring
|
|
SInvalidIndex = 'Invalid index %i';
|
|
|
|
type
|
|
|
|
{ TCache }
|
|
|
|
TCache = class;
|
|
|
|
ECacheError = class(Exception);
|
|
|
|
{ All slots are contained both in an array and in a double-linked list.
|
|
* The array, which doesn't need any additional memory, can be used for fast
|
|
sequential access; its indices can be exported to the user of the cache.
|
|
* The linked list is used for on-the-fly reordering of the elements, so
|
|
that the elements are MRU-sorted: The most recently used element is the
|
|
head, the last recently used element is the tail of the list. We need a
|
|
double-linked list: When the MRU value of an element changes, we will
|
|
have to walk the list reversed, but when we are searching for an entry,
|
|
we will search starting from the head. }
|
|
|
|
PCacheSlot = ^TCacheSlot;
|
|
TCacheSlot = record
|
|
Prev, Next: PCacheSlot; // -> double-linked list
|
|
Data: Pointer; // The custom data associated with this element
|
|
Index: Integer; // The array index of this slot
|
|
end;
|
|
|
|
PCacheSlotArray = ^TCacheSlotArray;
|
|
TCacheSlotArray = array[0..MaxInt div SizeOf(TCacheSlot) - 1] of TCacheSlot;
|
|
|
|
TOnIsDataEqual = function(ACache: TCache;
|
|
AData1, AData2: Pointer): Boolean of object;
|
|
TOnFreeSlot = procedure(ACache: TCache; SlotIndex: Integer) of object;
|
|
|
|
|
|
{ TCache implements a generic cache class.
|
|
If you use the "Add" method and not only "AddNew", you will have to set
|
|
the "OnIsDataEqual" property to your own compare function! }
|
|
|
|
TCache = class
|
|
private
|
|
FOnIsDataEqual: TOnIsDataEqual;
|
|
FOnFreeSlot: TOnFreeSlot;
|
|
function GetData(SlotIndex: Integer): Pointer;
|
|
function GetSlot(SlotIndex: Integer): PCacheSlot;
|
|
procedure SetData(SlotIndex: Integer; AData: Pointer);
|
|
procedure SetMRUSlot(ASlot: PCacheSlot);
|
|
procedure SetSlotCount(ACount: Integer);
|
|
protected
|
|
FSlotCount: Integer; // Number of cache elements
|
|
FSlots: PCacheSlotArray;
|
|
FMRUSlot, // First slot in MRU-sorted list
|
|
FLRUSlot: PCacheSlot; // Last slot in MRU-sorted list
|
|
public
|
|
constructor Create(ASlotCount: Integer);
|
|
destructor Destroy; override;
|
|
|
|
function Add(AData: Pointer): Integer; // Checks for duplicates
|
|
function AddNew(AData: Pointer): Integer; // No duplicate checks
|
|
function FindSlot(AData: Pointer): PCacheSlot; // nil => not found
|
|
function IndexOf(AData: Pointer): Integer; // -1 => not found
|
|
procedure Remove(AData: Pointer);
|
|
|
|
// Accesses to the "Data" array will be reflected by the MRU list!
|
|
property Data[SlotIndex: Integer]: Pointer read GetData write SetData;
|
|
property MRUSlot: PCacheSlot read FMRUSlot write SetMRUSlot;
|
|
property LRUSlot: PCacheSlot read FLRUSlot;
|
|
property SlotCount: Integer read FSlotCount write SetSlotCount;
|
|
property Slots[SlotIndex: Integer]: PCacheSlot read GetSlot;
|
|
|
|
property OnIsDataEqual: TOnIsDataEqual
|
|
read FOnIsDataEqual write FOnIsDataEqual;
|
|
{ OnFreeSlot is called when a slot is being released. This can only happen
|
|
during Add or AddNew, when there is no more free slot available. }
|
|
property OnFreeSlot: TOnFreeSlot read FOnFreeSlot write FOnFreeSlot;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
|
|
{ TCache }
|
|
|
|
function TCache.GetData(SlotIndex: Integer): Pointer;
|
|
begin
|
|
if (SlotIndex < 0) or (SlotIndex >= SlotCount) then
|
|
raise ECacheError.CreateFmt(SInvalidIndex, [SlotIndex]);
|
|
MRUSlot := @FSlots^[SlotIndex];
|
|
Result := MRUSlot^.Data;
|
|
end;
|
|
|
|
function TCache.GetSlot(SlotIndex: Integer): PCacheSlot;
|
|
begin
|
|
if (SlotIndex < 0) or (SlotIndex >= SlotCount) then
|
|
raise ECacheError.CreateFmt(SInvalidIndex, [SlotIndex]);
|
|
Result := @FSlots^[SlotIndex];
|
|
end;
|
|
|
|
procedure TCache.SetData(SlotIndex: Integer; AData: Pointer);
|
|
begin
|
|
if (SlotIndex < 0) or (SlotIndex >= FSlotCount) then
|
|
raise ECacheError.CreateFmt(SInvalidIndex, [SlotIndex]);
|
|
MRUSlot := @FSlots^[SlotIndex];
|
|
MRUSlot^.Data := AData;
|
|
end;
|
|
|
|
procedure TCache.SetMRUSlot(ASlot: PCacheSlot);
|
|
begin
|
|
if ASlot <> FMRUSlot then
|
|
begin
|
|
// Unchain ASlot
|
|
if Assigned(ASlot^.Prev) then
|
|
ASlot^.Prev^.Next := ASlot^.Next;
|
|
if Assigned(ASlot^.Next) then
|
|
ASlot^.Next^.Prev := ASlot^.Prev;
|
|
|
|
if ASlot = FLRUSlot then
|
|
FLRUSlot := ASlot^.Prev;
|
|
|
|
// Make ASlot the head of the double-linked list
|
|
ASlot^.Prev := nil;
|
|
ASlot^.Next := FMRUSlot;
|
|
FMRUSlot^.Prev := ASlot;
|
|
FMRUSlot := ASlot;
|
|
if not Assigned(FMRUSlot^.Next) then
|
|
FLRUSlot := FMRUSlot;
|
|
end;
|
|
end;
|
|
|
|
procedure TCache.SetSlotCount(ACount: Integer);
|
|
var
|
|
Slot: PCacheSlot;
|
|
i: Integer;
|
|
begin
|
|
if ACount <> SlotCount then
|
|
begin
|
|
if ACount < SlotCount then
|
|
begin
|
|
// Remove slots
|
|
|
|
if Assigned(OnFreeSlot) then
|
|
for i := ACount to SlotCount - 1 do
|
|
OnFreeSlot(Self, i);
|
|
|
|
while (MRUSlot^.Index >= ACount) and Assigned(MRUSlot^.Next) do
|
|
FMRUSlot := MRUSlot^.Next;
|
|
MRUSlot^.Prev := nil;
|
|
|
|
while (LRUSlot^.Index >= ACount) and Assigned(LRUSlot^.Prev) do
|
|
FLRUSlot := LRUSlot^.Prev;
|
|
LRUSlot^.Next := nil;
|
|
|
|
Slot := MRUSlot^.Next;
|
|
while Assigned(Slot) do
|
|
begin
|
|
if Slot^.Index >= ACount then
|
|
begin
|
|
Slot^.Prev^.Next := Slot^.Next;
|
|
if Assigned(Slot^.Next) then
|
|
Slot^.Next^.Prev := Slot^.Prev;
|
|
end;
|
|
Slot := Slot^.Next;
|
|
end;
|
|
|
|
ReallocMem(FSlots, ACount * SizeOf(TCacheSlot));
|
|
end else
|
|
begin
|
|
// Add new slots
|
|
ReallocMem(FSlots, ACount * SizeOf(TCacheSlot));
|
|
for i := SlotCount to ACount - 1 do
|
|
with FSlots^[i] do
|
|
begin
|
|
Prev := @FSlots^[i + 1];
|
|
Next := @FSlots^[i - 1];
|
|
Data := nil;
|
|
Index := i;
|
|
end;
|
|
LRUSlot^.Next := @FSlots^[ACount - 1];
|
|
FSlots^[ACount - 1].Prev := LRUSlot;
|
|
FLRUSlot := @FSlots^[SlotCount];
|
|
FLRUSlot^.Next := nil;
|
|
end;
|
|
FSlotCount := ACount;
|
|
end;
|
|
end;
|
|
|
|
constructor TCache.Create(ASlotCount: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
inherited Create;
|
|
FSlotCount := ASlotCount;
|
|
|
|
if FSlotCount = 0 then
|
|
exit;
|
|
|
|
{ Allocate the slots and initialize the double-linked list.
|
|
Note: The list is set up so that the last recently used
|
|
slot is the first slot! }
|
|
|
|
GetMem(FSlots, FSlotCount * SizeOf(TCacheSlot));
|
|
|
|
FMRUSlot := @FSlots^[FSlotCount - 1];
|
|
FLRUSlot := @FSlots^[0];
|
|
|
|
with FSlots^[0] do
|
|
begin
|
|
if FSlotCount > 1 then
|
|
Prev := @FSlots^[1]
|
|
else
|
|
Prev := nil;
|
|
Next := nil;
|
|
Data := nil;
|
|
Index := 0;
|
|
end;
|
|
|
|
for i := 1 to FSlotCount - 2 do
|
|
with FSlots^[i] do
|
|
begin
|
|
Next := @FSlots^[i - 1];
|
|
Prev := @FSlots^[i + 1];
|
|
Data := nil;
|
|
Index := i;
|
|
end;
|
|
|
|
with FSlots^[FSlotCount - 1] do
|
|
begin
|
|
Prev := nil;
|
|
if FSlotCount > 1 then
|
|
Next := @FSlots^[FSlotCount - 2];
|
|
Data := nil;
|
|
Index := FSlotCount - 1;
|
|
end;
|
|
end;
|
|
|
|
destructor TCache.Destroy;
|
|
begin
|
|
FreeMem(FSlots);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TCache.Add(AData: Pointer): Integer;
|
|
var
|
|
Slot: PCacheSlot;
|
|
begin
|
|
Slot := FindSlot(AData);
|
|
if Assigned(Slot) then
|
|
begin
|
|
MRUSlot := Slot;
|
|
Result := Slot^.Index;
|
|
end else
|
|
Result := AddNew(AData);
|
|
end;
|
|
|
|
function TCache.AddNew(AData: Pointer): Integer;
|
|
begin
|
|
if Assigned(OnFreeSlot) then
|
|
OnFreeSlot(Self, LRUSlot^.Index);
|
|
MRUSlot := LRUSlot;
|
|
MRUSlot^.Data := AData;
|
|
Result := MRUSlot^.Index;
|
|
end;
|
|
|
|
function TCache.FindSlot(AData: Pointer): PCacheSlot;
|
|
begin
|
|
ASSERT((SlotCount = 0) or Assigned(OnIsDataEqual));
|
|
Result := MRUSlot;
|
|
while Assigned(Result) do
|
|
begin
|
|
if OnIsDataEqual(Self, Result^.Data, AData) then
|
|
exit;
|
|
Result := Result^.Next;
|
|
end;
|
|
end;
|
|
|
|
function TCache.IndexOf(AData: Pointer): Integer;
|
|
var
|
|
Slot: PCacheSlot;
|
|
begin
|
|
ASSERT((SlotCount = 0) or Assigned(OnIsDataEqual));
|
|
Slot := MRUSlot;
|
|
while Assigned(Slot) do
|
|
begin
|
|
if OnIsDataEqual(Self, Slot^.Data, AData) then
|
|
begin
|
|
Result := Slot^.Index;
|
|
exit;
|
|
end;
|
|
Slot := Slot^.Next;
|
|
end;
|
|
Slot := -1;
|
|
end;
|
|
|
|
procedure TCache.Remove(AData: Pointer);
|
|
var
|
|
Slot: PCacheSlot;
|
|
begin
|
|
Slot := FindSlot(AData);
|
|
if Assigned(Slot) then
|
|
Slot^.Data := nil;
|
|
end;
|
|
|
|
|
|
end.
|
|
|
|
|
|
{
|
|
$Log$
|
|
Revision 1.3 2002-09-07 15:15:24 peter
|
|
* old logs removed and tabs fixed
|
|
|
|
}
|