diff --git a/docs/sphinx/reference-core.rst b/docs/sphinx/reference-core.rst index b6d0720e1..abc33ca30 100644 --- a/docs/sphinx/reference-core.rst +++ b/docs/sphinx/reference-core.rst @@ -351,6 +351,15 @@ Libobs Objects --------------------- +.. function:: obs_source_t *obs_get_transition_by_uuid(const char *uuid) + + Gets a transition by its UUID. + + Increments the source reference counter, use + :c:func:`obs_source_release()` to release it when complete. + +--------------------- + .. function:: obs_scene_t *obs_get_scene_by_name(const char *name) Gets a scene by its name. diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 7a3bd7999..c5b86dd4c 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -25,6 +25,7 @@ #include "util/platform.h" #include "util/profiler.h" #include "util/task.h" +#include "util/uthash.h" #include "callback/signal.h" #include "callback/proc.h" @@ -39,6 +40,12 @@ #include +/* Custom helpers for the UUID hash table */ +#define HASH_FIND_UUID(head, uuid, out) \ + HASH_FIND(hh_uuid, head, uuid, UUID_STR_LENGTH, out) +#define HASH_ADD_UUID(head, uuid_field, add) \ + HASH_ADD(hh_uuid, head, uuid_field[0], UUID_STR_LENGTH, add) + #define NUM_TEXTURES 2 #define NUM_CHANNELS 3 #define MICROSECOND_DEN 1000000 @@ -372,7 +379,11 @@ struct obs_core_audio { /* user sources, output channels, and displays */ struct obs_core_data { - struct obs_source *first_source; + /* Hash tables (uthash) */ + struct obs_source *sources; /* Lookup by UUID (hh_uuid) */ + struct obs_source *public_sources; /* Lookup by name (hh) */ + + /* Linked lists */ struct obs_source *first_audio_source; struct obs_display *first_display; struct obs_output *first_output; @@ -541,6 +552,9 @@ struct obs_context_data { struct obs_context_data *next; struct obs_context_data **prev_next; + UT_hash_handle hh; + UT_hash_handle hh_uuid; + bool private; }; @@ -554,11 +568,24 @@ extern void obs_context_data_free(struct obs_context_data *context); extern void obs_context_data_insert(struct obs_context_data *context, pthread_mutex_t *mutex, void *first); +extern void obs_context_data_insert_name(struct obs_context_data *context, + pthread_mutex_t *mutex, void *first); +extern void obs_context_data_insert_uuid(struct obs_context_data *context, + pthread_mutex_t *mutex, + void *first_uuid); + extern void obs_context_data_remove(struct obs_context_data *context); +extern void obs_context_data_remove_name(struct obs_context_data *context, + void *phead); +extern void obs_context_data_remove_uuid(struct obs_context_data *context, + void *puuid_head); + extern void obs_context_wait(struct obs_context_data *context); extern void obs_context_data_setname(struct obs_context_data *context, const char *name); +extern void obs_context_data_setname_ht(struct obs_context_data *context, + const char *name, void *phead); /* ------------------------------------------------------------------------- */ /* ref-counting */ diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 9b4086082..594d997f6 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -960,7 +960,7 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data) { const char *name = obs_data_get_string(item_data, "name"); const char *src_uuid = obs_data_get_string(item_data, "source_uuid"); - obs_source_t *source; + obs_source_t *source = NULL; const char *scale_filter_str; const char *blend_method_str; const char *blend_str; @@ -973,7 +973,9 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data) if (src_uuid && strlen(src_uuid) == UUID_STR_LENGTH) source = obs_get_source_by_uuid(src_uuid); - else + + /* Fall back to name if UUID was not found or is not set. */ + if (!source) source = obs_get_source_by_name(name); if (!source) { diff --git a/libobs/obs-source.c b/libobs/obs-source.c index f92d54f49..6b0540292 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -244,8 +244,13 @@ static void obs_source_init_finalize(struct obs_source *source) pthread_mutex_unlock(&obs->data.audio_sources_mutex); } - obs_context_data_insert(&source->context, &obs->data.sources_mutex, - &obs->data.first_source); + if (!source->context.private) { + obs_context_data_insert_name(&source->context, + &obs->data.sources_mutex, + &obs->data.public_sources); + } + obs_context_data_insert_uuid(&source->context, &obs->data.sources_mutex, + &obs->data.sources); } static bool obs_source_hotkey_mute(void *data, obs_hotkey_pair_id id, @@ -662,7 +667,10 @@ void obs_source_destroy(struct obs_source *source) while (source->filters.num) obs_source_filter_remove(source, source->filters.array[0]); - obs_context_data_remove(&source->context); + obs_context_data_remove_uuid(&source->context, &obs->data.sources); + if (!source->context.private) + obs_context_data_remove_name(&source->context, + &obs->data.public_sources); /* defer source destroy */ os_task_queue_queue_task(obs->destruction_task_thread, @@ -4259,7 +4267,13 @@ void obs_source_set_name(obs_source_t *source, const char *name) strcmp(name, source->context.name) != 0) { struct calldata data; char *prev_name = bstrdup(source->context.name); - obs_context_data_setname(&source->context, name); + + if (!source->context.private) { + obs_context_data_setname_ht(&source->context, name, + &obs->data.public_sources); + } else { + obs_context_data_setname(&source->context, name); + } calldata_init(&data); calldata_set_ptr(&data, "source", source); diff --git a/libobs/obs-video.c b/libobs/obs-video.c index ab66db3c5..0b922cb86 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -60,7 +60,7 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time) pthread_mutex_lock(&data->sources_mutex); - source = data->first_source; + source = data->sources; while (source) { obs_source_t *s = obs_source_get_ref(source); @@ -69,7 +69,7 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time) obs_source_release(s); } - source = (struct obs_source *)source->context.next; + source = (struct obs_source *)source->context.hh_uuid.next; } pthread_mutex_unlock(&data->sources_mutex); diff --git a/libobs/obs.c b/libobs/obs.c index 849f9001f..d2017ee27 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -1000,6 +1000,8 @@ static bool obs_init_data(void) if (!obs_view_init(&data->main_view)) goto fail; + data->sources = NULL; + data->public_sources = NULL; data->private_data = obs_data_create(); data->valid = true; @@ -1019,6 +1021,20 @@ void obs_main_view_free(struct obs_view *view) pthread_mutex_destroy(&view->channels_mutex); } +#define FREE_OBS_HASH_TABLE(handle, table, type) \ + do { \ + struct obs_context_data *ctx, *tmp; \ + int unfreed = 0; \ + HASH_ITER (handle, *(struct obs_context_data **)table, ctx, \ + tmp) { \ + obs_##type##_destroy((obs_##type##_t *)ctx); \ + unfreed++; \ + } \ + if (unfreed) \ + blog(LOG_INFO, "\t%d " #type "(s) were remaining", \ + unfreed); \ + } while (false) + #define FREE_OBS_LINKED_LIST(type) \ do { \ int unfreed = 0; \ @@ -1042,12 +1058,14 @@ static void obs_free_data(void) blog(LOG_INFO, "Freeing OBS context data"); - FREE_OBS_LINKED_LIST(source); FREE_OBS_LINKED_LIST(output); FREE_OBS_LINKED_LIST(encoder); FREE_OBS_LINKED_LIST(display); FREE_OBS_LINKED_LIST(service); + FREE_OBS_HASH_TABLE(hh, &data->public_sources, source); + FREE_OBS_HASH_TABLE(hh_uuid, &data->sources, source); + os_task_queue_wait(obs->destruction_task_thread); pthread_mutex_destroy(&data->sources_mutex); @@ -1869,17 +1887,16 @@ void obs_enum_sources(bool (*enum_proc)(void *, obs_source_t *), void *param) obs_source_t *source; pthread_mutex_lock(&obs->data.sources_mutex); - source = obs->data.first_source; + source = obs->data.public_sources; while (source) { obs_source_t *s = obs_source_get_ref(source); if (s) { - if (strcmp(s->info.id, group_info.id) == 0 && + if (s->info.type == OBS_SOURCE_TYPE_INPUT && !enum_proc(param, s)) { obs_source_release(s); break; - } else if (s->info.type == OBS_SOURCE_TYPE_INPUT && - !s->context.private && + } else if (strcmp(s->info.id, group_info.id) == 0 && !enum_proc(param, s)) { obs_source_release(s); break; @@ -1887,7 +1904,7 @@ void obs_enum_sources(bool (*enum_proc)(void *, obs_source_t *), void *param) obs_source_release(s); } - source = (obs_source_t *)source->context.next; + source = (obs_source_t *)source->context.hh.next; } pthread_mutex_unlock(&obs->data.sources_mutex); @@ -1898,20 +1915,20 @@ void obs_enum_scenes(bool (*enum_proc)(void *, obs_source_t *), void *param) obs_source_t *source; pthread_mutex_lock(&obs->data.sources_mutex); - source = obs->data.first_source; + source = obs->data.public_sources; while (source) { obs_source_t *s = obs_source_get_ref(source); if (s) { if (source->info.type == OBS_SOURCE_TYPE_SCENE && - !source->context.private && !enum_proc(param, s)) { + !enum_proc(param, s)) { obs_source_release(s); break; } obs_source_release(s); } - source = (obs_source_t *)source->context.next; + source = (obs_source_t *)source->context.hh.next; } pthread_mutex_unlock(&obs->data.sources_mutex); @@ -1940,11 +1957,31 @@ static inline void obs_enum(void *pstart, pthread_mutex_t *mutex, void *proc, pthread_mutex_unlock(mutex); } +static inline void obs_enum_uuid(void *pstart, pthread_mutex_t *mutex, + void *proc, void *param) +{ + struct obs_context_data **start = pstart, *context, *tmp; + bool (*enum_proc)(void *, void *) = proc; + + assert(start); + assert(mutex); + assert(enum_proc); + + pthread_mutex_lock(mutex); + + HASH_ITER (hh_uuid, *start, context, tmp) { + if (!enum_proc(param, context)) + break; + } + + pthread_mutex_unlock(mutex); +} + void obs_enum_all_sources(bool (*enum_proc)(void *, obs_source_t *), void *param) { - obs_enum(&obs->data.first_source, &obs->data.sources_mutex, enum_proc, - param); + obs_enum_uuid(&obs->data.sources, &obs->data.sources_mutex, enum_proc, + param); } void obs_enum_outputs(bool (*enum_proc)(void *, obs_output_t *), void *param) @@ -1974,15 +2011,41 @@ static inline void *get_context_by_name(void *vfirst, const char *name, pthread_mutex_lock(mutex); - context = *first; - while (context) { - if (!context->private && strcmp(context->name, name) == 0) { - context = addref(context); - break; + /* If context list head has a hash table, look the name up in there */ + if (*first && (*first)->hh.tbl) { + HASH_FIND_STR(*first, name, context); + } else { + context = *first; + while (context) { + if (!context->private && + strcmp(context->name, name) == 0) { + break; + } + + context = context->next; } - context = context->next; } + if (context) + addref(context); + + pthread_mutex_unlock(mutex); + return context; +} + +static void *get_context_by_uuid(void *ptable, const char *uuid, + pthread_mutex_t *mutex, + void *(*addref)(void *)) +{ + struct obs_context_data **ht = ptable; + struct obs_context_data *context; + + pthread_mutex_lock(mutex); + + HASH_FIND_UUID(*ht, uuid, context); + if (context) + addref(context); + pthread_mutex_unlock(mutex); return context; } @@ -2014,38 +2077,27 @@ static inline void *obs_id_(void *data) obs_source_t *obs_get_source_by_name(const char *name) { - return get_context_by_name(&obs->data.first_source, name, + return get_context_by_name(&obs->data.public_sources, name, &obs->data.sources_mutex, obs_source_addref_safe_); } obs_source_t *obs_get_source_by_uuid(const char *uuid) { - struct obs_source **first = &obs->data.first_source; - struct obs_source *source; - - pthread_mutex_lock(&obs->data.sources_mutex); - - source = *first; - while (source) { - if (strcmp(source->context.uuid, uuid) == 0) { - source = obs_source_get_ref(source); - break; - } - source = (struct obs_source *)source->context.next; - } - - pthread_mutex_unlock(&obs->data.sources_mutex); - return source; + return get_context_by_uuid(&obs->data.sources, uuid, + &obs->data.sources_mutex, + obs_source_addref_safe_); } obs_source_t *obs_get_transition_by_name(const char *name) { - struct obs_source **first = &obs->data.first_source; + struct obs_source **first = &obs->data.sources; struct obs_source *source; pthread_mutex_lock(&obs->data.sources_mutex); + /* Transitions are private but can be found via this method, so we + * can't look them up by name in the public_sources hash table. */ source = *first; while (source) { if (source->info.type == OBS_SOURCE_TYPE_TRANSITION && @@ -2053,13 +2105,18 @@ obs_source_t *obs_get_transition_by_name(const char *name) source = obs_source_addref_safe_(source); break; } - source = (void *)source->context.next; + source = (void *)source->context.hh_uuid.next; } pthread_mutex_unlock(&obs->data.sources_mutex); return source; } +obs_source_t *obs_get_transition_by_uuid(const char *uuid) +{ + return obs_get_source_by_uuid(uuid); +} + obs_output_t *obs_get_output_by_name(const char *name) { return get_context_by_name(&obs->data.first_output, name, @@ -2503,19 +2560,19 @@ obs_data_array_t *obs_save_sources_filtered(obs_save_source_filter_cb cb, pthread_mutex_lock(&data->sources_mutex); - source = data->first_source; + source = data->public_sources; while (source) { if ((source->info.type != OBS_SOURCE_TYPE_FILTER) != 0 && - !source->context.private && !source->removed && - !source->temp_removed && cb(data_, source)) { + !source->removed && !source->temp_removed && + cb(data_, source)) { obs_data_t *source_data = obs_save_source(source); obs_data_array_push_back(array, source_data); obs_data_release(source_data); } - source = (obs_source_t *)source->context.next; + source = (obs_source_t *)source->context.hh.next; } pthread_mutex_unlock(&data->sources_mutex); @@ -2539,14 +2596,25 @@ void obs_reset_source_uuids() { pthread_mutex_lock(&obs->data.sources_mutex); - struct obs_source *source = obs->data.first_source; - while (source) { - bfree((void *)source->context.uuid); - source->context.uuid = os_generate_uuid(); + /* Move all sources to a new hash table */ + struct obs_context_data *ht = + (struct obs_context_data *)obs->data.sources; + struct obs_context_data *new_ht = NULL; - source = (struct obs_source *)source->context.next; + struct obs_context_data *ctx, *tmp; + HASH_ITER (hh_uuid, ht, ctx, tmp) { + HASH_DELETE(hh_uuid, ht, ctx); + + bfree((void *)ctx->uuid); + ctx->uuid = os_generate_uuid(); + + HASH_ADD_UUID(new_ht, uuid, ctx); } + /* The old table will be automatically freed once the last element has + * been removed, so we can simply overwrite the pointer. */ + obs->data.sources = (struct obs_source *)new_ht; + pthread_mutex_unlock(&obs->data.sources_mutex); } @@ -2662,6 +2730,91 @@ void obs_context_data_insert(struct obs_context_data *context, pthread_mutex_unlock(mutex); } +static inline char *obs_context_deduplicate_name(void *phash, const char *name) +{ + struct obs_context_data *head = phash; + struct obs_context_data *item = NULL; + + HASH_FIND_STR(head, name, item); + if (!item) + return NULL; + + struct dstr new_name = {0}; + int suffix = 2; + + while (item) { + dstr_printf(&new_name, "%s %d", name, suffix++); + HASH_FIND_STR(head, new_name.array, item); + } + + return new_name.array; +} + +void obs_context_data_insert_name(struct obs_context_data *context, + pthread_mutex_t *mutex, void *pfirst) +{ + struct obs_context_data **first = pfirst; + char *new_name; + + assert(context); + assert(mutex); + assert(first); + + context->mutex = mutex; + + pthread_mutex_lock(mutex); + + /* Ensure name is not a duplicate. */ + new_name = obs_context_deduplicate_name(*first, context->name); + if (new_name) { + blog(LOG_WARNING, + "Attempted to insert context with duplicate name \"%s\"!" + " Name has been changed to \"%s\"", + context->name, new_name); + /* Since this happens before the context creation finishes, + * do not bother to add it to the rename cache. */ + bfree(context->name); + context->name = new_name; + } + + HASH_ADD_STR(*first, name, context); + + pthread_mutex_unlock(mutex); +} + +void obs_context_data_insert_uuid(struct obs_context_data *context, + pthread_mutex_t *mutex, void *pfirst_uuid) +{ + struct obs_context_data **first_uuid = pfirst_uuid; + struct obs_context_data *item = NULL; + + assert(context); + assert(mutex); + assert(first_uuid); + + context->mutex = mutex; + + pthread_mutex_lock(mutex); + + /* Ensure UUID is not a duplicate. + * This should only ever happen if a scene collection file has been + * manually edited and an entry has been duplicated without removing + * or regenerating the UUID. */ + HASH_FIND_UUID(*first_uuid, context->uuid, item); + if (item) { + blog(LOG_WARNING, + "Attempted to insert context with duplicate UUID \"%s\"!", + context->uuid); + /* It is practically impossible for the new UUID to be a + * duplicate, so don't bother checking again. */ + bfree((void *)context->uuid); + context->uuid = os_generate_uuid(); + } + + HASH_ADD_UUID(*first_uuid, uuid, context); + pthread_mutex_unlock(mutex); +} + void obs_context_data_remove(struct obs_context_data *context) { if (context && context->prev_next) { @@ -2674,6 +2827,35 @@ void obs_context_data_remove(struct obs_context_data *context) } } +void obs_context_data_remove_name(struct obs_context_data *context, void *phead) +{ + struct obs_context_data **head = phead; + + assert(head); + + if (!context) + return; + + pthread_mutex_lock(context->mutex); + HASH_DELETE(hh, *head, context); + pthread_mutex_unlock(context->mutex); +} + +void obs_context_data_remove_uuid(struct obs_context_data *context, + void *puuid_head) +{ + struct obs_context_data **uuid_head = puuid_head; + + assert(uuid_head); + + if (!context || !context->uuid || !uuid_head) + return; + + pthread_mutex_lock(context->mutex); + HASH_DELETE(hh_uuid, *uuid_head, context); + pthread_mutex_unlock(context->mutex); +} + void obs_context_wait(struct obs_context_data *context) { pthread_mutex_lock(context->mutex); @@ -2692,6 +2874,37 @@ void obs_context_data_setname(struct obs_context_data *context, pthread_mutex_unlock(&context->rename_cache_mutex); } +void obs_context_data_setname_ht(struct obs_context_data *context, + const char *name, void *phead) +{ + struct obs_context_data **head = phead; + char *new_name; + + pthread_mutex_lock(context->mutex); + pthread_mutex_lock(&context->rename_cache_mutex); + + HASH_DEL(*head, context); + if (context->name) + da_push_back(context->rename_cache, &context->name); + + /* Ensure new name is not a duplicate. */ + new_name = obs_context_deduplicate_name(*head, name); + if (new_name) { + blog(LOG_WARNING, + "Attempted to rename context to duplicate name \"%s\"!" + " New name has been changed to \"%s\"", + context->name, new_name); + context->name = new_name; + } else { + context->name = dup_name(name, context->private); + } + + HASH_ADD_STR(*head, name, context); + + pthread_mutex_unlock(&context->rename_cache_mutex); + pthread_mutex_unlock(context->mutex); +} + profiler_name_store_t *obs_get_profiler_name_store(void) { return obs->name_store; diff --git a/libobs/obs.h b/libobs/obs.h index 60db9b4fa..50d18c491 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -708,6 +708,9 @@ EXPORT obs_source_t *obs_get_source_by_uuid(const char *uuid); /** Get a transition source by its name. */ EXPORT obs_source_t *obs_get_transition_by_name(const char *name); +/** Get a transition source by its UUID. */ +EXPORT obs_source_t *obs_get_transition_by_uuid(const char *uuid); + /** Gets an output by its name. */ EXPORT obs_output_t *obs_get_output_by_name(const char *name);