mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-11-04 16:34:24 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			472 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			472 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
{
 | 
						|
 *****************************************************************************
 | 
						|
  This file is part of LazUtils.
 | 
						|
 | 
						|
  See the file COPYING.modifiedLGPL.txt, included in this distribution,
 | 
						|
  for details about the license.
 | 
						|
 *****************************************************************************
 | 
						|
 
 | 
						|
  Abstract:
 | 
						|
    A dynamic data queue to push and pop arbitrary data.
 | 
						|
}
 | 
						|
unit DynQueue;
 | 
						|
 | 
						|
{$mode objfpc}{$H+}
 | 
						|
 | 
						|
interface
 | 
						|
 | 
						|
uses
 | 
						|
  Classes, SysUtils,
 | 
						|
  // LazUtils
 | 
						|
  LazLoggerBase;
 | 
						|
  
 | 
						|
type
 | 
						|
  TDynamicQueueItem = record
 | 
						|
    Size: integer;
 | 
						|
    Data: array[0..0] of integer;// type is irrelevant, the record is open ended
 | 
						|
  end;
 | 
						|
  PDynamicQueueItem = ^TDynamicQueueItem;
 | 
						|
  ListOfPDynamicQueueItem = ^PDynamicQueueItem;
 | 
						|
 | 
						|
  { TDynamicDataQueue
 | 
						|
    A queue for arbitrary data. That means first in first out.
 | 
						|
 | 
						|
    Push: put data in the queue
 | 
						|
    Pop:  fetch data from the queue (data is removed from queue)
 | 
						|
    Top:  read data in the queue (data remains in the queue)
 | 
						|
 | 
						|
    This queue maintains internally a ring queue for pointers to data chunks of
 | 
						|
    TDynamicQueueItem. It is optimised to reduce the amount of data movement. }
 | 
						|
 | 
						|
  TDynamicDataQueue =  class
 | 
						|
  private
 | 
						|
    FItems: ListOfPDynamicQueueItem; // ring queue from FTopIndex to FLastIndex
 | 
						|
    FItemCapacity: integer; // length of ListOfPDynamicQueueItem
 | 
						|
    FTopIndex: integer; // first item in FItems
 | 
						|
    FLastIndex: integer; // last item in FItems
 | 
						|
    FMaximumBlockSize: integer;
 | 
						|
    FMinimumBlockSize: integer;
 | 
						|
    FSize: int64;
 | 
						|
    FTopItemSpace: integer; // space in top item
 | 
						|
    FLastItemSpace: integer; // remaining space in last item
 | 
						|
    procedure SetMaximumBlockSize(const AValue: integer);
 | 
						|
    procedure SetMinimumBlockSize(const AValue: integer);
 | 
						|
    procedure GrowItems;
 | 
						|
    procedure AddItem(ItemSize: integer);
 | 
						|
    function CalculateItemSize(ItemSize: integer): integer;
 | 
						|
    function PushInternal(Source: PByte; AStream: TStream; Count: integer): integer;// add to end of queue
 | 
						|
    function PopTopInternal(Dest: PByte; AStream: TStream; Count: integer; KeepData: Boolean): integer;// read from start of queue, remove from queue
 | 
						|
  public
 | 
						|
    constructor Create;
 | 
						|
    destructor Destroy; override;
 | 
						|
    procedure Clear;
 | 
						|
    procedure ConsistencyCheck;
 | 
						|
    procedure WriteDebugReport(WriteData: Boolean);
 | 
						|
    function Push(const Buffer; Count: integer): integer;// add to end of queue
 | 
						|
    function Push(AStream: TStream; Count: integer): integer;// add to end of queue
 | 
						|
    function Pop(var Buffer; Count: integer): integer; // read from start of queue, remove from queue
 | 
						|
    function Pop(AStream: TStream; Count: integer): integer;// read from start of queue, remove from queue
 | 
						|
    function Top(var Buffer; Count: integer): integer; // read from start of queue, keep data
 | 
						|
    function Top(AStream: TStream; Count: integer): integer;// read from start of queue, keep data
 | 
						|
    property Size: int64 read FSize;
 | 
						|
    property MinimumBlockSize: integer read FMinimumBlockSize write SetMinimumBlockSize;
 | 
						|
    property MaximumBlockSize: integer read FMaximumBlockSize write SetMaximumBlockSize;
 | 
						|
  end;
 | 
						|
 | 
						|
