summaryrefslogtreecommitdiff
path: root/src/glibext
diff options
context:
space:
mode:
authorCyrille Bagard <nocbos@gmail.com>2016-10-22 21:45:55 (GMT)
committerCyrille Bagard <nocbos@gmail.com>2016-10-22 21:45:55 (GMT)
commitfa30b0fb42d2e229de9f760bfa842f25738efc18 (patch)
tree27bdc4c0ddad3a81acbc380ffd79b8cb96e1b96d /src/glibext
parent40886e00da4c593227cecf3ab574f9c5d230b089 (diff)
Made all segments share their content to save memory.
Diffstat (limited to 'src/glibext')
-rw-r--r--src/glibext/gbuffersegment.c413
-rw-r--r--src/glibext/gbuffersegment.h12
2 files changed, 351 insertions, 74 deletions
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 <assert.h>
+#include <limits.h>
#include <malloc.h>
#include <stdbool.h>
#include <stdlib.h>
@@ -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("<span ");
@@ -583,9 +821,9 @@ char *g_buffer_segment_get_text(const GBufferSegment *segment, bool markup)
result = stradd(result, "foreground=\"#");
snprintf(color, sizeof(color), "%02hhx%02hhx%02hhx",
- (unsigned char)(segment->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, "<", "&lt;");
valid = strrpl(valid, "&", "&amp;");
@@ -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, "<SPAN class=\"%s\">", _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 -------------------- */