From e8ec23c4d4af0a8e269aa02f9aa615cf6336ef2b Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 16 Feb 2023 05:33:33 +0100 Subject: [PATCH] obs/source-tracker: Fix leaked source references This functionality broke at some point in the past without anyone noticing, resulting in most dropdowns that rely on this functionality being blank. Fixes #1025 --- source/filters/filter-blur.cpp | 6 +- source/filters/filter-dynamic-mask.cpp | 6 +- .../gfx/shader/gfx-shader-param-texture.cpp | 6 +- source/obs/obs-source-tracker.cpp | 310 +++++++++--------- source/obs/obs-source-tracker.hpp | 40 +-- source/sources/source-mirror.cpp | 6 +- 6 files changed, 187 insertions(+), 187 deletions(-) diff --git a/source/filters/filter-blur.cpp b/source/filters/filter-blur.cpp index 50dcb0a..0a60bb1 100644 --- a/source/filters/filter-blur.cpp +++ b/source/filters/filter-blur.cpp @@ -1,6 +1,6 @@ /* * Modern effects for a modern Streamer - * Copyright (C) 2017-2018 Michael Fabian Dirks + * Copyright (C) 2017-2023 Michael Fabian Dirks * * 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 @@ -863,13 +863,13 @@ obs_properties_t* blur_factory::get_properties2(blur_instance* data) OBS_COMBO_FORMAT_STRING); obs_property_list_add_string(p, "", ""); obs::source_tracker::get()->enumerate( - [&p](std::string name, obs_source_t*) { + [&p](std::string name, ::streamfx::obs::source) { obs_property_list_add_string(p, std::string(name + " (Source)").c_str(), name.c_str()); return false; }, obs::source_tracker::filter_video_sources); obs::source_tracker::get()->enumerate( - [&p](std::string name, obs_source_t*) { + [&p](std::string name, ::streamfx::obs::source) { obs_property_list_add_string(p, std::string(name + " (Scene)").c_str(), name.c_str()); return false; }, diff --git a/source/filters/filter-dynamic-mask.cpp b/source/filters/filter-dynamic-mask.cpp index d53f0ae..34a772f 100644 --- a/source/filters/filter-dynamic-mask.cpp +++ b/source/filters/filter-dynamic-mask.cpp @@ -1,6 +1,6 @@ /* * Modern effects for a modern Streamer - * Copyright (C) 2019 Michael Fabian Dirks + * Copyright (C) 2019-2023 Michael Fabian Dirks * * 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 @@ -754,7 +754,7 @@ obs_properties_t* dynamic_mask_factory::get_properties2(dynamic_mask_instance* d OBS_COMBO_FORMAT_STRING); obs_property_list_add_string(p, "", ""); obs::source_tracker::get()->enumerate( - [&p](std::string name, obs_source_t*) { + [&p](std::string name, ::streamfx::obs::source) { std::stringstream sstr; sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SOURCE) << ")"; obs_property_list_add_string(p, sstr.str().c_str(), name.c_str()); @@ -762,7 +762,7 @@ obs_properties_t* dynamic_mask_factory::get_properties2(dynamic_mask_instance* d }, obs::source_tracker::filter_video_sources); obs::source_tracker::get()->enumerate( - [&p](std::string name, obs_source_t*) { + [&p](std::string name, ::streamfx::obs::source) { std::stringstream sstr; sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SCENE) << ")"; obs_property_list_add_string(p, sstr.str().c_str(), name.c_str()); diff --git a/source/gfx/shader/gfx-shader-param-texture.cpp b/source/gfx/shader/gfx-shader-param-texture.cpp index 3640f00..38ca4df 100644 --- a/source/gfx/shader/gfx-shader-param-texture.cpp +++ b/source/gfx/shader/gfx-shader-param-texture.cpp @@ -1,5 +1,5 @@ // Modern effects for a modern Streamer -// Copyright (C) 2019 Michael Fabian Dirks +// Copyright (C) 2019-2023 Michael Fabian Dirks // // 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 @@ -215,7 +215,7 @@ void streamfx::gfx::shader::texture_parameter::properties(obs_properties_t* prop OBS_COMBO_FORMAT_STRING); obs_property_list_add_string(p, "", ""); obs::source_tracker::get()->enumerate( - [&p](std::string name, obs_source_t*) { + [&p](std::string name, ::streamfx::obs::source) { std::stringstream sstr; sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SOURCE) << ")"; obs_property_list_add_string(p, sstr.str().c_str(), name.c_str()); @@ -223,7 +223,7 @@ void streamfx::gfx::shader::texture_parameter::properties(obs_properties_t* prop }, obs::source_tracker::filter_video_sources); obs::source_tracker::get()->enumerate( - [&p](std::string name, obs_source_t*) { + [&p](std::string name, ::streamfx::obs::source) { std::stringstream sstr; sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SCENE) << ")"; obs_property_list_add_string(p, sstr.str().c_str(), name.c_str()); diff --git a/source/obs/obs-source-tracker.cpp b/source/obs/obs-source-tracker.cpp index 133afc4..d920ff0 100644 --- a/source/obs/obs-source-tracker.cpp +++ b/source/obs/obs-source-tracker.cpp @@ -1,6 +1,6 @@ /* * Modern effects for a modern Streamer - * Copyright (C) 2017-2018 Michael Fabian Dirks + * Copyright (C) 2017-2023 Michael Fabian Dirks * * 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 @@ -40,6 +40,158 @@ #define D_LOG_DEBUG(...) P_LOG_DEBUG(ST_PREFIX __VA_ARGS__) #endif +streamfx::obs::source_tracker::source_tracker() : _sources(), _mutex() +{ + auto osi = obs_get_signal_handler(); + if (osi) { + signal_handler_connect(osi, "source_create", &source_create_handler, this); + signal_handler_connect(osi, "source_destroy", &source_destroy_handler, this); + signal_handler_connect(osi, "source_rename", &source_rename_handler, this); + } else { + D_LOG_WARNING("No global signal handler was present at initialization.", nullptr) + } + + // Enumerate all current sources and filters. + obs_enum_all_sources( + [](void* param, obs_source_t* source) { + auto* self = reinterpret_cast<::streamfx::obs::source_tracker*>(param); + self->insert_source(source); + return true; + }, + this); +} + +streamfx::obs::source_tracker::~source_tracker() +{ + auto osi = obs_get_signal_handler(); + if (osi) { + signal_handler_disconnect(osi, "source_create", &source_create_handler, this); + signal_handler_disconnect(osi, "source_destroy", &source_destroy_handler, this); + signal_handler_disconnect(osi, "source_rename", &source_rename_handler, this); + } + + this->_sources.clear(); +} + +void streamfx::obs::source_tracker::enumerate(enumerate_cb_t ecb, filter_cb_t fcb) +{ + // Need func-local copy, otherwise we risk corruption if a new source is created or destroyed. + decltype(_sources) _clone; + { + std::lock_guard lock(_mutex); + _clone = _sources; + } + + for (auto kv : _clone) { + auto wsource = kv.second; + try { + auto source = wsource.lock(); + + if (fcb) { + if (fcb(kv.first, source)) { + continue; + } + } + + if (ecb) { + if (ecb(kv.first, source)) { + break; + } + } + } catch (...) { + continue; + } + } +} + +void streamfx::obs::source_tracker::insert_source(obs_source_t* source) +{ + const char* name = obs_source_get_name(source); + + // Don't track unnamed sources. + if (!name || (strnlen(name, 1) == 0)) { + D_LOG_DEBUG("Unnamed source '0x%08zX' left untracked.", source); + return; + } + + // Insert the newly tracked source into the map. + std::lock_guard lock(_mutex); + _sources.emplace(std::string{name}, ::streamfx::obs::weak_source{source}); +} + +void streamfx::obs::source_tracker::remove_source(obs_source_t* source) +{ + std::lock_guard lock(_mutex); + const char* name = obs_source_get_name(source); + + // Try and find the source by name. + if (name) { + if (auto kv = _sources.find(std::string{name}); kv != _sources.end()) { + _sources.erase(kv); + return; + } + } + + // Try and find the source by pointer. + for (auto kv = _sources.begin(); kv != _sources.end(); kv++) { + if (kv->second == source) { + _sources.erase(kv); + return; + } + } + + // If we're still here, there's something wrong. + if (name) { + D_LOG_ERROR("Attempt to remove untracked source '0x%08zX' with name %s failed.", source, name); + throw std::runtime_error("Failed to find given source."); + } +} + +void streamfx::obs::source_tracker::rename_source(std::string_view old_name, std::string_view new_name, + obs_source_t* source) +{ + if (old_name == new_name) { + throw std::runtime_error("New and old name are identical."); + } + + std::lock_guard lock(_mutex); + + // Remove the previously tracked entry. + if (auto kv = _sources.find(std::string{old_name}); kv != _sources.end()) { + _sources.erase(kv); + } + + // And then add the new entry. + _sources.emplace(std::string{new_name}, ::streamfx::obs::weak_source{source}); +} + +bool streamfx::obs::source_tracker::filter_sources(std::string, ::streamfx::obs::source source) +{ + return (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT); +} + +bool streamfx::obs::source_tracker::filter_audio_sources(std::string, ::streamfx::obs::source source) +{ + uint32_t flags = obs_source_get_output_flags(source); + return !(flags & OBS_SOURCE_AUDIO) || (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT); +} + +bool streamfx::obs::source_tracker::filter_video_sources(std::string, ::streamfx::obs::source source) +{ + uint32_t flags = obs_source_get_output_flags(source); + return !(flags & OBS_SOURCE_VIDEO) || (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT); +} + +bool streamfx::obs::source_tracker::filter_transitions(std::string, ::streamfx::obs::source source) +{ + return (obs_source_get_type(source) != OBS_SOURCE_TYPE_TRANSITION); +} + +bool streamfx::obs::source_tracker::filter_scenes(std::string, ::streamfx::obs::source source) +{ + return (obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE); +} + void streamfx::obs::source_tracker::source_create_handler(void* ptr, calldata_t* data) noexcept { auto* self = reinterpret_cast(ptr); @@ -66,6 +218,7 @@ void streamfx::obs::source_tracker::source_destroy_handler(void* ptr, calldata_t throw std::runtime_error("Missing 'source' parameter."); } + self->remove_source(source); } catch (const std::exception& ex) { DLOG_ERROR("Event 'source_destroy' caused exception: %s", ex.what()); } catch (...) { @@ -98,161 +251,6 @@ void streamfx::obs::source_tracker::source_rename_handler(void* ptr, calldata_t* } } -void streamfx::obs::source_tracker::insert_source(obs_source_t* source) -{ - const auto* name = obs_source_get_name(source); - if (!name) { // Do not track unnamed sources. - return; - } - - std::shared_ptr weak{obs_source_get_weak_source(source), streamfx::obs::obs_weak_source_deleter}; - if (!weak) { // This source has already been deleted, do not track. - return; - } - - std::unique_lock lock(_mutex); - _sources.insert({ - std::string(name), - weak, - }); -} - -void streamfx::obs::source_tracker::remove_source(obs_source_t* source) -{ - const char* name = obs_source_get_name(source); - - // Lock read & write access to the map. - std::unique_lock ul(_mutex); - - // Try and remove the source by name. - if (name != nullptr) { - auto found = _sources.find(std::string(name)); - if (found != _sources.end()) { - _sources.erase(found); - return; - } - } - - // If that didn't work, try and remove it by handle. - for (auto iter = _sources.begin(); iter != _sources.end(); iter++) { - if (obs_weak_source_get_source(iter->second.get()) == source) { - _sources.erase(iter); - return; - } - } - - // If that all failed, and the source is named, throw and report an error. - if (name) { - D_LOG_WARNING("Source '%s' was not tracked.", name); - throw std::runtime_error("Failed to find given source."); - } -} - -void streamfx::obs::source_tracker::rename_source(std::string_view old_name, std::string_view new_name, - obs_source_t* source) -{ - if (old_name == new_name) { - throw std::runtime_error("New and old name are identical."); - } - - std::unique_lock ul(_mutex); - auto found = _sources.find(std::string(old_name)); - if (found == _sources.end()) { - insert_source(source); - return; - } - - // Insert at new key, remove old pair. - _sources.insert({new_name.data(), found->second}); - _sources.erase(found); -} - -streamfx::obs::source_tracker::source_tracker() : _sources(), _mutex() -{ - auto osi = obs_get_signal_handler(); - signal_handler_connect(osi, "source_create", &source_create_handler, this); - signal_handler_connect(osi, "source_destroy", &source_destroy_handler, this); - signal_handler_connect(osi, "source_rename", &source_rename_handler, this); - - // Enumerate all current sources and filters. - obs_enum_all_sources( - [](void* param, obs_source_t* source) { - auto* self = reinterpret_cast<::streamfx::obs::source_tracker*>(param); - self->insert_source(source); - return true; - }, - this); -} - -streamfx::obs::source_tracker::~source_tracker() -{ - auto osi = obs_get_signal_handler(); - if (osi) { - signal_handler_disconnect(osi, "source_create", &source_create_handler, this); - signal_handler_disconnect(osi, "source_destroy", &source_destroy_handler, this); - signal_handler_disconnect(osi, "source_rename", &source_rename_handler, this); - } - - this->_sources.clear(); -} - -void streamfx::obs::source_tracker::enumerate(enumerate_cb_t ecb, filter_cb_t fcb) -{ - // Need func-local copy, otherwise we risk corruption if a new source is created or destroyed. - decltype(_sources) _clone; - { - std::unique_lock ul(_mutex); - _clone = _sources; - } - - for (auto kv : _clone) { - auto source = std::shared_ptr(obs_weak_source_get_source(kv.second.get()), - streamfx::obs::obs_source_deleter); - if (!source) { - continue; - } - - if (fcb) { - if (fcb(kv.first, source.get())) { - continue; - } - } - - if (ecb) { - if (ecb(kv.first, source.get())) { - break; - } - } - } -} - -bool streamfx::obs::source_tracker::filter_sources(std::string, obs_source_t* source) -{ - return (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT); -} - -bool streamfx::obs::source_tracker::filter_audio_sources(std::string, obs_source_t* source) -{ - uint32_t flags = obs_source_get_output_flags(source); - return !(flags & OBS_SOURCE_AUDIO) || (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT); -} - -bool streamfx::obs::source_tracker::filter_video_sources(std::string, obs_source_t* source) -{ - uint32_t flags = obs_source_get_output_flags(source); - return !(flags & OBS_SOURCE_VIDEO) || (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT); -} - -bool streamfx::obs::source_tracker::filter_transitions(std::string, obs_source_t* source) -{ - return (obs_source_get_type(source) != OBS_SOURCE_TYPE_TRANSITION); -} - -bool streamfx::obs::source_tracker::filter_scenes(std::string, obs_source_t* source) -{ - return (obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE); -} - std::shared_ptr streamfx::obs::source_tracker::get() { static std::mutex inst_mtx; diff --git a/source/obs/obs-source-tracker.hpp b/source/obs/obs-source-tracker.hpp index 13a35dc..2741ef7 100644 --- a/source/obs/obs-source-tracker.hpp +++ b/source/obs/obs-source-tracker.hpp @@ -1,6 +1,6 @@ /* * Modern effects for a modern Streamer - * Copyright (C) 2017-2018 Michael Fabian Dirks + * Copyright (C) 2017-2023 Michael Fabian Dirks * * 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 @@ -19,6 +19,7 @@ #pragma once #include "common.hpp" +#include "obs/obs-weak-source.hpp" #include "warning-disable.hpp" #include @@ -28,17 +29,8 @@ namespace streamfx::obs { class source_tracker { - std::map> _sources; - std::mutex _mutex; - - static void source_create_handler(void* ptr, calldata_t* data) noexcept; - static void source_destroy_handler(void* ptr, calldata_t* data) noexcept; - static void source_rename_handler(void* ptr, calldata_t* data) noexcept; - - protected: - void insert_source(obs_source_t* source); - void remove_source(obs_source_t* source); - void rename_source(std::string_view old_name, std::string_view new_name, obs_source_t* source); + std::map _sources; + std::mutex _mutex; public: // Callback function for enumerating sources. @@ -46,14 +38,14 @@ namespace streamfx::obs { // @param std::string Name of the Source // @param obs_source_t* Source // @return true to abort enumeration, false to keep going. - typedef std::function enumerate_cb_t; + typedef std::function enumerate_cb_t; // Filter function for enumerating sources. // // @param std::string Name of the Source // @param obs_source_t* Source // @return true to skip, false to pass along. - typedef std::function filter_cb_t; + typedef std::function filter_cb_t; protected: source_tracker(); @@ -67,12 +59,22 @@ namespace streamfx::obs { // @param filter_cb Filter function to narrow down results. void enumerate(enumerate_cb_t enumerate_cb, filter_cb_t filter_cb = nullptr); + protected: + void insert_source(obs_source_t* source); + void remove_source(obs_source_t* source); + void rename_source(std::string_view old_name, std::string_view new_name, obs_source_t* source); + public: - static bool filter_sources(std::string name, obs_source_t* source); - static bool filter_audio_sources(std::string name, obs_source_t* source); - static bool filter_video_sources(std::string name, obs_source_t* source); - static bool filter_transitions(std::string name, obs_source_t* source); - static bool filter_scenes(std::string name, obs_source_t* source); + static bool filter_sources(std::string name, ::streamfx::obs::source source); + static bool filter_audio_sources(std::string name, ::streamfx::obs::source source); + static bool filter_video_sources(std::string name, ::streamfx::obs::source source); + static bool filter_transitions(std::string name, ::streamfx::obs::source source); + static bool filter_scenes(std::string name, ::streamfx::obs::source source); + + private: + static void source_create_handler(void* ptr, calldata_t* data) noexcept; + static void source_destroy_handler(void* ptr, calldata_t* data) noexcept; + static void source_rename_handler(void* ptr, calldata_t* data) noexcept; public: // Singleton static std::shared_ptr get(); diff --git a/source/sources/source-mirror.cpp b/source/sources/source-mirror.cpp index 9be06e9..7e5c00c 100644 --- a/source/sources/source-mirror.cpp +++ b/source/sources/source-mirror.cpp @@ -1,6 +1,6 @@ /* * Modern effects for a modern Streamer -* Copyright (C) 2017 Michael Fabian Dirks +* Copyright (C) 2017-2023 Michael Fabian Dirks * * 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 @@ -348,7 +348,7 @@ obs_properties_t* mirror_factory::get_properties2(mirror_instance* data) obs_property_list_add_string(p, "", ""); obs::source_tracker::get()->enumerate( - [&p](std::string name, obs_source_t*) { + [&p](std::string name, ::streamfx::obs::source) { std::stringstream sstr; sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SOURCE) << ")"; obs_property_list_add_string(p, sstr.str().c_str(), name.c_str()); @@ -356,7 +356,7 @@ obs_properties_t* mirror_factory::get_properties2(mirror_instance* data) }, obs::source_tracker::filter_sources); obs::source_tracker::get()->enumerate( - [&p](std::string name, obs_source_t*) { + [&p](std::string name, ::streamfx::obs::source) { std::stringstream sstr; sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SCENE) << ")"; obs_property_list_add_string(p, sstr.str().c_str(), name.c_str());