Add timeout pool source
authorEmmanuele Bassi <ebassi@openedhand.com>
Sat, 9 Jun 2007 13:19:52 +0000 (13:19 +0000)
committerEmmanuele Bassi <ebassi@openedhand.com>
Sat, 9 Jun 2007 13:19:52 +0000 (13:19 +0000)
A ClutterTimeoutPool is a source for the GLib main loop which pools
multiple timeout functions. The pool is always sorted so that the first
timeout to expire is also the first element of the pool; hence, extraction
is a constant time operation. This also makes the usage of multiple
timeouts at the same priority not compete for a timeslice of the main
loop, leading to starvation.

clutter/Makefile.am
clutter/clutter-timeout-pool.c [new file with mode: 0644]
clutter/clutter-timeout-pool.h [new file with mode: 0644]

index a144374..5ee96a8 100644 (file)
@@ -62,6 +62,7 @@ source_h =                                    \
        $(srcdir)/clutter-stage.h               \
        $(srcdir)/clutter-texture.h             \
        $(srcdir)/clutter-timeline.h            \
+       $(srcdir)/clutter-timeout-pool.h        \
        $(srcdir)/clutter-effect.h              \
        $(srcdir)/clutter-units.h               \
        $(srcdir)/clutter-util.h                \
@@ -154,7 +155,8 @@ source_c = \
        clutter-rectangle.c             \
        clutter-texture.c               \
        clutter-timeline.c              \
-       $(srcdir)/clutter-effect.c      \
+       clutter-timeout-pool.c          \
+       clutter-effect.c                \
        clutter-util.c                  \
        $(NULL)
 
