diff --git a/driver.c b/driver.c index a282df040c..7e7d609955 100644 --- a/driver.c +++ b/driver.c @@ -435,7 +435,10 @@ void driver_uninit(int flags) #ifdef HAVE_MENU if (flags & DRIVER_MENU_MASK) + { menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL); + menu_driver_free(); + } #endif if ((flags & DRIVER_LOCATION_MASK) && !location_driver_ctl(RARCH_LOCATION_CTL_OWNS_DRIVER, NULL)) diff --git a/libretro-common/include/array/dynarray.h b/libretro-common/include/array/dynarray.h new file mode 100644 index 0000000000..261ae9462e --- /dev/null +++ b/libretro-common/include/array/dynarray.h @@ -0,0 +1,965 @@ +/* + * A header-only typesafe dynamic array implementation for plain C, + * kinda like C++ std::vector. This code is compatible with C++, but should + * only be used with POD (plain old data) types, as it uses memcpy() etc + * instead of copy/move construction/assignment. + * It requires a new type (created with the DA_TYPEDEF(ELEMENT_TYPE, ARRAY_TYPE_NAME) + * macro) for each kind of element you want to put in a dynamic array; however + * the "functions" to manipulate the array are actually macros and the same + * for all element types. + * The array elements are accessed via dynArr.p[i] or da_get(dynArr, i) + * - the latter checks whether i is a valid index and asserts if not. + * + * One thing to keep in mind is that, because of using macros, the arguments to + * the "functions" are usually evaluated more than once, so you should avoid + * putting things with side effect (like function-calls with side effects or i++) + * into them. Notable exceptions are the value arguments (v) of da_push() + * and da_insert(), so it's still ok to do da_push(arr, fun_with_sideffects()); + * or da_insert(a, 3, x++); + * + * The function-like da_* macros are short aliases of dg_dynarr_* macros. + * If the short names clash with anything in your code or other headers + * you are using, you can, before #including this header, do + * #define DG_DYNARR_NO_SHORTNAMES + * and use the long dg_dynarr_* forms of the macros instead. + * + * Using this library in your project: + * Put this file somewhere in your project. + * In *one* of your .c/.cpp files, do + * #define DG_DYNARR_IMPLEMENTATION + * #include "DG_dynarr.h" + * to create the implementation of this library in that file. + * You can just #include "DG_dynarr.h" (without the #define) in other source + * files to use it there. + * + * See below this comment block for a usage example. + * + * You can #define your own allocators, assertion and the amount of runtime + * checking of indexes, see CONFIGURATION section in the code for more information. + * + * + * This is heavily inspired by Sean Barrett's stretchy_buffer.h + * ( see: https://github.com/nothings/stb/blob/master/stretchy_buffer.h ) + * However I wanted to have a struct that holds the array pointer and the length + * and capacity, so that struct always remains at the same address while the + * array memory might be reallocated. + * I can live with arr.p[i] instead of arr[i], but I like how he managed to use + * macros to create an API that doesn't force the user to specify the stored + * type over and over again, so I stole some of his tricks :-) + * + * This has been tested with GCC 4.8 and clang 3.8 (-std=gnu89, -std=c99 and as C++; + * -std=c89 works if you convert the C++-style comments to C comments) and + * Microsoft Visual Studio 6 and 2010 (32bit) and 2013 (32bit and 64bit). + * I guess it works with all (recentish) C++ compilers and C compilers supporting + * C99 or even C89 + C++ comments (otherwise converting the comments should help). + * + * (C) 2016 Daniel Gibson + * + * LICENSE + * This software is dual-licensed to the public domain and under the following + * license: you are granted a perpetual, irrevocable license to copy, modify, + * publish, and distribute this file as you see fit. + * No warranty implied; use at your own risk. + * + * So you can do whatever you want with this code, including copying it + * (or parts of it) into your own source. + * No need to mention me or this "license" in your code or docs, even though + * it would be appreciated, of course. + */ +#if 0 // Usage Example: + #define DG_DYNARR_IMPLEMENTATION // this define is only needed in *one* .c/.cpp file! + #include "DG_dynarr.h" + + DA_TYPEDEF(int, MyIntArrType); // creates MyIntArrType - a dynamic array for ints + + void printIntArr(MyIntArrType* arr, const char* name) + { + // note that arr is a pointer here, so use *arr in the da_*() functions. + printf("%s = {", name); + if(da_count(*arr) > 0) + printf(" %d", arr->p[0]); + for(int i=1; ip[i]); + printf(" }\n"); + } + + void myFunction() + { + MyIntArrType a1 = {0}; // make sure to zero out the struct + // instead of = {0}; you could also call da_init(a1); + + da_push(a1, 42); + assert(da_count(a1) == 1 && a1.p[0] == 42); + + int* addedElements = da_addn_uninit(a1, 3); + assert(da_count(a1) == 4); + for(size_t i=0; i<3; ++i) + addedElements[i] = i+5; + + printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7 }" + + MyIntArrType a2; + da_init(a2); + + da_addn(a2, a1.p, da_count(a1)); // copy all elements from a1 to a2 + assert(da_count(a2) == 4); + + da_insert(a2, 1, 11); + printIntArr(&a2, "a2"); // "a2 = { 42, 11, 5, 6, 7 }" + + da_delete(a2, 2); + printIntArr(&a2, "a2"); // "a2 = { 42, 11, 6, 7 }" + + da_deletefast(a2, 0); + printIntArr(&a2, "a2"); // "a2 = { 7, 11, 6 }" + + da_push(a1, 3); + printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7, 3 }" + + int x=da_pop(a1); + printf("x = %d\n", x); // "x = 3" + printIntArr(&a1, "a1"); // "a1 = { 42, 5, 6, 7 }" + + da_free(a1); // make sure not to leak memory! + da_free(a2); + } +#endif // 0 (usage example) + +#ifndef DG__DYNARR_H +#define DG__DYNARR_H + +// ######### CONFIGURATION ######### + +// following: some #defines that you can tweak to your liking + +// you can reduce some overhead by defining DG_DYNARR_INDEX_CHECK_LEVEL to 2, 1 or 0 +#ifndef DG_DYNARR_INDEX_CHECK_LEVEL + + // 0: (almost) no index checking + // 1: macros "returning" something return a.p[0] or NULL if the index was invalid + // 2: assertions in all macros taking indexes that make sure they're valid + // 3: 1 and 2 + #define DG_DYNARR_INDEX_CHECK_LEVEL 3 + +#endif // DG_DYNARR_INDEX_CHECK_LEVEL + +// you can #define your own DG_DYNARR_ASSERT(condition, msgstring) +// that will be used for all assertions in this code. +#ifndef DG_DYNARR_ASSERT + #include + #define DG_DYNARR_ASSERT(cond, msg) assert((cond) && msg) +#endif + +// you can #define DG_DYNARR_OUT_OF_MEMORY to some code that will be executed +// if allocating memory fails +// it's needed only before the #define DG_DYNARR_IMPLEMENTATION #include of +// this header, so the following is here only for reference and commented out +/* + #ifndef DG_DYNARR_OUT_OF_MEMORY + #define DG_DYNARR_OUT_OF_MEMORY DG_DYNARR_ASSERT(0, "Out of Memory!"); + #endif +*/ + +// By default, C's malloc(), realloc() and free() is used to allocate/free heap memory +// (see beginning of "#ifdef DG_DYNARR_IMPLEMENTATION" block below). +// You can #define DG_DYNARR_MALLOC, DG_DYNARR_REALLOC and DG_DYNARR_FREE yourself +// to provide alternative implementations like Win32 Heap(Re)Alloc/HeapFree +// it's needed only before the #define DG_DYNARR_IMPLEMENTATION #include of +// this header, so the following is here only for reference and commented out +/* + #define DG_DYNARR_MALLOC(elemSize, numElems) malloc(elemSize*numElems) + + // oldNumElems is not used for C's realloc, but maybe you need it for + // your allocator to copy the old elements over + #define DG_DYNARR_REALLOC(ptr, elemSize, oldNumElems, newCapacity) \ + realloc(ptr, elemSize*newCapacity); + + #define DG_DYNARR_FREE(ptr) free(ptr) +*/ + +// if you want to prepend something to the non inline (DG_DYNARR_INLINE) functions, +// like "__declspec(dllexport)" or whatever, #define DG_DYNARR_DEF +#ifndef DG_DYNARR_DEF + // by defaults it's empty. + #define DG_DYNARR_DEF +#endif + +// some functions are inline, in case your compiler doesn't like "static inline" +// but wants "__inline__" or something instead, #define DG_DYNARR_INLINE accordingly. +#ifndef DG_DYNARR_INLINE + // for pre-C99 compilers you might have to use something compiler-specific (or maybe only "static") + #ifdef _MSC_VER + #define DG_DYNARR_INLINE static __inline + #else + #define DG_DYNARR_INLINE static inline + #endif +#endif + + + +// ############### Short da_* aliases for the long names ############### + +#ifndef DG_DYNARR_NO_SHORTNAMES + +// this macro is used to create an array type (struct) for elements of TYPE +// use like DA_TYPEDEF(int, MyIntArrType); MyIntArrType ia = {0}; da_push(ia, 42); ... +#define DA_TYPEDEF(TYPE, NewArrayTypeName) \ + DG_DYNARR_TYPEDEF(TYPE, NewArrayTypeName) + +// makes sure the array is initialized and can be used. +// either do YourArray arr = {0}; or YourArray arr; da_init(arr); +#define da_init(a) \ + dg_dynarr_init(a) + +/* + * This allows you to provide an external buffer that'll be used as long as it's big enough + * once you add more elements than buf can hold, fresh memory will be allocated on the heap + * Use like: + * DA_TYPEDEF(double, MyDoubleArrType); + * MyDoubleArrType arr; + * double buf[8]; + * dg_dynarr_init_external(arr, buf, 8); + * dg_dynarr_push(arr, 1.23); + * ... + */ +#define da_init_external(a, buf, buf_cap) \ + dg_dynarr_init_external(a, buf, buf_cap) + +// use this to free the memory allocated by dg_dynarr once you don't need the array anymore +// Note: it is safe to add new elements to the array after da_free() +// it will allocate new memory, just like it would directly after da_init() +#define da_free(a) \ + dg_dynarr_free(a) + + +// add an element to the array (appended at the end) +#define da_push(a, v) \ + dg_dynarr_push(a, v) + +// add an element to the array (appended at the end) +// does the same as push, just for consistency with addn (like insert and insertn) +#define da_add(a, v) \ + dg_dynarr_add(a, v) + +// append n elements to a and initialize them from array vals, doesn't return anything +// ! vals (and all other args) are evaluated multiple times ! +#define da_addn(a, vals, n) \ + dg_dynarr_addn(a, vals, n) + +// add n elements to the end of the array and zeroes them with memset() +// returns pointer to first added element, NULL if out of memory (array is empty then) +#define da_addn_zeroed(a, n) \ + dg_dynarr_addn_zeroed(a, n) + +// add n elements to the end of the array, will remain uninitialized +// returns pointer to first added element, NULL if out of memory (array is empty then) +#define da_addn_uninit(a, n) \ + dg_dynarr_addn_uninit(a, n) + + +// insert a single value v at index idx +#define da_insert(a, idx, v) \ + dg_dynarr_insert(a, idx, v) + +// insert n elements into a at idx, initialize them from array vals +// doesn't return anything +// ! vals (and all other args) is evaluated multiple times ! +#define da_insertn(a, idx, vals, n) \ + dg_dynarr_insertn(a, idx, vals, n) + +// insert n elements into a at idx and zeroe them with memset() +// returns pointer to first inserted element or NULL if out of memory +#define da_insertn_zeroed(a, idx, n) \ + dg_dynarr_insertn_zeroed(a, idx, n) + +// insert n uninitialized elements into a at idx; +// returns pointer to first inserted element or NULL if out of memory +#define da_insertn_uninit(a, idx, n) \ + dg_dynarr_insertn_uninit(a, idx, n) + +// set a single value v at index idx - like "a.p[idx] = v;" but with checks (unless disabled) +#define da_set(a, idx, v) \ + dg_dynarr_set(a, idx, v) + +// overwrite n elements of a, starting at idx, with values from array vals +// doesn't return anything +// ! vals (and all other args) is evaluated multiple times ! +#define da_setn(a, idx, vals, n) \ + dg_dynarr_setn(a, idx, vals, n) + +// delete the element at idx, moving all following elements (=> keeps order) +#define da_delete(a, idx) \ + dg_dynarr_delete(a, idx) + +// delete n elements starting at idx, moving all following elements (=> keeps order) +#define da_deleten(a, idx, n) \ + dg_dynarr_deleten(a, idx, n) + +// delete the element at idx, move the last element there (=> doesn't keep order) +#define da_deletefast(a, idx) \ + dg_dynarr_deletefast(a, idx) + +// delete n elements starting at idx, move the last n elements there (=> doesn't keep order) +#define da_deletenfast(a, idx, n) \ + dg_dynarr_deletenfast(a, idx, n) + +// removes all elements from the array, but does not free the buffer +// (if you want to free the buffer too, just use da_free()) +#define da_clear(a) \ + dg_dynarr_clear(a) + +// sets the logical number of elements in the array +// if cnt > dg_dynarr_count(a), the logical count will be increased accordingly +// and the new elements will be uninitialized +#define da_setcount(a, cnt) \ + dg_dynarr_setcount(a, cnt) + +// make sure the array can store cap elements without reallocating +// logical count remains unchanged +#define da_reserve(a, cap) \ + dg_dynarr_reserve(a, cap) + +// this makes sure a only uses as much memory as for its elements +// => maybe useful if a used to contain a huge amount of elements, +// but you deleted most of them and want to free some memory +// Note however that this implies an allocation and copying the remaining +// elements, so only do this if it frees enough memory to be worthwhile! +#define da_shrink_to_fit(a) \ + dg_dynarr_shrink_to_fit(a) + + +// removes and returns the last element of the array +#define da_pop(a) \ + dg_dynarr_pop(a) + +// returns the last element of the array +#define da_last(a) \ + dg_dynarr_last(a) + +// returns the pointer *to* the last element of the array +// (in contrast to dg_dynarr_end() which returns a pointer *after* the last element) +// returns NULL if array is empty +#define da_lastptr(a) \ + dg_dynarr_lastptr(a) + +// get element at index idx (like a.p[idx]), but with checks +// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0) +#define da_get(a, idx) \ + dg_dynarr_get(a,idx) + +// get pointer to element at index idx (like &a.p[idx]), but with checks +// and it returns NULL if idx is invalid +#define da_getptr(a, idx) \ + dg_dynarr_getptr(a, idx) + +// returns a pointer to the first element of the array +// (together with dg_dynarr_end() you can do C++-style iterating) +#define da_begin(a) \ + dg_dynarr_begin(a) + +// returns a pointer to the past-the-end element of the array +// Allows C++-style iterating, in case you're into that kind of thing: +// for(T *it=da_begin(a), *end=da_end(a); it!=end; ++it) foo(*it); +// (see da_lastptr() to get a pointer *to* the last element) +#define da_end(a) \ + dg_dynarr_end(a) + + +// returns (logical) number of elements currently in the array +#define da_count(a) \ + dg_dynarr_count(a) + +// get the current reserved capacity of the array +#define da_capacity(a) \ + dg_dynarr_capacity(a) + +// returns 1 if the array is empty, else 0 +#define da_empty(a) \ + dg_dynarr_empty(a) + +// returns 1 if the last (re)allocation when inserting failed (Out Of Memory) +// or if the array has never allocated any memory yet, else 0 +// deleting the contents when growing fails instead of keeping old may seem +// a bit uncool, but it's simple and OOM should rarely happen on modern systems +// anyway - after all you need to deplete both RAM and swap/pagefile.sys +#define da_oom(a) \ + dg_dynarr_oom(a) + + +// sort a using the given qsort()-comparator cmp +// (just a slim wrapper around qsort()) +#define da_sort(a, cmp) \ + dg_dynarr_sort(a, cmp) + +#endif // DG_DYNARR_NO_SHORTNAMES + + +// ######### Implementation of the actual macros (using the long names) ########## + +// use like DG_DYNARR_TYPEDEF(int, MyIntArrType); MyIntArrType ia = {0}; dg_dynarr_push(ia, 42); ... +#define DG_DYNARR_TYPEDEF(TYPE, NewArrayTypeName) \ + typedef struct { TYPE* p; dg__dynarr_md md; } NewArrayTypeName; + +// makes sure the array is initialized and can be used. +// either do YourArray arr = {0}; or YourArray arr; dg_dynarr_init(arr); +#define dg_dynarr_init(a) \ + dg__dynarr_init((void**)&(a).p, &(a).md, NULL, 0) + +// this allows you to provide an external buffer that'll be used as long as it's big enough +// once you add more elements than buf can hold, fresh memory will be allocated on the heap +#define dg_dynarr_init_external(a, buf, buf_cap) \ + dg__dynarr_init((void**)&(a).p, &(a).md, (buf), (buf_cap)) + +// use this to free the memory allocated by dg_dynarr +// Note: it is safe to add new elements to the array after dg_dynarr_free() +// it will allocate new memory, just like it would directly after dg_dynarr_init() +#define dg_dynarr_free(a) \ + dg__dynarr_free((void**)&(a).p, &(a).md) + + +// add an element to the array (appended at the end) +#define dg_dynarr_push(a, v) \ + (dg__dynarr_maybegrowadd(dg__dynarr_unp(a), 1) ? (((a).p[(a).md.cnt++] = (v)),0) : 0) + +// add an element to the array (appended at the end) +// does the same as push, just for consistency with addn (like insert and insertn) +#define dg_dynarr_add(a, v) \ + dg_dynarr_push((a), (v)) + +// append n elements to a and initialize them from array vals, doesn't return anything +// ! vals (and all other args) are evaluated multiple times ! +#define dg_dynarr_addn(a, vals, n) do { \ + DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL als vals to dg_dynarr_addn!"); \ + if((vals)!=NULL && dg__dynarr_add(dg__dynarr_unp(a), n, 0)) { \ + size_t i_=(a).md.cnt-(n), v_=0; \ + while(i_<(a).md.cnt) (a).p[i_++]=(vals)[v_++]; \ + } } DG__DYNARR_WHILE0 + +// add n elements to the end of the array and zeroe them with memset() +// returns pointer to first added element, NULL if out of memory (array is empty then) +#define dg_dynarr_addn_zeroed(a, n) \ + (dg__dynarr_add(dg__dynarr_unp(a), (n), 1) ? &(a).p[(a).md.cnt-(size_t)(n)] : NULL) + +// add n elements to the end of the array, which are uninitialized +// returns pointer to first added element, NULL if out of memory (array is empty then) +#define dg_dynarr_addn_uninit(a, n) \ + (dg__dynarr_add(dg__dynarr_unp(a), (n), 0) ? &(a).p[(a).md.cnt-(size_t)(n)] : NULL) + +// insert a single value v at index idx +#define dg_dynarr_insert(a, idx, v) \ + (dg__dynarr_checkidxle((a),(idx)), \ + dg__dynarr_insert(dg__dynarr_unp(a), (idx), 1, 0), \ + (a).p[dg__dynarr_idx((a).md, (idx))] = (v)) + +// insert n elements into a at idx, initialize them from array vals +// doesn't return anything +// ! vals (and all other args) is evaluated multiple times ! +#define dg_dynarr_insertn(a, idx, vals, n) do { \ + DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL as vals to dg_dynarr_insertn!"); \ + dg__dynarr_checkidxle((a),(idx)); \ + if((vals)!=NULL && dg__dynarr_insert(dg__dynarr_unp(a), (idx), (n), 0)){ \ + size_t i_=(idx), v_=0, e_=(idx)+(n); \ + while(i_ < e_) (a).p[i_++] = (vals)[v_++]; \ + }} DG__DYNARR_WHILE0 + +// insert n elements into a at idx and zeroe them with memset() +// returns pointer to first inserted element or NULL if out of memory +#define dg_dynarr_insertn_zeroed(a, idx, n) \ + (dg__dynarr_checkidxle((a),(idx)), \ + dg__dynarr_insert(dg__dynarr_unp(a), (idx), (n), 1) \ + ? &(a).p[dg__dynarr_idx((a).md, (idx))] : NULL) + +// insert n uninitialized elements into a at idx; +// returns pointer to first inserted element or NULL if out of memory +#define dg_dynarr_insertn_uninit(a, idx, n) \ + (dg__dynarr_checkidxle((a),(idx)), \ + dg__dynarr_insert(dg__dynarr_unp(a), idx, n, 0) \ + ? &(a).p[dg__dynarr_idx((a).md, (idx))] : NULL) + +// set a single value v at index idx - like "a.p[idx] = v;" but with checks (unless disabled) +#define dg_dynarr_set(a, idx, v) \ + (dg__dynarr_checkidx((a),(idx)), \ + (a).p[dg__dynarr_idx((a).md, (idx))] = (v)) + +// overwrite n elements of a, starting at idx, with values from array vals +// doesn't return anything +// ! vals (and all other args) is evaluated multiple times ! +#define dg_dynarr_setn(a, idx, vals, n) do { \ + DG_DYNARR_ASSERT((vals)!=NULL, "Don't pass NULL as vals to dg_dynarr_setn!"); \ + size_t idx_=(idx); size_t end_=idx_+(size_t)n; \ + dg__dynarr_checkidx((a),idx_); dg__dynarr_checkidx((a),end_-1); \ + if((vals)!=NULL && idx_ < (a).md.cnt && end_ <= (a).md.cnt) { \ + size_t v_=0; \ + while(idx_ < end_) (a).p[idx_++] = (vals)[v_++]; \ + }} DG__DYNARR_WHILE0 + + +// delete the element at idx, moving all following elements (=> keeps order) +#define dg_dynarr_delete(a, idx) \ + (dg__dynarr_checkidx((a),(idx)), dg__dynarr_delete(dg__dynarr_unp(a), (idx), 1)) + +// delete n elements starting at idx, moving all following elements (=> keeps order) +#define dg_dynarr_deleten(a, idx, n) \ + (dg__dynarr_checkidx((a),(idx)), dg__dynarr_delete(dg__dynarr_unp(a), (idx), (n))) + // TODO: check whether idx+n < count? + +// delete the element at idx, move the last element there (=> doesn't keep order) +#define dg_dynarr_deletefast(a, idx) \ + (dg__dynarr_checkidx((a),(idx)), dg__dynarr_deletefast(dg__dynarr_unp(a), (idx), 1)) + +// delete n elements starting at idx, move the last n elements there (=> doesn't keep order) +#define dg_dynarr_deletenfast(a, idx, n) \ + (dg__dynarr_checkidx((a),(idx)), dg__dynarr_deletefast(dg__dynarr_unp(a), idx, n)) + // TODO: check whether idx+n < count? + +// removes all elements from the array, but does not free the buffer +// (if you want to free the buffer too, just use dg_dynarr_free()) +#define dg_dynarr_clear(a) \ + ((a).md.cnt=0) + +// sets the logical number of elements in the array +// if cnt > dg_dynarr_count(a), the logical count will be increased accordingly +// and the new elements will be uninitialized +#define dg_dynarr_setcount(a, n) \ + (dg__dynarr_maybegrow(dg__dynarr_unp(a), (n)) ? ((a).md.cnt = (n)) : 0) + +// make sure the array can store cap elements without reallocating +// logical count remains unchanged +#define dg_dynarr_reserve(a, cap) \ + dg__dynarr_maybegrow(dg__dynarr_unp(a), (cap)) + +// this makes sure a only uses as much memory as for its elements +// => maybe useful if a used to contain a huge amount of elements, +// but you deleted most of them and want to free some memory +// Note however that this implies an allocation and copying the remaining +// elements, so only do this if it frees enough memory to be worthwhile! +#define dg_dynarr_shrink_to_fit(a) \ + dg__dynarr_shrink_to_fit(dg__dynarr_unp(a)) + + +#if (DG_DYNARR_INDEX_CHECK_LEVEL == 1) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3) + + // removes and returns the last element of the array + #define dg_dynarr_pop(a) \ + (dg__dynarr_check_notempty((a), "Don't pop an empty array!"), \ + (a).p[((a).md.cnt > 0) ? (--(a).md.cnt) : 0]) + + // returns the last element of the array + #define dg_dynarr_last(a) \ + (dg__dynarr_check_notempty((a), "Don't call da_last() on an empty array!"), \ + (a).p[((a).md.cnt > 0) ? ((a).md.cnt-1) : 0]) + +#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 2) + + // removes and returns the last element of the array + #define dg_dynarr_pop(a) \ + (dg__dynarr_check_notempty((a), "Don't pop an empty array!"), \ + (a).p[--(a).md.cnt]) + + // returns the last element of the array + #define dg_dynarr_last(a) \ + (dg__dynarr_check_notempty((a), "Don't call da_last() on an empty array!"), \ + (a).p[(a).md.cnt-1]) + +#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL + #error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) ! +#endif // DG_DYNARR_INDEX_CHECK_LEVEL + +// returns the pointer *to* the last element of the array +// (in contrast to dg_dynarr_end() which returns a pointer *after* the last element) +// returns NULL if array is empty +#define dg_dynarr_lastptr(a) \ + (((a).md.cnt > 0) ? ((a).p + (a).md.cnt - 1) : NULL) + +// get element at index idx (like a.p[idx]), but with checks +// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0) +#define dg_dynarr_get(a, idx) \ + (dg__dynarr_checkidx((a),(idx)), (a).p[dg__dynarr_idx((a).md, (idx))]) + +// get pointer to element at index idx (like &a.p[idx]), but with checks +// (unless you disabled them with #define DG_DYNARR_INDEX_CHECK_LEVEL 0) +// if index-checks are disabled, it returns NULL on invalid index (else it asserts() before returning) +#define dg_dynarr_getptr(a, idx) \ + (dg__dynarr_checkidx((a),(idx)), \ + ((size_t)(idx) < (a).md.cnt) ? ((a).p+(size_t)(idx)) : NULL) + +// returns a pointer to the first element of the array +// (together with dg_dynarr_end() you can do C++-style iterating) +#define dg_dynarr_begin(a) \ + ((a).p) + +// returns a pointer to the past-the-end element of the array +// Allows C++-style iterating, in case you're into that kind of thing: +// for(T *it=dg_dynarr_begin(a), *end=dg_dynarr_end(a); it!=end; ++it) foo(*it); +// (see dg_dynarr_lastptr() to get a pointer *to* the last element) +#define dg_dynarr_end(a) \ + ((a).p + (a).md.cnt) + + +// returns (logical) number of elements currently in the array +#define dg_dynarr_count(a) \ + ((a).md.cnt) + +// get the current reserved capacity of the array +#define dg_dynarr_capacity(a) \ + ((a).md.cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) + +// returns 1 if the array is empty, else 0 +#define dg_dynarr_empty(a) \ + ((a).md.cnt == 0) + +// returns 1 if the last (re)allocation when inserting failed (Out Of Memory) +// or if the array has never allocated any memory yet, else 0 +// deleting the contents when growing fails instead of keeping old may seem +// a bit uncool, but it's simple and OOM should rarely happen on modern systems +// anyway - after all you need to deplete both RAM and swap/pagefile.sys +// or deplete the address space, which /might/ happen with 32bit applications +// but probably not with 64bit (at least in the foreseeable future) +#define dg_dynarr_oom(a) \ + ((a).md.cap == 0) + + +// sort a using the given qsort()-comparator cmp +// (just a slim wrapper around qsort()) +#define dg_dynarr_sort(a, cmp) \ + qsort((a).p, (a).md.cnt, sizeof((a).p[0]), (cmp)) + + +// ######### Implementation-Details that are not part of the API ########## + +#include // size_t, malloc(), free(), realloc() +#include // memset(), memcpy(), memmove() + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + size_t cnt; // logical number of elements + size_t cap; // cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB is actual capacity (in elements, *not* bytes!) + // if(cap & DG__DYNARR_SIZE_T_MSB) the current memory is not allocated by dg_dynarr, + // but was set with dg_dynarr_init_external() + // that's handy to give an array a base-element storage on the stack, for example + // TODO: alternatively, we could introduce a flag field to this struct and use that, + // so we don't have to calculate & everytime cap is needed +} dg__dynarr_md; + +// I used to have the following in an enum, but MSVC assumes enums are always 32bit ints +static const size_t DG__DYNARR_SIZE_T_MSB = ((size_t)1) << (sizeof(size_t)*8 - 1); +static const size_t DG__DYNARR_SIZE_T_ALL_BUT_MSB = (((size_t)1) << (sizeof(size_t)*8 - 1))-1; + +// "unpack" the elements of an array struct for use with helper functions +// (to void** arr, dg__dynarr_md* md, size_t itemsize) +#define dg__dynarr_unp(a) \ + (void**)&(a).p, &(a).md, sizeof((a).p[0]) + +// MSVC warns about "conditional expression is constant" when using the +// do { ... } while(0) idiom in macros.. +#ifdef _MSC_VER + #if _MSC_VER >= 1400 // MSVC 2005 and newer + // people claim MSVC 2005 and newer support __pragma, even though it's only documented + // for 2008+ (https://msdn.microsoft.com/en-us/library/d9x1s805%28v=vs.90%29.aspx) + // the following workaround is based on + // http://cnicholson.net/2009/03/stupid-c-tricks-dowhile0-and-c4127/ + #define DG__DYNARR_WHILE0 \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + while(0) \ + __pragma(warning(pop)) + #else // older MSVC versions don't support __pragma - I heard this helps for them + #define DG__DYNARR_WHILE0 while(0,0) + #endif + +#else // other compilers + + #define DG__DYNARR_WHILE0 while(0) + +#endif // _MSC_VER + + +#if (DG_DYNARR_INDEX_CHECK_LEVEL == 2) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3) + + #define dg__dynarr_checkidx(a,i) \ + DG_DYNARR_ASSERT((size_t)i < a.md.cnt, "index out of bounds!") + + // special case for insert operations: == cnt is also ok, insert will append then + #define dg__dynarr_checkidxle(a,i) \ + DG_DYNARR_ASSERT((size_t)i <= a.md.cnt, "index out of bounds!") + + #define dg__dynarr_check_notempty(a, msg) \ + DG_DYNARR_ASSERT(a.md.cnt > 0, msg) + +#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 1) + + // no assertions that check if index is valid + #define dg__dynarr_checkidx(a,i) (void)0 + #define dg__dynarr_checkidxle(a,i) (void)0 + + #define dg__dynarr_check_notempty(a, msg) (void)0 + +#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL + #error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) ! +#endif // DG_DYNARR_INDEX_CHECK_LEVEL + + +#if (DG_DYNARR_INDEX_CHECK_LEVEL == 1) || (DG_DYNARR_INDEX_CHECK_LEVEL == 3) + + // the given index, if valid, else 0 + #define dg__dynarr_idx(md,i) \ + (((size_t)(i) < md.cnt) ? (size_t)(i) : 0) + +#elif (DG_DYNARR_INDEX_CHECK_LEVEL == 0) || (DG_DYNARR_INDEX_CHECK_LEVEL == 2) + + // don't check and default to 0 if invalid, but just use the given value + #define dg__dynarr_idx(md,i) (size_t)(i) + +#else // invalid DG_DYNARR_INDEX_CHECK_LEVEL + #error Invalid index check level DG_DYNARR_INDEX_CHECK_LEVEL (must be 0-3) ! +#endif // DG_DYNARR_INDEX_CHECK_LEVEL + +// the functions allocating/freeing memory are not implemented inline, but +// in the #ifdef DG_DYNARR_IMPLEMENTATION section +// one reason is that dg__dynarr_grow has the most code in it, the other is +// that windows has weird per-dll heaps so free() or realloc() should be +// called from code in the same dll that allocated the memory - these kind +// of wrapper functions that end up compiled into the exe or *one* dll +// (instead of inline functions compiled into everything) should ensure that. + +DG_DYNARR_DEF void +dg__dynarr_free(void** p, dg__dynarr_md* md); + +DG_DYNARR_DEF void +dg__dynarr_shrink_to_fit(void** arr, dg__dynarr_md* md, size_t itemsize); + +// grow array to have enough space for at least min_needed elements +// if it fails (OOM), the array will be deleted, a.p will be NULL, a.md.cap and a.md.cnt will be 0 +// and the functions returns 0; else (on success) it returns 1 +DG_DYNARR_DEF int +dg__dynarr_grow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed); + + +// the following functions are implemented inline, because they're quite short +// and mosty implemented in functions so the macros don't get too ugly + +DG_DYNARR_INLINE void +dg__dynarr_init(void** p, dg__dynarr_md* md, void* buf, size_t buf_cap) +{ + *p = buf; + md->cnt = 0; + if(buf == NULL) md->cap = 0; + else md->cap = (DG__DYNARR_SIZE_T_MSB | buf_cap); +} + +DG_DYNARR_INLINE int +dg__dynarr_maybegrow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed) +{ + if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) >= min_needed) return 1; + else return dg__dynarr_grow(arr, md, itemsize, min_needed); +} + +DG_DYNARR_INLINE int +dg__dynarr_maybegrowadd(void** arr, dg__dynarr_md* md, size_t itemsize, size_t num_add) +{ + size_t min_needed = md->cnt+num_add; + if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) >= min_needed) return 1; + else return dg__dynarr_grow(arr, md, itemsize, min_needed); +} + +DG_DYNARR_INLINE int +dg__dynarr_insert(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n, int init0) +{ + // allow idx == md->cnt to append + size_t oldCount = md->cnt; + size_t newCount = oldCount+n; + if(idx <= oldCount && dg__dynarr_maybegrow(arr, md, itemsize, newCount)) + { + unsigned char* p = (unsigned char*)*arr; // *arr might have changed in dg__dynarr_grow()! + // move all existing items after a[idx] to a[idx+n] + if(idx < oldCount) memmove(p+(idx+n)*itemsize, p+idx*itemsize, itemsize*(oldCount - idx)); + + // if the memory is supposed to be zeroed, do that + if(init0) memset(p+idx*itemsize, 0, n*itemsize); + + md->cnt = newCount; + return 1; + } + return 0; +} + +DG_DYNARR_INLINE int +dg__dynarr_add(void** arr, dg__dynarr_md* md, size_t itemsize, size_t n, int init0) +{ + size_t cnt = md->cnt; + if(dg__dynarr_maybegrow(arr, md, itemsize, cnt+n)) + { + unsigned char* p = (unsigned char*)*arr; // *arr might have changed in dg__dynarr_grow()! + // if the memory is supposed to be zeroed, do that + if(init0) memset(p+cnt*itemsize, 0, n*itemsize); + + md->cnt += n; + return 1; + } + return 0; +} + +DG_DYNARR_INLINE void +dg__dynarr_delete(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n) +{ + size_t cnt = md->cnt; + if(idx < cnt) + { + if(idx+n >= cnt) md->cnt = idx; // removing last element(s) => just reduce count + else + { + unsigned char* p = (unsigned char*)*arr; + // move all items following a[idx+n] to a[idx] + memmove(p+itemsize*idx, p+itemsize*(idx+n), itemsize*(cnt - (idx+n))); + md->cnt -= n; + } + } +} + +DG_DYNARR_INLINE void +dg__dynarr_deletefast(void** arr, dg__dynarr_md* md, size_t itemsize, size_t idx, size_t n) +{ + size_t cnt = md->cnt; + if(idx < cnt) + { + if(idx+n >= cnt) md->cnt = idx; // removing last element(s) => just reduce count + else + { + unsigned char* p = (unsigned char*)*arr; + // copy the last n items to a[idx] - but handle the case that + // the array has less than n elements left after the deleted elements + size_t numItemsAfterDeleted = cnt - (idx+n); + size_t m = (n < numItemsAfterDeleted) ? n : numItemsAfterDeleted; + memcpy(p+itemsize*idx, p+itemsize*(cnt - m), itemsize*m); + md->cnt -= n; + } + } +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // DG__DYNARR_H + + +// ############## Implementation of non-inline functions ############## + +#ifdef DG_DYNARR_IMPLEMENTATION + +// by default, C's malloc(), realloc() and free() is used to allocate/free heap memory. +// you can #define DG_DYNARR_MALLOC, DG_DYNARR_REALLOC and DG_DYNARR_FREE +// to provide alternative implementations like Win32 Heap(Re)Alloc/HeapFree +// +#ifndef DG_DYNARR_MALLOC + #define DG_DYNARR_MALLOC(elemSize, numElems) malloc(elemSize*numElems) + + // oldNumElems is not used here, but maybe you need it for your allocator + // to copy the old elements over + #define DG_DYNARR_REALLOC(ptr, elemSize, oldNumElems, newCapacity) \ + realloc(ptr, elemSize*newCapacity); + + #define DG_DYNARR_FREE(ptr) free(ptr) +#endif + +// you can #define DG_DYNARR_OUT_OF_MEMORY to some code that will be executed +// if allocating memory fails +#ifndef DG_DYNARR_OUT_OF_MEMORY + #define DG_DYNARR_OUT_OF_MEMORY DG_DYNARR_ASSERT(0, "Out of Memory!"); +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +DG_DYNARR_DEF void +dg__dynarr_free(void** p, dg__dynarr_md* md) +{ + // only free memory if it doesn't point to external memory + if(!(md->cap & DG__DYNARR_SIZE_T_MSB)) + { + DG_DYNARR_FREE(*p); + *p = NULL; + md->cap = 0; + } + md->cnt = 0; +} + + +DG_DYNARR_DEF int +dg__dynarr_grow(void** arr, dg__dynarr_md* md, size_t itemsize, size_t min_needed) +{ + size_t cap = md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB; + + DG_DYNARR_ASSERT(min_needed > cap, "dg__dynarr_grow() should only be called if storage actually needs to grow!"); + + if(min_needed < DG__DYNARR_SIZE_T_MSB) + { + size_t newcap = (cap > 4) ? (2*cap) : 8; // allocate for at least 8 elements + // make sure not to set DG__DYNARR_SIZE_T_MSB (unlikely anyway) + if(newcap >= DG__DYNARR_SIZE_T_MSB) newcap = DG__DYNARR_SIZE_T_MSB-1; + if(min_needed > newcap) newcap = min_needed; + + // the memory was allocated externally, don't free it, just copy contents + if(md->cap & DG__DYNARR_SIZE_T_MSB) + { + void* p = DG_DYNARR_MALLOC(itemsize, newcap); + if(p != NULL) memcpy(p, *arr, itemsize*md->cnt); + *arr = p; + } + else + { + void* p = DG_DYNARR_REALLOC(*arr, itemsize, md->cnt, newcap); + if(p == NULL) DG_DYNARR_FREE(*arr); // realloc failed, at least don't leak memory + *arr = p; + } + + // TODO: handle OOM by setting highest bit of count and keeping old data? + + if(*arr) md->cap = newcap; + else + { + md->cap = 0; + md->cnt = 0; + + DG_DYNARR_OUT_OF_MEMORY ; + + return 0; + } + return 1; + } + DG_DYNARR_ASSERT(min_needed < DG__DYNARR_SIZE_T_MSB, "Arrays must stay below SIZE_T_MAX/2 elements!"); + return 0; +} + +DG_DYNARR_DEF void +dg__dynarr_shrink_to_fit(void** arr, dg__dynarr_md* md, size_t itemsize) +{ + // only do this if we allocated the memory ourselves + if(!(md->cap & DG__DYNARR_SIZE_T_MSB)) + { + size_t cnt = md->cnt; + if(cnt == 0) dg__dynarr_free(arr, md); + else if((md->cap & DG__DYNARR_SIZE_T_ALL_BUT_MSB) > cnt) + { + void* p = DG_DYNARR_MALLOC(itemsize, cnt); + if(p != NULL) + { + memcpy(p, *arr, cnt*itemsize); + md->cap = cnt; + DG_DYNARR_FREE(*arr); + *arr = p; + } + } + } +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // DG_DYNARR_IMPLEMENTATION diff --git a/menu/menu_animation.c b/menu/menu_animation.c index 559aa7e5bf..b0e4cd9cae 100644 --- a/menu/menu_animation.c +++ b/menu/menu_animation.c @@ -23,6 +23,10 @@ #include #include +#define DG_DYNARR_IMPLEMENTATION +#include +#undef DG_DYNARR_IMPLEMENTATION + #include "menu_animation.h" #include "../configuration.h" #include "../performance_counters.h" @@ -31,7 +35,6 @@ struct tween { - bool alive; float duration; float running_since; float initial_value; @@ -43,14 +46,13 @@ struct tween void *userdata; }; +DA_TYPEDEF(struct tween, tween_array_t); + struct menu_animation { - struct tween *list; - bool need_defrag; - - size_t capacity; - size_t size; - size_t first_dead; + tween_array_t list; + tween_array_t pending; + bool in_update; }; typedef struct menu_animation menu_animation_t; @@ -314,12 +316,22 @@ static void menu_animation_ticker_generic(uint64_t idx, *width = max_width; } +void menu_animation_init(void) +{ + da_init(anim.list); + da_init(anim.pending); +} + +void menu_animation_free(void) +{ + da_free(anim.list); + da_free(anim.pending); +} + bool menu_animation_push(menu_animation_ctx_entry_t *entry) { struct tween t; - struct tween *target = NULL; - t.alive = true; t.duration = entry->duration; t.running_since = 0; t.initial_value = *entry->subject; @@ -446,66 +458,24 @@ bool menu_animation_push(menu_animation_ctx_entry_t *entry) /* ignore born dead tweens */ if (!t.easing || t.duration == 0 || t.initial_value == t.target_value) return false; - - if (anim.first_dead < anim.size && !anim.list[anim.first_dead].alive) - target = &anim.list[anim.first_dead++]; + + if (anim.in_update) + da_push(anim.pending, t); else - { - if (anim.size >= anim.capacity) - { - anim.capacity++; - anim.list = (struct tween*)realloc(anim.list, - anim.capacity * sizeof(struct tween)); - } - - target = &anim.list[anim.size++]; - } - - *target = t; - - anim.need_defrag = true; + da_push(anim.list, t); return true; } -static int menu_animation_defrag_cmp(const void *a, const void *b) -{ - const struct tween *ta = (const struct tween *)a; - const struct tween *tb = (const struct tween *)b; - - return tb->alive - ta->alive; -} - -/* defragments and shrinks the tween list when possible */ -static void menu_animation_defrag() -{ - size_t i; - - qsort(anim.list, anim.size, sizeof(anim.list[0]), menu_animation_defrag_cmp); - - for (i = anim.size-1; i > 0; i--) - { - if (anim.list[i].alive) - break; - - anim.size--; - } - - anim.first_dead = anim.size; - anim.need_defrag = false; -} - bool menu_animation_update(float delta_time) { unsigned i; - unsigned active_tweens = 0; + + anim.in_update = true; - for(i = 0; i < anim.size; i++) + for(i = 0; i < da_count(anim.list); i++) { - struct tween *tween = &anim.list[i]; - if (!tween || !tween->alive) - continue; - + struct tween *tween = da_getptr(anim.list, i); tween->running_since += delta_time; *tween->subject = tween->easing( @@ -517,32 +487,25 @@ bool menu_animation_update(float delta_time) if (tween->running_since >= tween->duration) { *tween->subject = tween->target_value; - tween->alive = false; - anim.need_defrag = true; if (tween->cb) tween->cb(tween->userdata); + + da_delete(anim.list, i); + i--; } - - if (tween->running_since < tween->duration) - active_tweens += 1; } - - if (anim.need_defrag) - menu_animation_defrag(); - - if (!active_tweens) + + if (da_count(anim.pending) > 0) { - anim.size = 0; - anim.first_dead = 0; - anim.need_defrag = false; - return false; + da_addn(anim.list, anim.pending.p, da_count(anim.pending)); + da_clear(anim.pending); } + anim.in_update = false; + animation_is_active = da_count(anim.list) > 0; - animation_is_active = true; - - return true; + return animation_is_active; } bool menu_animation_ticker(const menu_animation_ctx_ticker_t *ticker) @@ -626,18 +589,14 @@ bool menu_animation_kill_by_tag(menu_animation_ctx_tag *tag) if (!tag || *tag == (uintptr_t)-1) return false; - for (i = 0; i < anim.size; ++i) + for (i = 0; i < da_count(anim.list); ++i) { - if (anim.list[i].tag != *tag) + struct tween *t = da_getptr(anim.list, i); + if (t->tag != *tag) continue; - - anim.list[i].alive = false; - anim.list[i].subject = NULL; - - if (i < anim.first_dead) - anim.first_dead = i; - - anim.need_defrag = true; + + da_delete(anim.list, i); + --i; } return true; @@ -648,24 +607,19 @@ void menu_animation_kill_by_subject(menu_animation_ctx_subject_t *subject) unsigned i, j, killed = 0; float **sub = (float**)subject->data; - for (i = 0; i < anim.size && killed < subject->count; ++i) + for (i = 0; i < da_count(anim.list) && killed < subject->count; ++i) { - if (!anim.list[i].alive) - continue; + struct tween *t = da_getptr(anim.list, i); for (j = 0; j < subject->count; ++j) { - if (anim.list[i].subject != sub[j]) + if (t->subject != sub[j]) continue; - anim.list[i].alive = false; - anim.list[i].subject = NULL; - - if (i < anim.first_dead) - anim.first_dead = i; + da_delete(anim.list, i); + --i; killed++; - anim.need_defrag = true; break; } } @@ -684,13 +638,14 @@ bool menu_animation_ctl(enum menu_animation_ctl_state state, void *data) { size_t i; - for (i = 0; i < anim.size; i++) + for (i = 0; i < da_count(anim.list); i++) { - if (anim.list[i].subject) - anim.list[i].subject = NULL; + struct tween *t = da_getptr(anim.list, i); + if (t->subject) + t->subject = NULL; } - free(anim.list); + da_free(anim.list); memset(&anim, 0, sizeof(menu_animation_t)); } diff --git a/menu/menu_animation.h b/menu/menu_animation.h index d1bc513489..d5a50acedd 100644 --- a/menu/menu_animation.h +++ b/menu/menu_animation.h @@ -131,6 +131,10 @@ void menu_timer_start(menu_timer_t *timer, menu_timer_ctx_entry_t *timer_entry); void menu_timer_kill(menu_timer_t *timer); +void menu_animation_init(void); + +void menu_animation_free(void); + bool menu_animation_update(float delta_time); bool menu_animation_get_ideal_delta_time(menu_animation_ctx_delta_t *delta); diff --git a/menu/menu_driver.c b/menu/menu_driver.c index fbbfe10227..8560e89234 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -2099,11 +2099,17 @@ static bool menu_driver_context_reset(bool video_is_threaded) bool menu_driver_init(bool video_is_threaded) { + menu_animation_init(); if (menu_driver_init_internal(video_is_threaded)) return menu_driver_context_reset(video_is_threaded); return false; } +void menu_driver_free(void) +{ + menu_animation_free(); +} + void menu_driver_navigation_set(bool scroll) { if (menu_driver_ctx->navigation_set) diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 33f111a8ca..dcded3f3b3 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -678,6 +678,8 @@ bool menu_driver_push_list(menu_ctx_displaylist_t *disp_list); bool menu_driver_init(bool video_is_threaded); +void menu_driver_free(void); + void menu_driver_set_thumbnail_system(char *s, size_t len); void menu_driver_set_thumbnail_content(char *s, size_t len);