[gnome-text-editor] bufferscheduler: add buffer scheduler helper
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-text-editor] bufferscheduler: add buffer scheduler helper
- Date: Thu, 15 Jul 2021 00:58:04 +0000 (UTC)
commit 14780ad5693956e50242a0f57ab005803b385264
Author: Christian Hergert <chergert redhat com>
Date: Wed Jul 14 17:48:04 2021 -0700
bufferscheduler: add buffer scheduler helper
This is about helping coalesce work together but still limiting it within
a period that will reduce impact on the main loop.
src/editor-buffer-scheduler.c | 283 ++++++++++++++++++++++++++++++++++++++++++
src/editor-buffer-scheduler.h | 52 ++++++++
src/meson.build | 1 +
3 files changed, 336 insertions(+)
---
diff --git a/src/editor-buffer-scheduler.c b/src/editor-buffer-scheduler.c
new file mode 100644
index 0000000..4def0bb
--- /dev/null
+++ b/src/editor-buffer-scheduler.c
@@ -0,0 +1,283 @@
+/* editor-buffer-scheduler.c
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#include "editor-buffer-scheduler.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.
+ *
+ * This is somewhat hacky because we don't have the widgets to work off of,
+ * only the buffers which have no frame clocks. So we guess the amount of
+ * time between frames based on the longest monitor delay (lowest FPS).
+ */
+
+#define QUANTA_USEC (G_USEC_PER_SEC / 1000L) /* 1 msec */
+
+typedef struct
+{
+ GSource source;
+ GQueue queue;
+ gint64 interval;
+ gsize last_handler_id;
+} EditorBufferScheduler;
+
+typedef struct
+{
+ GList link;
+ GtkTextBuffer *buffer;
+ EditorBufferCallback callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+ gint64 ready_time;
+ gsize id;
+} EditorBufferTask;
+
+static GSource *the_source;
+
+static void
+editor_buffer_task_free (EditorBufferTask *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_clear_object (&task->buffer);
+
+ g_slice_free (EditorBufferTask, task);
+}
+
+static EditorBufferTask *
+editor_buffer_task_new (GtkTextBuffer *buffer,
+ EditorBufferCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ EditorBufferTask *task;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+ g_return_val_if_fail (callback != NULL, NULL);
+
+ task = g_slice_new0 (EditorBufferTask);
+ task->link.data = task;
+ task->buffer = g_object_ref (buffer);
+ 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 (EditorBufferScheduler *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
+editor_buffer_scheduler_prepare (GSource *source,
+ int *timeout)
+{
+ *timeout = -1;
+ return FALSE;
+}
+
+static gboolean
+editor_buffer_scheduler_check (GSource *source)
+{
+ EditorBufferScheduler *self = (EditorBufferScheduler *)source;
+ EditorBufferTask *task = g_queue_peek_head (&self->queue);
+
+ return task != NULL && task->ready_time <= g_source_get_time (source);
+}
+
+static gboolean
+editor_buffer_scheduler_dispatch (GSource *source,
+ GSourceFunc source_func,
+ gpointer user_data)
+{
+ EditorBufferScheduler *self = (EditorBufferScheduler *)source;
+ gint64 current = g_source_get_time (source);
+ gint64 deadline = current + QUANTA_USEC;
+ gint64 interval = get_interval (self);
+
+ /* Try to process as many items within our quanta if they */
+ while (g_get_monotonic_time () < deadline)
+ {
+ EditorBufferTask *task = g_queue_peek_head (&self->queue);
+
+ if (task == NULL)
+ break;
+
+ /* Liveliness check to catch miss-use early */
+ g_assert (GTK_IS_TEXT_BUFFER (task->buffer));
+
+ g_queue_unlink (&self->queue, &task->link);
+
+ if (task->callback (task->buffer, deadline, task->user_data))
+ {
+ task->ready_time = current + interval;
+ g_queue_push_tail_link (&self->queue, &task->link);
+ break;
+ }
+
+ editor_buffer_task_free (task);
+ }
+
+ if (self->queue.head != NULL)
+ {
+ EditorBufferTask *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
+editor_buffer_scheduler_finalize (GSource *source)
+{
+ EditorBufferScheduler *self = (EditorBufferScheduler *)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 = {
+ editor_buffer_scheduler_prepare,
+ editor_buffer_scheduler_check,
+ editor_buffer_scheduler_dispatch,
+ editor_buffer_scheduler_finalize,
+};
+
+static EditorBufferScheduler *
+get_scheduler (void)
+{
+ if (the_source == NULL)
+ {
+ the_source = g_source_new (&source_funcs, sizeof (EditorBufferScheduler));
+ g_source_set_name (the_source, "EditorBufferScheduler");
+ 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 (EditorBufferScheduler *)the_source;
+}
+
+gsize
+editor_buffer_scheduler_add (GtkTextBuffer *buffer,
+ EditorBufferCallback callback,
+ gpointer user_data)
+{
+ return editor_buffer_scheduler_add_full (buffer, callback, user_data, NULL);
+}
+
+gsize
+editor_buffer_scheduler_add_full (GtkTextBuffer *buffer,
+ EditorBufferCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ EditorBufferScheduler *self;
+ EditorBufferTask *task;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), 0);
+ g_return_val_if_fail (callback != NULL, 0);
+
+ self = get_scheduler ();
+ task = editor_buffer_task_new (buffer, 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
+editor_buffer_scheduler_remove (gsize handler_id)
+{
+ EditorBufferScheduler *self;
+
+ g_return_if_fail (handler_id != 0);
+
+ self = get_scheduler ();
+
+ for (const GList *iter = self->queue.head; iter != NULL; iter = iter->next)
+ {
+ EditorBufferTask *task = iter->data;
+
+ if (task->id == handler_id)
+ {
+ g_queue_unlink (&self->queue, &task->link);
+ editor_buffer_task_free (task);
+ break;
+ }
+ }
+
+ if (self->queue.head != NULL)
+ {
+ EditorBufferTask *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/src/editor-buffer-scheduler.h b/src/editor-buffer-scheduler.h
new file mode 100644
index 0000000..212a0dd
--- /dev/null
+++ b/src/editor-buffer-scheduler.h
@@ -0,0 +1,52 @@
+/* editor-buffer-scheduler.h
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef gboolean (*EditorBufferCallback) (GtkTextBuffer *buffer,
+ gint64 deadline,
+ gpointer user_data);
+
+gsize editor_buffer_scheduler_add (GtkTextBuffer *buffer,
+ EditorBufferCallback callback,
+ gpointer user_data);
+gsize editor_buffer_scheduler_add_full (GtkTextBuffer *buffer,
+ EditorBufferCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify);
+void editor_buffer_scheduler_remove (gsize handler_id);
+
+static inline void
+editor_buffer_scheduler_clear (gsize *handler_id_ptr)
+{
+ gsize val = *handler_id_ptr;
+
+ if (val)
+ {
+ *handler_id_ptr = 0;
+ editor_buffer_scheduler_remove (val);
+ }
+}
+
+G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index 2316460..a11eab6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -5,6 +5,7 @@ editor_sources = [
'editor-application-actions.c',
'editor-binding-group.c',
'editor-buffer-monitor.c',
+ 'editor-buffer-scheduler.c',
'editor-document.c',
'editor-info-bar.c',
'editor-frame-source.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]