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.
This commit is contained in:
derrod 2023-02-09 08:34:08 +01:00
parent 183fa8c06c
commit c68eeaef1d
7 changed files with 322 additions and 54 deletions

View file

@ -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.

View file

@ -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 */

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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