[gtksourceview/wip/chergert/scheduler] scheduler: add scheduler background worker helper
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/chergert/scheduler] scheduler: add scheduler background worker helper
- Date: Tue, 10 Aug 2021 22:32:59 +0000 (UTC)
commit 0d9c1b03b0f8b393b924c6d2941aab689d006968
Author: Christian Hergert <chergert redhat com>
Date: Tue Aug 10 15:32:46 2021 -0700
scheduler: add scheduler background worker helper
This is to be used by applications (and eventually internally with
syntax highlighting) to manage buffer updates in what will allow us to make
a more fair fashion along with reducing frame stuttering under load.
docs/reference/gtksourceview-5.0-sections.txt | 8 +
gtksourceview/gtksource.h | 1 +
gtksourceview/gtksourcescheduler.c | 279 ++++++++++++++++++++++++++
gtksourceview/gtksourcescheduler.h | 59 ++++++
gtksourceview/meson.build | 2 +
5 files changed, 349 insertions(+)
---
diff --git a/docs/reference/gtksourceview-5.0-sections.txt b/docs/reference/gtksourceview-5.0-sections.txt
index 12f89b7c..8177bf93 100644
--- a/docs/reference/gtksourceview-5.0-sections.txt
+++ b/docs/reference/gtksourceview-5.0-sections.txt
@@ -1027,6 +1027,14 @@ GTK_SOURCE_VERSION_MIN_REQUIRED
GTK_SOURCE_VERSION_MAX_ALLOWED
</SECTION>
+<SECTION>
+<FILE>scheduler</FILE>
+GtkSourceSchedulerCallback
+gtk_source_scheduler_add
+gtk_source_scheduler_add_full
+gtk_source_scheduler_remove
+</SECTION>
+
<SECTION>
<FILE>view</FILE>
GtkSourceView
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index e80198a9..996240c9 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -48,6 +48,7 @@
#include "gtksourcemarkattributes.h"
#include "gtksourceprintcompositor.h"
#include "gtksourceregion.h"
+#include "gtksourcescheduler.h"
#include "gtksourcesearchcontext.h"
#include "gtksourcesearchsettings.h"
#include "gtksourcesnippet.h"
diff --git a/gtksourceview/gtksourcescheduler.c b/gtksourceview/gtksourcescheduler.c
new file mode 100644
index 00000000..5da114b8
--- /dev/null
+++ b/gtksourceview/gtksourcescheduler.c
@@ -0,0 +1,279 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#include "gtksourcescheduler.h"
+
+/* The goal of this GSource is to let us pile on a bunch of background work but
+ * only do a small amount of it at a time per-frame cycle. This becomes more
+ * important when you have multiple documents open all competing to do
+ * background work and potentially stalling the main loop.
+ *
+ * Instead, we only do 1 msec of work at a time and then wait for the next
+ * frame to come in.
+ *
+ * Since we don't have access to the widgets, we need to base this work off the
+ * shortest delay between frames among the available monitors. Some aliasing is
+ * still possible depending on per-monitor scanouts, but we already have that
+ * issue without this.
+ */
+
+#define _1_MSEC (G_USEC_PER_SEC / 1000L)
+
+typedef struct
+{
+ GSource source;
+ GQueue queue;
+ gint64 interval;
+ gsize last_handler_id;
+} GtkSourceScheduler;
+
+typedef struct
+{
+ GList link;
+ GtkSourceSchedulerCallback callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+ gint64 ready_time;
+ gsize id;
+} GtkSourceTask;
+
+static GSource *the_source;
+
+static void
+gtk_source_task_free (GtkSourceTask *task)
+{
+ g_assert (task != NULL);
+ g_assert (task->link.data == (gpointer)task);
+ g_assert (task->link.next == NULL);
+ g_assert (task->link.prev == NULL);
+ g_assert (task->callback != NULL);
+
+ if (task->notify != NULL)
+ {
+ task->notify (task->user_data);
+ }
+
+ g_slice_free (GtkSourceTask, task);
+}
+
+static GtkSourceTask *
+gtk_source_task_new (GtkSourceSchedulerCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ GtkSourceTask *task;
+
+ g_return_val_if_fail (callback != NULL, NULL);
+
+ task = g_slice_new0 (GtkSourceTask);
+ task->link.data = task;
+ task->callback = callback;
+ task->user_data = user_data;
+ task->notify = notify;
+ task->ready_time = 0; /* Now */
+ task->id = 0;
+
+ return task;
+}
+
+static gint64
+get_interval (GtkSourceScheduler *self)
+{
+ if G_UNLIKELY (self->interval == 0)
+ {
+ GdkDisplay *display = gdk_display_get_default ();
+ GListModel *monitors = gdk_display_get_monitors (display);
+ guint n_items = g_list_model_get_n_items (monitors);
+ gint64 lowest_interval = 60000;
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr(GdkMonitor) monitor = g_list_model_get_item (monitors, i);
+ gint64 interval = gdk_monitor_get_refresh_rate (monitor);
+
+ if (interval != 0 && interval < lowest_interval)
+ lowest_interval = interval;
+ }
+
+ self->interval = (double)G_USEC_PER_SEC / (double)lowest_interval * 1000.0;
+ }
+
+ return self->interval;
+}
+
+static gboolean
+gtk_source_scheduler_prepare (GSource *source,
+ int *timeout)
+{
+ *timeout = -1;
+ return FALSE;
+}
+
+static gboolean
+gtk_source_scheduler_check (GSource *source)
+{
+ GtkSourceScheduler *self = (GtkSourceScheduler *)source;
+ GtkSourceTask *task = g_queue_peek_head (&self->queue);
+
+ return task != NULL && task->ready_time <= g_source_get_time (source);
+}
+
+static gboolean
+gtk_source_scheduler_dispatch (GSource *source,
+ GSourceFunc source_func,
+ gpointer user_data)
+{
+ GtkSourceScheduler *self = (GtkSourceScheduler *)source;
+ gint64 current = g_source_get_time (source);
+ gint64 deadline = current + _1_MSEC;
+ gint64 interval = get_interval (self);
+
+ /* Try to process as many items within our quanta if they */
+ while (g_get_monotonic_time () < deadline)
+ {
+ GtkSourceTask *task = g_queue_peek_head (&self->queue);
+
+ if (task == NULL)
+ break;
+
+ g_queue_unlink (&self->queue, &task->link);
+
+ if (task->callback (deadline, task->user_data))
+ {
+ task->ready_time = current + interval;
+ g_queue_push_tail_link (&self->queue, &task->link);
+ break;
+ }
+
+ gtk_source_task_free (task);
+ }
+
+ if (self->queue.head != NULL)
+ {
+ GtkSourceTask *task = g_queue_peek_head (&self->queue);
+ g_source_set_ready_time (source, task->ready_time);
+ return G_SOURCE_CONTINUE;
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_source_scheduler_finalize (GSource *source)
+{
+ GtkSourceScheduler *self = (GtkSourceScheduler *)source;
+
+ g_assert (self->queue.length == 0);
+ g_assert (self->queue.head == NULL);
+ g_assert (self->queue.tail == NULL);
+
+ if (source == the_source)
+ {
+ the_source = NULL;
+ }
+}
+
+static GSourceFuncs source_funcs = {
+ gtk_source_scheduler_prepare,
+ gtk_source_scheduler_check,
+ gtk_source_scheduler_dispatch,
+ gtk_source_scheduler_finalize,
+};
+
+static GtkSourceScheduler *
+get_scheduler (void)
+{
+ if (the_source == NULL)
+ {
+ the_source = g_source_new (&source_funcs, sizeof (GtkSourceScheduler));
+ g_source_set_name (the_source, "GtkSourceScheduler");
+ g_source_set_priority (the_source, G_PRIORITY_LOW);
+ g_source_set_ready_time (the_source, 0);
+ g_source_attach (the_source, g_main_context_default ());
+ g_source_unref (the_source);
+ }
+
+ return (GtkSourceScheduler *)the_source;
+}
+
+gsize
+gtk_source_scheduler_add (GtkSourceSchedulerCallback callback,
+ gpointer user_data)
+{
+ return gtk_source_scheduler_add_full (callback, user_data, NULL);
+}
+
+gsize
+gtk_source_scheduler_add_full (GtkSourceSchedulerCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ GtkSourceScheduler *self;
+ GtkSourceTask *task;
+
+ g_return_val_if_fail (callback != NULL, 0);
+
+ self = get_scheduler ();
+ task = gtk_source_task_new (callback, user_data, notify);
+ task->id = ++self->last_handler_id;
+
+ /* Request progress immediately */
+ g_queue_push_head_link (&self->queue, &task->link);
+ g_source_set_ready_time ((GSource *)self, g_source_get_time ((GSource *)self));
+
+ return task->id;
+}
+
+void
+gtk_source_scheduler_remove (gsize handler_id)
+{
+ GtkSourceScheduler *self;
+
+ g_return_if_fail (handler_id != 0);
+
+ self = get_scheduler ();
+
+ for (const GList *iter = self->queue.head; iter != NULL; iter = iter->next)
+ {
+ GtkSourceTask *task = iter->data;
+
+ if (task->id == handler_id)
+ {
+ g_queue_unlink (&self->queue, &task->link);
+ gtk_source_task_free (task);
+ break;
+ }
+ }
+
+ if (self->queue.head != NULL)
+ {
+ GtkSourceTask *task = g_queue_peek_head (&self->queue);
+ g_source_set_ready_time ((GSource *)self, task->ready_time);
+ }
+ else
+ {
+ g_source_destroy ((GSource *)self);
+ }
+}
diff --git a/gtksourceview/gtksourcescheduler.h b/gtksourceview/gtksourcescheduler.h
new file mode 100644
index 00000000..9e89a29a
--- /dev/null
+++ b/gtksourceview/gtksourcescheduler.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+# error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+typedef gboolean (*GtkSourceSchedulerCallback) (gint64 deadline,
+ gpointer user_data);
+
+GTK_SOURCE_AVAILABLE_IN_5_2
+gsize gtk_source_scheduler_add (GtkSourceSchedulerCallback callback,
+ gpointer user_data);
+GTK_SOURCE_AVAILABLE_IN_5_2
+gsize gtk_source_scheduler_add_full (GtkSourceSchedulerCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify);
+GTK_SOURCE_AVAILABLE_IN_5_2
+void gtk_source_scheduler_remove (gsize handler_id);
+
+static inline void
+gtk_source_scheduler_clear (gsize *handler_id_ptr)
+{
+ gsize val = *handler_id_ptr;
+
+ if (val)
+ {
+ *handler_id_ptr = 0;
+ gtk_source_scheduler_remove (val);
+ }
+}
+
+G_END_DECLS
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index 72d140d9..74ad73a0 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -35,6 +35,7 @@ core_public_h = files([
'gtksourcemarkattributes.h',
'gtksourceprintcompositor.h',
'gtksourceregion.h',
+ 'gtksourcescheduler.h',
'gtksourcesearchcontext.h',
'gtksourcesearchsettings.h',
'gtksourcesnippet.h',
@@ -83,6 +84,7 @@ core_public_c = files([
'gtksourcemarkattributes.c',
'gtksourceprintcompositor.c',
'gtksourceregion.c',
+ 'gtksourcescheduler.c',
'gtksourcesearchcontext.c',
'gtksourcesearchsettings.c',
'gtksourcesnippet.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]