lazarus/lcl/lclrescache.pas

562 lines
15 KiB
ObjectPascal

{
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
Author: Mattias Gaertner
Abstract:
Types and methods to cache interface resources.
See graphics.pp for examples.
}
unit LCLResCache;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Types, AVL_Tree,
// LazUtils
FPCAdds, LazLoggerBase, LazTracer,
// LCL
LCLType, WSReferences,
syncobjs; // This FCL unit must be in the end.
{off $DEFINE CheckResCacheConsistency}
type
TResourceCache = class;
TResourceCacheDescriptor = class;
{ TResourceCacheItem }
TResourceCacheItem = class
protected
FDestroying: boolean;
FReferenceCount: integer;
public
Handle: TLCLHandle;
Cache: TResourceCache;
FirstDescriptor, LastDescriptor: TResourceCacheDescriptor;
Next, Prev: TResourceCacheItem;
constructor Create(TheCache: TResourceCache; TheHandle: TLCLHandle);
destructor Destroy; override;
procedure IncreaseRefCount;
procedure DecreaseRefCount;
procedure AddToList(var First, Last: TResourceCacheItem);
procedure RemoveFromList(var First, Last: TResourceCacheItem);
procedure WarnReferenceHigh; virtual;
public
property ReferenceCount: integer read FReferenceCount;
end;
TResourceCacheItemClass = class of TResourceCacheItem;
{ TResourceCacheDescriptor }
TResourceCacheDescriptor = class
protected
FDestroying: boolean;
public
Item: TResourceCacheItem;
Cache: TResourceCache;
Next, Prev: TResourceCacheDescriptor;
constructor Create(TheCache: TResourceCache; TheItem: TResourceCacheItem);
destructor Destroy; override;
procedure AddToList(var First, Last: TResourceCacheDescriptor);
procedure RemoveFromList(var First, Last: TResourceCacheDescriptor);
end;
TResourceCacheDescriptorClass = class of TResourceCacheDescriptor;
{ TResourceCache }
TResourceCache = class
protected
FItems: TAvlTree;
FDescriptors: TAvlTree;
FDestroying: boolean;
FResourceCacheDescriptorClass: TResourceCacheDescriptorClass;
FResourceCacheItemClass: TResourceCacheItemClass;
FMaxUnusedItem: integer; // how many freed resources to keep
FFirstUnusedItem, FLastUnusedItem: TResourceCacheItem;
FUnUsedItemCount: integer;
FLock: TCriticalSection;
procedure RemoveItem(Item: TResourceCacheItem); virtual;
procedure RemoveDescriptor(Desc: TResourceCacheDescriptor); virtual;
procedure ItemUsed(Item: TResourceCacheItem);
procedure ItemUnused(Item: TResourceCacheItem);
function ItemIsUsed(Item: TResourceCacheItem): boolean;
public
constructor Create;
procedure Clear;
destructor Destroy; override;
function CompareItems(Tree: TAvlTree; Item1, Item2: Pointer): integer; virtual;
function CompareDescriptors(Tree: TAvlTree; Desc1, Desc2: Pointer): integer; virtual; abstract;
procedure ConsistencyCheck;
procedure Lock;
procedure Unlock;
public
property MaxUnusedItem: integer read FMaxUnusedItem
write FMaxUnusedItem;
property ResourceCacheItemClass: TResourceCacheItemClass
read FResourceCacheItemClass;
property ResourceCacheDescriptorClass: TResourceCacheDescriptorClass
read FResourceCacheDescriptorClass;
end;
{ THandleResourceCache }
THandleResourceCache = class(TResourceCache)
public
function FindItem(Handle: TLCLHandle): TResourceCacheItem;
end;
{ TBlockResourceCacheDescriptor }
TBlockResourceCacheDescriptor = class(TResourceCacheDescriptor)
public
Data: Pointer;
destructor Destroy; override;
end;
{ TBlockResourceCache }
TBlockResourceCache = class(THandleResourceCache)
private
FDataSize: integer;
protected
FOnCompareDescPtrWithDescriptor: TListSortCompare;
public
constructor Create(TheDataSize: integer);
function FindDescriptor(DescPtr: Pointer): TBlockResourceCacheDescriptor;
function AddResource(Handle: TLCLHandle; DescPtr: Pointer): TBlockResourceCacheDescriptor;
function CompareDescriptors(Tree: TAvlTree; Desc1, Desc2: Pointer): integer; override;
public
property DataSize: integer read FDataSize;
property OnCompareDescPtrWithDescriptor: TListSortCompare
read FOnCompareDescPtrWithDescriptor;
end;
function ComparePHandleWithResourceCacheItem(HandlePtr: PLCLHandle;
Item: TResourceCacheItem): integer;
function CompareDescPtrWithBlockResDesc(DescPtr: Pointer;
Item: TBlockResourceCacheDescriptor): integer;
implementation
function CompareLCLHandles(h1, h2: TLCLHandle): integer;
begin
if h1>h2 then
Result:=1
else if h1<h2 then
Result:=-1
else
Result:=0;
end;
function ComparePHandleWithResourceCacheItem(HandlePtr: PLCLHandle;
Item: TResourceCacheItem): integer;
begin
Result := CompareLCLHandles(HandlePtr^, Item.Handle);
end;
function CompareDescPtrWithBlockResDesc(DescPtr: Pointer;
Item: TBlockResourceCacheDescriptor): integer;
begin
Result := CompareMemRange(DescPtr, Item.Data,
TBlockResourceCache(Item.Cache).DataSize);
end;
{ TResourceCacheItem }
constructor TResourceCacheItem.Create(TheCache: TResourceCache; TheHandle: TLCLHandle);
begin
Cache := TheCache;
Handle := TheHandle;
end;
destructor TResourceCacheItem.Destroy;
begin
if FDestroying then
RaiseGDBException('');
FDestroying := True;
Cache.RemoveItem(Self);
//debugln('TResourceCacheItem.Destroy B ',dbgs(Self));
Handle := 0;
inherited Destroy;
//debugln('TResourceCacheItem.Destroy END ',dbgs(Self));
end;
procedure TResourceCacheItem.IncreaseRefCount;
begin
inc(FReferenceCount);
if FReferenceCount = 1 then
Cache.ItemUsed(Self);
{$IFDEF VerboseResCache}
if FReferenceCount = 10 then
begin
WarnReferenceHigh;
DumpStack;
end;
{$ENDIF}
if (FReferenceCount = 1000) or (FReferenceCount = 10000) then
WarnReferenceHigh;
end;
procedure TResourceCacheItem.DecreaseRefCount;
procedure RaiseRefCountZero;
begin
RaiseGDBException('TResourceCacheItem.DecreaseRefCount=0 '+ClassName);
end;
begin
//debugln('TResourceCacheItem.DecreaseRefCount ',ClassName,' ',dbgs(Self),' ',dbgs(FReferenceCount));
if FReferenceCount = 0 then
RaiseRefCountZero;
dec(FReferenceCount);
if FReferenceCount = 0 then
Cache.ItemUnused(Self);
//debugln('TResourceCacheItem.DecreaseRefCount END ');
end;
procedure TResourceCacheItem.AddToList(var First, Last: TResourceCacheItem);
// add as last
begin
Next := nil;
Prev := Last;
Last := Self;
if First = nil then First := Self;
if Prev <> nil then Prev.Next := Self;
end;
procedure TResourceCacheItem.RemoveFromList(var First,Last: TResourceCacheItem);
begin
if First = Self then First := Next;
if Last = Self then Last := Prev;
if Next <> nil then Next.Prev := Prev;
if Prev <> nil then Prev.Next := Next;
Next := nil;
Prev := nil;
end;
procedure TResourceCacheItem.WarnReferenceHigh;
begin
{$IFNDEF DisableChecks}
debugln('WARNING: TResourceCacheItem.IncreaseRefCount ',dbgs(FReferenceCount),' ',Cache.ClassName);
{$ENDIF}
end;
{ TResourceCacheDescriptor }
constructor TResourceCacheDescriptor.Create(TheCache: TResourceCache;
TheItem: TResourceCacheItem);
begin
Cache := TheCache;
Item := TheItem;
Item.IncreaseRefCount;
AddToList(Item.FirstDescriptor, Item.LastDescriptor);
end;
destructor TResourceCacheDescriptor.Destroy;
begin
if FDestroying then
RaiseGDBException('');
FDestroying := True;
Cache.RemoveDescriptor(Self);
inherited Destroy;
end;
procedure TResourceCacheDescriptor.AddToList(var First, Last: TResourceCacheDescriptor);
// add as last
begin
Next := nil;
Prev := Last;
Last := Self;
if First = nil then First := Self;
if Prev <> nil then Prev.Next := Self;
end;
procedure TResourceCacheDescriptor.RemoveFromList(var First, Last: TResourceCacheDescriptor);
begin
if First = Self then First := Next;
if Last = Self then Last := Prev;
if Next <> nil then Next.Prev := Prev;
if Prev <> nil then Prev.Next := Next;
Next := nil;
Prev := nil;
end;
{ TResourceCache }
procedure TResourceCache.RemoveItem(Item: TResourceCacheItem);
begin
if not FDestroying then
begin
while Item.FirstDescriptor <> nil do
begin
if Item.FirstDescriptor.FDestroying then
RaiseGDBException('TResourceCache.RemoveItem');
Item.FirstDescriptor.Free;
end;
FItems.Remove(Item);
end;
end;
procedure TResourceCache.RemoveDescriptor(Desc: TResourceCacheDescriptor);
var
Item: TResourceCacheItem;
begin
if not FDestroying then
begin
Item := Desc.Item;
if Item <> nil then
Desc.RemoveFromList(Item.FirstDescriptor, Item.LastDescriptor);
FDescriptors.Remove(Desc);
if (Item <> nil) and (Item.FirstDescriptor = nil) and (not Item.FDestroying) then
Item.Free;
end;
end;
procedure TResourceCache.ItemUsed(Item: TResourceCacheItem);
// called after creation or when Item is used again
begin
if not ItemIsUsed(Item) then
begin
// remove from unused list
Item.RemoveFromList(FFirstUnusedItem, FLastUnusedItem);
dec(FUnUsedItemCount);
end;
end;
procedure TResourceCache.ItemUnused(Item: TResourceCacheItem);
// called when Item is not used any more
var
DeleteItem: TResourceCacheItem;
begin
{$IFDEF CheckResCacheConsistency}
ConsistencyCheck;
{$ENDIF}
//debugln('TResourceCache.ItemUnused A ',ClassName,' ',dbgs(Self));
if not ItemIsUsed(Item) then
raise Exception.Create('TResourceCache.ItemUnused');
//debugln('TResourceCache.ItemUnused B ',ClassName,' ',dbgs(Self));
Item.AddToList(FFirstUnusedItem, FLastUnusedItem);
inc(FUnUsedItemCount);
//debugln('TResourceCache.ItemUnused C ',ClassName,' ',dbgs(Self));
if FUnUsedItemCount > FMaxUnusedItem then
begin
// maximum unused resources reached -> free the oldest
DeleteItem := FFirstUnusedItem;
DeleteItem.RemoveFromList(FFirstUnusedItem, FLastUnusedItem);
DeleteItem.Free;
end;
//debugln('TResourceCache.ItemUnused END ',ClassName,' ',dbgs(Self));
end;
function TResourceCache.ItemIsUsed(Item: TResourceCacheItem): boolean;
begin
Result := (FFirstUnusedItem <> Item) and (Item.Next = nil) and (Item.Prev = nil)
end;
constructor TResourceCache.Create;
begin
FMaxUnusedItem := 100;
FItems := TAvlTree.CreateObjectCompare(@CompareItems);
FDescriptors := TAvlTree.CreateObjectCompare(@CompareDescriptors);
FResourceCacheItemClass := TResourceCacheItem;
FResourceCacheDescriptorClass := TResourceCacheDescriptor;
FLock := TCriticalSection.Create;
end;
procedure TResourceCache.Clear;
begin
while FFirstUnusedItem <> nil do
FFirstUnusedItem.RemoveFromList(FFirstUnusedItem, FLastUnusedItem);
FItems.FreeAndClear;
FDescriptors.FreeAndClear;
end;
destructor TResourceCache.Destroy;
begin
FDestroying := True;
Clear;
FItems.Free;
FItems := nil;
FDescriptors.Free;
FDescriptors := nil;
FLock.Free;
inherited Destroy;
end;
function TResourceCache.CompareItems(Tree: TAvlTree; Item1, Item2: Pointer): integer;
begin
Result := CompareLCLHandles(TResourceCacheItem(Item1).Handle,
TResourceCacheItem(Item2).Handle);
end;
procedure TResourceCache.ConsistencyCheck;
var
ANode: TAvlTreeNode;
Item: TResourceCacheItem;
Desc: TResourceCacheDescriptor;
Desc2: TResourceCacheDescriptor;
begin
if (FFirstUnusedItem=nil) xor (FLastUnusedItem=nil) then
RaiseGDBException('');
// check items
FItems.ConsistencyCheck;
ANode := FItems.FindLowest;
while ANode <> nil do
begin
Item := TResourceCacheItem(ANode.Data);
if Item.FirstDescriptor = nil then
RaiseGDBException('');
if Item.LastDescriptor = nil then
RaiseGDBException('');
if Item.FirstDescriptor.Prev <> nil then
RaiseGDBException('');
if Item.LastDescriptor.Next <> nil then
RaiseGDBException('');
Desc := Item.FirstDescriptor;
while Desc <> nil do
begin
if Desc.Item <> Item then
RaiseGDBException('');
if (Desc.Next <> nil) and (Desc.Next.Prev <> Desc) then
RaiseGDBException('');
if (Desc.Prev <> nil) and (Desc.Prev.Next <> Desc) then
RaiseGDBException('');
if (Desc.Next = nil) and (Item.LastDescriptor <> Desc) then
RaiseGDBException('');
Desc := Desc.Next;
end;
ANode := FItems.FindSuccessor(ANode);
end;
// check Descriptors
FDescriptors.ConsistencyCheck;
ANode := FDescriptors.FindLowest;
while ANode <> nil do
begin
Desc := TResourceCacheDescriptor(ANode.Data);
Item := Desc.Item;
if Item = nil then
RaiseGDBException('');
Desc2 := Item.FirstDescriptor;
while (Desc2 <> nil) and (Desc2 <> Desc) do
Desc2 := Desc2.Next;
if Desc <> Desc2 then
RaiseGDBException('');
ANode := FItems.FindSuccessor(ANode);
end;
end;
procedure TResourceCache.Lock;
begin
FLock.Enter;
end;
procedure TResourceCache.Unlock;
begin
FLock.Leave;
end;
{ THandleResourceCache }
function THandleResourceCache.FindItem(Handle: TLCLHandle): TResourceCacheItem;
var
ANode: TAvlTreeNode;
begin
ANode := FItems.FindKey(@Handle,
TListSortCompare(@ComparePHandleWithResourceCacheItem));
if ANode <> nil then
Result := TResourceCacheItem(ANode.Data)
else
Result := nil;
end;
{ TBlockResourceCache }
constructor TBlockResourceCache.Create(TheDataSize: integer);
begin
inherited Create;
FDataSize := TheDataSize;
FResourceCacheDescriptorClass := TBlockResourceCacheDescriptor;
FOnCompareDescPtrWithDescriptor := TListSortCompare(@CompareDescPtrWithBlockResDesc);
end;
function TBlockResourceCache.FindDescriptor(DescPtr: Pointer): TBlockResourceCacheDescriptor;
var
ANode: TAvlTreeNode;
begin
ANode := FDescriptors.FindKey(DescPtr,FOnCompareDescPtrWithDescriptor);
if ANode <> nil then
Result := TBlockResourceCacheDescriptor(ANode.Data)
else
Result := nil;
end;
function TBlockResourceCache.AddResource(Handle: TLCLHandle; DescPtr: Pointer): TBlockResourceCacheDescriptor;
var
Item: TResourceCacheItem;
procedure RaiseDescriptorAlreadyAdded;
var
Msg: String;
i: Integer;
begin
Msg:='TBlockResourceCache.AddResource Descriptor Already Added '+LineEnding;
for i:=0 to DataSize-1 do
Msg:=Msg+HexStr(ord(PChar(DescPtr)[i]),2);
raise Exception.Create(Msg);
end;
begin
{$IFDEF CheckResCacheConsistency}
ConsistencyCheck;
{$ENDIF}
Result := FindDescriptor(DescPtr);
if Result <> nil then
RaiseDescriptorAlreadyAdded;
Item := FindItem(Handle);
if Item = nil then
begin
Item := FResourceCacheItemClass.Create(Self, Handle);
FItems.Add(Item);
end;
Result := TBlockResourceCacheDescriptor(FResourceCacheDescriptorClass.Create(Self, Item));
ReAllocMem(Result.Data, DataSize);
System.Move(DescPtr^, Result.Data^, DataSize);
FDescriptors.Add(Result);
end;
function TBlockResourceCache.CompareDescriptors(Tree: TAvlTree; Desc1, Desc2: Pointer): integer;
begin
Result := CompareMemRange(TBlockResourceCacheDescriptor(Desc1).Data,
TBlockResourceCacheDescriptor(Desc2).Data,
DataSize);
end;
{ TBlockResourceCacheDescriptor }
destructor TBlockResourceCacheDescriptor.Destroy;
begin
inherited Destroy;
ReAllocMem(Data, 0);
end;
end.