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

unit CnTimer;
{* |<PRE>
================================================================================
* 软件名称：不可视工具组件包
* 单元名称：高精度定时器组件TCnTimer单元
* 单元作者：周劲羽 (zjy@cnpack.org)
* 备    注：- Delphi自带的TTimer使用操作系统以消息方式提供的定时器，在Win9X下
*             定时精度仅为55ms，NT下约10ms。
*           - TCnTimer使用多媒体定时器进行定时控制，精度较高。其使用方式与TTimer
*             完全兼容，并提供了更多的功能。
*           - TCnTimerList定时器列表可以同时产生多个定时器。
*           - 所有定时器使用同一个内部定时器，适合大量使用的场合。
*           - 由于Win32是抢占式多任务操作系统，各个线程轮流享用CPU时间片，如果
*             其它的线程占用大量CPU时间，即使设置最高精度，也不一定能保证精确
*             的定时间隔。
* 开发平台：PWin98SE + Delphi 5.0
* 兼容测试：PWin9X/2000/XP + Delphi 5/6
* 本 地 化：该单元中的字符串均符合本地化处理方式
* 单元标识：$Id$
* 修改记录：2008.12.22 V2.2
*               增加同步事件属性 SyncEvent 并默认为真，使定时器事件可以在主线程中执行
*           2006.12.28 V2.1
*               去掉定时线程，改用多媒体定时器，以减少资源占用并解决在 DLL 中不能
*               使用的问题
*           2002.11.05 V2.0
*               重写全部代码，增加定时器列表，所有定时器使用同一线程定时
*           2002.04.18 V1.0
*               创建单元
================================================================================
|</PRE>}

interface

{$I CnPack.inc}

uses
  Windows, SysUtils, Classes, Forms, MMSystem, CnClasses, CnConsts, CnCompConsts,
  CnNativeDecl;

type

//==============================================================================
// 高精度定时器对象
//==============================================================================

{ TCnTimerObject }

  TCnTimerObject = class(TObject)
  private
    FActualFPS: Double;
    FEnabled: Boolean;
    FExecCount: Cardinal;
    FInterval: Cardinal;
    FLastTickCount: Cardinal;
    FOnTimer: TNotifyEvent;
    FRepeatCount: Cardinal;
    FSyncEvent: Boolean;
    function GetFPS: Double;
    procedure SetEnabled(Value: Boolean);
    procedure SetFPS(Value: Double);
    procedure SetInterval(Value: Cardinal);
    procedure SetRepeatCount(Value: Cardinal);
  protected
    procedure Timer; dynamic;
  public
    constructor Create;
    destructor Destroy; override;
    property ActualFPS: Double read FActualFPS;
    property ExecCount: Cardinal read FExecCount;
    property Enabled: Boolean read FEnabled write SetEnabled default True;
    property FPS: Double read GetFPS write SetFPS stored False;
    property Interval: Cardinal read FInterval write SetInterval default 1000;
    property OnTimer: TNotifyEvent read FOnTimer write FOnTimer;
    property RepeatCount: Cardinal read FRepeatCount write SetRepeatCount default 0;
    property SyncEvent: Boolean read FSyncEvent write FSyncEvent default True;
  end;

//==============================================================================
// 高精度定时器组件
//==============================================================================

{ TCnTimer }

  TCnTimer = class(TCnComponent)
  {* 高精度定时器组件，使用方法类似 TTimer。}
  private
    FTimerObject: TCnTimerObject;
    function GetActualFPS: Double;
    function GetEnabled: Boolean;
    function GetExecCount: Cardinal;
    function GetFPS: Double;
    function GetInterval: Cardinal;
    function GetOnTimer: TNotifyEvent;
    function GetRepeatCount: Cardinal;
    function GetSyncEvent: Boolean;
    procedure SetEnabled(Value: Boolean);
    procedure SetFPS(Value: Double);
    procedure SetInterval(Value: Cardinal);
    procedure SetOnTimer(Value: TNotifyEvent);
    procedure SetRepeatCount(Value: Cardinal);
    procedure SetSyncEvent(const Value: Boolean);
  protected
    procedure GetComponentInfo(var AName, Author, Email, Comment: string);
      override;
  public
    constructor Create(AOwner: TComponent); override;
    {* 类构造器}
    destructor Destroy; override;
    {* 类析构器}
    property ActualFPS: Double read GetActualFPS;
    {* 实际的定时器速率，次每秒}
    property ExecCount: Cardinal read GetExecCount;
    {* 已经执行过的次数}
  published
    property Enabled: Boolean read GetEnabled write SetEnabled default True;
    {* 定时器是否启用}
    property FPS: Double read GetFPS write SetFPS stored False;
    {* 定时器速度，次每秒}
    property Interval: Cardinal read GetInterval write SetInterval default 1000;
    {* 定时间隔，毫秒}
    property OnTimer: TNotifyEvent read GetOnTimer write SetOnTimer;
    {* 定时事件}
    property RepeatCount: Cardinal read GetRepeatCount write SetRepeatCount default 0;
    {* 定时事件次数，当定时事件发生指定次数后自动关闭。如果为 0 表示不限制}
    property SyncEvent: Boolean read GetSyncEvent write SetSyncEvent default True;
    {* 定时事件是否用同步方式在主线程中产生，如果为 False 则定时事件在多媒体定时器线程中调用。
       定时事件中如果涉及到 VCL 界面操作，应设为 True。}
  end;

