diff --git a/src/backend/game/game.cpp b/src/backend/game/game.cpp index 4a6334fb..67fea97b 100644 --- a/src/backend/game/game.cpp +++ b/src/backend/game/game.cpp @@ -41,7 +41,7 @@ namespace lc = boost::locale; namespace loot { Game::Game() : _pluginsFullyLoaded(false) {} - Game::Game(const GameSettings& gameSettings) : GameSettings(gameSettings.Id(), gameSettings.FolderName()), _pluginsFullyLoaded(false) { + Game::Game(const GameSettings& gameSettings) : GameSettings(gameSettings), _pluginsFullyLoaded(false) { this->SetName(gameSettings.Name()) .SetMaster(gameSettings.Master()) .SetRepoURL(gameSettings.RepoURL()) @@ -50,8 +50,31 @@ namespace loot { .SetRegistryKey(gameSettings.RegistryKey()); } + Game::Game(const Game& game) : + GameSettings(game), + LoadOrderHandler(game), + GameCache(game), + masterlist(game.masterlist), + userlist(game.userlist), + plugins(game.plugins), + _pluginsFullyLoaded(game.ArePluginsFullyLoaded()) {} + Game::Game(const unsigned int gameCode, const std::string& folder) : GameSettings(gameCode, folder), _pluginsFullyLoaded(false) {} + Game& Game::operator= (const Game& game) { + if (&game != this) { + GameSettings::operator=(game); + LoadOrderHandler::operator=(game); + GameCache::operator=(game); + + masterlist = game.masterlist; + userlist = game.userlist; + plugins = game.plugins; + _pluginsFullyLoaded = game.ArePluginsFullyLoaded(); + } + return *this; + } + void Game::Init(bool createFolder, const boost::filesystem::path& gameLocalAppData) { if (Id() != Game::tes4 && Id() != Game::tes5 && Id() != Game::fo3 && Id() != Game::fonv && Id() != Game::fo4) { throw error(error::invalid_args, lc::translate("Invalid game ID supplied.").str()); @@ -118,29 +141,35 @@ namespace loot { BOOST_LOG_TRIVIAL(trace) << "Scanning for plugins in " << this->DataPath(); for (fs::directory_iterator it(this->DataPath()); it != fs::directory_iterator(); ++it) { if (fs::is_regular_file(it->status()) && Plugin::IsValid(it->path().filename().string(), *this)) { - Plugin temp(it->path().filename().string()); - BOOST_LOG_TRIVIAL(info) << "Found plugin: " << temp.Name(); + string name = it->path().filename().string(); + BOOST_LOG_TRIVIAL(info) << "Found plugin: " << name; + + // Trim .ghost extension if present. + if (boost::iends_with(name, ".ghost")) + name = name.substr(0, name.length() - 6); uintmax_t fileSize = fs::file_size(it->path()); meanFileSize += fileSize; - //Insert the lowercased name as a key for case-insensitive matching. - string name = boost::locale::to_lower(temp.Name()); - plugins.insert(pair(name, temp)); - sizeMap.insert(pair(fileSize, name)); + sizeMap.emplace(fileSize, name); } } meanFileSize /= sizeMap.size(); //Rounding error, but not important. + // Reserve space in the plugins unordered_map to speed up inserting + // later and more importantly avoid any inserts invalidating + // iterators. + plugins.reserve(sizeMap.size()); + // Get the number of threads to use. // hardware_concurrency() may be zero, if so then use only one thread. - size_t threadsToUse = std::min((size_t)thread::hardware_concurrency(), plugins.size()); + size_t threadsToUse = std::min((size_t)thread::hardware_concurrency(), sizeMap.size()); threadsToUse = std::max(threadsToUse, (size_t)1); // Divide the plugins up by thread. - unsigned int pluginsPerThread = ceil((double)plugins.size() / threadsToUse); - vector::iterator>> pluginGroups(threadsToUse); - BOOST_LOG_TRIVIAL(info) << "Loading " << plugins.size() << " plugins using " << threadsToUse << " threads, with up to " << pluginsPerThread << " plugins per thread."; + unsigned int pluginsPerThread = ceil((double)sizeMap.size() / threadsToUse); + vector> pluginGroups(threadsToUse); + BOOST_LOG_TRIVIAL(info) << "Loading " << sizeMap.size() << " plugins using " << threadsToUse << " threads, with up to " << pluginsPerThread << " plugins per thread."; // The plugins should be split between the threads so that the data // load is as evenly spread as possible. @@ -149,7 +178,7 @@ namespace loot { if (currentGroup == threadsToUse) currentGroup = 0; BOOST_LOG_TRIVIAL(trace) << "Adding plugin " << plugin.second << " to loading group " << currentGroup; - pluginGroups[currentGroup].push_back(plugins.find(plugin.second)); + pluginGroups[currentGroup].push_back(plugin.second); ++currentGroup; } @@ -157,19 +186,11 @@ namespace loot { BOOST_LOG_TRIVIAL(trace) << "Starting plugin loading."; vector threads; while (threads.size() < threadsToUse) { - vector::iterator>& pluginGroup = pluginGroups[threads.size()]; - threads.push_back(thread([this, &pluginGroup, headersOnly]() { - for (auto it : pluginGroup) { - BOOST_LOG_TRIVIAL(trace) << "Loading " << it->second.Name(); - try { - it->second = Plugin(*this, it->second.Name(), headersOnly); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << it->second.Name() << ": Exception occurred: " << e.what(); - Plugin p(it->second.Name()); - p.Messages(list(1, Message(Message::error, lc::translate("An exception occurred while loading this plugin. Details:").str() + " " + e.what()))); - it->second = p; - } + vector& pluginGroup = pluginGroups[threads.size()]; + threads.push_back(thread([&]() { + for (auto pluginName : pluginGroup) { + BOOST_LOG_TRIVIAL(trace) << "Loading " << pluginName; + addPlugin(Plugin(*this, pluginName, headersOnly)); } })); } @@ -187,6 +208,11 @@ namespace loot { return _pluginsFullyLoaded; } + void Game::addPlugin(const Plugin&& plugin) { + std::lock_guard lock(mutex); + plugins.emplace(boost::locale::to_lower(plugin.Name()), plugin); + } + std::list ToGames(const std::list& settings) { return list(settings.begin(), settings.end()); } diff --git a/src/backend/game/game.h b/src/backend/game/game.h index 3d242291..92a78638 100644 --- a/src/backend/game/game.h +++ b/src/backend/game/game.h @@ -43,8 +43,11 @@ namespace loot { //Game functions. Game(); //Sets game to LOOT_Game::autodetect, with all other vars being empty. Game(const GameSettings& gameSettings); + Game(const Game& game); Game(const unsigned int baseGameCode, const std::string& lootFolder = ""); + Game& operator= (const Game& game); + void Init(bool createFolder, const boost::filesystem::path& gameLocalAppData = ""); void RedatePlugins(); //Change timestamps to match load order (Skyrim only). @@ -58,6 +61,9 @@ namespace loot { std::unordered_map plugins; //Map so that plugin data can be edited. private: bool _pluginsFullyLoaded; + std::mutex mutex; + + void addPlugin(const Plugin&& plugin); }; std::list ToGames(const std::list& settings); diff --git a/src/backend/plugin/plugin.cpp b/src/backend/plugin/plugin.cpp index b38494c9..27b45b3a 100644 --- a/src/backend/plugin/plugin.cpp +++ b/src/backend/plugin/plugin.cpp @@ -37,16 +37,6 @@ using namespace std; using libespm::FormId; namespace loot { - // TODO: Remove the name-only constructor. - Plugin::Plugin(const std::string& n) : - PluginMetadata(n), - libespm::Plugin(libespm::GameId::SKYRIM), - _isEmpty(true), - _isActive(false), - _loadsBsa(false), - crc(0), - numOverrideRecords(0) {} - Plugin::Plugin(const Game& game, const std::string& name, const bool headerOnly) : PluginMetadata(name), libespm::Plugin(game.LibespmId()), diff --git a/src/backend/plugin/plugin.h b/src/backend/plugin/plugin.h index 747068d7..36a0ffe0 100644 --- a/src/backend/plugin/plugin.h +++ b/src/backend/plugin/plugin.h @@ -41,7 +41,6 @@ namespace loot { class Plugin : public PluginMetadata, private libespm::Plugin { public: - Plugin(const std::string& name); Plugin(const Game& game, const std::string& name, const bool headerOnly); using libespm::Plugin::getDescription; diff --git a/src/tests/backend/plugin/test_plugin.h b/src/tests/backend/plugin/test_plugin.h index aa27a8a2..b6144330 100644 --- a/src/tests/backend/plugin/test_plugin.h +++ b/src/tests/backend/plugin/test_plugin.h @@ -28,23 +28,21 @@ along with LOOT. If not, see #include "backend/plugin/plugin.h" #include "tests/fixtures.h" -class Plugin : public SkyrimTest {}; +class Plugin : public SkyrimTest { +protected: + inline virtual void SetUp() { + SkyrimTest::SetUp(); + + game = loot::Game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + } + + loot::Game game; +}; TEST_F(Plugin, ConstructorsAndDataAccess) { - loot::Plugin plugin("Blank.esm"); - EXPECT_EQ("Blank.esm", plugin.Name()); - EXPECT_TRUE(plugin.getFormIds().empty()); - EXPECT_TRUE(plugin.getMasters().empty()); - EXPECT_FALSE(plugin.isMasterFile()); - EXPECT_TRUE(plugin.IsEmpty()); - EXPECT_EQ("", plugin.getDescription()); - EXPECT_EQ(0, plugin.Crc()); - - loot::Game game(loot::Game::tes5); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - - plugin = loot::Plugin(game, "Blank.esm", true); + loot::Plugin plugin(game, "Blank.esm", true); EXPECT_EQ("Blank.esm", plugin.Name()); EXPECT_TRUE(plugin.getFormIds().empty()); EXPECT_TRUE(plugin.getMasters().empty()); @@ -91,31 +89,18 @@ TEST_F(Plugin, ConstructorsAndDataAccess) { } TEST_F(Plugin, LoadsBSA) { - loot::Game game(loot::Game::tes5); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - EXPECT_FALSE(loot::Plugin(game, "Blank - Different.esm", true).LoadsBSA()); EXPECT_FALSE(loot::Plugin(game, "Blank\\.esm", true).LoadsBSA()); - EXPECT_FALSE(loot::Plugin("Blank.esm").LoadsBSA()); EXPECT_TRUE(loot::Plugin(game, "Blank.esm", true).LoadsBSA()); } TEST_F(Plugin, IsValid) { - loot::Game game(loot::Game::tes5); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - EXPECT_TRUE(loot::Plugin::IsValid("Blank.esm", game)); EXPECT_FALSE(loot::Plugin::IsValid("NotAPlugin.esm", game)); EXPECT_FALSE(loot::Plugin::IsValid("EmptyFile.esm", game)); } TEST_F(Plugin, IsActive) { - loot::Game game(loot::Game::tes5); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - loot::Plugin plugin(game, "Blank.esm", true); EXPECT_TRUE(plugin.IsActive()); @@ -124,76 +109,62 @@ TEST_F(Plugin, IsActive) { } TEST_F(Plugin, EqualityOperator) { - loot::Plugin plugin1("Blank.esm"); - loot::Plugin plugin2("blank.esm"); + loot::Plugin plugin1(game, "Blank.esm", true); + loot::Plugin plugin2(game, "blank.esm", true); EXPECT_TRUE(plugin1 == plugin2); EXPECT_TRUE(plugin2 == plugin1); - plugin1 = loot::Plugin("Blank.esm"); - plugin2 = loot::Plugin("Blank.esp"); + plugin2 = loot::Plugin(game, "Blank.esp", true); EXPECT_FALSE(plugin1 == plugin2); EXPECT_FALSE(plugin2 == plugin1); - plugin1 = loot::Plugin("Blank.esm"); - plugin2 = loot::Plugin("Blan.\\.esm"); + plugin2 = loot::Plugin(game, "Blan.\\.esm", true); EXPECT_TRUE(plugin1 == plugin2); EXPECT_TRUE(plugin2 == plugin1); - plugin1 = loot::Plugin("Blan.esm"); - plugin2 = loot::Plugin("Blan.\\.esm"); + plugin1 = loot::Plugin(game, "Blan.esm", true); EXPECT_FALSE(plugin1 == plugin2); EXPECT_FALSE(plugin2 == plugin1); - plugin1 = loot::Plugin("Blan.\\.esm"); - plugin2 = loot::Plugin("Blan.\\.esm"); + plugin1 = loot::Plugin(game, "Blan.\\.esm", true); EXPECT_TRUE(plugin1 == plugin2); EXPECT_TRUE(plugin2 == plugin1); - plugin1 = loot::Plugin("Blan(k|p).esm"); - plugin2 = loot::Plugin("Blan.\\.esm"); + plugin1 = loot::Plugin(game, "Blan(k|p).esm", true); EXPECT_FALSE(plugin1 == plugin2); EXPECT_FALSE(plugin2 == plugin1); } TEST_F(Plugin, InequalityOperator) { - loot::Plugin plugin1("Blank.esm"); - loot::Plugin plugin2("blank.esm"); + loot::Plugin plugin1(game, "Blank.esm", true); + loot::Plugin plugin2(game, "blank.esm", true); EXPECT_FALSE(plugin1 != plugin2); EXPECT_FALSE(plugin2 != plugin1); - plugin1 = loot::Plugin("Blank.esm"); - plugin2 = loot::Plugin("Blank.esp"); + plugin2 = loot::Plugin(game, "Blank.esp", true); EXPECT_TRUE(plugin1 != plugin2); EXPECT_TRUE(plugin2 != plugin1); - plugin1 = loot::Plugin("Blank.esm"); - plugin2 = loot::Plugin("Blan.\\.esm"); + plugin2 = loot::Plugin(game, "Blan.\\.esm", true); EXPECT_FALSE(plugin1 != plugin2); EXPECT_FALSE(plugin2 != plugin1); - plugin1 = loot::Plugin("Blan.esm"); - plugin2 = loot::Plugin("Blan.\\.esm"); + plugin1 = loot::Plugin(game, "Blan.esm", true); EXPECT_TRUE(plugin1 != plugin2); EXPECT_TRUE(plugin2 != plugin1); - plugin1 = loot::Plugin("Blan.\\.esm"); - plugin2 = loot::Plugin("Blan.\\.esm"); + plugin1 = loot::Plugin(game, "Blan.\\.esm", true); EXPECT_FALSE(plugin1 != plugin2); EXPECT_FALSE(plugin2 != plugin1); - plugin1 = loot::Plugin("Blan(k|p).esm"); - plugin2 = loot::Plugin("Blan.\\.esm"); + plugin1 = loot::Plugin(game, "Blan(k|p).esm", true); EXPECT_TRUE(plugin1 != plugin2); EXPECT_TRUE(plugin2 != plugin1); } TEST_F(Plugin, DoFormIDsOverlap) { - loot::Game game(loot::Game::tes5); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - - loot::Plugin plugin1("Blank.esm"); - loot::Plugin plugin2("blank.esm"); + loot::Plugin plugin1(game, "Blank.esm", true); + loot::Plugin plugin2(game, "blank.esm", true); EXPECT_FALSE(plugin1.DoFormIDsOverlap(plugin2)); EXPECT_FALSE(plugin2.DoFormIDsOverlap(plugin1)); @@ -214,12 +185,8 @@ TEST_F(Plugin, DoFormIDsOverlap) { } TEST_F(Plugin, OverlapFormIDs) { - loot::Game game(loot::Game::tes5); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - - loot::Plugin plugin1("Blank.esm"); - loot::Plugin plugin2("blank.esm"); + loot::Plugin plugin1(game, "Blank.esm", true); + loot::Plugin plugin2(game, "blank.esm", true); EXPECT_TRUE(plugin1.OverlapFormIDs(plugin2).empty()); EXPECT_TRUE(plugin2.OverlapFormIDs(plugin1).empty()); @@ -250,15 +217,7 @@ TEST_F(Plugin, OverlapFormIDs) { } TEST_F(Plugin, CheckInstallValidity) { - loot::Game game(loot::Game::tes5); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - - loot::Plugin plugin("Blank.esm"); - EXPECT_FALSE(plugin.CheckInstallValidity(game)); - EXPECT_TRUE(plugin.Messages().empty()); - - plugin = loot::Plugin(game, "Blank.esm", false); + loot::Plugin plugin(game, "Blank.esm", false); plugin.Reqs({ loot::File("Blank.missing.esm"), loot::File("Blank.esp"), @@ -286,7 +245,7 @@ TEST_F(Plugin, CheckInstallValidity) { loot::Message(loot::Message::error, "This plugin requires \"Blank - Different.esm\" to be active, but it is inactive."), }), plugin.Messages()); - plugin = loot::Plugin("Blank - Different Master Dependent.esp"); + plugin = loot::Plugin(game, "Blank - Different Master Dependent.esp", false); plugin.Tags({loot::Tag("Filter")}); EXPECT_FALSE(plugin.CheckInstallValidity(game)); EXPECT_TRUE(plugin.Messages().empty());