[glib/ebassi/source-once: 1/3] Add one-shot idle and timeout functions




commit 12571a08212e96fef079704453091a0fc317a2a9
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Tue May 24 17:07:04 2022 +0100

    Add one-shot idle and timeout functions
    
    Many idle and timeout sources are installed as "one shot": called once
    and immediately removed. While it's easy to write a simple callback that
    returns G_SOURCE_REMOVE, it would also be useful to have some sort of
    "visual" marker when reading the code; a way to immediately see that a
    callback (which may be defined elsewhere in the code) is meant to be
    invoked just once.
    
    Includes additional unit tests by Philip Withnall.

 docs/reference/glib/glib-sections.txt |   3 +
 glib/gmain.c                          | 118 ++++++++++++++++++++++++++++++++++
 glib/gmain.h                          |  20 ++++++
 glib/tests/mainloop.c                 |  69 ++++++++++++++++++++
 4 files changed, 210 insertions(+)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index bacf386840..038a97edeb 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -904,6 +904,7 @@ g_main_context_pop_thread_default
 g_timeout_source_new
 g_timeout_source_new_seconds
 g_timeout_add
+g_timeout_add_once
 g_timeout_add_full
 g_timeout_add_seconds
 g_timeout_add_seconds_full
@@ -911,6 +912,7 @@ g_timeout_add_seconds_full
 <SUBSECTION>
 g_idle_source_new
 g_idle_add
+g_idle_add_once
 g_idle_add_full
 g_idle_remove_by_data
 
@@ -954,6 +956,7 @@ g_source_get_context
 g_source_set_callback
 GSourceFunc
 G_SOURCE_FUNC
+GSourceOnceFunc
 g_source_set_callback_indirect
 g_source_set_ready_time
 g_source_get_ready_time
diff --git a/glib/gmain.c b/glib/gmain.c
index 74568b8a90..dd533c91d1 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -5157,6 +5157,85 @@ g_timeout_add (guint32        interval,
                             interval, function, data, NULL);
 }
 
+typedef struct {
+  GSourceOnceFunc function;
+  gpointer data;
+} OnceData;
+
+static void
+once_data_free (gpointer data)
+{
+  g_free (data);
+}
+
+static gboolean
+once_function (gpointer data)
+{
+  OnceData *once_data = data;
+
+  once_data->function (once_data->data);
+
+  return G_SOURCE_REMOVE;
+}
+
+/**
+ * g_timeout_add_once:
+ * @interval: the time after which the function will be called, in
+ *   milliseconds (1/1000ths of a second)
+ * @function: function to call
+ * @data: data to pass to @function
+ * 
+ * Sets a function to be called after @interval milliseconds have elapsed,
+ * with the default priority, %G_PRIORITY_DEFAULT.
+ *
+ * The given @function is called once.
+ *
+ * Note that timeout functions may be delayed, due to the processing of other
+ * event sources. Thus they should not be relied on for precise timing.
+ *
+ * See [memory management of sources][mainloop-memory-management] for details
+ * on how to handle the return value and memory management of @data.
+ *
+ * If you want to have a timer in the "seconds" range and do not care
+ * about the exact time of the first call of the timer, use the
+ * g_timeout_add_seconds() function; this function allows for more
+ * optimizations and more efficient system power usage.
+ *
+ * This internally creates a main loop source using g_timeout_source_new()
+ * and attaches it to the global #GMainContext using g_source_attach(), so
+ * the callback will be invoked in whichever thread is running that main
+ * context. You can do these steps manually if you need greater control or to
+ * use a custom main context.
+ * 
+ * It is safe to call this function from any thread.
+ *
+ * The interval given is in terms of monotonic time, not wall clock
+ * time.  See g_get_monotonic_time().
+ * 
+ * Returns: the ID (greater than 0) of the event source
+ *
+ * Since: 2.74
+ */
+guint
+g_timeout_add_once (guint32         interval,
+                    GSourceOnceFunc function,
+                    gpointer        data)
+{
+  OnceData *once_data;
+
+  g_return_val_if_fail (function != NULL, 0);
+
+  once_data = g_new (OnceData, 1);
+  once_data->function = function;
+  once_data->data = data;
+
+  return g_timeout_add_full (G_PRIORITY_DEFAULT,
+                             interval,
+                             once_function,
+                             once_data,
+                             once_data_free);
+}
+
 /**
  * g_timeout_add_seconds_full: (rename-to g_timeout_add_seconds)
  * @priority: the priority of the timeout source. Typically this will be in
@@ -6048,6 +6127,45 @@ g_idle_add (GSourceFunc    function,
   return g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, function, data, NULL);
 }
 
+/**
+ * g_idle_add_once:
+ * @function: function to call
+ * @data: data to pass to @function
+ *
+ * Adds a function to be called whenever there are no higher priority
+ * events pending to the default main loop. The function is given the
+ * default idle priority, %G_PRIORITY_DEFAULT_IDLE.
+ *
+ * The function will only be called once.
+ *
+ * See [memory management of sources][mainloop-memory-management] for details
+ * on how to handle the return value and memory management of @data.
+ * 
+ * This internally creates a main loop source using g_idle_source_new()
+ * and attaches it to the global #GMainContext using g_source_attach(), so
+ * the callback will be invoked in whichever thread is running that main
+ * context. You can do these steps manually if you need greater control or to
+ * use a custom main context.
+ *
+ * Returns: the ID (greater than 0) of the event source
+ *
+ * Since: 2.74
+ */
+guint
+g_idle_add_once (GSourceOnceFunc function,
+                 gpointer        data)
+{
+  OnceData *once_data;
+
+  g_return_val_if_fail (function != NULL, 0);
+
+  once_data = g_new (OnceData, 1);
+  once_data->function = function;
+  once_data->data = data;
+
+  return g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, once_function, once_data, once_data_free);
+}
+
 /**
  * g_idle_remove_by_data:
  * @data: the data for the idle source's callback.
diff --git a/glib/gmain.h b/glib/gmain.h
index 2cfa043cc9..aebf5cb233 100644
--- a/glib/gmain.h
+++ b/glib/gmain.h
@@ -194,6 +194,19 @@ typedef struct _GSourceFuncs            GSourceFuncs;
  */
 typedef gboolean (*GSourceFunc)       (gpointer user_data);
 
