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 g_periodic_new g_periodic_get_hz -g_periodic_get_priority +g_periodic_get_high_priority +g_periodic_get_low_priority 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 */ GSList *repairs; /* List */ @@ -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,21 +693,39 @@ 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) @@ -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,