From 0aee45a1174cecca6ed4bd703ef6299185fec6b8 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sun, 23 Jul 2023 22:44:02 +1000 Subject: [PATCH] Cache certain item generation calls from interfaces Helps a little with the lag from recipes when having crafting interfaces open, but it's still noticeable. Also micro-optimized Root maintenance by unlocking the Root mutexes for their respective shared_ptrs earlier once we have our own shared_ptr. --- source/frontend/StarContainerInterface.cpp | 2 +- source/frontend/StarCraftingInterface.cpp | 18 +++---- source/frontend/StarMerchantInterface.cpp | 6 +-- source/game/StarCollectionDatabase.cpp | 2 +- source/game/StarItemDatabase.cpp | 61 +++++++++++++++++----- source/game/StarItemDatabase.hpp | 13 +++++ source/game/StarPlayer.cpp | 2 +- source/game/StarQuestDescriptor.cpp | 4 +- source/game/StarRoot.cpp | 31 ++++++++--- 9 files changed, 100 insertions(+), 39 deletions(-) diff --git a/source/frontend/StarContainerInterface.cpp b/source/frontend/StarContainerInterface.cpp index 6540dfb..d69138a 100644 --- a/source/frontend/StarContainerInterface.cpp +++ b/source/frontend/StarContainerInterface.cpp @@ -142,7 +142,7 @@ ContainerPane::ContainerPane(WorldClientPtr worldClient, PlayerPtr player, Conta if (container->iconItem()) { auto itemDatabase = Root::singleton().itemDatabase(); - auto iconItem = itemDatabase->item(container->iconItem()); + auto iconItem = itemDatabase->itemShared(container->iconItem()); auto icon = make_shared(iconItem, "/interface/inventory/portrait.png"); icon->showDurability(false); icon->showRarity(false); diff --git a/source/frontend/StarCraftingInterface.cpp b/source/frontend/StarCraftingInterface.cpp index 5ad25ce..3156a78 100644 --- a/source/frontend/StarCraftingInterface.cpp +++ b/source/frontend/StarCraftingInterface.cpp @@ -127,7 +127,7 @@ CraftingPane::CraftingPane(WorldClientPtr worldClient, PlayerPtr player, Json co if (auto container = as(entity)) { if (container->iconItem()) { auto itemDatabase = Root::singleton().itemDatabase(); - auto iconItem = itemDatabase->item(container->iconItem()); + auto iconItem = itemDatabase->itemShared(container->iconItem()); auto icon = make_shared(iconItem, "/interface/inventory/portrait.png"); String title = this->title(); if (title.empty()) @@ -259,7 +259,7 @@ void CraftingPane::update(float dt) { auto description = fetchChild("description"); description->removeAllChildren(); - auto item = Root::singleton().itemDatabase()->item(recipe.output); + auto item = Root::singleton().itemDatabase()->itemShared(recipe.output); ItemTooltipBuilder::buildItemDescription(description, item); } } @@ -383,7 +383,7 @@ void CraftingPane::setupWidget(WidgetPtr const& widget, ItemRecipe const& recipe auto single = recipe.output.singular(); ItemPtr item = m_itemCache[single]; if (!item) { - item = root.itemDatabase()->item(single); + item = root.itemDatabase()->itemShared(single); m_itemCache[single] = item; } @@ -475,13 +475,13 @@ PanePtr CraftingPane::setupTooltip(ItemRecipe const& recipe) { auto currenciesConfig = root.assets()->json("/currencies.config"); for (auto const& p : recipe.currencyInputs) { if (p.second > 0) { - auto currencyItem = root.itemDatabase()->item(ItemDescriptor(currenciesConfig.get(p.first).getString("representativeItem"))); + auto currencyItem = root.itemDatabase()->itemShared(ItemDescriptor(currenciesConfig.get(p.first).getString("representativeItem"))); addIngredient(currencyItem, m_player->currency(p.first), p.second); } } for (auto const& input : recipe.inputs) { - auto item = root.itemDatabase()->item(input.singular()); + auto item = root.itemDatabase()->itemShared(input.singular()); size_t itemCount = itemDb->getCountOfItem(normalizedBag, input, recipe.matchInputParameters); addIngredient(item, itemCount, input.count()); } @@ -576,7 +576,7 @@ void CraftingPane::craft(int count) { remainingItemCount -= craftedItem->count(); m_player->giveItem(craftedItem); - for (auto collectable : recipe.collectables) + for (auto& collectable : recipe.collectables) m_player->addCollectable(collectable.first, collectable.second); } @@ -666,10 +666,10 @@ List CraftingPane::determineRecipes() { float printTime = m_settings.getFloat("printTime", 0); float printFactor = m_settings.getFloat("printCostFactor", 1.0); - for (auto itemName : itemList) { + for (auto& itemName : itemList) { ItemRecipe recipe; recipe.output = ItemDescriptor(itemName, 1); - auto recipeItem = itemDb->item(recipe.output); + auto recipeItem = itemDb->itemShared(recipe.output); int itemPrice = int(recipeItem->price() * printFactor); recipe.currencyInputs["money"] = itemPrice; recipe.outputRarity = recipeItem->rarity(); @@ -679,7 +679,7 @@ List CraftingPane::determineRecipes() { recipes.add(recipe); } } else if (m_settings.contains("recipes")) { - for (auto entry : m_settings.getArray("recipes")) { + for (auto& entry : m_settings.getArray("recipes")) { if (entry.type() == Json::Type::String) recipes.addAll(itemDb->recipesForOutputItem(entry.toString())); else diff --git a/source/frontend/StarMerchantInterface.cpp b/source/frontend/StarMerchantInterface.cpp index 8e3ad3a..17f4995 100644 --- a/source/frontend/StarMerchantInterface.cpp +++ b/source/frontend/StarMerchantInterface.cpp @@ -127,7 +127,7 @@ PanePtr MerchantPane::createTooltip(Vec2I const& screenPosition) { auto entry = m_itemGuiList->itemAt(i); if (entry->getChildAt(screenPosition)) { auto itemConfig = m_itemList.get(i); - ItemPtr item = Root::singleton().itemDatabase()->item(ItemDescriptor(itemConfig.get("item"))); + ItemPtr item = Root::singleton().itemDatabase()->itemShared(ItemDescriptor(itemConfig.get("item"))); return ItemTooltipBuilder::buildItemTooltip(item, m_player); } } @@ -232,7 +232,7 @@ void MerchantPane::buildItemList() { void MerchantPane::setupWidget(WidgetPtr const& widget, Json const& itemConfig) { auto& root = Root::singleton(); auto assets = root.assets(); - ItemPtr item = root.itemDatabase()->item(ItemDescriptor(itemConfig.get("item"))); + ItemPtr item = root.itemDatabase()->itemShared(ItemDescriptor(itemConfig.get("item"))); String name = item->friendlyName(); if (item->count() > 1) @@ -265,7 +265,7 @@ void MerchantPane::updateSelection() { if (m_selectedIndex != NPos) { auto itemConfig = m_itemList.get(m_selectedIndex); - m_selectedItem = Root::singleton().itemDatabase()->item(ItemDescriptor(itemConfig.get("item"))); + m_selectedItem = Root::singleton().itemDatabase()->itemShared(ItemDescriptor(itemConfig.get("item"))); findChild("spinCount.up")->enable(); findChild("spinCount.down")->enable(); m_countTextBox->setColor(Color::White); diff --git a/source/game/StarCollectionDatabase.cpp b/source/game/StarCollectionDatabase.cpp index d8d573d..40e7595 100644 --- a/source/game/StarCollectionDatabase.cpp +++ b/source/game/StarCollectionDatabase.cpp @@ -109,7 +109,7 @@ Collectable CollectionDatabase::parseMonsterCollectable(String const& name, Json Collectable CollectionDatabase::parseItemCollectable(String const& name, Json const& config) const { Collectable collectable = parseGenericCollectable(name, config); auto itemDatabase = Root::singleton().itemDatabase(); - auto item = itemDatabase->item(ItemDescriptor(config.getString("item"))); + auto item = itemDatabase->itemShared(ItemDescriptor(config.getString("item"))); collectable.title = item->friendlyName(); collectable.description = item->description(); diff --git a/source/game/StarItemDatabase.cpp b/source/game/StarItemDatabase.cpp index dfeddfd..200ebc1 100644 --- a/source/game/StarItemDatabase.cpp +++ b/source/game/StarItemDatabase.cpp @@ -140,6 +140,15 @@ ItemDatabase::ItemDatabase() addBlueprints(); } +void ItemDatabase::cleanup() { + { + MutexLocker locker(m_cacheMutex); + m_itemCache.cleanup([](ItemCacheEntry const&, ItemPtr const& item) { + return !item.unique(); + }); + } +} + ItemPtr ItemDatabase::diskLoad(Json const& diskStore) const { if (diskStore) { return item(ItemDescriptor::loadStore(diskStore)); @@ -204,20 +213,30 @@ ItemDatabase::ItemConfig ItemDatabase::itemConfig(String const& itemName, Json p return itemConfig; } -ItemPtr ItemDatabase::item(ItemDescriptor descriptor, Maybe level, Maybe seed) const { +ItemPtr ItemDatabase::itemShared(ItemDescriptor descriptor, Maybe level, Maybe seed) const { if (!descriptor) return {}; - ItemPtr item; - try { - item = createItem(m_items.get(descriptor.name()).type, itemConfig(descriptor.name(), descriptor.parameters(), level, seed)); - } catch (std::exception const& e) { - Logger::error("Could not instantiate item '{}'. {}", descriptor, outputException(e, false)); - item = createItem(m_items.get("perfectlygenericitem").type, itemConfig("perfectlygenericitem", {}, {})); - } - item->setCount(descriptor.count()); + ItemCacheEntry entry{ descriptor, level, seed }; + MutexLocker locker(m_cacheMutex); + if (ItemPtr* cached = m_itemCache.ptr(entry)) + return *cached; + else { + locker.unlock(); - return item; + ItemPtr item = tryCreateItem(descriptor, level, seed); + get<2>(entry) = item->parameters().optUInt("seed"); // Seed could've been changed by the buildscript + + locker.lock(); + return m_itemCache.get(entry, [&](ItemCacheEntry const&) -> ItemPtr { return move(item); }); + } +} + +ItemPtr ItemDatabase::item(ItemDescriptor descriptor, Maybe level, Maybe seed) const { + if (!descriptor) + return {}; + else + return tryCreateItem(descriptor, level, seed); } bool ItemDatabase::hasRecipeToMake(ItemDescriptor const& item) const { @@ -332,7 +351,7 @@ ItemRecipe ItemDatabase::parseRecipe(Json const& config) const { for (auto input : config.getArray("input")) { auto id = ItemDescriptor(input); if (itemType(id.name()) == ItemType::CurrencyItem) { - auto currencyItem = as(item(id)); + auto currencyItem = as(itemShared(id)); res.currencyInputs[currencyItem->currencyType()] += currencyItem->totalValue(); } else { res.inputs.push_back(id); @@ -342,7 +361,7 @@ ItemRecipe ItemDatabase::parseRecipe(Json const& config) const { res.output = ItemDescriptor(config.get("output")); res.duration = config.getFloat("duration", Root::singleton().assets()->json("/items/defaultParameters.config:defaultCraftDuration").toFloat()); res.groups = StringSet::from(jsonToStringList(config.get("groups", JsonArray()))); - if (auto item = ItemDatabase::item(res.output)) { + if (auto item = ItemDatabase::itemShared(res.output)) { res.outputRarity = item->rarity(); res.guiFilterString = guiFilterString(item); } @@ -480,6 +499,20 @@ ItemPtr ItemDatabase::createItem(ItemType type, ItemConfig const& config) { } } +ItemPtr ItemDatabase::tryCreateItem(ItemDescriptor const& descriptor, Maybe level, Maybe seed) const { + ItemPtr result; + try { + result = createItem(m_items.get(descriptor.name()).type, itemConfig(descriptor.name(), descriptor.parameters(), level, seed)); + } + catch (std::exception const& e) { + Logger::error("Could not instantiate item '{}'. {}", descriptor, outputException(e, false)); + result = createItem(m_items.get("perfectlygenericitem").type, itemConfig("perfectlygenericitem", {}, {})); + } + result->setCount(descriptor.count()); + + return result; +} + ItemDatabase::ItemData const& ItemDatabase::itemData(String const& name) const { if (auto p = m_items.ptr(name)) return *p; @@ -492,7 +525,7 @@ ItemRecipe ItemDatabase::makeRecipe(List inputs, ItemDescriptor res.output = move(output); res.duration = duration; res.groups = move(groups); - if (auto item = ItemDatabase::item(res.output)) { + if (auto item = ItemDatabase::itemShared(res.output)) { res.outputRarity = item->rarity(); res.guiFilterString = guiFilterString(item); } @@ -627,7 +660,7 @@ void ItemDatabase::addBlueprints() { for (auto const& recipe : m_recipes) { auto baseDesc = recipe.output; - auto baseItem = item(baseDesc); + auto baseItem = itemShared(baseDesc); String blueprintName = strf("{}-recipe", baseItem->name()); if (m_items.contains(blueprintName)) diff --git a/source/game/StarItemDatabase.hpp b/source/game/StarItemDatabase.hpp index 2c1fb1c..15dd7ee 100644 --- a/source/game/StarItemDatabase.hpp +++ b/source/game/StarItemDatabase.hpp @@ -6,6 +6,7 @@ #include "StarItem.hpp" #include "StarCasting.hpp" #include "StarLuaRoot.hpp" +#include "StarTtlCache.hpp" namespace Star { @@ -75,6 +76,8 @@ public: ItemDatabase(); + void cleanup(); + // Load an item based on item descriptor. If loadItem is called with a // live ptr, and the ptr matches the descriptor read, then no new item is // constructed. If ItemT is some other type than Item, then loadItem will @@ -112,8 +115,12 @@ public: // from the appropriate factory. If there is a problem instantiating the // item, will return a default item instead. If item is passed a null // ItemDescriptor, it will return a null pointer. + // The returned item pointer will be shared. Either call ->clone() or use item() instead for a copy. + ItemPtr itemShared(ItemDescriptor descriptor, Maybe level = {}, Maybe seed = {}) const; + // Same as itemShared, but makes a copy instead. Does not cache. ItemPtr item(ItemDescriptor descriptor, Maybe level = {}, Maybe seed = {}) const; + bool hasRecipeToMake(ItemDescriptor const& item) const; bool hasRecipeToMake(ItemDescriptor const& item, StringSet const& allowedTypes) const; @@ -153,6 +160,7 @@ private: }; static ItemPtr createItem(ItemType type, ItemConfig const& config); + ItemPtr tryCreateItem(ItemDescriptor const& descriptor, Maybe level = {}, Maybe seed = {}) const; ItemData const& itemData(String const& name) const; ItemRecipe makeRecipe(List inputs, ItemDescriptor output, float duration, StringSet groups) const; @@ -171,6 +179,11 @@ private: mutable RecursiveMutex m_luaMutex; LuaRootPtr m_luaRoot; + + typedef tuple, Maybe> ItemCacheEntry; + + mutable Mutex m_cacheMutex; + mutable HashTtlCache m_itemCache; }; template diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp index 7bfa224..7036a7b 100644 --- a/source/game/StarPlayer.cpp +++ b/source/game/StarPlayer.cpp @@ -1151,7 +1151,7 @@ ItemPtr Player::pickupItems(ItemPtr const& items) { m_effectsAnimator->playSound("pickup"); } auto itemDb = Root::singleton().itemDatabase(); - queueItemPickupMessage(itemDb->item(items->descriptor())); + queueItemPickupMessage(itemDb->itemShared(items->descriptor())); return m_inventory->addItems(items); } diff --git a/source/game/StarQuestDescriptor.cpp b/source/game/StarQuestDescriptor.cpp index 3709ef3..cb791e1 100644 --- a/source/game/StarQuestDescriptor.cpp +++ b/source/game/StarQuestDescriptor.cpp @@ -279,12 +279,12 @@ String questParamText(QuestParam const& parameter) { if (parameter.detail.is()) { QuestItem item = parameter.detail.get(); - return itemDatabase->item(item.descriptor())->friendlyName(); + return itemDatabase->itemShared(item.descriptor())->friendlyName(); } else if (parameter.detail.is()) { QuestItemList itemList = parameter.detail.get(); StringList itemStrings = itemList.transformed([&itemDatabase](ItemDescriptor const& itemDesc) -> String { - return strf("{} {}", itemDesc.count(), itemDatabase->item(itemDesc)->friendlyName()); + return strf("{} {}", itemDesc.count(), itemDatabase->itemShared(itemDesc)->friendlyName()); }); return itemStrings.join(", "); diff --git a/source/game/StarRoot.cpp b/source/game/StarRoot.cpp index df2fc98..5adbd0c 100644 --- a/source/game/StarRoot.cpp +++ b/source/game/StarRoot.cpp @@ -106,23 +106,38 @@ Root::Root(Settings settings) { { MutexLocker locker(m_objectDatabaseMutex); - if (m_objectDatabase) - m_objectDatabase->cleanup(); + if (ObjectDatabasePtr objectDb = m_objectDatabase) { + locker.unlock(); + objectDb->cleanup(); + } + } + { + MutexLocker locker(m_itemDatabaseMutex); + if (ItemDatabasePtr itemDb = m_itemDatabase) { + locker.unlock(); + itemDb->cleanup(); + } } { MutexLocker locker(m_monsterDatabaseMutex); - if (m_monsterDatabase) - m_monsterDatabase->cleanup(); + if (MonsterDatabasePtr monsterDb = m_monsterDatabase) { + locker.unlock(); + monsterDb->cleanup(); + } } { MutexLocker locker(m_assetsMutex); - if (m_assets) - m_assets->cleanup(); + if (AssetsPtr assets = m_assets) { + locker.unlock(); + assets->cleanup(); + } } { MutexLocker locker(m_tenantDatabaseMutex); - if (m_tenantDatabase) - m_tenantDatabase->cleanup(); + if (TenantDatabasePtr tenantDb = m_tenantDatabase) { + locker.unlock(); + tenantDb->cleanup(); + } } Random::addEntropy();