summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2019-11-29 12:49:15 +0100
committerwm4 <wm4@nowhere>2019-11-29 12:49:15 +0100
commitb16cea750f527088be79772e7cd601f86ce62ef2 (patch)
tree7bca2775a35f501baacafdbab63060d95ec99469
parent5e2658c98196f8fd3558a2ffe40bd789ed27e8a3 (diff)
downloadmpv-b16cea750f527088be79772e7cd601f86ce62ef2.tar.bz2
mpv-b16cea750f527088be79772e7cd601f86ce62ef2.tar.xz
player: change m_config to use new option handling mechanisms
Instead of making m_config a special-case, it more or less uses the underlying m_config_cache/m_config_shadow APIs properly. This makes the player core a (relatively) equivalent user of the core option API. In particular, this means that other threads can change core options with m_config_cache_write_opt() calls (before this commit, this merely led to diverging option values). An important change is that before this commit, mpctx->opts contained the "master copy" of all option data. Now it's just another copy of the option data, and the shadow copy is considered the master. This is why whenever mpctx->opts is written, the change needs to be copied to the master (thus why this commits add a bunch of m_config_notify... calls). If another thread (e.g. a VO) changes an option, async_change_cb is now invoked, which funnels the change notification through the player's layers. The new self_notification parameter on mp_option_change_callback is so that m_config_notify... doesn't trigger recursion, and it's used in cases where the change was already "processed". It's still needed to trigger libmpv property updates. (I considered using an extra m_config_cache for that, but it'd only cause problems with no advantages.) I think the recent changes actually forgot to send libmpv property updates in some cases. This should fix this anyway. In some cases, property updates are reworked, and the potential for bugs should be lower (probably). The primary point of this change is to allow external updates, for example by a VO writing the fullscreen option if the window state is changed by the window manager (rather than mpv changing it). This is not used yet, but the following commits will.
-rw-r--r--options/m_config.c144
-rw-r--r--options/m_config.h18
-rw-r--r--player/command.c22
-rw-r--r--player/command.h3
-rw-r--r--player/loadfile.c21
-rw-r--r--player/main.c4
-rw-r--r--player/playloop.c16
7 files changed, 142 insertions, 86 deletions
diff --git a/options/m_config.c b/options/m_config.c
index 52a9b2f6d8..a28ae4438b 100644
--- a/options/m_config.c
+++ b/options/m_config.c
@@ -142,6 +142,9 @@ struct m_opt_backup {
void *backup;
};
+static struct m_config_cache *m_config_cache_alloc_internal(void *ta_parent,
+ struct m_config_shadow *shadow,
+ const struct m_sub_options *group);
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);
@@ -446,7 +449,7 @@ static void config_destroy(void *p)
struct m_config *config = p;
m_config_restore_backups(config);
- talloc_free(config->data);
+ talloc_free(config->cache);
talloc_free(config->shadow);
}
@@ -460,9 +463,9 @@ struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
config->shadow = m_config_shadow_new(root);
if (root->size) {
- config->data = allocate_option_data(config, config->shadow, 0,
- config->shadow->data);
- config->optstruct = config->data->gdata[0].udata;
+ config->cache =
+ m_config_cache_alloc_internal(config, config->shadow, root);
+ config->optstruct = config->cache->opts;
}
struct opt_iterate_state it;
@@ -475,8 +478,9 @@ struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
.is_hidden = !!it.opt->deprecation_message,
};
- struct m_group_data *gdata =
- config->data ? m_config_gdata(config->data, it.group_index) : NULL;
+ 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;
@@ -939,6 +943,67 @@ static int m_config_handle_special_options(struct m_config *config,
return M_OPT_UNKNOWN;
}
+// This notification happens when anyone other than m_config->cache (i.e. not
+// through m_config_set_option_raw() or related) changes any options.
+static void async_change_cb(void *p)
+{
+ struct m_config *config = p;
+
+ void *ptr;
+ while (m_config_cache_get_next_changed(config->cache, &ptr)) {
+ // Regrettable linear search, might degenerate to quadratic.
+ for (int n = 0; n < config->num_opts; n++) {
+ struct m_config_option *co = &config->opts[n];
+ if (co->data == ptr) {
+ if (config->option_change_callback) {
+ config->option_change_callback(
+ config->option_change_callback_ctx, co,
+ config->cache->change_flags, false);
+ }
+ break;
+ }
+ }
+ config->cache->change_flags = 0;
+ }
+}
+
+void m_config_set_update_dispatch_queue(struct m_config *config,
+ struct mp_dispatch_queue *dispatch)
+{
+ m_config_cache_set_dispatch_change_cb(config->cache, dispatch,
+ async_change_cb, config);
+}
+
+// Normally m_config_cache will not send notifications when _we_ change our
+// own stuff. For whatever funny reasons, we need that, though.
+static void force_self_notify_change_opt(struct m_config *config,
+ struct m_config_option *co,
+ bool self_notification)
+{
+ int changed =
+ get_option_change_mask(config->shadow, co->group_index, 0, co->opt);
+
+ if (config->option_change_callback) {
+ config->option_change_callback(config->option_change_callback_ctx, co,
+ changed, self_notification);
+ }
+}
+
+void m_config_notify_change_opt_ptr(struct m_config *config, void *ptr)
+{
+ for (int n = 0; n < config->num_opts; n++) {
+ struct m_config_option *co = &config->opts[n];
+ if (co->data == ptr) {
+ if (m_config_cache_write_opt(config->cache, co->data))
+ force_self_notify_change_opt(config, co, true);
+ return;
+ }
+ }
+ // ptr doesn't point to any config->optstruct field declared in the
+ // option list?
+ assert(false);
+}
+
int m_config_set_option_raw(struct m_config *config,
struct m_config_option *co,
void *data, int flags)
@@ -959,10 +1024,11 @@ int m_config_set_option_raw(struct m_config *config,
if (!co->data)
return flags & M_SETOPT_FROM_CMDLINE ? 0 : M_OPT_UNKNOWN;
- m_option_copy(co->opt, co->data, data);
-
m_config_mark_co_flags(co, flags);
- m_config_notify_change_co(config, co);
+
+ m_option_copy(co->opt, co->data, data);
+ if (m_config_cache_write_opt(config->cache, co->data))
+ force_self_notify_change_opt(config, co, false);
return 0;
}
@@ -1356,11 +1422,10 @@ static void cache_destroy(void *p)
m_config_cache_set_dispatch_change_cb(cache, NULL, NULL, NULL);
}
-struct m_config_cache *m_config_cache_alloc(void *ta_parent,
- struct mpv_global *global,
+static struct m_config_cache *m_config_cache_alloc_internal(void *ta_parent,
+ struct m_config_shadow *shadow,
const struct m_sub_options *group)
{
- struct m_config_shadow *shadow = global->config;
int group_index = -1;
for (int n = 0; n < shadow->num_groups; n++) {
@@ -1397,6 +1462,13 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent,
return cache;
}
+struct m_config_cache *m_config_cache_alloc(void *ta_parent,
+ struct mpv_global *global,
+ const struct m_sub_options *group)
+{
+ return m_config_cache_alloc_internal(ta_parent, global->config, group);
+}
+
static void update_next_option(struct m_config_cache *cache, void **p_opt)
{
struct config_cache *in = cache->internal;
@@ -1577,54 +1649,6 @@ bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr)
return changed;
}
-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);
-
- struct m_config_data *data = shadow->data;
- struct m_group_data *gdata = m_config_gdata(data, co->group_index);
- assert(gdata);
-
- gdata->ts = atomic_fetch_add(&shadow->ts, 1) + 1;
-
- m_option_copy(co->opt, gdata->udata + co->opt->offset, co->data);
-
- for (int n = 0; n < shadow->num_listeners; n++) {
- struct config_cache *cache = shadow->listeners[n];
- if (cache->wakeup_cb && m_config_gdata(cache->data, co->group_index))
- cache->wakeup_cb(cache->wakeup_cb_ctx);
- }
-
- pthread_mutex_unlock(&shadow->lock);
- }
-
- int changed = get_option_change_mask(shadow, co->group_index, 0, co->opt);
-
- if (config->option_change_callback) {
- config->option_change_callback(config->option_change_callback_ctx, co,
- changed);
- }
-}
-
-void m_config_notify_change_opt_ptr(struct m_config *config, void *ptr)
-{
- for (int n = 0; n < config->num_opts; n++) {
- struct m_config_option *co = &config->opts[n];
- if (co->data == ptr) {
- m_config_notify_change_co(config, co);
- return;
- }
- }
- // ptr doesn't point to any config->optstruct field declared in the
- // option list?
- assert(false);
-}
-
void m_config_cache_set_wakeup_cb(struct m_config_cache *cache,
void (*cb)(void *ctx), void *cb_ctx)
{
diff --git a/options/m_config.h b/options/m_config.h
index f62bc9670f..25d26e05ee 100644
--- a/options/m_config.h
+++ b/options/m_config.h
@@ -74,8 +74,11 @@ typedef struct m_config {
// 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);
+ int flags, bool self_update);
void *option_change_callback_ctx;
// For the command line parser
@@ -84,7 +87,8 @@ typedef struct m_config {
void *optstruct; // struct mpopts or other
// Private. Non-NULL if data was allocated. m_config_option.data uses it.
- struct m_config_data *data;
+ // 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;
@@ -189,11 +193,7 @@ const char *m_config_get_positional_option(const struct m_config *config, int n)
int m_config_option_requires_param(struct m_config *config, bstr name);
// Notify m_config_cache users that the option has (probably) changed its value.
-void m_config_notify_change_co(struct m_config *config,
- struct m_config_option *co);
-// Like m_config_notify_change_co(), but automatically find the option by its
-// pointer within the global option struct (config->optstruct). In practice,
-// it means it works only on fields in MPContext.opts.
+// 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);
// Return all (visible) option names as NULL terminated string list.
@@ -253,6 +253,10 @@ 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.
diff --git a/player/command.c b/player/command.c
index fea26dbdcf..10359e2352 100644
--- a/player/command.c
+++ b/player/command.c
@@ -1889,6 +1889,8 @@ static int property_switch_track(void *ctx, struct m_property *prop,
// not always do what the user means, but keep the complexity low.
mpctx->opts->stream_id[order][type] =
mpctx->opts->stream_id[order][type] == -1 ? -2 : -1;
+ m_config_notify_change_opt_ptr(mpctx->mconfig,
+ &mpctx->opts->stream_id[order][type]);
}
return M_PROPERTY_OK;
}
@@ -3485,11 +3487,7 @@ static const char *const *const mp_event_property_change[] = {
E(MPV_EVENT_FILE_LOADED, "*"),
E(MP_EVENT_CHANGE_ALL, "*"),
E(MPV_EVENT_TRACKS_CHANGED, "track-list"),
- E(MPV_EVENT_TRACK_SWITCHED, "vid", "video", "aid", "audio", "sid", "sub",
- "secondary-sid"),
E(MPV_EVENT_IDLE, "*"),
- E(MPV_EVENT_PAUSE, "pause"),
- E(MPV_EVENT_UNPAUSE, "pause"),
E(MPV_EVENT_TICK, "time-pos", "audio-pts", "stream-pos", "avsync",
"percent-pos", "time-remaining", "playtime-remaining", "playback-time",
"estimated-vf-fps", "drop-frame-count", "vo-drop-frame-count",
@@ -4952,6 +4950,8 @@ static void cmd_track_add(void *p)
print_track_list(mpctx, "Track switched:");
} else {
mpctx->opts->stream_id[0][t->type] = t->user_tid;
+ m_config_notify_change_opt_ptr(mpctx->mconfig,
+ &mpctx->opts->stream_id[0][t->type]);
}
return;
}
@@ -4972,6 +4972,8 @@ static void cmd_track_add(void *p)
mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION);
} else {
mpctx->opts->stream_id[0][t->type] = t->user_tid;
+ m_config_notify_change_opt_ptr(mpctx->mconfig,
+ &mpctx->opts->stream_id[0][t->type]);
}
}
char *title = cmd->args[2].v.s;
@@ -6037,13 +6039,23 @@ static void update_priority(struct MPContext *mpctx)
#endif
}
-void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags)
+void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,
+ bool self_update)
{
struct MPContext *mpctx = ctx;
struct MPOpts *opts = mpctx->opts;
struct command_ctx *cmd = mpctx->command_ctx;
void *opt_ptr = co ? co->data : NULL; // NULL on start
+ if (co)
+ mp_notify_property(mpctx, co->name);
+
+ if (opt_ptr == &opts->pause)
+ mp_notify(mpctx, opts->pause ? MPV_EVENT_PAUSE : MPV_EVENT_UNPAUSE, 0);
+
+ if (self_update)
+ return;
+
if (flags & UPDATE_TERM)
mp_update_logging(mpctx, false);
diff --git a/player/command.h b/player/command.h
index 037dec1164..e980f1715e 100644
--- a/player/command.h
+++ b/player/command.h
@@ -78,7 +78,8 @@ void property_print_help(struct MPContext *mpctx);
int mp_property_do(const char* name, int action, void* val,
struct MPContext *mpctx);
-void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags);
+void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,
+ bool self_update);
void mp_notify(struct MPContext *mpctx, int event, void *arg);
void mp_notify_property(struct MPContext *mpctx, const char *property);
diff --git a/player/loadfile.c b/player/loadfile.c
index 9d515b2012..603b264f60 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -572,8 +572,11 @@ static void check_previous_track_selection(struct MPContext *mpctx)
// defaults are -1 (default selection), or -2 (off) for secondary tracks.
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
for (int i = 0; i < NUM_PTRACKS; i++) {
- if (opts->stream_id[i][t] >= 0)
+ if (opts->stream_id[i][t] >= 0) {
opts->stream_id[i][t] = i == 0 ? -1 : -2;
+ m_config_notify_change_opt_ptr(mpctx->mconfig,
+ &opts->stream_id[i][t]);
+ }
}
}
talloc_free(mpctx->track_layout_hash);
@@ -590,8 +593,11 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
// Mark the current track selection as explicitly user-requested. (This is
// different from auto-selection or disabling a track due to errors.)
- if (flags & FLAG_MARK_SELECTION)
+ if (flags & FLAG_MARK_SELECTION) {
mpctx->opts->stream_id[order][type] = track ? track->user_tid : -2;
+ m_config_notify_change_opt_ptr(mpctx->mconfig,
+ &mpctx->opts->stream_id[order][type]);
+ }
// No decoder should be initialized yet.
if (!mpctx->demuxer)
@@ -1623,8 +1629,10 @@ terminate_playback:
process_hooks(mpctx, "on_unload");
- if (mpctx->step_frames)
+ if (mpctx->step_frames) {
opts->pause = 1;
+ m_config_notify_change_opt_ptr(mpctx->mconfig, &opts->pause);
+ }
close_recorder(mpctx);
@@ -1730,8 +1738,11 @@ struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
if (mpctx->opts->shuffle)
playlist_shuffle(mpctx->playlist);
next = mpctx->playlist->first;
- if (next && mpctx->opts->loop_times > 1)
+ if (next && mpctx->opts->loop_times > 1) {
mpctx->opts->loop_times--;
+ m_config_notify_change_opt_ptr(mpctx->mconfig,
+ &mpctx->opts->loop_times);
+ }
} else {
next = mpctx->playlist->last;
// Don't jump to files that would immediately go to next file anyway
@@ -1852,7 +1863,7 @@ void close_recorder_and_error(struct MPContext *mpctx)
close_recorder(mpctx);
talloc_free(mpctx->opts->record_file);
mpctx->opts->record_file = NULL;
- mp_notify_property(mpctx, "record-file");
+ m_config_notify_change_opt_ptr(mpctx->mconfig, &mpctx->opts->record_file);
MP_ERR(mpctx, "Disabling stream recording.\n");
}
diff --git a/player/main.c b/player/main.c
index f4c7348af4..cac012a774 100644
--- a/player/main.c
+++ b/player/main.c
@@ -193,6 +193,7 @@ void mp_destroy(struct MPContext *mpctx)
assert(!mpctx->num_abort_list);
talloc_free(mpctx->abort_list);
pthread_mutex_destroy(&mpctx->abort_lock);
+ talloc_free(mpctx->mconfig); // destroy before dispatch
talloc_free(mpctx);
}
@@ -379,8 +380,9 @@ int mp_initialize(struct MPContext *mpctx, char **options)
mpctx->initialized = true;
mpctx->mconfig->option_change_callback = mp_option_change_callback;
mpctx->mconfig->option_change_callback_ctx = mpctx;
+ m_config_set_update_dispatch_queue(mpctx->mconfig, mpctx->dispatch);
// Run all update handlers.
- mp_option_change_callback(mpctx, NULL, UPDATE_OPTS_MASK);
+ mp_option_change_callback(mpctx, NULL, UPDATE_OPTS_MASK, false);
if (handle_help_options(mpctx))
return 1; // help
diff --git a/player/playloop.c b/player/playloop.c
index 5c83615a86..0e0c09654d 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -144,16 +144,12 @@ void update_core_idle_state(struct MPContext *mpctx)
void set_pause_state(struct MPContext *mpctx, bool user_pause)
{
struct MPOpts *opts = mpctx->opts;
- bool send_update = false;
- if (opts->pause != user_pause)
- send_update = true;
opts->pause = user_pause;
bool internal_paused = opts->pause || mpctx->paused_for_cache;
if (internal_paused != mpctx->paused) {
mpctx->paused = internal_paused;
- send_update = true;
if (mpctx->ao && mpctx->ao_chain) {
if (internal_paused) {
@@ -177,12 +173,15 @@ void set_pause_state(struct MPContext *mpctx, bool user_pause)
} else {
(void)get_relative_time(mpctx); // ignore time that passed during pause
}
+
+ // For some reason, these events are supposed to be sent even if only
+ // the internal pause state changed (and "pause" property didn't)... OK.
+ mp_notify(mpctx, opts->pause ? MPV_EVENT_PAUSE : MPV_EVENT_UNPAUSE, 0);
}
update_core_idle_state(mpctx);
- if (send_update)
- mp_notify(mpctx, opts->pause ? MPV_EVENT_PAUSE : MPV_EVENT_UNPAUSE, 0);
+ m_config_notify_change_opt_ptr(mpctx->mconfig, &opts->pause);
}
void update_internal_pause_state(struct MPContext *mpctx)
@@ -884,8 +883,10 @@ static void handle_loop_file(struct MPContext *mpctx)
target = ab[0];
prec = MPSEEK_EXACT;
} else if (opts->loop_file) {
- if (opts->loop_file > 0)
+ if (opts->loop_file > 0) {
opts->loop_file--;
+ m_config_notify_change_opt_ptr(mpctx->mconfig, &opts->loop_file);
+ }
target = get_start_time(mpctx, mpctx->play_dir);
}
@@ -1035,6 +1036,7 @@ int handle_force_window(struct MPContext *mpctx, bool force)
err:
mpctx->opts->force_vo = 0;
+ m_config_notify_change_opt_ptr(mpctx->mconfig, &mpctx->opts->force_vo);
uninit_video_out(mpctx);
MP_FATAL(mpctx, "Error opening/initializing the VO window.\n");
return -1;