Change playlist to use dynamic array

Instead of initial 12MB memory allocation, use a dynamically growing array.
This shares the container structure (now called rbuf) with the explore menu.
This commit is contained in:
Bernhard Schelling 2020-08-07 03:27:52 +09:00
parent 332505db86
commit 0a67c0416a
3 changed files with 258 additions and 157 deletions

View File

@ -0,0 +1,107 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rbuf.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_ARRAY_RBUF_H__
#define __LIBRETRO_SDK_ARRAY_RBUF_H__
/*
* This file implements stretchy buffers as invented (?) by Sean Barrett.
* Based on the implementation from the public domain Bitwise project
* by Per Vognsen - https://github.com/pervognsen/bitwise
*
* It's a super simple type safe dynamic array for C with no need
* to predeclare any type or anything.
* The first time an element is added, memory for 16 elements are allocated.
* Then every time length is about to exceed capacity, capacity is doubled.
*
* Sample usage:
*
* mytype_t* buf = NULL;
* RBUF_PUSH(buf, some_element);
* RBUF_PUSH(buf, other_element);
* -- now RBUF_LEN(buf) == 2, buf[0] == some_element, buf[1] == other_element
*
* -- Free allocated memory:
* RBUF_FREE(buf);
* -- now buf == NULL, RBUF_LEN(buf) == 0, RBUF_CAP(buf) == 0
*
* -- Explicitly increase allocated memory and set capacity:
* RBUF_FIT(buf, 100);
* -- now RBUF_LEN(buf) == 0, RBUF_CAP(buf) == 100
*
* -- Resize buffer (does not initialize or zero memory!)
* RBUF_RESIZE(buf, 200);
* -- now RBUF_LEN(buf) == 200, RBUF_CAP(buf) == 200
*
* -- To handle running out of memory:
* bool ran_out_of_memory = !RBUF_TRYFIT(buf, 1000);
* -- before RESIZE or PUSH. When out of memory, buf will stay unmodified.
*/
#include <retro_math.h> /* for MAX */
#include <stdlib.h> /* for malloc, realloc */
#define RBUF__HDR(b) (((struct rbuf__hdr *)(b))-1)
#define RBUF_LEN(b) ((b) ? RBUF__HDR(b)->len : 0)
#define RBUF_CAP(b) ((b) ? RBUF__HDR(b)->cap : 0)
#define RBUF_END(b) ((b) + RBUF_LEN(b))
#define RBUF_SIZEOF(b) ((b) ? RBUF_LEN(b)*sizeof(*b) : 0)
#define RBUF_FREE(b) ((b) ? (free(RBUF__HDR(b)), (b) = NULL) : 0)
#define RBUF_FIT(b, n) ((size_t)(n) <= RBUF_CAP(b) ? 0 : (*(void**)(&(b)) = rbuf__grow((b), (n), sizeof(*(b)))))
#define RBUF_PUSH(b, val) (RBUF_FIT((b), 1 + RBUF_LEN(b)), (b)[RBUF__HDR(b)->len++] = (val))
#define RBUF_POP(b) (b)[--RBUF__HDR(b)->len]
#define RBUF_RESIZE(b, sz) (RBUF_FIT((b), (sz)), ((b) ? RBUF__HDR(b)->len = (sz) : 0))
#define RBUF_CLEAR(b) ((b) ? RBUF__HDR(b)->len = 0 : 0)
#define RBUF_TRYFIT(b, n) (RBUF_FIT((b), (n)), ((b) && RBUF_CAP(b) >= (size_t)(n)))
struct rbuf__hdr
{
size_t len;
size_t cap;
};
static void *rbuf__grow(void *buf,
size_t new_len, size_t elem_size)
{
struct rbuf__hdr *new_hdr;
size_t new_cap = MAX(2 * RBUF_CAP(buf), MAX(new_len, 16));
size_t new_size = sizeof(struct rbuf__hdr) + new_cap*elem_size;
if (buf)
{
new_hdr = (struct rbuf__hdr *)realloc(RBUF__HDR(buf), new_size);
if (!new_hdr)
return buf; /* out of memory, return unchanged */
}
else
{
new_hdr = (struct rbuf__hdr *)malloc(new_size);
if (!new_hdr)
return NULL; /* out of memory */
new_hdr->len = 0;
}
new_hdr->cap = new_cap;
return new_hdr + 1;
}
#endif

View File