//==============================================================================
// 高精度定时器列表集合子项
//==============================================================================

{ TCnTimerItem }

  TCnTimerItem = class(TCollectionItem)
  {* 高精度定时器列表子项，使用方法类似 TTimer。}
  private
    FOnTimer: TNotifyEvent;
    FTimerObject: TCnTimerObject;
    function GetActualFPS: Double;
    function GetEnabled: Boolean;
    function GetExecCount: Cardinal;
    function GetFPS: Double;
    function GetInterval: Cardinal;
    function GetRepeatCount: Cardinal;
    procedure SetEnabled(Value: Boolean);
    procedure SetFPS(Value: Double);
    procedure SetInterval(Value: Cardinal);
    procedure SetRepeatCount(Value: Cardinal);
    function GetSyncEvent: Boolean;
    procedure SetSyncEvent(const Value: Boolean);
  protected
    procedure Timer(Sender: TObject);
  public
    constructor Create(Collection: TCollection); override;
    {* 类构造器}
    destructor Destroy; override;
    {* 类析构器}
    procedure Assign(Source: TPersistent); override;
    {* 赋值方法}
    property ActualFPS: Double read GetActualFPS;
    {* 实际的定时器速率，次每秒}
    property ExecCount: Cardinal read GetExecCount;
    {* 已经执行过的次数}
  published
    property Enabled: Boolean read GetEnabled write SetEnabled default True;
    {* 定时器是否启用}
    property FPS: Double read GetFPS write SetFPS stored False;
    {* 定时器速度，次每秒}
    property Interval: Cardinal read GetInterval write SetInterval default 1000;
    {* 定时间隔，毫秒}
    property OnTimer: TNotifyEvent read FOnTimer write FOnTimer;
    {* 定时事件}
    property RepeatCount: Cardinal read GetRepeatCount write SetRepeatCount default 0;
    {* 定时事件次数，当定时事件发生指定次数后自动关闭。如果为 0 表示不限制}
    property SyncEvent: Boolean read GetSyncEvent write SetSyncEvent default True;
    {* 定时事件是否用同步方式在主线程中产生，如果为 False 则定时事件在多媒体定时器线程中调用。
       定时事件中如果涉及到 VCL 界面操作，应设为 True。}
  end;

//==============================================================================
// 高精度定时器列表集合类
//==============================================================================

{ TCnTimerCollection }

  TCnTimerList = class;

  TCnTimerCollection = class(TOwnedCollection)
  {* 高精度定时器列表集合}
  private
    FTimerList: TCnTimerList;
    function GetItems(Index: Integer): TCnTimerItem;
    procedure SetItems(Index: Integer; Value: TCnTimerItem);
  protected
    property TimerList: TCnTimerList read FTimerList;
  public
    constructor Create(AOwner: TPersistent);
    {* 类构造器}
    property Items[Index: Integer]: TCnTimerItem read GetItems write SetItems; default;
    {* 定时器数组属性}
  end;

//==============================================================================
// 高精度定时器列表组件
//==============================================================================

{ TCnTimerList }

  TCnTimerEvent = procedure(Sender: TObject; Index: Integer; var Handled:
    Boolean) of object;
  {* 高精度定时器列表事件。Index 为产生事件的定时器子项序号，Handle 返回是否已处理，
     如果在事件中将 Handle 置为 true，将不产生该定时器子项事件}
    
  TCnTimerList = class(TCnComponent)
  {* 高精度定时器列表组件，可以定义多个定时器。}
  private
    FItems: TCnTimerCollection;
    FOnTimer: TCnTimerEvent;
    procedure SetItems(Value: TCnTimerCollection);
  protected
    procedure GetComponentInfo(var AName, Author, Email, Comment: string);
      override;
    function Timer(Index: Integer): Boolean;
  public
    constructor Create(AOwner: TComponent); override;
    {* 类构造器}
    destructor Destroy; override;
    {* 类析构器}
  published
    property Items: TCnTimerCollection read FItems write SetItems;
    {* 定时器列表}
    property OnTimer: TCnTimerEvent read FOnTimer write FOnTimer;
    {* 定时器事件}
  end;

