{******************************************************************************}
{                       CnPack For Delphi/C++Builder                           }
{                     中国人自己的开放源码第三方开发包                         }
{                   (C)Copyright 2001-2016 CnPack 开发组                       }
{                   ------------------------------------                       }
{                                                                              }
{            本开发包是开源的自由软件，您可以遵照 CnPack 的发布协议来修        }
{        改和重新发布这一程序。                                                }
{                                                                              }
{            发布这一开发包的目的是希望它有用，但没有任何担保。甚至没有        }
{        适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。        }
{                                                                              }
{            您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果        }
{        还没有，可访问我们的网站：                                            }
{                                                                              }
{            网站地址：http://www.cnpack.org                                   }
{            电子邮件：master@cnpack.org                                       }
{                                                                              }
{******************************************************************************}

unit CnIocpSimpleMemPool;
{* |<PRE>
================================================================================
* 软件名称：网络通讯组件包
* 单元名称：Windows完成端口(IOCP)组件使用的简单内存池实现单元
* 单元作者：cnwinds
*           菩提(cxmld@126.com) 移植修改
* 备    注：
*   1.TCnMemPoolMgr类是内存池管理的实现。
*     CnMemPoolMgr是TCnMemPoolMgr类的全局对象，可以通过该对象使用内存池。
*     他将大小(MemorySize)相同的内存块(TCnMemoryBlockItem)放在一个(TCnMemoryTypeItem)中进行管理。
*     TCnMemPoolMgr类中管理多个内存类型块(TCnMemoryTypeItem)。
*     一个内存类型块(TCnMemoryTypeItem)中包含了多个内存块(TCnMemoryBlockItem)。
*     阀值(Threshold)控制在一个TMemoryTypeItem中内存块的个数。
*       在系统频繁申请内存块的时候，总个数会大于阀值。
*       当系统对内存块的并发使用数低于阀值的时候释放内存块，让总个数等于阀值。
*       这个策略可以避免繁忙的时候频繁申请、释放内存，或者空闲的时候浪费内存。
*   2.TCnMemoryPool是一个控件。为了能可视化开发而产生的类。
*     可能出现多个控件的内存块大小相同，这样将对应到同一个内存类型块(TCnMemoryTypeItem)
*     出现这种情况多个控件将共用内存类型块(TCnMemoryTypeItem)中的内存块。阀值将取他们设置的最大值。
*
TODO >>>
*   1.TCnIocpMemPool类增加了一个方法:GetFreeMemoryType, 获取一个空闲的内存类型
*   2.TCnIocpMemPool分配的内存大小是固定,最大值由每一次分配决定
*   3.增加 TCnIocpSimpleMemPool来包装 TCnIocpMemPool的功能, 即租用内存和归还内存
*   4.使用"租用"和"归还"是为了区别正常的"分配内存"和"释放内存"
*   5.TCnIocpSimpleMemPool对应一个内存类型, 每个内存类型的类型值,自动获取
*   6.二个自定义名词(可能名字起得不够好):"内存块"和"内存类型块"
*     每个 TCnIocpSimpleMemPool 对应一个内存类型块, 它由多个"内存块"组成.每次用户
*     租用就是得到一个整的"内存块", 大小由第一次租用时确定.
*     TCnIocpMemPool包含了多个  "内存类型块", 即每注册一次时,就分配一个"内存类型块"
TODO >>>
*
* 开发平台：PWin2000Pro + Delphi 7.01
* 兼容测试：PWin9X/2000/XP + Delphi 5/6/7 + C++Builder 5/6
* 本 地 化：该单元中的字符串均符合本地化处理方式
* 单元标识：$Id$
* 修改记录：2008.09.16 V1.0
*                创建单元
================================================================================
|</PRE>}

interface

{$I CnPack.inc}

uses
  SysUtils, Classes, SyncObjs, Windows, Controls;

const
  SCnErrorNotRegister = '没有注册该内存类型(%d)!';
  SCnErrorBlockNotRent = '内存块没有被借出！';
  SCnErrorBlockUnknow = '没有该内存块！';

type
  TCreateMemoryEvent = procedure(Sender: TObject; var MemoryPtr: Pointer) of object;
  TFreeMemoryEvent = procedure(Sender: TObject; MemoryPtr: Pointer) of object;

  TCnMemoryBlockItem = record
  {* 内存块头}
    MemoryBlockPtr: Pointer;              //内存指针
    RentTime: Cardinal;                   //租用时间
    IsRent: Boolean;                      //是否租用
    RentCount: Cardinal;                  //租用次数
    Size: Cardinal;
  end;
  PCnMemoryBlockItem = ^TCnMemoryBlockItem;

  TCnMemoryTypeItem = record
  {* 内存类型块头}
    RefCount: Cardinal;                   //该类型块的引用次数
    MemorySize: Cardinal;                 //内存块的大小
    CreateMemoryProc: TCreateMemoryEvent; //创建内存方法指针
    FreeMemoryProc: TFreeMemoryEvent;     //释放内存方法指针
    Threshold: Cardinal;                  //内存块个数的阀值
                                          //如果缓存的块数多于该值则要启动清理程序。
    IdelCount: Cardinal;                  //空闲内存块的个数
    Lock: TCriticalSection;               //互锁相关
    MemoryBlockList: TList;               //内存块列表
  end;
  PCnMemoryTypeItem = ^TCnMemoryTypeItem;

  TCnSimpleMemPoolMgr = class
  private
    FLock: TCriticalSection;
    FMemoryTypeList: TList;

    function RegisterMemoryTypeItem(MemorySize: Cardinal;
                                    CreateMemoryProc: TCreateMemoryEvent;
                                    FreeMemoryProc: TFreeMemoryEvent): PCnMemoryTypeItem;
    {* 注册内存类型块(带线程锁)}

    procedure UnregisterMemoryTypeItem(MemoryTypeItem: PCnMemoryTypeItem);
    {* 注销内存类型块}

    function CreateMemoryBlockItem(MemoryTypeItem: PCnMemoryTypeItem): PCnMemoryBlockItem;
    procedure FreeMemoryBlockItem(MemoryTypeItem: PCnMemoryTypeItem;
      MemoryBlockItem: PCnMemoryBlockItem);

    function FindMemoryTypeItem(MemorySize: Cardinal;
                                CreateMemoryProc: TCreateMemoryEvent;
                                FreeMemoryProc: TFreeMemoryEvent): PCnMemoryTypeItem;

    procedure Clear;

  public
    constructor Create;
    destructor Destroy; override;

    function RegisterMemoryType(MemorySize: Cardinal;
                                CreateMemoryProc: TCreateMemoryEvent;
                                FreeMemoryProc: TFreeMemoryEvent): PCnMemoryTypeItem;
    {* 注册内存类型块 参数:内存类型, 创建和释放方法指针
       两方法指针是事件通知，同时可以自定义分配内存与释放内存的方法}

    procedure UnregisterMemoryType(MemoryTypeItem: PCnMemoryTypeItem);
    {* 注销内存类型块}
    
    procedure SetThreshold(MemoryTypeItem: PCnMemoryTypeItem; Threshold: Cardinal);
    {* 设置租用内存块的阀值。
      阀值和上限的区别：
        阀值表示当系统空闲的时候建议不要超过的值。上限表示任何时候都不能超过该值。
    }

    procedure RentMemory(MemoryTypeItem: PCnMemoryTypeItem; var MemoryPtr: Pointer);
    {* 租用一块内存}
    procedure ReturnMemory(MemoryTypeItem: PCnMemoryTypeItem; MemoryPtr: Pointer);
    {* 返还一块内存}
  end;

  TCnCustomSimpleMemPool = class (TComponent)
  private
    FMemorySize: Cardinal;
    FThreshold : Cardinal;
    FOnCreateMemory : TCreateMemoryEvent;
    FOnFreeMemory   : TFreeMemoryEvent;
    FMemTypeItem : PCnMemoryTypeItem;
    FIsReg: Boolean;    //是否已经注册到内存池管理器了

    procedure EnsureRegister;
    procedure DoRegister;
    procedure DoUnregister;

    procedure SetThreshold(const Value: Cardinal);
    procedure SetMemorySize(const Value: Cardinal);
    procedure SetOnCreateMemory(const Value: TCreateMemoryEvent);
    procedure SetOnFreeMemory(const Value: TFreeMemoryEvent);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure RentMemory(var MemoryPtr: Pointer);
    {* 租用内存}
    procedure ReturnMemory(MemoryPtr: Pointer);
    {* 归还内存}
  public
    property MemorySize: Cardinal read FMemorySize write SetMemorySize;
    {* 内存块的大小} 
    property Threshold : Cardinal read FThreshold write SetThreshold;
    {* 内存块的数量阀值(不是最大值,即可分配更多的内存块)}
    property OnCreateMemory : TCreateMemoryEvent read FOnCreateMemory write SetOnCreateMemory;
    {* 自定义在系统中分配内存的方法,默认实现采用 GetMemory}
    property OnFreeMemory: TFreeMemoryEvent read FOnFreeMemory write SetOnFreeMemory;
    {* 自定义在系统中释放内存的方法,默认实现采用 FreeMemory}
  end;

  TCnIocpSimpleMemPool = class(TCnCustomSimpleMemPool)
  published
    property MemorySize;
    {* 内存块的大小}
    property Threshold;
    {* 内存块的数量阀值(不是最大值,即可分配更多的内存块)}
    property OnCreateMemory;
    {* 自定义在系统中分配内存的方法,默认实现采用 GetMemory}
    property OnFreeMemory;
    {* 自定义在系统中释放内存的方法,默认实现采用 FreeMemory}
  end;

var
  CnSimpleMemPoolMgr: TCnSimpleMemPoolMgr;

implementation

{ TCnSimpleMemPoolMgr }

constructor TCnSimpleMemPoolMgr.Create;
begin
  FMemoryTypeList := TList.Create;
  FLock := TCriticalSection.Create;
end;

destructor TCnSimpleMemPoolMgr.Destroy;
begin
  Clear;
  FreeAndNil(FMemoryTypeList);
  FreeAndNil(FLock);
  inherited;
end;

procedure TCnSimpleMemPoolMgr.Clear;
var
  TypeItem: PCnMemoryTypeItem;
  I: Integer;
begin
  // 清除所有内存块
  FLock.Enter;
  try
    for I := 0 to FMemoryTypeList.Count - 1 do
    begin
      TypeItem := PCnMemoryTypeItem(FMemoryTypeList[I]);
      UnregisterMemoryTypeItem(TypeItem);
    end;
  finally
    FLock.Release;
  end;
end;

function TCnSimpleMemPoolMgr.RegisterMemoryTypeItem(MemorySize: Cardinal;
  CreateMemoryProc: TCreateMemoryEvent; FreeMemoryProc: TFreeMemoryEvent): PCnMemoryTypeItem;
begin
  Result := New(PCnMemoryTypeItem);
  Result^.Lock := TCriticalSection.Create;
  Result^.RefCount := 1;
  Result^.MemorySize := MemorySize;
  Result^.MemoryBlockList := TList.Create;
  Result^.CreateMemoryProc := CreateMemoryProc;
  Result^.FreeMemoryProc := FreeMemoryProc;
  Result^.Threshold := 20;
  Result^.IdelCount := 0;
end;

procedure TCnSimpleMemPoolMgr.UnregisterMemoryTypeItem(MemoryTypeItem: PCnMemoryTypeItem);
var
  I: Integer;
begin
  for I := 0 to MemoryTypeItem^.MemoryBlockList.Count - 1 do
    FreeMemoryBlockItem(MemoryTypeItem, MemoryTypeItem^.MemoryBlockList[I]);
  FreeAndNil(MemoryTypeItem^.Lock);
  FreeAndNil(MemoryTypeItem^.MemoryBlockList);
  Dispose(MemoryTypeItem);
end;

function TCnSimpleMemPoolMgr.CreateMemoryBlockItem(
  MemoryTypeItem: PCnMemoryTypeItem): PCnMemoryBlockItem;
var
  Size: Integer;
begin
  Size := MemoryTypeItem^.MemorySize;
  //创建内存块
  Result := New(PCnMemoryBlockItem);
  //申请内存。如果没有设置回调函数则使用GetMemory申请内存。
  if (Assigned(MemoryTypeItem.CreateMemoryProc)) then
    MemoryTypeItem^.CreateMemoryProc(Self, Result^.MemoryBlockPtr)
  else
    Result^.MemoryBlockPtr := GetMemory(Size);
  Result^.RentTime := 0;
  Result^.IsRent := False;
  Result^.RentCount := 0;
  Result^.Size := Size;
end;

procedure TCnSimpleMemPoolMgr.FreeMemoryBlockItem(MemoryTypeItem: PCnMemoryTypeItem;
  MemoryBlockItem: PCnMemoryBlockItem);
begin
  //释放内存
  if (Assigned(MemoryTypeItem.FreeMemoryProc)) then
    MemoryTypeItem.FreeMemoryProc(Self, MemoryBlockItem^.MemoryBlockPtr)
  else
    FreeMemory(MemoryBlockItem^.MemoryBlockPtr);
  //释放内存块
  Dispose(MemoryBlockItem);
end;

function TCnSimpleMemPoolMgr.FindMemoryTypeItem(MemorySize: Cardinal;
  CreateMemoryProc: TCreateMemoryEvent; FreeMemoryProc: TFreeMemoryEvent): PCnMemoryTypeItem;
var
  I: Integer;
begin
  FLock.Enter;
  try
    for I := 0 to FMemoryTypeList.Count - 1 do
    begin       
      Result := PCnMemoryTypeItem(FMemoryTypeList[I]);
      if (Result^.MemorySize = MemorySize) and
        (@Result^.CreateMemoryProc = @CreateMemoryProc) and
        (@Result^.FreeMemoryProc = @FreeMemoryProc) then Exit;
    end;
    Result := nil;
  finally
    FLock.Release;
  end;
end;

function TCnSimpleMemPoolMgr.RegisterMemoryType(MemorySize: Cardinal;
  CreateMemoryProc: TCreateMemoryEvent; FreeMemoryProc: TFreeMemoryEvent): PCnMemoryTypeItem;
begin
  Result := FindMemoryTypeItem(MemorySize, CreateMemoryProc, FreeMemoryProc);
  if Result = nil then                      //不存在,就创建
  begin
    Result := RegisterMemoryTypeItem(MemorySize, CreateMemoryProc, FreeMemoryProc);
    FLock.Enter;
    try
      FMemoryTypeList.Add(Result);            //并加入List中
    finally
      FLock.Release;  
    end;
  end else
  begin
    Inc(Result^.RefCount);                  //存在则增加引用计数
  end;
end;

procedure TCnSimpleMemPoolMgr.UnregisterMemoryType(MemoryTypeItem: PCnMemoryTypeItem);
begin
  //减少引用计数
  Dec(MemoryTypeItem^.RefCount);
  if MemoryTypeItem^.RefCount <> 0 then Exit;

  FLock.Enter;
  try
    FMemoryTypeList.Remove(MemoryTypeItem);
  finally
    FLock.Release;
  end;
  UnregisterMemoryTypeItem(MemoryTypeItem);
end;

procedure TCnSimpleMemPoolMgr.SetThreshold(MemoryTypeItem: PCnMemoryTypeItem; Threshold: Cardinal);
begin
  //如果一个MemoryTypeItem有多个引用，则使用最大的阀值
  if MemoryTypeItem <> nil then
  begin
    if MemoryTypeItem^.RefCount = 1 then
      MemoryTypeItem^.Threshold := Threshold
    else
      if MemoryTypeItem^.Threshold < Threshold then
        MemoryTypeItem^.Threshold := Threshold;
  end;
end;

procedure TCnSimpleMemPoolMgr.RentMemory(MemoryTypeItem: PCnMemoryTypeItem; var MemoryPtr: Pointer);
var
  BlockItem: PCnMemoryBlockItem;
begin
  //不需要循环查找，只要找到第一个内存块，如果被租用则表示所有内存块都已经被租用了。
  MemoryTypeItem^.Lock.Enter;
  try
    if MemoryTypeItem^.MemoryBlockList.Count > 0 then
    begin
      BlockItem := PCnMemoryBlockItem(MemoryTypeItem^.MemoryBlockList[0]);
      if not BlockItem^.IsRent then      //第0个内存块是否已租用
      begin
        MemoryTypeItem^.MemoryBlockList.Remove(BlockItem);
        MemoryTypeItem^.MemoryBlockList.Add(BlockItem);  //将内存块重新放入到LIST的最后
        MemoryPtr := BlockItem.MemoryBlockPtr;     //取得内存块的指针
        Inc(BlockItem^.RentCount);                 //租用总数+1
        BlockItem^.RentTime := GetTickCount;       //租用时间
        BlockItem^.IsRent := True;                 //置租用标志

        //空闲内存块个数减一
        Dec(MemoryTypeItem^.IdelCount);

        Exit;
      end;
    end;

    // 新创建一个内存块
    BlockItem := CreateMemoryBlockItem(MemoryTypeItem);
    MemoryPtr := BlockItem^.MemoryBlockPtr;
    Inc(BlockItem^.RentCount);
    BlockItem^.RentTime := GetTickCount;
    BlockItem^.IsRent := True;
    BlockItem^.Size := MemoryTypeItem^.MemorySize;
    MemoryTypeItem^.MemoryBlockList.Add(BlockItem);
  finally
    MemoryTypeItem^.Lock.Release;
  end;
end;

procedure TCnSimpleMemPoolMgr.ReturnMemory(MemoryTypeItem: PCnMemoryTypeItem; MemoryPtr: Pointer);
var
  I: Integer;
  BlockItem: PCnMemoryBlockItem;
  ReleaseCount: Cardinal;
  UsedCount: Cardinal;
  TotalCount: Cardinal;
begin
  //对内存块的调整不是强制性的，原则是在方便的时候调整一下内块的个数
  MemoryTypeItem^.Lock.Enter;
  try
    ReleaseCount := 0;
    //判断是否要删除内存块
    TotalCount := MemoryTypeItem^.MemoryBlockList.Count;
    if TotalCount > MemoryTypeItem^.Threshold then
    begin
      UsedCount := TotalCount - MemoryTypeItem^.IdelCount;
      if UsedCount < MemoryTypeItem^.Threshold then
      begin
        //计算要删除内存块的个数
        //不表示一定要删除这么多个内存块，理想情况下会删除这么多内存块
        ReleaseCount := TotalCount - MemoryTypeItem^.Threshold;
      end;
    end;
    
    for I := MemoryTypeItem^.MemoryBlockList.Count - 1 downto 0 do
    begin
      BlockItem := PCnMemoryBlockItem(MemoryTypeItem^.MemoryBlockList[I]);
      if MemoryPtr = BlockItem^.MemoryBlockPtr then         //查询内存块(比较地址相同)
      begin
        if BlockItem^.IsRent then
        begin
          //归还内存块
          BlockItem^.RentTime := 0;
          BlockItem^.IsRent := False;
          MemoryTypeItem^.MemoryBlockList.Remove(BlockItem);
          MemoryTypeItem^.MemoryBlockList.Insert(0, BlockItem);  //插入到第0个
          //空闲内存块个数加一
          Inc(MemoryTypeItem^.IdelCount);
          Exit;
        end 
        else
          raise Exception.Create(SCnErrorBlockNotRent); //没有被租用异常 
      end;
      //释放内存块
      if (ReleaseCount <> 0) and (not BlockItem^.IsRent) then
      begin
        FreeMemoryBlockItem(MemoryTypeItem, BlockItem);
        MemoryTypeItem^.MemoryBlockList.Remove(BlockItem);
        Dec(ReleaseCount);
      end;
    end;
    raise Exception.Create(SCnErrorBlockUnknow);        //没有找到内存块抛出异常
  finally
    MemoryTypeItem^.Lock.Release;
  end;
end;

{ TCnIocpSimpleMemPool }

constructor TCnCustomSimpleMemPool.Create(AOwner: TComponent);
begin
  inherited;
  FThreshold := 20;
  FMemorySize := 1024;
  FIsReg := False;
  //使用延迟注册方式，避免初始化参数造成反复注册
  //DoRegister;
end;

destructor TCnCustomSimpleMemPool.Destroy;
begin
  DoUnregister;
  inherited;
end;

procedure TCnCustomSimpleMemPool.EnsureRegister;
begin
  if not FIsReg then
    DoRegister;
end;

procedure TCnCustomSimpleMemPool.DoRegister;
begin
  if (not (csDesigning in ComponentState)) and (not FIsReg) then
  begin
    FMemTypeItem := CnSimpleMemPoolMgr.RegisterMemoryType(
      FMemorySize, FOnCreateMemory, FOnFreeMemory);
    CnSimpleMemPoolMgr.SetThreshold(FMemTypeItem, Threshold);
    FIsReg := True;
  end;
end;

procedure TCnCustomSimpleMemPool.DoUnregister;
begin
  if FIsReg then
  begin
    CnSimpleMemPoolMgr.UnregisterMemoryType(FMemTypeItem);
    FIsReg := False;
  end;
end;

procedure TCnCustomSimpleMemPool.RentMemory(var MemoryPtr: Pointer);
begin
  EnsureRegister;
  
  CnSimpleMemPoolMgr.RentMemory(FMemTypeItem, MemoryPtr);
end;

procedure TCnCustomSimpleMemPool.ReturnMemory(MemoryPtr: Pointer);
begin
  EnsureRegister;
  
  CnSimpleMemPoolMgr.ReturnMemory(FMemTypeItem, MemoryPtr);
end;

procedure TCnCustomSimpleMemPool.SetMemorySize(const Value: Cardinal);
begin
  if FMemorySize <> Value then
  begin
    if FIsReg then DoUnregister;

    FMemorySize := Value;
  end;
end;

procedure TCnCustomSimpleMemPool.SetThreshold(const Value: Cardinal);
begin
  if FThreshold <> Value then
  begin
    FThreshold := Value;
    if FIsReg then
      CnSimpleMemPoolMgr.SetThreshold(FMemTypeItem, FThreshold);
  end;
end;

procedure TCnCustomSimpleMemPool.SetOnCreateMemory(const Value: TCreateMemoryEvent);
begin
  if @FOnCreateMemory <> @Value then
  begin
    if FIsReg then DoUnregister;

    FOnCreateMemory := Value;
  end;
end;

procedure TCnCustomSimpleMemPool.SetOnFreeMemory(const Value: TFreeMemoryEvent);
begin
  if @FOnFreeMemory <> @Value then
  begin
    if FIsReg then DoUnregister;

    FOnFreeMemory := Value;
  end;
end;

initialization
  CnSimpleMemPoolMgr := TCnSimpleMemPoolMgr.Create;

finalization
  CnSimpleMemPoolMgr.Free;

end.
