2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Cadencing composant for GLScene (ease Progress processing)
8
10/11/12 - PW - Added CPP compatibility: restored GetCurrenttime instead of GetCurrentTime
9
07/08/11 - Yar - Added OnTotalProgress event, which happens after all iterations with fixed delta time (thanks Controller)
10
06/02/11 - Predator - Improved TGLCadencer for Lazarus
11
29/11/10 - Yar - Changed TASAPHandler.FMessageTime type to unsigned (thanks olkondr)
12
21/11/09 - DaStr - Bugfixed FSubscribedCadenceableComponents
14
09/11/09 - DaStr - Improved FPC compatibility
15
(thanks Predator) (BugtrackerID = 2893580)
16
21/09/07 - DaStr - Added TGLCadencer.SetCurrentTime()
17
17/03/07 - DaStr - Dropped Kylix support in favor of FPC (BugTracekrID=1681585)
18
02/08/04 - LR, YHC - BCB corrections: changed GetCurrentTime to GetCurrenttime
19
28/06/04 - LR - Added some ifdef Win32 for Linux
20
20/10/03 - EG - Fixed issues about cadencer destruction
21
29/08/03 - EG - Added MinDeltaTime and FixedDeltaTime
22
21/08/03 - EG - Fixed Application.OnIdle reset bug (Solerman Kaplon)
23
04/07/03 - EG - Improved TimeMultiplier transitions (supports zero)
24
06/06/03 - EG - Added cmApplicationIdle Mode
25
19/05/03 - EG - Added Reset (Roberto Bussola)
26
04/03/02 - EG - Added SetTimeMultiplier
27
01/07/02 - EG - Added TGLCadencedComponent
28
05/12/01 - EG - Fix in subscription mechanism (D6 IDE freezes gone?)
29
30/11/01 - EG - Added IsBusy (thx Chris S)
30
08/09/01 - EG - Added MaxDeltaTime limiter
31
23/08/01 - EG - No more "deprecated" warning for Delphi6
32
12/08/01 - EG - Protection against "timer flood"
33
19/07/01 - EG - Fixed Memory Leak in RegisterASAPCadencer,
34
Added speed limiter TASAPHandler.WndProc
35
01/02/01 - EG - Fixed "Freezing" when Enabled set to False
36
08/10/00 - EG - Added TASAPHandler to support multiple ASAP cadencers
37
19/06/00 - EG - Fixed TGLCadencer.Notification
38
14/04/00 - EG - Minor fixes
39
13/04/00 - EG - Creation
49
GLScene, GLCrossPlatform, GLBaseClasses,
50
Classes, Types, Forms, lmessages, SyncObjs;
51
//**************************************
57
{ Determines how the TGLCadencer operates.
58
- cmManual : you must trigger progress manually (in your code)
59
- cmASAP : progress is triggered As Soon As Possible after a previous
60
progress (uses windows messages).
61
- cmApplicationIdle : will hook Application.OnIdle, this will overwrite
62
any previous event handle, and only one cadencer may be in this mode. }
63
TGLCadencerMode = (cmManual, cmASAP, cmApplicationIdle);
65
// TGLCadencerTimeReference
67
{ Determines which time reference the TGLCadencer should use.
68
- cmRTC : the Real Time Clock is used (precise over long periods, but
69
not accurate to the millisecond, may limit your effective framerate
70
to less than 50 FPS on some systems)
71
- cmPerformanceCounter : the windows performance counter is used (nice
72
precision, may derive over long periods, this is the default option
73
as it allows the smoothest animation on fast systems)
74
- cmExternal : the CurrentTime property is used }
75
TGLCadencerTimeReference = (cmRTC, cmPerformanceCounter, cmExternal);
79
{ This component allows auto-progression of animation.
80
Basicly dropping this component and linking it to your TGLScene will send
81
it real-time progression events (time will be measured in seconds) while
82
keeping the CPU 100% busy if possible (ie. if things change in your scene).
83
The progression time (the one you'll see in you progression events)
84
is calculated using (CurrentTime-OriginTime)*TimeMultiplier,
85
CurrentTime being either manually or automatically updated using
86
TimeReference (setting CurrentTime does NOT trigger progression). }
87
TGLCadencer = class(TComponent)
90
FSubscribedCadenceableComponents: TList;
92
FTimeMultiplier: Double;
93
lastTime, downTime, lastMultiplier: Double;
95
FSleepLength: Integer;
96
FMode: TGLCadencerMode;
97
FTimeReference: TGLCadencerTimeReference;
100
FMaxDeltaTime, FMinDeltaTime, FFixedDeltaTime: Double;
101
FOnProgress, FOnTotalProgress : TGLProgressEvent;
102
FProgressing: Integer;
103
procedure SetCurrentTime(const Value: Double);
107
procedure Notification(AComponent: TComponent; Operation: TOperation);
109
function StoreTimeMultiplier: Boolean;
110
procedure SetEnabled(const val: Boolean);
111
procedure SetScene(const val: TGLScene);
112
procedure SetMode(const val: TGLCadencerMode);
113
procedure SetTimeReference(const val: TGLCadencerTimeReference);
114
procedure SetTimeMultiplier(const val: Double);
115
{ Returns raw ref time (no multiplier, no offset) }
116
function GetRawReferenceTime: Double;
117
procedure RestartASAP;
118
procedure Loaded; override;
120
procedure OnIdleEvent(Sender: TObject; var Done: Boolean);
124
constructor Create(AOwner: TComponent); override;
125
destructor Destroy; override;
127
procedure Subscribe(aComponent: TGLCadenceAbleComponent);
128
procedure UnSubscribe(aComponent: TGLCadenceAbleComponent);
130
{ Allows to manually trigger a progression.
131
Time stuff is handled automatically.
132
If cadencer is disabled, this functions does nothing. }
135
{ Adjusts CurrentTime if necessary, then returns its value. }
136
function GetCurrenttime: Double;
138
{ Returns True if a "Progress" is underway.
139
Be aware that as long as IsBusy is True, the Cadencer may be
140
sending messages and progression calls to cadenceable components
142
function IsBusy: Boolean;
144
{ Reset the time parameters and returns to zero.}
147
{ Value soustracted to current time to obtain progression time. }
148
property OriginTime: Double read FOriginTime write FOriginTime;
149
{ Current time (manually or automatically set, see TimeReference). }
150
property CurrentTime: Double read FCurrentTime write SetCurrentTime;
154
{ The TGLScene that will be cadenced (progressed). }
155
property Scene: TGLScene read FScene write SetScene;
156
{ Enables/Disables cadencing.
157
Disabling won't cause a jump when restarting, it is working like
158
a play/pause (ie. may modify OriginTime to keep things smooth). }
159
property Enabled: Boolean read FEnabled write SetEnabled default True;
161
{ Defines how CurrentTime is updated.
162
See TGLCadencerTimeReference.
163
Dynamically changeing the TimeReference may cause a "jump". }
164
property TimeReference: TGLCadencerTimeReference read FTimeReference write
165
SetTimeReference default cmPerformanceCounter;
167
{ Multiplier applied to the time reference.
168
Zero isn't an allowed value, and be aware that if negative values
169
are accepted, they may not be supported by other GLScene objects.
170
Changing the TimeMultiplier will alter OriginTime. }
171
property TimeMultiplier: Double read FTimeMultiplier write SetTimeMultiplier
172
stored StoreTimeMultiplier;
174
{ Maximum value for deltaTime in progression events.
175
If null or negative, no max deltaTime is defined, otherwise, whenever
176
an event whose actual deltaTime would be superior to MaxDeltaTime
177
occurs, deltaTime is clamped to this max, and the extra time is hidden
178
by the cadencer (it isn't visible in CurrentTime either).
179
This option allows to limit progression rate in simulations where
180
high values would result in errors/random behaviour. }
181
property MaxDeltaTime: Double read FMaxDeltaTime write FMaxDeltaTime;
183
{ Minimum value for deltaTime in progression events.
184
If superior to zero, this value specifies the minimum time step
185
between two progression events.
186
This option allows to limit progression rate in simulations where
187
low values would result in errors/random behaviour. }
188
property MinDeltaTime: Double read FMinDeltaTime write FMinDeltaTime;
190
{ Fixed time-step value for progression events.
191
If superior to zero, progression steps will happen with that fixed
192
delta time. The progression remains time based, so zero to N events
193
may be fired depending on the actual deltaTime (if deltaTime is
194
inferior to FixedDeltaTime, no event will be fired, if it is superior
195
to two times FixedDeltaTime, two events will be fired, etc.).
196
This option allows to use fixed time steps in simulations (while the
197
animation and rendering itself may happen at a lower or higher
199
property FixedDeltaTime: Double read FFixedDeltaTime write FFixedDeltaTime;
201
{ Adjusts how progression events are triggered.
202
See TGLCadencerMode. }
203
property Mode: TGLCadencerMode read FMode write SetMode default cmASAP;
205
{ Allows relinquishing time to other threads/processes.
206
A "sleep" is issued BEFORE each progress if SleepLength>=0 (see
207
help for the "sleep" procedure in delphi for details). }
208
property SleepLength: Integer read FSleepLength write FSleepLength default
211
{ Happens AFTER scene was progressed. }
212
property OnProgress: TGLProgressEvent read FOnProgress write FOnProgress;
213
{ Happens AFTER all iterations with fixed delta time. }
214
property OnTotalProgress : TGLProgressEvent read FOnTotalProgress write FOnTotalProgress;
217
// TGLCustomCadencedComponent
219
{ Adds a property to connect/subscribe to a cadencer. }
220
TGLCustomCadencedComponent = class(TGLUpdateAbleComponent)
223
FCadencer: TGLCadencer;
227
procedure SetCadencer(const val: TGLCadencer);
229
property Cadencer: TGLCadencer read FCadencer write SetCadencer;
233
destructor Destroy; override;
235
procedure Notification(AComponent: TComponent; Operation: TOperation);
239
// TGLCadencedComponent
241
TGLCadencedComponent = class(TGLCustomCadencedComponent)
247
// ---------------------------------------------------------------------
248
// ---------------------------------------------------------------------
249
// ---------------------------------------------------------------------
251
// ---------------------------------------------------------------------
252
// ---------------------------------------------------------------------
253
// ---------------------------------------------------------------------
258
LM_GLTIMER = LM_INTERFACELAST + 326;
263
TASAPHandler = class;
266
TTimerThread = class(TThread)
268
FOwner: TASAPHandler;
271
procedure Execute; override;
273
constructor Create(CreateSuspended: Boolean); virtual;
281
FTimerThread: TThread;
282
FMutex: TCriticalSection;
287
procedure Cadence(var Msg: TLMessage); message LM_GLTIMER;
290
destructor Destroy; override;
295
vASAPCadencerList: TList;
296
vHandler: TASAPHandler;
297
vCounterFrequency: Int64;
299
// RegisterASAPCadencer
302
procedure RegisterASAPCadencer(aCadencer: TGLCadencer);
304
if aCadencer.Mode = cmASAP then
306
if not Assigned(vASAPCadencerList) then
307
vASAPCadencerList := TList.Create;
308
if vASAPCadencerList.IndexOf(aCadencer) < 0 then
310
vASAPCadencerList.Add(aCadencer);
311
if not Assigned(vHandler) then
312
vHandler := TASAPHandler.Create;
315
else if aCadencer.Mode = cmApplicationIdle then
316
Application.OnIdle := aCadencer.OnIdleEvent;
319
// UnRegisterASAPCadencer
322
procedure UnRegisterASAPCadencer(aCadencer: TGLCadencer);
326
if aCadencer.Mode = cmASAP then
328
if Assigned(vASAPCadencerList) then
330
i := vASAPCadencerList.IndexOf(aCadencer);
332
vASAPCadencerList[i] := nil;
335
else if aCadencer.Mode = cmApplicationIdle then
336
Application.OnIdle := nil;
340
constructor TTimerThread.Create(CreateSuspended: Boolean);
342
inherited Create(CreateSuspended);
348
procedure TTimerThread.Execute;
350
lastTick, nextTick, curTick, perfFreq: Int64;
352
QueryPerformanceFrequency(perfFreq);
353
QueryPerformanceCounter(lastTick);
354
nextTick := lastTick + (FInterval * perfFreq) div 1000;
355
while not Terminated do
357
FOwner.FMutex.Acquire;
358
FOwner.FMutex.Release;
359
while not Terminated do
361
QueryPerformanceCounter(lastTick);
362
if lastTick >= nextTick then
366
if not Terminated then
368
// if time elapsed run user-event
369
Synchronize(FOwner.TimerProc);
370
QueryPerformanceCounter(curTick);
371
nextTick := lastTick + (FInterval * perfFreq) div 1000;
372
if nextTick <= curTick then
374
// CPU too slow... delay to avoid monopolizing what's left
375
nextTick := curTick + (FInterval * perfFreq) div 1000;
382
// ------------------ TASAPHandler ------------------
388
constructor TASAPHandler.Create;
392
// create timer thread
393
FMutex := TCriticalSection.Create;
395
FTimerThread := TTimerThread.Create(False);
397
with TTimerThread(FTimerThread) do
400
FreeOnTerminate := False;
401
Priority := tpTimeCritical;
411
destructor TASAPHandler.Destroy;
414
FTimerThread.Terminate;
417
FTimerThread.WaitFor;
424
procedure TASAPHandler.TimerProc;
428
NewMsg.Msg := LM_GLTIMER;
432
procedure TASAPHandler.Cadence(var Msg: TLMessage);
437
if Assigned(vHandler) and Assigned(vASAPCadencerList)
438
and (vASAPCadencerList.Count <> 0) then
439
for i := vASAPCadencerList.Count - 1 downto 0 do
441
cad := TGLCadencer(vASAPCadencerList[i]);
442
if Assigned(cad) and (cad.Mode = cmASAP)
443
and cad.Enabled and (cad.FProgressing = 0) then
445
if Application.Terminated then
448
cad.Enabled := False;
456
Application.HandleException(Self);
457
// it faulted, stop it
458
cad.Enabled := False;
466
// ------------------ TGLCadencer ------------------
472
constructor TGLCadencer.Create(AOwner: TComponent);
474
inherited Create(AOwner);
475
FTimeReference := cmPerformanceCounter;
476
downTime := GetRawReferenceTime;
477
FOriginTime := downTime;
478
FTimeMultiplier := 1;
487
destructor TGLCadencer.Destroy;
489
Assert(FProgressing = 0);
490
UnRegisterASAPCadencer(Self);
491
FSubscribedCadenceableComponents.Free;
492
FSubscribedCadenceableComponents := nil;
499
procedure TGLCadencer.Subscribe(aComponent: TGLCadenceAbleComponent);
501
if not Assigned(FSubscribedCadenceableComponents) then
502
FSubscribedCadenceableComponents := TList.Create;
503
if FSubscribedCadenceableComponents.IndexOf(aComponent) < 0 then
505
FSubscribedCadenceableComponents.Add(aComponent);
506
aComponent.FreeNotification(Self);
513
procedure TGLCadencer.UnSubscribe(aComponent: TGLCadenceAbleComponent);
517
if Assigned(FSubscribedCadenceableComponents) then
519
i := FSubscribedCadenceableComponents.IndexOf(aComponent);
522
FSubscribedCadenceableComponents.Delete(i);
523
aComponent.RemoveFreeNotification(Self);
531
procedure TGLCadencer.Notification(AComponent: TComponent; Operation:
534
if Operation = opRemove then
536
if AComponent = FScene then
538
if Assigned(FSubscribedCadenceableComponents) then
539
FSubscribedCadenceableComponents.Remove(AComponent);
547
procedure TGLCadencer.Loaded;
556
procedure TGLCadencer.OnIdleEvent(Sender: TObject; var Done: Boolean);
565
procedure TGLCadencer.RestartASAP;
567
if not (csLoading in ComponentState) then
569
if (Mode in [cmASAP, cmApplicationIdle]) and (not (csDesigning in
571
and Assigned(FScene) and Enabled then
572
RegisterASAPCadencer(Self)
574
UnRegisterASAPCadencer(Self);
581
procedure TGLCadencer.SetEnabled(const val: Boolean);
583
if FEnabled <> val then
586
if not (csDesigning in ComponentState) then
589
FOriginTime := FOriginTime + GetRawReferenceTime - downTime
591
downTime := GetRawReferenceTime;
600
procedure TGLCadencer.SetScene(const val: TGLScene);
602
if FScene <> val then
604
if Assigned(FScene) then
605
FScene.RemoveFreeNotification(Self);
607
if Assigned(FScene) then
608
FScene.FreeNotification(Self);
616
procedure TGLCadencer.SetTimeMultiplier(const val: Double);
620
if val <> FTimeMultiplier then
624
lastMultiplier := FTimeMultiplier;
629
rawRef := GetRawReferenceTime;
630
if FTimeMultiplier = 0 then
633
// continuity of time:
634
// (rawRef-newOriginTime)*val = (rawRef-FOriginTime)*lastMultiplier
635
FOriginTime := rawRef - (rawRef - FOriginTime) * lastMultiplier / val;
639
// continuity of time:
640
// (rawRef-newOriginTime)*val = (rawRef-FOriginTime)*FTimeMultiplier
641
FOriginTime := rawRef - (rawRef - FOriginTime) * FTimeMultiplier / val;
644
FTimeMultiplier := val;
648
// StoreTimeMultiplier
651
function TGLCadencer.StoreTimeMultiplier: Boolean;
653
Result := (FTimeMultiplier <> 1);
659
procedure TGLCadencer.SetMode(const val: TGLCadencerMode);
663
if FMode <> cmManual then
664
UnRegisterASAPCadencer(Self);
673
procedure TGLCadencer.SetTimeReference(const val: TGLCadencerTimeReference);
676
FTimeReference := val;
682
procedure TGLCadencer.Progress;
684
deltaTime, newTime, totalDelta: Double;
685
fullTotalDelta, firstLastTime : Double;
689
// basic protection against infinite loops,
690
// shall never happen, unless there is a bug in user code
691
if FProgressing < 0 then
695
// avoid stalling everything else...
696
if SleepLength >= 0 then
698
// in manual mode, the user is supposed to make sure messages are handled
699
// in Idle mode, this processing is implicit
700
if Mode = cmASAP then
702
//Application.ProcessMessages; Eater of resources and time
703
if (not Assigned(vASAPCadencerList))
704
or (vASAPCadencerList.IndexOf(Self) < 0) then
712
// One of the processed messages might have disabled us
716
newTime := GetCurrenttime;
717
deltaTime := newTime - lastTime;
718
if (deltaTime >= MinDeltaTime) and (deltaTime >= FixedDeltaTime) then
720
if FMaxDeltaTime > 0 then
722
if deltaTime > FMaxDeltaTime then
724
FOriginTime := FOriginTime + (deltaTime - FMaxDeltaTime) /
726
deltaTime := FMaxDeltaTime;
727
newTime := lastTime + deltaTime;
730
totalDelta := deltaTime;
731
fullTotalDelta := totalDelta;
732
firstLastTime := lastTime;
733
if FixedDeltaTime > 0 then
734
deltaTime := FixedDeltaTime;
735
while totalDelta >= deltaTime do
737
lastTime := lastTime + deltaTime;
738
if Assigned(FScene) and (deltaTime <> 0) then
740
FProgressing := -FProgressing;
742
FScene.Progress(deltaTime, lastTime);
744
FProgressing := -FProgressing;
747
pt.deltaTime := deltaTime;
748
pt.newTime := lastTime;
750
while Assigned(FSubscribedCadenceableComponents) and
751
(i <= FSubscribedCadenceableComponents.Count - 1) do
753
TGLCadenceAbleComponent(FSubscribedCadenceableComponents[i]).DoProgress(pt);
756
if Assigned(FOnProgress) and (not (csDesigning in ComponentState))
758
FOnProgress(Self, deltaTime, newTime);
759
if deltaTime <= 0 then
761
totalDelta := totalDelta - deltaTime;
763
if Assigned(FOnTotalProgress)
764
and (not (csDesigning in ComponentState)) then
765
FOnTotalProgress(Self, fullTotalDelta, firstLastTime);
774
// GetRawReferenceTime
777
function TGLCadencer.GetRawReferenceTime: Double;
781
case FTimeReference of
782
cmRTC: // Real Time Clock
783
Result := Now * (3600 * 24);
784
cmPerformanceCounter:
785
begin // HiRes Performance Counter
786
QueryPerformanceCounter(counter);
787
Result := counter / vCounterFrequency;
789
cmExternal: // User defined value
790
Result := FCurrentTime;
800
function TGLCadencer.GetCurrenttime: Double;
802
Result := (GetRawReferenceTime - FOriginTime) * FTimeMultiplier;
803
FCurrentTime := Result;
809
function TGLCadencer.IsBusy: Boolean;
811
Result := (FProgressing <> 0);
817
procedure TGLCadencer.Reset;
820
downTime := GetRawReferenceTime;
821
FOriginTime := downTime;
827
procedure TGLCadencer.SetCurrentTime(const Value: Double);
829
LastTime := Value - (FCurrentTime - LastTime);
830
FOriginTime := FOriginTime + (FCurrentTime - Value);
831
FCurrentTime := Value;
835
// ------------------ TGLCustomCadencedComponent ------------------
841
destructor TGLCustomCadencedComponent.Destroy;
850
procedure TGLCustomCadencedComponent.Notification(AComponent: TComponent;
851
Operation: TOperation);
853
if (Operation = opRemove) and (AComponent = FCadencer) then
861
procedure TGLCustomCadencedComponent.SetCadencer(const val: TGLCadencer);
863
if FCadencer <> val then
865
if Assigned(FCadencer) then
866
FCadencer.UnSubscribe(Self);
868
if Assigned(FCadencer) then
869
FCadencer.Subscribe(Self);
873
// ---------------------------------------------------------------------
874
// ---------------------------------------------------------------------
875
// ---------------------------------------------------------------------
877
// ---------------------------------------------------------------------
878
// ---------------------------------------------------------------------
879
// ---------------------------------------------------------------------
881
RegisterClasses([TGLCadencer]);
884
// Preparation for high resolution timer
885
if not QueryPerformanceFrequency(vCounterFrequency) then
886
vCounterFrequency := 0;
889
FreeAndNil(vHandler);
890
FreeAndNil(vASAPCadencerList);