event_async.c 8.78 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*****************************************************************************
 * event.c: New libvlc event control API
 *****************************************************************************
 * Copyright (C) 2007 the VideoLAN team
 * $Id $
 *
 * Authors: Filippo Carone <filippo@carone.org>
 *          Pierre d'Herbemont <pdherbemont # videolan.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
20 21 22
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24
 *****************************************************************************/

25 26 27 28
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

29 30
#include <assert.h>

31 32 33 34 35 36 37 38 39 40 41 42
#include <vlc/libvlc.h>

#include "libvlc_internal.h"
#include "event_internal.h"

struct queue_elmt {
    libvlc_event_listener_t listener;
    libvlc_event_t event;
    struct queue_elmt * next;
};

struct libvlc_event_async_queue {
43
    struct queue_elmt *first_elmt, *last_elmt;
44 45 46
    vlc_mutex_t lock;
    vlc_cond_t signal;
    vlc_thread_t thread;
47 48 49
    bool is_idle;
    vlc_cond_t signal_idle;
    vlc_threadvar_t is_asynch_dispatch_thread_var;
50 51 52
#ifndef NDEBUG
    long count;
#endif
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
};

/*
 * Utilities
 */

static void*  event_async_loop(void * arg);

static inline struct libvlc_event_async_queue * queue(libvlc_event_manager_t * p_em)
{
    return p_em->async_event_queue;
}

static inline bool is_queue_initialized(libvlc_event_manager_t * p_em)
{
    return queue(p_em) != NULL;
}

71 72
static inline bool current_thread_is_asynch_thread(libvlc_event_manager_t * p_em)
{
73 74
    return vlc_threadvar_get(queue(p_em)->is_asynch_dispatch_thread_var)
            != NULL;
75 76
}

77
/* Lock must be held */
78 79
static void push(libvlc_event_manager_t * p_em,
                 libvlc_event_listener_t * listener, libvlc_event_t * event)
80 81 82 83 84
{
    struct queue_elmt * elmt = malloc(sizeof(struct queue_elmt));
    elmt->listener = *listener;
    elmt->event = *event;
    elmt->next = NULL;
Rémi Duraffort's avatar
Rémi Duraffort committed
85

86
    /* Append to the end of the queue */
87 88 89 90 91
    if(!queue(p_em)->first_elmt)
        queue(p_em)->first_elmt = elmt;
    else
        queue(p_em)->last_elmt->next = elmt;
    queue(p_em)->last_elmt = elmt;
92 93

#ifndef NDEBUG
94 95 96 97 98
    enum { MaxQueueSize = 300000 };
    if(queue(p_em)->count++ > MaxQueueSize)
    {
        fprintf(stderr, "Warning: libvlc event overflow.\n");
        abort();
99
    }
100
#endif
101 102
}

103 104 105 106 107 108 109 110 111 112
static inline void queue_lock(libvlc_event_manager_t * p_em)
{
    vlc_mutex_lock(&queue(p_em)->lock);
}

static inline void queue_unlock(libvlc_event_manager_t * p_em)
{
    vlc_mutex_unlock(&queue(p_em)->lock);
}

113
/* Lock must be held */
114 115
static bool pop(libvlc_event_manager_t * p_em,
                libvlc_event_listener_t * listener, libvlc_event_t * event)
116
{
117 118 119 120 121 122
    if(!queue(p_em)->first_elmt)
        return false; /* No first_elmt */

    struct queue_elmt * elmt = queue(p_em)->first_elmt;
    *listener = elmt->listener;
    *event = elmt->event;
123

124 125 126 127 128 129
    queue(p_em)->first_elmt = elmt->next;
    if( !elmt->next ) queue(p_em)->last_elmt=NULL;

#ifndef NDEBUG
    queue(p_em)->count--;
#endif
Rémi Duraffort's avatar
Rémi Duraffort committed
130

131 132 133 134 135 136 137
    free(elmt);
    return true;
}

/* Lock must be held */
static void pop_listener(libvlc_event_manager_t * p_em, libvlc_event_listener_t * listener)
{
138
    struct queue_elmt * iter = queue(p_em)->first_elmt;
139 140 141 142
    struct queue_elmt * prev = NULL;
    while (iter) {
        if(listeners_are_equal(&iter->listener, listener))
        {
143
            struct queue_elmt * to_delete = iter;
144
            if(!prev)
145
                queue(p_em)->first_elmt = to_delete->next;
146
            else
147 148 149
                prev->next = to_delete->next;
            iter = to_delete->next;
            free(to_delete);
150 151 152
#ifndef NDEBUG
            queue(p_em)->count--;
#endif
153 154 155 156
        }
        else {
            prev = iter;
            iter = iter->next;
157 158
        }
    }
159
    queue(p_em)->last_elmt=prev;
160 161 162 163 164 165 166 167 168
}

/**************************************************************************
 *       libvlc_event_async_fini (internal) :
 *
 * Destroy what might have been created by.
 **************************************************************************/
