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, write to the
22 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 * Boston, MA 02111-1307, USA.
24 *
25 * EggTimeoutPool: 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 #include "egg-debug.h"
38 #include "egg-timeout-pool.h"
39
40 typedef struct _EggTimeout EggTimeout;
41 typedef enum {
42 EGG_TIMEOUT_NONE = 0,
43 EGG_TIMEOUT_READY = 1 << 1
44 } EggTimeoutFlags;
45
46 struct _EggTimeout
47 {
48 guint id;
49 EggTimeoutFlags flags;
50 gint refcount;
51
52 guint interval;
53 guint last_time;
54
55 GSourceFunc func;
56 gpointer data;
57 GDestroyNotify notify;
58 };
59
60 struct _EggTimeoutPool
61 {
62 GSource source;
63
64 guint next_id;
65
66 GTimeVal start_time;
67 GList *timeouts, *dispatched_timeouts;
68 gint ready;
69
70 guint id;
71 };
72
73 #define TIMEOUT_READY(timeout) (timeout->flags & EGG_TIMEOUT_READY)
74
75 static gboolean egg_timeout_pool_prepare (GSource *source,
76 gint *next_timeout);
77 static gboolean egg_timeout_pool_check (GSource *source);
78 static gboolean egg_timeout_pool_dispatch (GSource *source,
79 GSourceFunc callback,
80 gpointer data);
81 static void egg_timeout_pool_finalize (GSource *source);
82
83 static GSourceFuncs egg_timeout_pool_funcs =
84 {
85 egg_timeout_pool_prepare,
86 egg_timeout_pool_check,
87 egg_timeout_pool_dispatch,
88 egg_timeout_pool_finalize
89 };
90
91 static gint
egg_timeout_sort(gconstpointer a,gconstpointer b)92 egg_timeout_sort (gconstpointer a,
93 gconstpointer b)
94 {
95 const EggTimeout *t_a = a;
96 const EggTimeout *t_b = b;
97 gint comparison;
98
99 /* Keep 'ready' timeouts at the front */
100 if (TIMEOUT_READY (t_a))
101 return -1;
102
103 if (TIMEOUT_READY (t_b))
104 return 1;
105
106 /* Otherwise sort by expiration time */
107 comparison = (t_a->last_time + t_a->interval)
108 - (t_b->last_time + t_b->interval);
109 if (comparison < 0)
110 return -1;
111
112 if (comparison > 0)
113 return 1;
114
115 return 0;
116 }
117
118 static gint
egg_timeout_find_by_id(gconstpointer a,gconstpointer b)119 egg_timeout_find_by_id (gconstpointer a,
120 gconstpointer b)
121 {
122 const EggTimeout *t_a = a;
123
124 return t_a->id == GPOINTER_TO_UINT (b) ? 0 : 1;
125 }
126
127 static guint
egg_timeout_pool_get_ticks(EggTimeoutPool * pool)128 egg_timeout_pool_get_ticks (EggTimeoutPool *pool)
129 {
130 GTimeVal time_now;
131
132 g_source_get_current_time ((GSource *) pool, &time_now);
133
134 return (time_now.tv_sec - pool->start_time.tv_sec) * 1000
135 + (time_now.tv_usec - pool->start_time.tv_usec) / 1000;
136 }
137
138 static gboolean
egg_timeout_prepare(EggTimeoutPool * pool,EggTimeout * timeout,gint * next_timeout)139 egg_timeout_prepare (EggTimeoutPool *pool,
140 EggTimeout *timeout,
141 gint *next_timeout)
142 {
143 guint now = egg_timeout_pool_get_ticks (pool);
144
145 /* If time has gone backwards or the time since the last frame is
146 greater than the two frames worth then reset the time and do a
147 frame now */
148 if (timeout->last_time > now || now - timeout->last_time
149 > timeout->interval * 2)
150 {
151 timeout->last_time = now - timeout->interval;
152 if (next_timeout)
153 *next_timeout = 0;
154 return TRUE;
155 }
156 else if (now - timeout->last_time >= timeout->interval)
157 {
158 if (next_timeout)
159 *next_timeout = 0;
160 return TRUE;
161 }
162 else
163 {
164 if (next_timeout)
165 *next_timeout = timeout->interval + timeout->last_time - now;
166 return FALSE;
167 }
168 }
169
170 static gboolean
egg_timeout_dispatch(GSource * source,EggTimeout * timeout)171 egg_timeout_dispatch (GSource *source,
172 EggTimeout *timeout)
173 {
174 gboolean retval = FALSE;
175
176 if (G_UNLIKELY (!timeout->func))
177 {
178 g_warning ("Timeout dispatched without a callback.");
179 return FALSE;
180 }
181
182 if (timeout->func (timeout->data))
183 {
184 timeout->last_time += timeout->interval;
185
186 retval = TRUE;
187 }
188
189 return retval;
190 }
191
192 static EggTimeout *
egg_timeout_new(guint interval)193 egg_timeout_new (guint interval)
194 {
195 EggTimeout *timeout;
196
197 timeout = g_slice_new0 (EggTimeout);
198 timeout->interval = interval;
199 timeout->flags = EGG_TIMEOUT_NONE;
200 timeout->refcount = 1;
201
202 return timeout;
203 }
204
205 /* ref and unref are always called under the main Egg lock, so there
206 * is not need for us to use g_atomic_int_* API.
207 */
208
209 static EggTimeout *
egg_timeout_ref(EggTimeout * timeout)210 egg_timeout_ref (EggTimeout *timeout)
211 {
212 g_return_val_if_fail (timeout != NULL, timeout);
213 g_return_val_if_fail (timeout->refcount > 0, timeout);
214
215 timeout->refcount += 1;
216
217 return timeout;
218 }
219
220 static void
egg_timeout_unref(EggTimeout * timeout)221 egg_timeout_unref (EggTimeout *timeout)
222 {
223 g_return_if_fail (timeout != NULL);
224 g_return_if_fail (timeout->refcount > 0);
225
226 timeout->refcount -= 1;
227
228 if (timeout->refcount == 0)
229 {
230 if (timeout->notify)
231 timeout->notify (timeout->data);
232
233 g_slice_free (EggTimeout, timeout);
234 }
235 }
236
237 static void
egg_timeout_free(EggTimeout * timeout)238 egg_timeout_free (EggTimeout *timeout)
239 {
240 if (G_LIKELY (timeout))
241 {
242 if (timeout->notify)
243 timeout->notify (timeout->data);
244
245 g_slice_free (EggTimeout, timeout);
246 }
247 }
248
249 static gboolean
egg_timeout_pool_prepare(GSource * source,gint * next_timeout)250 egg_timeout_pool_prepare (GSource *source,
251 gint *next_timeout)
252 {
253 EggTimeoutPool *pool = (EggTimeoutPool *) source;
254 GList *l = pool->timeouts;
255
256 /* the pool is ready if the first timeout is ready */
257 if (l && l->data)
258 {
259 EggTimeout *timeout = l->data;
260 return egg_timeout_prepare (pool, timeout, next_timeout);
261 }
262 else
263 {
264 *next_timeout = -1;
265 return FALSE;
266 }
267 }
268
269 static gboolean
egg_timeout_pool_check(GSource * source)270 egg_timeout_pool_check (GSource *source)
271 {
272 EggTimeoutPool *pool = (EggTimeoutPool *) source;
273 GList *l = pool->timeouts;
274
275 egg_threads_enter ();
276
277 for (l = pool->timeouts; l; l = l->next)
278 {
279 EggTimeout *timeout = l->data;
280
281 /* since the timeouts are sorted by expiration, as soon
282 * as we get a check returning FALSE we know that the
283 * following timeouts are not expiring, so we break as
284 * soon as possible
285 */
286 if (egg_timeout_prepare (pool, timeout, NULL))
287 {
288 timeout->flags |= EGG_TIMEOUT_READY;
289 pool->ready += 1;
290 }
291 else
292 break;
293 }
294
295 egg_threads_leave ();
296
297 return (pool->ready > 0);
298 }
299
300 static gboolean
egg_timeout_pool_dispatch(GSource * source,GSourceFunc func,gpointer data)301 egg_timeout_pool_dispatch (GSource *source,
302 GSourceFunc func,
303 gpointer data)
304 {
305 EggTimeoutPool *pool = (EggTimeoutPool *) source;
306 GList *dispatched_timeouts;
307
308 /* the main loop might have predicted this, so we repeat the
309 * check for ready timeouts.
310 */
311 if (!pool->ready)
312 egg_timeout_pool_check (source);
313
314 egg_threads_enter ();
315
316 /* Iterate by moving the actual start of the list along so that it
317 * can cope with adds and removes while a timeout is being dispatched
318 */
319 while (pool->timeouts && pool->timeouts->data && pool->ready-- > 0)
320 {
321 EggTimeout *timeout = pool->timeouts->data;
322 GList *l;
323
324 /* One of the ready timeouts may have been removed during dispatch,
325 * in which case pool->ready will be wrong, but the ready timeouts
326 * are always kept at the start of the list so we can stop once
327 * we've reached the first non-ready timeout
328 */
329 if (!(TIMEOUT_READY (timeout)))
330 break;
331
332 /* Add a reference to the timeout so it can't disappear
333 * while it's being dispatched
334 */
335 egg_timeout_ref (timeout);
336
337 timeout->flags &= ~EGG_TIMEOUT_READY;
338
339 /* Move the list node to a list of dispatched timeouts */
340 l = pool->timeouts;
341 if (l->next)
342 l->next->prev = NULL;
343
344 pool->timeouts = l->next;
345
346 if (pool->dispatched_timeouts)
347 pool->dispatched_timeouts->prev = l;
348
349 l->prev = NULL;
350 l->next = pool->dispatched_timeouts;
351 pool->dispatched_timeouts = l;
352
353 if (!egg_timeout_dispatch (source, timeout))
354 {
355 /* The timeout may have already been removed, but nothing
356 * can be added to the dispatched_timeout list except in this
357 * function so it will always either be at the head of the
358 * dispatched list or have been removed
359 */
360 if (pool->dispatched_timeouts &&
361 pool->dispatched_timeouts->data == timeout)
362 {
363 pool->dispatched_timeouts =
364 g_list_delete_link (pool->dispatched_timeouts,
365 pool->dispatched_timeouts);
366
367 /* Remove the reference that was held by it being in the list */
368 egg_timeout_unref (timeout);
369 }
370 }
371
372 egg_timeout_unref (timeout);
373 }
374
375 /* Re-insert the dispatched timeouts in sorted order */
376 dispatched_timeouts = pool->dispatched_timeouts;
377 while (dispatched_timeouts)
378 {
379 EggTimeout *timeout = dispatched_timeouts->data;
380 GList *next = dispatched_timeouts->next;
381
382 if (timeout)
383 pool->timeouts = g_list_insert_sorted (pool->timeouts, timeout,
384 egg_timeout_sort);
385
386 dispatched_timeouts = next;
387 }
388
389 g_list_free (pool->dispatched_timeouts);
390 pool->dispatched_timeouts = NULL;
391
392 pool->ready = 0;
393
394 egg_threads_leave ();
395
396 return TRUE;
397 }
398
399 static void
egg_timeout_pool_finalize(GSource * source)400 egg_timeout_pool_finalize (GSource *source)
401 {
402 EggTimeoutPool *pool = (EggTimeoutPool *) source;
403
404 /* force destruction */
405 g_list_foreach (pool->timeouts, (GFunc) egg_timeout_free, NULL);
406 g_list_free (pool->timeouts);
407 }
408
409 /**
410 * egg_timeout_pool_new:
411 * @priority: the priority of the timeout pool. Typically this will
412 * be #G_PRIORITY_DEFAULT
413 *
414 * Creates a new timeout pool source. A timeout pool should be used when
415 * multiple timeout functions, running at the same priority, are needed and
416 * the g_timeout_add() API might lead to starvation of the time slice of
417 * the main loop. A timeout pool allocates a single time slice of the main
418 * loop and runs every timeout function inside it. The timeout pool is
419 * always sorted, so that the extraction of the next timeout function is
420 * a constant time operation.
421 *
422 * Inside Egg, every #EggTimeline share the same timeout pool, unless
423 * the EGG_TIMELINE=no-pool environment variable is set.
424 *
425 * #EggTimeoutPool is part of the #EggTimeline implementation
426 * and should not be used by application developers.
427 *
428 * Return value: the newly created #EggTimeoutPool. The created pool
429 * is owned by the GLib default context and will be automatically
430 * destroyed when the context is destroyed. It is possible to force
431 * the destruction of the timeout pool using g_source_destroy()
432 *
433 * Since: 0.4
434 */
435 EggTimeoutPool *
egg_timeout_pool_new(gint priority)436 egg_timeout_pool_new (gint priority)
437 {
438 EggTimeoutPool *pool;
439 GSource *source;
440
441 source = g_source_new (&egg_timeout_pool_funcs,
442 sizeof (EggTimeoutPool));
443 if (!source)
444 return NULL;
445
446 if (priority != G_PRIORITY_DEFAULT)
447 g_source_set_priority (source, priority);
448
449 pool = (EggTimeoutPool *) source;
450
451 g_get_current_time (&pool->start_time);
452 pool->next_id = 1;
453 pool->id = g_source_attach (source, NULL);
454
455 /* let the default GLib context manage the pool */
456 g_source_unref (source);
457
458 return pool;
459 }
460
461 /**
462 * egg_timeout_pool_add:
463 * @pool: a #EggTimeoutPool
464 * @interval: the time between calls to the function, in milliseconds
465 * @func: function to call
466 * @data: data to pass to the function, or %NULL
467 * @notify: function to call when the timeout is removed, or %NULL
468 *
469 * Sets a function to be called at regular intervals, and puts it inside
470 * the @pool. The function is repeatedly called until it returns %FALSE,
471 * at which point the timeout is automatically destroyed and the function
472 * won't be called again. If @notify is not %NULL, the @notify function
473 * will be called. The first call to @func will be at the end of @interval.
474 *
475 * Since version 0.8 this will try to compensate for delays. For
476 * example, if @func takes half the interval time to execute then the
477 * function will be called again half the interval time after it
478 * finished. Before version 0.8 it would not fire until a full
479 * interval after the function completes so the delay between calls
480 * would be @interval * 1.5. This function does not however try to
481 * invoke the function multiple times to catch up missing frames if
482 * @func takes more than @interval ms to execute.
483 *
484 * Return value: the ID (greater than 0) of the timeout inside the pool.
485 * Use egg_timeout_pool_remove() to stop the timeout.
486 *
487 * Since: 0.4
488 */
489 guint
egg_timeout_pool_add(EggTimeoutPool * pool,guint interval,GSourceFunc func,gpointer data,GDestroyNotify notify)490 egg_timeout_pool_add (EggTimeoutPool *pool,
491 guint interval,
492 GSourceFunc func,
493 gpointer data,
494 GDestroyNotify notify)
495 {
496 EggTimeout *timeout;
497 guint retval = 0;
498
499 timeout = egg_timeout_new (interval);
500
501 retval = timeout->id = pool->next_id++;
502
503 timeout->last_time = egg_timeout_pool_get_ticks (pool);
504 timeout->func = func;
505 timeout->data = data;
506 timeout->notify = notify;
507
508 pool->timeouts = g_list_insert_sorted (pool->timeouts, timeout,
509 egg_timeout_sort);
510
511 return retval;
512 }
513
514 /**
515 * egg_timeout_pool_remove:
516 * @pool: a #EggTimeoutPool
517 * @id: the id of the timeout to remove
518 *
519 * Removes a timeout function with @id from the timeout pool. The id
520 * is the same returned when adding a function to the timeout pool with
521 * egg_timeout_pool_add().
522 *
523 * Since: 0.4
524 */
525 void
egg_timeout_pool_remove(EggTimeoutPool * pool,guint id)526 egg_timeout_pool_remove (EggTimeoutPool *pool,
527 guint id)
528 {
529 GList *l;
530
531 if ((l = g_list_find_custom (pool->timeouts, GUINT_TO_POINTER (id),
532 egg_timeout_find_by_id)))
533 {
534 egg_timeout_unref (l->data);
535 pool->timeouts = g_list_delete_link (pool->timeouts, l);
536 }
537 else if ((l = g_list_find_custom (pool->dispatched_timeouts,
538 GUINT_TO_POINTER (id),
539 egg_timeout_find_by_id)))
540 {
541 egg_timeout_unref (l->data);
542 pool->dispatched_timeouts
543 = g_list_delete_link (pool->dispatched_timeouts, l);
544 }
545 }
546