feat: never destroy config singletons

There are lots of issues with it, so instead give ownership to C++ and
don't destroy on reloads (this does mean it does not reload, but it
doesn't need to anyways)
This commit is contained in:
2 * r + 2 * t 2026-04-11 22:11:30 +10:00
parent 9cf3ad4094
commit f65723ad71
4 changed files with 13 additions and 65 deletions

View file

@ -9,8 +9,6 @@ namespace caelestia::config {
namespace {
GlobalConfig* s_instance = nullptr;
QString configDir() {
return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/caelestia/");
}
@ -36,14 +34,7 @@ GlobalConfig::GlobalConfig(QObject* parent)
, m_sidebar(new SidebarConfig(this))
, m_services(new ServiceConfig(this))
, m_paths(new UserPaths(this)) {
// Set global instance
s_instance = this;
setupFileBackend(configDir() + QStringLiteral("shell.json"));
// If TokenConfig was created before us, bind now
if (TokenConfig::instance())
bindAppearanceTokens();
}
GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, QObject* parent)
@ -72,13 +63,10 @@ GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, QObj
syncFromGlobal(fallback);
}
GlobalConfig::~GlobalConfig() {
if (s_instance == this)
s_instance = nullptr;
}
GlobalConfig* GlobalConfig::instance() {
return s_instance;
static GlobalConfig instance;
instance.bindAppearanceTokens();
return &instance;
}
GlobalConfig* GlobalConfig::defaults() {
@ -91,14 +79,8 @@ void GlobalConfig::bindAppearanceTokens() {
if (m_tokensBound)
return;
auto* tokens = TokenConfig::instance();
if (!tokens) {
qCDebug(lcConfig) << "GlobalConfig::bindAppearanceTokens: TokenConfig not yet available";
return;
}
qCDebug(lcConfig) << "GlobalConfig::bindAppearanceTokens: binding appearance to token values";
auto* tokenAppearance = tokens->appearance();
auto* const tokenAppearance = TokenConfig::instance()->appearance();
m_appearance->rounding()->bindTokens(tokenAppearance->rounding());
m_appearance->spacing()->bindTokens(tokenAppearance->spacing());
m_appearance->padding()->bindTokens(tokenAppearance->padding());
@ -107,17 +89,9 @@ void GlobalConfig::bindAppearanceTokens() {
m_tokensBound = true;
}
GlobalConfig* GlobalConfig::create(QQmlEngine* engine, QJSEngine* jsEngine) {
auto* config = new GlobalConfig(engine);
// Ensure TokenConfig is created — appearance computed properties depend on token binding.
if (!TokenConfig::instance())
TokenConfig::create(engine, jsEngine);
// Bind now that both singletons exist
config->bindAppearanceTokens();
return config;
GlobalConfig* GlobalConfig::create(QQmlEngine*, QJSEngine*) {
QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership);
return instance();
}
// Config (attached type)
@ -140,8 +114,7 @@ void Config::connectScope() {
const Type* Config::name() const { \
if (m_scope && m_scope->config()) \
return m_scope->config()->name(); \
auto* global = GlobalConfig::instance(); \
return global ? global->name() : nullptr; \
return GlobalConfig::instance()->name(); \
}
CONFIG_ATTACHED_GETTER(AppearanceConfig, appearance)
@ -165,11 +138,6 @@ CONFIG_ATTACHED_GETTER(UserPaths, paths)
#undef CONFIG_ATTACHED_GETTER
Config* Config::qmlAttachedProperties(QObject* object) {
// Ensure GlobalConfig singleton is created before any attached property access
if (!GlobalConfig::instance()) {
if (auto* engine = qmlEngine(object))
engine->singletonInstance<GlobalConfig*>("Caelestia.Config", "GlobalConfig");
}
return new Config(ConfigScope::find(object), object);
}

View file

@ -50,8 +50,6 @@ public:
[[nodiscard]] Q_INVOKABLE GlobalConfig* defaults();
static GlobalConfig* create(QQmlEngine*, QJSEngine*);
~GlobalConfig() override;
void bindAppearanceTokens();
private:

View file

@ -9,8 +9,6 @@ namespace caelestia::config {
namespace {
TokenConfig* s_instance = nullptr;
QString configDir() {
return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/caelestia/");
}
@ -21,13 +19,7 @@ TokenConfig::TokenConfig(QObject* parent)
: RootConfig(parent)
, m_appearance(new AppearanceTokens(this))
, m_sizes(new SizeTokens(this)) {
s_instance = this;
setupFileBackend(configDir() + QStringLiteral("shell-tokens.json"));
// If GlobalConfig was created before us, trigger its binding
if (auto* global = GlobalConfig::instance())
global->bindAppearanceTokens();
}
TokenConfig::TokenConfig(TokenConfig* fallback, const QString& filePath, QObject* parent)
@ -41,13 +33,9 @@ TokenConfig::TokenConfig(TokenConfig* fallback, const QString& filePath, QObject
syncFromGlobal(fallback);
}
TokenConfig::~TokenConfig() {
if (s_instance == this)
s_instance = nullptr;
}
TokenConfig* TokenConfig::instance() {
return s_instance;
static TokenConfig instance;
return &instance;
}
TokenConfig* TokenConfig::defaults() {
@ -56,8 +44,9 @@ TokenConfig* TokenConfig::defaults() {
return m_defaults;
}
TokenConfig* TokenConfig::create(QQmlEngine* engine, QJSEngine*) {
return new TokenConfig(engine);
TokenConfig* TokenConfig::create(QQmlEngine*, QJSEngine*) {
QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership);
return instance();
}
// Tokens (attached type)
@ -132,11 +121,6 @@ const SizeTokens* Tokens::sizes() const {
}
Tokens* Tokens::qmlAttachedProperties(QObject* object) {
// Ensure GlobalConfig singleton is created before any attached property access
if (!GlobalConfig::instance()) {
if (auto* engine = qmlEngine(object))
engine->singletonInstance<GlobalConfig*>("Caelestia.Config", "GlobalConfig");
}
return new Tokens(ConfigScope::find(object), object);
}

View file

@ -332,8 +332,6 @@ public:
[[nodiscard]] Q_INVOKABLE TokenConfig* defaults();
static TokenConfig* create(QQmlEngine*, QJSEngine*);
~TokenConfig() override;
private:
friend class MonitorConfigManager;
explicit TokenConfig(QObject* parent = nullptr);