summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/global.h6
-rw-r--r--options/m_config.c207
-rw-r--r--options/m_config.h54
-rw-r--r--player/main.c1
-rw-r--r--player/misc.c1
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,
};