1 /*
2 * Clutter.
3 *
4 * An OpenGL based 'interactive canvas' library.
5 *
6 * Authored By Matthew Allum <mallum@openedhand.com>
7 *
8 * Copyright (C) 2006 OpenedHand
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library 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 GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22 *
23 *
24 *
25 * ClutterTimeoutPool: pool of timeout functions using the same slice of
26 * the GLib main loop
27 *
28 * Author: Emmanuele Bassi <ebassi@openedhand.com>
29 *
30 * Based on similar code by Tristan van Berkom
31 */
32
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36
37 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
38 #include "deprecated/clutter-main.h"
39
40 #include "clutter-timeout-pool.h"
41
42 #include "clutter-debug.h"
43 #include "clutter-timeout-interval.h"
44
45 typedef struct _ClutterTimeout ClutterTimeout;
46 typedef enum {
47 CLUTTER_TIMEOUT_NONE = 0,
48 CLUTTER_TIMEOUT_READY = 1 << 1
49 } ClutterTimeoutFlags;
50
51 struct _ClutterTimeout
52 {
53 guint id;
54 ClutterTimeoutFlags flags;
55 gint refcount;
56
57 ClutterTimeoutInterval interval;
58
59 GSourceFunc func;
60 gpointer data;
61 GDestroyNotify notify;
62 };
63
64 struct _ClutterTimeoutPool
65 {
66 GSource source;
67
68 guint next_id;
69
70 GList *timeouts;
71 GList *dispatched_timeouts;
72
73 gint ready;
74
75 guint id;
76 };
77
78 #define TIMEOUT_READY(timeout) (timeout->flags & CLUTTER_TIMEOUT_READY)
79
80 static gboolean clutter_timeout_pool_prepare (GSource *source,
81 gint *next_timeout);
82 static gboolean clutter_timeout_pool_check (GSource *source);
83 static gboolean clutter_timeout_pool_dispatch (GSource *source,
84 GSourceFunc callback,
85 gpointer data);
86 static void clutter_timeout_pool_finalize (GSource *source);
87
88 static GSourceFuncs clutter_timeout_pool_funcs =
89 {
90 clutter_timeout_pool_prepare,
91 clutter_timeout_pool_check,
92 clutter_timeout_pool_dispatch,
93 clutter_timeout_pool_finalize
94 };
95
96 static gint
clutter_timeout_sort(gconstpointer a,gconstpointer b)97 clutter_timeout_sort (gconstpointer a,
98 gconstpointer b)
99 {
100 const ClutterTimeout *t_a = a;
101 const ClutterTimeout *t_b = b;
102
103 /* Keep 'ready' timeouts at the front */
104 if (TIMEOUT_READY (t_a))
105 return -1;
106
107 if (TIMEOUT_READY (t_b))
108 return 1;
109
110 return _clutter_timeout_interval_compare_expiration (&t_a->interval,
111 &t_b->interval);
112 }
113
114 static gint
clutter_timeout_find_by_id(gconstpointer a,gconstpointer b)115 clutter_timeout_find_by_id (gconstpointer a,
116 gconstpointer b)
117 {
118 const ClutterTimeout *t_a = a;
119
120 return t_a->id == GPOINTER_TO_UINT (b) ? 0 : 1;
121 }
122
123 static ClutterTimeout *
clutter_timeout_new(guint fps)124 clutter_timeout_new (guint fps)
125 {
126 ClutterTimeout *timeout;
127
128 timeout = g_slice_new0 (ClutterTimeout);
129 _clutter_timeout_interval_init (&timeout->interval, fps);
130 timeout->flags = CLUTTER_TIMEOUT_NONE;
131 timeout->refcount = 1;
132
133 return timeout;
134 }
135
136 static gboolean
clutter_timeout_prepare(ClutterTimeoutPool * pool,ClutterTimeout * timeout,gint * next_timeout)137 clutter_timeout_prepare (ClutterTimeoutPool *pool,
138 ClutterTimeout *timeout,
139 gint *next_timeout)
140 {
141 GSource *source = (GSource *) pool;
142 gint64 now;
143
144 #if GLIB_CHECK_VERSION (2, 27, 3)
145 now = g_source_get_time (source) / 1000;
146 #else
147 {
148 GTimeVal source_time;
149 g_source_get_current_time (source, &source_time);
150 now = source_time.tv_sec * 1000 + source_time.tv_usec / 1000;
151 }
152 #endif
153
154 return _clutter_timeout_interval_prepare (now,
155 &timeout->interval,
156 next_timeout);
157 }
158
159 /* ref and unref are always called under the main Clutter lock, so there
160 * is not need for us to use g_atomic_int_* API.
161 */
162
163 static ClutterTimeout *
clutter_timeout_ref(ClutterTimeout * timeout)164 clutter_timeout_ref (ClutterTimeout *timeout)
165 {
166 g_return_val_if_fail (timeout != NULL, timeout);
167 g_return_val_if_fail (timeout->refcount > 0, timeout);
168
169 timeout->refcount += 1;
170
171 return timeout;
172 }
173
174 static void
clutter_timeout_unref(ClutterTimeout * timeout)175 clutter_timeout_unref (ClutterTimeout *timeout)
176 {
177 g_return_if_fail (timeout != NULL);
178 g_return_if_fail (timeout->refcount > 0);
179
180 timeout->refcount -= 1;
181
182 if (timeout->refcount == 0)
183 {
184 if (timeout->notify)
185 timeout->notify (timeout->data);
186
187 g_slice_free (ClutterTimeout, timeout);
188 }
189 }
190
191 static void
clutter_timeout_free(ClutterTimeout * timeout)192 clutter_timeout_free (ClutterTimeout *timeout)
193 {
194 if (G_LIKELY (timeout))
195 {
196 if (timeout->notify)
197 timeout->notify (timeout->data);
198
199 g_slice_free (ClutterTimeout, timeout);
200 }
201 }
202
203 static gboolean
clutter_timeout_pool_prepare(GSource * source,gint * next_timeout)204 clutter_timeout_pool_prepare (GSource *source,
205 gint *next_timeout)
206 {
207 ClutterTimeoutPool *pool = (ClutterTimeoutPool *) source;
208 GList *l = pool->timeouts;
209
210 /* the pool is ready if the first timeout is ready */
211 if (l && l->data)
212 {
213 ClutterTimeout *timeout = l->data;
214 return clutter_timeout_prepare (pool, timeout, next_timeout);
215 }
216 else
217 {
218 *next_timeout = -1;
219 return FALSE;
220 }
221 }
222
223 static gboolean
clutter_timeout_pool_check(GSource * source)224 clutter_timeout_pool_check (GSource *source)
225 {
226 ClutterTimeoutPool *pool = (ClutterTimeoutPool *) source;
227 GList *l;
228
229 clutter_threads_enter ();
230
231 for (l = pool->timeouts; l; l = l->next)
232 {
233 ClutterTimeout *timeout = l->data;
234
235 /* since the timeouts are sorted by expiration, as soon
236 * as we get a check returning FALSE we know that the
237 * following timeouts are not expiring, so we break as
238 * soon as possible
239 */
240 if (clutter_timeout_prepare (pool, timeout, NULL))
241 {
242 timeout->flags |= CLUTTER_TIMEOUT_READY;
243 pool->ready += 1;
244 }
245 else
246 break;
247 }
248
249 clutter_threads_leave ();
250
251 return (pool->ready > 0);
252 }
253
254 static gboolean
clutter_timeout_pool_dispatch(GSource * source,GSourceFunc func,gpointer data)255 clutter_timeout_pool_dispatch (GSource *source,
256 GSourceFunc func,
257 gpointer data)
258 {
259 ClutterTimeoutPool *pool = (ClutterTimeoutPool *) source;
260 GList *dispatched_timeouts;
261
262 /* the main loop might have predicted this, so we repeat the
263 * check for ready timeouts.
264 */
265 if (!pool->ready)
266 clutter_timeout_pool_check (source);
267
268 clutter_threads_enter ();
269
270 /* Iterate by moving the actual start of the list along so that it
271 * can cope with adds and removes while a timeout is being dispatched
272 */
273 while (pool->timeouts && pool->timeouts->data && pool->ready-- > 0)
274 {
275 ClutterTimeout *timeout = pool->timeouts->data;
276 GList *l;
277
278 /* One of the ready timeouts may have been removed during dispatch,
279 * in which case pool->ready will be wrong, but the ready timeouts
280 * are always kept at the start of the list so we can stop once
281 * we've reached the first non-ready timeout
282 */
283 if (!(TIMEOUT_READY (timeout)))
284 break;
285
286 /* Add a reference to the timeout so it can't disappear
287 * while it's being dispatched
288 */
289 clutter_timeout_ref (timeout);
290
291 timeout->flags &= ~CLUTTER_TIMEOUT_READY;
292
293 /* Move the list node to a list of dispatched timeouts */
294 l = pool->timeouts;
295 if (l->next)
296 l->next->prev = NULL;
297
298 pool->timeouts = l->next;
299
300 if (pool->dispatched_timeouts)
301 pool->dispatched_timeouts->prev = l;
302
303 l->prev = NULL;
304 l->next = pool->dispatched_timeouts;
305 pool->dispatched_timeouts = l;
306
307 if (!_clutter_timeout_interval_dispatch (&timeout->interval,
308 timeout->func, timeout->data))
309 {
310 /* The timeout may have already been removed, but nothing
311 * can be added to the dispatched_timeout list except in this
312 * function so it will always either be at the head of the
313 * dispatched list or have been removed
314 */
315 if (pool->dispatched_timeouts &&
316 pool->dispatched_timeouts->data == timeout)
317 {
318 pool->dispatched_timeouts =
319 g_list_delete_link (pool->dispatched_timeouts,
320 pool->dispatched_timeouts);
321
322 /* Remove the reference that was held by it being in the list */
323 clutter_timeout_unref (timeout);
324 }
325 }
326
327 clutter_timeout_unref (timeout);
328 }
329
330 /* Re-insert the dispatched timeouts in sorted order */
331 dispatched_timeouts = pool->dispatched_timeouts;
332 while (dispatched_timeouts)
333 {
334 ClutterTimeout *timeout = dispatched_timeouts->data;
335 GList *next = dispatched_timeouts->next;
336
337 if (timeout)
338 pool->timeouts = g_list_insert_sorted (pool->timeouts, timeout,
339 clutter_timeout_sort);
340
341 dispatched_timeouts = next;
342 }
343
344 g_list_free (pool->dispatched_timeouts);
345 pool->dispatched_timeouts = NULL;
346
347 pool->ready = 0;
348
349 clutter_threads_leave ();
350
351 return TRUE;
352 }
353
354 static void
clutter_timeout_pool_finalize(GSource * source)355 clutter_timeout_pool_finalize (GSource *source)
356 {
357 ClutterTimeoutPool *pool = (ClutterTimeoutPool *) source;
358
359 /* force destruction */
360 g_list_foreach (pool->timeouts, (GFunc) clutter_timeout_free, NULL);
361 g_list_free (pool->timeouts);
362 }
363
364 /**
365 * clutter_timeout_pool_new:
366 * @priority: the priority of the timeout pool. Typically this will
367 * be #G_PRIORITY_DEFAULT
368 *
369 * Creates a new timeout pool source. A timeout pool should be used when
370 * multiple timeout functions, running at the same priority, are needed and
371 * the g_timeout_add() API might lead to starvation of the time slice of
372 * the main loop. A timeout pool allocates a single time slice of the main
373 * loop and runs every timeout function inside it. The timeout pool is
374 * always sorted, so that the extraction of the next timeout function is
375 * a constant time operation.
376 *
377 * Return value: the newly created #ClutterTimeoutPool. The created pool
378 * is owned by the GLib default context and will be automatically
379 * destroyed when the context is destroyed. It is possible to force
380 * the destruction of the timeout pool using g_source_destroy()
381 *
382 * Since: 0.4
383 *
384 * Deprecated: 1.6: There is no direct replacement for this API
385 */
386 ClutterTimeoutPool *
clutter_timeout_pool_new(gint priority)387 clutter_timeout_pool_new (gint priority)
388 {
389 ClutterTimeoutPool *pool;
390 GSource *source;
391
392 source = g_source_new (&clutter_timeout_pool_funcs,
393 sizeof (ClutterTimeoutPool));
394 if (!source)
395 return NULL;
396
397 g_source_set_name (source, "Clutter timeout pool");
398
399 if (priority != G_PRIORITY_DEFAULT)
400 g_source_set_priority (source, priority);
401
402 pool = (ClutterTimeoutPool *) source;
403 pool->next_id = 1;
404 pool->id = g_source_attach (source, NULL);
405
406 /* let the default GLib context manage the pool */
407 g_source_unref (source);
408
409 return pool;
410 }
411
412 /**
413 * clutter_timeout_pool_add:
414 * @pool: a #ClutterTimeoutPool
415 * @fps: the time between calls to the function, in frames per second
416 * @func: function to call
417 * @data: (closure): data to pass to the function, or %NULL
418 * @notify: function to call when the timeout is removed, or %NULL
419 *
420 * Sets a function to be called at regular intervals, and puts it inside
421 * the @pool. The function is repeatedly called until it returns %FALSE,
422 * at which point the timeout is automatically destroyed and the function
423 * won't be called again. If @notify is not %NULL, the @notify function
424 * will be called. The first call to @func will be at the end of @interval.
425 *
426 * Since Clutter 0.8 this will try to compensate for delays. For
427 * example, if @func takes half the interval time to execute then the
428 * function will be called again half the interval time after it
429 * finished. Before version 0.8 it would not fire until a full
430 * interval after the function completes so the delay between calls
431 * would be @interval * 1.5. This function does not however try to
432 * invoke the function multiple times to catch up missing frames if
433 * @func takes more than @interval ms to execute.
434 *
435 * Return value: the ID (greater than 0) of the timeout inside the pool.
436 * Use clutter_timeout_pool_remove() to stop the timeout.
437 *
438 * Since: 0.4
439 *
440 * Deprecated: 1.6: There is no direct replacement for this API
441 */
442 guint
clutter_timeout_pool_add(ClutterTimeoutPool * pool,guint fps,GSourceFunc func,gpointer data,GDestroyNotify notify)443 clutter_timeout_pool_add (ClutterTimeoutPool *pool,
444 guint fps,
445 GSourceFunc func,
446 gpointer data,
447 GDestroyNotify notify)
448 {
449 ClutterTimeout *timeout;
450 guint retval = 0;
451
452 timeout = clutter_timeout_new (fps);
453
454 retval = timeout->id = pool->next_id++;
455
456 timeout->func = func;
457 timeout->data = data;
458 timeout->notify = notify;
459
460 pool->timeouts = g_list_insert_sorted (pool->timeouts, timeout,
461 clutter_timeout_sort);
462
463 return retval;
464 }
465
466 /**
467 * clutter_timeout_pool_remove:
468 * @pool: a #ClutterTimeoutPool
469 * @id_: the id of the timeout to remove
470 *
471 * Removes a timeout function with @id_ from the timeout pool. The id
472 * is the same returned when adding a function to the timeout pool with
473 * clutter_timeout_pool_add().
474 *
475 * Since: 0.4
476 *
477 * Deprecated: 1.6: There is no direct replacement for this API
478 */
479 void
clutter_timeout_pool_remove(ClutterTimeoutPool * pool,guint id_)480 clutter_timeout_pool_remove (ClutterTimeoutPool *pool,
481 guint id_)
482 {
483 GList *l;
484
485 if ((l = g_list_find_custom (pool->timeouts, GUINT_TO_POINTER (id_),
486 clutter_timeout_find_by_id)))
487 {
488 clutter_timeout_unref (l->data);
489 pool->timeouts = g_list_delete_link (pool->timeouts, l);
490 }
491 else if ((l = g_list_find_custom (pool->dispatched_timeouts,
492 GUINT_TO_POINTER (id_),
493 clutter_timeout_find_by_id)))
494 {
495 clutter_timeout_unref (l->data);
496
497 pool->dispatched_timeouts =
498 g_list_delete_link (pool->dispatched_timeouts, l);
499 }
500 }
501