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