@ -23,21 +23,7 @@
#include "../libretro-db/libretrodb.h"
#include <compat/strcasestr.h>
#include <compat/strl.h>
/* Stretchy buffers, invented (?) by Sean Barrett */
#define EX_BUF__HDR(b) (((struct ex_buf_hdr *)(b))-1)
#define EX_BUF_LEN(b) ((b) ? EX_BUF__HDR(b)->len : 0)
#define EX_BUF_CAP(b) ((b) ? EX_BUF__HDR(b)->cap : 0)
#define EX_BUF_END(b) ((b) + EX_BUF_LEN(b))
#define EX_BUF_SIZEOF(b) ((b) ? EX_BUF_LEN(b)*sizeof(*b) : 0)
#define EX_BUF_FREE(b) ((b) ? (free(EX_BUF__HDR(b)), (b) = NULL) : 0)
#define EX_BUF_FIT(b, n) ((size_t)(n) <= EX_BUF_CAP(b) ? 0 : (*(void**)(&(b)) = ex_buf__grow((b), (n), sizeof(*(b)))))
#define EX_BUF_PUSH(b, val) (EX_BUF_FIT((b), 1 + EX_BUF_LEN(b)), (b)[EX_BUF__HDR(b)->len++] = (val))
#define EX_BUF_POP(b) (b)[--EX_BUF__HDR(b)->len]
#define EX_BUF_RESIZE(b, sz) (EX_BUF_FIT((b), (sz)), ((b) ? EX_BUF__HDR(b)->len = (sz) : 0))
#define EX_BUF_CLEAR(b) ((b) ? EX_BUF__HDR(b)->len = 0 : 0)
#include <array/rbuf.h>
#define EX_ARENA_ALIGNMENT 8
#define EX_ARENA_BLOCK_SIZE (64 * 1024)
@ -70,12 +56,6 @@ enum
EXPLORE_TYPE_FIRSTITEM = EXPLORE_TYPE_FIRSTCATEGORY + EXPLORE_CAT_COUNT
};
struct ex_buf_hdr
{
size_t len;
size_t cap;
};
/* Arena allocator */
typedef struct ex_arena
{
@ -147,30 +127,13 @@ explore_by_info[EXPLORE_CAT_COUNT] =
/* TODO/FIXME - static global */
static explore_state_t* explore_state;
static void *ex_buf__grow(const void *buf,
size_t new_len, size_t elem_size)
{
struct ex_buf_hdr *new_hdr;
size_t new_cap = MAX(2 * EX_BUF_CAP(buf), MAX(new_len, 16));
size_t new_size = sizeof(struct ex_buf_hdr) + new_cap*elem_size;
if (buf)
new_hdr = (struct ex_buf_hdr *)realloc(EX_BUF__HDR(buf), new_size);
else
{
new_hdr = (struct ex_buf_hdr *)malloc(new_size);
new_hdr->len = 0;
}
new_hdr->cap = new_cap;
return new_hdr + 1;
}
static void ex_arena_grow(ex_arena *arena, size_t min_size)
{
size_t size = EX_ARENA_ALIGN_UP(
MAX(min_size, EX_ARENA_BLOCK_SIZE), EX_ARENA_ALIGNMENT);
arena->ptr = (char *)malloc(size);
arena->end = arena->ptr + size;
EX_BUF_PUSH(arena->blocks, arena->ptr);
RBUF_PUSH(arena->blocks, arena->ptr);
}
static void *ex_arena_alloc(ex_arena *arena, size_t size)
@ -190,10 +153,10 @@ static void ex_arena_free(ex_arena *arena)
{
char **it;
for (it = arena->blocks; it != EX_BUF_END(arena->blocks); it++)
for (it = arena->blocks; it != RBUF_END(arena->blocks); it++)
free(*it);
EX_BUF_FREE(arena->blocks);
RBUF_FREE(arena->blocks);
arena->ptr = NULL;
arena->end = NULL;
arena->blocks = NULL;
@ -459,14 +422,14 @@ static void explore_add_unique_string(
sizeof(explore_string_t) + len);
memcpy(entry->str, str, len);
entry->str[len] = '\0';
EX_BUF_PUSH(explore->by[cat], entry);
RBUF_PUSH(explore->by[cat], entry);
ex_hashmap32_setptr(&maps[cat], hash, entry);
}
if (!e->by[cat])
e->by[cat] = entry;
else
EX_BUF_PUSH(*split_buf, entry);
RBUF_PUSH(*split_buf, entry);
if (*p_next == '\0')
return;
@ -493,7 +456,7 @@ static void explore_unload_icons(explore_state_t *state)
unsigned i;
if (!state)
return;
for (i = 0; i != EX_BUF_LEN(state->icons); i++)
for (i = 0; i != RBUF_LEN(state->icons); i++)
if (state->icons[i])
video_driver_texture_unload(&state->icons[i]);
}
@ -504,16 +467,16 @@ static void explore_free(explore_state_t *state)
if (!state)
return;
for (i = 0; i != EXPLORE_CAT_COUNT; i++)
EX_BUF_FREE(state->by[i]);
RBUF_FREE(state->by[i]);
EX_BUF_FREE(state->entries);
RBUF_FREE(state->entries);
for (i = 0; i != EX_BUF_LEN(state->playlists); i++)
for (i = 0; i != RBUF_LEN(state->playlists); i++)
playlist_free(state->playlists[i]);
EX_BUF_FREE(state->playlists);
RBUF_FREE(state->playlists);
explore_unload_icons(state);
EX_BUF_FREE(state->icons);
RBUF_FREE(state->icons);
ex_arena_free(&state->arena);
}
@ -525,8 +488,8 @@ static void explore_load_icons(explore_state_t *state)
if (!state)
return;
system_count = EX_BUF_LEN(state->by[EXPLORE_BY_SYSTEM]);
EX_BUF_RESIZE(state->icons, system_count);
system_count = RBUF_LEN(state->by[EXPLORE_BY_SYSTEM]);
RBUF_RESIZE(state->icons, system_count);
fill_pathname_application_special(path, sizeof(path),
APPLICATION_SPECIAL_DIRECTORY_ASSETS_SYSICONS);
@ -689,8 +652,8 @@ static explore_state_t *explore_build_list(void)
continue;
}
EX_BUF_PUSH(rdbs, newrdb);
rdb_num = (uintptr_t)EX_BUF_LEN(rdbs);
RBUF_PUSH(rdbs, newrdb);
rdb_num = (uintptr_t)RBUF_LEN(rdbs);
ex_hashmap32_setnum(&rdb_indices, rdb_hash, rdb_num);
}
@ -715,14 +678,14 @@ static explore_state_t *explore_build_list(void)
}
if (used_entries)
EX_BUF_PUSH(explore->playlists, playlist);
RBUF_PUSH(explore->playlists, playlist);
else
playlist_free(playlist);
}
/* Loop through all RDBs referenced in the playlists
* and load meta data strings */
for (i = 0; i != EX_BUF_LEN(rdbs); i++)
for (i = 0; i != RBUF_LEN(rdbs); i++)
{
struct rmsgpack_dom_value item;
struct explore_rdb* rdb = &rdbs[i];
@ -841,19 +804,19 @@ static explore_state_t *explore_build_list(void)
}
#endif
if (EX_BUF_LEN(split_buf))
if (RBUF_LEN(split_buf))
{
size_t len;
EX_BUF_PUSH(split_buf, NULL); /* terminator */
len = EX_BUF_SIZEOF(split_buf);
RBUF_PUSH(split_buf, NULL); /* terminator */
len = RBUF_SIZEOF(split_buf);
e.split = (explore_string_t **)
ex_arena_alloc(&explore->arena, len);
memcpy(e.split, split_buf, len);
EX_BUF_CLEAR(split_buf);
RBUF_CLEAR(split_buf);
}
EX_BUF_PUSH(explore->entries, e);
RBUF_PUSH(explore->entries, e);
/* if all entries have found connections, we can leave early */
if (--rdb->count == 0)
@ -870,14 +833,14 @@ static explore_state_t *explore_build_list(void)
ex_hashmap32_free(&rdb->playlist_crcs);
ex_hashmap32_free(&rdb->playlist_names);
}
EX_BUF_FREE(split_buf);
RBUF_FREE(split_buf);
ex_hashmap32_free(&rdb_indices);
EX_BUF_FREE(rdbs);
RBUF_FREE(rdbs);
for (i = 0; i != EXPLORE_CAT_COUNT; i++)
{
uint32_t idx;
size_t len = EX_BUF_LEN(explore->by[i]);
size_t len = RBUF_LEN(explore->by[i]);
if (explore->by[i])
qsort(explore->by[i], len, sizeof(*explore->by[i]),
@ -889,7 +852,7 @@ static explore_state_t *explore_build_list(void)
ex_hashmap32_free(&cat_maps[i]);
}
qsort(explore->entries,
EX_BUF_LEN(explore->entries),
RBUF_LEN(explore->entries),
sizeof(*explore->entries), explore_qsort_func_entries);
return explore;
}
@ -1085,7 +1048,7 @@ unsigned menu_displaylist_explore(file_list_t *list)
explore_string_t **entries = explore_state->by[cat];
size_t tmplen;
if (!EX_BUF_LEN(entries))
if (!RBUF_LEN(entries))
continue;
for (i = 1; i < depth; i++)
@ -1100,14 +1063,14 @@ unsigned menu_displaylist_explore(file_list_t *list)
if (explore_by_info[cat].is_numeric)
{
snprintf(tmp + tmplen, sizeof(tmp) - tmplen, " (%s - %s)",
entries[0]->str, entries[EX_BUF_LEN(entries) - 1]->str);
entries[0]->str, entries[RBUF_LEN(entries) - 1]->str);
}
else
{
strlcat(tmp, " (", sizeof(tmp));
snprintf(tmp + tmplen + 2, sizeof(tmp) - tmplen - 2,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_EXPLORE_ITEMS_COUNT),
(unsigned)EX_BUF_LEN(entries));
(unsigned)RBUF_LEN(entries));
strlcat(tmp, ")", sizeof(tmp));
}
}
@ -1133,7 +1096,7 @@ SKIP_EXPLORE_BY_CATEGORY:;
{
/* List all items in a selected explore by category */
explore_string_t **entries = explore_state->by[current_cat];
unsigned i_last = EX_BUF_LEN(entries) - 1;
unsigned i_last = RBUF_LEN(entries) - 1;
for (i = 0; i <= i_last; i++)
explore_menu_entry(list, explore_state,
entries[i]->str, EXPLORE_TYPE_FIRSTITEM + i);
@ -1223,7 +1186,7 @@ SKIP_EXPLORE_BY_CATEGORY:;
}
e = explore_state->entries;
e_end = EX_BUF_END(explore_state->entries);
e_end = RBUF_END(explore_state->entries);
for (; e != e_end; e++)
{
@ -1307,7 +1270,7 @@ SKIP_ENTRY:;
strlcpy(explore_state->title,
pl_entry->label, sizeof(explore_state->title));
for (pl_idx = 0; pl_idx != EX_BUF_LEN(explore_state->playlists); pl_idx++)
for (pl_idx = 0; pl_idx != RBUF_LEN(explore_state->playlists); pl_idx++)
{
menu_displaylist_info_t info;
const struct playlist_entry* pl_first = NULL;
@ -1348,12 +1311,12 @@ uintptr_t menu_explore_get_entry_icon(unsigned type)
if (explore_state->show_icons == EXPLORE_ICONS_CONTENT)
{
explore_entry_t* e = &explore_state->entries[i];
if (e < EX_BUF_END(explore_state->entries))
if (e < RBUF_END(explore_state->entries))
return explore_state->icons[e->by[EXPLORE_BY_SYSTEM]->idx];
}
else if (explore_state->show_icons == EXPLORE_ICONS_SYSTEM_CATEGORY)
{
if (i < EX_BUF_LEN(explore_state->icons))
if (i < RBUF_LEN(explore_state->icons))
return explore_state->icons[i];
}
return 0;

View File

@ -30,6 +30,7 @@
#include <file/file_path.h>
#include <lists/string_list.h>
#include <formats/jsonsax_full.h>
#include <array/rbuf.h>
#include "playlist.h"
#include "verbosity.h"
@ -63,8 +64,6 @@ struct content_playlist
enum playlist_thumbnail_mode left_thumbnail_mode;
enum playlist_sort_mode sort_mode;
size_t size;
char *default_core_path;
char *default_core_name;
char *base_content_directory;
@ -78,6 +77,7 @@ typedef struct
bool in_items;
bool in_subsystem_roms;
bool capacity_exceeded;
bool out_of_memory;
unsigned array_depth;
unsigned object_depth;
@ -355,7 +355,7 @@ uint32_t playlist_get_size(playlist_t *playlist)
{
if (!playlist)
return 0;
return (uint32_t)playlist->size;
return (uint32_t)RBUF_LEN(playlist->entries);
}
char *playlist_get_conf_path(playlist_t *playlist)
@ -379,7 +379,7 @@ void playlist_get_index(playlist_t *playlist,
size_t idx,
const struct playlist_entry **entry)
{
if (!playlist || !entry || (idx >= playlist->size))
if (!playlist || !entry || (idx >= RBUF_LEN(playlist->entries)))
return;
*entry = &playlist->entries[idx];
@ -452,16 +452,16 @@ static void playlist_free_entry(struct playlist_entry *entry)
void playlist_delete_index(playlist_t *playlist,
size_t idx)
{
struct playlist_entry *entry_to_delete = NULL;
size_t len;
struct playlist_entry *entry_to_delete;
if (!playlist)
return;
if (idx >= playlist->size)
len = RBUF_LEN(playlist->entries);
if (idx >= len)
return;
playlist->size = playlist->size - 1;
/* Free unwanted entry */
entry_to_delete = (struct playlist_entry *)(playlist->entries + idx);
if (entry_to_delete)
@ -469,7 +469,9 @@ void playlist_delete_index(playlist_t *playlist,
/* Shift remaining entries to fill the gap */
memmove(playlist->entries + idx, playlist->entries + idx + 1,
(playlist->size - idx) * sizeof(struct playlist_entry));
(len - 1 - idx) * sizeof(struct playlist_entry));
RBUF_RESIZE(playlist->entries, len - 1);
playlist->modified = true;
}
@ -497,7 +499,7 @@ void playlist_delete_by_path(playlist_t *playlist,
strlcpy(real_search_path, search_path, sizeof(real_search_path));
path_resolve_realpath(real_search_path, sizeof(real_search_path), true);
while (i < playlist->size)
while (i < RBUF_LEN(playlist->entries))
{
if (!playlist_path_equal(real_search_path, playlist->entries[i].path,
&playlist->config))
@ -518,7 +520,7 @@ void playlist_get_index_by_path(playlist_t *playlist,
const char *search_path,
const struct playlist_entry **entry)
{
size_t i;
size_t i, len;
char real_search_path[PATH_MAX_LENGTH];
real_search_path[0] = '\0';
@ -530,7 +532,7 @@ void playlist_get_index_by_path(playlist_t *playlist,
strlcpy(real_search_path, search_path, sizeof(real_search_path));
path_resolve_realpath(real_search_path, sizeof(real_search_path), true);
for (i = 0; i < playlist->size; i++)
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
{
if (!playlist_path_equal(real_search_path, playlist->entries[i].path,
&playlist->config))
@ -545,7 +547,7 @@ void playlist_get_index_by_path(playlist_t *playlist,
bool playlist_entry_exists(playlist_t *playlist,
const char *path)
{
size_t i;
size_t i, len;
char real_search_path[PATH_MAX_LENGTH];
real_search_path[0] = '\0';
@ -557,7 +559,7 @@ bool playlist_entry_exists(playlist_t *playlist,
strlcpy(real_search_path, path, sizeof(real_search_path));
path_resolve_realpath(real_search_path, sizeof(real_search_path), true);
for (i = 0; i < playlist->size; i++)
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
if (playlist_path_equal(real_search_path, playlist->entries[i].path,
&playlist->config))
return true;
@ -570,7 +572,7 @@ void playlist_update(playlist_t *playlist, size_t idx,
{
struct playlist_entry *entry = NULL;
if (!playlist || idx > playlist->size)
if (!playlist || idx >= RBUF_LEN(playlist->entries))
return;
entry = &playlist->entries[idx];
@ -631,7 +633,7 @@ void playlist_update_runtime(playlist_t *playlist, size_t idx,
{
struct playlist_entry *entry = NULL;
if (!playlist || idx > playlist->size)
if (!playlist || idx >= RBUF_LEN(playlist->entries))
return;
entry = &playlist->entries[idx];
@ -736,7 +738,7 @@ void playlist_update_runtime(playlist_t *playlist, size_t idx,
bool playlist_push_runtime(playlist_t *playlist,
const struct playlist_entry *entry)
{
size_t i;
size_t i, len;
char real_path[PATH_MAX_LENGTH];
char real_core_path[PATH_MAX_LENGTH];
@ -771,7 +773,8 @@ bool playlist_push_runtime(playlist_t *playlist,
return false;
}
for (i = 0; i < playlist->size; i++)
len = RBUF_LEN(playlist->entries);
for (i = 0; i < len; i++)
{
struct playlist_entry tmp;
const char *entry_path = playlist->entries[i].path;
@ -801,19 +804,27 @@ bool playlist_push_runtime(playlist_t *playlist,
goto success;
}
if (playlist->size == playlist->config.capacity)
{
struct playlist_entry *last_entry = &playlist->entries[playlist->config.capacity - 1];
if (playlist->config.capacity == 0)
return false;
if (last_entry)
playlist_free_entry(last_entry);
playlist->size--;
if (len == playlist->config.capacity)
{
struct playlist_entry *last_entry = &playlist->entries[len - 1];
playlist_free_entry(last_entry);
len--;
}
else
{
/* Allocate memory to fit one more item and resize the buffer */
if (!RBUF_TRYFIT(playlist->entries, len + 1))
return false; /* out of memory */
RBUF_RESIZE(playlist->entries, len + 1);
}
if (playlist->entries)
{
memmove(playlist->entries + 1, playlist->entries,
(playlist->config.capacity - 1) * sizeof(struct playlist_entry));
len * sizeof(struct playlist_entry));
playlist->entries[0].path = NULL;
playlist->entries[0].core_path = NULL;
@ -843,8 +854,6 @@ bool playlist_push_runtime(playlist_t *playlist,
playlist->entries[0].last_played_str = strdup(entry->last_played_str);
}
playlist->size++;
success:
playlist->modified = true;
@ -904,7 +913,7 @@ void playlist_resolve_path(enum playlist_file_mode mode,
bool playlist_push(playlist_t *playlist,
const struct playlist_entry *entry)
{
size_t i;
size_t i, len;
char real_path[PATH_MAX_LENGTH];
char real_core_path[PATH_MAX_LENGTH];
const char *core_name = entry->core_name;
@ -954,7 +963,8 @@ bool playlist_push(playlist_t *playlist,
}
}
for (i = 0; i < playlist->size; i++)
len = RBUF_LEN(playlist->entries);
for (i = 0; i < len; i++)
{
struct playlist_entry tmp;
const char *entry_path = playlist->entries[i].path;
@ -1068,20 +1078,27 @@ bool playlist_push(playlist_t *playlist,
goto success;
}
if (playlist->size == playlist->config.capacity)
{
struct playlist_entry *last_entry =
&playlist->entries[playlist->config.capacity - 1];
if (playlist->config.capacity == 0)
return false;
if (last_entry)
playlist_free_entry(last_entry);
playlist->size--;
if (len == playlist->config.capacity)
{
struct playlist_entry *last_entry = &playlist->entries[len - 1];
playlist_free_entry(last_entry);
len--;
}
else
{
/* Allocate memory to fit one more item and resize the buffer */
if (!RBUF_TRYFIT(playlist->entries, len + 1))
return false; /* out of memory */
RBUF_RESIZE(playlist->entries, len + 1);
}
if (playlist->entries)
{
memmove(playlist->entries + 1, playlist->entries,
(playlist->config.capacity - 1) * sizeof(struct playlist_entry));
len * sizeof(struct playlist_entry));
playlist->entries[0].path = NULL;
playlist->entries[0].label = NULL;
@ -1132,8 +1149,6 @@ bool playlist_push(playlist_t *playlist,
}
}
playlist->size++;
success:
playlist->modified = true;
@ -1170,7 +1185,7 @@ static void JSONLogError(JSONContext *pCtx)
void playlist_write_runtime_file(playlist_t *playlist)
{
size_t i;
size_t i, len;
intfstream_t *file = NULL;
JSONContext context = {0};
@ -1218,7 +1233,7 @@ void playlist_write_runtime_file(playlist_t *playlist)
JSON_Writer_WriteStartArray(context.writer);
JSON_Writer_WriteNewLine(context.writer);
for (i = 0; i < playlist->size; i++)
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
{
JSON_Writer_WriteSpace(context.writer, 4);
JSON_Writer_WriteStartObject(context.writer);
@ -1379,7 +1394,7 @@ void playlist_write_runtime_file(playlist_t *playlist)
JSON_Writer_WriteSpace(context.writer, 4);
JSON_Writer_WriteEndObject(context.writer);
if (i < playlist->size - 1)
if (i < len - 1)
JSON_Writer_WriteComma(context.writer);
JSON_Writer_WriteNewLine(context.writer);
@ -1416,7 +1431,7 @@ static JSON_Status JSON_CALL JSON_Writer_WriteSpace_NULL(JSON_Writer writer, siz
void playlist_write_file(playlist_t *playlist)
{
size_t i;
size_t i, len;
intfstream_t *file = NULL;
bool compressed = false;
@ -1457,7 +1472,7 @@ void playlist_write_file(playlist_t *playlist)
#ifdef RARCH_INTERNAL
if (playlist->config.old_format)
{
for (i = 0; i < playlist->size; i++)
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
intfstream_printf(file, "%s\n%s\n%s\n%s\n%s\n%s\n",
playlist->entries[i].path ? playlist->entries[i].path : "",
playlist->entries[i].label ? playlist->entries[i].label : "",
@ -1640,7 +1655,7 @@ void playlist_write_file(playlist_t *playlist)
JSON_Writer_WriteStartArray(context.writer);
json_write_new_line(context.writer);
for (i = 0; i < playlist->size; i++)
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
{
json_write_space(context.writer, 4);
JSON_Writer_WriteStartObject(context.writer);
@ -1813,7 +1828,7 @@ void playlist_write_file(playlist_t *playlist)
json_write_space(context.writer, 4);
JSON_Writer_WriteEndObject(context.writer);
if (i < playlist->size - 1)
if (i < len - 1)
JSON_Writer_WriteComma(context.writer);
json_write_new_line(context.writer);
@ -1846,7 +1861,7 @@ end:
*/
void playlist_free(playlist_t *playlist)
{
size_t i;
size_t i, len;
if (!playlist)
return;
@ -1865,7 +1880,7 @@ void playlist_free(playlist_t *playlist)
if (playlist->entries)
{
for (i = 0; i < playlist->size; i++)
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
{
struct playlist_entry *entry = &playlist->entries[i];
@ -1873,8 +1888,7 @@ void playlist_free(playlist_t *playlist)
playlist_free_entry(entry);
}
free(playlist->entries);
playlist->entries = NULL;
RBUF_FREE(playlist->entries);
}
free(playlist);
@ -1888,18 +1902,18 @@ void playlist_free(playlist_t *playlist)
**/
void playlist_clear(playlist_t *playlist)
{
size_t i;
size_t i, len;
if (!playlist)
return;
for (i = 0; i < playlist->size; i++)
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
{
struct playlist_entry *entry = &playlist->entries[i];
if (entry)
playlist_free_entry(entry);
}
playlist->size = 0;
RBUF_CLEAR(playlist->entries);
}
/**
@ -1913,7 +1927,7 @@ size_t playlist_size(playlist_t *playlist)
{
if (!playlist)
return 0;
return playlist->size;
return RBUF_LEN(playlist->entries);
}
/**
@ -1993,8 +2007,19 @@ static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser)
{
if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded)
{
if (pCtx->playlist->size < pCtx->playlist->config.capacity)
pCtx->current_entry = &pCtx->playlist->entries[pCtx->playlist->size];
size_t len = RBUF_LEN(pCtx->playlist->entries);
if (len < pCtx->playlist->config.capacity)
{
/* Allocate memory to fit one more item but don't resize the
* buffer just yet, wait until JSONEndObjectHandler for that */
if (!RBUF_TRYFIT(pCtx->playlist->entries, len + 1))
{
pCtx->out_of_memory = true;
return JSON_Parser_Abort;
}
pCtx->current_entry = &pCtx->playlist->entries[len];
memset(pCtx->current_entry, 0, sizeof(*pCtx->current_entry));
}
else
{
/* Hit max item limit.
@ -2022,7 +2047,8 @@ static JSON_Parser_HandlerResult JSONEndObjectHandler(JSON_Parser parser)
if (pCtx->in_items && pCtx->object_depth == 2)
{
if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded)
pCtx->playlist->size++;
RBUF_RESIZE(pCtx->playlist->entries,
RBUF_LEN(pCtx->playlist->entries) + 1);
}
retro_assert(pCtx->object_depth > 0);
@ -2281,6 +2307,8 @@ static bool playlist_read_file(playlist_t *playlist)
{
unsigned i;
int test_char;
bool res = true;
#if defined(HAVE_ZLIB)
/* Always use RZIP interface when reading playlists
* > this will automatically handle uncompressed
@ -2386,6 +2414,12 @@ static bool playlist_read_file(playlist_t *playlist)
if (!JSON_Parser_Parse(context.parser, chunk,
(size_t)length, JSON_False))
{
if (context.out_of_memory)
{
RARCH_WARN("Ran out of memory while parsing JSON playlist\n");
res = false;
goto json_cleanup;
}
/* Note: Chunk may not be null-terminated.
* It is therefore dangerous to print its contents.
* Setting a size limit here mitigates the issue, but
@ -2418,6 +2452,7 @@ json_cleanup:
}
else
{
size_t len = RBUF_LEN(playlist->entries);
char line_buf[PLAYLIST_ENTRIES][PATH_MAX_LENGTH] = {{0}};
/* Unnecessary, but harmless */
@ -2425,8 +2460,7 @@ json_cleanup:
line_buf[i][0] = '\0';
/* Read playlist entries */
playlist->size = 0;
while (playlist->size < playlist->config.capacity)
while (len < playlist->config.capacity)
{
size_t i;
size_t lines_read = 0;
@ -2454,11 +2488,17 @@ json_cleanup:
* is a valid playlist entry */
if (lines_read >= PLAYLIST_ENTRIES)
{
struct playlist_entry *entry =
&playlist->entries[playlist->size];
struct playlist_entry* entry;
if (!entry)
continue;
if (!RBUF_TRYFIT(playlist->entries, len + 1))
{
res = false; /* out of memory */
goto end;
}
RBUF_RESIZE(playlist->entries, len + 1);
entry = &playlist->entries[len++];
memset(entry, 0, sizeof(*entry));
/* path */
if (!string_is_empty(line_buf[0]))
@ -2483,8 +2523,6 @@ json_cleanup:
/* db_name */
if (!string_is_empty(line_buf[5]))
entry->db_name = strdup(line_buf[5]);
playlist->size++;
}
/* If fewer than 'PLAYLIST_ENTRIES' lines were
* read, then this is metadata */
@ -2611,7 +2649,7 @@ json_cleanup:
end:
intfstream_close(file);
free(file);
return true;
return res;
}
void playlist_free_cached(void)
@ -2658,18 +2696,8 @@ bool playlist_init_cached(const playlist_config_t *config)
**/
playlist_t *playlist_init(const playlist_config_t *config)
{
struct playlist_entry *entries = NULL;
playlist_t *playlist = (playlist_t*)malloc(sizeof(*playlist));
/* Cache configuration parameters */
if (!playlist || !playlist_config_copy(config, &playlist->config))
goto error;
/* Create entries array */
entries = (struct playlist_entry*)calloc(
playlist->config.capacity, sizeof(*entries));
if (!entries)
if (!playlist)
goto error;
/* Set initial values */
@ -2677,18 +2705,22 @@ playlist_t *playlist_init(const playlist_config_t *config)
playlist->old_format = false;
playlist->compressed = false;
playlist->cached_external = false;
playlist->size = 0;
playlist->default_core_name = NULL;
playlist->default_core_path = NULL;
playlist->base_content_directory = NULL;
playlist->entries = entries;
playlist->entries = NULL;
playlist->label_display_mode = LABEL_DISPLAY_MODE_DEFAULT;
playlist->right_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
playlist->left_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
playlist->sort_mode = PLAYLIST_SORT_MODE_DEFAULT;
/* Cache configuration parameters */
if (!playlist_config_copy(config, &playlist->config))
goto error;
/* Attempt to read any existing playlist file */
playlist_read_file(playlist);
if (!playlist_read_file(playlist))
goto error;
/* Try auto-fixing paths if enabled, and playlist
* base content directory is different */
@ -2697,11 +2729,10 @@ playlist_t *playlist_init(const playlist_config_t *config)
!string_is_equal(playlist->base_content_directory,
playlist->config.base_content_directory))
{
size_t i;
size_t j;
size_t i, j, len;
char tmp_entry_path[PATH_MAX_LENGTH];
for (i = 0; i < playlist->size; i++)
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
{
struct playlist_entry *entry = &playlist->entries[i];
@ -2850,7 +2881,7 @@ void playlist_qsort(playlist_t *playlist)
(playlist->sort_mode == PLAYLIST_SORT_MODE_OFF))
return;
qsort(playlist->entries, playlist->size,
qsort(playlist->entries, RBUF_LEN(playlist->entries),
sizeof(struct playlist_entry),
(int (*)(const void *, const void *))playlist_qsort_func);
}
@ -2890,7 +2921,7 @@ bool playlist_index_is_valid(playlist_t *playlist, size_t idx,
if (!playlist)
return false;
if (idx >= playlist->size)
if (idx >= RBUF_LEN(playlist->entries))
return false;
return string_is_equal(playlist->entries[idx].path, path) &&