implementation
 | 
						|
 | 
						|
{ TDynamicDataQueue }
 | 
						|
 | 
						|
procedure TDynamicDataQueue.SetMinimumBlockSize(const AValue: integer);
 | 
						|
begin
 | 
						|
  if (FMinimumBlockSize=AValue) then exit;
 | 
						|
  FMinimumBlockSize:=AValue;
 | 
						|
  if FMinimumBlockSize<16 then FMinimumBlockSize:=16;
 | 
						|
  if FMaximumBlockSize<FMinimumBlockSize then
 | 
						|
    FMaximumBlockSize:=FMinimumBlockSize;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TDynamicDataQueue.GrowItems;
 | 
						|
var
 | 
						|
  NewCapacity: LongInt;
 | 
						|
  NewSize: Integer;
 | 
						|
  NewItems: ListOfPDynamicQueueItem;
 | 
						|
  DestIndex: Integer;
 | 
						|
  SrcIndex: LongInt;
 | 
						|
begin
 | 
						|
  // allocate a new ring queue
 | 
						|
  NewCapacity:=FItemCapacity;
 | 
						|
  if NewCapacity<8 then
 | 
						|
    NewCapacity:=8
 | 
						|
  else
 | 
						|
    NewCapacity:=NewCapacity*2;
 | 
						|
  NewSize:=NewCapacity*SizeOf(Pointer);
 | 
						|
  GetMem(NewItems,NewSize);
 | 
						|
  FillChar(NewItems^,NewSize,0);
 | 
						|
 | 
						|
  // copy old items
 | 
						|
  DestIndex:=0;
 | 
						|
  if FItems<>nil then begin
 | 
						|
    SrcIndex:=FTopIndex;
 | 
						|
    repeat
 | 
						|
      NewItems[DestIndex]:=FItems[SrcIndex];
 | 
						|
      if SrcIndex=FLastIndex then break;
 | 
						|
      inc(DestIndex);
 | 
						|
      inc(SrcIndex);
 | 
						|
      if SrcIndex=FItemCapacity then
 | 
						|
        SrcIndex:=0;
 | 
						|
    until false;
 | 
						|
    FreeMem(FItems);
 | 
						|
  end;
 | 
						|
  FTopIndex:=0;
 | 
						|
  FLastIndex:=DestIndex;
 | 
						|
  FItems:=NewItems;
 | 
						|
  FItemCapacity:=NewCapacity;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TDynamicDataQueue.AddItem(ItemSize: integer);
 | 
						|
var
 | 
						|
  NewIndex: Integer;
 | 
						|
 | 
						|
  procedure RaiseInconsistency;
 | 
						|
  begin
 | 
						|
    raise Exception.Create('TDynamicDataQueue.AddItem NewIndex='+IntToStr(NewIndex));
 | 
						|
  end;
 | 
						|
 | 
						|
