Launcher: Added bucket allocator to avoid thousands of strdup filling internal memory

This commit is contained in:
Alex Duchesne 2025-11-07 18:22:12 -05:00
parent 7b37da4901
commit 36b2ae46fc
5 changed files with 82 additions and 7 deletions

View File

@ -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;
}

View File

@ -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()

View File

@ -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

View File

@ -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);

View File

@ -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;