AsphyreTimer.pas
上传用户:ctlcnc
上传日期:2021-12-10
资源大小:4933k
文件大小:9k
源码类别:

2D图形编程

开发平台:

Delphi

  1. unit AsphyreTimer;
  2. //---------------------------------------------------------------------------
  3. // AsphyreTimer.pas                                     Modified: 09-Nov-2006
  4. // Single-Core Timer based on Idle event                          Version 3.1
  5. //---------------------------------------------------------------------------
  6. // The contents of this file are subject to the Mozilla Public License
  7. // Version 1.1 (the "License"); you may not use this file except in
  8. // compliance with the License. You may obtain a copy of the License at
  9. // http://www.mozilla.org/MPL/
  10. //
  11. // Software distributed under the License is distributed on an "AS IS"
  12. // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing rights and limitations
  14. // under the License.
  15. //---------------------------------------------------------------------------
  16. // Changes since v2.0:
  17. //
  18. //   * Working internally with latency instead of frame-rate, giving much
  19. //     higher accuracy and avoiding some division problems.
  20. //   * Changed event name from OnRender to OnTimer, to avoid confusion
  21. //     with TAsphyreDevice events.
  22. //   + Added MaxFPS property which will limit OnTimer occurence to certain
  23. //     fixed speed. This is useful to prevent the application using all
  24. //     system's resources just to render a scene like 1000 FPS, which is
  25. //     not necessary anyway.
  26. //   * Changed Speed type to floating-point. This allows choosing fractional
  27. //     processing speeds like "74.2".
  28. //   * Changed MayRender property name to Enabled.
  29. //   + Added Precision property which will inform the user about the
  30. //     precision system used.
  31. //   + Added Delta property which informs the actual ratio between running
  32. //     speed (OnTimer event) and the specified one in Speed property.
  33. //   + Added Latency property, which informs the time elapsed since
  34. //     previous OnTimer event (in milliseconds).
  35. //
  36. // Changes since v3.0:
  37. //
  38. //   * Using 12:20 fixed-point instead of floating-point numbers now, which
  39. //     reduces the consequential errors and prevents any unnecessary jumps
  40. //     in OnProcess event.
  41. //   - Removed Theta parameter from Process() function, because it wasn't of
  42. //     much use and it doesn't work well with fixed-point math.
  43. //---------------------------------------------------------------------------
  44. interface
  45. //---------------------------------------------------------------------------
  46. uses
  47.  Windows, Classes, Forms, Math, MMSystem;
  48. //---------------------------------------------------------------------------
  49. type
  50.  TPerformancePrecision = (ppLow, ppHigh);
  51. //---------------------------------------------------------------------------
  52.  TAsphyreTimer = class
  53.  private
  54.   FMaxFPS : Integer;
  55.   FSpeed  : Real;
  56.   FEnabled: Boolean;
  57.   FOnTimer: TNotifyEvent;
  58.   FFrameRate: Integer;
  59.   FPrecision : TPerformancePrecision;
  60.   PrevTime  : Cardinal;
  61.   PrevTime64: Int64;
  62.   FOnProcess: TNotifyEvent;
  63.   Processed : Boolean;
  64.   LatencyFP : Integer;
  65.   DeltaFP   : Integer;
  66.   HighFreq  : Int64;
  67.   MinLatency: Integer;
  68.   SpeedLatcy: Integer;
  69.   FixedDelta: Integer;
  70.   SampleLatency: Integer;
  71.   SampleIndex: Integer;
  72.   function RetreiveLatency(): Integer;
  73.   procedure AppIdle(Sender: TObject; var Done: Boolean);
  74.   procedure SetSpeed(const Value: Real);
  75.   procedure SetMaxFPS(const Value: Integer);
  76.   function GetDelta(): Real;
  77.   function GetLatency(): Real;
  78.  public
  79.   property Delta  : Real read GetDelta;
  80.   property Latency: Real read GetLatency;
  81.   property FrameRate: Integer read FFrameRate;
  82.   procedure Process();
  83.   constructor Create();
  84.  published
  85.   // The speed at which processing will be made.
  86.   property Speed: Real read FSpeed write SetSpeed;
  87.   // The maximum allowed frame rate.
  88.   property MaxFPS: Integer read FMaxFPS write SetMaxFPS;
  89.   // Whether this timer is active or not.
  90.   property Enabled: Boolean read FEnabled write FEnabled;
  91.   // The precision of timer's calculations.
  92.   property Precision: TPerformancePrecision read FPrecision;
  93.   property OnTimer  : TNotifyEvent read FOnTimer write FOnTimer;
  94.   property OnProcess: TNotifyEvent read FOnProcess write FOnProcess;
  95.  end;
  96. //---------------------------------------------------------------------------
  97. var
  98.  Timer: TAsphyreTimer = nil;
  99. //---------------------------------------------------------------------------
  100. implementation
  101. //---------------------------------------------------------------------------
  102. const
  103.  FixedHigh = $100000;
  104.  DeltaLimit = 32 * FixedHigh;
  105. //---------------------------------------------------------------------------
  106. constructor TAsphyreTimer.Create();
  107. begin
  108.  inherited;
  109.  Speed := 60.0;
  110.  MaxFPS:= 100;
  111.  FPrecision:= ppLow;
  112.  if (QueryPerformanceFrequency(HighFreq)) then FPrecision:= ppHigh;
  113.  if (FPrecision = ppHigh) then
  114.   QueryPerformanceCounter(PrevTime64) else PrevTime:= GetTickCount;
  115.  Application.OnIdle:= AppIdle;
  116.  timeBeginPeriod(1);
  117.  FixedDelta := 0;
  118.  FFrameRate := 0;
  119.  SampleLatency:= 0;
  120.  SampleIndex  := 0;
  121.  Processed    := False;
  122. end;
  123. //---------------------------------------------------------------------------
  124. procedure TAsphyreTimer.SetSpeed(const Value: Real);
  125. begin
  126.  FSpeed:= Value;
  127.  if (FSpeed < 1.0) then FSpeed:= 1.0;
  128.  SpeedLatcy:= Round(FixedHigh * 1000.0 / FSpeed);
  129. end;
  130. //---------------------------------------------------------------------------
  131. procedure TAsphyreTimer.SetMaxFPS(const Value: Integer);
  132. begin
  133.  FMaxFPS:= Value;
  134.  if (FMaxFPS < 1) then FMaxFPS:= 1;
  135.  MinLatency:= Round(FixedHigh * 1000.0 / FMaxFPS);
  136. end;
  137. //---------------------------------------------------------------------------
  138. function TAsphyreTimer.GetDelta(): Real;
  139. begin
  140.  Result:= DeltaFP / FixedHigh;
  141. end;
  142. //---------------------------------------------------------------------------
  143. function TAsphyreTimer.GetLatency(): Real;
  144. begin
  145.  Result:= LatencyFP / FixedHigh;
  146. end;
  147. //---------------------------------------------------------------------------
  148. function TAsphyreTimer.RetreiveLatency(): Integer;
  149. var
  150.  CurTime  : Cardinal;
  151.  CurTime64: Int64;
  152. begin
  153.  if (FPrecision = ppHigh) then
  154.   begin
  155.    QueryPerformanceCounter(CurTime64);
  156.    Result:= ((CurTime64 - PrevTime64) * FixedHigh * 1000) div HighFreq;
  157.    PrevTime64:= CurTime64;
  158.   end else
  159.   begin
  160.    CurTime := GetTickCount;
  161.    Result  := (CurTime - PrevTime) * FixedHigh;
  162.    PrevTime:= CurTime;
  163.   end;
  164. end;
  165. //---------------------------------------------------------------------------
  166. procedure TAsphyreTimer.AppIdle(Sender: TObject; var Done: Boolean);
  167. var
  168.  WaitAmount: Integer;
  169.  SampleMax : Integer;
  170. begin
  171.  Done:= False;
  172.                 
  173.  // (1) Retreive current latency.
  174.  LatencyFP:= RetreiveLatency();
  175.  // (2) If Timer is disabled, wait a little to avoid using 100% of CPU.
  176.  if (not FEnabled) then
  177.   begin
  178.    SleepEx(5, True);
  179.    Exit;
  180.   end;
  181.  // (3) Adjust to maximum FPS, if necessary.
  182.  if (LatencyFP < MinLatency) then
  183.   begin
  184.    WaitAmount:= (MinLatency - LatencyFP) div FixedHigh;
  185.    SleepEx(WaitAmount, True);
  186.   end else WaitAmount:= 0;
  187.  // (4) The running speed ratio.
  188.  DeltaFP:= (Int64(LatencyFP) * FixedHigh) div SpeedLatcy;
  189.  // -> provide Delta limit to prevent auto-loop lockup.
  190.  if (DeltaFP > DeltaLimit) then DeltaFP:= DeltaLimit;
  191.  // (5) Calculate Frame Rate every second.
  192.  SampleLatency:= SampleLatency + LatencyFP + (WaitAmount * FixedHigh);
  193.  if (LatencyFP <= 0) then SampleMax:= 4
  194.   else SampleMax:= (Int64(FixedHigh) * 1000) div LatencyFP;
  195.  Inc(SampleIndex);
  196.  if (SampleIndex >= SampleMax) then
  197.   begin
  198.    FFrameRate   := (Int64(SampleIndex) * FixedHigh * 1000) div SampleLatency;
  199.    SampleLatency:= 0;
  200.    SampleIndex  := 0;
  201.   end;
  202.  // (6) Increase processing queque, if processing was made last time.
  203.  if (Processed) then
  204.   begin
  205.    Inc(FixedDelta, DeltaFP);
  206.    Processed:= False;
  207.   end;
  208.  // (7) Call timer event.
  209.  if (Assigned(FOnTimer)) then FOnTimer(Self);
  210. end;
  211. //---------------------------------------------------------------------------
  212. procedure TAsphyreTimer.Process();
  213. var
  214.  i, Amount: Integer;
  215. begin
  216.  Processed:= True;
  217.  Amount:= FixedDelta div FixedHigh;
  218.  if (Amount < 1) then Exit;
  219.  if (Assigned(FOnProcess)) then
  220.   for i:= 1 to Amount do
  221.    FOnProcess(Self);
  222.  FixedDelta:= FixedDelta and (FixedHigh - 1);  
  223. end;
  224. //---------------------------------------------------------------------------
  225. initialization
  226.  Timer:= TAsphyreTimer.Create();
  227. //---------------------------------------------------------------------------
  228. finalization
  229.  Timer.Free();
  230. //---------------------------------------------------------------------------
  231. end.