summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-03-12 23:07:05 +0100
committerwm4 <wm4@nowhere>2020-03-13 16:50:27 +0100
commiteb381cbd4b38dd496ee0be609f1a66c360a76448 (patch)
treefc812f68a26dc873f6f512ff6e592547bbcfd205
parentd3ad4e23088da95697ab2ec385267c06293c4515 (diff)
downloadmpv-eb381cbd4b38dd496ee0be609f1a66c360a76448.tar.bz2
mpv-eb381cbd4b38dd496ee0be609f1a66c360a76448.tar.xz
options: split m_config.c/h
Move the "old" mostly command line parsing and option management related code to m_config_frontend.c/h. Move the the code that enables other part of the player to access options to m_config_core.c/h. "frontend" is out of lack of creativity for a better name. Unfortunately, the separation isn't quite clean yet. m_config_frontend.c still references some m_config_core.c implementation details, and m_config_new() is even left in m_config_core.c for now. There some odd functions that should be removed as well (marked as "Bad functions"). Fixing these things requires more changes and will be done separately. struct m_config is left with the current name to reduce diff noise. Also, since there are a _lot_ source files that include m_config.h, add a replacement m_config.h that "redirects" to m_config_core.h.
-rw-r--r--audio/out/ao.c2
-rw-r--r--filters/user_filters.c2
-rw-r--r--options/m_config.h367
-rw-r--r--options/m_config_core.c894
-rw-r--r--options/m_config_core.h166
-rw-r--r--options/m_config_frontend.c (renamed from options/m_config.c)870
-rw-r--r--options/m_config_frontend.h260
-rw-r--r--options/m_option.c2
-rw-r--r--options/parse_commandline.c2
-rw-r--r--options/parse_configfile.h2
-rw-r--r--player/command.c2
-rw-r--r--player/playloop.c2
-rw-r--r--wscript_build.py3
13 files changed, 1336 insertions, 1238 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 71c17e03b0..b75323051a 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -29,7 +29,7 @@
#include "audio/format.h"
#include "options/options.h"
-#include "options/m_config.h"
+#include "options/m_config_frontend.h"
#include "osdep/endian.h"
#include "common/msg.h"
#include "common/common.h"
diff --git a/filters/user_filters.c b/filters/user_filters.c
index e9ccec507d..72a2ab892c 100644
--- a/filters/user_filters.c
+++ b/filters/user_filters.c
@@ -4,7 +4,7 @@
#include "common/common.h"
#include "common/msg.h"
-#include "options/m_config.h"
+#include "options/m_config_frontend.h"
#include "f_lavfi.h"
#include "user_filters.h"
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;
+
+