From 4cb264a3ff35cd1dcd8a41f87fb87791920d5156 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 20 May 2018 20:50:01 +0200 Subject: m_config: remove an old temporary hack Actually rewrite most of the option management code. This affects how options are allocated, and how thread-safe access to them is done. One thing that is nicer is that creating m_config_cache does not need to ridiculously recreate and store the entire option list again. Instead, option metadata and option storage are now separated. m_config contains the metadata, and m_config_data all or parts of the actual option values. (m_config_cache simply uses the metadata part of m_config, which is immutable after creation.) The mentioned hack was introduced in commit 1a2319f3e4cc4, and is the global state around g_group_mutex. Although it was "benign" global state, it's good that it's finally removed. --- options/m_config.c | 620 ++++++++++++++++++++++++++++------------------------- options/m_config.h | 24 ++- 2 files changed, 336 insertions(+), 308 deletions(-) diff --git a/options/m_config.c b/options/m_config.c index aa0018314d..77c0fcdc06 100644 --- a/options/m_config.c +++ b/options/m_config.c @@ -55,19 +55,42 @@ static const union m_option_value default_value; // For use with m_config_cache. struct m_config_shadow { - pthread_mutex_t lock; struct m_config *root; - char *data; + pthread_mutex_t lock; + // -- protected by lock + struct m_config_data *data; // protected shadow copy of the option data struct m_config_cache **listeners; int num_listeners; }; // Represents a sub-struct (OPT_SUBSTRUCT()). struct m_config_group { - const struct m_sub_options *group; // or NULL for top-level options - int parent_group; // index of parent group in m_config.groups - void *opts; // pointer to group user option struct - atomic_llong ts; // incremented on every write access + const struct m_sub_options *group; + int group_count; // 1 + number of all sub groups owned by this (so + // m_config.groups[idx..idx+group_count] is used by the + // entire tree of sub groups included by this group) + int parent_group; // index of parent group into m_config.groups[], or + // -1 for group 0 + int parent_ptr; // ptr offset in the parent group's data, or -1 if + // none + int co_index; // index of the first group opt into m_config.opts[] + int co_end_index; // index of the last group opt + 1 (i.e. exclusive) +}; + +// A copy of option data. Used for the main option struct, the shadow data, +// and copies for m_config_cache. +struct m_config_data { + struct m_config *root; // root config (with up-to-date data) + int group_index; // start index into m_config.groups[] + struct m_group_data *gdata; // user struct allocation (our copy of data) + int num_gdata; // (group_index+num_gdata = end index) + atomic_llong ts; // last change timestamp we've seen +}; + +// Per m_config_data state for each m_config_group. +struct m_group_data { + char *udata; // pointer to group user option struct + long long ts; // incremented on every write access }; struct m_profile { @@ -86,6 +109,20 @@ struct m_opt_backup { void *backup; }; +static void add_sub_group(struct m_config *config, const char *name_prefix, + int parent_group_index, int parent_ptr, + const struct m_sub_options *subopts); + +static struct m_group_data *m_config_gdata(struct m_config_data *data, + int group_index) +{ + if (group_index < data->group_index || + group_index >= data->group_index + data->num_gdata) + return NULL; + + return &data->gdata[group_index - data->group_index]; +} + static int show_profile(struct m_config *config, bstr param) { struct m_profile *p; @@ -140,30 +177,125 @@ static void substruct_write_ptr(void *ptr, void *val) memcpy(ptr, &src, sizeof(src)); } -static void add_options(struct m_config *config, - struct m_config_option *parent, - void *optstruct, - const void *optstruct_def, - const struct m_option *defs); +// Initialize a field with a given value. In case this is dynamic data, it has +// to be allocated and copied. src can alias dst, also can be NULL. +static void init_opt_inplace(const struct m_option *opt, void *dst, + const void *src) +{ + union m_option_value temp = {0}; + if (src) + memcpy(&temp, src, opt->type->size); + memset(dst, 0, opt->type->size); + m_option_copy(opt, dst, &temp); +} -static void config_destroy(void *p) +static void alloc_group(struct m_config_data *data, int group_index, + struct m_config_data *copy) { - struct m_config *config = p; - m_config_restore_backups(config); - for (int n = 0; n < config->num_opts; n++) { - struct m_config_option *co = &config->opts[n]; + assert(group_index == data->group_index + data->num_gdata); + assert(group_index < data->root->num_groups); + struct m_config_group *group = &data->root->groups[group_index]; + const struct m_sub_options *opts = group->group; + + MP_TARRAY_GROW(data, data->gdata, data->num_gdata); + struct m_group_data *gdata = &data->gdata[data->num_gdata++]; - m_option_free(co->opt, co->data); + struct m_group_data *copy_gdata = + copy ? m_config_gdata(copy, group_index) : NULL; - if (config->shadow && co->shadow_offset >= 0) - m_option_free(co->opt, config->shadow->data + co->shadow_offset); + *gdata = (struct m_group_data){ + .udata = talloc_zero_size(data, opts->size), + .ts = copy_gdata ? copy_gdata->ts : 0, + }; + + if (opts->defaults) + memcpy(gdata->udata, opts->defaults, opts->size); + + char *copy_src = copy_gdata ? copy_gdata->udata : NULL; + + for (int n = group->co_index; n < group->co_end_index; n++) { + assert(n >= 0 && n < data->root->num_opts); + struct m_config_option *co = &data->root->opts[n]; + + if (co->opt->offset < 0 || co->opt->type->size == 0) + continue; + + const void *defptr = co->default_data; + if (copy_src) + defptr = copy_src + co->opt->offset; + + if (defptr) + init_opt_inplace(co->opt, gdata->udata + co->opt->offset, defptr); + } + + // If there's a parent, update its pointer to the new struct. + if (group->parent_group >= data->group_index && group->parent_ptr >= 0) { + struct m_group_data *parent_gdata = + m_config_gdata(data, group->parent_group); + assert(parent_gdata); + + substruct_write_ptr(parent_gdata->udata + group->parent_ptr, gdata->udata); } +} + +static void free_option_data(void *p) +{ + struct m_config_data *data = p; + + for (int i = 0; i < data->num_gdata; i++) { + struct m_group_data *gdata = &data->gdata[i]; + struct m_config_group *group = &data->root->groups[data->group_index + i]; + + for (int n = group->co_index; n < group->co_end_index; n++) { + struct m_config_option *co = &data->root->opts[n]; + + if (co->opt->offset >= 0 && co->opt->type->size > 0) + m_option_free(co->opt, gdata->udata + co->opt->offset); + } + } +} + +// Allocate data using the option description in root, starting at group_index +// (index into m_config.groups[]). +// If copy is not NULL, copy all data from there (for groups which are in both +// m_config_data instances), in all other cases init the data with the defaults. +static struct m_config_data *allocate_option_data(void *ta_parent, + struct m_config *root, + int group_index, + struct m_config_data *copy) +{ + assert(group_index >= 0 && group_index < root->num_groups); + struct m_config_data *data = talloc_zero(ta_parent, struct m_config_data); + talloc_set_destructor(data, free_option_data); + + data->root = root; + data->group_index = group_index; + + struct m_config_group *root_group = &root->groups[group_index]; + assert(root_group->group_count > 0); + + for (int n = group_index; n < group_index + root_group->group_count; n++) + alloc_group(data, n, copy); + + if (copy) + data->ts = copy->ts; + + return data; +} + +static void config_destroy(void *p) +{ + struct m_config *config = p; + m_config_restore_backups(config); if (config->shadow) { // must all have been unregistered assert(config->shadow->num_listeners == 0); pthread_mutex_destroy(&config->shadow->lock); + talloc_free(config->shadow); } + + talloc_free(config->data); } struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, @@ -175,22 +307,27 @@ struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, *config = (struct m_config) {.log = log, .size = size, .defaults = defaults, .options = options}; - // size==0 means a dummy object is created - if (size) { - config->optstruct = talloc_zero_size(config, size); - if (defaults) - memcpy(config->optstruct, defaults, size); - } - - config->num_groups = 1; - MP_TARRAY_GROW(config, config->groups, 1); - config->groups[0] = (struct m_config_group){ - .parent_group = -1, - .opts = config->optstruct, + struct m_sub_options *subopts = talloc_ptrtype(config, subopts); + *subopts = (struct m_sub_options){ + .opts = options, + .size = size, + .defaults = defaults, }; + add_sub_group(config, NULL, -1, -1, subopts); + + if (!size) + return config; + + config->data = allocate_option_data(config, config, 0, NULL); + config->optstruct = config->data->gdata[0].udata; + + for (int n = 0; n < config->num_opts; n++) { + struct m_config_option *co = &config->opts[n]; + struct m_group_data *gdata = m_config_gdata(config->data, co->group_index); + if (gdata && co->opt->offset >= 0) + co->data = gdata->udata + co->opt->offset; + } - if (options) - add_options(config, NULL, config->optstruct, defaults, options); return config; } @@ -216,14 +353,14 @@ struct m_config *m_config_from_obj_desc_noalloc(void *talloc_ctx, return m_config_new(talloc_ctx, log, 0, desc->priv_defaults, desc->options); } -static struct m_config_group *find_group(struct mpv_global *global, - const struct m_option *cfg) +static const struct m_config_group *find_group(struct mpv_global *global, + const struct m_option *cfg) { struct m_config_shadow *shadow = global->config; struct m_config *root = shadow->root; for (int n = 0; n < root->num_groups; n++) { - if (cfg && root->groups[n].group && root->groups[n].group->opts == cfg) + if (root->groups[n].group->opts == cfg) return &root->groups[n]; } @@ -238,7 +375,7 @@ static struct m_config_group *find_group(struct mpv_global *global, void *m_config_group_from_desc(void *ta_parent, struct mp_log *log, struct mpv_global *global, struct m_obj_desc *desc, const char *name) { - struct m_config_group *group = find_group(global, desc->options); + const struct m_config_group *group = find_group(global, desc->options); if (group) { return mp_get_config_group(ta_parent, global, group->group); } else { @@ -335,211 +472,120 @@ void m_config_backup_all_opts(struct m_config *config) ensure_backup(config, &config->opts[n]); } -static void m_config_add_option(struct m_config *config, - struct m_config_option *parent, - void *optstruct, - const void *optstruct_def, - const struct m_option *arg); - -static void add_options(struct m_config *config, - struct m_config_option *parent, - void *optstruct, - const void *optstruct_def, - const struct m_option *defs) -{ - for (int i = 0; defs && defs[i].name; i++) - m_config_add_option(config, parent, optstruct, optstruct_def, &defs[i]); -} - -static void add_sub_options(struct m_config *config, - struct m_config_option *parent, - const struct m_sub_options *subopts) -{ - // Can't be used multiple times. - for (int n = 0; n < config->num_groups; n++) - assert(config->groups[n].group != subopts); - - // You can only use UPDATE_ flags here. - assert(!(subopts->change_flags & ~(unsigned)UPDATE_OPTS_MASK)); - - void *new_optstruct = NULL; - if (config->optstruct) { // only if not noalloc - new_optstruct = talloc_zero_size(config, subopts->size); - if (subopts->defaults) - memcpy(new_optstruct, subopts->defaults, subopts->size); - } - if (parent && parent->data) - substruct_write_ptr(parent->data, new_optstruct); - - const void *new_optstruct_def = NULL; - if (parent && parent->default_data) - new_optstruct_def = substruct_read_ptr(parent->default_data); - if (!new_optstruct_def) - new_optstruct_def = subopts->defaults; - - int group = config->num_groups++; - MP_TARRAY_GROW(config, config->groups, group); - config->groups[group] = (struct m_config_group){ - .group = subopts, - .parent_group = parent ? parent->group : 0, - .opts = new_optstruct, - }; - - struct m_config_option next = { - .name = "", - .group = group, - }; - if (parent && parent->name && parent->name[0]) - next.name = parent->name; - if (subopts->prefix && subopts->prefix[0]) { - assert(next.name); - next.name = subopts->prefix; - } - add_options(config, &next, new_optstruct, new_optstruct_def, subopts->opts); -} - -#define MAX_VO_AO 16 - -struct group_entry { - const struct m_obj_list *entry; - struct m_sub_options subs[MAX_VO_AO]; - bool initialized; -}; - -static struct group_entry g_groups[2]; // limited by max. m_obj_list overall -static int g_num_groups = 0; -static pthread_mutex_t g_group_mutex = PTHREAD_MUTEX_INITIALIZER; - -static const struct m_sub_options *get_cached_group(const struct m_obj_list *list, - int n, struct m_sub_options *v) -{ - pthread_mutex_lock(&g_group_mutex); - - struct group_entry *group = NULL; - for (int i = 0; i < g_num_groups; i++) { - if (g_groups[i].entry == list) { - group = &g_groups[i]; - break; - } - } - if (!group) { - assert(g_num_groups < MP_ARRAY_SIZE(g_groups)); - group = &g_groups[g_num_groups++]; - group->entry = list; - } - - if (!group->initialized) { - if (!v) { - n = -1; - group->initialized = true; - } else { - assert(n < MAX_VO_AO); // simply increase this if it fails - group->subs[n] = *v; - } - } - - pthread_mutex_unlock(&g_group_mutex); - - return n >= 0 ? &group->subs[n] : NULL; -} - static void init_obj_settings_list(struct m_config *config, + int parent_group_index, const struct m_obj_list *list) { struct m_obj_desc desc; for (int n = 0; ; n++) { - if (!list->get_desc(&desc, n)) { - if (list->use_global_options) - get_cached_group(list, n, NULL); + if (!list->get_desc(&desc, n)) break; + if (desc.global_opts) { + add_sub_group(config, NULL, parent_group_index, -1, + desc.global_opts); } - if (desc.global_opts) - add_sub_options(config, NULL, desc.global_opts); if (list->use_global_options && desc.options) { - struct m_sub_options conf = { + struct m_sub_options *conf = talloc_ptrtype(config, conf); + *conf = (struct m_sub_options){ .prefix = desc.options_prefix, .opts = desc.options, .defaults = desc.priv_defaults, .size = desc.priv_size, }; - add_sub_options(config, NULL, get_cached_group(list, n, &conf)); + add_sub_group(config, NULL, parent_group_index, -1, conf); } } } -// Initialize a field with a given value. In case this is dynamic data, it has -// to be allocated and copied. src can alias dst, also can be NULL. -static void init_opt_inplace(const struct m_option *opt, void *dst, - const void *src) +static const char *concat_name(void *ta_parent, const char *a, const char *b) { - union m_option_value temp = {0}; - if (src) - memcpy(&temp, src, opt->type->size); - memset(dst, 0, opt->type->size); - m_option_copy(opt, dst, &temp); + assert(a); + assert(b); + if (!a[0]) + return b; + if (!b[0]) + return a; + return talloc_asprintf(ta_parent, "%s-%s", a, b); } -static void m_config_add_option(struct m_config *config, - struct m_config_option *parent, - void *optstruct, - const void *optstruct_def, - const struct m_option *arg) +static void add_sub_group(struct m_config *config, const char *name_prefix, + int parent_group_index, int parent_ptr, + const struct m_sub_options *subopts) { - assert(config != NULL); - assert(arg != NULL); + // Can't be used multiple times. + for (int n = 0; n < config->num_groups; n++) + assert(config->groups[n].group != subopts); + + // You can only use UPDATE_ flags here. + assert(!(subopts->change_flags & ~(unsigned)UPDATE_OPTS_MASK)); - const char *parent_name = parent ? parent->name : ""; + assert(parent_group_index >= -1 && parent_group_index < config->num_groups); - struct m_config_option co = { - .opt = arg, - .name = arg->name, - .shadow_offset = -1, - .group = parent ? parent->group : 0, - .default_data = &default_value, - .is_hidden = !!arg->deprecation_message, + int group_index = config->num_groups++; + MP_TARRAY_GROW(config, config->groups, group_index); + config->groups[group_index] = (struct m_config_group){ + .group = subopts, + .parent_group = parent_group_index, + .parent_ptr = parent_ptr, + .co_index = config->num_opts, }; - if (arg->offset >= 0) { - if (optstruct) - co.data = (char *)optstruct + arg->offset; - if (optstruct_def) - co.default_data = (char *)optstruct_def + arg->offset; - } + const void *optstruct_def = subopts->defaults; + + if (subopts->prefix && subopts->prefix[0]) + name_prefix = subopts->prefix; + if (!name_prefix) + name_prefix = ""; - if (arg->defval) - co.default_data = arg->defval; + for (int i = 0; subopts->opts && subopts->opts[i].name; i++) { + const struct m_option *opt = &subopts->opts[i]; - // Fill in the full name - if (!co.name[0]) { - co.name = parent_name; - } else if (parent_name[0]) { - co.name = talloc_asprintf(config, "%s-%s", parent_name, co.name); + if (opt->type == &m_option_type_subconfig) + continue; + + struct m_config_option co = { + .name = concat_name(config, name_prefix, opt->name), + .opt = opt, + .group_index = group_index, + .default_data = &default_value, + .is_hidden = !!opt->deprecation_message, + }; + + if (opt->offset >= 0 && optstruct_def) + co.default_data = (char *)optstruct_def + opt->offset; + + if (opt->defval) + co.default_data = opt->defval; + + if (opt->type != &m_option_type_subconfig) + MP_TARRAY_APPEND(config, config->opts, config->num_opts, co); } - if (arg->type == &m_option_type_subconfig) { - const struct m_sub_options *subopts = arg->priv; - add_sub_options(config, &co, subopts); - } else { - int size = arg->type->size; - if (optstruct && size) { - // The required alignment is unknown, so go with the maximum C - // could require. Slightly wasteful, but not that much. - int align = (size - config->shadow_size % size) % size; - int offset = config->shadow_size + align; - assert(offset <= INT16_MAX); - co.shadow_offset = offset; - config->shadow_size = co.shadow_offset + size; - } + config->groups[group_index].co_end_index = config->num_opts; - // Initialize options - if (co.data && co.default_data) - init_opt_inplace(arg, co.data, co.default_data); + // Initialize sub-structs. These have to come after, because co_index and + // co_end_index must strictly be for a single struct only. + for (int i = 0; subopts->opts && subopts->opts[i].name; i++) { + const struct m_option *opt = &subopts->opts[i]; - MP_TARRAY_APPEND(config, config->opts, config->num_opts, co); + if (opt->type == &m_option_type_subconfig) { + const struct m_sub_options *new_subopts = opt->priv; - if (arg->type == &m_option_type_obj_settings_list) - init_obj_settings_list(config, (const struct m_obj_list *)arg->priv); + // Providing default structs in-place is not allowed. + if (opt->offset >= 0 && optstruct_def) { + void *ptr = (char *)optstruct_def + opt->offset; + assert(!substruct_read_ptr(ptr)); + } + + const char *prefix = concat_name(config, name_prefix, opt->name); + add_sub_group(config, prefix, group_index, opt->offset, new_subopts); + } else if (opt->type == &m_option_type_obj_settings_list) { + const struct m_obj_list *objlist = opt->priv; + init_obj_settings_list(config, group_index, objlist); + } } + + config->groups[group_index].group_count = config->num_groups - group_index; } struct m_config_option *m_config_get_co_raw(const struct m_config *config, @@ -753,7 +799,6 @@ static int m_config_handle_special_options(struct m_config *config, return M_OPT_UNKNOWN; } - // Unlike m_config_set_option_raw() this does not go through the property layer // via config.option_set_callback. int m_config_set_option_raw_direct(struct m_config *config, @@ -1191,33 +1236,13 @@ void m_config_create_shadow(struct m_config *config) assert(config->global && config->options && config->size); assert(!config->shadow && !config->global->config); - config->shadow = talloc_zero(config, struct m_config_shadow); - config->shadow->data = talloc_zero_size(config->shadow, config->shadow_size); - + config->shadow = talloc_zero(NULL, struct m_config_shadow); + config->shadow->data = + allocate_option_data(config->shadow, config, 0, config->data); config->shadow->root = config; pthread_mutex_init(&config->shadow->lock, NULL); config->global->config = config->shadow; - - for (int n = 0; n < config->num_opts; n++) { - struct m_config_option *co = &config->opts[n]; - if (co->shadow_offset < 0) - continue; - m_option_copy(co->opt, config->shadow->data + co->shadow_offset, co->data); - } -} - -// Return whether parent is a parent of group. Also returns true if they're equal. -static bool is_group_included(struct m_config *config, int group, int parent) -{ - for (;;) { - if (group == parent) - return true; - if (group < 0) - break; - group = config->groups[group].parent_group; - } - return false; } static void cache_destroy(void *p) @@ -1236,58 +1261,60 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent, { struct m_config_shadow *shadow = global->config; struct m_config *root = shadow->root; + int group_index = -1; + + for (int n = 0; n < root->num_groups; n++) { + // group==NULL is special cased to root group. + if (root->groups[n].group == group || (!group && !n)) { + group_index = n; + break; + } + } + + assert(group_index >= 0); // invalid group (or not in option tree) struct m_config_cache *cache = talloc_zero(ta_parent, struct m_config_cache); talloc_set_destructor(cache, cache_destroy); cache->shadow = shadow; - cache->shadow_config = m_config_new(cache, mp_null_log, root->size, - root->defaults, root->options); - struct m_config *config = cache->shadow_config; + pthread_mutex_lock(&shadow->lock); + cache->data = allocate_option_data(cache, root, group_index, shadow->data); + pthread_mutex_unlock(&shadow->lock); - assert(config->num_opts == root->num_opts); - for (int n = 0; n < root->num_opts; n++) { - assert(config->opts[n].opt->type == root->opts[n].opt->type); - assert(config->opts[n].shadow_offset == root->opts[n].shadow_offset); - } + cache->opts = cache->data->gdata[0].udata; - cache->ts = -1; - cache->group = -1; + return cache; +} - for (int n = 0; n < config->num_groups; n++) { - if (config->groups[n].group == group) { - cache->opts = config->groups[n].opts; - cache->group = n; - break; - } - } +static void update_options(struct m_config_data *dst, struct m_config_data *src) +{ + assert(dst->root == src->root); + + dst->ts = src->ts; + + // Must be from same root, but they can have arbitrary overlap. + int group_s = MPMAX(dst->group_index, src->group_index); + int group_e = MPMIN(dst->group_index + dst->num_gdata, + src->group_index + src->num_gdata); + assert(group_s >= 0 && group_e <= dst->root->num_groups); + for (int n = group_s; n < group_e; n++) { + struct m_config_group *g = &dst->root->groups[n]; + struct m_group_data *gsrc = m_config_gdata(src, n); + struct m_group_data *gdst = m_config_gdata(dst, n); + assert(gsrc && gdst); + + if (gsrc->ts <= gdst->ts) + continue; + gdst->ts = gsrc->ts; - assert(cache->group >= 0); - assert(cache->opts); - - // If we're not on the top-level, restrict set of options to the sub-group - // to reduce update costs. (It would be better not to add them in the first - // place.) - if (cache->group > 0) { - int num_opts = config->num_opts; - config->num_opts = 0; - for (int n = 0; n < num_opts; n++) { - struct m_config_option *co = &config->opts[n]; - if (is_group_included(config, co->group, cache->group)) { - config->opts[config->num_opts++] = *co; - } else { - m_option_free(co->opt, co->data); + for (int i = g->co_index; i < g->co_end_index; i++) { + struct m_config_option *co = &dst->root->opts[i]; + if (co->opt->offset >= 0 && co->opt->type->size) { + m_option_copy(co->opt, gdst->udata + co->opt->offset, + gsrc->udata + co->opt->offset); } } - for (int n = 0; n < config->num_groups; n++) { - if (!is_group_included(config, n, cache->group)) - TA_FREEP(&config->groups[n].opts); - } } - - m_config_cache_update(cache); - - return cache; } bool m_config_cache_update(struct m_config_cache *cache) @@ -1296,16 +1323,11 @@ bool m_config_cache_update(struct m_config_cache *cache) // Using atomics and checking outside of the lock - it's unknown whether // this makes it faster or slower. Just cargo culting it. - if (atomic_load(&shadow->root->groups[cache->group].ts) <= cache->ts) + if (atomic_load(&shadow->data->ts) <= cache->data->ts) return false; pthread_mutex_lock(&shadow->lock); - cache->ts = atomic_load(&shadow->root->groups[cache->group].ts); - for (int n = 0; n < cache->shadow_config->num_opts; n++) { - struct m_config_option *co = &cache->shadow_config->opts[n]; - if (co->shadow_offset >= 0) - m_option_copy(co->opt, co->data, shadow->data + co->shadow_offset); - } + update_options(cache->data, shadow->data); pthread_mutex_unlock(&shadow->lock); return true; } @@ -1314,35 +1336,36 @@ void m_config_notify_change_co(struct m_config *config, struct m_config_option *co) { struct m_config_shadow *shadow = config->shadow; + assert(co->data); if (shadow) { pthread_mutex_lock(&shadow->lock); - if (co->shadow_offset >= 0) - m_option_copy(co->opt, shadow->data + co->shadow_offset, co->data); - pthread_mutex_unlock(&shadow->lock); - } - int changed = co->opt->flags & UPDATE_OPTS_MASK; + struct m_config_data *data = shadow->data; + struct m_group_data *gdata = m_config_gdata(data, co->group_index); + assert(gdata); - int group = co->group; - while (group >= 0) { - struct m_config_group *g = &config->groups[group]; - atomic_fetch_add(&g->ts, 1); - if (g->group) - changed |= g->group->change_flags; - group = g->parent_group; - } + gdata->ts = atomic_fetch_add(&data->ts, 1) + 1; + + m_option_copy(co->opt, gdata->udata + co->opt->offset, co->data); - if (shadow) { - pthread_mutex_lock(&shadow->lock); for (int n = 0; n < shadow->num_listeners; n++) { struct m_config_cache *cache = shadow->listeners[n]; if (cache->wakeup_cb) cache->wakeup_cb(cache->wakeup_cb_ctx); } + pthread_mutex_unlock(&shadow->lock); } + int changed = co->opt->flags & UPDATE_OPTS_MASK; + int group_index = co->group_index; + while (group_index >= 0) { + struct m_config_group *g = &config->groups[group_index]; + changed |= g->group->change_flags; + group_index = g->parent_group; + } + if (config->option_change_callback) { config->option_change_callback(config->option_change_callback_ctx, co, changed); @@ -1441,11 +1464,14 @@ void mp_read_option_raw(struct mpv_global *global, const char *name, struct m_config_shadow *shadow = global->config; struct m_config_option *co = m_config_get_co_raw(shadow->root, bstr0(name)); assert(co); - assert(co->shadow_offset >= 0); + assert(co->opt->offset >= 0); assert(co->opt->type == type); + struct m_group_data *gdata = m_config_gdata(shadow->data, co->group_index); + assert(gdata); + memset(dst, 0, co->opt->type->size); - m_option_copy(co->opt, dst, shadow->data + co->shadow_offset); + m_option_copy(co->opt, dst, gdata->udata + co->opt->offset); } struct m_config *mp_get_root_config(struct mpv_global *global) diff --git a/options/m_config.h b/options/m_config.h index 1f6f5157a3..2c4531ff86 100644 --- a/options/m_config.h +++ b/options/m_config.h @@ -43,8 +43,7 @@ struct m_config_option { bool is_set_from_config : 1; // Set by a config file bool is_set_locally : 1; // Has a backup entry bool warning_was_printed : 1; - int16_t shadow_offset; // Offset into m_config_shadow.data - int16_t group; // Index into m_config.groups + int16_t group_index; // Index into m_config.groups const char *name; // Full name (ie option-subopt) const struct m_option *opt; // Option description void *data; // Raw value of the option @@ -94,14 +93,17 @@ typedef struct m_config { void *optstruct; // struct mpopts or other - int shadow_size; - - // List of m_sub_options instances. + // Private. List of m_sub_options instances. // Index 0 is the top-level and is always present. + // Immutable after init. + // Invariant: a parent is always at a lower index than any of its children. struct m_config_group *groups; int num_groups; - // Thread-safe shadow memory; only set for the main m_config. + // Private. Non-NULL if data was allocated. m_config_option.data uses it. + struct m_config_data *data; + + // Private. Thread-safe shadow memory; only set for the main m_config. struct m_config_shadow *shadow; } m_config_t; @@ -264,14 +266,13 @@ struct mpv_node m_config_get_profiles(struct m_config *config); // the cache itself is allowed. struct m_config_cache { // The struct as indicated by m_config_cache_alloc's group parameter. + // (Internally the same as data->gdata[0]->udata.) void *opts; // Internal. - struct m_config_shadow *shadow; - struct m_config *shadow_config; - long long ts; - int group; - bool in_list; + struct m_config_shadow *shadow; // real data + struct m_config_data *data; // copy for the cache user + bool in_list; // registered as listener with root config // --- Implicitly synchronized by setting/unsetting wakeup_cb. struct mp_dispatch_queue *wakeup_dispatch_queue; void (*wakeup_dispatch_cb)(void *ctx); @@ -320,6 +321,7 @@ bool m_config_cache_update(struct m_config_cache *cache); // Like m_config_cache_alloc(), but return the struct (m_config_cache->opts) // directly, with no way to update the config. Basically this returns a copy // with a snapshot of the current option values. +// group==NULL is a special case, and always returns the root group. void *mp_get_config_group(void *ta_parent, struct mpv_global *global, const struct m_sub_options *group); -- cgit v1.2.3