diff options
-rw-r--r-- | common/global.h | 6 | ||||
-rw-r--r-- | options/m_config.c | 207 | ||||
-rw-r--r-- | options/m_config.h | 54 | ||||
-rw-r--r-- | player/main.c | 1 | ||||
-rw-r--r-- | player/misc.c | 1 |
5 files changed, 264 insertions, 5 deletions
diff --git a/common/global.h b/common/global.h index fb70b4ff01..879ca72386 100644 --- a/common/global.h +++ b/common/global.h @@ -5,9 +5,13 @@ // The only purpose of this is to make mpv library-safe. // Think hard before adding new members. struct mpv_global { - struct MPOpts *opts; struct mp_log *log; + struct m_config_shadow *config; struct mp_client_api *client_api; + + // Using this is deprecated and should be avoided (missing synchronization). + // Use m_config_cache to access mpv_global.config instead. + struct MPOpts *opts; }; #endif 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) { diff --git a/options/m_config.h b/options/m_config.h index c8225706ee..101565745f 100644 --- a/options/m_config.h +++ b/options/m_config.h @@ -19,6 +19,7 @@ #define MPLAYER_M_CONFIG_H #include <stddef.h> +#include <stdint.h> #include <stdbool.h> #include "misc/bstr.h" @@ -41,6 +42,8 @@ struct m_config_option { bool is_set_from_cmdline : 1; // Set by user from command line 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 const char *name; // Full name (ie option-subopt) const struct m_option *opt; // Option description void *data; // Raw value of the option @@ -80,6 +83,16 @@ typedef struct m_config { bool subopt_deprecation_warning; void *optstruct; // struct mpopts or other + + int shadow_size; + + // List of m_sub_options instances. + // Index 0 is the top-level and is always present. + struct m_config_group *groups; + int num_groups; + + // Thread-safe shadow memory; only set for the main m_config. + struct m_config_shadow *shadow; } m_config_t; // Create a new config object. @@ -96,6 +109,10 @@ struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, size_t size, const void *defaults, const struct m_option *options); +// Creates "backup" shadow memory for use with m_config_cache. Sets it on +// mpv_global. Expected to be called at early init on the main m_config. +void m_config_create_shadow(struct m_config *config); + // (Warning: new object references config->log and others.) struct m_config *m_config_dup(void *talloc_ctx, struct m_config *config); @@ -187,6 +204,10 @@ const char *m_config_get_positional_option(const struct m_config *config, int n) // Returns: error code (<0), or number of expected params (0, 1) int m_config_option_requires_param(struct m_config *config, bstr name); +// Notify m_config_cache users that the option has (probably) changed its value. +void m_config_notify_change_co(struct m_config *config, + struct m_config_option *co); + // Return all (visible) option names as NULL terminated string list. char **m_config_list_options(void *ta_parent, const struct m_config *config); @@ -252,4 +273,37 @@ void *m_config_alloc_struct(void *talloc_ctx, void *m_sub_options_copy(void *talloc_ctx, const struct m_sub_options *opts, const void *ptr); +// This can be used to create and synchronize per-thread option structs, +// which then can be read without synchronization. No concurrent access to +// the cache itself is allowed. +struct m_config_cache { + // The struct as indicated by m_config_cache_alloc's group parameter. + void *opts; + + // Internal. + struct m_config_shadow *shadow; + struct m_config *shadow_config; + long long ts; + int group; +}; + +// Create a mirror copy from the global options. +// ta_parent: parent for the returned allocation +// global: option data source +// group: the option group to return. This can be NULL for the global option +// struct (MPOpts), or m_sub_options used in a certain OPT_SUBSTRUCT() +// item. +struct m_config_cache *m_config_cache_alloc(void *ta_parent, + struct mpv_global *global, + const struct m_sub_options *group); + +// Update the options in cache->opts to current global values. Return whether +// there was an update notification at all (which may or may not indicate that +// some options have changed). +// Keep in mind that while the cache->opts pointer does not change, the option +// data itself will (e.g. string options might be reallocated). +bool m_config_cache_update(struct m_config_cache *cache); + +struct m_config *mp_get_root_config(struct mpv_global *global); + #endif /* MPLAYER_M_CONFIG_H */ diff --git a/player/main.c b/player/main.c index 6acb617520..b60c522cb0 100644 --- a/player/main.c +++ b/player/main.c @@ -364,6 +364,7 @@ struct MPContext *mp_create(void) mpctx->mconfig->is_toplevel = true; mpctx->mconfig->global = mpctx->global; m_config_parse(mpctx->mconfig, "", bstr0(def_config), NULL, 0); + m_config_create_shadow(mpctx->mconfig); mpctx->global->opts = mpctx->opts; diff --git a/player/misc.c b/player/misc.c index 489eceb6a3..62223ebfef 100644 --- a/player/misc.c +++ b/player/misc.c @@ -261,6 +261,7 @@ struct mpv_global *create_sub_global(struct MPContext *mpctx) struct m_config *new_config = m_config_dup(new, mpctx->mconfig); *new = (struct mpv_global){ .log = mpctx->global->log, + .config = mpctx->global->config, .opts = new_config->optstruct, .client_api = mpctx->clients, }; |