/src/Core/ThumbnailManager.cs
C# | 373 lines | 273 code | 63 blank | 37 comment | 28 complexity | 6a17f8f19d8228744643a666d9f59f11 MD5 | raw file
- /////////////////////////////////////////////////////////////////////////////////
- // Paint.NET //
- // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
- // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
- // See src/Resources/Files/License.txt for full licensing and attribution //
- // details. //
- // . //
- /////////////////////////////////////////////////////////////////////////////////
-
- using System.Linq;
- using PaintDotNet.Base;
- using PaintDotNet.SystemLayer;
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Threading;
-
- namespace PaintDotNet
- {
- using ThumbnailReadyArgs = EventArgs<Pair<IThumbnailProvider, Surface>>;
- using ThumbnailReadyHandler = EventHandler<EventArgs<Pair<IThumbnailProvider, Surface>>>;
- using ThumbnailStackItem = Triple<IThumbnailProvider, EventHandler<EventArgs<Pair<IThumbnailProvider, Surface>>>, int>;
- using ThumbnailReadyEventDetails = Triple<EventHandler<EventArgs<Pair<IThumbnailProvider, Surface>>>, object, EventArgs<Pair<IThumbnailProvider, Surface>>>;
-
- // TODO: Add calls to VerifyNotDispose() for the next release where we can get enough testing for it
-
- public sealed class ThumbnailManager
- : IDisposable
- {
- private int _updateLatency = 67;
-
- public bool IsDisposed { get; private set; }
-
- public int UpdateLatency
- {
- get
- {
- return _updateLatency;
- }
-
- set
- {
- _updateLatency = value;
- }
- }
-
- private readonly Stack<ThumbnailStackItem> _renderQueue;
- private readonly ISynchronizeInvoke _syncContext;
- private Thread _renderThread;
- private volatile bool _quitRenderThread;
- private readonly object _updateLock;
-
- /*
- private void VerifyNotDisposed()
- {
- if (IsDisposed)
- {
- throw new ObjectDisposedException("ThumbnailManager");
- }
- }
- * */
-
- // This event is non-signaled during the period of time between when the rendering thread has popped
- // an item from the renderQueue, and when it finishes holding a reference to it (i.e. either it
- // finishes rendering the thumbnail, or it decides not to). At all other times, this event is signaled.
- private ManualResetEvent _renderingInactive;
-
- private List<ThumbnailReadyEventDetails> _thumbnailReadyInvokeList =
- new List<ThumbnailReadyEventDetails>();
-
- private void DrainThumbnailReadyInvokeList()
- {
- List<ThumbnailReadyEventDetails> invokeListCopy;
-
- lock (_thumbnailReadyInvokeList)
- {
- invokeListCopy = _thumbnailReadyInvokeList;
- _thumbnailReadyInvokeList = new List<ThumbnailReadyEventDetails>();
- }
-
- foreach (ThumbnailReadyEventDetails invokeMe in invokeListCopy)
- {
- invokeMe.First.Invoke(invokeMe.Second, invokeMe.Third);
- }
- }
-
- private void OnThumbnailReady(IThumbnailProvider dw, ThumbnailReadyHandler callback, Surface thumb)
- {
- Pair<IThumbnailProvider, Surface> data = Pair.Create(dw, thumb);
- var e = new ThumbnailReadyArgs(data);
-
- lock (_thumbnailReadyInvokeList)
- {
- _thumbnailReadyInvokeList.Add(new ThumbnailReadyEventDetails(callback, this, e));
- }
-
- try
- {
- _syncContext.BeginInvoke(new Procedure(DrainThumbnailReadyInvokeList), null);
- }
-
- catch (ObjectDisposedException)
- {
- // Ignore this error
- }
-
- catch (InvalidOperationException)
- {
- // If syncContext was destroyed, then ignore
- }
- }
-
- public ThumbnailManager(ISynchronizeInvoke syncContext)
- {
- _syncContext = syncContext;
- _updateLock = new object();
- _quitRenderThread = false;
- _renderQueue = new Stack<ThumbnailStackItem>();
- _renderingInactive = new ManualResetEvent(true);
- _renderThread = new Thread(RenderThread);
- _renderThread.Start();
- }
-
- ~ThumbnailManager()
- {
- Dispose(false);
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- private void Dispose(bool disposing)
- {
- IsDisposed = true;
-
- if (!disposing) return;
- _quitRenderThread = true;
-
- lock (_updateLock)
- {
- Monitor.Pulse(_updateLock);
- }
-
- if (_renderThread != null)
- {
- _renderThread.Join();
- _renderThread = null;
- }
-
- if (_renderingInactive == null) return;
- _renderingInactive.Close();
- _renderingInactive = null;
- }
-
- public void DrainQueue()
- {
- int oldLatency = _updateLatency;
- _updateLatency = 0;
-
- int count = 1;
- while (count > 0)
- {
- lock (_updateLock)
- {
- count = _renderQueue.Count;
- }
-
- _renderingInactive.WaitOne();
- }
-
- _updateLatency = oldLatency;
- DrainThumbnailReadyInvokeList();
- }
-
- public void RemoveFromQueue(IThumbnailProvider nukeMe)
- {
- lock (_updateLock)
- {
- ThumbnailStackItem[] tsiArray = _renderQueue.ToArray();
- var tsiAccumulate = tsiArray.Where(t => t.First != nukeMe).ToList();
-
- _renderQueue.Clear();
-
- foreach (ThumbnailStackItem t in tsiAccumulate)
- {
- _renderQueue.Push(t);
- }
- }
- }
-
- public void ClearQueue()
- {
- int oldUpdateLatency = _updateLatency;
- _updateLatency = 0;
-
- lock (_updateLock)
- {
- _renderQueue.Clear();
- }
-
- _renderingInactive.WaitOne();
- _updateLatency = oldUpdateLatency;
- }
-
- public void QueueThumbnailUpdate(IThumbnailProvider updateMe, int thumbSideLength, ThumbnailReadyHandler callback)
- {
- if (thumbSideLength < 1)
- {
- throw new ArgumentOutOfRangeException("thumbSideLength", "must be greater than or equal to 1");
- }
-
- lock (_updateLock)
- {
- bool doIt = false;
- var addMe = new ThumbnailStackItem(updateMe, callback, thumbSideLength);
-
- if (_renderQueue.Count == 0)
- {
- doIt = true;
- }
- else
- {
- ThumbnailStackItem top = _renderQueue.Peek();
-
- if (addMe != top)
- {
- doIt = true;
- }
- }
-
- // Only add this item to the queue if the item is not already at the top of the queue
- if (doIt)
- {
- _renderQueue.Push(addMe);
- }
-
- Monitor.Pulse(_updateLock);
- }
- }
-
- private void RenderThread()
- {
- try
- {
- RenderThreadImpl();
- }
-
- finally
- {
- _renderingInactive.Set();
- }
- }
-
- private void RenderThreadImpl()
- {
- while (true)
- {
- ThumbnailStackItem renderMe;
-
- // Wait for either a new item to render, or a signal to quit
- lock (_updateLock)
- {
- if (_quitRenderThread)
- {
- return;
- }
-
- while (_renderQueue.Count == 0)
- {
- Monitor.Wait(_updateLock);
-
- if (_quitRenderThread)
- {
- return;
- }
- }
-
- _renderingInactive.Reset();
- renderMe = _renderQueue.Pop();
- }
-
- // Sleep for a short while. Our main goal is to ensure that the
- // item is not re-queued for updating very soon after we start
- // rendering it.
- Thread.Sleep(_updateLatency);
-
- bool doRender = true;
-
- // While we were asleep, ensure that this same item has not been
- // re-added to the render queue. If it has, we will skip rendering
- // it. This covers the scenario where an item is updated many
- // times in rapid succession, in which case we want to wait until
- // the item has settled down before spending any CPU time rendering
- // its thumbnail.
- lock (_updateLock)
- {
- if (_quitRenderThread)
- {
- return;
- }
-
- if (_renderQueue.Count > 0)
- {
- if (renderMe == _renderQueue.Peek())
- {
- doRender = false;
- }
- }
- }
-
- if (doRender)
- {
- try
- {
- Surface thumb;
-
- using (new ThreadBackground(ThreadBackgroundFlags.All))
- {
- thumb = renderMe.First.RenderThumbnail(renderMe.Third);
- }
-
- // If this same item has already been re-queued for an update, then throw
- // away what we just rendered. Otherwise we may get flickering as the
- // item was being updated while we were rendering the preview.
- bool discard = false;
-
- lock (_updateLock)
- {
- if (_quitRenderThread)
- {
- thumb.Dispose();
- thumb = null;
- return;
- }
-
- if (_renderQueue.Count > 0)
- {
- if (renderMe == _renderQueue.Peek())
- {
- discard = true;
- thumb.Dispose();
- thumb = null;
- }
- }
- }
-
- if (!discard)
- {
- OnThumbnailReady(renderMe.First, renderMe.Second, thumb);
- }
- }
-
- catch (Exception ex)
- {
- try
- {
- Tracing.Ping("Exception in RenderThread while calling CreateThumbnail: " + ex.ToString());
- }
-
- catch (Exception)
- {
- }
- }
- }
-
- _renderingInactive.Set();
- }
- }
- }
- }