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 { namespace {
GlobalConfig* s_instance = nullptr;
QString configDir() { QString configDir() {
return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/caelestia/"); return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/caelestia/");
} }
@ -36,14 +34,7 @@ GlobalConfig::GlobalConfig(QObject* parent)
, m_sidebar(new SidebarConfig(this)) , m_sidebar(new SidebarConfig(this))
, m_services(new ServiceConfig(this)) , m_services(new ServiceConfig(this))
, m_paths(new UserPaths(this)) { , m_paths(new UserPaths(this)) {
// Set global instance
s_instance = this;
setupFileBackend(configDir() + QStringLiteral("shell.json")); 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) GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, QObject* parent)
@ -72,13 +63,10 @@ GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, QObj
syncFromGlobal(fallback); syncFromGlobal(fallback);
} }
GlobalConfig::~GlobalConfig() {
if (s_instance == this)
s_instance = nullptr;
}
GlobalConfig* GlobalConfig::instance() { GlobalConfig* GlobalConfig::instance() {
return s_instance; static GlobalConfig instance;
instance.bindAppearanceTokens();
return &instance;
} }
GlobalConfig* GlobalConfig::defaults() { GlobalConfig* GlobalConfig::defaults() {
@ -91,14 +79,8 @@ void GlobalConfig::bindAppearanceTokens() {
if (m_tokensBound) if (m_tokensBound)
return; 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"; 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->rounding()->bindTokens(tokenAppearance->rounding());
m_appearance->spacing()->bindTokens(tokenAppearance->spacing()); m_appearance->spacing()->bindTokens(tokenAppearance->spacing());
m_appearance->padding()->bindTokens(tokenAppearance->padding()); m_appearance->padding()->bindTokens(tokenAppearance->padding());
@ -107,17 +89,9 @@ void GlobalConfig::bindAppearanceTokens() {
m_tokensBound = true; m_tokensBound = true;
} }
GlobalConfig* GlobalConfig::create(QQmlEngine* engine, QJSEngine* jsEngine) { GlobalConfig* GlobalConfig::create(QQmlEngine*, QJSEngine*) {
auto* config = new GlobalConfig(engine); QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership);
return instance();
// 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;
} }
// Config (attached type) // Config (attached type)
@ -140,8 +114,7 @@ void Config::connectScope() {
const Type* Config::name() const { \ const Type* Config::name() const { \
if (m_scope && m_scope->config()) \ if (m_scope && m_scope->config()) \
return m_scope->config()->name(); \ return m_scope->config()->name(); \
auto* global = GlobalConfig::instance(); \ return GlobalConfig::instance()->name(); \
return global ? global->name() : nullptr; \
} }
CONFIG_ATTACHED_GETTER(AppearanceConfig, appearance) CONFIG_ATTACHED_GETTER(AppearanceConfig, appearance)
@ -165,11 +138,6 @@ CONFIG_ATTACHED_GETTER(UserPaths, paths)
#undef CONFIG_ATTACHED_GETTER #undef CONFIG_ATTACHED_GETTER
Config* Config::qmlAttachedProperties(QObject* object) { 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); return new Config(ConfigScope::find(object), object);
} }

View file

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

View file

@ -9,8 +9,6 @@ namespace caelestia::config {
namespace { namespace {
TokenConfig* s_instance = nullptr;
QString configDir() { QString configDir() {
return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/caelestia/"); return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/caelestia/");
} }
@ -21,13 +19,7 @@ TokenConfig::TokenConfig(QObject* parent)
: RootConfig(parent) : RootConfig(parent)
, m_appearance(new AppearanceTokens(this)) , m_appearance(new AppearanceTokens(this))
, m_sizes(new SizeTokens(this)) { , m_sizes(new SizeTokens(this)) {
s_instance = this;
setupFileBackend(configDir() + QStringLiteral("shell-tokens.json")); 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) TokenConfig::TokenConfig(TokenConfig* fallback, const QString& filePath, QObject* parent)
@ -41,13 +33,9 @@ TokenConfig::TokenConfig(TokenConfig* fallback, const QString& filePath, QObject
syncFromGlobal(fallback); syncFromGlobal(fallback);
} }
TokenConfig::~TokenConfig() {
if (s_instance == this)
s_instance = nullptr;
}
TokenConfig* TokenConfig::instance() { TokenConfig* TokenConfig::instance() {
return s_instance; static TokenConfig instance;
return &instance;
} }
TokenConfig* TokenConfig::defaults() { TokenConfig* TokenConfig::defaults() {
@ -56,8 +44,9 @@ TokenConfig* TokenConfig::defaults() {
return m_defaults; return m_defaults;
} }
TokenConfig* TokenConfig::create(QQmlEngine* engine, QJSEngine*) { TokenConfig* TokenConfig::create(QQmlEngine*, QJSEngine*) {
return new TokenConfig(engine); QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership);
return instance();
} }
// Tokens (attached type) // Tokens (attached type)
@ -132,11 +121,6 @@ const SizeTokens* Tokens::sizes() const {
} }
Tokens* Tokens::qmlAttachedProperties(QObject* object) { 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); return new Tokens(ConfigScope::find(object), object);
} }

View file

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