begin
 | 
						|
  // check that there is space for the new item
 | 
						|
  NewIndex:=FLastIndex;
 | 
						|
  if (FItems<>nil) and (FItems[NewIndex]<>nil) then begin
 | 
						|
    inc(NewIndex);
 | 
						|
    if NewIndex>=FItemCapacity then
 | 
						|
      NewIndex:=0;
 | 
						|
  end;
 | 
						|
  if NewIndex=FTopIndex then begin
 | 
						|
    GrowItems;
 | 
						|
    NewIndex:=FLastIndex;
 | 
						|
    if FItems[NewIndex]<>nil then begin
 | 
						|
      inc(NewIndex);
 | 
						|
      if NewIndex>=FItemCapacity then
 | 
						|
        NewIndex:=0;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
  if (FItems=nil) then RaiseInconsistency;
 | 
						|
  if (FItems[NewIndex]<>nil) then RaiseInconsistency;
 | 
						|
  
 | 
						|
  FLastIndex:=NewIndex;
 | 
						|
  GetMem(FItems[FLastIndex],SizeOf(TDynamicQueueItem.Size)+ItemSize);
 | 
						|
  FItems[FLastIndex]^.Size:=ItemSize;
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.CalculateItemSize(ItemSize: integer): integer;
 | 
						|
begin
 | 
						|
  Result:=ItemSize;
 | 
						|
  if Result<MinimumBlockSize then
 | 
						|
    Result:=MinimumBlockSize;
 | 
						|
  if Result>MaximumBlockSize then
 | 
						|
    Result:=MaximumBlockSize;
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.PushInternal(Source: PByte; AStream: TStream;
 | 
						|
  Count: integer): integer;
 | 
						|
var
 | 
						|
  CurCount: PtrInt;
 | 
						|
  NewItemSize: LongInt;
 | 
						|
  LastItem: PDynamicQueueItem;
 | 
						|
  Dest: Pointer;
 | 
						|
begin
 | 
						|
  Result:=0;
 | 
						|
  if Count<=0 then exit;
 | 
						|
  while true do begin
 | 
						|
    while FLastItemSpace>0 do begin
 | 
						|
      // fill the last item
 | 
						|
      CurCount:=Count;
 | 
						|
      if CurCount>FLastItemSpace then
 | 
						|
        CurCount:=FLastItemSpace;
 | 
						|
      LastItem:=FItems[FLastIndex];
 | 
						|
      Dest:=Pointer(@(LastItem^.Data))+LastItem^.Size-FLastItemSpace;
 | 
						|
 | 
						|
      // beware: read from a stream can raise an exception
 | 
						|
      if Source<>nil then
 | 
						|
        System.Move(Source[Result],Dest^,CurCount)
 | 
						|
      else
 | 
						|
        CurCount:=AStream.Read(Dest^,CurCount);
 | 
						|
      if CurCount<=0 then exit;
 | 
						|
 | 
						|
      // transfer succeeded
 | 
						|
      dec(FLastItemSpace,CurCount); // space decreased
 | 
						|
      inc(fSize,CurCount);   // Queue increased
 | 
						|
      inc(Result,CurCount);  // bytes transferred
 | 
						|
      dec(Count,CurCount);   // less to transfer
 | 
						|
      if Count=0 then exit;
 | 
						|
    end;
 | 
						|
    // add new
 | 
						|
    NewItemSize:=CalculateItemSize(Count);
 | 
						|
    AddItem(NewItemSize);
 | 
						|
    FLastItemSpace:=NewItemSize;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.PopTopInternal(Dest: PByte; AStream: TStream;
 | 
						|
  Count: integer; KeepData: Boolean): integer;
 | 
						|
  
 | 
						|
  procedure RaiseInconsistencySizeNot0;
 | 
						|
  begin
 | 
						|
    raise Exception.Create('TDynamicDataQueue.PopTopInternal inconsistency size<>0');
 | 
						|
  end;
 | 
						|
  
 | 
						|
  procedure RaiseInconsistencyEmptyItem;
 | 
						|
  begin
 | 
						|
    raise Exception.Create('TDynamicDataQueue.PopTopInternal inconsistency empty item');
 | 
						|
  end;
 | 
						|
  
 | 
						|
  procedure RaiseInconsistencySizeNegative;
 | 
						|
  begin
 | 
						|
    raise Exception.Create('TDynamicDataQueue.PopTopInternal inconsistency size<0');
 | 
						|
  end;
 | 
						|
  
 | 
						|
