diff options
Diffstat (limited to 'options/m_config.c')
-rw-r--r-- | options/m_config.c | 207 |
1 files changed, 203 insertions, 4 deletions
diff --git a/options/m_config.c b/options/m_config.c index 7da23dc8d0..25b7d75c7f 100644 --- a/options/m_config.c +++ b/options/m_config.c @@ -27,6 +27,7 @@ #include <strings.h> #include <assert.h> #include <stdbool.h> +#include <pthread.h> #include "libmpv/client.h" @@ -34,9 +35,11 @@ #include "m_config.h" #include "options/m_option.h" +#include "common/global.h" #include "common/msg.h" #include "common/msg_control.h" #include "misc/node.h" +#include "osdep/atomics.h" static const union m_option_value default_value; @@ -46,6 +49,21 @@ static const union m_option_value default_value; // Maximal include depth. #define MAX_RECURSION_DEPTH 8 +// For use with m_config_cache. +struct m_config_shadow { + pthread_mutex_t lock; + struct m_config *root; + char *data; +}; + +// 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 +}; + struct m_profile { struct m_profile *next; char *name; @@ -183,8 +201,17 @@ static void config_destroy(void *p) { struct m_config *config = p; m_config_restore_backups(config); - for (int n = 0; n < config->num_opts; n++) - m_option_free(config->opts[n].opt, config->opts[n].data); + for (int n = 0; n < config->num_opts; n++) { + struct m_config_option *co = &config->opts[n]; + + m_option_free(co->opt, co->data); + + if (config->shadow && co->shadow_offset >= 0) + m_option_free(co->opt, config->shadow->data + co->shadow_offset); + } + + if (config->shadow) + pthread_mutex_destroy(&config->shadow->lock); } struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, @@ -195,12 +222,21 @@ struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, talloc_set_destructor(config, config_destroy); *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, + }; + if (options) add_options(config, NULL, config->optstruct, defaults, options); return config; @@ -356,6 +392,8 @@ static void m_config_add_option(struct m_config *config, struct m_config_option co = { .opt = arg, .name = arg->name, + .shadow_offset = -1, + .group = parent ? parent->group : 0, }; if (arg->offset >= 0) { @@ -363,6 +401,14 @@ static void m_config_add_option(struct m_config *config, co.data = (char *)optstruct + arg->offset; if (optstruct_def) co.default_data = (char *)optstruct_def + arg->offset; + int size = arg->type->size; + if (optstruct && size) { + // The required alignment is unknown, so go with the minimum C + // could require. Slightly wasteful, but not that much. + int align = (size - config->shadow_size % size) % size; + co.shadow_offset = config->shadow_size + align; + config->shadow_size = co.shadow_offset + size; + } } if (arg->defval) @@ -385,6 +431,10 @@ static void m_config_add_option(struct m_config *config, if (arg->type->flags & M_OPT_TYPE_HAS_CHILD) { const struct m_sub_options *subopts = arg->priv; + // Can't be used multiple times. + for (int n = 0; n < config->num_groups; n++) + assert(config->groups[n].group != subopts); + void *new_optstruct = NULL; if (co.data) { new_optstruct = m_config_alloc_struct(config, subopts); @@ -395,6 +445,16 @@ static void m_config_add_option(struct m_config *config, if (!new_optstruct_def) new_optstruct_def = subopts->defaults; + int parent_group = co.group; + co.group = config->num_groups++; + MP_TARRAY_GROW(config, config->groups, co.group); + struct m_config_group *group = &config->groups[co.group]; + *group = (struct m_config_group){ + .group = subopts, + .parent_group = parent_group, + .opts = new_optstruct, + }; + add_options(config, &co, new_optstruct, new_optstruct_def, subopts->opts); } else { // Initialize options @@ -531,8 +591,7 @@ static void handle_on_set(struct m_config *config, struct m_config_option *co, if (flags & M_SETOPT_FROM_CMDLINE) co->is_set_from_cmdline = true; - if (config->global && (co->opt->flags & M_OPT_TERM)) - mp_msg_update_msglevels(config->global); + m_config_notify_change_co(config, co); } // The type data points to is as in: co->opt @@ -949,6 +1008,146 @@ struct mpv_node m_config_get_profiles(struct m_config *config) return root; } +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->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; +} + +struct m_config_cache *m_config_cache_alloc(void *ta_parent, + struct mpv_global *global, + const struct m_sub_options *group) +{ + struct m_config_shadow *shadow = global->config; + struct m_config *root = shadow->root; + + struct m_config_cache *cache = talloc_zero(ta_parent, struct m_config_cache); + 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; + + 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->ts = -1; + cache->group = -1; + + 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; + } + } + + 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 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) +{ + struct m_config_shadow *shadow = cache->shadow; + + // 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) + 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); + } + pthread_mutex_unlock(&shadow->lock); + return true; +} + +void m_config_notify_change_co(struct m_config *config, + struct m_config_option *co) +{ + struct m_config_shadow *shadow = config->shadow; + + 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 group = co->group; + while (group >= 0) { + atomic_fetch_add(&config->groups[group].ts, 1); + group = config->groups[group].parent_group; + } + } + + if (config->global && (co->opt->flags & M_OPT_TERM)) + mp_msg_update_msglevels(config->global); +} + +struct m_config *mp_get_root_config(struct mpv_global *global) +{ + return global->config->root; +} + void *m_config_alloc_struct(void *talloc_ctx, const struct m_sub_options *subopts) { |