implementation

uses
  Messages;

const
  UM_CNTIMER = WM_USER + 101;

type

//==============================================================================
// 高精度定时器管理器（私有类）
//==============================================================================

{ TCnTimerMgr }

  TCnTimerMgr = class(TObject)
  private
    FTimerList: TThreadList;
    FTimerRes: Integer;
    FTimerID: Integer;
    FHwnd: HWND;
    function InitMMTimer: Boolean;
    procedure FreeMMTimer;
  protected
    procedure ClearTimer;
    procedure DoTimer(Sync: Boolean);
    procedure Timer; virtual;
    procedure WndProc(var Message: TMessage);
  public
    constructor Create;
    destructor Destroy; override;
    function AddTimer: TCnTimerObject;
    procedure DeleteTimer(TimerObject: TCnTimerObject);
  end;

//==============================================================================
// 高精度定时器管理器（私有类）
//==============================================================================

{ TCnTimerMgr }

constructor TCnTimerMgr.Create;
begin
  inherited Create;
  FTimerList := TThreadList.Create;
  FHwnd := AllocateHWnd(WndProc);
  InitMMTimer;
end;

destructor TCnTimerMgr.Destroy;
begin
  DeallocateHWnd(FHwnd);
  FreeMMTimer;
  ClearTimer;
  FreeAndNil(FTimerList);
  inherited Destroy;
end;

procedure MMTimerProc(uTimerID, uMessage: UINT; dwUser, dw1, dw2: TCnNativePointer) stdcall;
begin
  TCnTimerMgr(dwUser).Timer;
end;  

function TCnTimerMgr.InitMMTimer: Boolean;
var
  tc: TIMECAPS;
begin
  Result := False;
  if timeGetDevCaps(@tc, SizeOf(TIMECAPS)) = TIMERR_NOERROR then
  begin
    FTimerRes := tc.wPeriodMin;
    if timeBeginPeriod(FTimerRes) = TIMERR_NOERROR then
    begin
      FTimerID := timeSetEvent(tc.wPeriodMin, 0, MMTimerProc, Cardinal(Self),
        TIME_PERIODIC);
      Result := FTimerID <> 0;
    end
    else
      FTimerRes := 0;
  end;
end;

procedure TCnTimerMgr.FreeMMTimer;
begin
  if FTimerID <> 0 then
  begin
    timeKillEvent(FTimerID);
  end;
  
  if FTimerRes <> 0 then
  begin
    timeEndPeriod(FTimerRes);
  end;   
end;

function TCnTimerMgr.AddTimer: TCnTimerObject;
begin
  Result := TCnTimerObject.Create;
  with FTimerList.LockList do
  try
    Add(Result);
  finally
    FTimerList.UnlockList;
  end;
end;

procedure TCnTimerMgr.ClearTimer;
var
  i: Integer;
begin
  with FTimerList.LockList do
  try
    for i := Count - 1 downto 0 do
    begin
      TCnTimerObject(Items[i]).Free;
      Delete(i);
    end;
  finally
    FTimerList.UnlockList;
  end;
end;

procedure TCnTimerMgr.DeleteTimer(TimerObject: TCnTimerObject);
var
  i: Integer;
begin
  with FTimerList.LockList do
  try
    for i := 0 to Count - 1 do
      if Items[i] = TimerObject then
      begin
        TimerObject.Free;
        Delete(i);
        Exit;
      end;
  finally
    FTimerList.UnlockList;
  end;
end;

procedure TCnTimerMgr.DoTimer(Sync: Boolean);
var
  i: Integer;
  CurrTick: Cardinal;
begin
  with FTimerList.LockList do
  try
    CurrTick := timeGetTime;
    for i := 0 to Count - 1 do
      with TCnTimerObject(Items[i]) do
        if Enabled and (FSyncEvent = Sync) and(Interval <> 0) and
          (CurrTick - FLastTickCount >= Interval) and Assigned(FOnTimer) then
        begin
          if CurrTick <> FLastTickCount then
            FActualFPS := 1000 / (CurrTick - FLastTickCount)
          else
            FActualFPS := 0;
          FLastTickCount := CurrTick;
          try
            Timer;
          except
            Application.HandleException(Self);
          end;
        end;
  finally
    FTimerList.UnlockList;
  end;
