mirror of
https://github.com/mpv-player/mpv
synced 2025-02-18 05:37:04 +00:00
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.
This commit is contained in:
parent
707b404820
commit
4cb264a3ff
@ -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 alloc_group(struct m_config_data *data, int group_index,
|
||||
struct m_config_data *copy)
|
||||
{
|
||||
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++];
|
||||
|
||||
struct m_group_data *copy_gdata =
|
||||
copy ? m_config_gdata(copy, group_index) : NULL;
|
||||
|
||||
*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);
|
||||
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) {
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
static void init_obj_settings_list(struct m_config *config,
|
||||
int parent_group_index,
|
||||
const struct m_obj_list *list)
|
||||
{
|
||||
for (int i = 0; defs && defs[i].name; i++)
|
||||
m_config_add_option(config, parent, optstruct, optstruct_def, &defs[i]);
|
||||
struct m_obj_desc desc;
|
||||
for (int n = 0; ; n++) {
|
||||
if (!list->get_desc(&desc, n))
|
||||
break;
|
||||
if (desc.global_opts) {
|
||||
add_sub_group(config, NULL, parent_group_index, -1,
|
||||
desc.global_opts);
|
||||
}
|
||||
if (list->use_global_options && desc.options) {
|
||||
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_group(config, NULL, parent_group_index, -1, conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_sub_options(struct m_config *config,
|
||||
struct m_config_option *parent,
|
||||
const struct m_sub_options *subopts)
|
||||
static const char *concat_name(void *ta_parent, const char *a, const char *b)
|
||||
{
|
||||
// Can't be used multiple times.
|
||||
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 add_sub_group(struct m_config *config, const char *name_prefix,
|
||||
int parent_group_index, int parent_ptr,
|
||||
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);
|
||||
assert(parent_group_index >= -1 && parent_group_index < config->num_groups);
|
||||
|
||||
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){
|
||||
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 ? parent->group : 0,
|
||||
.opts = new_optstruct,
|
||||
.parent_group = parent_group_index,
|
||||
.parent_ptr = parent_ptr,
|
||||
.co_index = config->num_opts,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
const void *optstruct_def = subopts->defaults;
|
||||
|
||||
#define MAX_VO_AO 16
|
||||
if (subopts->prefix && subopts->prefix[0])
|
||||
name_prefix = subopts->prefix;
|
||||
if (!name_prefix)
|
||||
name_prefix = "";
|
||||
|
||||
struct group_entry {
|
||||
const struct m_obj_list *entry;
|
||||
struct m_sub_options subs[MAX_VO_AO];
|
||||
bool initialized;
|
||||
};
|
||||
for (int i = 0; subopts->opts && subopts->opts[i].name; i++) {
|
||||
const struct m_option *opt = &subopts->opts[i];
|
||||
|
||||
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;
|
||||
if (opt->type == &m_option_type_subconfig)
|
||||
continue;
|
||||
|
||||
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 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,
|
||||
};
|
||||
|
||||
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 (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 (!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;
|
||||
config->groups[group_index].co_end_index = config->num_opts;
|
||||
|
||||
// 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];
|
||||
|
||||
if (opt->type == &m_option_type_subconfig) {
|
||||
const struct m_sub_options *new_subopts = opt->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);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_group_mutex);
|
||||
|
||||
return n >= 0 ? &group->subs[n] : NULL;
|
||||
}
|
||||
|
||||
static void init_obj_settings_list(struct m_config *config,
|
||||
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);
|
||||
break;
|
||||
}
|
||||
if (desc.global_opts)
|
||||
add_sub_options(config, NULL, desc.global_opts);
|
||||
if (list->use_global_options && desc.options) {
|
||||
struct m_sub_options conf = {
|
||||
.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 m_config_add_option(struct m_config *config,
|
||||
struct m_config_option *parent,
|
||||
void *optstruct,
|
||||
const void *optstruct_def,
|
||||
const struct m_option *arg)
|
||||
{
|
||||
assert(config != NULL);
|
||||
assert(arg != NULL);
|
||||
|
||||
const char *parent_name = parent ? parent->name : "";
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
if (arg->offset >= 0) {
|
||||
if (optstruct)
|
||||
co.data = (char *)optstruct + arg->offset;
|
||||
if (optstruct_def)
|
||||
co.default_data = (char *)optstruct_def + arg->offset;
|
||||
}
|
||||
|
||||
if (arg->defval)
|
||||
co.default_data = arg->defval;
|
||||
|
||||
// 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 (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;
|
||||
}
|
||||
|
||||
// Initialize options
|
||||
if (co.data && co.default_data)
|
||||
init_opt_inplace(arg, co.data, co.default_data);
|
||||
|
||||
MP_TARRAY_APPEND(config, config->opts, config->num_opts, co);
|
||||
|
||||
if (arg->type == &m_option_type_obj_settings_list)
|
||||
init_obj_settings_list(config, (const struct m_obj_list *)arg->priv);
|
||||
}
|
||||
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,76 +1261,73 @@ 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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
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(cache->group >= 0);
|
||||
assert(cache->opts);
|
||||
assert(group_index >= 0); // invalid group (or not in option tree)
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
struct m_config_cache *cache = talloc_zero(ta_parent, struct m_config_cache);
|
||||
talloc_set_destructor(cache, cache_destroy);
|
||||
cache->shadow = shadow;
|
||||
|
||||
m_config_cache_update(cache);
|
||||
pthread_mutex_lock(&shadow->lock);
|
||||
cache->data = allocate_option_data(cache, root, group_index, shadow->data);
|
||||
pthread_mutex_unlock(&shadow->lock);
|
||||
|
||||
cache->opts = cache->data->gdata[0].udata;
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user