diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 942c08ca..df645b43 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -1043,6 +1043,7 @@ typedef struct { rg_gui_option_t *options; size_t count; + rg_bucket_t *filenames; bool (*validator)(const char *path); } file_picker_opts_t; @@ -1054,7 +1055,7 @@ static int file_picker_cb(const rg_scandir_t *entry, void *arg) rg_gui_option_t *options = realloc(f->options, (f->count + 2) * sizeof(rg_gui_option_t)); if (!options) return RG_SCANDIR_STOP; - char *name = strdup(entry->basename); + char *name = rg_bucket_insert(f->filenames, entry->basename, strlen(entry->basename) + 1); f->options = options; f->options[f->count++] = (rg_gui_option_t){(intptr_t)name, name, NULL, RG_DIALOG_FLAG_NORMAL, NULL}; return RG_SCANDIR_CONTINUE; @@ -1065,6 +1066,7 @@ char *rg_gui_file_picker(const char *title, const char *path, bool (*validator)( file_picker_opts_t options = { .options = calloc(8, sizeof(rg_gui_option_t)), .count = 0, + .filenames = rg_bucket_create(4096, 1), .validator = validator, }; char *filepath = NULL; @@ -1098,8 +1100,7 @@ char *rg_gui_file_picker(const char *title, const char *path, bool (*validator)( } cleanup: - for (size_t i = 0; i < options.count; ++i) - free((void *)(options.options[i].arg)); + rg_bucket_free(options.filenames); free(options.options); return filepath; } diff --git a/components/retro-go/rg_utils.c b/components/retro-go/rg_utils.c index 8e842ef1..c354a5b2 100644 --- a/components/retro-go/rg_utils.c +++ b/components/retro-go/rg_utils.c @@ -351,6 +351,63 @@ const char *rg_unique_string(const char *str) return obj->data; } +typedef struct rg_bucket_s +{ + size_t capacity; + size_t cursor; + size_t alignment; + rg_bucket_t *prev; + rg_bucket_t *next; + uint8_t data[]; +} rg_bucket_t; + +rg_bucket_t *rg_bucket_create(size_t capacity_bytes, size_t alignment_bytes) +{ + rg_bucket_t *bucket = calloc(1, sizeof(rg_bucket_t) + capacity_bytes); + if (!bucket) + return NULL; + bucket->capacity = capacity_bytes; + bucket->alignment = alignment_bytes ?: 1; + return bucket; +} + +void *rg_bucket_insert(rg_bucket_t *bucket, const void *item, size_t item_bytes) +{ + if (!bucket || bucket->capacity < item_bytes) + { + RG_LOGW("Item size exceeds bucket capacity!"); + return NULL; + } + while (bucket->cursor + item_bytes > bucket->capacity) + { + if (!bucket->next) // End of the list, must allocate + { + rg_bucket_t *new_bucket = rg_bucket_create(bucket->capacity, bucket->alignment); + if (!new_bucket) + return NULL; + new_bucket->prev = bucket; + bucket->next = new_bucket; + bucket = new_bucket; + break; + } + bucket = bucket->next; + } + void *ptr = memcpy(bucket->data + bucket->cursor, item, item_bytes); + bucket->cursor += item_bytes; + bucket->cursor += bucket->cursor % bucket->alignment; + return ptr; +} + +void rg_bucket_free(rg_bucket_t *bucket) +{ + while (bucket) + { + rg_bucket_t *next = bucket->next; + free(bucket); + bucket = next; + } +} + // Note: You should use calloc/malloc everywhere possible. This function is used to ensure // that some memory is put in specific regions for performance or hardware reasons. // Memory from this function should be freed with free() diff --git a/components/retro-go/rg_utils.h b/components/retro-go/rg_utils.h index 8d7b5b25..16ac9101 100644 --- a/components/retro-go/rg_utils.h +++ b/components/retro-go/rg_utils.h @@ -73,6 +73,14 @@ const char *rg_relpath(const char *path); uint32_t rg_crc32(uint32_t crc, const uint8_t *buf, size_t len); uint32_t rg_hash(const char *buf, size_t len); +/* Bucket allocator */ +// The bucket allocator is basically a linked-list of buckets. It's not smart in any way and can only grow. +// Its purpose is to replace thousands of small allocations that fill internal memory (eg strdup) +typedef struct rg_bucket_s rg_bucket_t; +rg_bucket_t *rg_bucket_create(size_t capacity_bytes, size_t alignment_bytes); +void *rg_bucket_insert(rg_bucket_t *bucket, const void *item, size_t item_bytes); +void rg_bucket_free(rg_bucket_t *bucket); + /* Misc */ void *rg_alloc(size_t size, uint32_t caps); // rg_usleep behaves like usleep in libc: it will sleep for *at least* `us` microseconds, but possibly more diff --git a/launcher/main/applications.c b/launcher/main/applications.c index 08419f7c..9c0d8ccb 100644 --- a/launcher/main/applications.c +++ b/launcher/main/applications.c @@ -61,8 +61,15 @@ static int scan_folder_cb(const rg_scandir_t *entry, void *arg) app->files_capacity = new_capacity; } + char *name = rg_bucket_insert(app->filenames, entry->basename, strlen(entry->basename) + 1); + if (!name) + { + RG_LOGW("Ran out of memory for names, file scanning stopped at %d entries ...", app->files_count); + return RG_SCANDIR_STOP; + } + app->files[app->files_count++] = (retro_file_t) { - .name = strdup(entry->basename), + .name = name, .folder = rg_unique_string(entry->dirname), .checksum = 0, .missing_cover = 0, @@ -422,8 +429,8 @@ static void event_handler(gui_event_t event, tab_t *tab) { if (app && app->initialized) { - for (size_t i = 0; i < app->files_count; ++i) - free((char *)app->files[i].name); + rg_bucket_free(app->filenames); + app->filenames = rg_bucket_create(4096, 1); app->files_count = 0; app->initialized = false; } @@ -674,6 +681,7 @@ static void application(const char *desc, const char *name, const char *exts, co app->available = rg_system_have_app(app->partition); app->files = calloc(100, sizeof(retro_file_t)); app->files_capacity = 100; + app->filenames = rg_bucket_create(4096, 1); app->crc_offset = crc_offset; gui_add_tab(app->short_name, app->description, app, event_handler); @@ -685,7 +693,7 @@ void applications_init(void) application("Super Nintendo", "snes", "smc sfc zip", "retro-core", 0); application("Nintendo Gameboy", "gb", "gb gbc zip", "retro-core", 0); application("Nintendo Gameboy Color", "gbc", "gbc gb zip", "retro-core", 0); - // application("Nintendo Gameboy Advance", "gba", "gba zip", "gbsp", 0); + application("Nintendo Gameboy Advance", "gba", "gba zip", "gbsp", 0); application("Nintendo Game & Watch", "gw", "gw", "retro-core", 0); // application("Sega SG-1000", "sg1", "sms sg sg1", "retro-core", 0); application("Sega Master System", "sms", "sms sg zip", "retro-core", 0); diff --git a/launcher/main/applications.h b/launcher/main/applications.h index 3a6104fb..b313ec6c 100644 --- a/launcher/main/applications.h +++ b/launcher/main/applications.h @@ -40,6 +40,7 @@ typedef struct retro_app_s retro_file_t *files; size_t files_capacity; size_t files_count; + rg_bucket_t *filenames; bool use_crc_covers; bool initialized; bool available;