cachecls.pp 8.5 KB

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