cachecls.pp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. {
  2. Generic cache class for FCL
  3. Copyright (C) 2000 by Sebastian Guenther ([email protected])
  4. See the file COPYING.FPC, included in this distribution,
  5. for details about the copyright.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. }
  10. {$mode objfpc}
  11. {$H+}
  12. {$IFNDEF FPC_DOTTEDUNITS}
  13. unit CacheCls;
  14. {$ENDIF FPC_DOTTEDUNITS}
  15. interface
  16. {$IFDEF FPC_DOTTEDUNITS}
  17. uses System.SysUtils;
  18. {$ELSE FPC_DOTTEDUNITS}
  19. uses SysUtils;
  20. {$ENDIF FPC_DOTTEDUNITS}
  21. resourcestring
  22. SInvalidIndex = 'Invalid index %i';
  23. type
  24. { TCache }
  25. TCache = class;
  26. ECacheError = class(Exception);
  27. { All slots are contained both in an array and in a double-linked list.
  28. * The array, which doesn't need any additional memory, can be used for fast
  29. sequential access; its indices can be exported to the user of the cache.
  30. * The linked list is used for on-the-fly reordering of the elements, so
  31. that the elements are MRU-sorted: The most recently used element is the
  32. head, the last recently used element is the tail of the list. We need a
  33. double-linked list: When the MRU value of an element changes, we will
  34. have to walk the list reversed, but when we are searching for an entry,
  35. we will search starting from the head. }
  36. PCacheSlot = ^TCacheSlot;
  37. TCacheSlot = record
  38. Prev, Next: PCacheSlot; // -> double-linked list
  39. Data: Pointer; // The custom data associated with this element
  40. Index: Integer; // The array index of this slot
  41. end;
  42. PCacheSlotArray = ^TCacheSlotArray;
  43. TCacheSlotArray = array[0..MaxInt div SizeOf(TCacheSlot) - 1] of TCacheSlot;
  44. TOnIsDataEqual = function(ACache: TCache;
  45. AData1, AData2: Pointer): Boolean of object;
  46. TOnFreeSlot = procedure(ACache: TCache; SlotIndex: Integer) of object;
  47. { TCache implements a generic cache class.
  48. If you use the "Add" method and not only "AddNew", you will have to set
  49. the "OnIsDataEqual" property to your own compare function! }
  50. TCache = class
  51. private
  52. FOnIsDataEqual: TOnIsDataEqual;
  53. FOnFreeSlot: TOnFreeSlot;
  54. function GetData(SlotIndex: Integer): Pointer;
  55. function GetSlot(SlotIndex: Integer): PCacheSlot;
  56. procedure SetData(SlotIndex: Integer; AData: Pointer);
  57. procedure SetMRUSlot(ASlot: PCacheSlot);
  58. procedure SetSlotCount(ACount: Integer);
  59. protected
  60. FSlotCount: Integer; // Number of cache elements
  61. FSlots: PCacheSlotArray;
  62. FMRUSlot, // First slot in MRU-sorted list
  63. FLRUSlot: PCacheSlot; // Last slot in MRU-sorted list
  64. public
  65. constructor Create(ASlotCount: Integer);
  66. destructor Destroy; override;
  67. function Add(AData: Pointer): Integer; // Checks for duplicates
  68. function AddNew(AData: Pointer): Integer; // No duplicate checks
  69. function FindSlot(AData: Pointer): PCacheSlot; // nil => not found
  70. function IndexOf(AData: Pointer): Integer; // -1 => not found
  71. procedure Remove(AData: Pointer);
  72. // Accesses to the "Data" array will be reflected by the MRU list!
  73. property Data[SlotIndex: Integer]: Pointer read GetData write SetData;
  74. property MRUSlot: PCacheSlot read FMRUSlot write SetMRUSlot;
  75. property LRUSlot: PCacheSlot read FLRUSlot;
  76. property SlotCount: Integer read FSlotCount write SetSlotCount;
  77. property Slots[SlotIndex: Integer]: PCacheSlot read GetSlot;
  78. property OnIsDataEqual: TOnIsDataEqual
  79. read FOnIsDataEqual write FOnIsDataEqual;
  80. { OnFreeSlot is called when a slot is being released. This can only happen
  81. during Add or AddNew, when there is no more free slot available. }
  82. property OnFreeSlot: TOnFreeSlot read FOnFreeSlot write FOnFreeSlot;
  83. end;
  84. implementation
  85. { TCache }
  86. function TCache.GetData(SlotIndex: Integer): Pointer;
  87. begin
  88. if (SlotIndex < 0) or (SlotIndex >= SlotCount) then
  89. raise ECacheError.CreateFmt(SInvalidIndex, [SlotIndex]);
  90. MRUSlot := @FSlots^[SlotIndex];
  91. Result := MRUSlot^.Data;
  92. end;
  93. function TCache.GetSlot(SlotIndex: Integer): PCacheSlot;
  94. begin
  95. if (SlotIndex < 0) or (SlotIndex >= SlotCount) then
  96. raise ECacheError.CreateFmt(SInvalidIndex, [SlotIndex]);
  97. Result := @FSlots^[SlotIndex];
  98. end;
  99. procedure TCache.SetData(SlotIndex: Integer; AData: Pointer);
  100. begin
  101. if (SlotIndex < 0) or (SlotIndex >= FSlotCount) then
  102. raise ECacheError.CreateFmt(SInvalidIndex, [SlotIndex]);
  103. MRUSlot := @FSlots^[SlotIndex];
  104. MRUSlot^.Data := AData;
  105. end;
  106. procedure TCache.SetMRUSlot(ASlot: PCacheSlot);
  107. begin
  108. if ASlot <> FMRUSlot then
  109. begin
  110. // Unchain ASlot
  111. if Assigned(ASlot^.Prev) then
  112. ASlot^.Prev^.Next := ASlot^.Next;
  113. if Assigned(ASlot^.Next) then
  114. ASlot^.Next^.Prev := ASlot^.Prev;
  115. if ASlot = FLRUSlot then
  116. FLRUSlot := ASlot^.Prev;
  117. // Make ASlot the head of the double-linked list
  118. ASlot^.Prev := nil;
  119. ASlot^.Next := FMRUSlot;
  120. FMRUSlot^.Prev := ASlot;
  121. FMRUSlot := ASlot;
  122. if not Assigned(FMRUSlot^.Next) then
  123. FLRUSlot := FMRUSlot;
  124. end;
  125. end;
  126. procedure TCache.SetSlotCount(ACount: Integer);
  127. var
  128. Slot: PCacheSlot;
  129. i: Integer;
  130. begin
  131. if ACount <> SlotCount then
  132. begin
  133. if ACount < SlotCount then
  134. begin
  135. // Remove slots
  136. if Assigned(OnFreeSlot) then
  137. for i := ACount to SlotCount - 1 do
  138. OnFreeSlot(Self, i);
  139. while (MRUSlot^.Index >= ACount) and Assigned(MRUSlot^.Next) do
  140. FMRUSlot := MRUSlot^.Next;
  141. MRUSlot^.Prev := nil;
  142. while (LRUSlot^.Index >= ACount) and Assigned(LRUSlot^.Prev) do
  143. FLRUSlot := LRUSlot^.Prev;
  144. LRUSlot^.Next := nil;
  145. Slot := MRUSlot^.Next;
  146. while Assigned(Slot) do
  147. begin
  148. if Slot^.Index >= ACount then
  149. begin
  150. Slot^.Prev^.Next := Slot^.Next;
  151. if Assigned(Slot^.Next) then
  152. Slot^.Next^.Prev := Slot^.Prev;
  153. end;
  154. Slot := Slot^.Next;
  155. end;
  156. ReallocMem(FSlots, ACount * SizeOf(TCacheSlot));
  157. end else
  158. begin
  159. // Add new slots
  160. ReallocMem(FSlots, ACount * SizeOf(TCacheSlot));
  161. for i := SlotCount to ACount - 1 do
  162. with FSlots^[i] do
  163. begin
  164. Prev := @FSlots^[i + 1];
  165. Next := @FSlots^[i - 1];
  166. Data := nil;
  167. Index := i;
  168. end;
  169. LRUSlot^.Next := @FSlots^[ACount - 1];
  170. FSlots^[ACount - 1].Prev := LRUSlot;
  171. FLRUSlot := @FSlots^[SlotCount];
  172. FLRUSlot^.Next := nil;
  173. end;
  174. FSlotCount := ACount;
  175. end;
  176. end;
  177. constructor TCache.Create(ASlotCount: Integer);
  178. var
  179. i: Integer;
  180. begin
  181. inherited Create;
  182. FSlotCount := ASlotCount;
  183. if FSlotCount = 0 then
  184. exit;
  185. { Allocate the slots and initialize the double-linked list.
  186. Note: The list is set up so that the last recently used
  187. slot is the first slot! }
  188. GetMem(FSlots, FSlotCount * SizeOf(TCacheSlot));
  189. FMRUSlot := @FSlots^[FSlotCount - 1];
  190. FLRUSlot := @FSlots^[0];
  191. with FSlots^[0] do
  192. begin
  193. if FSlotCount > 1 then
  194. Prev := @FSlots^[1]
  195. else
  196. Prev := nil;
  197. Next := nil;
  198. Data := nil;
  199. Index := 0;
  200. end;
  201. for i := 1 to FSlotCount - 2 do
  202. with FSlots^[i] do
  203. begin
  204. Next := @FSlots^[i - 1];
  205. Prev := @FSlots^[i + 1];
  206. Data := nil;
  207. Index := i;
  208. end;
  209. with FSlots^[FSlotCount - 1] do
  210. begin
  211. Prev := nil;
  212. if FSlotCount > 1 then
  213. Next := @FSlots^[FSlotCount - 2];
  214. Data := nil;
  215. Index := FSlotCount - 1;
  216. end;
  217. end;
  218. destructor TCache.Destroy;
  219. begin
  220. FreeMem(FSlots);
  221. inherited Destroy;
  222. end;
  223. function TCache.Add(AData: Pointer): Integer;
  224. var
  225. Slot: PCacheSlot;
  226. begin
  227. Slot := FindSlot(AData);
  228. if Assigned(Slot) then
  229. begin
  230. MRUSlot := Slot;
  231. Result := Slot^.Index;
  232. end else
  233. Result := AddNew(AData);
  234. end;
  235. function TCache.AddNew(AData: Pointer): Integer;
  236. begin
  237. if Assigned(OnFreeSlot) then
  238. OnFreeSlot(Self, LRUSlot^.Index);
  239. MRUSlot := LRUSlot;
  240. MRUSlot^.Data := AData;
  241. Result := MRUSlot^.Index;
  242. end;
  243. function TCache.FindSlot(AData: Pointer): PCacheSlot;
  244. begin
  245. ASSERT((SlotCount = 0) or Assigned(OnIsDataEqual));
  246. Result := MRUSlot;
  247. while Assigned(Result) do
  248. begin
  249. if OnIsDataEqual(Self, Result^.Data, AData) then
  250. exit;
  251. Result := Result^.Next;
  252. end;
  253. end;
  254. function TCache.IndexOf(AData: Pointer): Integer;
  255. var
  256. Slot: PCacheSlot;
  257. begin
  258. ASSERT((SlotCount = 0) or Assigned(OnIsDataEqual));
  259. Slot := MRUSlot;
  260. while Assigned(Slot) do
  261. begin
  262. if OnIsDataEqual(Self, Slot^.Data, AData) then
  263. begin
  264. Result := Slot^.Index;
  265. exit;
  266. end;
  267. Slot := Slot^.Next;
  268. end;
  269. indexof := -1;
  270. end;
  271. procedure TCache.Remove(AData: Pointer);
  272. var
  273. Slot: PCacheSlot;
  274. begin
  275. Slot := FindSlot(AData);
  276. if Assigned(Slot) then
  277. Slot^.Data := nil;
  278. end;
  279. end.