From 619edf5e7d46eea314c3d5194eeaed7bee5635d1 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Thu, 31 Mar 2022 17:34:17 -0700 Subject: [PATCH 01/14] libobs: Add pointer to obs_data_item name Rather than have to look up the string each time, just add a pointer to the string. Still all stored in the same memory though. --- libobs/obs-data.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libobs/obs-data.c b/libobs/obs-data.c index c6a03e081..f277151c4 100644 --- a/libobs/obs-data.c +++ b/libobs/obs-data.c @@ -30,6 +30,7 @@ struct obs_data_item { volatile long ref; + const char *name; struct obs_data *parent; struct obs_data_item *next; enum obs_data_type type; @@ -295,7 +296,10 @@ static struct obs_data_item *obs_data_item_create(const char *name, item->data_size = size; } - strcpy(get_item_name(item), name); + char *name_ptr = get_item_name(item); + item->name = name_ptr; + + strcpy(name_ptr, name); memcpy(get_item_data(item), data, size); item_data_addref(item); @@ -354,6 +358,7 @@ obs_data_item_ensure_capacity(struct obs_data_item *item) new_item = brealloc(item, new_size); new_item->capacity = new_size; + new_item->name = get_item_name(new_item); obs_data_item_reattach(item, new_item); return new_item; @@ -1671,7 +1676,7 @@ const char *obs_data_item_get_name(obs_data_item_t *item) if (!item) return NULL; - return get_item_name(item); + return item->name; } void obs_data_item_set_string(obs_data_item_t **item, const char *val) From d0554e25c18120229a7cf47019b9e42b31fbe380 Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 8 Feb 2023 12:05:03 +0100 Subject: [PATCH 02/14] deps: Add uthash --- deps/CMakeLists.txt | 1 + deps/uthash/.clang-format | 3 + deps/uthash/CMakeLists.txt | 7 + deps/uthash/uthash/LICENSE | 21 + deps/uthash/uthash/uthash.h | 1136 +++++++++++++++++++++++++++++++++++ 5 files changed, 1168 insertions(+) create mode 100644 deps/uthash/.clang-format create mode 100644 deps/uthash/CMakeLists.txt create mode 100644 deps/uthash/uthash/LICENSE create mode 100644 deps/uthash/uthash/uthash.h diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index f8a741046..90ceeb6be 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -12,6 +12,7 @@ add_subdirectory(file-updater) add_subdirectory(obs-scripting) add_subdirectory(opts-parser) add_subdirectory(libcaption) +add_subdirectory(uthash) # Use bundled jansson version as fallback find_package(Jansson 2.5 QUIET) diff --git a/deps/uthash/.clang-format b/deps/uthash/.clang-format new file mode 100644 index 000000000..6420a4688 --- /dev/null +++ b/deps/uthash/.clang-format @@ -0,0 +1,3 @@ +Language: Cpp +SortIncludes: false +DisableFormat: true diff --git a/deps/uthash/CMakeLists.txt b/deps/uthash/CMakeLists.txt new file mode 100644 index 000000000..7817c7433 --- /dev/null +++ b/deps/uthash/CMakeLists.txt @@ -0,0 +1,7 @@ +project(uthash) + +add_library(uthash INTERFACE) +add_library(OBS::uthash ALIAS uthash) +set_target_properties(uthash PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${CMAKE_CURRENT_SOURCE_DIR}") +set_target_properties(uthash PROPERTIES FOLDER "deps") diff --git a/deps/uthash/uthash/LICENSE b/deps/uthash/uthash/LICENSE new file mode 100644 index 000000000..e75a243af --- /dev/null +++ b/deps/uthash/uthash/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2005-2021, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/deps/uthash/uthash/uthash.h b/deps/uthash/uthash/uthash.h new file mode 100644 index 000000000..ac78fdab5 --- /dev/null +++ b/deps/uthash/uthash/uthash.h @@ -0,0 +1,1136 @@ +/* +Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ From 401ae8a57071395d5a2f2616f3fc1f015539ed29 Mon Sep 17 00:00:00 2001 From: derrod Date: Fri, 10 Mar 2023 00:55:46 +0100 Subject: [PATCH 03/14] clang-format: add HASH_ITER to ForEachMacros --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index c9dfc48e9..89cbc19a6 100644 --- a/.clang-format +++ b/.clang-format @@ -57,6 +57,7 @@ ForEachMacros: - 'json_object_foreach' - 'json_object_foreach_safe' - 'json_array_foreach' + - 'HASH_ITER' IncludeBlocks: Preserve IndentCaseLabels: false IndentPPDirectives: None From 2986d36d9bcc158e964037defbe6a9e1986a9e92 Mon Sep 17 00:00:00 2001 From: derrod Date: Sun, 12 Mar 2023 01:48:17 +0100 Subject: [PATCH 04/14] libobs/util: Add uthash Adds a header including uthash and redefining some of its options for use withint libobs and plugins (potentially). --- libobs/CMakeLists.txt | 2 ++ libobs/util/uthash.h | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 libobs/util/uthash.h diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index e72e443ab..c69557e3e 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -201,6 +201,7 @@ target_sources( util/threading.h util/utf8.c util/utf8.h + util/uthash.h util/util_uint64.h util/util_uint128.h util/curl/curl-helper.h @@ -247,6 +248,7 @@ target_link_libraries( FFmpeg::swresample Jansson::Jansson OBS::caption + OBS::uthash ZLIB::ZLIB PUBLIC Threads::Threads) diff --git a/libobs/util/uthash.h b/libobs/util/uthash.h new file mode 100644 index 000000000..5698466ea --- /dev/null +++ b/libobs/util/uthash.h @@ -0,0 +1,34 @@ +/****************************************************************************** + Copyright (C) 2023 by Dennis Sädtler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +/* + * This file (re)defines various uthash settings for use in libobs + */ + +#include + +/* Use OBS allocator */ +#undef uthash_malloc +#undef uthash_free +#define uthash_malloc(sz) bmalloc(sz) +#define uthash_free(ptr, sz) bfree(ptr) + +/* Use SFH (Super Fast Hash) function instead of JEN */ +#undef HASH_FUNCTION +#define HASH_FUNCTION HASH_SFH From f4a88149983b959dd0cb05b33e945efccbe8d7f1 Mon Sep 17 00:00:00 2001 From: Jim Date: Wed, 8 Feb 2023 12:05:52 +0100 Subject: [PATCH 05/14] libobs: Use uthash for obs data objects Co-authored-by: Rodney --- libobs/obs-data.c | 136 ++++++++++++++-------------------------------- 1 file changed, 41 insertions(+), 95 deletions(-) diff --git a/libobs/obs-data.c b/libobs/obs-data.c index f277151c4..6d6d76f4b 100644 --- a/libobs/obs-data.c +++ b/libobs/obs-data.c @@ -20,6 +20,7 @@ #include "util/dstr.h" #include "util/darray.h" #include "util/platform.h" +#include "util/uthash.h" #include "graphics/vec2.h" #include "graphics/vec3.h" #include "graphics/vec4.h" @@ -32,7 +33,7 @@ struct obs_data_item { volatile long ref; const char *name; struct obs_data *parent; - struct obs_data_item *next; + UT_hash_handle hh; enum obs_data_type type; size_t name_len; size_t data_len; @@ -46,7 +47,7 @@ struct obs_data_item { struct obs_data { volatile long ref; char *json; - struct obs_data_item *first_item; + struct obs_data_item *items; }; struct obs_data_array { @@ -306,45 +307,21 @@ static struct obs_data_item *obs_data_item_create(const char *name, return item; } -static struct obs_data_item **get_item_prev_next(struct obs_data *data, - struct obs_data_item *current) -{ - if (!current || !data) - return NULL; - - struct obs_data_item **prev_next = &data->first_item; - struct obs_data_item *item = data->first_item; - - while (item) { - if (item == current) - return prev_next; - - prev_next = &item->next; - item = item->next; - } - - return NULL; -} - static inline void obs_data_item_detach(struct obs_data_item *item) { - struct obs_data_item **prev_next = - get_item_prev_next(item->parent, item); - - if (prev_next) { - *prev_next = item->next; - item->next = NULL; + if (item->parent) { + HASH_DEL(item->parent->items, item); + item->parent = NULL; } } -static inline void obs_data_item_reattach(struct obs_data_item *old_ptr, - struct obs_data_item *new_ptr) +static inline void obs_data_item_reattach(struct obs_data *parent, + struct obs_data_item *item) { - struct obs_data_item **prev_next = - get_item_prev_next(new_ptr->parent, old_ptr); - - if (prev_next) - *prev_next = new_ptr; + if (parent) { + HASH_ADD_STR(parent->items, name, item); + item->parent = parent; + } } static struct obs_data_item * @@ -356,16 +333,23 @@ obs_data_item_ensure_capacity(struct obs_data_item *item) if (item->capacity >= new_size) return item; + struct obs_data *parent = item->parent; + obs_data_item_detach(item); + new_item = brealloc(item, new_size); new_item->capacity = new_size; new_item->name = get_item_name(new_item); - obs_data_item_reattach(item, new_item); + obs_data_item_reattach(parent, new_item); + return new_item; } static inline void obs_data_item_destroy(struct obs_data_item *item) { + if (item->parent) + HASH_DEL(item->parent->items, item); + item_data_release(item); item_default_data_release(item); item_autoselect_data_release(item); @@ -601,9 +585,11 @@ static inline void set_json_array(json_t *json, const char *name, static json_t *obs_data_to_json(obs_data_t *data) { json_t *json = json_object(); - obs_data_item_t *item = NULL; - for (item = obs_data_first(data); item; obs_data_item_next(&item)) { + obs_data_item_t *item = NULL; + obs_data_item_t *temp = NULL; + + HASH_ITER (hh, data->items, item, temp) { enum obs_data_type type = obs_data_item_gettype(item); const char *name = get_item_name(item); @@ -709,12 +695,11 @@ void obs_data_addref(obs_data_t *data) static inline void obs_data_destroy(struct obs_data *data) { - struct obs_data_item *item = data->first_item; + struct obs_data_item *item, *temp; - while (item) { - struct obs_data_item *next = item->next; + HASH_ITER (hh, data->items, item, temp) { + obs_data_item_detach(item); obs_data_item_release(&item); - item = next; } /* NOTE: don't use bfree for json text, allocated by json */ @@ -824,9 +809,9 @@ obs_data_t *obs_data_get_defaults(obs_data_t *data) if (!data) return defaults; - struct obs_data_item *item = data->first_item; + struct obs_data_item *item, *temp; - while (item) { + HASH_ITER (hh, data->items, item, temp) { const char *name = get_item_name(item); switch (item->type) { case OBS_DATA_NULL: @@ -892,8 +877,6 @@ obs_data_t *obs_data_get_defaults(obs_data_t *data) break; } } - - item = item->next; } return defaults; @@ -904,16 +887,9 @@ static struct obs_data_item *get_item(struct obs_data *data, const char *name) if (!data) return NULL; - struct obs_data_item *item = data->first_item; - - while (item) { - if (strcmp(get_item_name(item), name) == 0) - return item; - - item = item->next; - } - - return NULL; + struct obs_data_item *item; + HASH_FIND_STR(data->items, name, item); + return item; } static void set_item_data(struct obs_data *data, struct obs_data_item **item, @@ -926,31 +902,8 @@ static void set_item_data(struct obs_data *data, struct obs_data_item **item, if ((!item || !*item) && data) { new_item = obs_data_item_create(name, ptr, size, type, default_data, autoselect_data); - - obs_data_item_t *prev = obs_data_first(data); - obs_data_item_t *next = obs_data_first(data); - obs_data_item_next(&next); - for (; prev && next; - obs_data_item_next(&prev), obs_data_item_next(&next)) { - if (strcmp(get_item_name(next), name) > 0) - break; - } - new_item->parent = data; - if (prev && strcmp(get_item_name(prev), name) < 0) { - prev->next = new_item; - new_item->next = next; - - } else { - data->first_item = new_item; - new_item->next = prev; - } - - if (!prev) - data->first_item = new_item; - - obs_data_item_release(&prev); - obs_data_item_release(&next); + HASH_ADD_STR(data->items, name, new_item); } else if (default_data) { obs_data_item_set_default_data(item, ptr, size, type); @@ -1077,16 +1030,13 @@ static inline void copy_item(struct obs_data *data, struct obs_data_item *item) void obs_data_apply(obs_data_t *target, obs_data_t *apply_data) { - struct obs_data_item *item; - if (!target || !apply_data || target == apply_data) return; - item = apply_data->first_item; + struct obs_data_item *item, *temp; - while (item) { + HASH_ITER (hh, apply_data->items, item, temp) { copy_item(target, item); - item = item->next; } } @@ -1130,16 +1080,12 @@ static inline void clear_item(struct obs_data_item *item) void obs_data_clear(obs_data_t *target) { - struct obs_data_item *item; - if (!target) return; - item = target->first_item; - - while (item) { + struct obs_data_item *item, *temp; + HASH_ITER (hh, target->items, item, temp) { clear_item(item); - item = item->next; } } @@ -1600,9 +1546,9 @@ obs_data_item_t *obs_data_first(obs_data_t *data) if (!data) return NULL; - if (data->first_item) - os_atomic_inc_long(&data->first_item->ref); - return data->first_item; + if (data->items) + os_atomic_inc_long(&data->items->ref); + return data->items; } obs_data_item_t *obs_data_item_byname(obs_data_t *data, const char *name) @@ -1619,7 +1565,7 @@ obs_data_item_t *obs_data_item_byname(obs_data_t *data, const char *name) bool obs_data_item_next(obs_data_item_t **item) { if (item && *item) { - obs_data_item_t *next = (*item)->next; + obs_data_item_t *next = (*item)->hh.next; obs_data_item_release(item); *item = next; From e720bb4c090a7a4a21bf871a03754d436648692f Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 8 Feb 2023 12:06:55 +0100 Subject: [PATCH 06/14] libobs: Use uthash for config --- libobs/util/config-file.c | 232 +++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 129 deletions(-) diff --git a/libobs/util/config-file.c b/libobs/util/config-file.c index c64304ce5..8a844147a 100644 --- a/libobs/util/config-file.c +++ b/libobs/util/config-file.c @@ -17,47 +17,52 @@ #include #include #include + #include "config-file.h" #include "threading.h" #include "platform.h" #include "base.h" #include "bmem.h" -#include "darray.h" #include "lexer.h" #include "dstr.h" +#include "uthash.h" struct config_item { char *name; char *value; + UT_hash_handle hh; }; static inline void config_item_free(struct config_item *item) { bfree(item->name); bfree(item->value); + bfree(item); } struct config_section { char *name; - struct darray items; /* struct config_item */ + struct config_item *items; + UT_hash_handle hh; }; static inline void config_section_free(struct config_section *section) { - struct config_item *items = section->items.array; - size_t i; + struct config_item *item; + struct config_item *temp; + HASH_ITER (hh, section->items, item, temp) { + HASH_DELETE(hh, section->items, item); + config_item_free(item); + } - for (i = 0; i < section->items.num; i++) - config_item_free(items + i); - - darray_free(§ion->items); bfree(section->name); + bfree(section); } struct config_data { char *file; - struct darray sections; /* struct config_section */ - struct darray defaults; /* struct config_section */ + struct config_section *sections; + struct config_section *defaults; pthread_mutex_t mutex; }; @@ -152,18 +157,25 @@ static void unescape(struct dstr *str) *write = '\0'; } -static void config_add_item(struct darray *items, struct strref *name, +static void config_add_item(struct config_item **items, struct strref *name, struct strref *value) { - struct config_item item; + struct config_item *item; struct dstr item_value; - dstr_init_copy_strref(&item_value, value); - unescape(&item_value); + item = bzalloc(sizeof(struct config_item)); + item->name = bstrdup_n(name->array, name->len); - item.name = bstrdup_n(name->array, name->len); - item.value = item_value.array; - darray_push_back(sizeof(struct config_item), items, &item); + if (!strref_is_empty(value)) { + dstr_init_copy_strref(&item_value, value); + unescape(&item_value); + item->value = bstrdup_n(item_value.array, item_value.len); + dstr_free(&item_value); + } else { + item->value = bzalloc(1); + } + + HASH_ADD_STR(*items, name, item); } static void config_parse_section(struct config_section *section, @@ -201,20 +213,12 @@ static void config_parse_section(struct config_section *section, strref_clear(&value); config_parse_string(lex, &value, 0); - - if (strref_is_empty(&value)) { - struct config_item item; - item.name = bstrdup_n(name.array, name.len); - item.value = bzalloc(1); - darray_push_back(sizeof(struct config_item), - §ion->items, &item); - } else { - config_add_item(§ion->items, &name, &value); - } + config_add_item(§ion->items, &name, &value); } } -static void parse_config_data(struct darray *sections, struct lexer *lex) +static void parse_config_data(struct config_section **sections, + struct lexer *lex) { struct strref section_name; struct base_token token; @@ -244,14 +248,16 @@ static void parse_config_data(struct darray *sections, struct lexer *lex) if (!section_name.len) return; - section = darray_push_back_new(sizeof(struct config_section), - sections); + section = bzalloc(sizeof(struct config_section)); section->name = bstrdup_n(section_name.array, section_name.len); + config_parse_section(section, lex); + + HASH_ADD_STR(*sections, name, section); } } -static int config_parse_file(struct darray *sections, const char *file, +static int config_parse_file(struct config_section **sections, const char *file, bool always_open) { char *file_data; @@ -347,7 +353,6 @@ int config_save(config_t *config) { FILE *f; struct dstr str, tmp; - size_t i, j; int ret = CONFIG_ERROR; if (!config) @@ -366,21 +371,19 @@ int config_save(config_t *config) return CONFIG_FILENOTFOUND; } - for (i = 0; i < config->sections.num; i++) { - struct config_section *section = darray_item( - sizeof(struct config_section), &config->sections, i); + struct config_section *section, *stmp; + struct config_item *item, *itmp; - if (i) + int idx = 0; + HASH_ITER (hh, config->sections, section, stmp) { + if (idx++) dstr_cat(&str, "\n"); dstr_cat(&str, "["); dstr_cat(&str, section->name); dstr_cat(&str, "]\n"); - for (j = 0; j < section->items.num; j++) { - struct config_item *item = darray_item( - sizeof(struct config_item), §ion->items, j); - + HASH_ITER (hh, section->items, item, itmp) { dstr_copy(&tmp, item->value ? item->value : ""); dstr_replace(&tmp, "\\", "\\\\"); dstr_replace(&tmp, "\r", "\\r"); @@ -465,22 +468,21 @@ cleanup: void config_close(config_t *config) { - struct config_section *defaults, *sections; - size_t i; + struct config_section *section, *temp; if (!config) return; - defaults = config->defaults.array; - sections = config->sections.array; + HASH_ITER (hh, config->sections, section, temp) { + HASH_DELETE(hh, config->sections, section); + config_section_free(section); + } - for (i = 0; i < config->defaults.num; i++) - config_section_free(defaults + i); - for (i = 0; i < config->sections.num; i++) - config_section_free(sections + i); + HASH_ITER (hh, config->defaults, section, temp) { + HASH_DELETE(hh, config->defaults, section); + config_section_free(section); + } - darray_free(&config->defaults); - darray_free(&config->sections); bfree(config->file); pthread_mutex_destroy(&config->mutex); bfree(config); @@ -488,94 +490,77 @@ void config_close(config_t *config) size_t config_num_sections(config_t *config) { - return config->sections.num; + return HASH_CNT(hh, config->sections); } const char *config_get_section(config_t *config, size_t idx) { struct config_section *section; + struct config_section *temp; const char *name = NULL; + size_t ctr = 0; pthread_mutex_lock(&config->mutex); - if (idx >= config->sections.num) + if (idx >= config_num_sections(config)) goto unlock; - section = darray_item(sizeof(struct config_section), &config->sections, - idx); - name = section->name; + HASH_ITER (hh, config->sections, section, temp) { + if (idx == ctr++) { + name = section->name; + break; + } + } unlock: pthread_mutex_unlock(&config->mutex); return name; } -static const struct config_item *config_find_item(const struct darray *sections, - const char *section, - const char *name) +static const struct config_item * +config_find_item(const struct config_section *sections, const char *section, + const char *name) { - size_t i, j; + struct config_section *sec; + struct config_item *res; - for (i = 0; i < sections->num; i++) { - const struct config_section *sec = - darray_item(sizeof(struct config_section), sections, i); + HASH_FIND_STR(sections, section, sec); + if (!sec) + return NULL; - if (astrcmpi(sec->name, section) == 0) { - for (j = 0; j < sec->items.num; j++) { - struct config_item *item = - darray_item(sizeof(struct config_item), - &sec->items, j); + HASH_FIND_STR(sec->items, name, res); - if (astrcmpi(item->name, name) == 0) - return item; - } - } - } - - return NULL; + return res; } -static void config_set_item(config_t *config, struct darray *sections, +static void config_set_item(config_t *config, struct config_section **sections, const char *section, const char *name, char *value) { - struct config_section *sec = NULL; - struct config_section *array = sections->array; + struct config_section *sec; struct config_item *item; - size_t i, j; pthread_mutex_lock(&config->mutex); - for (i = 0; i < sections->num; i++) { - struct config_section *cur_sec = array + i; - struct config_item *items = cur_sec->items.array; - - if (astrcmpi(cur_sec->name, section) == 0) { - for (j = 0; j < cur_sec->items.num; j++) { - item = items + j; - - if (astrcmpi(item->name, name) == 0) { - bfree(item->value); - item->value = value; - goto unlock; - } - } - - sec = cur_sec; - break; - } - } - + HASH_FIND_STR(*sections, section, sec); if (!sec) { - sec = darray_push_back_new(sizeof(struct config_section), - sections); + sec = bzalloc(sizeof(struct config_section)); sec->name = bstrdup(section); + + HASH_ADD_STR(*sections, name, sec); } - item = darray_push_back_new(sizeof(struct config_item), &sec->items); - item->name = bstrdup(name); - item->value = value; + HASH_FIND_STR(sec->items, name, item); + if (!item) { + item = bzalloc(sizeof(struct config_item)); + item->name = bstrdup(name); + item->value = value; + + HASH_ADD_STR(sec->items, name, item); + } else { + bfree(item->value); + item->value = value; + } -unlock: pthread_mutex_unlock(&config->mutex); } @@ -681,9 +666,9 @@ const char *config_get_string(config_t *config, const char *section, pthread_mutex_lock(&config->mutex); - item = config_find_item(&config->sections, section, name); + item = config_find_item(config->sections, section, name); if (!item) - item = config_find_item(&config->defaults, section, name); + item = config_find_item(config->defaults, section, name); if (item) value = item->value; @@ -754,33 +739,22 @@ double config_get_double(config_t *config, const char *section, bool config_remove_value(config_t *config, const char *section, const char *name) { - struct darray *sections = &config->sections; + struct config_section *sec; + struct config_item *item; bool success = false; pthread_mutex_lock(&config->mutex); - for (size_t i = 0; i < sections->num; i++) { - struct config_section *sec = - darray_item(sizeof(struct config_section), sections, i); - - if (astrcmpi(sec->name, section) != 0) - continue; - - for (size_t j = 0; j < sec->items.num; j++) { - struct config_item *item = darray_item( - sizeof(struct config_item), &sec->items, j); - - if (astrcmpi(item->name, name) == 0) { - config_item_free(item); - darray_erase(sizeof(struct config_item), - &sec->items, j); - success = true; - goto unlock; - } + HASH_FIND_STR(config->sections, section, sec); + if (sec) { + HASH_FIND_STR(sec->items, name, item); + if (item) { + HASH_DELETE(hh, sec->items, item); + config_item_free(item); + success = true; } } -unlock: pthread_mutex_unlock(&config->mutex); return success; } @@ -793,7 +767,7 @@ const char *config_get_default_string(config_t *config, const char *section, pthread_mutex_lock(&config->mutex); - item = config_find_item(&config->defaults, section, name); + item = config_find_item(config->defaults, section, name); if (item) value = item->value; @@ -846,7 +820,7 @@ bool config_has_user_value(config_t *config, const char *section, { bool success; pthread_mutex_lock(&config->mutex); - success = config_find_item(&config->sections, section, name) != NULL; + success = config_find_item(config->sections, section, name) != NULL; pthread_mutex_unlock(&config->mutex); return success; } @@ -856,7 +830,7 @@ bool config_has_default_value(config_t *config, const char *section, { bool success; pthread_mutex_lock(&config->mutex); - success = config_find_item(&config->defaults, section, name) != NULL; + success = config_find_item(config->defaults, section, name) != NULL; pthread_mutex_unlock(&config->mutex); return success; } From 183fa8c06c9b269f1ea323cf33981e8fb75f252f Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 8 Feb 2023 12:07:11 +0100 Subject: [PATCH 07/14] libobs: Use uthash for translation lookup --- libobs/util/text-lookup.c | 200 ++++++-------------------------------- libobs/util/text-lookup.h | 4 +- 2 files changed, 33 insertions(+), 171 deletions(-) diff --git a/libobs/util/text-lookup.c b/libobs/util/text-lookup.c index 6fc144f78..7a74fb928 100644 --- a/libobs/util/text-lookup.c +++ b/libobs/util/text-lookup.c @@ -15,166 +15,34 @@ */ #include + #include "dstr.h" #include "text-lookup.h" #include "lexer.h" #include "platform.h" +#include "uthash.h" /* ------------------------------------------------------------------------- */ -struct text_leaf { +struct text_item { char *lookup, *value; + UT_hash_handle hh; }; -static inline void text_leaf_destroy(struct text_leaf *leaf) +static inline void text_item_destroy(struct text_item *item) { - if (leaf) { - bfree(leaf->lookup); - bfree(leaf->value); - bfree(leaf); - } -} - -/* ------------------------------------------------------------------------- */ - -struct text_node { - struct dstr str; - struct text_node *first_subnode; - struct text_leaf *leaf; - - struct text_node *next; -}; - -static void text_node_destroy(struct text_node *node) -{ - struct text_node *subnode; - - if (!node) - return; - - subnode = node->first_subnode; - while (subnode) { - struct text_node *destroy_node = subnode; - - subnode = subnode->next; - text_node_destroy(destroy_node); - } - - dstr_free(&node->str); - if (node->leaf) - text_leaf_destroy(node->leaf); - bfree(node); -} - -static struct text_node *text_node_bychar(struct text_node *node, char ch) -{ - struct text_node *subnode = node->first_subnode; - - while (subnode) { - if (!dstr_is_empty(&subnode->str) && - subnode->str.array[0] == ch) - return subnode; - - subnode = subnode->next; - } - - return NULL; -} - -static struct text_node *text_node_byname(struct text_node *node, - const char *name) -{ - struct text_node *subnode = node->first_subnode; - - while (subnode) { - if (astrcmpi_n(subnode->str.array, name, subnode->str.len) == 0) - return subnode; - - subnode = subnode->next; - } - - return NULL; + bfree(item->lookup); + bfree(item->value); + bfree(item); } /* ------------------------------------------------------------------------- */ struct text_lookup { struct dstr language; - struct text_node *top; + struct text_item *items; }; -static void lookup_createsubnode(const char *lookup_val, struct text_leaf *leaf, - struct text_node *node) -{ - struct text_node *new = bzalloc(sizeof(struct text_node)); - new->leaf = leaf; - new->next = node->first_subnode; - dstr_copy(&new->str, lookup_val); - - node->first_subnode = new; -} - -static void lookup_splitnode(const char *lookup_val, size_t len, - struct text_leaf *leaf, struct text_node *node) -{ - struct text_node *split = bzalloc(sizeof(struct text_node)); - - dstr_copy(&split->str, node->str.array + len); - split->leaf = node->leaf; - split->first_subnode = node->first_subnode; - node->first_subnode = split; - - dstr_resize(&node->str, len); - - if (lookup_val[len] != 0) { - node->leaf = NULL; - lookup_createsubnode(lookup_val + len, leaf, node); - } else { - node->leaf = leaf; - } -} - -static inline void lookup_replaceleaf(struct text_node *node, - struct text_leaf *leaf) -{ - text_leaf_destroy(node->leaf); - node->leaf = leaf; -} - -static void lookup_addstring(const char *lookup_val, struct text_leaf *leaf, - struct text_node *node) -{ - struct text_node *child; - - /* value already exists, so replace */ - if (!lookup_val || !*lookup_val) { - lookup_replaceleaf(node, leaf); - return; - } - - child = text_node_bychar(node, *lookup_val); - if (child) { - size_t len; - - for (len = 0; len < child->str.len; len++) { - char val1 = child->str.array[len], - val2 = lookup_val[len]; - - if (val1 != val2) - break; - } - - if (len == child->str.len) { - lookup_addstring(lookup_val + len, leaf, child); - return; - } else { - lookup_splitnode(lookup_val, len, leaf, child); - } - } else { - lookup_createsubnode(lookup_val, leaf, node); - } -} - static void lookup_getstringtoken(struct lexer *lex, struct strref *token) { const char *temp = lex->offset; @@ -300,7 +168,8 @@ static void lookup_addfiledata(struct text_lookup *lookup, strref_clear(&value); while (lookup_gettoken(&lex, &name)) { - struct text_leaf *leaf; + struct text_item *item; + struct text_item *old; bool got_eq = false; if (*name.array == '\n') @@ -315,14 +184,14 @@ static void lookup_addfiledata(struct text_lookup *lookup, goto getval; } - leaf = bmalloc(sizeof(struct text_leaf)); - leaf->lookup = bstrdup_n(name.array, name.len); - leaf->value = convert_string(value.array, value.len); + item = bzalloc(sizeof(struct text_item)); + item->lookup = bstrdup_n(name.array, name.len); + item->value = convert_string(value.array, value.len); - for (size_t i = 0; i < name.len; i++) - leaf->lookup[i] = toupper(leaf->lookup[i]); + HASH_REPLACE_STR(lookup->items, lookup, item, old); - lookup_addstring(leaf->lookup, leaf, lookup->top); + if (old) + text_item_destroy(old); if (!lookup_goto_nextline(&lex)) break; @@ -332,27 +201,19 @@ static void lookup_addfiledata(struct text_lookup *lookup, } static inline bool lookup_getstring(const char *lookup_val, const char **out, - struct text_node *node) + struct text_lookup *lookup) { - struct text_node *child; - char ch; + struct text_item *item; - if (!node) + if (!lookup->items) return false; - child = text_node_byname(node, lookup_val); - if (!child) + HASH_FIND_STR(lookup->items, lookup_val, item); + + if (!item) return false; - lookup_val += child->str.len; - ch = *lookup_val; - if (ch) - return lookup_getstring(lookup_val, out, child); - - if (!child->leaf) - return false; - - *out = child->leaf->value; + *out = item->value; return true; } @@ -387,9 +248,6 @@ bool text_lookup_add(lookup_t *lookup, const char *path) if (!file_str.array) return false; - if (!lookup->top) - lookup->top = bzalloc(sizeof(struct text_node)); - dstr_replace(&file_str, "\r", " "); lookup_addfiledata(lookup, file_str.array); dstr_free(&file_str); @@ -400,9 +258,13 @@ bool text_lookup_add(lookup_t *lookup, const char *path) void text_lookup_destroy(lookup_t *lookup) { if (lookup) { - dstr_free(&lookup->language); - text_node_destroy(lookup->top); + struct text_item *item, *tmp; + HASH_ITER (hh, lookup->items, item, tmp) { + HASH_DELETE(hh, lookup->items, item); + text_item_destroy(item); + } + dstr_free(&lookup->language); bfree(lookup); } } @@ -411,6 +273,6 @@ bool text_lookup_getstr(lookup_t *lookup, const char *lookup_val, const char **out) { if (lookup) - return lookup_getstring(lookup_val, out, lookup->top); + return lookup_getstring(lookup_val, out, lookup); return false; } diff --git a/libobs/util/text-lookup.h b/libobs/util/text-lookup.h index f01161867..4042f75c6 100644 --- a/libobs/util/text-lookup.h +++ b/libobs/util/text-lookup.h @@ -20,8 +20,8 @@ * Text Lookup interface * * Used for storing and looking up localized strings. Stores localization - * strings in a radix/trie tree to efficiently look up associated strings via a - * unique string identifier name. + * strings in a hashmap to efficiently look up associated strings via a + * unique string identifier name. */ #include "c99defs.h" From c68eeaef1df1bc7587f46311d714c02da07fd2dc Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 9 Feb 2023 08:34:08 +0100 Subject: [PATCH 08/14] 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. --- docs/sphinx/reference-core.rst | 9 + libobs/obs-internal.h | 29 +++- libobs/obs-scene.c | 6 +- libobs/obs-source.c | 22 ++- libobs/obs-video.c | 4 +- libobs/obs.c | 303 ++++++++++++++++++++++++++++----- libobs/obs.h | 3 + 7 files changed, 322 insertions(+), 54 deletions(-) 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); From eff3cf134c284839f36b13c7d0f515a313995217 Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 9 Feb 2023 14:01:26 +0100 Subject: [PATCH 09/14] libobs: Use uthash for hotkey name map --- libobs/obs-hotkey-name-map.c | 346 ++++------------------------------- libobs/obs-internal.h | 4 +- 2 files changed, 35 insertions(+), 315 deletions(-) diff --git a/libobs/obs-hotkey-name-map.c b/libobs/obs-hotkey-name-map.c index a7e0683fa..6ef6b36ad 100644 --- a/libobs/obs-hotkey-name-map.c +++ b/libobs/obs-hotkey-name-map.c @@ -20,305 +20,55 @@ #include #include -#include #include "obs-internal.h" -struct obs_hotkey_name_map_edge; -struct obs_hotkey_name_map_node; struct obs_hotkey_name_map; -typedef struct obs_hotkey_name_map_edge obs_hotkey_name_map_edge_t; -typedef struct obs_hotkey_name_map_node obs_hotkey_name_map_node_t; -typedef struct obs_hotkey_name_map obs_hotkey_name_map_t; +typedef struct obs_hotkey_name_map_item obs_hotkey_name_map_item_t; -struct obs_hotkey_name_map_node { - bool is_leaf; - union { - int val; - DARRAY(obs_hotkey_name_map_edge_t) children; - }; +struct obs_hotkey_name_map_item { + char *key; + int val; + UT_hash_handle hh; }; -struct obs_hotkey_name_map { - obs_hotkey_name_map_node_t root; - obs_hotkey_name_map_node_t *leaves; - size_t num_leaves; - size_t next_leaf; -}; - -struct obs_hotkey_name_map_edge_prefix { - uint8_t prefix_len; - char *prefix; -}; - -#define NAME_MAP_COMPRESS_LENGTH \ - (sizeof(struct obs_hotkey_name_map_edge_prefix) - sizeof(uint8_t)) - -struct obs_hotkey_name_map_edge { - union { - struct { - uint8_t prefix_len; - char *prefix; - }; - struct { - uint8_t compressed_len; - char compressed_prefix[NAME_MAP_COMPRESS_LENGTH]; - }; - }; - struct obs_hotkey_name_map_node *node; -}; - -static inline obs_hotkey_name_map_node_t *new_node(void) -{ - return bzalloc(sizeof(obs_hotkey_name_map_node_t)); -} - -static inline obs_hotkey_name_map_node_t *new_leaf(void) -{ - obs_hotkey_name_map_t *name_map = obs->hotkeys.name_map; - obs_hotkey_name_map_node_t *node = - &name_map->leaves[name_map->next_leaf]; - name_map->next_leaf += 1; - - node->is_leaf = true; - return node; -} - -static inline char *get_prefix(obs_hotkey_name_map_edge_t *e) -{ - return e->prefix_len >= NAME_MAP_COMPRESS_LENGTH ? e->prefix - : e->compressed_prefix; -} - -static void set_prefix(obs_hotkey_name_map_edge_t *e, const char *prefix, - size_t l) -{ - assert(e->prefix_len == 0); - - e->compressed_len = (uint8_t)l; - if (l < NAME_MAP_COMPRESS_LENGTH) - strncpy(e->compressed_prefix, prefix, l); - else - e->prefix = bstrdup_n(prefix, l); -} - -static obs_hotkey_name_map_edge_t *add_leaf(obs_hotkey_name_map_node_t *node, - const char *key, size_t l, int v) -{ - obs_hotkey_name_map_edge_t *e = da_push_back_new(node->children); - - set_prefix(e, key, l); - - e->node = new_leaf(); - e->node->val = v; - - return e; -} - -static void shrink_prefix(obs_hotkey_name_map_edge_t *e, size_t l) -{ - bool old_comp = e->prefix_len < NAME_MAP_COMPRESS_LENGTH; - bool new_comp = l < NAME_MAP_COMPRESS_LENGTH; - - char *str = get_prefix(e); - - e->prefix_len = (uint8_t)l; - if (get_prefix(e) != str) - strncpy(get_prefix(e), str, l); - else - str[l] = 0; - - if (!old_comp && new_comp) - bfree(str); -} - -static void connect(obs_hotkey_name_map_edge_t *e, - obs_hotkey_name_map_node_t *n) -{ - e->node = n; -} - -static void reduce_edge(obs_hotkey_name_map_edge_t *e, const char *key, - size_t l, int v) -{ - const char *str = get_prefix(e), *str_ = key; - size_t common_length = 0; - while (*str == *str_) { - common_length += 1; - str += 1; - str_ += 1; - } - - obs_hotkey_name_map_node_t *new_node_ = new_node(); - obs_hotkey_name_map_edge_t *tail = - da_push_back_new(new_node_->children); - - connect(tail, e->node); - set_prefix(tail, str, e->prefix_len - common_length); - - add_leaf(new_node_, str_, l - common_length, v); - - connect(e, new_node_); - shrink_prefix(e, common_length); -} - -enum obs_hotkey_name_map_edge_compare_result { - RES_MATCHES, - RES_NO_MATCH, - RES_COMMON_PREFIX, - RES_PREFIX_MATCHES, -}; - -static enum obs_hotkey_name_map_edge_compare_result -compare_prefix(obs_hotkey_name_map_edge_t *edge, const char *key, size_t l) -{ - uint8_t pref_len = edge->prefix_len; - const char *str = get_prefix(edge); - size_t i = 0; - - for (; i < l && i < pref_len; i++) - if (str[i] != key[i]) - break; - - if (i != 0 && pref_len == i) - return l == i ? RES_MATCHES : RES_PREFIX_MATCHES; - if (i != 0) - return pref_len == i ? RES_PREFIX_MATCHES : RES_COMMON_PREFIX; - return RES_NO_MATCH; -} - -static void insert(obs_hotkey_name_map_edge_t *edge, - obs_hotkey_name_map_node_t *node, const char *key, size_t l, - int v) -{ - if (node->is_leaf && l > 0) { - obs_hotkey_name_map_node_t *new_node_ = new_node(); - connect(edge, new_node_); - - obs_hotkey_name_map_edge_t *edge = - da_push_back_new(new_node_->children); - connect(edge, node); - add_leaf(new_node_, key, l, v); - return; - } - - if (node->is_leaf && l == 0) { - node->val = v; - return; - } - - for (size_t i = 0; i < node->children.num; i++) { - obs_hotkey_name_map_edge_t *e = &node->children.array[i]; - - switch (compare_prefix(e, key, l)) { - case RES_NO_MATCH: - continue; - - case RES_MATCHES: - case RES_PREFIX_MATCHES: - insert(e, e->node, key + e->prefix_len, - l - e->prefix_len, v); - return; - - case RES_COMMON_PREFIX: - reduce_edge(e, key, l, v); - return; - } - } - - add_leaf(node, key, l, v); -} - -static void obs_hotkey_name_map_insert(obs_hotkey_name_map_t *trie, +static void obs_hotkey_name_map_insert(obs_hotkey_name_map_item_t **hmap, const char *key, int v) { - if (!trie || !key) + if (!hmap || !key) return; - insert(NULL, &trie->root, key, strlen(key), v); + obs_hotkey_name_map_item_t *t; + HASH_FIND_STR(*hmap, key, t); + if (t) + return; + + t = bzalloc(sizeof(obs_hotkey_name_map_item_t)); + t->key = bstrdup(key); + t->val = v; + + HASH_ADD_STR(*hmap, key, t); } -static bool obs_hotkey_name_map_lookup(obs_hotkey_name_map_t *trie, +static bool obs_hotkey_name_map_lookup(obs_hotkey_name_map_item_t *hmap, const char *key, int *v) { - if (!trie || !key) + if (!hmap || !key) return false; - size_t len = strlen(key); - obs_hotkey_name_map_node_t *n = &trie->root; + obs_hotkey_name_map_item_t *elem; - size_t i = 0; - for (; i < n->children.num;) { - obs_hotkey_name_map_edge_t *e = &n->children.array[i]; + HASH_FIND_STR(hmap, key, elem); - switch (compare_prefix(e, key, len)) { - case RES_NO_MATCH: - i++; - continue; - - case RES_COMMON_PREFIX: - return false; - - case RES_PREFIX_MATCHES: - key += e->prefix_len; - len -= e->prefix_len; - n = e->node; - i = 0; - continue; - - case RES_MATCHES: - n = e->node; - if (!n->is_leaf) { - for (size_t j = 0; j < n->children.num; j++) { - if (n->children.array[j].prefix_len) - continue; - - if (v) - *v = n->children.array[j] - .node->val; - return true; - } - return false; - } - - if (v) - *v = n->val; - return true; - } + if (elem) { + *v = elem->val; + return true; } return false; } -static void show_node(obs_hotkey_name_map_node_t *node, int in) -{ - if (node->is_leaf) { - printf(": % 3d\n", node->val); - return; - } - - printf("\n"); - for (int i = 0; i < in; i += 2) - printf("| "); - printf("%lu:\n", (unsigned long)node->children.num); - - for (size_t i = 0; i < node->children.num; i++) { - for (int i = 0; i < in; i += 2) - printf("| "); - printf("\\ "); - - obs_hotkey_name_map_edge_t *e = &node->children.array[i]; - printf("%s", get_prefix(e)); - show_node(e->node, in + 2); - } -} - -void trie_print_size(obs_hotkey_name_map_t *trie) -{ - show_node(&trie->root, 0); -} - static const char *obs_key_names[] = { #define OBS_HOTKEY(x) #x, #include "obs-hotkeys.h" @@ -350,17 +100,7 @@ static obs_key_t obs_key_from_name_fallback(const char *name) static void init_name_map(void) { - obs->hotkeys.name_map = bzalloc(sizeof(struct obs_hotkey_name_map)); - obs_hotkey_name_map_t *name_map = obs->hotkeys.name_map; - -#define OBS_HOTKEY(x) name_map->num_leaves += 1; -#include "obs-hotkeys.h" -#undef OBS_HOTKEY - - size_t size = sizeof(obs_hotkey_name_map_node_t) * name_map->num_leaves; - name_map->leaves = bzalloc(size); - -#define OBS_HOTKEY(x) obs_hotkey_name_map_insert(name_map, #x, x); +#define OBS_HOTKEY(x) obs_hotkey_name_map_insert(&obs->hotkeys.name_map, #x, x); #include "obs-hotkeys.h" #undef OBS_HOTKEY } @@ -380,37 +120,17 @@ obs_key_t obs_key_from_name(const char *name) return OBS_KEY_NONE; } -static void free_node(obs_hotkey_name_map_node_t *node, bool release); - -static void free_edge(obs_hotkey_name_map_edge_t *edge) -{ - free_node(edge->node, true); - - if (edge->prefix_len < NAME_MAP_COMPRESS_LENGTH) - return; - - bfree(get_prefix(edge)); -} - -static void free_node(obs_hotkey_name_map_node_t *node, bool release) -{ - if (!node->is_leaf) { - for (size_t i = 0; i < node->children.num; i++) - free_edge(&node->children.array[i]); - - da_free(node->children); - } - - if (release && !node->is_leaf) - bfree(node); -} - void obs_hotkey_name_map_free(void) { if (!obs || !obs->hotkeys.name_map) return; - free_node(&obs->hotkeys.name_map->root, false); - bfree(obs->hotkeys.name_map->leaves); - bfree(obs->hotkeys.name_map); + obs_hotkey_name_map_item_t *root = obs->hotkeys.name_map; + obs_hotkey_name_map_item_t *n, *tmp; + + HASH_ITER (hh, root, n, tmp) { + HASH_DEL(root, n); + bfree(n->key); + bfree(n); + } } diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index c5b86dd4c..82bebd495 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -193,7 +193,7 @@ struct obs_hotkey_binding { obs_hotkey_t *hotkey; }; -struct obs_hotkey_name_map; +struct obs_hotkey_name_map_item; void obs_hotkey_name_map_free(void); /* ------------------------------------------------------------------------- */ @@ -431,7 +431,7 @@ struct obs_core_hotkeys { obs_hotkeys_platform_t *platform_context; pthread_once_t name_map_init_token; - struct obs_hotkey_name_map *name_map; + struct obs_hotkey_name_map_item *name_map; signal_handler_t *signals; From 78aa4e03dca62c3e1439cae2eade1c6d247c8a78 Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 9 Feb 2023 16:15:41 +0100 Subject: [PATCH 10/14] libobs: Use uthash for properties --- libobs/obs-properties.c | 189 +++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 111 deletions(-) diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index 397d93416..ab683ed1d 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -191,16 +191,16 @@ struct obs_property { obs_property_modified_t modified; obs_property_modified2_t modified2; - struct obs_property *next; + UT_hash_handle hh; }; struct obs_properties { void *param; void (*destroy)(void *param); uint32_t flags; + uint32_t groups; - struct obs_property *first_property; - struct obs_property **last; + struct obs_property *properties; struct obs_property *parent; }; @@ -208,7 +208,6 @@ obs_properties_t *obs_properties_create(void) { struct obs_properties *props; props = bzalloc(sizeof(struct obs_properties)); - props->last = &props->first_property; return props; } @@ -277,15 +276,14 @@ static void obs_property_destroy(struct obs_property *property) void obs_properties_destroy(obs_properties_t *props) { if (props) { - struct obs_property *p = props->first_property; + struct obs_property *p, *tmp; if (props->destroy && props->param) props->destroy(props->param); - while (p) { - struct obs_property *next = p->next; + HASH_ITER (hh, props->properties, p, tmp) { + HASH_DEL(props->properties, p); obs_property_destroy(p); - p = next; } bfree(props); @@ -294,31 +292,32 @@ void obs_properties_destroy(obs_properties_t *props) obs_property_t *obs_properties_first(obs_properties_t *props) { - return (props != NULL) ? props->first_property : NULL; + return (props != NULL) ? props->properties : NULL; } obs_property_t *obs_properties_get(obs_properties_t *props, const char *name) { - struct obs_property *property; + struct obs_property *property, *tmp; if (!props) return NULL; - property = props->first_property; - while (property) { - if (strcmp(property->name, name) == 0) - return property; + HASH_FIND_STR(props->properties, name, property); + if (property) + return property; - if (property->type == OBS_PROPERTY_GROUP) { - obs_properties_t *group = - obs_property_group_content(property); - obs_property_t *found = obs_properties_get(group, name); - if (found != NULL) { - return found; - } - } + if (!props->groups) + return NULL; - property = property->next; + /* Recursively check groups as well, if any */ + HASH_ITER (hh, props->properties, property, tmp) { + if (property->type != OBS_PROPERTY_GROUP) + continue; + + obs_properties_t *group = obs_property_group_content(property); + obs_property_t *found = obs_properties_get(group, name); + if (found) + return found; } return NULL; @@ -334,51 +333,29 @@ void obs_properties_remove_by_name(obs_properties_t *props, const char *name) if (!props) return; - /* obs_properties_t is a forward-linked-list, so we need to keep both - * previous and current pointers around. That way we can fix up the - * pointers for the previous element if we find a match. - */ - struct obs_property *cur = props->first_property; - struct obs_property *prev = props->first_property; + struct obs_property *cur, *tmp; - while (cur) { - if (strcmp(cur->name, name) == 0) { - // Fix props->last pointer. - if (props->last == &cur->next) { - if (cur == prev) { - // If we are the last entry and there - // is no previous entry, reset. - props->last = &props->first_property; - } else { - // If we are the last entry and there - // is a previous entry, update. - props->last = &prev->next; - } - } + HASH_FIND_STR(props->properties, name, cur); - // Fix props->first_property. - if (props->first_property == cur) - props->first_property = cur->next; + if (cur) { + HASH_DELETE(hh, props->properties, cur); - // Update the previous element next pointer with our - // next pointer. This is an automatic no-op if both - // elements alias the same memory. - prev->next = cur->next; + if (cur->type == OBS_PROPERTY_GROUP) + props->groups--; - // Finally clear our own next pointer and destroy. - cur->next = NULL; - obs_property_destroy(cur); + obs_property_destroy(cur); + return; + } - break; - } + if (!props->groups) + return; - if (cur->type == OBS_PROPERTY_GROUP) { - obs_properties_remove_by_name( - obs_property_group_content(cur), name); - } + HASH_ITER (hh, props->properties, cur, tmp) { + if (cur->type != OBS_PROPERTY_GROUP) + continue; - prev = cur; - cur = cur->next; + obs_properties_remove_by_name(obs_property_group_content(cur), + name); } } @@ -386,10 +363,9 @@ void obs_properties_apply_settings_internal(obs_properties_t *props, obs_data_t *settings, obs_properties_t *realprops) { - struct obs_property *p; + struct obs_property *p, *tmp; - p = props->first_property; - while (p) { + HASH_ITER (hh, props->properties, p, tmp) { if (p->type == OBS_PROPERTY_GROUP) { obs_properties_apply_settings_internal( obs_property_group_content(p), settings, @@ -399,7 +375,6 @@ void obs_properties_apply_settings_internal(obs_properties_t *props, p->modified(realprops, p, settings); else if (p->modified2) p->modified2(p->priv, realprops, p, settings); - p = p->next; } } @@ -414,13 +389,6 @@ void obs_properties_apply_settings(obs_properties_t *props, /* ------------------------------------------------------------------------- */ -static inline void propertes_add(struct obs_properties *props, - struct obs_property *p) -{ - *props->last = p; - props->last = &p->next; -} - static inline size_t get_property_size(enum obs_property_type type) { switch (type) { @@ -471,7 +439,8 @@ static inline struct obs_property *new_prop(struct obs_properties *props, p->type = type; p->name = bstrdup(name); p->desc = bstrdup(desc); - propertes_add(props, p); + + HASH_ADD_STR(props->properties, name, p); return p; } @@ -489,22 +458,22 @@ static inline obs_properties_t *get_topmost_parent(obs_properties_t *props) static inline bool contains_prop(struct obs_properties *props, const char *name) { - struct obs_property *p = props->first_property; + struct obs_property *p, *tmp; + HASH_FIND_STR(props->properties, name, p); - while (p) { - if (strcmp(p->name, name) == 0) { - blog(LOG_WARNING, "Property '%s' exists", name); + if (p) { + blog(LOG_WARNING, "Property '%s' exists", name); + return true; + } + + if (!props->groups) + return false; + + HASH_ITER (hh, props->properties, p, tmp) { + if (p->type != OBS_PROPERTY_GROUP) + continue; + if (contains_prop(obs_property_group_content(p), name)) return true; - } - - if (p->type == OBS_PROPERTY_GROUP) { - if (contains_prop(obs_property_group_content(p), - name)) { - return true; - } - } - - p = p->next; } return false; @@ -753,24 +722,23 @@ static bool check_property_group_recursion(obs_properties_t *parent, obs_properties_t *group) { /* Scan the group for the parent. */ - obs_property_t *current_property = group->first_property; - while (current_property) { - if (current_property->type == OBS_PROPERTY_GROUP) { - obs_properties_t *cprops = - obs_property_group_content(current_property); - if (cprops == parent) { - /* Contains find_props */ - return true; - } else if (cprops == group) { - /* Contains self, shouldn't be possible but - * lets verify anyway. */ - return true; - } - if (check_property_group_recursion(parent, cprops)) - return true; - } + obs_property_t *p, *tmp; - current_property = current_property->next; + HASH_ITER (hh, group->properties, p, tmp) { + if (p->type != OBS_PROPERTY_GROUP) + continue; + + obs_properties_t *cprops = obs_property_group_content(p); + if (cprops == parent) { + /* Contains find_props */ + return true; + } else if (cprops == group) { + /* Contains self, shouldn't be possible but + * lets verify anyway. */ + return true; + } + if (check_property_group_recursion(parent, cprops)) + return true; } return false; @@ -779,13 +747,11 @@ static bool check_property_group_recursion(obs_properties_t *parent, static bool check_property_group_duplicates(obs_properties_t *parent, obs_properties_t *group) { - obs_property_t *current_property = group->first_property; - while (current_property) { - if (has_prop(parent, current_property->name)) { - return true; - } + obs_property_t *p, *tmp; - current_property = current_property->next; + HASH_ITER (hh, group->properties, p, tmp) { + if (has_prop(parent, p->name)) + return true; } return false; @@ -812,6 +778,7 @@ obs_property_t *obs_properties_add_group(obs_properties_t *props, return NULL; obs_property_t *p = new_prop(props, name, desc, OBS_PROPERTY_GROUP); + props->groups++; group->parent = p; struct group_data *data = get_property_data(p); @@ -849,7 +816,7 @@ bool obs_property_next(obs_property_t **p) if (!p || !*p) return false; - *p = (*p)->next; + *p = (*p)->hh.next; return *p != NULL; } From e6a7cff03471f6972968dc3a1cb1a340525e456d Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 11 Feb 2023 21:22:41 +0100 Subject: [PATCH 11/14] libobs: Use uthash for hotkeys and hotkey pairs --- libobs/obs-hotkey.c | 410 ++++++++++++++---------------------------- libobs/obs-internal.h | 8 +- libobs/obs.c | 3 +- 3 files changed, 141 insertions(+), 280 deletions(-) diff --git a/libobs/obs-hotkey.c b/libobs/obs-hotkey.c index 0ad318436..d26c84dab 100644 --- a/libobs/obs-hotkey.c +++ b/libobs/obs-hotkey.c @@ -19,6 +19,18 @@ #include "obs-internal.h" +/* Since ids are just sequential size_t integers, we don't really need a + * hash function to get an even distribution across buckets. + * (Realistically this should never wrap, who has 4.29 billion hotkeys?!) */ +#undef HASH_FUNCTION +#define HASH_FUNCTION(s, len, hashv) (hashv) = *s % UINT_MAX + +/* Custom definitions to make adding/looking up size_t integers easier */ +#define HASH_ADD_HKEY(head, idfield, add) \ + HASH_ADD(hh, head, idfield, sizeof(size_t), add) +#define HASH_FIND_HKEY(head, id, out) \ + HASH_FIND(hh, head, &(id), sizeof(size_t), out) + static inline bool lock(void) { if (!obs) @@ -79,60 +91,52 @@ obs_hotkey_t *obs_hotkey_binding_get_hotkey(obs_hotkey_binding_t *binding) return binding->hotkey; } -static inline bool find_id(obs_hotkey_id id, size_t *idx); void obs_hotkey_set_name(obs_hotkey_id id, const char *name) { - size_t idx; - - if (!find_id(id, &idx)) + obs_hotkey_t *hotkey; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, id, hotkey); + if (!hotkey) return; - obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx]; bfree(hotkey->name); hotkey->name = bstrdup(name); } void obs_hotkey_set_description(obs_hotkey_id id, const char *desc) { - size_t idx; - - if (!find_id(id, &idx)) + obs_hotkey_t *hotkey; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, id, hotkey); + if (!hotkey) return; - obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx]; bfree(hotkey->description); hotkey->description = bstrdup(desc); } -static inline bool find_pair_id(obs_hotkey_pair_id id, size_t *idx); void obs_hotkey_pair_set_names(obs_hotkey_pair_id id, const char *name0, const char *name1) { - size_t idx; - obs_hotkey_pair_t pair; + obs_hotkey_pair_t *pair; - if (!find_pair_id(id, &idx)) + HASH_FIND_HKEY(obs->hotkeys.hotkey_pairs, id, pair); + if (!pair) return; - pair = obs->hotkeys.hotkey_pairs.array[idx]; - - obs_hotkey_set_name(pair.id[0], name0); - obs_hotkey_set_name(pair.id[1], name1); + obs_hotkey_set_name(pair->id[0], name0); + obs_hotkey_set_name(pair->id[1], name1); } void obs_hotkey_pair_set_descriptions(obs_hotkey_pair_id id, const char *desc0, const char *desc1) { - size_t idx; - obs_hotkey_pair_t pair; + obs_hotkey_pair_t *pair; - if (!find_pair_id(id, &idx)) + HASH_FIND_HKEY(obs->hotkeys.hotkey_pairs, id, pair); + if (!pair) return; - pair = obs->hotkeys.hotkey_pairs.array[idx]; - - obs_hotkey_set_description(pair.id[0], desc0); - obs_hotkey_set_description(pair.id[1], desc1); + obs_hotkey_set_description(pair->id[0], desc0); + obs_hotkey_set_description(pair->id[1], desc1); } static void hotkey_signal(const char *signal, obs_hotkey_t *hotkey) @@ -146,7 +150,6 @@ static void hotkey_signal(const char *signal, obs_hotkey_t *hotkey) calldata_free(&data); } -static inline void fixup_pointers(void); static inline void load_bindings(obs_hotkey_t *hotkey, obs_data_array_t *data); static inline void context_add_hotkey(struct obs_context_data *context, @@ -164,9 +167,8 @@ obs_hotkey_register_internal(obs_hotkey_registerer_t type, void *registerer, if ((obs->hotkeys.next_id + 1) == OBS_INVALID_HOTKEY_ID) blog(LOG_WARNING, "obs-hotkey: Available hotkey ids exhausted"); - obs_hotkey_t *base_addr = obs->hotkeys.hotkeys.array; obs_hotkey_id result = obs->hotkeys.next_id++; - obs_hotkey_t *hotkey = da_push_back_new(obs->hotkeys.hotkeys); + obs_hotkey_t *hotkey = bzalloc(sizeof(obs_hotkey_t)); hotkey->id = result; hotkey->name = bstrdup(name); @@ -177,6 +179,8 @@ obs_hotkey_register_internal(obs_hotkey_registerer_t type, void *registerer, hotkey->registerer = registerer; hotkey->pair_partner_id = OBS_INVALID_HOTKEY_PAIR_ID; + HASH_ADD_HKEY(obs->hotkeys.hotkeys, id, hotkey); + if (context) { obs_data_array_t *data = obs_data_get_array(context->hotkey_data, name); @@ -186,9 +190,6 @@ obs_hotkey_register_internal(obs_hotkey_registerer_t type, void *registerer, context_add_hotkey(context, result); } - if (base_addr != obs->hotkeys.hotkeys.array) - fixup_pointers(); - hotkey_signal("hotkey_register", hotkey); return result; @@ -275,8 +276,6 @@ obs_hotkey_id obs_hotkey_register_source(obs_source_t *source, const char *name, return id; } -static inline void fixup_pair_pointers(void); - static obs_hotkey_pair_t *create_hotkey_pair(struct obs_context_data *context, obs_hotkey_active_func func0, obs_hotkey_active_func func1, @@ -286,8 +285,7 @@ static obs_hotkey_pair_t *create_hotkey_pair(struct obs_context_data *context, blog(LOG_WARNING, "obs-hotkey: Available hotkey pair ids " "exhausted"); - obs_hotkey_pair_t *base_addr = obs->hotkeys.hotkey_pairs.array; - obs_hotkey_pair_t *pair = da_push_back_new(obs->hotkeys.hotkey_pairs); + obs_hotkey_pair_t *pair = bzalloc(sizeof(obs_hotkey_pair_t)); pair->pair_id = obs->hotkeys.next_pair_id++; pair->func[0] = func0; @@ -297,12 +295,11 @@ static obs_hotkey_pair_t *create_hotkey_pair(struct obs_context_data *context, pair->data[0] = data0; pair->data[1] = data1; + HASH_ADD_HKEY(obs->hotkeys.hotkey_pairs, pair_id, pair); + if (context) da_push_back(context->hotkey_pairs, &pair->pair_id); - if (base_addr != obs->hotkeys.hotkey_pairs.array) - fixup_pair_pointers(); - return pair; } @@ -336,7 +333,6 @@ static void obs_hotkey_pair_second_func(void *data, obs_hotkey_id id, pair->pressed1 = pressed; } -static inline bool find_id(obs_hotkey_id id, size_t *idx); static obs_hotkey_pair_id register_hotkey_pair_internal( obs_hotkey_registerer_t type, void *registerer, void *(*weak_ref)(void *), struct obs_context_data *context, @@ -361,12 +357,14 @@ static obs_hotkey_pair_id register_hotkey_pair_internal( obs_hotkey_pair_second_func, pair); - size_t idx; - if (find_id(pair->id[0], &idx)) - obs->hotkeys.hotkeys.array[idx].pair_partner_id = pair->id[1]; + obs_hotkey_t *hotkey_1, *hotkey_2; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, pair->id[0], hotkey_1); + HASH_FIND_HKEY(obs->hotkeys.hotkeys, pair->id[1], hotkey_2); - if (find_id(pair->id[1], &idx)) - obs->hotkeys.hotkeys.array[idx].pair_partner_id = pair->id[0]; + if (hotkey_1) + hotkey_1->pair_partner_id = pair->id[1]; + if (hotkey_2) + hotkey_2->pair_partner_id = pair->id[0]; obs_hotkey_pair_id id = pair->pair_id; @@ -470,34 +468,6 @@ obs_hotkey_pair_id obs_hotkey_pair_register_source( func0, func1, data0, data1); } -typedef bool (*obs_hotkey_internal_enum_func)(void *data, size_t idx, - obs_hotkey_t *hotkey); - -static inline void enum_hotkeys(obs_hotkey_internal_enum_func func, void *data) -{ - const size_t num = obs->hotkeys.hotkeys.num; - obs_hotkey_t *array = obs->hotkeys.hotkeys.array; - for (size_t i = 0; i < num; i++) { - if (!func(data, i, &array[i])) - break; - } -} - -typedef bool (*obs_hotkey_pair_internal_enum_func)(size_t idx, - obs_hotkey_pair_t *pair, - void *data); - -static inline void enum_hotkey_pairs(obs_hotkey_pair_internal_enum_func func, - void *data) -{ - const size_t num = obs->hotkeys.hotkey_pairs.num; - obs_hotkey_pair_t *array = obs->hotkeys.hotkey_pairs.array; - for (size_t i = 0; i < num; i++) { - if (!func(i, &array[i], data)) - break; - } -} - typedef bool (*obs_hotkey_binding_internal_enum_func)( void *data, size_t idx, obs_hotkey_binding_t *binding); @@ -512,102 +482,7 @@ static inline void enum_bindings(obs_hotkey_binding_internal_enum_func func, } } -struct obs_hotkey_internal_find_forward { - obs_hotkey_id id; - bool found; - size_t idx; -}; - -static inline bool find_id_helper(void *data, size_t idx, obs_hotkey_t *hotkey) -{ - struct obs_hotkey_internal_find_forward *find = data; - if (hotkey->id != find->id) - return true; - - find->idx = idx; - find->found = true; - return false; -} - -static inline bool find_id(obs_hotkey_id id, size_t *idx) -{ - struct obs_hotkey_internal_find_forward find = {id, false, 0}; - enum_hotkeys(find_id_helper, &find); - *idx = find.idx; - return find.found; -} - -static inline bool pointer_fixup_func(void *data, size_t idx, - obs_hotkey_binding_t *binding) -{ - UNUSED_PARAMETER(idx); - UNUSED_PARAMETER(data); - - size_t idx_; - if (!find_id(binding->hotkey_id, &idx_)) { - bcrash("obs-hotkey: Could not find hotkey id '%" PRIuMAX "' " - "for binding '%s' (modifiers 0x%x)", - (uintmax_t)binding->hotkey_id, - obs_key_to_name(binding->key.key), - binding->key.modifiers); - binding->hotkey = NULL; - return true; - } - - binding->hotkey = &obs->hotkeys.hotkeys.array[idx_]; - - return true; -} - -static inline void fixup_pointers(void) -{ - enum_bindings(pointer_fixup_func, NULL); -} - -struct obs_hotkey_internal_find_pair_forward { - obs_hotkey_pair_id id; - bool found; - size_t idx; -}; - -static inline bool find_pair_id_helper(size_t idx, obs_hotkey_pair_t *pair, - void *data) -{ - struct obs_hotkey_internal_find_pair_forward *find = data; - if (pair->pair_id != find->id) - return true; - - find->idx = idx; - find->found = true; - return false; -} - -static inline bool find_pair_id(obs_hotkey_pair_id id, size_t *idx) -{ - struct obs_hotkey_internal_find_pair_forward find = {id, false, 0}; - enum_hotkey_pairs(find_pair_id_helper, &find); - *idx = find.idx; - return find.found; -} - -static inline bool pair_pointer_fixup_func(size_t idx, obs_hotkey_pair_t *pair, - void *data) -{ - UNUSED_PARAMETER(data); - - if (find_id(pair->id[0], &idx)) - obs->hotkeys.hotkeys.array[idx].data = pair; - - if (find_id(pair->id[1], &idx)) - obs->hotkeys.hotkeys.array[idx].data = pair; - - return true; -} - -static inline void fixup_pair_pointers(void) -{ - enum_hotkey_pairs(pair_pointer_fixup_func, NULL); -} +typedef bool (*obs_hotkey_internal_enum_func)(void *data, obs_hotkey_t *hotkey); static inline void enum_context_hotkeys(struct obs_context_data *context, obs_hotkey_internal_enum_func func, @@ -615,13 +490,13 @@ static inline void enum_context_hotkeys(struct obs_context_data *context, { const size_t num = context->hotkeys.num; const obs_hotkey_id *array = context->hotkeys.array; - obs_hotkey_t *hotkey_array = obs->hotkeys.hotkeys.array; - for (size_t i = 0; i < num; i++) { - size_t idx; - if (!find_id(array[i], &idx)) - continue; + obs_hotkey_t *hotkey; - if (!func(data, idx, &hotkey_array[idx])) + for (size_t i = 0; i < num; i++) { + HASH_FIND_HKEY(obs->hotkeys.hotkeys, array[i], hotkey); + if (!hotkey) + continue; + if (!func(data, hotkey)) break; } } @@ -682,13 +557,12 @@ static inline void remove_bindings(obs_hotkey_id id); void obs_hotkey_load_bindings(obs_hotkey_id id, obs_key_combination_t *combinations, size_t num) { - size_t idx; - if (!lock()) return; - if (find_id(id, &idx)) { - obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx]; + obs_hotkey_t *hotkey; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, id, hotkey); + if (hotkey) { remove_bindings(id); for (size_t i = 0; i < num; i++) create_binding(hotkey, combinations[i]); @@ -700,23 +574,20 @@ void obs_hotkey_load_bindings(obs_hotkey_id id, void obs_hotkey_load(obs_hotkey_id id, obs_data_array_t *data) { - size_t idx; - if (!lock()) return; - if (find_id(id, &idx)) { + obs_hotkey_t *hotkey; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, id, hotkey); + if (hotkey) { remove_bindings(id); - load_bindings(&obs->hotkeys.hotkeys.array[idx], data); + load_bindings(hotkey, data); } unlock(); } -static inline bool enum_load_bindings(void *data, size_t idx, - obs_hotkey_t *hotkey) +static inline bool enum_load_bindings(void *data, obs_hotkey_t *hotkey) { - UNUSED_PARAMETER(idx); - obs_data_array_t *hotkey_data = obs_data_get_array(data, hotkey->name); if (!hotkey_data) return true; @@ -776,19 +647,22 @@ void obs_hotkey_pair_load(obs_hotkey_pair_id id, obs_data_array_t *data0, if ((!data0 && !data1) || !lock()) return; - size_t idx; - if (!find_pair_id(id, &idx)) + obs_hotkey_pair_t *pair; + HASH_FIND_HKEY(obs->hotkeys.hotkey_pairs, id, pair); + if (!pair) goto unlock; - obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx]; + obs_hotkey_t *p1, *p2; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, pair->id[0], p1); + HASH_FIND_HKEY(obs->hotkeys.hotkeys, pair->id[1], p2); - if (find_id(pair->id[0], &idx)) { + if (p1) { remove_bindings(pair->id[0]); - load_bindings(&obs->hotkeys.hotkeys.array[idx], data0); + load_bindings(p1, data0); } - if (find_id(pair->id[1], &idx)) { + if (p2) { remove_bindings(pair->id[1]); - load_bindings(&obs->hotkeys.hotkeys.array[idx], data1); + load_bindings(p2, data1); } unlock: @@ -845,14 +719,16 @@ static inline obs_data_array_t *save_hotkey(obs_hotkey_t *hotkey) obs_data_array_t *obs_hotkey_save(obs_hotkey_id id) { - size_t idx; obs_data_array_t *result = NULL; if (!lock()) return result; - if (find_id(id, &idx)) - result = save_hotkey(&obs->hotkeys.hotkeys.array[idx]); + obs_hotkey_t *hotkey; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, id, hotkey); + if (hotkey) + result = save_hotkey(hotkey); + unlock(); return result; @@ -864,28 +740,29 @@ void obs_hotkey_pair_save(obs_hotkey_pair_id id, obs_data_array_t **p_data0, if ((!p_data0 && !p_data1) || !lock()) return; - size_t idx; - if (!find_pair_id(id, &idx)) + obs_hotkey_pair_t *pair; + HASH_FIND_HKEY(obs->hotkeys.hotkey_pairs, id, pair); + if (!pair) goto unlock; - obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx]; - - if (p_data0 && find_id(pair->id[0], &idx)) { - *p_data0 = save_hotkey(&obs->hotkeys.hotkeys.array[idx]); + obs_hotkey_t *hotkey; + if (p_data0) { + HASH_FIND_HKEY(obs->hotkeys.hotkeys, pair->id[0], hotkey); + if (hotkey) + *p_data0 = save_hotkey(hotkey); } - if (p_data1 && find_id(pair->id[1], &idx)) { - *p_data1 = save_hotkey(&obs->hotkeys.hotkeys.array[idx]); + if (p_data1) { + HASH_FIND_HKEY(obs->hotkeys.hotkeys, pair->id[1], hotkey); + if (hotkey) + *p_data1 = save_hotkey(hotkey); } unlock: unlock(); } -static inline bool enum_save_hotkey(void *data, size_t idx, - obs_hotkey_t *hotkey) +static inline bool enum_save_hotkey(void *data, obs_hotkey_t *hotkey) { - UNUSED_PARAMETER(idx); - obs_data_array_t *hotkey_data = save_hotkey(hotkey); obs_data_set_array(data, hotkey->name, hotkey_data); obs_data_array_release(hotkey_data); @@ -1021,59 +898,55 @@ static void release_registerer(obs_hotkey_t *hotkey) hotkey->registerer = NULL; } -static inline bool unregister_hotkey(obs_hotkey_id id) +static inline void unregister_hotkey(obs_hotkey_id id) { if (id >= obs->hotkeys.next_id) - return false; + return; - size_t idx; - if (!find_id(id, &idx)) - return false; + obs_hotkey_t *hotkey; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, id, hotkey); + if (!hotkey) + return; - obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx]; + HASH_DEL(obs->hotkeys.hotkeys, hotkey); hotkey_signal("hotkey_unregister", hotkey); release_registerer(hotkey); - bfree(hotkey->name); - bfree(hotkey->description); - if (hotkey->registerer_type == OBS_HOTKEY_REGISTERER_SOURCE) obs_weak_source_release(hotkey->registerer); - da_erase(obs->hotkeys.hotkeys, idx); - remove_bindings(id); + bfree(hotkey->name); + bfree(hotkey->description); + bfree(hotkey); - return obs->hotkeys.hotkeys.num >= idx; + remove_bindings(id); } -static inline bool unregister_hotkey_pair(obs_hotkey_pair_id id) +static inline void unregister_hotkey_pair(obs_hotkey_pair_id id) { if (id >= obs->hotkeys.next_pair_id) - return false; + return; - size_t idx; - if (!find_pair_id(id, &idx)) - return false; + obs_hotkey_pair_t *pair; + HASH_FIND_HKEY(obs->hotkeys.hotkey_pairs, id, pair); + if (!pair) + return; - obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx]; + unregister_hotkey(pair->id[0]); + unregister_hotkey(pair->id[1]); - bool need_fixup = unregister_hotkey(pair->id[0]); - need_fixup = unregister_hotkey(pair->id[1]) || need_fixup; - if (need_fixup) - fixup_pointers(); - - da_erase(obs->hotkeys.hotkey_pairs, idx); - return obs->hotkeys.hotkey_pairs.num >= idx; + HASH_DEL(obs->hotkeys.hotkey_pairs, pair); + bfree(pair); } void obs_hotkey_unregister(obs_hotkey_id id) { if (!lock()) return; - if (unregister_hotkey(id)) - fixup_pointers(); + + unregister_hotkey(id); unlock(); } @@ -1082,9 +955,7 @@ void obs_hotkey_pair_unregister(obs_hotkey_pair_id id) if (!lock()) return; - if (unregister_hotkey_pair(id)) - fixup_pair_pointers(); - + unregister_hotkey_pair(id); unlock(); } @@ -1093,13 +964,8 @@ static void context_release_hotkeys(struct obs_context_data *context) if (!context->hotkeys.num) goto cleanup; - bool need_fixup = false; for (size_t i = 0; i < context->hotkeys.num; i++) - need_fixup = unregister_hotkey(context->hotkeys.array[i]) || - need_fixup; - - if (need_fixup) - fixup_pointers(); + unregister_hotkey(context->hotkeys.array[i]); cleanup: da_free(context->hotkeys); @@ -1110,14 +976,8 @@ static void context_release_hotkey_pairs(struct obs_context_data *context) if (!context->hotkey_pairs.num) goto cleanup; - bool need_fixup = false; for (size_t i = 0; i < context->hotkey_pairs.num; i++) - need_fixup = unregister_hotkey_pair( - context->hotkey_pairs.array[i]) || - need_fixup; - - if (need_fixup) - fixup_pair_pointers(); + unregister_hotkey_pair(context->hotkey_pairs.array[i]); cleanup: da_free(context->hotkey_pairs); @@ -1137,17 +997,22 @@ void obs_hotkeys_context_release(struct obs_context_data *context) void obs_hotkeys_free(void) { - const size_t num = obs->hotkeys.hotkeys.num; - obs_hotkey_t *hotkeys = obs->hotkeys.hotkeys.array; - for (size_t i = 0; i < num; i++) { - bfree(hotkeys[i].name); - bfree(hotkeys[i].description); - - release_registerer(&hotkeys[i]); + obs_hotkey_t *hotkey, *tmp; + HASH_ITER (hh, obs->hotkeys.hotkeys, hotkey, tmp) { + HASH_DEL(obs->hotkeys.hotkeys, hotkey); + bfree(hotkey->name); + bfree(hotkey->description); + release_registerer(hotkey); + bfree(hotkey); } + + obs_hotkey_pair_t *pair, *tmp2; + HASH_ITER (hh, obs->hotkeys.hotkey_pairs, pair, tmp2) { + HASH_DEL(obs->hotkeys.hotkey_pairs, pair); + bfree(pair); + } + da_free(obs->hotkeys.bindings); - da_free(obs->hotkeys.hotkeys); - da_free(obs->hotkeys.hotkey_pairs); for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { if (obs->hotkeys.translations[i]) { @@ -1157,26 +1022,17 @@ void obs_hotkeys_free(void) } } -struct obs_hotkey_internal_enum_forward { - obs_hotkey_enum_func func; - void *data; -}; - -static inline bool enum_hotkey(void *data, size_t idx, obs_hotkey_t *hotkey) -{ - UNUSED_PARAMETER(idx); - - struct obs_hotkey_internal_enum_forward *forward = data; - return forward->func(forward->data, hotkey->id, hotkey); -} - void obs_enum_hotkeys(obs_hotkey_enum_func func, void *data) { - struct obs_hotkey_internal_enum_forward forwarder = {func, data}; if (!lock()) return; - enum_hotkeys(enum_hotkey, &forwarder); + obs_hotkey_t *hk, *tmp; + HASH_ITER (hh, obs->hotkeys.hotkeys, hk, tmp) { + if (!func(data, hk->id, hk)) + break; + } + unlock(); } @@ -1409,11 +1265,11 @@ void obs_hotkey_trigger_routed_callback(obs_hotkey_id id, bool pressed) if (!obs->hotkeys.reroute_hotkeys) goto unlock; - size_t idx; - if (!find_id(id, &idx)) + obs_hotkey_t *hotkey; + HASH_FIND_HKEY(obs->hotkeys.hotkeys, id, hotkey); + if (!hotkey) goto unlock; - obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx]; hotkey->func(hotkey->data, id, hotkey, pressed); unlock: diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 82bebd495..f6a383550 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -154,6 +154,8 @@ struct obs_hotkey { void *registerer; obs_hotkey_id pair_partner_id; + + UT_hash_handle hh; }; struct obs_hotkey_pair { @@ -163,6 +165,8 @@ struct obs_hotkey_pair { bool pressed0; bool pressed1; void *data[2]; + + UT_hash_handle hh; }; typedef struct obs_hotkey_pair obs_hotkey_pair_t; @@ -412,9 +416,9 @@ struct obs_core_data { /* user hotkeys */ struct obs_core_hotkeys { pthread_mutex_t mutex; - DARRAY(obs_hotkey_t) hotkeys; + obs_hotkey_t *hotkeys; obs_hotkey_id next_id; - DARRAY(obs_hotkey_pair_t) hotkey_pairs; + obs_hotkey_pair_t *hotkey_pairs; obs_hotkey_pair_id next_pair_id; pthread_t hotkey_thread; diff --git a/libobs/obs.c b/libobs/obs.c index d2017ee27..cb17d5d88 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -1132,7 +1132,8 @@ static inline bool obs_init_hotkeys(void) assert(hotkeys != NULL); - da_init(hotkeys->hotkeys); + hotkeys->hotkeys = NULL; + hotkeys->hotkey_pairs = NULL; hotkeys->signals = obs->signals; hotkeys->name_map_init_token = obs_pthread_once_init_token; hotkeys->mute = bstrdup("Mute"); From a41c616b218bf75e2c4cb4d6a59117abf78e4adf Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 11 Feb 2023 21:38:21 +0100 Subject: [PATCH 12/14] libobs: Do not send hotkey_bindings_changed if nothing changed --- libobs/obs-hotkey.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libobs/obs-hotkey.c b/libobs/obs-hotkey.c index d26c84dab..48f47e102 100644 --- a/libobs/obs-hotkey.c +++ b/libobs/obs-hotkey.c @@ -549,10 +549,11 @@ static inline void load_bindings(obs_hotkey_t *hotkey, obs_data_array_t *data) obs_data_release(item); } - hotkey_signal("hotkey_bindings_changed", hotkey); + if (count) + hotkey_signal("hotkey_bindings_changed", hotkey); } -static inline void remove_bindings(obs_hotkey_id id); +static inline bool remove_bindings(obs_hotkey_id id); void obs_hotkey_load_bindings(obs_hotkey_id id, obs_key_combination_t *combinations, size_t num) @@ -563,12 +564,14 @@ void obs_hotkey_load_bindings(obs_hotkey_id id, obs_hotkey_t *hotkey; HASH_FIND_HKEY(obs->hotkeys.hotkeys, id, hotkey); if (hotkey) { - remove_bindings(id); + bool changed = remove_bindings(id); for (size_t i = 0; i < num; i++) create_binding(hotkey, combinations[i]); - hotkey_signal("hotkey_bindings_changed", hotkey); + if (num || changed) + hotkey_signal("hotkey_bindings_changed", hotkey); } + unlock(); } @@ -858,8 +861,9 @@ static inline bool find_binding(obs_hotkey_id id, size_t *idx) static inline void release_pressed_binding(obs_hotkey_binding_t *binding); -static inline void remove_bindings(obs_hotkey_id id) +static inline bool remove_bindings(obs_hotkey_id id) { + bool removed = false; size_t idx; while (find_binding(id, &idx)) { obs_hotkey_binding_t *binding = @@ -869,7 +873,10 @@ static inline void remove_bindings(obs_hotkey_id id) release_pressed_binding(binding); da_erase(obs->hotkeys.bindings, idx); + removed = true; } + + return removed; } static void release_registerer(obs_hotkey_t *hotkey) From 13040ad24173a3ea3d04cb0cddd0a12f159ab7af Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 9 Feb 2023 14:21:18 +0100 Subject: [PATCH 13/14] obs-ffmpeg: Fix translation key capitalisation --- plugins/obs-ffmpeg/obs-ffmpeg-source.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-source.c b/plugins/obs-ffmpeg/obs-ffmpeg-source.c index 7758b944d..20ae1ef80 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-source.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-source.c @@ -487,7 +487,7 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings) static const char *ffmpeg_source_getname(void *unused) { UNUSED_PARAMETER(unused); - return obs_module_text("FFMpegSource"); + return obs_module_text("FFmpegSource"); } static void restart_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, From c971e0ead3d66874bf7af860e6ca5c9cf9a73522 Mon Sep 17 00:00:00 2001 From: derrod Date: Fri, 10 Feb 2023 12:42:34 +0100 Subject: [PATCH 14/14] UI: Fix capitalisation of SysTrayEnabled --- UI/window-basic-main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 280309652..a5dad5b1f 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1990,7 +1990,7 @@ void OBSBasic::OBSInit() /* Show the main window, unless the tray icon isn't available * or neither the setting nor flag for starting minimized is set. */ bool sysTrayEnabled = config_get_bool(App()->GlobalConfig(), - "BasicWindow", "sysTrayEnabled"); + "BasicWindow", "SysTrayEnabled"); bool sysTrayWhenStarted = config_get_bool( App()->GlobalConfig(), "BasicWindow", "SysTrayWhenStarted"); bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() &&