From fa30b0fb42d2e229de9f760bfa842f25738efc18 Mon Sep 17 00:00:00 2001 From: Cyrille Bagard Date: Sat, 22 Oct 2016 23:45:55 +0200 Subject: Made all segments share their content to save memory. --- ChangeLog | 9 + src/glibext/gbuffersegment.c | 413 +++++++++++++++++++++++++++++++++++-------- src/glibext/gbuffersegment.h | 12 ++ src/gui/core/core.c | 7 +- 4 files changed, 365 insertions(+), 76 deletions(-) diff --git a/ChangeLog b/ChangeLog index 78a5a8a..4be9aa4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,15 @@ 16-10-22 Cyrille Bagard * src/glibext/gbuffersegment.c: + * src/glibext/gbuffersegment.h: + Make all segments share their content to save memory. + + * src/gui/core/core.c: + Setup and free the global hash table for segment contents. + +16-10-22 Cyrille Bagard + + * src/glibext/gbuffersegment.c: Reduce the memory usage by cutting down the size of GBufferSegment from 152 bytes to 64 bytes. diff --git a/src/glibext/gbuffersegment.c b/src/glibext/gbuffersegment.c index 43c710c..a2947ae 100644 --- a/src/glibext/gbuffersegment.c +++ b/src/glibext/gbuffersegment.c @@ -24,6 +24,8 @@ #include "gbuffersegment.h" +#include +#include #include #include #include @@ -37,7 +39,7 @@ -/* -------------------- NATURE DE BASE POUR UN FRAGMENT DE TEXTE -------------------- */ +/* ----------------------- ISOLATION DE CONTENUS PARTAGEABLES ----------------------- */ /* Propriétés de rendu */ @@ -59,6 +61,40 @@ typedef struct _rendering_pattern_t } rendering_pattern_t; +/* Contenu brut pour segment, potentiellement commun */ +typedef struct _seg_content +{ + unsigned int ref_count; /* Compteur de références */ + + rendering_pattern_t *pattern; /* Propriétés du rendu */ + + fnv64_t hash; /* Empreinte pour comparaisons */ + char text[0]; /* Texte brut conservé */ + +} seg_content; + + +/* Conservation de toutes les créations partagées */ +static GHashTable *_segcnt_htable; + + +/* Fournit l'empreinte d'un contenu pour segments. */ +static guint get_seg_content_hash(const seg_content *); + +/* Détermine si deux contenus pour segments sont identiques. */ +static bool is_seg_content_equal(const seg_content *, const seg_content *); + +/* Détermine si deux contenus pour segments sont identiques. */ +static seg_content *get_shared_content(const seg_content *); + +/* Abandonne un contenu pour segments. */ +static void release_shared_content(seg_content *); + + + +/* -------------------- NATURE DE BASE POUR UN FRAGMENT DE TEXTE -------------------- */ + + /* Nom des éléments CSS */ #define SEGMENT_NAME(s) "segment-" s @@ -103,14 +139,9 @@ struct _GBufferSegment { GObject parent; /* A laisser en premier */ - GObject *creator; /* Objet à l'origine du segment*/ - - char *text; /* Texte brut conservé */ - fnv64_t hash; /* Empreinte pour comparaisons */ - - rendering_pattern_t *pattern; /* Propriétés du rendu */ + seg_content *content; /* Contenu, partagé ou non */ - gint x_advance; /* Dimensions du texte */ + GObject *creator; /* Objet à l'origine du segment*/ }; @@ -144,9 +175,8 @@ static void g_buffer_segment_dispose(GBufferSegment *); /* Procède à la libération totale de la mémoire. */ static void g_buffer_segment_finalize(GBufferSegment *); -/* Définit les dernières propriétés de rendu restantes. */ -static void g_buffer_segment_prepare(GBufferSegment *, size_t); - +/* Met à jour le contenu d'un fragment de texte. */ +void g_buffer_segment_set_text(GBufferSegment *, rendering_pattern_t *, const char *, size_t); @@ -164,6 +194,201 @@ struct _segcnt_list /* ---------------------------------------------------------------------------------- */ +/* ISOLATION DE CONTENUS PARTAGEABLES */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Initialise la table mémorisant les contenus pour segments. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool init_segment_content_hash_table(void) +{ + _segcnt_htable = g_hash_table_new_full((GHashFunc)get_seg_content_hash, + (GEqualFunc)is_seg_content_equal, + free, NULL); + + return (_segcnt_htable != NULL); + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Organise la sortie de la table des contenus pour segments. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +void exit_segment_content_hash_table(void) +{ + assert(g_hash_table_size(_segcnt_htable) == 0); + + g_hash_table_unref(_segcnt_htable); + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu pour segment à consulter. * +* * +* Description : Fournit l'empreinte d'un contenu pour segments. * +* * +* Retour : Empreinte de lu contenu représenté. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static guint get_seg_content_hash(const seg_content *content) +{ + return content->hash; + +} + + +/****************************************************************************** +* * +* Paramètres : content = premier contenu pour segment à analyser. * +* other = second contenu pour segment à analyser. * +* * +* Description : Détermine si deux contenus pour segments sont identiques. * +* * +* Retour : Bilan de la comparaison. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool is_seg_content_equal(const seg_content *content, const seg_content *other) +{ + bool result; /* Résultat à retourner */ + + result = (content->pattern == other->pattern); + + result &= (cmp_fnv_64a(content->hash, other->hash) == 0); + + result &= (strcmp(content->text, other->text) == 0); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = premier contenu pour segment à analyser. * +* other = second contenu pour segment à analyser. * +* * +* Description : Détermine si deux contenus pour segments sont identiques. * +* * +* Retour : Bilan de la comparaison. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static seg_content *get_shared_content(const seg_content *content) +{ + seg_content *result; /* Contenu partagé à renvoyer */ + gboolean found; /* Le contenu existe déjà ? */ + size_t allocated; /* Besoin complet en mémoire */ +#ifndef NDEBUG + gboolean created; /* Validation de mise en place */ +#endif + + /** + * On considère qu'il n'y a pas besoin de mutex ici, puisque toutes les + * opérations se réalisent à priori dans le seul thread principal pour l'affichage. + */ + + found = g_hash_table_lookup_extended(_segcnt_htable, content, (gpointer *)&result, NULL); + + if (!found) + { + allocated = sizeof(seg_content) + strlen(content->text) + 1; + + result = (seg_content *)malloc(allocated); + + memcpy(result, content, allocated); + + result->ref_count = 1; + +#ifndef NDEBUG + created = g_hash_table_insert(_segcnt_htable, result, result); + assert(created); +#else + g_hash_table_insert(_segcnt_htable, result, result); +#endif + + } + + else + { + assert(result->ref_count < UINT_MAX); + + result->ref_count++; + + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : content = contenu pour segments à délaisser. * +* * +* Description : Abandonne un contenu pour segments. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void release_shared_content(seg_content *content) +{ +#ifndef NDEBUG + gboolean deleted; /* Validation de suppression */ +#endif + + /** + * On considère qu'il n'y a pas besoin de mutex ici, puisque toutes les + * opérations se réalisent à priori dans le seul thread principal pour l'affichage. + */ + + if (--content->ref_count == 0) + { +#ifndef NDEBUG + deleted = g_hash_table_remove(_segcnt_htable, content); + assert(deleted); +#else + g_hash_table_remove(_segcnt_htable, content); +#endif + } + +} + + + + +/* ---------------------------------------------------------------------------------- */ /* NATURE DE BASE POUR UN FRAGMENT DE TEXTE */ /* ---------------------------------------------------------------------------------- */ @@ -361,6 +586,8 @@ static void g_buffer_segment_init(GBufferSegment *segment) static void g_buffer_segment_dispose(GBufferSegment *segment) { + release_shared_content(segment->content); + if (segment->creator != NULL) g_object_unref(segment->creator); @@ -409,13 +636,9 @@ GBufferSegment *g_buffer_segment_new(RenderingTagType type, const char *text, si result = g_object_new(G_TYPE_BUFFER_SEGMENT, NULL); - result->text = strndup(text, length); - result->hash = fnv_64a_hash(result->text); - class = G_BUFFER_SEGMENT_GET_CLASS(result); - result->pattern = &class->patterns[type]; - g_buffer_segment_prepare(result, length); + g_buffer_segment_set_text(result, &class->patterns[type], text, length); return result; @@ -424,10 +647,10 @@ GBufferSegment *g_buffer_segment_new(RenderingTagType type, const char *text, si /****************************************************************************** * * -* Paramètres : segment = instance de segment à affiner. * -* text = chaîne de caractères à traiter. * +* Paramètres : segment = instance de segment à compléter. * +* obj = instance GLib quelconque à mémoriser. * * * -* Description : Définit les dernières propriétés de rendu restantes. * +* Description : Associe à un segment un objet GLib identifié comme créateur. * * * * Retour : - * * * @@ -435,20 +658,13 @@ GBufferSegment *g_buffer_segment_new(RenderingTagType type, const char *text, si * * ******************************************************************************/ -static void g_buffer_segment_prepare(GBufferSegment *segment, size_t length) +void g_buffer_segment_set_creator(GBufferSegment *segment, GObject *obj) { - GBufferSegmentClass *class; /* Classe associée à l'instance*/ - cairo_font_slant_t slant; /* Style d'impression */ - cairo_font_weight_t weight; /* Poids de la police */ - - /* Taille */ - - class = G_BUFFER_SEGMENT_GET_CLASS(segment); - - slant = segment->pattern->slant; - weight = segment->pattern->weight; + if (segment->creator != NULL) + g_object_unref(segment->creator); - segment->x_advance = class->x_advances[CAIRO_FONT_INDEX(slant, weight)] * length; + segment->creator = obj; + g_object_ref(obj); } @@ -456,45 +672,61 @@ static void g_buffer_segment_prepare(GBufferSegment *segment, size_t length) /****************************************************************************** * * * Paramètres : segment = instance de segment à compléter. * -* obj = instance GLib quelconque à mémoriser. * * * -* Description : Associe à un segment un objet GLib identifié comme créateur. * +* Description : Renvoie vers un éventuel objet lié en tant que créateur. * * * -* Retour : - * +* Retour : Instance GLib quelconque ou NULL si aucune référencée. * * * * Remarques : - * * * ******************************************************************************/ -void g_buffer_segment_set_creator(GBufferSegment *segment, GObject *obj) +GObject *g_buffer_segment_get_creator(const GBufferSegment *segment) { if (segment->creator != NULL) - g_object_unref(segment->creator); + g_object_ref(segment->creator); - segment->creator = obj; - g_object_ref(obj); + return segment->creator; } /****************************************************************************** * * -* Paramètres : segment = instance de segment à compléter. * +* Paramètres : segment = fragment de texte à mettre à jour. * +* pattern = paramètres d'impression du texte. * +* text = chaîne de caractères à traiter. * +* length = quantité de ces caractères. * * * -* Description : Renvoie vers un éventuel objet lié en tant que créateur. * +* Description : Met à jour le contenu d'un fragment de texte. * * * -* Retour : Instance GLib quelconque ou NULL si aucune référencée. * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -GObject *g_buffer_segment_get_creator(const GBufferSegment *segment) +void g_buffer_segment_set_text(GBufferSegment *segment, rendering_pattern_t *pattern, const char *text, size_t length) { - if (segment->creator != NULL) - g_object_ref(segment->creator); + char atmp[sizeof(seg_content) + 128]; /* Allocation static facile */ + seg_content *content; /* Contenu à mettre en place ? */ - return segment->creator; + if (length < (sizeof(atmp) - sizeof(seg_content))) + content = (seg_content *)atmp; + else + content = (seg_content *)malloc(sizeof(seg_content) + length + 1); + + content->pattern = pattern; + + content->hash = fnv_64a_hash(text); + + memcpy(content->text, text, length); + content->text[length] = '\0'; + + segment->content = get_shared_content(content); + + if (content != (seg_content *)atmp) + free(content); } @@ -515,12 +747,17 @@ GObject *g_buffer_segment_get_creator(const GBufferSegment *segment) void g_buffer_segment_update_text(GBufferSegment *segment, const char *text, size_t length) { - free(segment->text); + rendering_pattern_t *pattern; /* Conservation des paramètres */ + + /* Destruction */ + + pattern = segment->content->pattern; + + release_shared_content(segment->content); - segment->text = strndup(text, length); - segment->hash = fnv_64a_hash(segment->text); + /* Création */ - g_buffer_segment_prepare(segment, length); + g_buffer_segment_set_text(segment, pattern, text, length); g_signal_emit_by_name(segment, "content-changed"); @@ -544,9 +781,7 @@ bool g_buffer_segment_compare(const GBufferSegment *segment, const GBufferSegmen { bool result; /* Bilan à retourner */ - result = (cmp_fnv_64a(segment->hash, ref->hash) == 0); - - result &= (strcmp(segment->text, ref->text) == 0); + result = is_seg_content_equal(segment->content, ref->content); return result; @@ -569,12 +804,15 @@ bool g_buffer_segment_compare(const GBufferSegment *segment, const GBufferSegmen char *g_buffer_segment_get_text(const GBufferSegment *segment, bool markup) { char *result; /* Description à renvoyer */ + const seg_content *content; /* Accès au contenu */ char color[7]; /* Couleur hexadécimale */ char *valid; + content = segment->content; + /* Résolution du cas simple */ if (!markup) - return strdup(segment->text); + return strdup(content->text); result = strdup("pattern->foreground.color.red * 255), - (unsigned char)(segment->pattern->foreground.color.green * 255), - (unsigned char)(segment->pattern->foreground.color.blue * 255)); + (unsigned char)(content->pattern->foreground.color.red * 255), + (unsigned char)(content->pattern->foreground.color.green * 255), + (unsigned char)(content->pattern->foreground.color.blue * 255)); result = stradd(result, color); @@ -595,7 +833,7 @@ char *g_buffer_segment_get_text(const GBufferSegment *segment, bool markup) result = stradd(result, "style=\""); - switch (segment->pattern->slant) + switch (content->pattern->slant) { case CAIRO_FONT_SLANT_NORMAL: result = stradd(result, "normal"); @@ -617,7 +855,7 @@ char *g_buffer_segment_get_text(const GBufferSegment *segment, bool markup) result = stradd(result, "weight=\""); - switch (segment->pattern->weight) + switch (content->pattern->weight) { case CAIRO_FONT_WEIGHT_NORMAL: result = stradd(result, "normal"); @@ -635,7 +873,7 @@ char *g_buffer_segment_get_text(const GBufferSegment *segment, bool markup) result = stradd(result, ">"); - valid = strdup(segment->text); + valid = strdup(content->text); valid = strrpl(valid, "<", "<"); valid = strrpl(valid, "&", "&"); @@ -664,7 +902,22 @@ char *g_buffer_segment_get_text(const GBufferSegment *segment, bool markup) gint g_buffer_segment_get_width(const GBufferSegment *segment) { - return segment->x_advance; + gint result; /* Largeur à retourner */ + GBufferSegmentClass *class; /* Classe associée à l'instance*/ + const seg_content *content; /* Accès au contenu */ + cairo_font_slant_t slant; /* Style d'impression */ + cairo_font_weight_t weight; /* Poids de la police */ + + class = G_BUFFER_SEGMENT_GET_CLASS(segment); + + content = segment->content; + + slant = content->pattern->slant; + weight = content->pattern->weight; + + result = class->x_advances[CAIRO_FONT_INDEX(slant, weight)] * strlen(content->text); + + return result; } @@ -698,7 +951,7 @@ gint g_buffer_segment_get_caret_position(const GBufferSegment *segment, gint x) else { - char_width = width / strlen(segment->text); + char_width = width / strlen(segment->content->text); result = (x / char_width) * char_width; if ((x % char_width) > (char_width / 2)) @@ -735,7 +988,7 @@ bool g_buffer_segment_move_caret(const GBufferSegment *segment, gint *x, bool ct result = false; width = g_buffer_segment_get_width(segment); - char_width = width / strlen(segment->text); + char_width = width / strlen(segment->content->text); if (dir == GDK_SCROLL_LEFT) { @@ -794,12 +1047,16 @@ bool g_buffer_segment_move_caret(const GBufferSegment *segment, gint *x, bool ct void g_buffer_segment_draw(GBufferSegment *segment, cairo_t *cr, gint *x, gint y, const segcnt_list *list) { bool selected; /* Marquer une sélection ? */ + gint width; /* Largeur du segment */ GBufferSegmentClass *class; /* Accès aux infos globales */ - const rendering_color_t *used_fg; /* Couleur d'impression utile */ cairo_operator_t old; /* Sauvegarde avant changement */ + const seg_content *content; /* Accès au contenu */ + const rendering_color_t *used_fg; /* Couleur d'impression utile */ selected = selection_list_has_segment_content(list, segment); + width = g_buffer_segment_get_width(segment); + /* Fond du texte */ if (selected) { @@ -811,7 +1068,7 @@ void g_buffer_segment_draw(GBufferSegment *segment, cairo_t *cr, gint *x, gint y class->selection_bg.color.blue, class->selection_bg.color.alpha); - cairo_rectangle(cr, *x, y, segment->x_advance, 17); + cairo_rectangle(cr, *x, y, width, 17); old = cairo_get_operator(cr); cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE); @@ -822,10 +1079,12 @@ void g_buffer_segment_draw(GBufferSegment *segment, cairo_t *cr, gint *x, gint y /* Couleur d'impression */ + content = segment->content; + if (selected) - used_fg = &segment->pattern->inverted; + used_fg = &content->pattern->inverted; else - used_fg = &segment->pattern->foreground; + used_fg = &content->pattern->foreground; if (used_fg->has_color) cairo_set_source_rgba(cr, @@ -838,14 +1097,14 @@ void g_buffer_segment_draw(GBufferSegment *segment, cairo_t *cr, gint *x, gint y /* Impression du texte */ - cairo_select_font_face(cr, "mono", segment->pattern->slant, segment->pattern->weight); + cairo_select_font_face(cr, "mono", content->pattern->slant, content->pattern->weight); cairo_set_font_size(cr, 13); cairo_move_to(cr, *x, y + 17 - 3); /* 3 = font extents.descent */ - cairo_show_text(cr, segment->text); + cairo_show_text(cr, content->text); - *x += segment->x_advance; + *x += width; } @@ -944,21 +1203,24 @@ void g_buffer_segment_export_style(buffer_export_context *ctx, BufferExportType void g_buffer_segment_export(const GBufferSegment *segment, buffer_export_context *ctx, BufferExportType type) { + const seg_content *content; /* Accès au contenu */ GBufferSegmentClass *class; /* Classe des segments */ size_t index; /* Indice du modèle de rendu */ + content = segment->content; + switch (type) { case BET_HTML: class = G_BUFFER_SEGMENT_GET_CLASS(segment); - index = (segment->pattern - class->patterns); + index = (content->pattern - class->patterns); dprintf(ctx->fd, "", _segment_names[index]); break; default: break; } - dprintf(ctx->fd, "%s", segment->text); + dprintf(ctx->fd, "%s", content->text); switch (type) { @@ -1069,20 +1331,23 @@ bool reset_segment_content_list(segcnt_list *list) bool add_segment_content_to_selection_list(segcnt_list *list, const GBufferSegment *segment) { bool result; /* Bilan à retourner */ + const seg_content *content; /* Accès au contenu */ size_t i; /* Boucle de parcours */ static const char white_list[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; result = false; + content = segment->content; + for (i = 0; i < (sizeof(white_list) - 1) && !result; i++) - result = (strchr(segment->text, white_list[i]) != NULL); + result = (strchr(content->text, white_list[i]) != NULL); if (result) { list->hashes = (fnv64_t *)realloc(list->hashes, ++list->count * sizeof(fnv64_t)); - list->hashes[list->count - 1] = segment->hash; + list->hashes[list->count - 1] = content->hash; } @@ -1112,7 +1377,7 @@ bool selection_list_has_segment_content(const segcnt_list *list, const GBufferSe result = false; for (i = 0; i < list->count && !result; i++) - result = (cmp_fnv_64a(list->hashes[i], segment->hash) == 0); + result = (cmp_fnv_64a(list->hashes[i], segment->content->hash) == 0); return result; diff --git a/src/glibext/gbuffersegment.h b/src/glibext/gbuffersegment.h index 57e3575..911df7b 100644 --- a/src/glibext/gbuffersegment.h +++ b/src/glibext/gbuffersegment.h @@ -36,6 +36,18 @@ typedef struct _segcnt_list segcnt_list; + +/* ----------------------- ISOLATION DE CONTENUS PARTAGEABLES ----------------------- */ + + +/* Initialise la table mémorisant les contenus pour segments. */ +bool init_segment_content_hash_table(void); + +/* Organise la sortie de la table des contenus pour segments. */ +void exit_segment_content_hash_table(void); + + + /* -------------------- NATURE DE BASE POUR UN FRAGMENT DE TEXTE -------------------- */ diff --git a/src/gui/core/core.c b/src/gui/core/core.c index 16e5cc8..dbff1b8 100644 --- a/src/gui/core/core.c +++ b/src/gui/core/core.c @@ -28,6 +28,7 @@ #include "../../core/params.h" +#include "../../glibext/gbuffersegment.h" @@ -47,9 +48,10 @@ bool load_all_gui_components(GObject *ref) { bool result; /* Bilan à retourner */ - result = true; + result = init_segment_content_hash_table(); - load_main_panels(ref); + if (result) + load_main_panels(ref); return result; @@ -93,5 +95,6 @@ bool complete_loading_of_all_gui_components(GGenConfig *config) void unload_all_gui_components(void) { + exit_segment_content_hash_table(); } -- cgit v0.11.2-87-g4458