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);