mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-11-11 06:15:05 +00:00
obs/source-tracker: Fix missing sources and modernize singleton design
In some rare cases, a bug is observed where some sources end up missing despite being visible in the OBS Studio UI. This is most likely related to us actually missing the events due to plugin load order. We can fix this by explicitly enumerating sources in the constructor. Additionally in order to reduce the human error factor, we should avoid explicit initialize() and finalize() calls for our singleton. Instead the get() function should do all of the heavy lifting, including thread safety, so that the human writing the code will have next to no chances to break it.
This commit is contained in:
parent
0627628884
commit
6dd661a41c
3 changed files with 158 additions and 97 deletions
|
@ -18,127 +18,167 @@
|
|||
*/
|
||||
|
||||
#include "obs-source-tracker.hpp"
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include "obs/obs-tools.hpp"
|
||||
#include "plugin.hpp"
|
||||
#include "util/util-logging.hpp"
|
||||
|
||||
static std::shared_ptr<streamfx::obs::source_tracker> 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 "<obs::source_tracker> "
|
||||
#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<streamfx::obs::source_tracker*>(ptr);
|
||||
{
|
||||
auto* self = reinterpret_cast<streamfx::obs::source_tracker*>(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<streamfx::obs::source_tracker*>(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<streamfx::obs::source_tracker*>(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<obs_weak_source_t> 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<std::mutex> 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<std::mutex> 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<streamfx::obs::source_tracker*>(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<std::mutex> ul(_mutex);
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* name = obs_source_get_name(target);
|
||||
if (!name) { // Not tracking unnamed sources.
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> 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<streamfx::obs::source_tracker*>(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<std::mutex> ul(_mutex);
|
||||
auto found = _sources.find(std::string(old_name));
|
||||
if (found == _sources.end()) {
|
||||
insert_source(source);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> 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<streamfx::obs::source_tracker>();
|
||||
}
|
||||
|
||||
void streamfx::obs::source_tracker::finalize()
|
||||
{
|
||||
source_tracker_instance.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<streamfx::obs::source_tracker> 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<std::mutex> ul(_lock);
|
||||
std::unique_lock<std::mutex> 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> streamfx::obs::source_tracker::get()
|
||||
{
|
||||
static std::mutex inst_mtx;
|
||||
static std::weak_ptr<streamfx::obs::source_tracker> inst_weak;
|
||||
|
||||
std::unique_lock<std::mutex> lock(inst_mtx);
|
||||
if (inst_weak.expired()) {
|
||||
auto instance = std::shared_ptr<streamfx::obs::source_tracker>(new streamfx::obs::source_tracker());
|
||||
inst_weak = instance;
|
||||
return instance;
|
||||
} else {
|
||||
return inst_weak.lock();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,20 +26,16 @@
|
|||
namespace streamfx::obs {
|
||||
class source_tracker {
|
||||
std::map<std::string, std::shared_ptr<obs_weak_source_t>> _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<streamfx::obs::source_tracker> 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<bool(std::string, obs_source_t*)> 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<streamfx::obs::source_tracker> get();
|
||||
};
|
||||
} // namespace streamfx::obs
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
static std::shared_ptr<streamfx::util::threadpool> _threadpool;
|
||||
static std::shared_ptr<streamfx::obs::gs::vertex_buffer> _gs_fstri_vb;
|
||||
static std::shared_ptr<streamfx::gfx::opengl> _streamfx_gfx_opengl;
|
||||
static std::shared_ptr<streamfx::obs::source_tracker> _source_tracker;
|
||||
|
||||
MODULE_EXPORT bool obs_module_load(void)
|
||||
try {
|
||||
|
@ -106,7 +107,7 @@ try {
|
|||
_threadpool = std::make_shared<streamfx::util::threadpool>();
|
||||
|
||||
// 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
|
||||
|
|
Loading…
Reference in a new issue