var
 | 
						|
  Item: PDynamicQueueItem;
 | 
						|
  CurCount: Integer;
 | 
						|
  Source: PByte;
 | 
						|
  CurItemSize: LongInt;
 | 
						|
  ReadIndex: LongInt;
 | 
						|
  TransferredCount: LongInt;
 | 
						|
begin
 | 
						|
  Result:=0;
 | 
						|
  if Count<=0 then exit;
 | 
						|
  ReadIndex:=FTopIndex;
 | 
						|
 | 
						|
  while Count>0 do begin
 | 
						|
    if FItems=nil then exit; // no data
 | 
						|
    
 | 
						|
    Item:=FItems[ReadIndex];
 | 
						|
    CurItemSize:=Item^.Size;
 | 
						|
    if ReadIndex=FLastIndex then
 | 
						|
      dec(CurItemSize,FLastItemSpace);
 | 
						|
    CurCount:=CurItemSize;
 | 
						|
    if ReadIndex=FTopIndex then
 | 
						|
      dec(CurCount,FTopItemSpace);
 | 
						|
    if CurCount<=0 then
 | 
						|
      RaiseInconsistencyEmptyItem;
 | 
						|
  
 | 
						|
    // copy data from the TopItem
 | 
						|
    if CurCount>Count then
 | 
						|
      CurCount:=Count;
 | 
						|
    Source:=PByte(@Item^.Data);
 | 
						|
    if ReadIndex=FTopIndex then
 | 
						|
      inc(Source,FTopItemSpace);
 | 
						|
 | 
						|
    // beware: writing to a stream can raise an exception
 | 
						|
    if Dest<>nil then begin
 | 
						|
      System.Move(Source^,Dest[Result],CurCount);
 | 
						|
      TransferredCount:=CurCount;
 | 
						|
    end else
 | 
						|
      TransferredCount:=AStream.Write(Dest^,CurCount);
 | 
						|
    if TransferredCount<=0 then
 | 
						|
      exit;
 | 
						|
      
 | 
						|
    // transfer succeeded (at least partially)
 | 
						|
    inc(Result,TransferredCount); // bytes transferred
 | 
						|
    dec(Count,TransferredCount);  // less to transfer
 | 
						|
    if (not KeepData) then begin
 | 
						|
      dec(FSize,TransferredCount);  // Queue decreased
 | 
						|
      if FSize<0 then RaiseInconsistencySizeNegative;
 | 
						|
      
 | 
						|
      if (ReadIndex=FTopIndex) then begin
 | 
						|
        inc(FTopItemSpace,TransferredCount); // space in top item increased
 | 
						|
 | 
						|
        if (FTopItemSpace=CurItemSize) then begin
 | 
						|
          // item complete -> remove item
 | 
						|
          FreeMem(Item);
 | 
						|
          FItems[FTopIndex]:=nil;
 | 
						|
          if FTopIndex=FLastIndex then begin
 | 
						|
            // complete queue read
 | 
						|
            if Size<>0 then RaiseInconsistencySizeNot0;
 | 
						|
            Clear;
 | 
						|
            exit;
 | 
						|
          end;
 | 
						|
 | 
						|
          FTopItemSpace:=0;
 | 
						|
          inc(FTopIndex);
 | 
						|
          if FTopIndex=FItemCapacity then FTopIndex:=0;
 | 
						|
        end;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
    if (Count=0) or (TransferredCount<CurCount) then exit;
 | 
						|
 | 
						|
    if TransferredCount=CurCount then begin
 | 
						|
      // next item
 | 
						|
      inc(ReadIndex);
 | 
						|
      if ReadIndex=FItemCapacity then ReadIndex:=0;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TDynamicDataQueue.SetMaximumBlockSize(const AValue: integer);
 | 
						|
