Explorar o código

libobs: Use uthash for source objects

All sources are part of a hash table that allows a lookup by UUID.
Public sources additionally are in a hash table allowing lookup by name.
derrod %!s(int64=2) %!d(string=hai) anos
pai
achega
c68eeaef1d
Modificáronse 7 ficheiros con 322 adicións e 54 borrados
  1. 9 0
      docs/sphinx/reference-core.rst
  2. 28 1
      libobs/obs-internal.h
  3. 4 2
      libobs/obs-scene.c
  4. 18 4
      libobs/obs-source.c
  5. 2 2
      libobs/obs-video.c
  6. 258 45
      libobs/obs.c
  7. 3 0
      libobs/obs.h

+ 9 - 0
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.

+ 28 - 1
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 <caption/caption.h>
 
+/* 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  */

+ 4 - 2
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) {

+ 18 - 4
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);

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

+ 258 - 45
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;
+
+	struct obs_context_data *ctx, *tmp;
+	HASH_ITER (hh_uuid, ht, ctx, tmp) {
+		HASH_DELETE(hh_uuid, ht, ctx);
 
-		source = (struct obs_source *)source->context.next;
+		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;

+ 3 - 0
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);