particle/string: don’t thrash the text-run cache

a5bbf0b769 introduced text-run
shaping.

Do avoid having to re-shape non-changing strings every time the bar is
refreshed, the *particle* (i.e. not the exposable) caches the last
shaped text-run.

Then, in expose(), it then assumes that that cached text-run is
the *same* text-run as returned from begin_expose().

This is true in most cases, but *not* when a single particle is
re-used to instantiate multiple exposables, as is commonly done by
modules generating dynlists. For example, the i3/sway module.

This fixes it, by making the cache growable, and by adding a “lock” to
each cache entry.

The lock is set in begin_expose(), to indicate that this particular
cache entry is needed in expose().

If we can’t find a matching cache entry, we first try to find a free
“slot” by searching for either unused, or used-but-not-locked cache
entries.

If that fails, we grow the cache and add a new entry.

In expose(), we unset the lock.

Closes #47
This commit is contained in:
Daniel Eklöf 2021-05-25 17:36:55 +02:00
parent 0e9c96e6b3
commit 15ed0e043b
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F

View file

@ -10,21 +10,26 @@
#include "../particle.h"
#include "../plugin.h"
struct text_run_cache {
uint64_t hash;
struct fcft_text_run *run;
int width;
bool in_use;
};
struct private {
char *text;
size_t max_len;
struct {
uint64_t hash;
struct fcft_text_run *run;
int width;
} cached;
size_t cache_size;
struct text_run_cache *cache;
};
struct eprivate {
/* Set when instantiating */
char *text;
ssize_t cache_idx;
const struct fcft_glyph **glyphs;
const struct fcft_glyph **allocated_glyphs;
long *kern_x;
@ -66,21 +71,28 @@ begin_expose(struct exposable *exposable)
e->glyphs = e->allocated_glyphs = NULL;
e->num_glyphs = 0;
e->kern_x = NULL;
e->cache_idx = -1;
uint64_t hash = sdbm_hash(e->text);
if (p->cached.hash == hash) {
e->glyphs = p->cached.run->glyphs;
e->num_glyphs = p->cached.run->count;
e->kern_x = calloc(p->cached.run->count, sizeof(e->kern_x[0]));
for (size_t i = 0; i < p->cache_size; i++) {
if (p->cache[i].hash == hash) {
assert(p->cache[i].run != NULL);
p->cache[i].in_use = true;
e->cache_idx = i;
e->glyphs = p->cache[i].run->glyphs;
e->num_glyphs = p->cache[i].run->count;
e->kern_x = calloc(p->cache[i].run->count, sizeof(e->kern_x[0]));
exposable->width =
exposable->particle->left_margin +
p->cached.width +
p->cache[i].width +
exposable->particle->right_margin;
return exposable->width;
}
}
size_t chars = mbstowcs(NULL, e->text, 0);
if (chars != (size_t)-1) {
@ -96,11 +108,32 @@ begin_expose(struct exposable *exposable)
for (size_t i = 0; i < run->count; i++)
w += run->glyphs[i]->advance.x;
fcft_text_run_destroy(p->cached.run);
p->cached.hash = hash;
p->cached.run = run;
p->cached.width = w;
ssize_t cache_idx = -1;
for (size_t i = 0; i < p->cache_size; i++) {
if (p->cache[i].run == NULL || !p->cache[i].in_use) {
fcft_text_run_destroy(p->cache[i].run);
cache_idx = i;
break;
}
}
if (cache_idx < 0) {
size_t new_size = p->cache_size + 1;
struct text_run_cache *new_cache = realloc(
p->cache, new_size * sizeof(new_cache[0]));
p->cache_size = new_size;
p->cache = new_cache;
cache_idx = new_size - 1;
}
assert(cache_idx >= 0 && cache_idx < p->cache_size);
p->cache[cache_idx].hash = hash;
p->cache[cache_idx].run = run;
p->cache[cache_idx].width = w;
p->cache[cache_idx].in_use = true;
e->cache_idx = cache_idx;
e->num_glyphs = run->count;
e->glyphs = run->glyphs;
}
@ -147,6 +180,11 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int
const struct eprivate *e = exposable->private;
const struct fcft_font *font = exposable->particle->font;
if (e->cache_idx >= 0) {
struct private *priv = exposable->particle->private;
priv->cache[e->cache_idx].in_use = false;
}
if (e->num_glyphs == 0)
return;
@ -247,7 +285,9 @@ static void
particle_destroy(struct particle *particle)
{
struct private *p = particle->private;
fcft_text_run_destroy(p->cached.run);
for (size_t i = 0; i < p->cache_size; i++)
fcft_text_run_destroy(p->cache[i].run);
free(p->cache);
free(p->text);
free(p);
particle_default_destroy(particle);
@ -259,7 +299,8 @@ string_new(struct particle *common, const char *text, size_t max_len)
struct private *p = calloc(1, sizeof(*p));
p->text = strdup(text);
p->max_len = max_len;
p->cached.hash = -1;
p->cache_size = 0;
p->cache = NULL;
common->private = p;
common->destroy = &particle_destroy;