/* vifm * Copyright (C) 2013 xaizek. * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "color_manager.h" #include <assert.h> /* assert() */ #include <stddef.h> /* NULL */ #include <stdlib.h> /* bsearch() */ #include "../compat/reallocarray.h" #include "../utils/macros.h" #include "../utils/utils.h" #include "colors.h" /* Number of color pairs preallocated by curses library. */ #define PREALLOCATED_COUNT 1 /* Maximum allowed number of unsorted color pairs. */ #define MAX_UNSORTED_PAIRS 512 /* Initial size of the pair cache. */ #define START_CACHE_SIZE 128 /* Size factor for growing pair cache. */ #define CACHE_GROWTH_FACTOR 2 /* Pair cache entry that maps foreground-background pair to its index. */ typedef struct { int fg; /* Foreground color. */ int bg; /* Background color. */ int pair; /* Index. */ } pair_cache_t; static int find_pair(int fg, int bg); static int allocate_pair(int fg, int bg); static int compress_pair_space(void); static int pair_cache_cmp(const void *a, const void *b); /* Number of color pairs available. */ static int avail_pairs; /* Number of used color pairs. */ static int used_pairs; /* Configuration data passed in during initialization. */ static colmgr_conf_t conf; /* Flag, which is set after the unit is initialized. */ static int initialized; /* Default foreground and background colors. */ static int def_fg, def_bg; /* Pair cache to avoid expensive linear lookup of thousands of elements. */ static pair_cache_t *pair_cache; /* Current size of the cache. */ static int pair_cache_size; /* Current maximum size of the cache. */ static int pair_cache_capacity; /* Size of the sorted prefix of the cache. The unsorted tail needs to be looked * up linearly. */ static int pair_cache_sorted; static pair_cache_t * add_to_cache(void) { if(pair_cache_size == pair_cache_capacity) { /* Start small, then grow cache size fast. */ int new_capacity = (pair_cache_capacity == 0) ? START_CACHE_SIZE : pair_cache_capacity*CACHE_GROWTH_FACTOR; void *ptr = reallocarray(pair_cache, new_capacity, sizeof(*pair_cache)); if(ptr == NULL) { return NULL; } pair_cache_capacity = new_capacity; pair_cache = ptr; } return &pair_cache[pair_cache_size++]; } void colmgr_init(const colmgr_conf_t *conf_init) { assert(conf_init != NULL && "conf_init structure is required."); assert(conf_init->init_pair != NULL && "init_pair must be set."); assert(conf_init->pair_content != NULL && "pair_content must be set."); assert(conf_init->pair_in_use != NULL && "pair_in_use must be set."); assert(conf_init->pair_moved != NULL && "pair_moved must be set."); conf = *conf_init; initialized = 1; colmgr_reset(); /* Query default colors as implementation might not return -1 via * pair_content(), which will confuse this unit. */ if(conf.pair_content(0, &def_fg, &def_bg) != 0) { def_fg = -1; def_bg = -1; } } void colmgr_reset(void) { assert(initialized && "colmgr_init() must be called before this function!"); used_pairs = PREALLOCATED_COUNT; avail_pairs = conf.max_color_pairs - used_pairs; pair_cache_size = 0; pair_cache_sorted = 0; } int colmgr_get_pair(int fg, int bg) { assert(initialized && "colmgr_init() must be called before this function!"); if(fg < 0) { fg = -1; } if(bg < 0) { bg = -1; } int p = find_pair(fg, bg); if(p != -1) { return p; } p = allocate_pair(fg, bg); return (p == -1) ? 0 : p; } /* Tries to find pair with specified colors among already allocated pairs. * Returns -1 when search fails. */ static int find_pair(int fg, int bg) { if(fg < 0) { fg = def_fg; } if(bg < 0) { bg = def_bg; } /* The check is to not call bsearch() on NULL. */ if(pair_cache_sorted > 0) { pair_cache_t key = { .fg = fg, .bg = bg }; pair_cache_t *needle = bsearch(&key, pair_cache, pair_cache_sorted, sizeof(*pair_cache), &pair_cache_cmp); if(needle != NULL) { return needle->pair; } } int i; for(i = pair_cache_sorted; i < pair_cache_size; ++i) { pair_cache_t *entry = &pair_cache[i]; if(entry->fg == fg && entry->bg == bg) { return entry->pair; } } return -1; } /* Allocates new color pair. Returns new pair index, or -1 on failure. */ static int allocate_pair(int fg, int bg) { if(avail_pairs == 0) { /* Out of pairs, free unused ones. */ if(compress_pair_space() != 0) { return -1; } } if(conf.init_pair(used_pairs, fg, bg) != 0) { return -1; } pair_cache_t *cache_entry = add_to_cache(); if(cache_entry == NULL) { return -1; } cache_entry->fg = fg; cache_entry->bg = bg; cache_entry->pair = used_pairs; /* Color pairs are not resorted on every allocation to avoid performance * hit. */ if(pair_cache_size - pair_cache_sorted > MAX_UNSORTED_PAIRS) { safe_qsort(pair_cache, pair_cache_size, sizeof(*pair_cache), &pair_cache_cmp); pair_cache_sorted = pair_cache_size; } --avail_pairs; return used_pairs++; } /* Returns zero if at least one pair is now available, otherwise non-zero is * returned. */ static int compress_pair_space(void) { /* TODO: in case of performance issues cache pair_in_use() in the first loop * or change the function to fill in bit field of pairs. */ int i; int j; int in_use; int first_unused; in_use = 0; first_unused = -1; for(i = PREALLOCATED_COUNT; i < used_pairs; ++i) { if(conf.pair_in_use(i)) { ++in_use; } else if(first_unused == -1) { first_unused = i; } } if(first_unused == -1) { /* No unused pairs. */ return -1; } j = first_unused; for(i = PREALLOCATED_COUNT + in_use; i < used_pairs; ++i) { if(conf.pair_in_use(i)) { /* Copy old pair's content to the new one. */ int fg, bg; if(conf.pair_content(i, &fg, &bg) != 0 || conf.init_pair(j, fg, bg) != 0) { /* Something went wrong. Can't return because we might have already * moved some pairs and need to update cache accordingly. Set j in a * way that will avoid any shrinking of color pairs. */ j = used_pairs; break; } conf.pair_moved(i, j); /* Advance to next unused pair. */ do { ++j; } while(conf.pair_in_use(j)); } } used_pairs = j; avail_pairs = conf.max_color_pairs - used_pairs; pair_cache_size = 0; for(i = PREALLOCATED_COUNT; i < used_pairs; ++i) { int fg, bg; if(conf.pair_content(i, &fg, &bg) == 0) { pair_cache_t *cache_entry = add_to_cache(); if(cache_entry != NULL) { cache_entry->fg = fg; cache_entry->bg = bg; cache_entry->pair = i; } } } safe_qsort(pair_cache, pair_cache_size, sizeof(*pair_cache), &pair_cache_cmp); pair_cache_sorted = pair_cache_size; return 0; } /* Comparator for pair_cache_t. Returns values <0, =0 and >0 to signify that * first argument is less than, equal or greater than the second one. */ static int pair_cache_cmp(const void *a, const void *b) { const pair_cache_t *s = a; const pair_cache_t *t = b; return (s->fg != t->fg ? SORT_CMP(s->fg, t->fg) : SORT_CMP(s->bg, t->bg)); } int colmgr_used_pairs(void) { assert(initialized && "colmgr_init() must be called before this function!"); return used_pairs; } /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set cinoptions+=t0 filetype=c : */