diff options
author | wm4 <wm4@nowhere> | 2017-08-22 15:50:33 +0200 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2017-08-22 15:50:33 +0200 |
commit | d2bdb72b69536370c3046107fc593896e74803c3 (patch) | |
tree | 09d5913f981ee5f5e0d43fbfd24b06f67eb8dd5e /options/m_config.c | |
parent | 5361c0b5d897ec7c969d2a75bb2dde345d62a59c (diff) | |
download | mpv-d2bdb72b69536370c3046107fc593896e74803c3.tar.bz2 mpv-d2bdb72b69536370c3046107fc593896e74803c3.tar.xz |
options: add a thread-safe way to notify option updates
So far, we had a thread-safe way to read options, but no option update
notification mechanism. Everything was funneled though the main thread's
central mp_option_change_callback() function. For example, if the
panscan options were changed, the function called vo_control() with
VOCTRL_SET_PANSCAN to manually notify the VO thread of updates. This
worked, but's pretty inconvenient. Most of these problems come from the
fact that MPlayer was written as a single-threaded program.
This commit works towards a more flexible mechanism. It adds an update
callback to m_config_cache (the thing that is already used for
thread-safe access of global options).
This alone would still be rather inconvenient, at least in context of
VOs. Add another mechanism on top of it that uses mp_dispatch_queue, and
takes care of some annoying synchronization issues. We extend
mp_dispatch_queue itself to make this easier and slightly more
efficient.
As a first application, use this to reimplement certain VO scaling and
renderer options. The update_opts() function translates these to the
"old" VOCTRLs, though.
An annoyingly subtle issue is that m_config_cache's destructor now
releases pending notifications, and must be released before the
associated dispatch queue. Otherwise, it could happen that option
updates during e.g. VO destruction queue or run stale entries, which is
not expected.
Rather untested. The singly-linked list code in dispatch.c is probably
buggy, and I bet some aspects about synchronization are not entirely
sane.
Diffstat (limited to 'options/m_config.c')
-rw-r--r-- | options/m_config.c | 91 |
1 files changed, 90 insertions, 1 deletions
diff --git a/options/m_config.c b/options/m_config.c index 378bed25ad..24f4b83c45 100644 --- a/options/m_config.c +++ b/options/m_config.c @@ -39,6 +39,7 @@ #include "common/global.h" #include "common/msg.h" #include "common/msg_control.h" +#include "misc/dispatch.h" #include "misc/node.h" #include "osdep/atomic.h" @@ -57,6 +58,8 @@ struct m_config_shadow { pthread_mutex_t lock; struct m_config *root; char *data; + struct m_config_cache **listeners; + int num_listeners; }; // Represents a sub-struct (OPT_SUBSTRUCT()). @@ -156,8 +159,11 @@ static void config_destroy(void *p) m_option_free(co->opt, config->shadow->data + co->shadow_offset); } - if (config->shadow) + if (config->shadow) { + // must all have been unregistered + assert(config->shadow->num_listeners == 0); pthread_mutex_destroy(&config->shadow->lock); + } } struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, @@ -1190,6 +1196,16 @@ static bool is_group_included(struct m_config *config, int group, int parent) return false; } +static void cache_destroy(void *p) +{ + struct m_config_cache *cache = p; + + // (technically speaking, being able to call them both without anything + // breaking is a feature provided by these functions) + m_config_cache_set_wakeup_cb(cache, NULL, NULL); + 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, const struct m_sub_options *group) @@ -1198,6 +1214,7 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent, struct m_config *root = shadow->root; struct m_config_cache *cache = talloc_zero(ta_parent, struct m_config_cache); + talloc_set_destructor(cache, cache_destroy); cache->shadow = shadow; cache->shadow_config = m_config_new(cache, mp_null_log, root->size, root->defaults, root->options); @@ -1292,12 +1309,84 @@ void m_config_notify_change_co(struct m_config *config, group = g->parent_group; } + if (shadow) { + pthread_mutex_lock(&shadow->lock); + for (int n = 0; n < shadow->num_listeners; n++) { + struct m_config_cache *cache = shadow->listeners[n]; + if (cache->wakeup_cb) + cache->wakeup_cb(cache->wakeup_cb_ctx); + } + pthread_mutex_unlock(&shadow->lock); + } + if (config->option_change_callback) { config->option_change_callback(config->option_change_callback_ctx, co, changed); } } +void m_config_cache_set_wakeup_cb(struct m_config_cache *cache, + void (*cb)(void *ctx), void *cb_ctx) +{ + struct m_config_shadow *shadow = cache->shadow; + + pthread_mutex_lock(&shadow->lock); + if (cache->in_list) { + for (int n = 0; n < shadow->num_listeners; n++) { + if (shadow->listeners[n] == cache) + MP_TARRAY_REMOVE_AT(shadow->listeners, shadow->num_listeners, n); + } + if (!shadow->num_listeners) { + talloc_free(shadow->listeners); + shadow->listeners = NULL; + } + } + if (cb) { + MP_TARRAY_APPEND(NULL, shadow->listeners, shadow->num_listeners, cache); + cache->in_list = true; + cache->wakeup_cb = cb; + cache->wakeup_cb_ctx = cb_ctx; + } + pthread_mutex_unlock(&shadow->lock); +} + +static void dispatch_notify(void *p) +{ + struct m_config_cache *cache = p; + + assert(cache->wakeup_dispatch_queue); + mp_dispatch_enqueue_notify(cache->wakeup_dispatch_queue, + cache->wakeup_dispatch_cb, + cache->wakeup_dispatch_cb_ctx); +} + +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) +{ + // Remove the old one is tricky. Firts make sure no new notifications will + // come. + m_config_cache_set_wakeup_cb(cache, NULL, NULL); + // Remove any pending notifications (assume we're on the same thread as + // any potential mp_dispatch_queue_process() callers). + if (cache->wakeup_dispatch_queue) { + mp_dispatch_cancel_fn(cache->wakeup_dispatch_queue, + cache->wakeup_dispatch_cb, + cache->wakeup_dispatch_cb_ctx); + } + + cache->wakeup_dispatch_queue = NULL; + cache->wakeup_dispatch_cb = NULL; + cache->wakeup_dispatch_cb_ctx = NULL; + + if (cb) { + cache->wakeup_dispatch_queue = dispatch; + cache->wakeup_dispatch_cb = cb; + cache->wakeup_dispatch_cb_ctx = cb_ctx; + m_config_cache_set_wakeup_cb(cache, dispatch_notify, cache); + } +} + bool m_config_is_in_group(struct m_config *config, const struct m_sub_options *group, struct m_config_option *co) |