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
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2023-02-16 05:33:33 +01:00
parent 5e307a8e36
commit e8ec23c4d4
6 changed files with 187 additions and 187 deletions

View File

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

View File

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

View File

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

View File

@ -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<decltype(_mutex)> 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<decltype(_mutex)> 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<decltype(_mutex)> 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<decltype(_mutex)> 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<streamfx::obs::source_tracker*>(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<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> 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<std::mutex> 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<std::mutex> 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<std::mutex> ul(_mutex);
_clone = _sources;
}
for (auto kv : _clone) {
auto source = std::shared_ptr<obs_source_t>(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> streamfx::obs::source_tracker::get()
{
static std::mutex inst_mtx;

View File

@ -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 <functional>
@ -28,17 +29,8 @@
namespace streamfx::obs {
class source_tracker {
std::map<std::string, std::shared_ptr<obs_weak_source_t>> _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<std::string, ::streamfx::obs::weak_source> _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<bool(std::string, obs_source_t*)> enumerate_cb_t;
typedef std::function<bool(std::string, ::streamfx::obs::source)> 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<bool(std::string, obs_source_t*)> filter_cb_t;
typedef std::function<bool(std::string, ::streamfx::obs::source)> 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<streamfx::obs::source_tracker> get();

View File

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