diff options
Diffstat (limited to 'options')
-rw-r--r-- | options/m_config.h | 367 | ||||
-rw-r--r-- | options/m_config_core.c | 894 | ||||
-rw-r--r-- | options/m_config_core.h | 166 | ||||
-rw-r--r-- | options/m_config_frontend.c (renamed from options/m_config.c) | 870 | ||||
-rw-r--r-- | options/m_config_frontend.h | 260 | ||||
-rw-r--r-- | options/m_option.c | 2 | ||||
-rw-r--r-- | options/parse_commandline.c | 2 | ||||
-rw-r--r-- | options/parse_configfile.h | 2 |
8 files changed, 1330 insertions, 1233 deletions
diff --git a/options/m_config.h b/options/m_config.h index c2783feee3..d2ce2b4467 100644 --- a/options/m_config.h +++ b/options/m_config.h @@ -1,366 +1 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef MPLAYER_M_CONFIG_H -#define MPLAYER_M_CONFIG_H - -#include <stddef.h> -#include <stdint.h> -#include <stdbool.h> - -#include "misc/bstr.h" - -// m_config provides an API to manipulate the config variables in MPlayer. -// It makes use of the Options API to provide a context stack that -// allows saving and later restoring the state of all variables. - -typedef struct m_profile m_profile_t; -struct m_option; -struct m_option_type; -struct m_sub_options; -struct m_obj_desc; -struct m_obj_settings; -struct mp_log; -struct mp_dispatch_queue; - -// Config option -struct m_config_option { - bool is_set_from_cmdline : 1; // Set by user from command line - 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 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 -}; - -// Config object -/** \ingroup Config */ -typedef struct m_config { - struct mp_log *log; - struct mpv_global *global; // can be NULL - - // Registered options. - struct m_config_option *opts; // all options, even suboptions - int num_opts; - - // List of defined profiles. - struct m_profile *profiles; - // Depth when recursively including profiles. - int profile_depth; - - struct m_opt_backup *backup_opts; - - bool use_profiles; - bool is_toplevel; - int (*includefunc)(void *ctx, char *filename, int flags); - void *includefunc_ctx; - - // Notification after an option was successfully written to. - // Uses flags as set in UPDATE_OPTS_MASK. - // self_update==true means the update was caused by a call to - // m_config_notify_change_opt_ptr(). If false, it's caused either by - // m_config_set_option_*() (and similar) calls or external updates. - void (*option_change_callback)(void *ctx, struct m_config_option *co, - int flags, bool self_update); - void *option_change_callback_ctx; - - // For the command line parser - int recursion_depth; - - void *optstruct; // struct mpopts or other - - // Private. Non-NULL if data was allocated. m_config_option.data uses it. - // API users call m_config_set_update_dispatch_queue() to get async updates. - struct m_config_cache *cache; - - // Private. Thread-safe shadow memory; only set for the main m_config. - struct m_config_shadow *shadow; -} m_config_t; - -// Create a new config object. -// talloc_ctx: talloc parent context for the m_config allocation -// root: description of all options -// Note that the m_config object will keep pointers to root and log. -struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, - const struct m_sub_options *root); - -// Create a m_config for the given desc. This is for --af/--vf, which have -// different sub-options for every filter (represented by separate desc -// structs). -// args is an array of key/value pairs (args=[k0, v0, k1, v1, ..., NULL]). -// name/defaults is only needed for the legacy af-defaults/vf-defaults options. -struct m_config *m_config_from_obj_desc_and_args(void *ta_parent, - struct mp_log *log, struct mpv_global *global, struct m_obj_desc *desc, - const char *name, struct m_obj_settings *defaults, char **args); - -// Like m_config_from_obj_desc_and_args(), but don't allocate option the -// struct, i.e. m_config.optstruct==NULL. This is used by the sub-option -// parser (--af/--vf, to a lesser degree --ao/--vo) to check sub-option names -// and types. -struct m_config *m_config_from_obj_desc_noalloc(void *talloc_ctx, - struct mp_log *log, - struct m_obj_desc *desc); - -// Allocate a priv struct that is backed by global options (like AOs and VOs, -// anything that uses m_obj_list.use_global_options == true). -// The result contains a snapshot of the current option values of desc->options. -// For convenience, desc->options can be NULL; then priv struct is allocated -// with just zero (or priv_defaults if set). -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); - -// Make sure the option is backed up. If it's already backed up, do nothing. -// All backed up options can be restored with m_config_restore_backups(). -void m_config_backup_opt(struct m_config *config, const char *opt); - -// Call m_config_backup_opt() on all options. -void m_config_backup_all_opts(struct m_config *config); - -// Restore all options backed up with m_config_backup_opt(), and delete the -// backups afterwards. -void m_config_restore_backups(struct m_config *config); - -enum { - M_SETOPT_PRE_PARSE_ONLY = 1, // Silently ignore non-M_OPT_PRE_PARSE opt. - M_SETOPT_CHECK_ONLY = 2, // Don't set, just check name/value - M_SETOPT_FROM_CONFIG_FILE = 4, // Reject M_OPT_NOCFG opt. (print error) - M_SETOPT_FROM_CMDLINE = 8, // Mark as set by command line - M_SETOPT_BACKUP = 16, // Call m_config_backup_opt() before - M_SETOPT_PRESERVE_CMDLINE = 32, // Don't set if already marked as FROM_CMDLINE - M_SETOPT_NO_PRE_PARSE = 128, // Reject M_OPT_PREPARSE options - M_SETOPT_NO_OVERWRITE = 256, // Skip options marked with FROM_* -}; - -// Set the named option to the given string. This is for command line and config -// file use only. -// flags: combination of M_SETOPT_* flags (0 for normal operation) -// Returns >= 0 on success, otherwise see OptionParserReturn. -int m_config_set_option_cli(struct m_config *config, struct bstr name, - struct bstr param, int flags); - -// Similar to m_config_set_option_cli(), but set as data in its native format. -// This takes care of some details like sending change notifications. -// The type data points to is as in: co->opt -int m_config_set_option_raw(struct m_config *config, struct m_config_option *co, - void *data, int flags); - -void m_config_mark_co_flags(struct m_config_option *co, int flags); - -// Convert the mpv_node to raw option data, then call m_config_set_option_raw(). -struct mpv_node; -int m_config_set_option_node(struct m_config *config, bstr name, - struct mpv_node *data, int flags); - -// Return option descriptor. You shouldn't use this. -struct m_config_option *m_config_get_co(const struct m_config *config, - struct bstr name); -// Same as above, but does not resolve aliases or trigger warning messages. -struct m_config_option *m_config_get_co_raw(const struct m_config *config, - struct bstr name); - -// Special uses only. Look away. -int m_config_get_co_count(struct m_config *config); -struct m_config_option *m_config_get_co_index(struct m_config *config, int index); -const void *m_config_get_co_default(const struct m_config *config, - struct m_config_option *co); - -// Return the n-th option by position. n==0 is the first option. If there are -// less than (n + 1) options, return NULL. -const char *m_config_get_positional_option(const struct m_config *config, int n); - -// Return a hint to the option parser whether a parameter is/may be required. -// The option may still accept empty/non-empty parameters independent from -// this, and this function is useful only for handling ambiguous options like -// flags (e.g. "--a" is ok, "--a=yes" is also ok). -// 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. -// This will force a self-notification back to config->option_change_callback. -void m_config_notify_change_opt_ptr(struct m_config *config, void *ptr); - -// Exactly like m_config_notify_change_opt_ptr(), but the option change callback -// (config->option_change_callback()) is invoked with self_update=false, if at all. -void m_config_notify_change_opt_ptr_notify(struct m_config *config, void *ptr); - -// Return all (visible) option names as NULL terminated string list. -char **m_config_list_options(void *ta_parent, const struct m_config *config); - -void m_config_print_option_list(const struct m_config *config, const char *name); - - -/* Find the profile with the given name. - * \param config The config object. - * \param arg The profile's name. - * \return The profile object or NULL. - */ -struct m_profile *m_config_get_profile0(const struct m_config *config, - char *name); -struct m_profile *m_config_get_profile(const struct m_config *config, bstr name); - -// Apply and clear the default profile - it's the only profile that new config -// files do not simply append to (for configfile parser). -void m_config_finish_default_profile(struct m_config *config, int flags); - -/* Get the profile with the given name, creating it if necessary. - * \param config The config object. - * \param arg The profile's name. - * \return The profile object. - */ -struct m_profile *m_config_add_profile(struct m_config *config, char *name); - -/* Set the description of a profile. - * Used by the config file parser when defining a profile. - * - * \param p The profile object. - * \param arg The profile's name. - */ -void m_profile_set_desc(struct m_profile *p, bstr desc); - -/* Add an option to a profile. - * Used by the config file parser when defining a profile. - * - * \param config The config object. - * \param p The profile object. - * \param name The option's name. - * \param val The option's value. - */ -int m_config_set_profile_option(struct m_config *config, struct m_profile *p, - bstr name, bstr val); - -/* Enables profile usage - * Used by the config file parser when loading a profile. - * - * \param config The config object. - * \param p The profile object. - * \param flags M_SETOPT_* bits - * Returns error code (<0) or 0 on success - */ -int m_config_set_profile(struct m_config *config, char *name, int flags); - -struct mpv_node m_config_get_profiles(struct m_config *config); - -// Run async option updates here. This will call option_change_callback() on it. -void m_config_set_update_dispatch_queue(struct m_config *config, - struct mp_dispatch_queue *dispatch); - -// 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. - // (Internally the same as internal->gdata[0]->udata.) - void *opts; - // Accumulated change flags. The user can set this to 0 to unset all flags. - // They are set when calling any of the update functions. A flag is only set - // once the new value is visible in ->opts. - uint64_t change_flags; - - // Set to non-NULL for logging all option changes as they are retrieved - // with one of the update functions (like m_config_cache_update()). - struct mp_log *debug; - - // Do not access. - struct config_cache *internal; -}; - -// Create a mirror copy from the global options. -// Keep in mind that a m_config_cache object is not thread-safe; it merely -// provides thread-safe access to the global options. All API functions for -// the same m_config_cache object must synchronized, unless otherwise noted. -// This does not create an initial change event (m_config_cache_update() will -// return false), but note that a change might be asynchronously signaled at any -// time. -// ta_parent: parent for the returned allocation -// global: option data source -// group: the option group to return -struct m_config_cache *m_config_cache_alloc(void *ta_parent, - struct mpv_global *global, - const struct m_sub_options *group); - -// If any of the options in the group possibly changes, call this callback. The -// callback must not actually access the cache or anything option related. -// Instead, it must wake up the thread that normally accesses the cache. -void m_config_cache_set_wakeup_cb(struct m_config_cache *cache, - void (*cb)(void *ctx), void *cb_ctx); - -// If any of the options in the group change, call this callback on the given -// dispatch queue. This is higher level than m_config_cache_set_wakeup_cb(), -// and you can do anything you want in the callback (assuming the dispatch -// queue is processed in the same thread that accesses m_config_cache API). -// To ensure clean shutdown, you must destroy the m_config_cache (or unset the -// callback) before the dispatch queue is destroyed. -void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache, - struct mp_dispatch_queue *dispatch, - void (*cb)(void *ctx), void *cb_ctx); - -// 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). -// New change flags are or-ed into cache->change_flags with this call (if you -// use them, you should probably do cache->change_flags=0 before this call). -bool m_config_cache_update(struct m_config_cache *cache); - -// Check for changes and return fine grained change information. -// Warning: this conflicts with m_config_cache_update(). If you call -// m_config_cache_update(), all options will be marked as "not changed", -// and this function will return false. Also, calling this function and -// then m_config_cache_update() is not supported, and may skip updating -// some fields. -// This returns true as long as there is a changed option, and false if all -// changed options have been returned. -// If multiple options have changed, the new option value is visible only once -// this function has returned the change for it. -// out_ptr: pointer to a void*, which is set to the cache->opts field associated -// with the changed option if the function returns true; set to NULL -// if no option changed. -// returns: *out_ptr!=NULL (true if there was a changed option) -bool m_config_cache_get_next_changed(struct m_config_cache *cache, void **out_ptr); - -// Copy the option field pointed to by ptr to the global option storage. This -// is sort of similar to m_config_set_option_raw(), except doesn't require -// access to the main thread. (And you can't pass any flags.) -// You write the new value to the option struct, and then call this function -// with the pointer to it. You will not get a change notification for it (though -// you might still get a redundant wakeup callback). -// Changing the option struct and not calling this function before any update -// function (like m_config_cache_update()) will leave the value inconsistent, -// and will possibly (but not necessarily) overwrite it with the next update -// call. -// ptr: points to any field in cache->opts that is managed by an option. If -// this is not the case, the function crashes for your own good. -// returns: if true, this was an update; if false, shadow had same value -bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr); - -// 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. -void *mp_get_config_group(void *ta_parent, struct mpv_global *global, - const struct m_sub_options *group); - -// Read a single global option in a thread-safe way. For multiple options, -// use m_config_cache. The option must exist and match the provided type (the -// type is used as a sanity check only). Performs semi-expensive lookup. -// Warning: new code must not use this. -void mp_read_option_raw(struct mpv_global *global, const char *name, - const struct m_option_type *type, void *dst); - -#endif /* MPLAYER_M_CONFIG_H */ +#include "m_config_core.h"
\ No newline at end of file diff --git a/options/m_config_core.c b/options/m_config_core.c new file mode 100644 index 0000000000..0b7d40ea6e --- /dev/null +++ b/options/m_config_core.c @@ -0,0 +1,894 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <strings.h> +#include <assert.h> +#include <stdbool.h> +#include <pthread.h> + +#include "m_config_core.h" +#include "options/m_option.h" +#include "common/common.h" +#include "common/global.h" +#include "common/msg.h" +#include "common/msg_control.h" +#include "misc/dispatch.h" +#include "osdep/atomic.h" + +// Maximum possibly option name length (as it appears to the user). +#define MAX_OPT_NAME_LEN 80 + +// For use with m_config_cache. +struct m_config_shadow { + pthread_mutex_t lock; + // Incremented on every option change. + mp_atomic_uint64 ts; + // -- immutable after init + // 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; + // -- protected by lock + struct m_config_data *data; // protected shadow copy of the option data + struct config_cache **listeners; + int num_listeners; +}; + +// Represents a sub-struct (OPT_SUBSTRUCT()). +struct m_config_group { + const struct m_sub_options *group; + int group_count; // 1 + number of all sub groups owned by this (so + // m_config_shadow.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_shadow.groups[], + // or -1 for group 0 + int parent_ptr; // ptr offset in the parent group's data, or -1 if + // none + const char *prefix; // concat_name(_, prefix, opt->name) => full name + // (the parent names are already included in this) +}; + +// 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_shadow *shadow; // option definitions etc., main data copy + 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) +}; + +struct config_cache { + struct m_config_cache *public; + + struct m_config_data *data; // public data + struct m_config_data *src; // global data (currently ==shadow->data) + struct m_config_shadow *shadow; // global metadata + uint64_t ts; // timestamp of this data copy + bool in_list; // part of m_config_shadow->listeners[] + int upd_group; // for "incremental" change notification + int upd_opt; + + + // --- Implicitly synchronized by setting/unsetting wakeup_cb. + struct mp_dispatch_queue *wakeup_dispatch_queue; + void (*wakeup_dispatch_cb)(void *ctx); + void *wakeup_dispatch_cb_ctx; + + // --- Protected by shadow->lock + void (*wakeup_cb)(void *ctx); + void *wakeup_cb_ctx; +}; + +// Per m_config_data state for each m_config_group. +struct m_group_data { + char *udata; // pointer to group user option struct + uint64_t ts; // timestamp of the data copy +}; + + +static void add_sub_group(struct m_config_shadow *shadow, 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]; +} + +// Like concat_name(), but returns either a, b, or buf. buf/buf_size is used as +// target for snprintf(). (buf_size is recommended to be MAX_OPT_NAME_LEN.) +static const char *concat_name_buf(char *buf, size_t buf_size, + const char *a, const char *b) +{ + assert(a); + assert(b); + if (!a[0]) + return b; + if (!b[0]) + return a; + snprintf(buf, buf_size, "%s-%s", a, b); + return buf; +} + +// Return full option name from prefix (a) and option name (b). Returns either +// a, b, or a talloc'ed string under ta_parent. +static const char *concat_name(void *ta_parent, const char *a, const char *b) +{ + char buf[MAX_OPT_NAME_LEN]; + const char *r = concat_name_buf(buf, sizeof(buf), a, b); + return r == buf ? talloc_strdup(ta_parent, r) : r; +} + +struct opt_iterate_state { + // User can read these fields. + int group_index; + int opt_index; + const struct m_option *opt; + const char *full_name; // may point to name_buf + + // Internal. + int group_index_end; + char name_buf[MAX_OPT_NAME_LEN]; + struct m_config_shadow *shadow; +}; + +// Start iterating all options and sub-options in the given group. +static void opt_iterate_init(struct opt_iterate_state *iter, + struct m_config_shadow *shadow, int group_index) +{ + assert(group_index >= 0 && group_index < shadow->num_groups); + iter->group_index = group_index; + iter->group_index_end = group_index + shadow->groups[group_index].group_count; + iter->opt_index = -1; + iter->shadow = shadow; +} + +// Get the first or next option. Returns false if end reached. If this returns +// true, most fields in *iter are valid. +// This does not return pseudo-option entries (like m_option_type_subconfig). +static bool opt_iterate_next(struct opt_iterate_state *iter) +{ + if (iter->group_index < 0) + return false; + + + while (1) { + if (iter->group_index >= iter->group_index_end) { + iter->group_index = -1; + return false; + } + + iter->opt_index += 1; + + struct m_config_group *g = &iter->shadow->groups[iter->group_index]; + const struct m_option *opts = g->group->opts; + + if (!opts || !opts[iter->opt_index].name) { + iter->group_index += 1; + iter->opt_index = -1; + continue; + } + + iter->opt = &opts[iter->opt_index]; + + if (iter->opt->type == &m_option_type_subconfig) + continue; + + iter->full_name = concat_name_buf(iter->name_buf, sizeof(iter->name_buf), + g->prefix, iter->opt->name); + return true; + } + assert(0); +} + +// The memcpys are supposed to work around the strict aliasing violation, +// that would result if we just dereferenced a void** (where the void** is +// actually casted from struct some_type* ). The dummy struct type is in +// theory needed, because void* and struct pointers could have different +// representations, while pointers to different struct types don't. +static void *substruct_read_ptr(const void *ptr) +{ + struct mp_dummy_ *res; + memcpy(&res, ptr, sizeof(res)); + return res; +} +static void substruct_write_ptr(void *ptr, void *val) +{ + struct mp_dummy_ *src = val; + memcpy(ptr, &src, sizeof(src)); +} + +// Initialize a field with a given value. In case this is dynamic data, it has +// to be allocated and copied. src can alias dst. +static void init_opt_inplace(const struct m_option *opt, void *dst, + const void *src) +{ + // The option will use dynamic memory allocation iff it has a free callback. + if (opt->type->free) { + union m_option_value temp; + memcpy(&temp, src, opt->type->size); + memset(dst, 0, opt->type->size); + m_option_copy(opt, dst, &temp); + } else if (src != dst) { + memcpy(dst, src, opt->type->size); + } +} + +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->shadow->num_groups); + struct m_config_group *group = &data->shadow->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 = 0; opts->opts && opts->opts[n].name; n++) { + const struct m_option *opt = &opts->opts[n]; + + if (opt->offset < 0 || opt->type->size == 0) + continue; + + void *dst = gdata->udata + opt->offset; + const void *defptr = opt->defval ? opt->defval : dst; + if (copy_src) + defptr = copy_src + opt->offset; + + init_opt_inplace(opt, dst, 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->shadow->groups[data->group_index + i]; + const struct m_option *opts = group->group->opts; + + for (int n = 0; opts && opts[n].name; n++) { + const struct m_option *opt = &opts[n]; + + if (opt->offset >= 0 && opt->type->size > 0) + m_option_free(opt, gdata->udata + opt->offset); + } + } +} + +// Allocate data using the option description in shadow, 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_shadow *shadow, + int group_index, + struct m_config_data *copy) +{ + assert(group_index >= 0 && group_index < shadow->num_groups); + struct m_config_data *data = talloc_zero(ta_parent, struct m_config_data); + talloc_set_destructor(data, free_option_data); + + data->shadow = shadow; + data->group_index = group_index; + + struct m_config_group *root_group = &shadow->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); + + return data; +} + +static void shadow_destroy(void *p) +{ + struct m_config_shadow *shadow = p; + + // must all have been unregistered + assert(shadow->num_listeners == 0); + + talloc_free(shadow->data); + pthread_mutex_destroy(&shadow->lock); +} + +struct m_config_shadow *m_config_shadow_new(const struct m_sub_options *root) +{ + struct m_config_shadow *shadow = talloc_zero(NULL, struct m_config_shadow); + talloc_set_destructor(shadow, shadow_destroy); + pthread_mutex_init(&shadow->lock, NULL); + + add_sub_group(shadow, NULL, -1, -1, root); + + if (!root->size) + return shadow; + + shadow->data = allocate_option_data(shadow, shadow, 0, NULL); + + return shadow; +} + +#include "m_config_frontend.h" + +static void config_destroy(void *p) +{ + struct m_config *config = p; + config->option_change_callback = NULL; + m_config_restore_backups(config); + + talloc_free(config->cache); + talloc_free(config->shadow); +} + +struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, + const struct m_sub_options *root) +{ + struct m_config *config = talloc(talloc_ctx, struct m_config); + talloc_set_destructor(config, config_destroy); + *config = (struct m_config){.log = log,}; + + config->shadow = m_config_shadow_new(root); + + if (root->size) { + config->cache = m_config_cache_from_shadow(config, config->shadow, root); + config->optstruct = config->cache->opts; + } + + struct opt_iterate_state it; + opt_iterate_init(&it, config->shadow, 0); + while (opt_iterate_next(&it)) { + struct m_config_option co = { + .name = talloc_strdup(config, it.full_name), + .opt = it.opt, + .group_index = it.group_index, + }; + + struct m_group_data *gdata = config->cache + ? m_config_gdata(config->cache->internal->data, it.group_index) + : NULL; + + if (gdata && co.opt->offset >= 0) + co.data = gdata->udata + co.opt->offset; + + MP_TARRAY_APPEND(config, config->opts, config->num_opts, co); + } + + return config; +} + +static void init_obj_settings_list(struct m_config_shadow *shadow, + int parent_group_index, + const struct m_obj_list *list) +{ + struct m_obj_desc desc; + for (int n = 0; ; n++) { + if (!list->get_desc(&desc, n)) + break; + if (desc.global_opts) { + add_sub_group(shadow, NULL, parent_group_index, -1, + desc.global_opts); + } + if (list->use_global_options && desc.options) { + struct m_sub_options *conf = talloc_ptrtype(shadow, conf); + *conf = (struct m_sub_options){ + .prefix = desc.options_prefix, + .opts = desc.options, + .defaults = desc.priv_defaults, + .size = desc.priv_size, + }; + add_sub_group(shadow, NULL, parent_group_index, -1, conf); + } + } +} + +static void add_sub_group(struct m_config_shadow *shadow, 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 < shadow->num_groups; n++) + assert(shadow->groups[n].group != subopts); + + if (!name_prefix) + name_prefix = ""; + if (subopts->prefix && subopts->prefix[0]) { + assert(!name_prefix[0]); + name_prefix = subopts->prefix; + } + + // You can only use UPDATE_ flags here. + assert(!(subopts->change_flags & ~(unsigned)UPDATE_OPTS_MASK)); + + assert(parent_group_index >= -1 && parent_group_index < shadow->num_groups); + + int group_index = shadow->num_groups++; + MP_TARRAY_GROW(shadow, shadow->groups, group_index); + shadow->groups[group_index] = (struct m_config_group){ + .group = subopts, + .parent_group = parent_group_index, + .parent_ptr = parent_ptr, + .prefix = name_prefix, + }; + + 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 && subopts->defaults) { + void *ptr = (char *)subopts->defaults + opt->offset; + assert(!substruct_read_ptr(ptr)); + } + + const char *prefix = concat_name(shadow, name_prefix, opt->name); + add_sub_group(shadow, 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(shadow, group_index, objlist); + } + } + |