summaryrefslogtreecommitdiff
path: root/src/libaudcore/mainloop.cc
blob: 359e5616cb1d3530646e8010f66f5e627bccfd3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/*
 * mainloop.cc
 * Copyright 2014-2017 John Lindgren
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions, and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions, and the following disclaimer in the documentation
 *    provided with the distribution.
 *
 * This software is provided "as is" and without any warranty, express or
 * implied. In no event shall the authors be liable for any damages arising from
 * the use of this software.
 */

#include "mainloop.h"

#include <glib.h>

#ifdef USE_QT
#include <QCoreApplication>
#endif

#include "internal.h"
#include "multihash.h"
#include "runtime.h"

struct QueuedFuncParams
{
    QueuedFunc::Func func;
    void * data;
    int interval_ms;
    bool repeat;
};

// QueuedFunc itself is a tiny handle/identifier object; most of the related
// code lives in various "helper" objects.  This is the base class inherited
// by all the different types of helpers.

struct QueuedFuncHelper
{
    QueuedFunc * queued;
    QueuedFuncParams params;

    // Creates an appropriate helper subclass for the given parameters and
    // schedules it to run the QueuedFunc
    static QueuedFuncHelper * create(QueuedFunc * queued,
                                     const QueuedFuncParams & params);

    // Callback which runs the QueuedFunc, if still active
    void run();

    // Cancels any scheduled run of the QueuedFunc and marks the helper for
    // deletion (it may not deleted immediately, but should not be accessed
    // again after calling this function)
    virtual void cancel() = 0;

    virtual ~QueuedFuncHelper() {}

protected:
    QueuedFuncHelper(QueuedFunc * queued, const QueuedFuncParams & params)
        : queued(queued), params(params)
    {
    }
};

// The following hash table implements a thread-safe "registry" of active
// QueuedFuncs.  This allows a callback executing in the main thread to safely
// look up the information it needs, without accessing the QueuedFunc directly
// and risking use-after-free.

struct QueuedFuncNode : public MultiHash::Node
{
    // Creates a helper to be registered in the hash
    QueuedFuncNode(QueuedFunc * queued, const QueuedFuncParams & params)
        : helper(QueuedFuncHelper::create(queued, params))
    {
    }

    // Cancels a helper when it is unregistered from the hash
    ~QueuedFuncNode() { helper->cancel(); }

    // Replaces the registration of one helper with another
    void reset(QueuedFunc * queued, const QueuedFuncParams & params)
    {
        helper->cancel();
        helper = QueuedFuncHelper::create(queued, params);
    }

    // Checks whether a helper is still registered
    bool is_current(QueuedFuncHelper * test_helper) const
    {
        return test_helper == helper;
    }

    // Hash comparison function
    bool match(const QueuedFunc * queued) const
    {
        return queued == helper->queued;
    }

private:
    QueuedFuncHelper * helper;
};

static MultiHash_T<QueuedFuncNode, QueuedFunc> func_table;
static bool in_lockdown = false;

// Helper logic common between GLib and Qt

// "run" logic executed within the hash table lock
struct RunCheck
{
    QueuedFuncHelper * helper;
    bool okay_to_run;

    // Called if the QueuedFunc is not registered in the hash
    // This indicates a "stale" event that should not be processed
    QueuedFuncNode * add(const QueuedFunc *) { return nullptr; }

    bool found(const QueuedFuncNode * node)
    {
        // Check whether a different helper has been registered
        // This also indicates a "stale" event that should not be processed
        if (!node->is_current(helper))
            return false;

        // We are still registered and good to go
        okay_to_run = true;

        // Leave a periodic timer registered
        if (helper->params.repeat)
            return false;

        // Unregister and cancel a one-time callback
        delete node;
        return true;
    }
};

void QueuedFuncHelper::run()
{
    // Check whether it's okay to run.  The actual check is performed within
    // the MultiHash lock to eliminate race conditions.
    RunCheck r = {this, false};
    func_table.lookup(queued, ptr_hash(queued), r);

    if (r.okay_to_run)
        params.func(params.data);
}

// GLib implementation -- simple wrapper around g_timeout_add_full()

class HelperGLib : public QueuedFuncHelper
{
public:
    HelperGLib(QueuedFunc * queued, const QueuedFuncParams & params)
        : QueuedFuncHelper(queued, params)
    {
        glib_source =
            g_timeout_add_full(G_PRIORITY_HIGH, params.interval_ms, run_cb,
                               this, aud::delete_obj<HelperGLib>);
    }

    void cancel()
    {
        // GLib will delete the helper after we return to the main loop
        g_source_remove(glib_source);
    }

private:
    static gboolean run_cb(void * me)
    {
        (static_cast<HelperGLib *>(me))->run();
        return G_SOURCE_CONTINUE;
    }

    int glib_source = 0;
};

#ifdef USE_QT

// Qt implementation -- rather more complicated

// One-time callbacks are implemented through custom events posted to a "router"
// object.  In this case, the QueuedFuncHelper class is the QEvent itself.  The
// router object is created in, and associated with, the main thread, thus
// events are executed in the main thread regardless of their origin.  Events
// can be queued even before the QApplication has been created.  Note that
// QEvents cannot be cancelled once posted; therefore it's necessary to check
// on our side that the QueuedFunc is still active when the event is received.

