cachecls.pp 8.5 KB

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