end;

procedure TCnTimerMgr.Timer;
begin
  DoTimer(False);
  PostMessage(FHwnd, UM_CNTIMER, 0, 0);
end;

procedure TCnTimerMgr.WndProc(var Message: TMessage);
begin
  if Message.Msg = UM_CNTIMER then
  begin
    DoTimer(True);
  end;  
end;

var
  TimerMgr: TCnTimerMgr;

function GetTimerMgr: TCnTimerMgr;
begin
  if TimerMgr = nil then
    TimerMgr := TCnTimerMgr.Create;
  Result := TimerMgr;
end;

//==============================================================================
// 高精度定时器对象
//==============================================================================

{ TCnTimerObject }

constructor TCnTimerObject.Create;
begin
  inherited Create;
  FEnabled := True;
  FExecCount := 0;
  FInterval := 1000;
  FLastTickCount := timeGetTime;
  FRepeatCount := 0;
  FSyncEvent := True;
end;

destructor TCnTimerObject.Destroy;
begin
end;

function TCnTimerObject.GetFPS: Double;
begin
  if Interval = 0 then
    Result := 0
  else
    Result := 1000 / Interval;
end;

procedure TCnTimerObject.SetEnabled(Value: Boolean);
begin
  if FEnabled <> Value then
  begin
    FEnabled := Value;
    FExecCount := 0;
    if FEnabled then
    begin
      FLastTickCount := timeGetTime;
    end;
  end;
end;

procedure TCnTimerObject.SetFPS(Value: Double);
begin
  if Value < 0 then
    Exit
  else if Value < 1 / High(Word) then
    Value := 1 / High(Word)
  else if Value > 1000 then
    Value := 1000;
  FInterval := Round(1000 / Value);
end;

procedure TCnTimerObject.SetInterval(Value: Cardinal);
begin
  if FInterval <> Value then
  begin
    FInterval := Value;
    FLastTickCount := timeGetTime;
  end;
end;

procedure TCnTimerObject.SetRepeatCount(Value: Cardinal);
begin
  if FRepeatCount <> Value then
  begin
    FRepeatCount := Value;
  end;
end;

procedure TCnTimerObject.Timer;
begin
  Inc(FExecCount);
  if Assigned(FOnTimer) then FOnTimer(Self);
  if (RepeatCount <> 0) and (FExecCount >= RepeatCount) then
  begin
    Enabled := False;
  end;
end;

//==============================================================================
// 高精度定时器组件
//==============================================================================

{ TCnTimer }

constructor TCnTimer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FTimerObject := GetTimerMgr.AddTimer;
end;

destructor TCnTimer.Destroy;
begin
  if TimerMgr <> nil then
    TimerMgr.DeleteTimer(FTimerObject);
  inherited Destroy;
end;

function TCnTimer.GetActualFPS: Double;
begin
  Result := FTimerObject.ActualFPS;
end;

procedure TCnTimer.GetComponentInfo(var AName, Author, Email, Comment: string);
begin
  AName := SCnTimerName;
  Author := SCnPack_Zjy;
  Email := SCnPack_ZjyEmail;
  Comment := SCnTimerComment;
end;

function TCnTimer.GetEnabled: Boolean;
begin
  Result := FTimerObject.Enabled;
end;

function TCnTimer.GetExecCount: Cardinal;
begin
  Result := FTimerObject.ExecCount;
end;

function TCnTimer.GetFPS: Double;
begin
  Result := FTimerObject.FPS;
end;

function TCnTimer.GetInterval: Cardinal;
begin
  Result := FTimerObject.Interval;
end;

function TCnTimer.GetOnTimer: TNotifyEvent;
begin
  Result := FTimerObject.OnTimer;
end;

function TCnTimer.GetRepeatCount: Cardinal;
begin
  Result := FTimerObject.RepeatCount;
end;

function TCnTimer.GetSyncEvent: Boolean;
begin
  Result := FTimerObject.SyncEvent;
end;

procedure TCnTimer.SetEnabled(Value: Boolean);
begin
  FTimerObject.Enabled := Value;
end;

procedure TCnTimer.SetFPS(Value: Double);
begin
  FTimerObject.FPS := Value;
end;

