cachecls.pp 8.4 KB

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