summaryrefslogtreecommitdiffstats
path: root/options/m_config.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2016-09-02 15:45:22 +0200
committerwm4 <wm4@nowhere>2016-09-02 15:50:40 +0200
commit423e53ba0bad034685e5229720d55548afb1efbe (patch)
tree08c8ed2d32f7c7ed88b3d468d09249adec6017ee /options/m_config.c
parentf2e25e9e1f5aa28689d152d7a7cb4c39bdac9c82 (diff)
downloadmpv-423e53ba0bad034685e5229720d55548afb1efbe.tar.bz2
mpv-423e53ba0bad034685e5229720d55548afb1efbe.tar.xz
m_config: introduce basic mechanism to synchronize global option updates
The way option runtime changes are handled is pretty bad in the current codebase. There's a big option struct (MPOpts), which contains almost everything, and for which no synchronization mechanism exists. This was handled by either making some options read-only after initialization, duplicating the option struct, using sub-options (in the VO), and so on. Introduce a mechanism that creates a copy of the global options (or parts of it), and provides a well-defined way to update them in a thread-safe way. Most code can remain the same, just that all the component glue code has to explicitly make use of it first. There is still lots of room for improvement. For example, the update mechanism could be better.
Diffstat (limited to 'options/m_config.c')
-rw-r--r--options/m_config.c207
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)
{