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;