summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/reference/gio/gio-sections.txt3
-rw-r--r--gio/gio.symbols3
-rw-r--r--gio/gperiodic.c155
-rw-r--r--gio/gperiodic.h6
4 files changed, 151 insertions, 16 deletions
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 04daff768..061a4c376 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -2911,7 +2911,8 @@ GPeriodic
<SUBSECTION>
g_periodic_new
g_periodic_get_hz
-g_periodic_get_priority
+g_periodic_get_high_priority
+g_periodic_get_low_priority
<SUBSECTION>
GPeriodicTickFunc
g_periodic_add
diff --git a/gio/gio.symbols b/gio/gio.symbols
index 19fa68ce4..cccb48841 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1958,7 +1958,8 @@ g_simple_action_set_enabled
g_periodic_block
g_periodic_damaged
g_periodic_get_hz
-g_periodic_get_priority
+g_periodic_get_high_priority
+g_periodic_get_low_priority
g_periodic_get_type
g_periodic_new
g_periodic_add
diff --git a/gio/gperiodic.c b/gio/gperiodic.c
index 31a8bf217..2c1b6e7c9 100644
--- a/gio/gperiodic.c
+++ b/gio/gperiodic.c
@@ -43,6 +43,44 @@
* while the clock is not running, in which case the rate of repairs
* will be rate limited as if the clock were running.
*
+ * #GPeriodic has a configurable priority range consisting of a high and
+ * low value. Other sources with a priority higher than the high value
+ * might starve #GPeriodic and sources with the priority lower than the
+ * low value may be starved by #GPeriodic.
+ *
+ * #GPeriodic will engage in dynamic scheduling with respect to sources
+ * that have their priorities within the high to low range. A given
+ * #GPeriodic will ensure that the events dispatched from itself are
+ * generally using less than 50% of the CPU (on average) if other tasks
+ * are pending. If no other sources within the range are pending then
+ * #GPeriodic will use up to all of the available CPU (which can lead to
+ * starvation of lower-priority sources, as mentioned above). The 50%
+ * figure is entirely arbitrary and may change or become configurable in
+ * the future.
+ *
+ * For example, if a #GPeriodic has been set to run at 10Hz and a
+ * particular iteration uses 140ms of time, then 2 ticks will be
+ * "skipped" to give other sources a chance to run (ie: the next tick
+ * will occur 300ms later rather than 100ms later, giving 160ms of time
+ * for other sources).
+ *
+ * This means that the high priority value for #GPeriodic should be set
+ * quite high (above anything else) and the low priority value for
+ * #GPeriodic should be set lower than everything except true "idle"
+ * handlers (ie: things that you want to run only when the program is
+ * truly idle).
+ *
+ * #GPeriodic generally assumes that although the things attached to it
+ * may be poorly behaved in terms of non-yielding behaviour (either
+ * individually or in aggregate), the other sources on the main loop
+ * should be "well behaved". Other sources should try not to block the
+ * CPU for a substantial portion of the periodic interval.
+ *
+ * The sources attached to a #GPeriodic are permitted to be somewhat
+ * less well-behaved because they are generally rendering the UI for the
+ * user (which should be done smoothly) and also because they will be
+ * throttled by #GPeriodic.
+ *
* #GPeriodic is intended to be used as a paint clock for managing
* geometry updates and painting of windows.
*
@@ -87,6 +125,9 @@ struct _GPeriodic
guint64 last_run;
guint blocked;
guint hz;
+ guint skip_frames;
+ guint stop_skip_id;
+ gint low_priority;
GSList *ticks; /* List<GPeriodicTick> */
GSList *repairs; /* List<GPeriodicRepair> */
@@ -104,7 +145,8 @@ enum
{
PROP_NONE,
PROP_HZ,
- PROP_PRIORITY
+ PROP_HIGH_PRIORITY,
+ PROP_LOW_PRIORITY
};
static guint g_periodic_tick;
@@ -116,11 +158,29 @@ g_periodic_get_microticks (GPeriodic *periodic)
return g_source_get_time (periodic->source) * periodic->hz;
}
+static gboolean
+g_periodic_stop_skip (gpointer data)
+{
+ GPeriodic *periodic = data;
+
+ periodic->skip_frames = 0;
+
+ g_message ("Skipping frames ends");
+
+ periodic->stop_skip_id = 0;
+
+ return FALSE;
+}
+
static void
g_periodic_run (GPeriodic *periodic)
{
+ gint64 start, usec;
+
g_assert (periodic->blocked == 0);
+ start = g_get_monotonic_time ();
+
if (periodic->ticks)
{
guint64 microseconds;
@@ -160,6 +220,43 @@ g_periodic_run (GPeriodic *periodic)
g_signal_emit (periodic, g_periodic_repair, 0);
periodic->in_repair = FALSE;
}
+
+ usec = g_get_monotonic_time () - start;
+ g_assert (usec >= 0);
+
+ /* Take the time it took to render, multiply by two and round down to
+ * a whole number of frames. This ensures that we don't take more
+ * than 50% of the CPU with rendering.
+ *
+ * Examples (at 10fps for easy math. 1 frame = 100ms):
+ *
+ * 0-49ms to render: no frames skipped
+ *
+ * We used less than half of the time to render. OK. We will run
+ * the next frame 100ms after this one ran (no skips).
+ *
+ * 50-99ms to render: 1 frame skipped
+ *
+ * We used more than half the time. Skip one frame so that we run
+ * 200ms later rather than 100ms later. We already used up to
+ * 99ms worth of that 200ms window, so that gives 101ms for other
+ * things to run (50%). For figures closer to 50ms the other
+ * stuff will actually get more than 50%.
+ *
+ * 100-150ms to render: 2 frames skipped, etc.
+ */
+ periodic->skip_frames = 2 * usec * periodic->hz / 1000000;
+
+ if (periodic->skip_frames)
+ {
+ g_message ("Slow painting; (possibly) skipping %d frames\n",
+ periodic->skip_frames);
+
+ if (periodic->stop_skip_id == 0)
+ periodic->stop_skip_id = g_idle_add_full (periodic->low_priority,
+ g_periodic_stop_skip,
+ periodic, NULL);
+ }
}
static gboolean
@@ -173,7 +270,7 @@ g_periodic_prepare (GSource *source,
{
gint64 remaining;
- remaining = periodic->last_run + 1000000 -
+ remaining = periodic->last_run + 1000000 * (1 + periodic->skip_frames) -
g_periodic_get_microticks (periodic);
if (remaining > 0)
@@ -216,7 +313,7 @@ g_periodic_check (GSource *source)
guint64 now = g_periodic_get_microticks (periodic);
/* Run if it's not too soon. */
- return !(now < periodic->last_run + 1000000);
+ return !(now < periodic->last_run + 1000000 * (periodic->skip_frames + 1));
}
else
@@ -271,10 +368,14 @@ g_periodic_get_property (GObject *object, guint prop_id,
switch (prop_id)
{
- case PROP_PRIORITY:
+ case PROP_HIGH_PRIORITY:
g_value_set_int (value, g_source_get_priority (periodic->source));
break;
+ case PROP_LOW_PRIORITY:
+ g_value_set_int (value, periodic->low_priority);
+ break;
+
case PROP_HZ:
g_value_set_uint (value, periodic->hz);
break;
@@ -292,10 +393,14 @@ g_periodic_set_property (GObject *object, guint prop_id,
switch (prop_id)
{
- case PROP_PRIORITY:
+ case PROP_HIGH_PRIORITY:
g_source_set_priority (periodic->source, g_value_get_int (value));
break;
+ case PROP_LOW_PRIORITY:
+ periodic->low_priority = g_value_get_int (value);
+ break;
+
case PROP_HZ:
periodic->hz = g_value_get_uint (value);
break;
@@ -351,11 +456,17 @@ g_periodic_class_init (GObjectClass *class)
1, 120, 1, G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
- g_object_class_install_property (class, PROP_PRIORITY,
- g_param_spec_int ("priority", "priority level",
+ g_object_class_install_property (class, PROP_HIGH_PRIORITY,
+ g_param_spec_int ("high-priority", "high priority level",
"the GSource priority level to run at",
G_MININT, G_MAXINT, 0, G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (class, PROP_LOW_PRIORITY,
+ g_param_spec_int ("low-priority", "low priority level",
+ "ignore tasks below this priority level",
+ G_MININT, G_MAXINT, 0, G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
/**
@@ -582,22 +693,40 @@ g_periodic_get_hz (GPeriodic *periodic)
}
/**
- * g_periodic_get_priority:
+ * g_periodic_get_high_priority:
* @periodic: a #GPeriodic clock
*
* Gets the #GSource priority of the clock.
*
- * Returns: the priority level
+ * Returns: the high priority level
*
* Since: 2.28
**/
gint
-g_periodic_get_priority (GPeriodic *periodic)
+g_periodic_get_high_priority (GPeriodic *periodic)
{
return g_source_get_priority (periodic->source);
}
/**
+ * g_periodic_get_low_priority:
+ * @periodic: a #GPeriodic clock
+ *
+ * Gets the priority level that #GPeriodic uses to check for mainloop
+ * inactivity. Other sources scheduled below this level of priority are
+ * effectively ignored by #GPeriodic and may be starved.
+ *
+ * Returns: the low priority level
+ *
+ * Since: 2.28
+ **/
+gint
+g_periodic_get_low_priority (GPeriodic *periodic)
+{
+ return periodic->low_priority;
+}
+
+/**
* g_periodic_new:
* @hz: the frequency of the new clock in Hz (between 1 and 120)
* @priority: the #GSource priority to run at
@@ -618,12 +747,14 @@ g_periodic_get_priority (GPeriodic *periodic)
**/
GPeriodic *
g_periodic_new (guint hz,
- gint priority)
+ gint high_priority,
+ gint low_priority)
{
g_return_val_if_fail (1 <= hz && hz <= 120, NULL);
return g_object_new (G_TYPE_PERIODIC,
"hz", hz,
- "priority", priority,
+ "high-priority", high_priority,
+ "low-priority", low_priority,
NULL);
}
diff --git a/gio/gperiodic.h b/gio/gperiodic.h
index b48f60eee..5bff03566 100644
--- a/gio/gperiodic.h
+++ b/gio/gperiodic.h
@@ -45,9 +45,11 @@ typedef void (* GPeriodicRepairFunc) (GPeriod
GType g_periodic_get_type (void);
GPeriodic * g_periodic_new (guint hz,
- gint priority);
+ gint high_priority,
+ gint low_priority);
guint g_periodic_get_hz (GPeriodic *periodic);
-gint g_periodic_get_priority (GPeriodic *periodic);
+gint g_periodic_get_high_priority (GPeriodic *periodic);
+gint g_periodic_get_low_priority (GPeriodic *periodic);
guint g_periodic_add (GPeriodic *periodic,
GPeriodicTickFunc callback,