2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Implements a HDS Filter that generates HeightData tiles in a seperate thread.
7
This component is a TGLHeightDataSourceFilter, which uses a TGLHeightDataSourceThread,
8
to asyncronously search the HeightData cache for any queued tiles.
9
When found, it then prepares the queued tile in its own TGLHeightDataThread.
11
This allows the GUI to remain responsive, and prevents freezes when new tiles are
12
being prepared. Although this keeps the framerate up, it may cause holes in the
13
terrain to show, if the HeightDataThreads cant keep up with the TerrainRenderer's
14
requests for new tiles.
18
22/04/10 - Yar - Fixes after GLState revision
19
11/10/07 - DaStr - Added $I GLScene.inc, removed unused dependancy
20
25/03/07 - DaStr - Replaced Dialogs with GLCrossPlatform for Delphi5 compatibility
21
22/03/07 - LIN - Added UseDirtyTiles property - Specifies how dirty tiles are replaced.
22
22/03/07 - LIN - Data is now prepared in 3 stages:
23
BeforePreparingData : (Main Thread)
24
PreparingData : (Sub-Thread) (Main Thread if MaxThreads=0)
25
AfterPreparingData : (Main Thread)
26
05/03/07 - LIN - Added ThreadCount and WaitFor
27
12/02/07 - LIN - Creation
37
uses Classes, GLHeightData, GLCrossPlatform;
41
TIdleEvent = procedure(Sender:TGLAsyncHDS;TilesUpdated:boolean) of object;
42
TNewTilePreparedEvent = procedure (Sender : TGLAsyncHDS; heightData : TGLHeightData) of object; //a tile was updated (called INSIDE the sub-thread?)
46
{ TUseDirtyTiles determines if/how dirty tiles are displayed and when they are released.
50
When a tile is maked as dirty, a replacement is queued immediately.
51
However, the replacement cant be used until the HDThread has finished preparing it.
52
Dirty tiles can be deleted as soon as they are no longer used/displayed.
54
Possible states for a TUseDirtyTiles.
55
hdsNever : Dirty tiles get released immediately, leaving a hole in the terrain, until the replacement is hdsReady.
56
hdsUntilReplaced : Dirty tiles are used, until the HDThread has finished preparing the queued replacement.
57
hdsUntilAllReplaced : Waits until the HDSThread has finished preparing ALL queued tiles,
58
before allowing the renderer to switch over to the new set of tiles.
59
(This prevents a fading checkerbox effect.)
61
TUseDirtyTiles=(dtNever,dtUntilReplaced,dtUntilAllReplaced);
64
TGLAsyncHDS = class (TGLHeightDataSourceFilter)
67
FOnIdleEvent :TIdleEvent;
68
FOnNewTilePrepared : TNewTilePreparedEvent;
69
FUseDirtyTiles:TUseDirtyTiles;
70
FTilesUpdated:boolean;
74
//TilesUpdated:boolean;
76
constructor Create(AOwner: TComponent); override;
77
destructor Destroy; override;
78
procedure BeforePreparingData(heightData : TGLHeightData); override;
79
procedure StartPreparingData(heightData : TGLHeightData); override;
80
procedure ThreadIsIdle; override;
81
procedure NewTilePrepared(heightData:TGLHeightData);
82
function ThreadCount:integer;
83
procedure WaitFor(TimeOut:integer=2000);
84
//procedure NotifyChange(Sender : TObject); override;
85
function TilesUpdated:boolean; //Returns true if tiles have been updated since the flag was last reset
86
procedure TilesUpdatedFlagReset; //sets the TilesUpdatedFlag to false; (is ThreadSafe)
89
property OnIdle : TIdleEvent read FOnIdleEvent write FOnIdleEvent;
90
property OnNewTilePrepared : TNewTilePreparedEvent read FOnNewTilePrepared write FOnNewTilePrepared;
91
property UseDirtyTiles :TUseDirtyTiles read FUseDirtyTiles write FUseDirtyTiles;
92
property MaxThreads; //sets the maximum number of simultaineous threads that will prepare tiles.(>1 is rarely needed)
93
property Active; //set to false, to ignore new queued tiles.(Partially processed tiles will still be completed)
96
TGLAsyncHDThread = class(TGLHeightDataThread)
99
HDS : TGLHeightDataSource;
100
Procedure Execute; override;
105
// ------------------------------------------------------------------
106
// ------------------------------------------------------------------
107
// ------------------------------------------------------------------
109
// ------------------------------------------------------------------
110
// ------------------------------------------------------------------
111
// ------------------------------------------------------------------
116
// ------------------ TGLAsyncHDS ------------------
121
constructor TGLAsyncHDS.Create(AOwner: TComponent);
123
inherited Create(AOwner);
125
FUseDirtyTiles:=dtNever;
131
destructor TGLAsyncHDS.Destroy;
136
// BeforePreparingData
138
procedure TGLAsyncHDS.BeforePreparingData(heightData : TGLHeightData);
140
if FUseDirtyTiles=dtNever then begin
141
if heightData.OldVersion<>nil then begin
142
heightData.OldVersion.DontUse:=true;
143
heightData.DontUse:=false;
146
if assigned(HeightDataSource) then HeightDataSource.BeforePreparingData(heightData);
151
procedure TGLAsyncHDS.StartPreparingData(heightData : TGLHeightData);
152
var HDThread : TGLAsyncHDThread;
153
HDS:TGLHeightDataSource;
155
HDS:=HeightDataSource;
156
//---if there is no linked HDS then return an empty tile--
157
if not Assigned(HDS) then begin
158
heightData.DataState:=hdsNone;
161
if (Active=false) then exit;
163
//---If not using threads then prepare the HD tile directly--- (everything else freezes until done)
164
if MaxThreads=0 then begin
165
HDS.StartPreparingData(HeightData);
166
if heightData.DataState=hdsPreparing
167
then heightData.DataState:=hdsReady
168
else heightData.DataState:=hdsNone;
169
end else begin //--MaxThreads>0 : start the thread and go back to start the next one--
170
heightData.DataState:=hdsPreparing; //prevent other threads from preparing this HD.
171
HDThread:=TGLAsyncHDThread.Create(true);
172
HDThread.Owner:=self;
173
HDThread.HDS:=self.HeightDataSource;
174
HDThread.HeightData:=HeightData;
175
heightData.Thread:=HDThread;
176
HDThread.FreeOnTerminate:=false;
177
{$IFDEF GLS_DELPHI_2009_DOWN}
188
procedure TGLAsyncHDS.ThreadIsIdle;
193
//----------- dtUntilAllReplaced -------------
194
//Switch to the new version of ALL dirty tiles
195
lst:=self.Data.LockList;
197
if FUseDirtyTiles=dtUntilAllReplaced then begin
201
HD:=TGLHeightData(lst.Items[i]);
202
if (HD.DataState in [hdsReady,hdsNone])
203
and(Hd.DontUse)and(HD.OldVersion<>nil) then begin
205
HD.OldVersion.DontUse:=true;
209
end;//Until All Replaced
210
if Assigned(FOnIdleEvent) then FOnIdleEvent(Self,FTilesUpdated);
212
self.Data.UnlockList;
214
//--------------------------------------------
217
//OnNewTilePrepared event
219
procedure TGLAsyncHDS.NewTilePrepared(heightData:TGLHeightData);
222
if assigned(HeightDataSource) then HeightDataSource.AfterPreparingData(HeightData);
223
with self.Data.LockList do begin
226
//--------------- dtUntilReplaced -------------
227
//Tell terrain renderer to display the new tile
228
if (FUseDirtyTiles=dtUntilReplaced)and(HD.DontUse)and(HD.OldVersion<>nil) then begin
229
HD.DontUse:=false; //No longer ignore the new tile
230
HD.OldVersion.DontUse:=true; //Start ignoring the old tile
232
//---------------------------------------------
233
if HD.DontUse=false then FTilesUpdated:=true;
234
if Assigned(FOnNewTilePrepared) then FOnNewTilePrepared(Self,HeightData); //OnNewTilePrepared Event
236
self.Data.UnlockList;
242
// Count the active threads
244
function TGLAsyncHDS.ThreadCount:integer;
249
lst:=self.Data.LockList;
251
while(i<lst.Count)and(TdCtr<self.MaxThreads) do begin
252
HD:=TGLHeightData(lst.Items[i]);
253
if HD.Thread<>nil then Inc(TdCtr);
256
self.Data.UnlockList;
261
// Wait for all running threads to finish.
262
// Should only be called after setting Active to false,
263
// to prevent new threads from starting.
264
procedure TGLAsyncHDS.WaitFor(TimeOut:Integer=2000);
265
var OutTime:TDateTime;
267
Assert(self.active=false);
268
OutTime:=now+TimeOut;
269
While ((now<OutTime)and(ThreadCount>0)) do begin
272
Assert(ThreadCount=0);
276
procedure TGLAsyncHDS.NotifyChange(Sender : TObject);
282
// This function prevents the user from trying to write directly to this variable.
283
// FTilesUpdated if NOT threadsafe and should only be reset with TilesUpdatedFlagReset.
284
function TGLAsyncHDS.TilesUpdated:boolean;
286
result:=FTilesUpdated;
289
// Set the TilesUpdatedFlag to false. (is Threadsafe)
290
procedure TGLAsyncHDS.TilesUpdatedFlagReset;
292
if not assigned(self) then exit; //prevents AV on Application termination.
293
with Data.LockList do try
294
FTilesUpdated:=False;
295
finally Data.UnlockList; end;
298
//-------------------HD Thread----------------
299
Procedure TGLAsyncHDThread.Execute;
301
HDS.StartPreparingData(HeightData);
302
HeightData.Thread:=nil;
306
Procedure TGLAsyncHDThread.Sync;
308
Owner.NewTilePrepared(heightData);
309
if heightData.DataState=hdsPreparing then heightData.DataState:=hdsReady;
312
//--------------------------------------------
314
// ------------------------------------------------------------------
315
// ------------------------------------------------------------------
316
// ------------------------------------------------------------------
318
// ------------------------------------------------------------------
319
// ------------------------------------------------------------------
320
// ------------------------------------------------------------------
322
// class registrations
323
RegisterClass(TGLAsyncHDS);