diff --git a/source/obs/obs-source-tracker.cpp b/source/obs/obs-source-tracker.cpp index 6236cc6..aba02a9 100644 --- a/source/obs/obs-source-tracker.cpp +++ b/source/obs/obs-source-tracker.cpp @@ -18,127 +18,167 @@ */ #include "obs-source-tracker.hpp" +#include #include #include "obs/obs-tools.hpp" -#include "plugin.hpp" +#include "util/util-logging.hpp" -static std::shared_ptr source_tracker_instance; +#ifdef _DEBUG +#define ST_PREFIX "<%s> " +#define D_LOG_ERROR(x, ...) P_LOG_ERROR(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__) +#define D_LOG_WARNING(x, ...) P_LOG_WARN(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__) +#define D_LOG_INFO(x, ...) P_LOG_INFO(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__) +#define D_LOG_DEBUG(x, ...) P_LOG_DEBUG(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__) +#else +#define ST_PREFIX " " +#define D_LOG_ERROR(...) P_LOG_ERROR(ST_PREFIX __VA_ARGS__) +#define D_LOG_WARNING(...) P_LOG_WARN(ST_PREFIX __VA_ARGS__) +#define D_LOG_INFO(...) P_LOG_INFO(ST_PREFIX __VA_ARGS__) +#define D_LOG_DEBUG(...) P_LOG_DEBUG(ST_PREFIX __VA_ARGS__) +#endif void streamfx::obs::source_tracker::source_create_handler(void* ptr, calldata_t* data) noexcept -try { - streamfx::obs::source_tracker* self = reinterpret_cast(ptr); +{ + auto* self = reinterpret_cast(ptr); + try { + obs_source_t* source = nullptr; + if (calldata_get_ptr(data, "source", &source); !source) { + throw std::runtime_error("Missing 'source' parameter."); + } - obs_source_t* target = nullptr; - calldata_get_ptr(data, "source", &target); - - if (!target) { - return; + self->insert_source(source); + } catch (const std::exception& ex) { + DLOG_ERROR("Event 'source_create' caused exception: %s", ex.what()); + } catch (...) { + DLOG_ERROR("Event 'source_create' caused unknown exception.", nullptr); } +} - const char* name = obs_source_get_name(target); +void streamfx::obs::source_tracker::source_destroy_handler(void* ptr, calldata_t* data) noexcept +{ + auto* self = reinterpret_cast(ptr); + try { + obs_source_t* source = nullptr; + if (calldata_get_ptr(data, "source", &source); !source) { + throw std::runtime_error("Missing 'source' parameter."); + } + + } catch (const std::exception& ex) { + DLOG_ERROR("Event 'source_destroy' caused exception: %s", ex.what()); + } catch (...) { + DLOG_ERROR("Event 'source_destroy' caused unknown exception.", nullptr); + } +} + +void streamfx::obs::source_tracker::source_rename_handler(void* ptr, calldata_t* data) noexcept +{ + auto* self = reinterpret_cast(ptr); + try { + obs_source_t* source = nullptr; + if (calldata_get_ptr(data, "source", &source); !source) { + throw std::runtime_error("Missing 'source' parameter."); + } + + const char* old_name = nullptr; + if (calldata_get_string(data, "prev_name", &old_name); !old_name) { + throw std::runtime_error("Missing 'prev_name' parameter."); + } + + const char* new_name = nullptr; + if (calldata_get_string(data, "new_name", &new_name); !new_name) { + throw std::runtime_error("Missing 'new_name' parameter."); + } + + self->rename_source(old_name, new_name, source); + } catch (...) { + DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + } +} + +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; } - obs_weak_source_t* weak = obs_source_get_weak_source(target); + 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 ul(self->_lock); - self->_sources.insert({std::string(name), {weak, streamfx::obs::obs_weak_source_deleter}}); - } -} catch (...) { - DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + std::unique_lock lock(_mutex); + _sources.insert({ + std::string(name), + weak, + }); } -void streamfx::obs::source_tracker::source_destroy_handler(void* ptr, calldata_t* data) noexcept -try { - streamfx::obs::source_tracker* self = reinterpret_cast(ptr); +void streamfx::obs::source_tracker::remove_source(obs_source_t* source) +{ + const char* name = obs_source_get_name(source); - obs_source_t* target = nullptr; - calldata_get_ptr(data, "source", &target); + // Lock read & write access to the map. + std::unique_lock ul(_mutex); - if (!target) { - return; - } - - const char* name = obs_source_get_name(target); - if (!name) { // Not tracking unnamed sources. - return; - } - - { - std::unique_lock ul(self->_lock); - auto found = self->_sources.find(std::string(name)); - if (found == self->_sources.end()) { + // 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; } - self->_sources.erase(found); } -} catch (...) { - DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + + // 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::source_rename_handler(void* ptr, calldata_t* data) noexcept -try { - streamfx::obs::source_tracker* self = reinterpret_cast(ptr); +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."); + } - obs_source_t* target = nullptr; - const char* prev_name = nullptr; - const char* new_name = nullptr; - calldata_get_ptr(data, "source", &target); - calldata_get_string(data, "prev_name", &prev_name); - calldata_get_string(data, "new_name", &new_name); - - if (strcmp(prev_name, new_name) == 0) { - // They weren't renamed at all, invalid event. + std::unique_lock ul(_mutex); + auto found = _sources.find(std::string(old_name)); + if (found == _sources.end()) { + insert_source(source); return; } - { - std::unique_lock ul(self->_lock); - auto found = self->_sources.find(std::string(prev_name)); - if (found == self->_sources.end()) { - // Untracked source, insert. - obs_weak_source_t* weak = obs_source_get_weak_source(target); - if (!weak) { - return; - } - self->_sources.insert({new_name, {weak, streamfx::obs::obs_weak_source_deleter}}); - return; - } - - // Insert at new key, remove old pair. - self->_sources.insert({new_name, found->second}); - self->_sources.erase(found); - } -} catch (...) { - DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); + // Insert at new key, remove old pair. + _sources.insert({new_name.data(), found->second}); + _sources.erase(found); } -void streamfx::obs::source_tracker::initialize() -{ - source_tracker_instance = std::make_shared(); -} - -void streamfx::obs::source_tracker::finalize() -{ - source_tracker_instance.reset(); -} - -std::shared_ptr streamfx::obs::source_tracker::get() -{ - return source_tracker_instance; -} - -streamfx::obs::source_tracker::source_tracker() +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() @@ -158,7 +198,7 @@ void streamfx::obs::source_tracker::enumerate(enumerate_cb_t ecb, filter_cb_t fc // Need func-local copy, otherwise we risk corruption if a new source is created or destroyed. decltype(_sources) _clone; { - std::unique_lock ul(_lock); + std::unique_lock ul(_mutex); _clone = _sources; } @@ -209,3 +249,18 @@ bool streamfx::obs::source_tracker::filter_scenes(std::string, obs_source_t* sou { return (obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE); } + +std::shared_ptr streamfx::obs::source_tracker::get() +{ + static std::mutex inst_mtx; + static std::weak_ptr inst_weak; + + std::unique_lock lock(inst_mtx); + if (inst_weak.expired()) { + auto instance = std::shared_ptr(new streamfx::obs::source_tracker()); + inst_weak = instance; + return instance; + } else { + return inst_weak.lock(); + } +} diff --git a/source/obs/obs-source-tracker.hpp b/source/obs/obs-source-tracker.hpp index bff87c3..a8303a7 100644 --- a/source/obs/obs-source-tracker.hpp +++ b/source/obs/obs-source-tracker.hpp @@ -26,20 +26,16 @@ namespace streamfx::obs { class source_tracker { std::map> _sources; - std::mutex _lock; + 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; - public: // Singleton - static void initialize(); - static void finalize(); - static std::shared_ptr get(); - - public: - source_tracker(); - ~source_tracker(); + 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: // Callback function for enumerating sources. @@ -56,6 +52,12 @@ namespace streamfx::obs { // @return true to skip, false to pass along. typedef std::function filter_cb_t; + protected: + source_tracker(); + + public: + ~source_tracker(); + //! Enumerate all tracked sources // // @param enumerate_cb The function called for each tracked source. @@ -68,5 +70,8 @@ namespace streamfx::obs { 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); + + public: // Singleton + static std::shared_ptr get(); }; } // namespace streamfx::obs diff --git a/source/plugin.cpp b/source/plugin.cpp index 3882120..3701394 100644 --- a/source/plugin.cpp +++ b/source/plugin.cpp @@ -94,6 +94,7 @@ static std::shared_ptr _threadpool; static std::shared_ptr _gs_fstri_vb; static std::shared_ptr _streamfx_gfx_opengl; +static std::shared_ptr _source_tracker; MODULE_EXPORT bool obs_module_load(void) try { @@ -106,7 +107,7 @@ try { _threadpool = std::make_shared(); // Initialize Source Tracker - streamfx::obs::source_tracker::initialize(); + _source_tracker = streamfx::obs::source_tracker::get(); // Initialize GLAD (OpenGL) { @@ -310,7 +311,7 @@ try { } // Finalize Source Tracker - streamfx::obs::source_tracker::finalize(); + _source_tracker.reset(); // // Auto-Updater //#ifdef ENABLE_UPDATER