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