123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- {
- $Id$
- Generic cache class for FCL
- Copyright (C) 2000 by Sebastian Guenther ([email protected])
- 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.2 2000-07-13 11:32:58 michael
- + removed logs
-
- }
|