diff --git a/src/gui/gcal-event-widget-pool.c b/src/gui/gcal-event-widget-pool.c new file mode 100644 index 00000000..aeba017d --- /dev/null +++ b/src/gui/gcal-event-widget-pool.c @@ -0,0 +1,197 @@ +/* + * gcal-event-widget-pool.c + * + * Copyright 2026 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalEventWidgetPool" + +#include "gcal-core-macros.h" +#include "gcal-debug.h" +#include "gcal-event-widget.h" +#include "gcal-event-widget-pool.h" + +#define N_PREALLOCATED_EVENT_WIDGETS 600 + +#define GDK_ARRAY_TYPE_NAME GcalEventWidgets +#define GDK_ARRAY_NAME gcal_event_widgets +#define GDK_ARRAY_ELEMENT_TYPE GcalEventWidget* +#define GDK_ARRAY_PREALLOC N_PREALLOCATED_EVENT_WIDGETS +#define GDK_ARRAY_FREE_FUNC g_object_unref +#include "gdkarrayimpl.c" + +G_STATIC_ASSERT (N_PREALLOCATED_EVENT_WIDGETS > 0); + +/** + * GcalEventWidgetPool: + * + * A pool of event widgets. + * + * Instantiating widgets with templates is expensive, and in Calendar's + * case, we instantiate hundreds of GcalEventWidgets often. This takes + * a considerable time and affects the performance of the application. + * + * When GcalEventWidgetPool is created, it pre-allocates a number of + * GcalEventWidgets. Consumers can then take event widgets from the + * pool, and then return these event widgets back when they're unused. + */ + +struct _GcalEventWidgetPool +{ + GObject parent_instance; + + GcalEventWidgets event_widgets; +}; + +G_DEFINE_FINAL_TYPE (GcalEventWidgetPool, gcal_event_widget_pool, G_TYPE_OBJECT) + + +/* + * GObject overrides + */ + +static void +gcal_event_widget_pool_finalize (GObject *object) +{ + GcalEventWidgetPool *self = GCAL_EVENT_WIDGET_POOL (object); + + gcal_event_widgets_clear (&self->event_widgets); + + G_OBJECT_CLASS (gcal_event_widget_pool_parent_class)->finalize (object); +} + +static void +gcal_event_widget_pool_class_init (GcalEventWidgetPoolClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gcal_event_widget_pool_finalize; +} + +static void +gcal_event_widget_pool_init (GcalEventWidgetPool *self) +{ + g_autoptr (GTimer) timer = g_timer_new (); + + g_timer_start (timer); + for (size_t i = 0; i < N_PREALLOCATED_EVENT_WIDGETS; i++) + { + g_autoptr (GcalEventWidget) event_widget = NULL; + + event_widget = g_object_new (GCAL_TYPE_EVENT_WIDGET, NULL); + g_object_ref_sink (event_widget); + + gcal_event_widgets_append (&self->event_widgets, g_steal_pointer (&event_widget)); + } + g_timer_stop (timer); + + g_debug ("Initialized %u event widgets in %lf seconds", + N_PREALLOCATED_EVENT_WIDGETS, + g_timer_elapsed (timer, NULL)); +} + +/** + * gcal_event_widget_pool_new: + * + * Creates a new event widget pool. + */ +GcalEventWidgetPool * +gcal_event_widget_pool_new (void) +{ + g_assert (GCAL_IS_MAIN_THREAD ()); + + return g_object_new (GCAL_TYPE_EVENT_WIDGET_POOL, NULL); +} + +/** + * gcal_event_widget_pool_take_or_create: + * @event: (transfer none): an event + * + * Takes an event widget from pool, with @event applied to + * it. This is akin to creating a new event widget, and + * setting it to @event. + * + * The pool will allocate a new event widget if it is empty. + * + * Returns: (transfer full): an event widget + */ +GtkWidget * +gcal_event_widget_pool_take_or_create (GcalEventWidgetPool *self, + GcalEvent *event) +{ + GcalEventWidget *event_widget; + size_t size = 0; + + g_assert (GCAL_IS_EVENT_WIDGET_POOL (self)); + g_assert (GCAL_IS_EVENT (event)); + g_assert (GCAL_IS_MAIN_THREAD ()); + + size = gcal_event_widgets_get_size (&self->event_widgets); + + if (size == 0) + { + GtkWidget *new_event_widget; + + g_debug ("Pool %p is empty, creating new event widget for event %s", self, gcal_event_get_uid (event)); + + new_event_widget = gcal_event_widget_new (event); + g_object_ref_sink (new_event_widget); + + return (GtkWidget *) g_steal_pointer (&new_event_widget); + } + + event_widget = gcal_event_widgets_get (&self->event_widgets, size - 1); + + g_debug ("Taking cached event widget %p from pool for event %s", event_widget, gcal_event_get_uid (event)); + + gcal_event_widgets_splice (&self->event_widgets, size - 1, 1, TRUE, NULL, 0); + g_assert (GCAL_IS_EVENT_WIDGET (event_widget)); + + gcal_event_widget_set_event (event_widget, event); + + return (GtkWidget *) g_steal_pointer (&event_widget); +} + +/** + * gcal_event_widget_pool_reclaim: + * @event_widget: (transfer full): an event + * + * Reclaims @event_widget into the pool. The event widget is + * reset, and stored for later usage. The pool takes full + * ownserhip of the event widget, so the caller must *not* + * destroy it. + * + * The @event_widget must have been removed from its parent. + */ +void +gcal_event_widget_pool_reclaim (GcalEventWidgetPool *self, + GtkWidget *event_widget) +{ + g_assert (GCAL_IS_EVENT_WIDGET_POOL (self)); + g_assert (GCAL_IS_EVENT_WIDGET (event_widget)); + g_assert (gtk_widget_get_parent (event_widget) == NULL); + g_assert (GCAL_IS_MAIN_THREAD ()); + + g_debug ("Reclaiming event widget %p", event_widget); + + gcal_event_widget_set_event (GCAL_EVENT_WIDGET (event_widget), NULL); + gcal_event_widget_set_timestamp_policy (GCAL_EVENT_WIDGET (event_widget), GCAL_TIMESTAMP_POLICY_NONE); + + gcal_event_widgets_append (&self->event_widgets, GCAL_EVENT_WIDGET (g_steal_pointer (&event_widget))); +} + diff --git a/src/gui/gcal-event-widget-pool.h b/src/gui/gcal-event-widget-pool.h new file mode 100644 index 00000000..560904d1 --- /dev/null +++ b/src/gui/gcal-event-widget-pool.h @@ -0,0 +1,39 @@ +/* + * gcal-event-widget-pool.h + * + * Copyright 2026 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "gcal-event.h" + +G_BEGIN_DECLS + +#define GCAL_TYPE_EVENT_WIDGET_POOL (gcal_event_widget_pool_get_type()) +G_DECLARE_FINAL_TYPE (GcalEventWidgetPool, gcal_event_widget_pool, GCAL, EVENT_WIDGET_POOL, GObject) + +GcalEventWidgetPool *gcal_event_widget_pool_new (void); +GtkWidget *gcal_event_widget_pool_take_or_create (GcalEventWidgetPool *self, + GcalEvent *event); +void gcal_event_widget_pool_reclaim (GcalEventWidgetPool *self, + GtkWidget *event_widget); + +G_END_DECLS diff --git a/src/gui/meson.build b/src/gui/meson.build index e22489a8..182b1e0e 100644 --- a/src/gui/meson.build +++ b/src/gui/meson.build @@ -47,6 +47,7 @@ sources += files( 'gcal-drop-overlay.c', 'gcal-event-popover.c', 'gcal-event-widget.c', + 'gcal-event-widget-pool.c', 'gcal-fading-label.c', 'gcal-meeting-row.c', 'gcal-overflow-bin.c',