From 4da0bffeee05c4589ae4d0addc7c695e31669990 Mon Sep 17 00:00:00 2001
From: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com>
Date: Sun, 8 Feb 2026 10:08:10 +0100
Subject: Add instance manager to plugin API (#2335)
---
src/CMakeLists.txt | 3 ++
src/envshortcut.cpp | 2 +-
src/instancemanager.cpp | 65 ++++++++++++++++++++++++++++++++++++-------
src/instancemanager.h | 50 +++++++++++++++++++++------------
src/instancemanagerdialog.cpp | 5 ----
src/instancemanagerproxy.cpp | 51 +++++++++++++++++++++++++++++++++
src/instancemanagerproxy.h | 50 +++++++++++++++++++++++++++++++++
src/moapplication.cpp | 2 +-
src/moapplication.h | 4 +--
src/organizerproxy.cpp | 8 ++++++
src/organizerproxy.h | 3 ++
11 files changed, 206 insertions(+), 37 deletions(-)
create mode 100644 src/instancemanagerproxy.cpp
create mode 100644 src/instancemanagerproxy.h
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 123e876b..8b425272 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -109,6 +109,7 @@ mo2_add_filter(NAME src/application GROUPS
multiprocess
sanitychecks
selfupdater
+ systemtraymanager
updatedialog
)
@@ -273,7 +274,9 @@ mo2_add_filter(NAME src/profiles GROUPS
mo2_add_filter(NAME src/proxies GROUPS
downloadmanagerproxy
+ executableslistproxy
gamefeaturesproxy
+ instancemanagerproxy
modlistproxy
organizerproxy
pluginlistproxy
diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp
index 204435b7..be696105 100644
--- a/src/envshortcut.cpp
+++ b/src/envshortcut.cpp
@@ -132,7 +132,7 @@ Shortcut::Shortcut() : m_iconIndex(0) {}
Shortcut::Shortcut(const Executable& exe) : Shortcut()
{
- const auto i = *InstanceManager::singleton().currentInstance();
+ const auto& i = *InstanceManager::singleton().currentInstance();
m_name = MOBase::sanitizeFileName(exe.title());
m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath();
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 90e9e519..fcb7bf67 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -39,6 +39,8 @@ along with Mod Organizer. If not, see .
#include
#include
#include
+#include
+#include
using namespace MOBase;
@@ -57,11 +59,25 @@ QString Instance::displayName() const
QString Instance::gameName() const
{
+ if (!m_iniValuesRead && m_gameName.isEmpty()) {
+ std::scoped_lock lock(m_iniValuesMutex);
+ if (!m_iniValuesRead) {
+ readFromIni();
+ }
+ }
+
return m_gameName;
}
QString Instance::gameDirectory() const
{
+ if (!m_iniValuesRead && m_gameDir.isEmpty()) {
+ std::scoped_lock lock(m_iniValuesMutex);
+ if (!m_iniValuesRead) {
+ readFromIni();
+ }
+ }
+
return m_gameDir;
}
@@ -72,6 +88,13 @@ QString Instance::directory() const
QString Instance::baseDirectory() const
{
+ if (!m_iniValuesRead) {
+ std::scoped_lock lock(m_iniValuesMutex);
+ if (!m_iniValuesRead) {
+ readFromIni();
+ }
+ }
+
return m_baseDir;
}
@@ -82,6 +105,13 @@ MOBase::IPluginGame* Instance::gamePlugin() const
QString Instance::profileName() const
{
+ if (!m_iniValuesRead) {
+ std::scoped_lock lock(m_iniValuesMutex);
+ if (!m_iniValuesRead) {
+ readFromIni();
+ }
+ }
+
return m_profile;
}
@@ -110,12 +140,13 @@ bool Instance::isActive() const
return false;
}
-bool Instance::readFromIni()
+bool Instance::readFromIni() const
{
Settings s(iniPath());
if (s.iniStatus() != QSettings::NoError) {
log::error("can't read ini {}", iniPath());
+ m_iniValuesRead = true;
return false;
}
@@ -143,14 +174,18 @@ bool Instance::readFromIni()
// figuring out profile from ini if it's missing
getProfile(s);
+ m_iniValuesRead = true;
return true;
}
Instance::SetupResults Instance::setup(PluginContainer& plugins)
{
- // read initial values from the ini
- if (!readFromIni()) {
- return SetupResults::BadIni;
+ if (!m_iniValuesRead) {
+ std::scoped_lock lock(m_iniValuesMutex);
+ // read initial values from the ini
+ if (!m_iniValuesRead && !readFromIni()) {
+ return SetupResults::BadIni;
+ }
}
// getting game plugin
@@ -310,7 +345,7 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins)
}
}
-void Instance::getProfile(const Settings& s)
+void Instance::getProfile(const Settings& s) const
{
if (!m_profile.isEmpty()) {
// there's already a profile set up, probably an override
@@ -511,7 +546,7 @@ void InstanceManager::clearOverrides()
m_overrideProfileName = {};
}
-std::unique_ptr InstanceManager::currentInstance() const
+std::shared_ptr InstanceManager::currentInstance() const
{
const QString profile = m_overrideProfileName ? *m_overrideProfileName : "";
@@ -520,20 +555,20 @@ std::unique_ptr InstanceManager::currentInstance() const
if (!allowedToChangeInstance()) {
// force portable instance
- return std::make_unique(portablePath(), true, profile);
+ return std::make_shared(portablePath(), true, profile);
}
if (name.isEmpty()) {
if (portableInstanceExists()) {
// use portable
- return std::make_unique(portablePath(), true, profile);
+ return std::make_shared(portablePath(), true, profile);
} else {
// no instance set
return {};
}
}
- return std::make_unique(instancePath(name), false, profile);
+ return std::make_shared(instancePath(name), false, profile);
}
void InstanceManager::clearCurrentInstance()
@@ -583,6 +618,16 @@ std::vector InstanceManager::globalInstancePaths() const
return list;
}
+std::shared_ptr
+InstanceManager::getGlobalInstance(const QString& instanceName) const
+{
+ if (!instanceExists(instanceName)) {
+ return nullptr;
+ }
+
+ return std::make_shared(instancePath(instanceName), false, "");
+}
+
bool InstanceManager::hasAnyInstances() const
{
return portableInstanceExists() || !globalInstancePaths().empty();
@@ -696,7 +741,7 @@ bool InstanceManager::instanceExists(const QString& instanceName) const
return root.exists(instanceName);
}
-std::unique_ptr selectInstance()
+std::shared_ptr selectInstance()
{
auto& m = InstanceManager::singleton();
diff --git a/src/instancemanager.h b/src/instancemanager.h
index 176033e8..61cee327 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -1,9 +1,16 @@
#ifndef MODORGANIZER_INSTANCEMANAGER_INCLUDED
#define MODORGANIZER_INSTANCEMANAGER_INCLUDED
+#include
#include
#include
+#include
+#include
+#include
+
+#include
+
namespace MOBase
{
class IPluginGame;
@@ -26,7 +33,7 @@ class PluginContainer;
// setGame() and setVariant() can be called before retrying setup(); this
// happens on startup if that information is missing
//
-class Instance
+class Instance : public MOBase::IInstance
{
public:
// returned by setup()
@@ -92,18 +99,6 @@ public:
//
Instance(QString dir, bool portable, QString profileName = {});
- // reads in values from the INI if they were not given yet:
- // - game name
- // - game directory
- // - game variant
- // - profile name
- //
- // note that setup() already calls this
- //
- // returns false if the ini couldn't be read from
- //
- bool readFromIni();
-
// finds the appropriate game plugin and sets it up so MO can use it; this
// calls readFromIni() first
//
@@ -192,9 +187,24 @@ public:
private:
QString m_dir;
bool m_portable;
- QString m_gameName, m_gameDir, m_gameVariant, m_baseDir;
+ mutable QString m_gameName, m_gameDir, m_gameVariant, m_baseDir;
MOBase::IPluginGame* m_plugin;
- QString m_profile;
+ mutable QString m_profile;
+
+ mutable std::mutex m_iniValuesMutex;
+ mutable std::atomic m_iniValuesRead{false};
+
+ // reads in values from the INI if they were not given yet:
+ // - game name
+ // - game directory
+ // - game variant
+ // - profile name
+ //
+ // note that setup() already calls this
+ //
+ // returns false if the ini couldn't be read from
+ //
+ bool readFromIni() const;
// figures out the game plugin for this instance
//
@@ -202,7 +212,7 @@ private:
// figures out the profile name for this instance
//
- void getProfile(const Settings& s);
+ void getProfile(const Settings& s) const;
// updates the ini with the given values and the ones found by setup()
//
@@ -259,7 +269,7 @@ public:
// instance name in the registry is empty or non-existent and there is no
// portable instance set up
//
- std::unique_ptr currentInstance() const;
+ std::shared_ptr currentInstance() const;
// sets the instance name in the registry so the same instance is opened next
// time MO runs
@@ -295,6 +305,10 @@ public:
//
std::vector globalInstancePaths() const;
+ // returns the global Instance with the given name, or null if it doesn't exist
+ //
+ std::shared_ptr getGlobalInstance(const QString& instanceName) const;
+
// sanitizes the given instance name and either
// 1) returns it if there is no instance with this name
// 2) tries to add " (N)" at the end until it works
@@ -343,7 +357,7 @@ enum class SetupInstanceResults
// instance manager dialog and returns the selected instance or empty if the
// user cancelled
//
-std::unique_ptr selectInstance();
+std::shared_ptr selectInstance();
// calls instance.setup() tries to handle problems by itself:
//
diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp
index b5139e90..06e6d796 100644
--- a/src/instancemanagerdialog.cpp
+++ b/src/instancemanagerdialog.cpp
@@ -257,11 +257,6 @@ void InstanceManagerDialog::updateInstances()
m_instances.insert(m_instances.begin(),
std::make_unique(m.portablePath(), true));
}
-
- // read all inis, ignore errors
- for (auto&& i : m_instances) {
- i->readFromIni();
- }
}
void InstanceManagerDialog::updateList()
diff --git a/src/instancemanagerproxy.cpp b/src/instancemanagerproxy.cpp
new file mode 100644
index 00000000..2ff7f714
--- /dev/null
+++ b/src/instancemanagerproxy.cpp
@@ -0,0 +1,51 @@
+/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see .
+*/
+
+#include "instancemanagerproxy.h"
+
+#include "instancemanager.h"
+
+#include
+#include
+
+#include
+#include
+
+#include
+
+InstanceManagerProxy::InstanceManagerProxy(InstanceManager* instanceManager)
+ : m_Proxied(instanceManager)
+{}
+
+std::shared_ptr InstanceManagerProxy::currentInstance() const
+{
+ return m_Proxied->currentInstance();
+}
+
+std::vector InstanceManagerProxy::globalInstancePaths() const
+{
+ const auto globalPaths = m_Proxied->globalInstancePaths();
+ return std::vector(globalPaths.begin(), globalPaths.end());
+}
+
+std::shared_ptr
+InstanceManagerProxy::getGlobalInstance(const QString& instanceName) const
+{
+ return m_Proxied->getGlobalInstance(instanceName);
+}
diff --git a/src/instancemanagerproxy.h b/src/instancemanagerproxy.h
new file mode 100644
index 00000000..3091db09
--- /dev/null
+++ b/src/instancemanagerproxy.h
@@ -0,0 +1,50 @@
+/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see .
+*/
+
+#ifndef INSTANCEMANAGERPROXY_H
+#define INSTANCEMANAGERPROXY_H
+
+#include "instancemanager.h"
+
+#include
+#include
+
+#include
+#include
+
+#include
+
+namespace MOBase
+{
+class IInstance;
+}
+
+class InstanceManagerProxy : public MOBase::IInstanceManager
+{
+public:
+ InstanceManagerProxy(InstanceManager* instanceManager);
+ std::shared_ptr currentInstance() const override;
+ std::vector globalInstancePaths() const override;
+ std::shared_ptr
+ getGlobalInstance(const QString& instanceName) const override;
+
+private:
+ InstanceManager* m_Proxied;
+};
+#endif // INSTANCEMANAGERPROXY_H
diff --git a/src/moapplication.cpp b/src/moapplication.cpp
index a92be85e..e676436a 100644
--- a/src/moapplication.cpp
+++ b/src/moapplication.cpp
@@ -439,7 +439,7 @@ void MOApplication::externalMessage(const QString& message)
}
}
-std::unique_ptr MOApplication::getCurrentInstance(bool forceSelect)
+std::shared_ptr MOApplication::getCurrentInstance(bool forceSelect)
{
auto& m = InstanceManager::singleton();
auto currentInstance = m.currentInstance();
diff --git a/src/moapplication.h b/src/moapplication.h
index 498242f3..f3805545 100644
--- a/src/moapplication.h
+++ b/src/moapplication.h
@@ -81,14 +81,14 @@ private:
QString m_defaultStyle;
std::unique_ptr m_modules;
- std::unique_ptr m_instance;
+ std::shared_ptr m_instance;
std::unique_ptr m_settings;
std::unique_ptr m_nexus;
std::unique_ptr m_plugins;
std::unique_ptr m_core;
void externalMessage(const QString& message);
- std::unique_ptr getCurrentInstance(bool forceSelect);
+ std::shared_ptr getCurrentInstance(bool forceSelect);
std::optional setupInstanceLoop(Instance& currentInstance, PluginContainer& pc);
void purgeOldFiles();
};
diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp
index 973af3d1..90ab900a 100644
--- a/src/organizerproxy.cpp
+++ b/src/organizerproxy.cpp
@@ -5,6 +5,7 @@
#include "gamefeaturesproxy.h"
#include "glob_matching.h"
#include "instancemanager.h"
+#include "instancemanagerproxy.h"
#include "modlistproxy.h"
#include "organizercore.h"
#include "plugincontainer.h"
@@ -26,6 +27,8 @@ OrganizerProxy::OrganizerProxy(OrganizerCore* organizer,
PluginContainer* pluginContainer,
MOBase::IPlugin* plugin)
: m_Proxied(organizer), m_PluginContainer(pluginContainer), m_Plugin(plugin),
+ m_InstanceManagerProxy(
+ std::make_unique(&InstanceManager::singleton())),
m_DownloadManagerProxy(
std::make_unique(this, organizer->downloadManager())),
m_ModListProxy(std::make_unique(this, organizer->modList())),
@@ -353,6 +356,11 @@ std::shared_ptr OrganizerProxy::virtualFileTree() const
return m_Proxied->m_VirtualFileTree.value();
}
+MOBase::IInstanceManager* OrganizerProxy::instanceManager() const
+{
+ return m_InstanceManagerProxy.get();
+}
+
MOBase::IDownloadManager* OrganizerProxy::downloadManager() const
{
return m_DownloadManagerProxy.get();
diff --git a/src/organizerproxy.h b/src/organizerproxy.h
index 8382bd24..01271990 100644
--- a/src/organizerproxy.h
+++ b/src/organizerproxy.h
@@ -12,6 +12,7 @@ class GameFeaturesProxy;
class PluginContainer;
class DownloadManagerProxy;
class ExecutablesListProxy;
+class InstanceManagerProxy;
class ModListProxy;
class PluginListProxy;
@@ -63,6 +64,7 @@ public: // IOrganizer interface
const std::function& filter) const override;
std::shared_ptr virtualFileTree() const override;
+ MOBase::IInstanceManager* instanceManager() const override;
MOBase::IDownloadManager* downloadManager() const override;
MOBase::IPluginList* pluginList() const override;
MOBase::IModList* modList() const override;
@@ -156,6 +158,7 @@ private:
std::vector m_Connections;
+ std::unique_ptr m_InstanceManagerProxy;
std::unique_ptr m_DownloadManagerProxy;
std::unique_ptr m_ModListProxy;
std::unique_ptr m_PluginListProxy;
--
cgit v1.3.1