begin
 | 
						|
  if FMaximumBlockSize=AValue then exit;
 | 
						|
  FMaximumBlockSize:=AValue;
 | 
						|
  if FMaximumBlockSize<FMinimumBlockSize then
 | 
						|
    FMaximumBlockSize:=FMinimumBlockSize;
 | 
						|
end;
 | 
						|
 | 
						|
constructor TDynamicDataQueue.Create;
 | 
						|
begin
 | 
						|
  FMinimumBlockSize:=512;
 | 
						|
  FMaximumBlockSize:=4096;
 | 
						|
end;
 | 
						|
 | 
						|
destructor TDynamicDataQueue.Destroy;
 | 
						|
begin
 | 
						|
  Clear;
 | 
						|
  inherited Destroy;
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.Push(const Buffer; Count: integer): integer;
 | 
						|
begin
 | 
						|
  Result:=PushInternal(PByte(@Buffer),nil,Count);
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.Push(AStream: TStream; Count: integer): integer;
 | 
						|
begin
 | 
						|
  Result:=PushInternal(nil,AStream,Count);
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.Pop(var Buffer; Count: integer): integer;
 | 
						|
begin
 | 
						|
  Result:=PopTopInternal(PByte(@Buffer),nil,Count,false);
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.Pop(AStream: TStream; Count: integer): integer;
 | 
						|
begin
 | 
						|
  Result:=PopTopInternal(nil,AStream,Count,false);
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.Top(var Buffer; Count: integer): integer;
 | 
						|
begin
 | 
						|
  Result:=PopTopInternal(PByte(@Buffer),nil,Count,true);
 | 
						|
end;
 | 
						|
 | 
						|
function TDynamicDataQueue.Top(AStream: TStream; Count: integer): integer;
 | 
						|
begin
 | 
						|
  Result:=PopTopInternal(nil,AStream,Count,true);
 | 
						|
end;
 | 
						|
 | 
						|
procedure TDynamicDataQueue.Clear;
 | 
						|
begin
 | 
						|
  while FTopIndex<>FLastIndex do begin
 | 
						|
    FreeMem(FItems[FTopIndex]);
 | 
						|
    inc(FTopIndex);
 | 
						|
    if FTopIndex=FItemCapacity then
 | 
						|
      FTopIndex:=0;
 | 
						|
  end;
 | 
						|
  FTopIndex:=0;
 | 
						|
  FLastIndex:=0;
 | 
						|
  FSize:=0;
 | 
						|
  FreeMem(FItems);
 | 
						|
  FItems:=nil;
 | 
						|
  FItemCapacity:=0;
 | 
						|
  FTopItemSpace:=0;
 | 
						|
  FLastItemSpace:=0;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TDynamicDataQueue.ConsistencyCheck;
 | 
						|
 | 
						|
  procedure Error(const Msg: string);
 | 
						|
  begin
 | 
						|
    raise Exception.Create('TDynamicDataQueue.ConsistencyCheck '+Msg);
 | 
						|
  end;
 | 
						|
 | 
						|
var
 | 
						|
  i: LongInt;
 | 
						|
  RealSize: int64;
 | 
						|
  CurSize: LongInt;
 | 
						|
