GXS.AsyncHDS.pas 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. // /
  2. // The graphics engine GLXEngine. The unit of GXScene for Delphi
  3. //
  4. unit GXS.AsyncHDS;
  5. (*
  6. Implements a HDS Filter that generates HeightData tiles in a seperate thread.
  7. This component is a TgxHeightDataSourceFilter, which uses a TgxHeightDataSourceThread,
  8. to asyncronously search the HeightData cache for any queued tiles.
  9. When found, it then prepares the queued tile in its own TgxHeightDataThread.
  10. This allows the GUI to remain responsive, and prevents freezes when new tiles are
  11. being prepared. Although this keeps the framerate up, it may cause holes in the
  12. terrain to show, if the HeightDataThreads cant keep up with the TerrainRenderer's
  13. requests for new tiles.
  14. *)
  15. interface
  16. {$I Stage.Defines.inc}
  17. uses
  18. System.Classes,
  19. System.SysUtils,
  20. GXS.HeightData;
  21. type
  22. TgxAsyncHDS = class;
  23. TIdleEvent = procedure(Sender: TgxAsyncHDS; TilesUpdated: boolean) of object;
  24. TNewTilePreparedEvent = procedure(Sender: TgxAsyncHDS;
  25. HeightData: TgxHeightData) of object; // a tile was updated (called INSIDE the sub-thread?)
  26. (* Determines if/how dirty tiles are displayed and when they are released.
  27. When a tile is maked as dirty, a replacement is queued immediately.
  28. However, the replacement cant be used until the HDThread has finished preparing it.
  29. Dirty tiles can be deleted as soon as they are no longer used/displayed.
  30. Possible states for a TUseDirtyTiles.
  31. hdsNever : Dirty tiles get released immediately, leaving a hole in the terrain, until the replacement is hdsReady.
  32. hdsUntilReplaced : Dirty tiles are used, until the HDThread has finished preparing the queued replacement.
  33. hdsUntilAllReplaced : Waits until the HDSThread has finished preparing ALL queued tiles,
  34. before allowing the renderer to switch over to the new set of tiles.
  35. (This prevents a fading checkerbox effect.) *)
  36. TUseDirtyTiles=(dtNever,dtUntilReplaced,dtUntilAllReplaced);
  37. TgxAsyncHDS = class(TgxHeightDataSourceFilter)
  38. private
  39. FOnIdleEvent: TIdleEvent;
  40. FOnNewTilePrepared: TNewTilePreparedEvent;
  41. FUseDirtyTiles: TUseDirtyTiles;
  42. FTilesUpdated: boolean;
  43. public
  44. // TilesUpdated:boolean;
  45. constructor Create(AOwner: TComponent); override;
  46. destructor Destroy; override;
  47. procedure BeforePreparingData(HeightData: TgxHeightData); override;
  48. procedure StartPreparingData(HeightData: TgxHeightData); override;
  49. procedure ThreadIsIdle; override;
  50. procedure NewTilePrepared(HeightData: TgxHeightData);
  51. function ThreadCount: integer;
  52. (* Wait for all running threads to finish.
  53. Should only be called after setting Active to false,
  54. to prevent new threads from starting. *)
  55. procedure WaitFor(TimeOut: integer = 2000);
  56. // procedure NotifyChange(Sender : TObject); override;
  57. (* This function prevents the user from trying to write directly to this variable.
  58. FTilesUpdated if NOT threadsafe and should only be reset with TilesUpdatedFlagReset. *)
  59. function TilesUpdated: boolean; // Returns true if tiles have been updated since the flag was last reset
  60. procedure TilesUpdatedFlagReset; // sets the TilesUpdatedFlag to false; (is ThreadSafe)
  61. published
  62. property OnIdle: TIdleEvent read FOnIdleEvent write FOnIdleEvent;
  63. property OnNewTilePrepared: TNewTilePreparedEvent read FOnNewTilePrepared write FOnNewTilePrepared;
  64. property UseDirtyTiles: TUseDirtyTiles read FUseDirtyTiles write FUseDirtyTiles;
  65. property MaxThreads; // sets the maximum number of simultaineous threads that will prepare tiles.(>1 is rarely needed)
  66. property Active; // set to false, to ignore new queued tiles.(Partially processed tiles will still be completed)
  67. end;
  68. TgxAsyncHDThread = class(TgxHeightDataThread)
  69. public
  70. Owner: TgxAsyncHDS;
  71. HDS: TgxHeightDataSource;
  72. Procedure Execute; override;
  73. Procedure Sync;
  74. end;
  75. // ------------------------------------------------------------------
  76. implementation
  77. // ------------------------------------------------------------------
  78. // ------------------
  79. // ------------------ TgxAsyncHDS ------------------
  80. // ------------------
  81. constructor TgxAsyncHDS.Create(AOwner: TComponent);
  82. begin
  83. inherited Create(AOwner);
  84. MaxThreads := 1;
  85. FUseDirtyTiles := dtNever;
  86. FTilesUpdated := true;
  87. end;
  88. destructor TgxAsyncHDS.Destroy;
  89. begin
  90. inherited Destroy;
  91. end;
  92. procedure TgxAsyncHDS.BeforePreparingData(HeightData: TgxHeightData);
  93. begin
  94. if FUseDirtyTiles = dtNever then
  95. begin
  96. if HeightData.OldVersion <> nil then
  97. begin
  98. HeightData.OldVersion.DontUse := true;
  99. HeightData.DontUse := false;
  100. end;
  101. end;
  102. if assigned(HeightDataSource) then
  103. HeightDataSource.BeforePreparingData(HeightData);
  104. end;
  105. procedure TgxAsyncHDS.StartPreparingData(HeightData: TgxHeightData);
  106. var
  107. HDThread: TgxAsyncHDThread;
  108. HDS: TgxHeightDataSource;
  109. begin
  110. HDS := HeightDataSource;
  111. // ---if there is no linked HDS then return an empty tile--
  112. if not assigned(HDS) then
  113. begin
  114. HeightData.DataState := hdsNone;
  115. exit;
  116. end;
  117. if (Active = false) then
  118. exit;
  119. // ---If not using threads then prepare the HD tile directly--- (everything else freezes until done)
  120. if MaxThreads = 0 then
  121. begin
  122. HDS.StartPreparingData(HeightData);
  123. if HeightData.DataState = hdsPreparing then
  124. HeightData.DataState := hdsReady
  125. else
  126. HeightData.DataState := hdsNone;
  127. end
  128. else
  129. begin // --MaxThreads>0 : start the thread and go back to start the next one--
  130. HeightData.DataState := hdsPreparing; // prevent other threads from preparing this HD.
  131. HDThread := TgxAsyncHDThread.Create(true);
  132. HDThread.Owner := self;
  133. HDThread.HDS := self.HeightDataSource;
  134. HDThread.HeightData := HeightData;
  135. HeightData.Thread := HDThread;
  136. HDThread.FreeOnTerminate := false;
  137. HDThread.Start;
  138. end;
  139. end;
  140. procedure TgxAsyncHDS.ThreadIsIdle;
  141. var
  142. i: integer;
  143. lst: TList;
  144. HD: TgxHeightData;
  145. begin
  146. // ----------- dtUntilAllReplaced -------------
  147. // Switch to the new version of ALL dirty tiles
  148. lst := self.Data.LockList;
  149. try
  150. if FUseDirtyTiles = dtUntilAllReplaced then
  151. begin
  152. i := lst.Count;
  153. while (i > 0) do
  154. begin
  155. dec(i);
  156. HD := TgxHeightData(lst.Items[i]);
  157. if (HD.DataState in [hdsReady, hdsNone]) and (HD.DontUse) and (HD.OldVersion <> nil) then
  158. begin
  159. HD.DontUse := false;
  160. HD.OldVersion.DontUse := true;
  161. FTilesUpdated := true;
  162. end;
  163. end;
  164. end; // Until All Replaced
  165. if assigned(FOnIdleEvent) then
  166. FOnIdleEvent(self, FTilesUpdated);
  167. finally
  168. self.Data.UnlockList;
  169. end;
  170. // --------------------------------------------
  171. end;
  172. procedure TgxAsyncHDS.NewTilePrepared(HeightData: TgxHeightData);
  173. var
  174. HD: TgxHeightData;
  175. begin
  176. if assigned(HeightDataSource) then
  177. HeightDataSource.AfterPreparingData(HeightData);
  178. with self.Data.LockList do
  179. begin
  180. try
  181. HD := HeightData;
  182. // --------------- dtUntilReplaced -------------
  183. // Tell terrain renderer to display the new tile
  184. if (FUseDirtyTiles = dtUntilReplaced) and (HD.DontUse) and (HD.OldVersion <> nil) then
  185. begin
  186. HD.DontUse := false; // No longer ignore the new tile
  187. HD.OldVersion.DontUse := true; // Start ignoring the old tile
  188. end;
  189. // ---------------------------------------------
  190. if HD.DontUse = false then
  191. FTilesUpdated := true;
  192. if assigned(FOnNewTilePrepared) then
  193. FOnNewTilePrepared(self, HeightData); // OnNewTilePrepared Event
  194. finally
  195. self.Data.UnlockList;
  196. end;
  197. end;
  198. end;
  199. function TgxAsyncHDS.ThreadCount: integer;
  200. var
  201. lst: TList;
  202. i, TdCtr: integer;
  203. HD: TgxHeightData;
  204. begin
  205. lst := self.Data.LockList;
  206. i := 0;
  207. TdCtr := 0;
  208. while (i < lst.Count) and (TdCtr < self.MaxThreads) do
  209. begin
  210. HD := TgxHeightData(lst.Items[i]);
  211. if HD.Thread <> nil then
  212. Inc(TdCtr);
  213. Inc(i);
  214. end;
  215. self.Data.UnlockList;
  216. result := TdCtr;
  217. end;
  218. procedure TgxAsyncHDS.WaitFor(TimeOut: integer = 2000);
  219. var
  220. OutTime: TDateTime;
  221. begin
  222. Assert(self.Active = false);
  223. OutTime := now + TimeOut;
  224. While ((now < OutTime) and (ThreadCount > 0)) do
  225. begin
  226. sleep(0);
  227. end;
  228. Assert(ThreadCount = 0);
  229. end;
  230. {
  231. procedure TgxAsyncHDS.NotifyChange(Sender : TObject);
  232. begin
  233. TilesChanged:=true;
  234. end;
  235. }
  236. function TgxAsyncHDS.TilesUpdated: boolean;
  237. begin
  238. result := FTilesUpdated;
  239. end;
  240. // Set the TilesUpdatedFlag to false. (is Threadsafe)
  241. procedure TgxAsyncHDS.TilesUpdatedFlagReset;
  242. begin
  243. if not assigned(self) then
  244. exit; // prevents AV on Application termination.
  245. with Data.LockList do
  246. try
  247. FTilesUpdated := false;
  248. finally
  249. Data.UnlockList;
  250. end;
  251. end;
  252. // -------------------HD Thread----------------
  253. Procedure TgxAsyncHDThread.Execute;
  254. Begin
  255. HDS.StartPreparingData(HeightData);
  256. HeightData.Thread := nil;
  257. Synchronize(Sync);
  258. end;
  259. Procedure TgxAsyncHDThread.Sync;
  260. begin
  261. Owner.NewTilePrepared(HeightData);
  262. if HeightData.DataState = hdsPreparing then
  263. HeightData.DataState := hdsReady;
  264. end;
  265. // ------------------------------------------------------------------
  266. initialization
  267. // ------------------------------------------------------------------
  268. RegisterClass(TgxAsyncHDS);
  269. end.