+/**
+ * GSourceOnceFunc:
+ * @user_data: data passed to the function, set when the source was
+ *   created
+ *
+ * A source function that is only called once.
+ *
+ * See: g_idle_add_once(), g_timeout_add_once()
+ *
+ * Since: 2.74
+ */
+typedef void (* GSourceOnceFunc) (gpointer user_data);
+
 /**
  * G_SOURCE_FUNC:
  * @f: a function pointer.
@@ -772,6 +785,10 @@ GLIB_AVAILABLE_IN_ALL
 guint    g_timeout_add              (guint           interval,
                                      GSourceFunc     function,
                                      gpointer        data);
+GLIB_AVAILABLE_IN_2_74
+guint    g_timeout_add_once         (guint           interval,
+                                     GSourceOnceFunc function,
+                                     gpointer        data);
 GLIB_AVAILABLE_IN_ALL
 guint    g_timeout_add_seconds_full (gint            priority,
                                      guint           interval,
@@ -800,6 +817,9 @@ guint    g_idle_add_full            (gint            priority,
                                      GSourceFunc     function,
                                      gpointer        data,
                                      GDestroyNotify  notify);
+GLIB_AVAILABLE_IN_2_74
+guint    g_idle_add_once            (GSourceOnceFunc function,
+                                     gpointer        data);
 GLIB_AVAILABLE_IN_ALL
 gboolean g_idle_remove_by_data      (gpointer        data);
 
diff --git a/glib/tests/mainloop.c b/glib/tests/mainloop.c
index 38eee475cd..fd3cac1b99 100644
--- a/glib/tests/mainloop.c
+++ b/glib/tests/mainloop.c
@@ -2309,6 +2309,72 @@ test_maincontext_source_finalization_from_dispatch (gconstpointer user_data)
   g_main_context_unref (c);
 }
 
+static void
+once_cb (gpointer user_data)
+{
+  guint *counter = user_data;
+
+  *counter = *counter + 1;
+}
+
+static void
+test_maincontext_idle_once (void)
+{
+  guint counter = 0;
+  guint source_id;
+  GSource *source;
+
+  g_test_summary ("Test g_idle_add_once() works");
+
+  source_id = g_idle_add_once (once_cb, &counter);
+  source = g_main_context_find_source_by_id (NULL, source_id);
+  g_assert_nonnull (source);
+  g_source_ref (source);
+
+  /* Iterating the main context should dispatch the source. */
+  g_assert_cmpuint (counter, ==, 0);
+  g_main_context_iteration (NULL, FALSE);
+  g_assert_cmpuint (counter, ==, 1);
+
+  /* Iterating it again should not dispatch the source again. */
+  g_main_context_iteration (NULL, FALSE);
+  g_assert_cmpuint (counter, ==, 1);
+  g_assert_true (g_source_is_destroyed (source));
+
+  g_clear_pointer (&source, g_source_unref);
+}
+
+static void
+test_maincontext_timeout_once (void)
+{
+  guint counter = 0, check_counter = 0;
+  guint source_id;
+  GSource *source;
+
+  g_test_summary ("Test g_timeout_add_once() works");
+
+  source_id = g_timeout_add_once (10 /* ms */, once_cb, &counter);
+  source = g_main_context_find_source_by_id (NULL, source_id);
+  g_assert_nonnull (source);
+  g_source_ref (source);
+
+  /* Iterating the main context should dispatch the source, though we have to block. */
+  g_assert_cmpuint (counter, ==, 0);
+  g_main_context_iteration (NULL, TRUE);
+  g_assert_cmpuint (counter, ==, 1);
+
+  /* Iterating it again should not dispatch the source again. We add a second
+   * timeout and block until that is dispatched. Given the ordering guarantees,
+   * we should then know whether the first one would have re-dispatched by then. */
+  g_timeout_add_once (30 /* ms */, once_cb, &check_counter);
+  g_main_context_iteration (NULL, TRUE);
+  g_assert_cmpuint (check_counter, ==, 1);
+  g_assert_cmpuint (counter, ==, 1);
+  g_assert_true (g_source_is_destroyed (source));
+
+  g_clear_pointer (&source, g_source_unref);
+}
+
 static void
 test_steal_fd (void)
 {
@@ -2363,6 +2429,9 @@ main (int argc, char *argv[])
       g_test_add_data_func (name, GINT_TO_POINTER (i), test_maincontext_source_finalization_from_dispatch);
       g_free (name);
     }
+  g_test_add_func ("/maincontext/idle-once", test_maincontext_idle_once);
+  g_test_add_func ("/maincontext/timeout-once", test_maincontext_timeout_once);
+
   g_test_add_func ("/mainloop/basic", test_mainloop_basic);
   g_test_add_func ("/mainloop/timeouts", test_timeouts);
   g_test_add_func ("/mainloop/priorities", test_priorities);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]