void
libvlc_event_async_fini(libvlc_event_manager_t * p_em)
Rémi Duraffort's avatar
Rémi Duraffort committed
169
{
170
    if(!is_queue_initialized(p_em)) return;
171 172 173 174 175 176

    if(current_thread_is_asynch_thread(p_em))
    {
        fprintf(stderr, "*** Error: releasing the last reference of the observed object from its callback thread is not (yet!) supported\n");
        abort();
    }
Rémi Duraffort's avatar
Rémi Duraffort committed
177

178 179 180 181 182 183 184 185 186
    vlc_thread_t thread = queue(p_em)->thread;
    if(thread)
    {
        vlc_cancel(thread);
        vlc_join(thread, NULL);
    }

    vlc_mutex_destroy(&queue(p_em)->lock);
    vlc_cond_destroy(&queue(p_em)->signal);
187 188
    vlc_cond_destroy(&queue(p_em)->signal_idle);
    vlc_threadvar_delete(&queue(p_em)->is_asynch_dispatch_thread_var);
189

190
    struct queue_elmt * iter = queue(p_em)->first_elmt;
191 192 193 194 195
    while (iter) {
        struct queue_elmt * elemt_to_delete = iter;
        iter = iter->next;
        free(elemt_to_delete);
    }
Rémi Duraffort's avatar
Rémi Duraffort committed
196

197 198 199 200 201 202 203 204 205 206 207 208 209
    free(queue(p_em));
}

/**************************************************************************
 *       libvlc_event_async_init (private) :
 *
 * Destroy what might have been created by.
 **************************************************************************/
static void
libvlc_event_async_init(libvlc_event_manager_t * p_em)
{
    p_em->async_event_queue = calloc(1, sizeof(struct libvlc_event_async_queue));

210 211 212 213 214 215
    int error = vlc_threadvar_create(&queue(p_em)->is_asynch_dispatch_thread_var, NULL);
    assert(!error);

    vlc_mutex_init(&queue(p_em)->lock);
    vlc_cond_init(&queue(p_em)->signal);
    vlc_cond_init(&queue(p_em)->signal_idle);
Rémi Duraffort's avatar
Rémi Duraffort committed
216

217
    error = vlc_clone (&queue(p_em)->thread, event_async_loop, p_em, VLC_THREAD_PRIORITY_LOW);
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
    if(error)
    {
        free(p_em->async_event_queue);
        p_em->async_event_queue = NULL;
        return;
    }

}

/**************************************************************************
 *       libvlc_event_async_ensure_listener_removal (internal) :
 *
 * Make sure no more message will be issued to the listener.
 **************************************************************************/
void
libvlc_event_async_ensure_listener_removal(libvlc_event_manager_t * p_em, libvlc_event_listener_t * listener)
{
    if(!is_queue_initialized(p_em)) return;

237
    queue_lock(p_em);
238
    pop_listener(p_em, listener);
Rémi Duraffort's avatar
Rémi Duraffort committed
239

240
    // Wait for the asynch_loop to have processed all events.
241
    if(!current_thread_is_asynch_thread(p_em))
242
    {
243 244
        while(!queue(p_em)->is_idle)
            vlc_cond_wait(&queue(p_em)->signal_idle, &queue(p_em)->lock);
245
    }
246
    queue_unlock(p_em);
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
}

/**************************************************************************
 *       libvlc_event_async_dispatch (internal) :
 *
 * Send an event in an asynchronous way.
 **************************************************************************/
void
libvlc_event_async_dispatch(libvlc_event_manager_t * p_em, libvlc_event_listener_t * listener, libvlc_event_t * event)
{
    // We do a lazy init here, to prevent constructing the thread when not needed.
    vlc_mutex_lock(&p_em->object_lock);
    if(!queue(p_em))
        libvlc_event_async_init(p_em);
    vlc_mutex_unlock(&p_em->object_lock);

263
    queue_lock(p_em);
264 265
    push(p_em, listener, event);
    vlc_cond_signal(&queue(p_em)->signal);
266
    queue_unlock(p_em);
267 268 269 270 271 272 273 274 275 276 277 278 279
}

/**************************************************************************
 *       event_async_loop (private) :
 *
 * Send queued events.
 **************************************************************************/
static void * event_async_loop(void * arg)
{
    libvlc_event_manager_t * p_em = arg;
    libvlc_event_listener_t listener;
    libvlc_event_t event;

280
    vlc_threadvar_set(queue(p_em)->is_asynch_dispatch_thread_var, p_em);
281

282
    queue_lock(p_em);
283 284
    while (true) {
        int has_listener = pop(p_em, &listener, &event);
Pierre's avatar
Pierre committed
285

286
        if (has_listener)
287 288 289 290 291
        {
            queue_unlock(p_em);
            listener.pf_callback(&event, listener.p_user_data); // This might edit the queue
            queue_lock(p_em);
        }
292
        else
293
        {
294 295
            queue(p_em)->is_idle = true;

296
            mutex_cleanup_push(&queue(p_em)->lock);
297
            vlc_cond_broadcast(&queue(p_em)->signal_idle); // We'll be idle
298
            vlc_cond_wait(&queue(p_em)->signal, &queue(p_em)->lock);
299
            vlc_cleanup_pop();
Rémi Duraffort's avatar
Rémi Duraffort committed
300

301
            queue(p_em)->is_idle = false;
302
        }
303
    }
304
    queue_unlock(p_em);
305 306
    return NULL;
}