]> git.sesse.net Git - vlc/blob - src/control/event_async.c
Fix libvlc_video_set_scale()
[vlc] / src / control / event_async.c
1 /*****************************************************************************
2  * event.c: New libvlc event control API
3  *****************************************************************************
4  * Copyright (C) 2007 the VideoLAN team
5  * $Id $
6  *
7  * Authors: Filippo Carone <filippo@carone.org>
8  *          Pierre d'Herbemont <pdherbemont # videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <assert.h>
30
31 #include <vlc/libvlc.h>
32
33 #include "libvlc_internal.h"
34 #include "event_internal.h"
35
36 struct queue_elmt {
37     libvlc_event_listener_t listener;
38     libvlc_event_t event;
39     struct queue_elmt * next;
40 };
41
42 struct libvlc_event_async_queue {
43     struct queue_elmt *first_elmt, *last_elmt;
44     vlc_mutex_t lock;
45     vlc_cond_t signal;
46     vlc_thread_t thread;
47     bool is_idle;
48     vlc_cond_t signal_idle;
49     vlc_threadvar_t is_asynch_dispatch_thread_var;
50 #ifndef NDEBUG
51     long count;
52 #endif
53 };
54
55 /*
56  * Utilities
57  */
58
59 static void*  event_async_loop(void * arg);
60
61 static inline struct libvlc_event_async_queue * queue(libvlc_event_manager_t * p_em)
62 {
63     return p_em->async_event_queue;
64 }
65
66 static inline bool is_queue_initialized(libvlc_event_manager_t * p_em)
67 {
68     return queue(p_em) != NULL;
69 }
70
71 static inline bool current_thread_is_asynch_thread(libvlc_event_manager_t * p_em)
72 {
73     return vlc_threadvar_get(queue(p_em)->is_asynch_dispatch_thread_var);
74 }
75
76 /* Lock must be held */
77 static void push(libvlc_event_manager_t * p_em,
78                  libvlc_event_listener_t * listener, libvlc_event_t * event)
79 {
80     struct queue_elmt * elmt = malloc(sizeof(struct queue_elmt));
81     elmt->listener = *listener;
82     elmt->event = *event;
83     elmt->next = NULL;
84
85     /* Append to the end of the queue */
86     if(!queue(p_em)->first_elmt)
87         queue(p_em)->first_elmt = elmt;
88     else
89         queue(p_em)->last_elmt->next = elmt;
90     queue(p_em)->last_elmt = elmt;
91
92 #ifndef NDEBUG
93     enum { MaxQueueSize = 300000 };
94     if(queue(p_em)->count++ > MaxQueueSize)
95     {
96         fprintf(stderr, "Warning: libvlc event overflow.\n");
97         abort();
98     }
99 #endif
100 }
101
102 static inline void queue_lock(libvlc_event_manager_t * p_em)
103 {
104     vlc_mutex_lock(&queue(p_em)->lock);
105 }
106
107 static inline void queue_unlock(libvlc_event_manager_t * p_em)
108 {
109     vlc_mutex_unlock(&queue(p_em)->lock);
110 }
111
112 /* Lock must be held */
113 static bool pop(libvlc_event_manager_t * p_em,
114                 libvlc_event_listener_t * listener, libvlc_event_t * event)
115 {
116     if(!queue(p_em)->first_elmt)
117         return false; /* No first_elmt */
118
119     struct queue_elmt * elmt = queue(p_em)->first_elmt;
120     *listener = elmt->listener;
121     *event = elmt->event;
122
123     queue(p_em)->first_elmt = elmt->next;
124     if( !elmt->next ) queue(p_em)->last_elmt=NULL;
125
126 #ifndef NDEBUG
127     queue(p_em)->count--;
128 #endif
129
130     free(elmt);
131     return true;
132 }
133
134 /* Lock must be held */
135 static void pop_listener(libvlc_event_manager_t * p_em, libvlc_event_listener_t * listener)
136 {
137     struct queue_elmt * iter = queue(p_em)->first_elmt;
138     struct queue_elmt * prev = NULL;
139     while (iter) {
140         if(listeners_are_equal(&iter->listener, listener))
141         {
142             struct queue_elmt * to_delete = iter;
143             if(!prev)
144                 queue(p_em)->first_elmt = to_delete->next;
145             else
146                 prev->next = to_delete->next;
147             iter = to_delete->next;
148             free(to_delete);
149 #ifndef NDEBUG
150             queue(p_em)->count--;
151 #endif
152         }
153         else {
154             prev = iter;
155             iter = iter->next;
156         }
157     }
158     queue(p_em)->last_elmt=prev;
159 }
160
161 /**************************************************************************
162  *       libvlc_event_async_fini (internal) :
163  *
164  * Destroy what might have been created by.
165  **************************************************************************/
166 void
167 libvlc_event_async_fini(libvlc_event_manager_t * p_em)
168 {
169     if(!is_queue_initialized(p_em)) return;
170
171     if(current_thread_is_asynch_thread(p_em))
172     {
173         fprintf(stderr, "*** Error: releasing the last reference of the observed object from its callback thread is not (yet!) supported\n");
174         abort();
175     }
176
177     vlc_thread_t thread = queue(p_em)->thread;
178     if(thread)
179     {
180         vlc_cancel(thread);
181         vlc_join(thread, NULL);
182     }
183
184     vlc_mutex_destroy(&queue(p_em)->lock);
185     vlc_cond_destroy(&queue(p_em)->signal);
186     vlc_cond_destroy(&queue(p_em)->signal_idle);
187     vlc_threadvar_delete(&queue(p_em)->is_asynch_dispatch_thread_var);
188
189     struct queue_elmt * iter = queue(p_em)->first_elmt;
190     while (iter) {
191         struct queue_elmt * elemt_to_delete = iter;
192         iter = iter->next;
193         free(elemt_to_delete);
194     }
195
196     free(queue(p_em));
197 }
198
199 /**************************************************************************
200  *       libvlc_event_async_init (private) :
201  *
202  * Destroy what might have been created by.
203  **************************************************************************/
204 static void
205 libvlc_event_async_init(libvlc_event_manager_t * p_em)
206 {
207     p_em->async_event_queue = calloc(1, sizeof(struct libvlc_event_async_queue));
208
209     int error = vlc_threadvar_create(&queue(p_em)->is_asynch_dispatch_thread_var, NULL);
210     assert(!error);
211
212     vlc_mutex_init(&queue(p_em)->lock);
213     vlc_cond_init(&queue(p_em)->signal);
214     vlc_cond_init(&queue(p_em)->signal_idle);
215
216     error = vlc_clone (&queue(p_em)->thread, event_async_loop, p_em, VLC_THREAD_PRIORITY_LOW);
217     if(error)
218     {
219         free(p_em->async_event_queue);
220         p_em->async_event_queue = NULL;
221         return;
222     }
223
224 }
225
226 /**************************************************************************
227  *       libvlc_event_async_ensure_listener_removal (internal) :
228  *
229  * Make sure no more message will be issued to the listener.
230  **************************************************************************/
231 void
232 libvlc_event_async_ensure_listener_removal(libvlc_event_manager_t * p_em, libvlc_event_listener_t * listener)
233 {
234     if(!is_queue_initialized(p_em)) return;
235
236     queue_lock(p_em);
237     pop_listener(p_em, listener);
238
239     // Wait for the asynch_loop to have processed all events.
240     if(!current_thread_is_asynch_thread(p_em))
241     {
242         while(!queue(p_em)->is_idle)
243             vlc_cond_wait(&queue(p_em)->signal_idle, &queue(p_em)->lock);
244     }
245     queue_unlock(p_em);
246 }
247
248 /**************************************************************************
249  *       libvlc_event_async_dispatch (internal) :
250  *
251  * Send an event in an asynchronous way.
252  **************************************************************************/
253 void
254 libvlc_event_async_dispatch(libvlc_event_manager_t * p_em, libvlc_event_listener_t * listener, libvlc_event_t * event)
255 {
256     // We do a lazy init here, to prevent constructing the thread when not needed.
257     vlc_mutex_lock(&p_em->object_lock);
258     if(!queue(p_em))
259         libvlc_event_async_init(p_em);
260     vlc_mutex_unlock(&p_em->object_lock);
261
262     queue_lock(p_em);
263     push(p_em, listener, event);
264     vlc_cond_signal(&queue(p_em)->signal);
265     queue_unlock(p_em);
266 }
267
268 /**************************************************************************
269  *       event_async_loop (private) :
270  *
271  * Send queued events.
272  **************************************************************************/
273 static void * event_async_loop(void * arg)
274 {
275     libvlc_event_manager_t * p_em = arg;
276     libvlc_event_listener_t listener;
277     libvlc_event_t event;
278
279     vlc_threadvar_set(queue(p_em)->is_asynch_dispatch_thread_var, p_em);
280
281     queue_lock(p_em);
282     while (true) {
283         int has_listener = pop(p_em, &listener, &event);
284
285         if (has_listener)
286         {
287             queue_unlock(p_em);
288             listener.pf_callback(&event, listener.p_user_data); // This might edit the queue
289             queue_lock(p_em);
290         }
291         else
292         {
293             queue(p_em)->is_idle = true;
294
295             mutex_cleanup_push(&queue(p_em)->lock);
296             vlc_cond_broadcast(&queue(p_em)->signal_idle); // We'll be idle
297             vlc_cond_wait(&queue(p_em)->signal, &queue(p_em)->lock);
298             vlc_cleanup_pop();
299
300             queue(p_em)->is_idle = false;
301         }
302     }
303     queue_unlock(p_em);
304     return NULL;
305 }