begin
 | 
						|
  if Size<0 then Error('');
 | 
						|
  if FMinimumBlockSize>FMaximumBlockSize then Error('');
 | 
						|
  if FMinimumBlockSize<16 then Error('');
 | 
						|
  if (FItems=nil) then begin
 | 
						|
    if Size<>0 then Error('');
 | 
						|
  end else begin
 | 
						|
    if FItemCapacity<=0 then Error('');
 | 
						|
    if Size=0 then Error('');
 | 
						|
    if FTopIndex<0 then Error('');
 | 
						|
    if FLastIndex<0 then Error('');
 | 
						|
    if FTopIndex>=FItemCapacity then Error('');
 | 
						|
    if FLastIndex>=FItemCapacity then Error('');
 | 
						|
    
 | 
						|
    // check used items
 | 
						|
    RealSize:=0;
 | 
						|
    i:=FTopIndex;
 | 
						|
    repeat
 | 
						|
      if FItems[i]=nil then Error('');
 | 
						|
      if FItems[i]^.Size<=0 then Error('');
 | 
						|
      CurSize:=FItems[i]^.Size;
 | 
						|
      if FTopIndex=i then
 | 
						|
        dec(CurSize,FTopItemSpace);
 | 
						|
      if FLastIndex=i then
 | 
						|
        dec(CurSize,FLastItemSpace);
 | 
						|
      inc(RealSize,CurSize);
 | 
						|
      if i=FLastIndex then break;
 | 
						|
      inc(i);
 | 
						|
      if i=FItemCapacity then i:=0;
 | 
						|
    until false;
 | 
						|
    if RealSize<>Size then Error('');
 | 
						|
    
 | 
						|
    // check unused items
 | 
						|
    inc(i);
 | 
						|
    if i=FItemCapacity then i:=0;
 | 
						|
    while (i<>FTopIndex) do begin
 | 
						|
      if FItems[i]<>nil then Error('');
 | 
						|
      inc(i);
 | 
						|
      if i=FItemCapacity then i:=0;
 | 
						|
    end;
 | 
						|
 | 
						|
    // check space
 | 
						|
    if FLastItemSpace<0 then Error('');
 | 
						|
    if FItems[FLastIndex]^.Size<=FLastItemSpace then Error('');
 | 
						|
    if FTopItemSpace<0 then Error('');
 | 
						|
    if FItems[FTopIndex]^.Size<=FTopItemSpace then Error('');
 | 
						|
    if (FTopIndex=FLastIndex)
 | 
						|
    and (FTopItemSpace>=FItems[FTopIndex]^.Size-FLastItemSpace) then Error('');
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TDynamicDataQueue.WriteDebugReport(WriteData: Boolean);
 | 
						|
var
 | 
						|
  i: LongInt;
 | 
						|
  DataCount: LongInt;
 | 
						|
  DataOffset: Integer;
 | 
						|
begin
 | 
						|
  DebugLn(['TDynamicDataQueue.WriteDebugReport FItemCapacity=',FItemCapacity,
 | 
						|
    ' FTopIndex=',FTopIndex,' FTopItemSpace=',FTopItemSpace,
 | 
						|
    ' FLastIndex=',FLastIndex,' FLastItemSpace=',FLastItemSpace,
 | 
						|
    ' Size=',Size,
 | 
						|
    ' MinimumBlockSize=',MinimumBlockSize,
 | 
						|
    ' MaximumBlockSize=',MaximumBlockSize]);
 | 
						|
  if FItems<>nil then begin
 | 
						|
    i:=FTopIndex;
 | 
						|
    repeat
 | 
						|
      DataCount:=FItems[i]^.Size;
 | 
						|
      DataOffset:=0;
 | 
						|
      if FTopIndex=i then begin
 | 
						|
        dec(DataCount,FTopItemSpace);
 | 
						|
        inc(DataOffset,FTopItemSpace);
 | 
						|
      end;
 | 
						|
      if i=FLastIndex then
 | 
						|
        dec(DataCount,FLastItemSpace);
 | 
						|
      debugln([i,' Item=',HexStr({%H-}PtrUInt(FItems[i]),8),' Size=',fItems[i]^.Size,' Start=',DataOffset,' Count=',DataCount]);
 | 
						|
      if WriteData then begin
 | 
						|
        debugln(dbgMemRange(PByte(@FItems[i]^.Data)+DataOffset,DataCount));
 | 
						|
      end;
 | 
						|
      
 | 
						|
      if i=FLastIndex then break;
 | 
						|
      inc(i);
 | 
						|
      if i=FItemCapacity then i:=0;
 | 
						|
    until false;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
end.
 |