Add VariableRef wrapper and variable reference collector

Just a wrapper around weark_ptr<Variable> to make tracking usage easier
This commit is contained in:
WarmUpTill
2026-10-19 17:45:55 +02:00
committed by WarmUpTill
parent 7e1acd8f61
commit 392aebff5c
5 changed files with 210 additions and 0 deletions

View File

@@ -321,6 +321,8 @@ target_sources(
lib/variables/variable-line-edit.hpp
lib/variables/variable-number.hpp
lib/variables/variable-number.tpp
lib/variables/variable-ref.cpp
lib/variables/variable-ref.hpp
lib/variables/variable-spinbox.cpp
lib/variables/variable-spinbox.hpp
lib/variables/variable-string.cpp

View File

@@ -0,0 +1,78 @@
#include "variable-ref.hpp"
#include "variable.hpp"
namespace advss {
thread_local static VariableRefCollector *activeCollector = nullptr;
VariableRefCollector::VariableRefCollector() : _previous(activeCollector)
{
activeCollector = this;
}
VariableRefCollector::~VariableRefCollector()
{
activeCollector = _previous;
}
void VariableRefCollector::Add(const std::string &name, int count)
{
_counts[name] += count;
}
void ReportVariableRef(const std::weak_ptr<Variable> &variable)
{
if (!activeCollector) {
return;
}
auto var = variable.lock();
if (!var) {
return;
}
activeCollector->Add(var->Name());
}
static int countOccurrences(const std::string &text, const std::string &pattern)
{
int count = 0;
for (auto pos = text.find(pattern); pos != std::string::npos;
pos = text.find(pattern, pos + pattern.length())) {
count++;
}
return count;
}
void ReportVariableRefsInText(const std::string &text)
{
if (!activeCollector || text.find("${") == std::string::npos) {
return;
}
for (const auto &variable : GetVariables()) {
const auto &name = variable->Name();
const int count = countOccurrences(text, "${" + name + "}");
if (count > 0) {
activeCollector->Add(name, count);
}
}
}
void VariableRef::Save(obs_data_t *obj, const char *key) const
{
auto var = _variable.lock();
if (!var) {
return;
}
obs_data_set_string(obj, key, var->Name().c_str());
ReportVariableRef(_variable);
}
void VariableRef::Load(obs_data_t *obj, const char *key)
{
_variable = GetWeakVariableByName(obs_data_get_string(obj, key));
}
} // namespace advss

View File

@@ -0,0 +1,59 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <memory>
#include <obs-data.h>
#include <string>
#include <unordered_map>
namespace advss {
class Variable;
// While an instance is alive, variable references saved on the current
// thread are counted by variable name.
class VariableRefCollector {
public:
EXPORT VariableRefCollector();
EXPORT ~VariableRefCollector();
VariableRefCollector(const VariableRefCollector &) = delete;
VariableRefCollector &operator=(const VariableRefCollector &) = delete;
EXPORT void Add(const std::string &name, int count = 1);
const std::unordered_map<std::string, int> &Counts() const
{
return _counts;
}
private:
std::unordered_map<std::string, int> _counts;
VariableRefCollector *_previous = nullptr;
};
EXPORT void ReportVariableRef(const std::weak_ptr<Variable> &variable);
EXPORT void ReportVariableRefsInText(const std::string &text);
class VariableRef {
public:
VariableRef() = default;
VariableRef(const std::weak_ptr<Variable> &variable)
: _variable(variable)
{
}
VariableRef &operator=(const std::weak_ptr<Variable> &variable)
{
_variable = variable;
return *this;
}
operator std::weak_ptr<Variable>() const { return _variable; }
std::shared_ptr<Variable> lock() const { return _variable.lock(); }
bool expired() const { return _variable.expired(); }
EXPORT void Save(obs_data_t *obj, const char *key) const;
EXPORT void Load(obs_data_t *obj, const char *key);
private:
std::weak_ptr<Variable> _variable;
};
} // namespace advss

View File

@@ -128,6 +128,7 @@ target_sources(
${ADVSS_SOURCE_DIR}/lib/utils/item-selection-helpers.cpp
${ADVSS_SOURCE_DIR}/lib/utils/name-dialog.cpp
${ADVSS_SOURCE_DIR}/lib/utils/resizing-text-edit.cpp
${ADVSS_SOURCE_DIR}/lib/variables/variable-ref.cpp
${ADVSS_SOURCE_DIR}/lib/variables/variable.cpp)
# --- macro / macro-condition --- #

View File

@@ -1,6 +1,9 @@
#include "catch.hpp"
#include <variable-ref.hpp>
#include <variable.hpp>
#include <obs.hpp>
#include <thread>
TEST_CASE("Variable", "[variable]")
@@ -50,3 +53,70 @@ TEST_CASE("Variable", "[variable]")
variable.SetValue(123);
REQUIRE(*variable.GetSecondsSinceLastChange() > 0);
}
static std::shared_ptr<advss::Variable> addVariable(const std::string &name)
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "name", name.c_str());
auto variable = std::make_shared<advss::Variable>();
variable->Load(data);
advss::GetVariables().emplace_back(variable);
return variable;
}
TEST_CASE("VariableRef", "[variable]")
{
auto var = addVariable("var");
addVariable("var2");
addVariable("Var");
SECTION("Save reports and writes name")
{
advss::VariableRefCollector collector;
advss::VariableRef ref(var);
OBSDataAutoRelease data = obs_data_create();
ref.Save(data, "key");
REQUIRE(std::string(obs_data_get_string(data, "key")) == "var");
REQUIRE(collector.Counts().at("var") == 1);
advss::VariableRef loaded;
loaded.Load(data, "key");
REQUIRE(loaded.lock() == var);
}
SECTION("Expired ref is not saved or reported")
{
advss::VariableRefCollector collector;
advss::VariableRef ref;
OBSDataAutoRelease data = obs_data_create();
ref.Save(data, "key");
REQUIRE_FALSE(obs_data_has_user_value(data, "key"));
REQUIRE(collector.Counts().empty());
}
SECTION("Text references")
{
advss::VariableRefCollector collector;
advss::ReportVariableRefsInText(
"${var} ${var2} ${var} var Var");
const auto &counts = collector.Counts();
REQUIRE(counts.at("var") == 2);
REQUIRE(counts.at("var2") == 1);
REQUIRE(counts.find("Var") == counts.end());
}
SECTION("Nested collectors")
{
advss::VariableRefCollector outer;
{
advss::VariableRefCollector inner;
advss::ReportVariableRef(var);
REQUIRE(inner.Counts().at("var") == 1);
}
REQUIRE(outer.Counts().empty());
advss::ReportVariableRef(var);
REQUIRE(outer.Counts().at("var") == 1);
}
advss::GetVariables().clear();
}