procedure TCnTimer.SetInterval(Value: Cardinal);
begin
  FTimerObject.Interval := Value;
end;

procedure TCnTimer.SetOnTimer(Value: TNotifyEvent);
begin
  FTimerObject.OnTimer := Value;
end;

procedure TCnTimer.SetRepeatCount(Value: Cardinal);
begin
  FTimerObject.RepeatCount := Value;
end;

procedure TCnTimer.SetSyncEvent(const Value: Boolean);
begin
  FTimerObject.SyncEvent := Value;
end;

//==============================================================================
// 高精度定时器列表集合子项
//==============================================================================

{ TCnTimerItem }

constructor TCnTimerItem.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FTimerObject := GetTimerMgr.AddTimer;
  FTimerObject.OnTimer := Timer;
end;

destructor TCnTimerItem.Destroy;
begin
  if TimerMgr <> nil then
    TimerMgr.DeleteTimer(FTimerObject);
  inherited Destroy;
end;

procedure TCnTimerItem.Assign(Source: TPersistent);
begin
  if Source is TCnTimerItem then
  begin
    Enabled := TCnTimerItem(Source).Enabled;
    Interval := TCnTimerItem(Source).Interval;
    RepeatCount := TCnTimerItem(Source).RepeatCount;
  end
  else
    inherited;
end;

function TCnTimerItem.GetActualFPS: Double;
begin
  Result := FTimerObject.ActualFPS;
end;

function TCnTimerItem.GetEnabled: Boolean;
begin
  Result := FTimerObject.Enabled;
end;

function TCnTimerItem.GetExecCount: Cardinal;
begin
  Result := FTimerObject.ExecCount;
end;

function TCnTimerItem.GetFPS: Double;
begin
  Result := FTimerObject.FPS;
end;

function TCnTimerItem.GetInterval: Cardinal;
begin
  Result := FTimerObject.Interval;
end;

function TCnTimerItem.GetRepeatCount: Cardinal;
begin
  Result := FTimerObject.RepeatCount;
end;

function TCnTimerItem.GetSyncEvent: Boolean;
begin
  Result := FTimerObject.SyncEvent;
end;

procedure TCnTimerItem.SetEnabled(Value: Boolean);
begin
  FTimerObject.Enabled := Value;
end;

procedure TCnTimerItem.SetFPS(Value: Double);
begin
  FTimerObject.FPS := Value;
end;

procedure TCnTimerItem.SetInterval(Value: Cardinal);
begin
  FTimerObject.Interval := Value;
end;

procedure TCnTimerItem.SetRepeatCount(Value: Cardinal);
begin
  FTimerObject.RepeatCount := Value;
end;

procedure TCnTimerItem.SetSyncEvent(const Value: Boolean);
begin
  FTimerObject.SyncEvent := Value;
end;

procedure TCnTimerItem.Timer(Sender: TObject);
begin
  if not TCnTimerList(TCnTimerCollection(Collection).GetOwner).Timer(Index) then
    if Assigned(FOnTimer) then
      FOnTimer(Self);
end;

//==============================================================================
// 高精度定时器列表集合类
//==============================================================================

{ TCnTimerCollection }

constructor TCnTimerCollection.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner, TCnTimerItem);
  Assert(AOwner is TCnTimerList);
end;

function TCnTimerCollection.GetItems(Index: Integer): TCnTimerItem;
begin
  Result := TCnTimerItem(inherited Items[Index]);
end;

procedure TCnTimerCollection.SetItems(Index: Integer; Value: TCnTimerItem);
begin
  inherited Items[Index] := Value;
end;

//==============================================================================
// 高精度定时器列表组件
//==============================================================================

{ TCnTimerList }

constructor TCnTimerList.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FItems := TCnTimerCollection.Create(Self);
end;

destructor TCnTimerList.Destroy;
begin
  FItems.Free;
  inherited Destroy;
end;

procedure TCnTimerList.GetComponentInfo(var AName, Author, Email, Comment:
  string);
begin
  AName := SCnTimerListName;
  Author := SCnPack_Zjy;
  Email := SCnPack_ZjyEmail;
  Comment := SCnTimerListComment;
end;

procedure TCnTimerList.SetItems(Value: TCnTimerCollection);
begin
  FItems.Assign(Value);
end;

function TCnTimerList.Timer(Index: Integer): Boolean;
begin
  Result := False;
  if Assigned(FOnTimer) then
    FOnTimer(Self, Index, Result);
end;

initialization

finalization
  if TimerMgr <> nil then
    FreeAndNil(TimerMgr);

end.

