Try to optimize playlists

I'm not entirely sure about this.
This commit is contained in:
Přemysl Eric Janouch 2016-10-12 13:11:17 +02:00
parent 4542bdd239
commit 7b79dc3e57
Signed by: p
GPG Key ID: B715679E3A361BE6
1 changed files with 137 additions and 96 deletions

233
nncmpp.c
View File

@ -407,6 +407,119 @@ poller_curl_remove (struct poller_curl *self, CURL *easy, struct error **e)
return true; return true;
} }
// --- Compact map -------------------------------------------------------------
// MPD provides us with a hefty amount of little key-value maps. The overhead
// of str_map for such constant (string -> string) maps is too high and it's
// much better to serialize them (mainly cache locality and memory efficiency).
//
// This isn't intended to be reusable and has case insensitivity built-in.
typedef uint8_t *compact_map_t; ///< Compacted (string -> string) map
static compact_map_t
compact_map (struct str_map *map)
{
struct str s;
str_init (&s);
struct str_map_iter iter;
str_map_iter_init (&iter, map);
char *value;
static const size_t zero = 0, alignment = sizeof zero;
while ((value = str_map_iter_next (&iter)))
{
size_t entry_len = iter.link->key_length + 1 + strlen (value) + 1;
size_t padding_len = (alignment - entry_len % alignment) % alignment;
entry_len += padding_len;
str_append_data (&s, &entry_len, sizeof entry_len);
str_append_printf (&s, "%s%c%s%c", iter.link->key, 0, value, 0);
str_append_data (&s, &zero, padding_len);
}
str_append_data (&s, &zero, sizeof zero);
return (compact_map_t) str_steal (&s);
}
static char *
compact_map_find (compact_map_t data, const char *needle)
{
size_t entry_len;
while ((entry_len = *(size_t *) data))
{
data += sizeof entry_len;
if (!strcasecmp_ascii (needle, (const char *) data))
return (char *) data + strlen (needle) + 1;
data += entry_len;
}
return NULL;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct item_list
{
compact_map_t *items; ///< Compacted (string -> string) maps
size_t len; ///< Length
size_t alloc; ///< Allocated items
};
static void
item_list_init (struct item_list *self)
{
memset (self, 0, sizeof *self);
self->items = xcalloc (sizeof *self->items, (self->alloc = 16));
}
static void
item_list_free (struct item_list *self)
{
for (size_t i = 0; i < self->len; i++)
free (self->items[i]);
free (self->items);
}
static bool
item_list_set (struct item_list *self, int i, struct str_map *item)
{
if (i < 0 || (size_t) i >= self->len)
return false;
free (self->items[i]);
self->items[i] = compact_map (item);
return true;
}
static compact_map_t
item_list_get (struct item_list *self, int i)
{
if (i < 0 || (size_t) i >= self->len || !self->items[i])
return false;
return self->items[i];
}
static void
item_list_resize (struct item_list *self, size_t len)
{
// Make the allocated array big enough but not too large
size_t new_alloc = self->alloc;
while (new_alloc < len)
new_alloc <<= 1;
while ((new_alloc >> 1) >= len
&& (new_alloc - len) >= 1024)
new_alloc >>= 1;
for (size_t i = len; i < self->len; i++)
free (self->items[i]);
if (new_alloc != self->alloc)
self->items = xreallocarray (self->items,
sizeof *self->items, (self->alloc = new_alloc));
for (size_t i = self->len; i < len; i++)
self->items[i] = NULL;
self->len = len;
}
// --- Application ------------------------------------------------------------- // --- Application -------------------------------------------------------------
// Function names are prefixed mostly because of curses which clutters the // Function names are prefixed mostly because of curses which clutters the
@ -460,13 +573,6 @@ struct tab
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct playlist
{
struct str_map *items; ///< Current playlist
size_t len; ///< Length
size_t alloc; ///< Allocated items
};
struct attrs struct attrs
{ {
short fg; ///< Foreground colour index short fg; ///< Foreground colour index
@ -508,7 +614,7 @@ static struct app_context
int song_duration; ///< Song duration in seconds int song_duration; ///< Song duration in seconds
int volume; ///< Current volume int volume; ///< Current volume
struct playlist playlist; ///< Current playlist struct item_list playlist; ///< Current playlist
uint32_t playlist_version; ///< Playlist version uint32_t playlist_version; ///< Playlist version
// Data: // Data:
@ -566,68 +672,6 @@ tab_free (struct tab *self)
free (self->name); free (self->name);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
playlist_init (struct playlist *self)
{
memset (self, 0, sizeof *self);
self->items = xcalloc (sizeof *self->items, (self->alloc = 16));
}
static void
playlist_free (struct playlist *self)
{
for (size_t i = 0; i < self->len; i++)
str_map_free (&self->items[i]);
free (self->items);
}
static bool
playlist_set (struct playlist *self, int i, struct str_map *item)
{
if (i < 0 || (size_t) i >= self->len)
return false;
str_map_free (&self->items[i]);
self->items[i] = *item;
return true;
}
static struct str_map *
playlist_get (struct playlist *self, int i)
{
if (i < 0 || (size_t) i >= self->len)
return false;
return &self->items[i];
}
static void
playlist_resize (struct playlist *self, size_t len)
{
// Make the allocated array big enough but not too large
size_t new_alloc = self->alloc;
while (new_alloc < len)
new_alloc <<= 1;
while (len < (new_alloc >> 2)
&& new_alloc >= (STR_MAP_MIN_ALLOC << 1))
new_alloc >>= 1;
// Dispose of items that are out of range and resize the array if needed
for (size_t i = len; i < self->len; i++)
str_map_free (&self->items[i]);
if (new_alloc != self->alloc)
self->items = xreallocarray (self->items,
sizeof *self->items, new_alloc);
// We need to initialize placeholders so that str_map_find() succeeds
for (size_t i = self->len; i < len; i++)
str_map_init (&self->items[i]);
self->len = len;
}
// --- Configuration ----------------------------------------------------------- // --- Configuration -----------------------------------------------------------
static struct config_schema g_config_settings[] = static struct config_schema g_config_settings[] =
@ -746,12 +790,8 @@ load_config_streams (struct config_item *subtree, void *user_data)
print_warning ("`%s': stream URIs must be strings", iter.link->key); print_warning ("`%s': stream URIs must be strings", iter.link->key);
else else
{ {
struct str s; str_vector_add_owned (&g_ctx.streams, xstrdup_printf ("%s%c%s",
str_init (&s); iter.link->key, 0, item->value.string.str));
str_append (&s, iter.link->key);
str_append_c (&s, '\0');
str_append_str (&s, &item->value.string);
str_vector_add_owned (&g_ctx.streams, str_steal (&s));
} }
qsort (g_ctx.streams.vector, g_ctx.streams.len, qsort (g_ctx.streams.vector, g_ctx.streams.len,
sizeof *g_ctx.streams.vector, str_vector_sort_utf8_cb); sizeof *g_ctx.streams.vector, str_vector_sort_utf8_cb);
@ -809,7 +849,7 @@ app_init_context (void)
mpd_client_init (&g_ctx.client, &g_ctx.poller); mpd_client_init (&g_ctx.client, &g_ctx.poller);
config_init (&g_ctx.config); config_init (&g_ctx.config);
str_vector_init (&g_ctx.streams); str_vector_init (&g_ctx.streams);
playlist_init (&g_ctx.playlist); item_list_init (&g_ctx.playlist);
// This is also approximately what libunistring does internally, // This is also approximately what libunistring does internally,
// since the locale name is canonicalized by locale_charset(). // since the locale name is canonicalized by locale_charset().
@ -862,7 +902,7 @@ app_free_context (void)
mpd_client_free (&g_ctx.client); mpd_client_free (&g_ctx.client);
str_map_free (&g_ctx.playback_info); str_map_free (&g_ctx.playback_info);
str_vector_free (&g_ctx.streams); str_vector_free (&g_ctx.streams);
playlist_free (&g_ctx.playlist); item_list_free (&g_ctx.playlist);
config_free (&g_ctx.config); config_free (&g_ctx.config);
poller_free (&g_ctx.poller); poller_free (&g_ctx.poller);
@ -1112,9 +1152,8 @@ app_flush_header (struct row_buffer *buf, chtype attrs)
static void static void
app_draw_song_info (void) app_draw_song_info (void)
{ {
struct str_map *map; compact_map_t map;
if (!(map = playlist_get (&g_ctx.playlist, g_ctx.song)) if (!(map = item_list_get (&g_ctx.playlist, g_ctx.song)))
|| !soft_assert (map->len != 0))
return; return;
// XXX: can we get rid of this and still make it look acceptable? // XXX: can we get rid of this and still make it look acceptable?
@ -1122,9 +1161,9 @@ app_draw_song_info (void)
chtype a_highlight = APP_ATTR (HIGHLIGHT); chtype a_highlight = APP_ATTR (HIGHLIGHT);
char *title; char *title;
if ((title = str_map_find (map, "title")) if ((title = compact_map_find (map, "title"))
|| (title = str_map_find (map, "name")) || (title = compact_map_find (map, "name"))
|| (title = str_map_find (map, "file"))) || (title = compact_map_find (map, "file")))
{ {
struct row_buffer buf; struct row_buffer buf;
row_buffer_init (&buf); row_buffer_init (&buf);
@ -1132,8 +1171,8 @@ app_draw_song_info (void)
app_flush_header (&buf, a_highlight); app_flush_header (&buf, a_highlight);
} }
char *artist = str_map_find (map, "artist"); char *artist = compact_map_find (map, "artist");
char *album = str_map_find (map, "album"); char *album = compact_map_find (map, "album");
if (!artist && !album) if (!artist && !album)
return; return;
@ -1919,8 +1958,8 @@ current_tab_on_item_draw (size_t item_index, struct row_buffer *buffer,
int width) int width)
{ {
// TODO: better output // TODO: better output
struct str_map *map = playlist_get (&g_ctx.playlist, item_index); compact_map_t map = item_list_get (&g_ctx.playlist, item_index);
row_buffer_append (buffer, str_map_find (map, "file"), row_buffer_append (buffer, compact_map_find (map, "file"),
(int) item_index == g_ctx.song ? A_BOLD : 0); (int) item_index == g_ctx.song ? A_BOLD : 0);
} }
@ -2249,9 +2288,9 @@ info_tab_on_item_draw (size_t item_index, struct row_buffer *buffer, int width)
} }
static void static void
info_tab_add (struct str_map *map, const char *field) info_tab_add (compact_map_t data, const char *field)
{ {
const char *value = str_map_find (map, field); const char *value = compact_map_find (data, field);
if (!value) value = ""; if (!value) value = "";
str_vector_add (&g_info_tab.keys, field); str_vector_add (&g_info_tab.keys, field);
@ -2266,8 +2305,8 @@ info_tab_update (void)
str_vector_reset (&g_info_tab.values); str_vector_reset (&g_info_tab.values);
g_info_tab.super.item_count = 0; g_info_tab.super.item_count = 0;
struct str_map *map; compact_map_t map;
if ((map = playlist_get (&g_ctx.playlist, g_ctx.song))) if ((map = item_list_get (&g_ctx.playlist, g_ctx.song)))
{ {
info_tab_add (map, "Title"); info_tab_add (map, "Title");
info_tab_add (map, "Artist"); info_tab_add (map, "Artist");
@ -2471,13 +2510,15 @@ mpd_process_info_chunk (struct str_map *map, char *file)
if (!file) if (!file)
{ {
if (xstrtoul_map (map, "playlistlength", &n)) if (xstrtoul_map (map, "playlistlength", &n))
playlist_resize (&g_ctx.playlist, n); item_list_resize (&g_ctx.playlist, n);
g_ctx.playback_info = *map; g_ctx.playback_info = *map;
} }
else if (!xstrtoul_map (map, "pos", &n) else
|| !playlist_set (&g_ctx.playlist, n, map)) {
if (xstrtoul_map (map, "pos", &n))
item_list_set (&g_ctx.playlist, n, map);
str_map_free (map); str_map_free (map);
}
mpd_init_response_map (map); mpd_init_response_map (map);
} }