class EventRouter : public QObject
{
protected:
    void customEvent(QEvent * event);
};

static EventRouter router;

class HelperQEvent : public QueuedFuncHelper, public QEvent
{
public:
    HelperQEvent(QueuedFunc * queued, const QueuedFuncParams & params)
        : QueuedFuncHelper(queued, params), QEvent(User)
    {
        QCoreApplication::postEvent(&router, this, Qt::HighEventPriority);
    }

    void cancel() {} // Qt will delete the event after it fires
};

void EventRouter::customEvent(QEvent * event)
{
    dynamic_cast<HelperQEvent *>(event)->run();
}

// Periodic callbacks are implemented through QObject's timer capability.  In
// this case, the QueuedFuncHelper is a QObject that is re-associated with the
// main thread immediately after creation.  The timer is started in a roundabout
// fashion due to the fact that QObject::startTimer() does not work until the
// QApplication has been created.  To work around this, a single QEvent is first
// posted.  When this initial event is executed (in the main thread), we know
// that the QApplication is up.  We then start the timer and run our own
// callback from the QTimerEvents we receive.

class HelperQTimer : public QueuedFuncHelper, public QObject
{
public:
    HelperQTimer(QueuedFunc * queued, const QueuedFuncParams & params)
        : QueuedFuncHelper(queued, params)
    {
        moveToThread(router.thread()); // main thread
        QCoreApplication::postEvent(this, new QEvent(QEvent::User),
                                    Qt::HighEventPriority);
    }

    void cancel() { deleteLater(); }

protected:
    void customEvent(QEvent *) { startTimer(params.interval_ms); }
    void timerEvent(QTimerEvent *) { run(); }
};

#endif // USE_QT

// creates the appropriate helper subclass
QueuedFuncHelper * QueuedFuncHelper::create(QueuedFunc * queued,
                                            const QueuedFuncParams & params)
{
#ifdef USE_QT
    if (aud_get_mainloop_type() == MainloopType::Qt)
    {
        if (params.interval_ms > 0)
            return new HelperQTimer(queued, params);
        else
            return new HelperQEvent(queued, params);
    }
#endif

    return new HelperGLib(queued, params);
}

// "start" logic executed within the hash table lock
struct Starter
{
    QueuedFunc * queued;
    const QueuedFuncParams & params;

    // register a new helper for this QueuedFunc
    QueuedFuncNode * add(const QueuedFunc *)
    {
        return in_lockdown ? nullptr : new QueuedFuncNode(queued, params);
    }

    // cancel the old helper and register a replacement
    bool found(QueuedFuncNode * node)
    {
        node->reset(queued, params);
        return false;
    }
};

// common entry point used by all queue() and start() variants
static void start_func(QueuedFunc * queued, const QueuedFuncParams & params)
{
    Starter s = {queued, params};
    func_table.lookup(queued, ptr_hash(queued), s);
}

EXPORT void QueuedFunc::queue(Func func, void * data)
{
    start_func(this, {func, data, 0, false});
    _running = false;
}

EXPORT void QueuedFunc::queue(int delay_ms, Func func, void * data)
{
    g_return_if_fail(delay_ms >= 0);
    start_func(this, {func, data, delay_ms, false});
    _running = false;
}

EXPORT void QueuedFunc::start(int interval_ms, Func func, void * data)
{
    g_return_if_fail(interval_ms > 0);
    start_func(this, {func, data, interval_ms, true});
    _running = true;
}

// "stop" logic executed within the hash table lock
struct Stopper
{
    // not registered, do nothing
    QueuedFuncNode * add(const QueuedFunc *) { return nullptr; }

    // unregister and cancel helper
    bool found(QueuedFuncNode * node)
    {
        delete node;
        return true;
    }
};

EXPORT void QueuedFunc::stop()
{
    Stopper s;
    func_table.lookup(this, ptr_hash(this), s);
    _running = false;
}

// unregister a pending callback at shutdown
static bool cleanup_node(QueuedFuncNode * node)
{
    delete node;
    return true;
}
// inhibit all future callbacks at shutdown
static void enter_lockdown() { in_lockdown = true; }

EXPORT void QueuedFunc::inhibit_all()
{
    func_table.iterate(cleanup_node, enter_lockdown);
}

// main loop implementation follows

static GMainLoop * glib_mainloop;

EXPORT void mainloop_run()
{
#ifdef USE_QT
    if (aud_get_mainloop_type() == MainloopType::Qt)
    {
        static char app_name[] = "audacious";
        static int dummy_argc = 1;
        static char * dummy_argv[] = {app_name, nullptr};

        QCoreApplication(dummy_argc, dummy_argv).exec();
    }
    else
#endif
    {
        glib_mainloop = g_main_loop_new(nullptr, true);
        g_main_loop_run(glib_mainloop);
        g_main_loop_unref(glib_mainloop);
        glib_mainloop = nullptr;
    }
}

EXPORT void mainloop_quit()
{
#ifdef USE_QT
    if (aud_get_mainloop_type() == MainloopType::Qt)
    {
        qApp->quit();
    }
    else
#endif
    {
        g_main_loop_quit(glib_mainloop);
    }
}