Fix hang when examining content containing subtitles.
[dcpomatic.git] / src / lib / signaller.h
1 /*
2     Copyright (C) 2015 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #ifndef DCPOMATIC_SIGNALLER_H
21 #define DCPOMATIC_SIGNALLER_H
22
23 #include "signal_manager.h"
24 #include <boost/thread/mutex.hpp>
25
26 class WrapperBase
27 {
28 public:
29         WrapperBase ()
30                 : _valid (true)
31                 , _finished (false)
32         {}
33
34         virtual ~WrapperBase () {}
35
36         /* Can be called from any thread */
37         void invalidate ()
38         {
39                 boost::mutex::scoped_lock lm (_mutex);
40                 _valid = false;
41         }
42
43         bool finished () const {
44                 boost::mutex::scoped_lock lm (_mutex, boost::try_to_lock);
45                 if (!lm) {
46                         /* It's possible that emission of this
47                            wrapper's signal causes another signal to
48                            be emitted, which causes finished() on this
49                            wrapper to be called (by Signaller::emit).
50                            In this case, just say that the wrapper is
51                            not yet finished.
52                         */
53                         return false;
54                 }
55                 return _finished;
56         }
57
58 protected:
59         /* Protect _valid and _finished */
60         mutable boost::mutex _mutex;
61         bool _valid;
62         bool _finished;
63 };
64
65 /** Helper class to manage lifetime of signals, specifically to address
66  *  the problem where an object containing a signal is deleted before
67  *  its signal is emitted.
68  */
69 template <class T>
70 class Wrapper : public WrapperBase
71 {
72 public:
73         Wrapper (T signal)
74                 : _signal (signal)
75         {
76
77         }
78
79         /* Called by the UI thread only */
80         void signal ()
81         {
82                 boost::mutex::scoped_lock lm (_mutex);
83                 if (_valid) {
84                         _signal ();
85                 }
86                 _finished = true;
87         }
88
89 private:
90         T _signal;
91 };
92
93 /** Parent for any class which needs to raise cross-thread signals (from non-UI
94  *  to UI).  Subclasses should call, e.g. emit (boost::bind (boost::ref (MySignal), foo, bar));
95  */
96 class Signaller
97 {
98 public:
99         /* Can be called from any thread */
100         virtual ~Signaller () {
101                 boost::mutex::scoped_lock lm (_signaller_mutex);
102                 for (std::list<WrapperBase*>::iterator i = _wrappers.begin(); i != _wrappers.end(); ++i) {
103                         (*i)->invalidate ();
104                 }
105         }
106
107         /* Can be called from any thread */
108         template <class T>
109         void emit (T signal)
110         {
111                 Wrapper<T>* w = new Wrapper<T> (signal);
112                 if (signal_manager) {
113                         signal_manager->emit (boost::bind (&Wrapper<T>::signal, w));
114                 }
115
116                 boost::mutex::scoped_lock lm (_signaller_mutex);
117
118                 /* Clean up finished Wrappers */
119                 std::list<WrapperBase*>::iterator i = _wrappers.begin ();
120                 while (i != _wrappers.end ()) {
121                         std::list<WrapperBase*>::iterator tmp = i;
122                         ++tmp;
123                         if ((*i)->finished ()) {
124                                 delete *i;
125                                 _wrappers.erase (i);
126                         }
127                         i = tmp;
128                 }
129
130                 /* Add the new one */
131                 _wrappers.push_back (w);
132         }
133
134 private:
135         /* Protect _wrappers */
136         boost::mutex _signaller_mutex;
137         std::list<WrapperBase*> _wrappers;
138 };
139
140 #endif