diff --git a/clutter/clutter-timeout-pool.c b/clutter/clutter-timeout-pool.c
new file mode 100644 (file)
index 0000000..dfceb88
--- /dev/null
@@ -0,0 +1,369 @@
+#include "config.h"
+
+#include "clutter-timeout-pool.h"
+
+#include "clutter-main.h"
+#include "clutter-debug.h"
+#include "clutter-private.h"
+
+typedef struct _ClutterTimeout  ClutterTimeout;
+
+struct _ClutterTimeout
+{
+  guint id;
+  
+  guint interval;
+
+  GSourceFunc func;
+  gpointer data;
+  GDestroyNotify notify;
+
+  GTimeVal expiration;
+};
+
+struct _ClutterTimeoutPool
+{
+  GSource source;
+
+  guint next_id;
+
+  GList *timeouts;
+  gint ready;
+
+  guint id;
+};
+
+static gboolean clutter_timeout_pool_prepare  (GSource     *source,
+                                               gint        *next_timeout);
+static gboolean clutter_timeout_pool_check    (GSource     *source);
+static gboolean clutter_timeout_pool_dispatch (GSource     *source,
+                                               GSourceFunc  callback,
+                                               gpointer     data);
+static void clutter_timeout_pool_finalize (GSource     *source);
+
+static GSourceFuncs clutter_timeout_pool_funcs =
+{
+  clutter_timeout_pool_prepare,
+  clutter_timeout_pool_check,
+  clutter_timeout_pool_dispatch,
+  clutter_timeout_pool_finalize
+};
+
+static gint
+clutter_timeout_sort (gconstpointer a,
+                      gconstpointer b)
+{
+  const ClutterTimeout *t_a = a;
+  const ClutterTimeout *t_b = b;
+
+  return ((t_a->expiration.tv_sec < t_b->expiration.tv_sec) ||
+          ((t_a->expiration.tv_sec == t_b->expiration.tv_sec) &&
+           (t_a->expiration.tv_usec <= t_b->expiration.tv_usec)));
+}
+
+static gint
+clutter_timeout_find_by_id (gconstpointer a,
+                            gconstpointer b)
+{
+  const ClutterTimeout *t_a = a;
+
+  return t_a->id == GPOINTER_TO_UINT (b) ? 0 : 1;
+}
+
+static void
+clutter_timeout_set_expiration (ClutterTimeout *timeout,
+                                GTimeVal       *current_time)
+{
+  guint seconds = timeout->interval / 1000;
+  guint msecs = timeout->interval - seconds * 1000;
+
+  timeout->expiration.tv_sec  = current_time->tv_sec + seconds;
+  timeout->expiration.tv_usec = current_time->tv_usec + msecs * 1000;
+
+  if (timeout->expiration.tv_usec >= 1000000)
+    {
+      timeout->expiration.tv_usec -= 1000000;
+      timeout->expiration.tv_sec += 1;
+    }
+}
+
+static gboolean
+clutter_timeout_prepare (GSource        *source,
+                         ClutterTimeout *timeout,
+                         gint           *next_timeout)
+{
+  glong sec;
+  glong msec;
+  GTimeVal current_time;
+
+  g_source_get_current_time (source, &current_time);
+
+  sec = timeout->expiration.tv_sec - current_time.tv_sec;
+  msec = (timeout->expiration.tv_usec - current_time.tv_usec) / 1000;
+
+  if (sec < 0 || (sec == 0 && msec < 0))
+    msec = 0;
+  else
+    {
+      glong interval_sec = timeout->interval / 1000;
+      glong interval_msec = timeout->interval % 1000;
+
+      if (msec < 0)
+        {
+          msec += 1000;
+          sec -= 1;
+        }
+
+      if (sec > interval_sec ||
+          (sec == interval_sec && msec > interval_msec))
+        {
+          clutter_timeout_set_expiration (timeout, &current_time);
+          msec = MIN (G_MAXINT, timeout->interval);
+        }
+      else
+        msec = MIN (G_MAXINT, (guint) msec + 1000 * (guint) sec);
+    }
+
+  *next_timeout = (gint) msec;
+  return (msec == 0);
+}
+
+static gboolean
+clutter_timeout_check (GSource        *source,
+                       ClutterTimeout *timeout)
+{
+  GTimeVal current_time;
+
+  g_source_get_current_time (source, &current_time);
+
+  return ((timeout->expiration.tv_sec < current_time.tv_sec) ||
+          ((timeout->expiration.tv_sec == current_time.tv_sec) &&
+           (timeout->expiration.tv_usec <= current_time.tv_usec)));
+}
+
+static gboolean
+clutter_timeout_dispatch (GSource        *source,
+                          ClutterTimeout *timeout)
+{
+  if (G_UNLIKELY (!timeout->func))
+    {
+      g_warning ("Timeout dispatched without a callback.");
+      return FALSE;
+    }
+
+  if (timeout->func (timeout->data))
+    {
+      GTimeVal current_time;
+
+      g_source_get_current_time (source, &current_time);
+      clutter_timeout_set_expiration (timeout, &current_time);
+
+      return TRUE;
+    }
+  else
+    return FALSE;
+}
+
+static ClutterTimeout *
+clutter_timeout_new (guint interval)
+{
+  ClutterTimeout *timeout;
+  GTimeVal current_time;
+
+  timeout = g_slice_new0 (ClutterTimeout);
+  timeout->interval = interval;
+
+  g_get_current_time (&current_time);
+  clutter_timeout_set_expiration (timeout, &current_time);
+
+  return timeout;
+}
+
+static void
+clutter_timeout_free (ClutterTimeout *timeout)
+{
+  if (G_LIKELY (timeout))
+    {
+      if (timeout->notify)
+        timeout->notify (timeout->data);
+
+      g_slice_free (ClutterTimeout, timeout);
+    }
+}
+
+static gboolean
+clutter_timeout_pool_prepare (GSource *source,
+                              gint    *next_timeout)
+{
+  ClutterTimeoutPool *pool = (ClutterTimeoutPool *) source;
+  GList *l = pool->timeouts;
+
+  if (l)
+    {
+      ClutterTimeout *timeout = l->data;
+      return clutter_timeout_prepare (source, timeout, next_timeout);
+    }
+  else
+    {
+      *next_timeout = -1;
+      return FALSE;
+    }
+}
+
+static gboolean
+clutter_timeout_pool_check (GSource *source)
+{
+  ClutterTimeoutPool *pool = (ClutterTimeoutPool *) source;
+  GList *l = pool->timeouts;
+  
+  for (l = pool->timeouts; l; l = l->next)
+    {
+      ClutterTimeout *timeout = l->data;
+
+      if (clutter_timeout_check (source, timeout))
+        pool->ready += 1;
+    }
+
+  return (pool->ready > 0);
+}
+
+static gboolean
+clutter_timeout_pool_dispatch (GSource     *source,
+                               GSourceFunc  func,
+                               gpointer     data)
+{
+  ClutterTimeoutPool *pool = (ClutterTimeoutPool *) source;
+  GList *l = pool->timeouts;
+  gboolean sort_needed = FALSE;
+
+  if (!pool->ready)
+    clutter_timeout_pool_check (source);
+
+  while (l && l->data && (pool->ready-- > 0))
+    {
+      ClutterTimeout *timeout = l->data;
+      GList *next = l->next;
+
+      if (clutter_timeout_dispatch (source, timeout))
+        {
+          sort_needed = TRUE;
+        }
+      else
+        {
+          pool->timeouts = g_list_delete_link (pool->timeouts, l);
+          clutter_timeout_free (timeout);
+        }
+
+      l = next;
+    }
+
+  if (sort_needed)
+    pool->timeouts = g_list_sort (pool->timeouts, clutter_timeout_sort);
+
+  pool->ready = 0;
+
+  return TRUE;
+}
+
+static void
+clutter_timeout_pool_finalize (GSource *source)
+{
+  ClutterTimeoutPool *pool = (ClutterTimeoutPool *) source;
+
+  g_list_foreach (pool->timeouts, (GFunc) clutter_timeout_free, NULL);
+  g_list_free (pool->timeouts);
+}
+
+/**
+ * clutter_timeout_pool_new:
+ * @priority:
+ *
+ * FIXME
+ *
+ * Return value: the newly created #ClutterTimeoutPool
+ *
+ * Since: 0.4
+ */
+ClutterTimeoutPool *
+clutter_timeout_pool_new (gint priority)
+{
+  ClutterTimeoutPool *pool;
+  GSource *source;
+
+  source = g_source_new (&clutter_timeout_pool_funcs,
+                         sizeof (ClutterTimeoutPool));
+  if (!source)
+    return NULL;
+
+  if (priority != G_PRIORITY_DEFAULT)
+    g_source_set_priority (source, priority);
+
+  pool = (ClutterTimeoutPool *) source;
+  pool->next_id = 1;
+  pool->id = g_source_attach (source, NULL);
+
+  g_source_unref (source);
+
+  return pool;
+}
+
+/**
+ * clutter_timeout_pool_add:
+ * @pool: a #ClutterTimeoutPool
+ * @interval: FIXME
+ * @func: FIXME
+ * @data: FIXME
+ * @notify: FIXME
+ *
+ * FIXME
+ *
+ * Return value: FIXME
+ *
+ * Since: 0.4
+ */
+guint
+clutter_timeout_pool_add (ClutterTimeoutPool *pool,
+                          guint               interval,
+                          GSourceFunc         func,
+                          gpointer            data,
+                          GDestroyNotify      notify)
+{
+  ClutterTimeout *timeout;
+  guint retval = 0;
+
+  timeout = clutter_timeout_new (interval);
+  retval = timeout->id = pool->next_id++;
+
+  timeout->func = func;
+  timeout->data = data;
+  timeout->notify = notify;
+
+  pool->timeouts = g_list_insert_sorted (pool->timeouts, timeout,
+                                         clutter_timeout_sort);
+
+  return retval;
+}
+
+/**
+ * clutter_timeout_pool_remove:
+ * @pool: a #ClutterTimeoutPool
+ * @id: FIXME
+ *
+ * FIXME
+ *
+ * Since: 0.4
+ */
+void
+clutter_timeout_pool_remove (ClutterTimeoutPool *pool,
+                             guint               id)
+{
+  GList *l;
+
+  l = g_list_find_custom (pool->timeouts, GUINT_TO_POINTER (id),
+                          clutter_timeout_find_by_id);
+  if (l)
+    {
+      clutter_timeout_free (l->data);
+      pool->timeouts = g_list_delete_link (pool->timeouts, l);
+    }
+}
diff --git a/clutter/clutter-timeout-pool.h b/clutter/clutter-timeout-pool.h
new file mode 100644 (file)
index 0000000..0d27a24
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef __CLUTTER_TIMEOUT_POOL_H__
+#define __CLUTTER_TIMEOUT_POOL_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ClutterTimeoutPool    ClutterTimeoutPool;
+
+ClutterTimeoutPool *clutter_timeout_pool_new    (gint                priority);
+guint               clutter_timeout_pool_add    (ClutterTimeoutPool *pool,
+                                                 guint               interval,
+                                                 GSourceFunc         func,
+                                                 gpointer            data,
+                                                 GDestroyNotify      notify);
+void                clutter_timeout_pool_remove (ClutterTimeoutPool *pool,
+                                                 guint               id);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_TIMEOUT_POOL_H__ */