From 87e28a6062b1423314ce4f0376fe6c5c7c99427b Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 16 Jun 2023 13:08:31 -0700 Subject: [PATCH] libsysprof-gtk: use timeseries for rendering chart marks This allows us to pre-calculate the values and re-use them, while also allowing for us to avoid inflating GObject's for marks unless it becomes necessary to access additional information. --- .../sysprof-mark-chart-item-private.h | 16 +- src/libsysprof-gtk/sysprof-mark-chart-item.c | 143 +++++++++++++---- src/libsysprof-gtk/sysprof-mark-chart-row.c | 147 ++++++++++++++---- 3 files changed, 246 insertions(+), 60 deletions(-) diff --git a/src/libsysprof-gtk/sysprof-mark-chart-item-private.h b/src/libsysprof-gtk/sysprof-mark-chart-item-private.h index 01c5d266..cca28a65 100644 --- a/src/libsysprof-gtk/sysprof-mark-chart-item-private.h +++ b/src/libsysprof-gtk/sysprof-mark-chart-item-private.h @@ -30,10 +30,16 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (SysprofMarkChartItem, sysprof_mark_chart_item, SYSPROF, MARK_CHART_ITEM, GObject) -SysprofMarkChartItem *sysprof_mark_chart_item_new (SysprofSession *session, - SysprofMarkCatalog *catalog); -SysprofMarkCatalog *sysprof_mark_chart_item_get_catalog (SysprofMarkChartItem *self); -SysprofSession *sysprof_mark_chart_item_get_session (SysprofMarkChartItem *self); -GListModel *sysprof_mark_chart_item_get_marks (SysprofMarkChartItem *self); +SysprofMarkChartItem *sysprof_mark_chart_item_new (SysprofSession *session, + SysprofMarkCatalog *catalog); +SysprofMarkCatalog *sysprof_mark_chart_item_get_catalog (SysprofMarkChartItem *self); +SysprofSession *sysprof_mark_chart_item_get_session (SysprofMarkChartItem *self); +void sysprof_mark_chart_item_load_time_series (SysprofMarkChartItem *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SysprofTimeSeries *sysprof_mark_chart_item_load_time_series_finish (SysprofMarkChartItem *self, + GAsyncResult *result, + GError **error); G_END_DECLS diff --git a/src/libsysprof-gtk/sysprof-mark-chart-item.c b/src/libsysprof-gtk/sysprof-mark-chart-item.c index 92795f3d..b9fe26e5 100644 --- a/src/libsysprof-gtk/sysprof-mark-chart-item.c +++ b/src/libsysprof-gtk/sysprof-mark-chart-item.c @@ -27,30 +27,34 @@ struct _SysprofMarkChartItem GObject parent_instance; SysprofSession *session; SysprofMarkCatalog *catalog; - GtkFilterListModel *filtered; }; enum { PROP_0, PROP_SESSION, PROP_CATALOG, - PROP_MARKS, N_PROPS }; +enum { + CHANGED, + N_SIGNALS +}; + G_DEFINE_FINAL_TYPE (SysprofMarkChartItem, sysprof_mark_chart_item, G_TYPE_OBJECT) -static GParamSpec *properties [N_PROPS]; +static GParamSpec *properties[N_PROPS]; +static guint signals[N_SIGNALS]; static void -sysprof_mark_chart_item_constructed (GObject *object) +sysprof_mark_chart_item_session_notify_selection_cb (SysprofMarkChartItem *self, + GParamSpec *pspec, + SysprofSession *session) { - SysprofMarkChartItem *self = (SysprofMarkChartItem *)object; + g_assert (SYSPROF_IS_MARK_CHART_ITEM (self)); + g_assert (SYSPROF_IS_SESSION (session)); - self->filtered = gtk_filter_list_model_new (g_object_ref (G_LIST_MODEL (self->catalog)), NULL); - g_object_bind_property (self->session, "filter", self->filtered, "filter", G_BINDING_SYNC_CREATE); - - G_OBJECT_CLASS (sysprof_mark_chart_item_parent_class)->constructed (object); + g_signal_emit (self, signals[CHANGED], 0); } static void @@ -78,10 +82,6 @@ sysprof_mark_chart_item_get_property (GObject *object, g_value_set_object (value, sysprof_mark_chart_item_get_catalog (self)); break; - case PROP_MARKS: - g_value_set_object (value, sysprof_mark_chart_item_get_marks (self)); - break; - case PROP_SESSION: g_value_set_object (value, sysprof_mark_chart_item_get_session (self)); break; @@ -107,6 +107,11 @@ sysprof_mark_chart_item_set_property (GObject *object, case PROP_SESSION: self->session = g_value_dup_object (value); + g_signal_connect_object (self->session, + "notify::selection", + G_CALLBACK (sysprof_mark_chart_item_session_notify_selection_cb), + self, + G_CONNECT_SWAPPED); break; default: @@ -119,7 +124,6 @@ sysprof_mark_chart_item_class_init (SysprofMarkChartItemClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->constructed = sysprof_mark_chart_item_constructed; object_class->dispose = sysprof_mark_chart_item_dispose; object_class->get_property = sysprof_mark_chart_item_get_property; object_class->set_property = sysprof_mark_chart_item_set_property; @@ -129,17 +133,20 @@ sysprof_mark_chart_item_class_init (SysprofMarkChartItemClass *klass) SYSPROF_TYPE_MARK_CATALOG, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - properties[PROP_MARKS] = - g_param_spec_object ("marks", NULL, NULL, - G_TYPE_LIST_MODEL, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - properties[PROP_SESSION] = g_param_spec_object ("session", NULL, NULL, SYSPROF_TYPE_SESSION, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); + + signals[CHANGED] = g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0); } static void @@ -160,14 +167,6 @@ sysprof_mark_chart_item_new (SysprofSession *session, NULL); } -GListModel * -sysprof_mark_chart_item_get_marks (SysprofMarkChartItem *self) -{ - g_return_val_if_fail (SYSPROF_IS_MARK_CHART_ITEM (self), NULL); - - return G_LIST_MODEL (self->filtered); -} - SysprofMarkCatalog * sysprof_mark_chart_item_get_catalog (SysprofMarkChartItem *self) { @@ -179,3 +178,93 @@ sysprof_mark_chart_item_get_session (SysprofMarkChartItem *self) { return self->session; } + +typedef struct _LoadTimeSeries +{ + GListModel *model; + SysprofTimeSpan time_span; +} LoadTimeSeries; + +static void +load_time_series_free (LoadTimeSeries *state) +{ + g_clear_object (&state->model); + g_free (state); +} + +static void +load_time_series_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + LoadTimeSeries *state = task_data; + g_autoptr(SysprofTimeSeries) series = NULL; + guint n_items; + + g_assert (G_IS_TASK (task)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + series = sysprof_time_series_new (state->model, state->time_span); + n_items = g_list_model_get_n_items (state->model); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentMark) mark = g_list_model_get_item (state->model, i); + SysprofTimeSpan time_span; + + time_span.begin_nsec = sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (mark)); + time_span.end_nsec = time_span.begin_nsec + sysprof_document_mark_get_duration (mark); + + sysprof_time_series_add (series, time_span, i); + } + + sysprof_time_series_sort (series); + + g_task_return_pointer (task, + g_steal_pointer (&series), + (GDestroyNotify)sysprof_time_series_unref); +} + +void +sysprof_mark_chart_item_load_time_series (SysprofMarkChartItem *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + LoadTimeSeries *state; + + g_return_if_fail (SYSPROF_IS_MARK_CHART_ITEM (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_mark_chart_item_load_time_series); + + if (self->catalog == NULL || self->session == NULL) + { + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "No available data to generate"); + return; + } + + state = g_new0 (LoadTimeSeries, 1); + state->model = g_object_ref (G_LIST_MODEL (self->catalog)); + state->time_span = *sysprof_session_get_selected_time (self->session); + + g_task_set_task_data (task, state, (GDestroyNotify)load_time_series_free); + g_task_run_in_thread (task, load_time_series_worker); +} + +SysprofTimeSeries * +sysprof_mark_chart_item_load_time_series_finish (SysprofMarkChartItem *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_CHART_ITEM (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} diff --git a/src/libsysprof-gtk/sysprof-mark-chart-row.c b/src/libsysprof-gtk/sysprof-mark-chart-row.c index 6097e7cc..22b627ef 100644 --- a/src/libsysprof-gtk/sysprof-mark-chart-row.c +++ b/src/libsysprof-gtk/sysprof-mark-chart-row.c @@ -26,8 +26,11 @@ struct _SysprofMarkChartRow { - GtkWidget parent_instance; + GtkWidget parent_instance; + GCancellable *cancellable; + SysprofTimeSeries *series; SysprofMarkChartItem *item; + guint update_source; }; enum { @@ -40,6 +43,25 @@ G_DEFINE_FINAL_TYPE (SysprofMarkChartRow, sysprof_mark_chart_row, GTK_TYPE_WIDGE static GParamSpec *properties [N_PROPS]; +static void +cancel_and_clear (GCancellable **cancellable) +{ + g_cancellable_cancel (*cancellable); + g_clear_object (cancellable); +} + +static void +sysprof_mark_chart_row_set_series (SysprofMarkChartRow *self, + SysprofTimeSeries *series) +{ + g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + + g_clear_pointer (&self->series, sysprof_time_series_unref); + self->series = series ? sysprof_time_series_ref (series) : NULL; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + static void sysprof_mark_chart_row_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) @@ -48,37 +70,36 @@ sysprof_mark_chart_row_snapshot (GtkWidget *widget, static const GdkRGBA blue = {0,0,1,1}; static const GdkRGBA red = {1,0,0,1}; static const GdkRGBA white = {1,1,1,1}; + const SysprofTimeSeriesValue *values; GListModel *model; PangoLayout *layout; - guint n_items; + guint n_values; int width; int height; g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); g_assert (GTK_IS_SNAPSHOT (snapshot)); - if (self->item == NULL) + if (self->series == NULL) return; width = gtk_widget_get_width (widget); height = gtk_widget_get_height (widget); - model = sysprof_mark_chart_item_get_marks (self->item); - n_items = g_list_model_get_n_items (model); + model = sysprof_time_series_get_model (self->series); + values = sysprof_time_series_get_values (self->series, &n_values); layout = gtk_widget_create_pango_layout (widget, NULL); - for (guint i = 0; i < n_items; i++) + for (guint i = 0; i < n_values; i++) { - g_autoptr(SysprofDocumentMark) mark = g_list_model_get_item (model, i); - double begin, end; + const SysprofTimeSeriesValue *v = &values[i]; - sysprof_document_mark_get_time_fraction (mark, &begin, &end); - - if (begin == end) + if (v->begin == v->end) { gtk_snapshot_save (snapshot); - gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (begin*width, height/2)); + gtk_snapshot_translate (snapshot, + &GRAPHENE_POINT_INIT (v->begin * width, height / 2)); gtk_snapshot_rotate (snapshot, 45.f); gtk_snapshot_append_color (snapshot, &red, @@ -87,22 +108,34 @@ sysprof_mark_chart_row_snapshot (GtkWidget *widget, } else { - const char *message = sysprof_document_mark_get_message (mark); - gtk_snapshot_append_color (snapshot, &blue, - &GRAPHENE_RECT_INIT (begin*width, 0, end*width, height)); + &GRAPHENE_RECT_INIT (v->begin * width, + 0, + v->end * width, + height)); - if (message) + if (i + 1 < n_values && values[i+1].begin > v->end) { - pango_layout_set_text (layout, message, -1); + g_autoptr(SysprofDocumentMark) mark = g_list_model_get_item (model, v->index); + const char *message = sysprof_document_mark_get_message (mark); - gtk_snapshot_save (snapshot); - gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (begin*width, 0, end*width, height)); - gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (begin*width, 0)); - gtk_snapshot_append_layout (snapshot, layout, &white); - gtk_snapshot_pop (snapshot); - gtk_snapshot_restore (snapshot); + if (message && message[0]) + { + pango_layout_set_text (layout, message, -1); + + gtk_snapshot_save (snapshot); + gtk_snapshot_push_clip (snapshot, + &GRAPHENE_RECT_INIT (v->begin * width, + 0, + v->end * width, + height)); + gtk_snapshot_translate (snapshot, + &GRAPHENE_POINT_INIT (v->begin * width, 0)); + gtk_snapshot_append_layout (snapshot, layout, &white); + gtk_snapshot_pop (snapshot); + gtk_snapshot_restore (snapshot); + } } } } @@ -110,11 +143,64 @@ sysprof_mark_chart_row_snapshot (GtkWidget *widget, g_object_unref (layout); } +static void +sysprof_mark_chart_row_load_time_series_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofMarkChartItem *item = (SysprofMarkChartItem *)object; + g_autoptr(SysprofMarkChartRow) self = user_data; + g_autoptr(SysprofTimeSeries) series = NULL; + + g_assert (SYSPROF_IS_MARK_CHART_ITEM (item)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + + series = sysprof_mark_chart_item_load_time_series_finish (item, result, NULL); + + sysprof_mark_chart_row_set_series (self, series); +} + +static gboolean +sysprof_mark_chart_row_dispatch_update (gpointer user_data) +{ + SysprofMarkChartRow *self = user_data; + + g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + + g_clear_handle_id (&self->update_source, g_source_remove); + cancel_and_clear (&self->cancellable); + + if (self->item != NULL) + { + self->cancellable = g_cancellable_new (); + sysprof_mark_chart_item_load_time_series (self->item, + self->cancellable, + sysprof_mark_chart_row_load_time_series_cb, + g_object_ref (self)); + } + + return G_SOURCE_REMOVE; +} + static void sysprof_mark_chart_row_queue_update (SysprofMarkChartRow *self) { g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + cancel_and_clear (&self->cancellable); + g_clear_handle_id (&self->update_source, g_source_remove); + self->update_source = g_idle_add (sysprof_mark_chart_row_dispatch_update, self); +} + +static void +sysprof_mark_chart_row_item_changed_cb (SysprofMarkChartRow *self, + SysprofMarkChartItem *item) +{ + g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + g_assert (SYSPROF_IS_MARK_CHART_ITEM (item)); + + sysprof_mark_chart_row_queue_update (self); } static void @@ -122,6 +208,8 @@ sysprof_mark_chart_row_dispose (GObject *object) { SysprofMarkChartRow *self = (SysprofMarkChartRow *)object; + g_clear_handle_id (&self->update_source, g_source_remove); + sysprof_mark_chart_row_set_item (self, NULL); G_OBJECT_CLASS (sysprof_mark_chart_row_parent_class)->dispose (object); @@ -208,17 +296,20 @@ sysprof_mark_chart_row_set_item (SysprofMarkChartRow *self, if (self->item == item) return; + cancel_and_clear (&self->cancellable); + g_clear_handle_id (&self->update_source, g_source_remove); + if (self->item) - g_signal_handlers_disconnect_by_func (sysprof_mark_chart_item_get_marks (self->item), - G_CALLBACK (sysprof_mark_chart_row_queue_update), + g_signal_handlers_disconnect_by_func (self->item, + G_CALLBACK (sysprof_mark_chart_row_item_changed_cb), self); g_set_object (&self->item, item); if (item) - g_signal_connect_object (sysprof_mark_chart_item_get_marks (item), - "items-changed", - G_CALLBACK (sysprof_mark_chart_row_queue_update), + g_signal_connect_object (self->item, + "changed", + G_CALLBACK (sysprof_mark_chart_row_item_changed_cb), self, G_CONNECT_SWAPPED);