PageRenderTime 27ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Core/ThumbnailManager.cs

https://bitbucket.org/tuldok89/openpdn
C# | 373 lines | 273 code | 63 blank | 37 comment | 28 complexity | 6a17f8f19d8228744643a666d9f59f11 MD5 | raw file
  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using System.Linq;
  10. using PaintDotNet.Base;
  11. using PaintDotNet.SystemLayer;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.ComponentModel;
  15. using System.Threading;
  16. namespace PaintDotNet
  17. {
  18. using ThumbnailReadyArgs = EventArgs<Pair<IThumbnailProvider, Surface>>;
  19. using ThumbnailReadyHandler = EventHandler<EventArgs<Pair<IThumbnailProvider, Surface>>>;
  20. using ThumbnailStackItem = Triple<IThumbnailProvider, EventHandler<EventArgs<Pair<IThumbnailProvider, Surface>>>, int>;
  21. using ThumbnailReadyEventDetails = Triple<EventHandler<EventArgs<Pair<IThumbnailProvider, Surface>>>, object, EventArgs<Pair<IThumbnailProvider, Surface>>>;
  22. // TODO: Add calls to VerifyNotDispose() for the next release where we can get enough testing for it
  23. public sealed class ThumbnailManager
  24. : IDisposable
  25. {
  26. private int _updateLatency = 67;
  27. public bool IsDisposed { get; private set; }
  28. public int UpdateLatency
  29. {
  30. get
  31. {
  32. return _updateLatency;
  33. }
  34. set
  35. {
  36. _updateLatency = value;
  37. }
  38. }
  39. private readonly Stack<ThumbnailStackItem> _renderQueue;
  40. private readonly ISynchronizeInvoke _syncContext;
  41. private Thread _renderThread;
  42. private volatile bool _quitRenderThread;
  43. private readonly object _updateLock;
  44. /*
  45. private void VerifyNotDisposed()
  46. {
  47. if (IsDisposed)
  48. {
  49. throw new ObjectDisposedException("ThumbnailManager");
  50. }
  51. }
  52. * */
  53. // This event is non-signaled during the period of time between when the rendering thread has popped
  54. // an item from the renderQueue, and when it finishes holding a reference to it (i.e. either it
  55. // finishes rendering the thumbnail, or it decides not to). At all other times, this event is signaled.
  56. private ManualResetEvent _renderingInactive;
  57. private List<ThumbnailReadyEventDetails> _thumbnailReadyInvokeList =
  58. new List<ThumbnailReadyEventDetails>();
  59. private void DrainThumbnailReadyInvokeList()
  60. {
  61. List<ThumbnailReadyEventDetails> invokeListCopy;
  62. lock (_thumbnailReadyInvokeList)
  63. {
  64. invokeListCopy = _thumbnailReadyInvokeList;
  65. _thumbnailReadyInvokeList = new List<ThumbnailReadyEventDetails>();
  66. }
  67. foreach (ThumbnailReadyEventDetails invokeMe in invokeListCopy)
  68. {
  69. invokeMe.First.Invoke(invokeMe.Second, invokeMe.Third);
  70. }
  71. }
  72. private void OnThumbnailReady(IThumbnailProvider dw, ThumbnailReadyHandler callback, Surface thumb)
  73. {
  74. Pair<IThumbnailProvider, Surface> data = Pair.Create(dw, thumb);
  75. var e = new ThumbnailReadyArgs(data);
  76. lock (_thumbnailReadyInvokeList)
  77. {
  78. _thumbnailReadyInvokeList.Add(new ThumbnailReadyEventDetails(callback, this, e));
  79. }
  80. try
  81. {
  82. _syncContext.BeginInvoke(new Procedure(DrainThumbnailReadyInvokeList), null);
  83. }
  84. catch (ObjectDisposedException)
  85. {
  86. // Ignore this error
  87. }
  88. catch (InvalidOperationException)
  89. {
  90. // If syncContext was destroyed, then ignore
  91. }
  92. }
  93. public ThumbnailManager(ISynchronizeInvoke syncContext)
  94. {
  95. _syncContext = syncContext;
  96. _updateLock = new object();
  97. _quitRenderThread = false;
  98. _renderQueue = new Stack<ThumbnailStackItem>();
  99. _renderingInactive = new ManualResetEvent(true);
  100. _renderThread = new Thread(RenderThread);
  101. _renderThread.Start();
  102. }
  103. ~ThumbnailManager()
  104. {
  105. Dispose(false);
  106. }
  107. public void Dispose()
  108. {
  109. Dispose(true);
  110. GC.SuppressFinalize(this);
  111. }
  112. private void Dispose(bool disposing)
  113. {
  114. IsDisposed = true;
  115. if (!disposing) return;
  116. _quitRenderThread = true;
  117. lock (_updateLock)
  118. {
  119. Monitor.Pulse(_updateLock);
  120. }
  121. if (_renderThread != null)
  122. {
  123. _renderThread.Join();
  124. _renderThread = null;
  125. }
  126. if (_renderingInactive == null) return;
  127. _renderingInactive.Close();
  128. _renderingInactive = null;
  129. }
  130. public void DrainQueue()
  131. {
  132. int oldLatency = _updateLatency;
  133. _updateLatency = 0;
  134. int count = 1;
  135. while (count > 0)
  136. {
  137. lock (_updateLock)
  138. {
  139. count = _renderQueue.Count;
  140. }
  141. _renderingInactive.WaitOne();
  142. }
  143. _updateLatency = oldLatency;
  144. DrainThumbnailReadyInvokeList();
  145. }
  146. public void RemoveFromQueue(IThumbnailProvider nukeMe)
  147. {
  148. lock (_updateLock)
  149. {
  150. ThumbnailStackItem[] tsiArray = _renderQueue.ToArray();
  151. var tsiAccumulate = tsiArray.Where(t => t.First != nukeMe).ToList();
  152. _renderQueue.Clear();
  153. foreach (ThumbnailStackItem t in tsiAccumulate)
  154. {
  155. _renderQueue.Push(t);
  156. }
  157. }
  158. }
  159. public void ClearQueue()
  160. {
  161. int oldUpdateLatency = _updateLatency;
  162. _updateLatency = 0;
  163. lock (_updateLock)
  164. {
  165. _renderQueue.Clear();
  166. }
  167. _renderingInactive.WaitOne();
  168. _updateLatency = oldUpdateLatency;
  169. }
  170. public void QueueThumbnailUpdate(IThumbnailProvider updateMe, int thumbSideLength, ThumbnailReadyHandler callback)
  171. {
  172. if (thumbSideLength < 1)
  173. {
  174. throw new ArgumentOutOfRangeException("thumbSideLength", "must be greater than or equal to 1");
  175. }
  176. lock (_updateLock)
  177. {
  178. bool doIt = false;
  179. var addMe = new ThumbnailStackItem(updateMe, callback, thumbSideLength);
  180. if (_renderQueue.Count == 0)
  181. {
  182. doIt = true;
  183. }
  184. else
  185. {
  186. ThumbnailStackItem top = _renderQueue.Peek();
  187. if (addMe != top)
  188. {
  189. doIt = true;
  190. }
  191. }
  192. // Only add this item to the queue if the item is not already at the top of the queue
  193. if (doIt)
  194. {
  195. _renderQueue.Push(addMe);
  196. }
  197. Monitor.Pulse(_updateLock);
  198. }
  199. }
  200. private void RenderThread()
  201. {
  202. try
  203. {
  204. RenderThreadImpl();
  205. }
  206. finally
  207. {
  208. _renderingInactive.Set();
  209. }
  210. }
  211. private void RenderThreadImpl()
  212. {
  213. while (true)
  214. {
  215. ThumbnailStackItem renderMe;
  216. // Wait for either a new item to render, or a signal to quit
  217. lock (_updateLock)
  218. {
  219. if (_quitRenderThread)
  220. {
  221. return;
  222. }
  223. while (_renderQueue.Count == 0)
  224. {
  225. Monitor.Wait(_updateLock);
  226. if (_quitRenderThread)
  227. {
  228. return;
  229. }
  230. }
  231. _renderingInactive.Reset();
  232. renderMe = _renderQueue.Pop();
  233. }
  234. // Sleep for a short while. Our main goal is to ensure that the
  235. // item is not re-queued for updating very soon after we start
  236. // rendering it.
  237. Thread.Sleep(_updateLatency);
  238. bool doRender = true;
  239. // While we were asleep, ensure that this same item has not been
  240. // re-added to the render queue. If it has, we will skip rendering
  241. // it. This covers the scenario where an item is updated many
  242. // times in rapid succession, in which case we want to wait until
  243. // the item has settled down before spending any CPU time rendering
  244. // its thumbnail.
  245. lock (_updateLock)
  246. {
  247. if (_quitRenderThread)
  248. {
  249. return;
  250. }
  251. if (_renderQueue.Count > 0)
  252. {
  253. if (renderMe == _renderQueue.Peek())
  254. {
  255. doRender = false;
  256. }
  257. }
  258. }
  259. if (doRender)
  260. {
  261. try
  262. {
  263. Surface thumb;
  264. using (new ThreadBackground(ThreadBackgroundFlags.All))
  265. {
  266. thumb = renderMe.First.RenderThumbnail(renderMe.Third);
  267. }
  268. // If this same item has already been re-queued for an update, then throw
  269. // away what we just rendered. Otherwise we may get flickering as the
  270. // item was being updated while we were rendering the preview.
  271. bool discard = false;
  272. lock (_updateLock)
  273. {
  274. if (_quitRenderThread)
  275. {
  276. thumb.Dispose();
  277. thumb = null;
  278. return;
  279. }
  280. if (_renderQueue.Count > 0)
  281. {
  282. if (renderMe == _renderQueue.Peek())
  283. {
  284. discard = true;
  285. thumb.Dispose();
  286. thumb = null;
  287. }
  288. }
  289. }
  290. if (!discard)
  291. {
  292. OnThumbnailReady(renderMe.First, renderMe.Second, thumb);
  293. }
  294. }
  295. catch (Exception ex)
  296. {
  297. try
  298. {
  299. Tracing.Ping("Exception in RenderThread while calling CreateThumbnail: " + ex.ToString());
  300. }
  301. catch (Exception)
  302. {
  303. }
  304. }
  305. }
  306. _renderingInactive.Set();
  307. }
  308. }
  309. }
  310. }