Try to optimize playlists
I'm not entirely sure about this.
This commit is contained in:
parent
4542bdd239
commit
7b79dc3e57
233
nncmpp.c
233
nncmpp.c
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue