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.
This commit is contained in:
Kae 2023-07-23 22:44:02 +10:00
parent 121d27446b
commit 0aee45a117
9 changed files with 100 additions and 39 deletions

View File

@ -142,7 +142,7 @@ ContainerPane::ContainerPane(WorldClientPtr worldClient, PlayerPtr player, Conta
if (container->iconItem()) { if (container->iconItem()) {
auto itemDatabase = Root::singleton().itemDatabase(); auto itemDatabase = Root::singleton().itemDatabase();
auto iconItem = itemDatabase->item(container->iconItem()); auto iconItem = itemDatabase->itemShared(container->iconItem());
auto icon = make_shared<ItemSlotWidget>(iconItem, "/interface/inventory/portrait.png"); auto icon = make_shared<ItemSlotWidget>(iconItem, "/interface/inventory/portrait.png");
icon->showDurability(false); icon->showDurability(false);
icon->showRarity(false); icon->showRarity(false);

View File

@ -127,7 +127,7 @@ CraftingPane::CraftingPane(WorldClientPtr worldClient, PlayerPtr player, Json co
if (auto container = as<ContainerEntity>(entity)) { if (auto container = as<ContainerEntity>(entity)) {
if (container->iconItem()) { if (container->iconItem()) {
auto itemDatabase = Root::singleton().itemDatabase(); auto itemDatabase = Root::singleton().itemDatabase();
auto iconItem = itemDatabase->item(container->iconItem()); auto iconItem = itemDatabase->itemShared(container->iconItem());
auto icon = make_shared<ItemSlotWidget>(iconItem, "/interface/inventory/portrait.png"); auto icon = make_shared<ItemSlotWidget>(iconItem, "/interface/inventory/portrait.png");
String title = this->title(); String title = this->title();
if (title.empty()) if (title.empty())
@ -259,7 +259,7 @@ void CraftingPane::update(float dt) {
auto description = fetchChild<Widget>("description"); auto description = fetchChild<Widget>("description");
description->removeAllChildren(); description->removeAllChildren();
auto item = Root::singleton().itemDatabase()->item(recipe.output); auto item = Root::singleton().itemDatabase()->itemShared(recipe.output);
ItemTooltipBuilder::buildItemDescription(description, item); ItemTooltipBuilder::buildItemDescription(description, item);
} }
} }
@ -383,7 +383,7 @@ void CraftingPane::setupWidget(WidgetPtr const& widget, ItemRecipe const& recipe
auto single = recipe.output.singular(); auto single = recipe.output.singular();
ItemPtr item = m_itemCache[single]; ItemPtr item = m_itemCache[single];
if (!item) { if (!item) {
item = root.itemDatabase()->item(single); item = root.itemDatabase()->itemShared(single);
m_itemCache[single] = item; m_itemCache[single] = item;
} }
@ -475,13 +475,13 @@ PanePtr CraftingPane::setupTooltip(ItemRecipe const& recipe) {
auto currenciesConfig = root.assets()->json("/currencies.config"); auto currenciesConfig = root.assets()->json("/currencies.config");
for (auto const& p : recipe.currencyInputs) { for (auto const& p : recipe.currencyInputs) {
if (p.second > 0) { 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); addIngredient(currencyItem, m_player->currency(p.first), p.second);
} }
} }
for (auto const& input : recipe.inputs) { 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); size_t itemCount = itemDb->getCountOfItem(normalizedBag, input, recipe.matchInputParameters);
addIngredient(item, itemCount, input.count()); addIngredient(item, itemCount, input.count());
} }
@ -576,7 +576,7 @@ void CraftingPane::craft(int count) {
remainingItemCount -= craftedItem->count(); remainingItemCount -= craftedItem->count();
m_player->giveItem(craftedItem); m_player->giveItem(craftedItem);
for (auto collectable : recipe.collectables) for (auto& collectable : recipe.collectables)
m_player->addCollectable(collectable.first, collectable.second); m_player->addCollectable(collectable.first, collectable.second);
} }
@ -666,10 +666,10 @@ List<ItemRecipe> CraftingPane::determineRecipes() {
float printTime = m_settings.getFloat("printTime", 0); float printTime = m_settings.getFloat("printTime", 0);
float printFactor = m_settings.getFloat("printCostFactor", 1.0); float printFactor = m_settings.getFloat("printCostFactor", 1.0);
for (auto itemName : itemList) { for (auto& itemName : itemList) {
ItemRecipe recipe; ItemRecipe recipe;
recipe.output = ItemDescriptor(itemName, 1); recipe.output = ItemDescriptor(itemName, 1);
auto recipeItem = itemDb->item(recipe.output); auto recipeItem = itemDb->itemShared(recipe.output);
int itemPrice = int(recipeItem->price() * printFactor); int itemPrice = int(recipeItem->price() * printFactor);
recipe.currencyInputs["money"] = itemPrice; recipe.currencyInputs["money"] = itemPrice;
recipe.outputRarity = recipeItem->rarity(); recipe.outputRarity = recipeItem->rarity();
@ -679,7 +679,7 @@ List<ItemRecipe> CraftingPane::determineRecipes() {
recipes.add(recipe); recipes.add(recipe);
} }
} else if (m_settings.contains("recipes")) { } 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) if (entry.type() == Json::Type::String)
recipes.addAll(itemDb->recipesForOutputItem(entry.toString())); recipes.addAll(itemDb->recipesForOutputItem(entry.toString()));
else else

View File

@ -127,7 +127,7 @@ PanePtr MerchantPane::createTooltip(Vec2I const& screenPosition) {
auto entry = m_itemGuiList->itemAt(i); auto entry = m_itemGuiList->itemAt(i);
if (entry->getChildAt(screenPosition)) { if (entry->getChildAt(screenPosition)) {
auto itemConfig = m_itemList.get(i); 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); return ItemTooltipBuilder::buildItemTooltip(item, m_player);
} }
} }
@ -232,7 +232,7 @@ void MerchantPane::buildItemList() {
void MerchantPane::setupWidget(WidgetPtr const& widget, Json const& itemConfig) { void MerchantPane::setupWidget(WidgetPtr const& widget, Json const& itemConfig) {
auto& root = Root::singleton(); auto& root = Root::singleton();
auto assets = root.assets(); 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(); String name = item->friendlyName();
if (item->count() > 1) if (item->count() > 1)
@ -265,7 +265,7 @@ void MerchantPane::updateSelection() {
if (m_selectedIndex != NPos) { if (m_selectedIndex != NPos) {
auto itemConfig = m_itemList.get(m_selectedIndex); 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<ButtonWidget>("spinCount.up")->enable(); findChild<ButtonWidget>("spinCount.up")->enable();
findChild<ButtonWidget>("spinCount.down")->enable(); findChild<ButtonWidget>("spinCount.down")->enable();
m_countTextBox->setColor(Color::White); m_countTextBox->setColor(Color::White);

View File

@ -109,7 +109,7 @@ Collectable CollectionDatabase::parseMonsterCollectable(String const& name, Json
Collectable CollectionDatabase::parseItemCollectable(String const& name, Json const& config) const { Collectable CollectionDatabase::parseItemCollectable(String const& name, Json const& config) const {
Collectable collectable = parseGenericCollectable(name, config); Collectable collectable = parseGenericCollectable(name, config);
auto itemDatabase = Root::singleton().itemDatabase(); 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.title = item->friendlyName();
collectable.description = item->description(); collectable.description = item->description();

View File

@ -140,6 +140,15 @@ ItemDatabase::ItemDatabase()
addBlueprints(); 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 { ItemPtr ItemDatabase::diskLoad(Json const& diskStore) const {
if (diskStore) { if (diskStore) {
return item(ItemDescriptor::loadStore(diskStore)); return item(ItemDescriptor::loadStore(diskStore));
@ -204,20 +213,30 @@ ItemDatabase::ItemConfig ItemDatabase::itemConfig(String const& itemName, Json p
return itemConfig; return itemConfig;
} }
ItemPtr ItemDatabase::item(ItemDescriptor descriptor, Maybe<float> level, Maybe<uint64_t> seed) const { ItemPtr ItemDatabase::itemShared(ItemDescriptor descriptor, Maybe<float> level, Maybe<uint64_t> seed) const {
if (!descriptor) if (!descriptor)
return {}; return {};
ItemPtr item; ItemCacheEntry entry{ descriptor, level, seed };
try { MutexLocker locker(m_cacheMutex);
item = createItem(m_items.get(descriptor.name()).type, itemConfig(descriptor.name(), descriptor.parameters(), level, seed)); if (ItemPtr* cached = m_itemCache.ptr(entry))
} catch (std::exception const& e) { return *cached;
Logger::error("Could not instantiate item '{}'. {}", descriptor, outputException(e, false)); else {
item = createItem(m_items.get("perfectlygenericitem").type, itemConfig("perfectlygenericitem", {}, {})); locker.unlock();
}
item->setCount(descriptor.count());
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<float> level, Maybe<uint64_t> seed) const {
if (!descriptor)
return {};
else
return tryCreateItem(descriptor, level, seed);
} }
bool ItemDatabase::hasRecipeToMake(ItemDescriptor const& item) const { bool ItemDatabase::hasRecipeToMake(ItemDescriptor const& item) const {
@ -332,7 +351,7 @@ ItemRecipe ItemDatabase::parseRecipe(Json const& config) const {
for (auto input : config.getArray("input")) { for (auto input : config.getArray("input")) {
auto id = ItemDescriptor(input); auto id = ItemDescriptor(input);
if (itemType(id.name()) == ItemType::CurrencyItem) { if (itemType(id.name()) == ItemType::CurrencyItem) {
auto currencyItem = as<CurrencyItem>(item(id)); auto currencyItem = as<CurrencyItem>(itemShared(id));
res.currencyInputs[currencyItem->currencyType()] += currencyItem->totalValue(); res.currencyInputs[currencyItem->currencyType()] += currencyItem->totalValue();
} else { } else {
res.inputs.push_back(id); res.inputs.push_back(id);
@ -342,7 +361,7 @@ ItemRecipe ItemDatabase::parseRecipe(Json const& config) const {
res.output = ItemDescriptor(config.get("output")); res.output = ItemDescriptor(config.get("output"));
res.duration = config.getFloat("duration", Root::singleton().assets()->json("/items/defaultParameters.config:defaultCraftDuration").toFloat()); res.duration = config.getFloat("duration", Root::singleton().assets()->json("/items/defaultParameters.config:defaultCraftDuration").toFloat());
res.groups = StringSet::from(jsonToStringList(config.get("groups", JsonArray()))); 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.outputRarity = item->rarity();
res.guiFilterString = guiFilterString(item); res.guiFilterString = guiFilterString(item);
} }
@ -480,6 +499,20 @@ ItemPtr ItemDatabase::createItem(ItemType type, ItemConfig const& config) {
} }
} }
ItemPtr ItemDatabase::tryCreateItem(ItemDescriptor const& descriptor, Maybe<float> level, Maybe<uint64_t> 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 { ItemDatabase::ItemData const& ItemDatabase::itemData(String const& name) const {
if (auto p = m_items.ptr(name)) if (auto p = m_items.ptr(name))
return *p; return *p;
@ -492,7 +525,7 @@ ItemRecipe ItemDatabase::makeRecipe(List<ItemDescriptor> inputs, ItemDescriptor
res.output = move(output); res.output = move(output);
res.duration = duration; res.duration = duration;
res.groups = move(groups); res.groups = move(groups);
if (auto item = ItemDatabase::item(res.output)) { if (auto item = ItemDatabase::itemShared(res.output)) {
res.outputRarity = item->rarity(); res.outputRarity = item->rarity();
res.guiFilterString = guiFilterString(item); res.guiFilterString = guiFilterString(item);
} }
@ -627,7 +660,7 @@ void ItemDatabase::addBlueprints() {
for (auto const& recipe : m_recipes) { for (auto const& recipe : m_recipes) {
auto baseDesc = recipe.output; auto baseDesc = recipe.output;
auto baseItem = item(baseDesc); auto baseItem = itemShared(baseDesc);
String blueprintName = strf("{}-recipe", baseItem->name()); String blueprintName = strf("{}-recipe", baseItem->name());
if (m_items.contains(blueprintName)) if (m_items.contains(blueprintName))

View File

@ -6,6 +6,7 @@
#include "StarItem.hpp" #include "StarItem.hpp"
#include "StarCasting.hpp" #include "StarCasting.hpp"
#include "StarLuaRoot.hpp" #include "StarLuaRoot.hpp"
#include "StarTtlCache.hpp"
namespace Star { namespace Star {
@ -75,6 +76,8 @@ public:
ItemDatabase(); ItemDatabase();
void cleanup();
// Load an item based on item descriptor. If loadItem is called with a // 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 // 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 // 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 // from the appropriate factory. If there is a problem instantiating the
// item, will return a default item instead. If item is passed a null // item, will return a default item instead. If item is passed a null
// ItemDescriptor, it will return a null pointer. // 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<float> level = {}, Maybe<uint64_t> seed = {}) const;
// Same as itemShared, but makes a copy instead. Does not cache.
ItemPtr item(ItemDescriptor descriptor, Maybe<float> level = {}, Maybe<uint64_t> seed = {}) const; ItemPtr item(ItemDescriptor descriptor, Maybe<float> level = {}, Maybe<uint64_t> seed = {}) const;
bool hasRecipeToMake(ItemDescriptor const& item) const; bool hasRecipeToMake(ItemDescriptor const& item) const;
bool hasRecipeToMake(ItemDescriptor const& item, StringSet const& allowedTypes) const; bool hasRecipeToMake(ItemDescriptor const& item, StringSet const& allowedTypes) const;
@ -153,6 +160,7 @@ private:
}; };
static ItemPtr createItem(ItemType type, ItemConfig const& config); static ItemPtr createItem(ItemType type, ItemConfig const& config);
ItemPtr tryCreateItem(ItemDescriptor const& descriptor, Maybe<float> level = {}, Maybe<uint64_t> seed = {}) const;
ItemData const& itemData(String const& name) const; ItemData const& itemData(String const& name) const;
ItemRecipe makeRecipe(List<ItemDescriptor> inputs, ItemDescriptor output, float duration, StringSet groups) const; ItemRecipe makeRecipe(List<ItemDescriptor> inputs, ItemDescriptor output, float duration, StringSet groups) const;
@ -171,6 +179,11 @@ private:
mutable RecursiveMutex m_luaMutex; mutable RecursiveMutex m_luaMutex;
LuaRootPtr m_luaRoot; LuaRootPtr m_luaRoot;
typedef tuple<ItemDescriptor, Maybe<float>, Maybe<uint64_t>> ItemCacheEntry;
mutable Mutex m_cacheMutex;
mutable HashTtlCache<ItemCacheEntry, ItemPtr> m_itemCache;
}; };
template <typename ItemT> template <typename ItemT>

View File

@ -1151,7 +1151,7 @@ ItemPtr Player::pickupItems(ItemPtr const& items) {
m_effectsAnimator->playSound("pickup"); m_effectsAnimator->playSound("pickup");
} }
auto itemDb = Root::singleton().itemDatabase(); auto itemDb = Root::singleton().itemDatabase();
queueItemPickupMessage(itemDb->item(items->descriptor())); queueItemPickupMessage(itemDb->itemShared(items->descriptor()));
return m_inventory->addItems(items); return m_inventory->addItems(items);
} }

View File

@ -279,12 +279,12 @@ String questParamText(QuestParam const& parameter) {
if (parameter.detail.is<QuestItem>()) { if (parameter.detail.is<QuestItem>()) {
QuestItem item = parameter.detail.get<QuestItem>(); QuestItem item = parameter.detail.get<QuestItem>();
return itemDatabase->item(item.descriptor())->friendlyName(); return itemDatabase->itemShared(item.descriptor())->friendlyName();
} else if (parameter.detail.is<QuestItemList>()) { } else if (parameter.detail.is<QuestItemList>()) {
QuestItemList itemList = parameter.detail.get<QuestItemList>(); QuestItemList itemList = parameter.detail.get<QuestItemList>();
StringList itemStrings = itemList.transformed([&itemDatabase](ItemDescriptor const& itemDesc) -> String { 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(", "); return itemStrings.join(", ");

View File

@ -106,23 +106,38 @@ Root::Root(Settings settings) {
{ {
MutexLocker locker(m_objectDatabaseMutex); MutexLocker locker(m_objectDatabaseMutex);
if (m_objectDatabase) if (ObjectDatabasePtr objectDb = m_objectDatabase) {
m_objectDatabase->cleanup(); locker.unlock();
objectDb->cleanup();
}
}
{
MutexLocker locker(m_itemDatabaseMutex);
if (ItemDatabasePtr itemDb = m_itemDatabase) {
locker.unlock();
itemDb->cleanup();
}
} }
{ {
MutexLocker locker(m_monsterDatabaseMutex); MutexLocker locker(m_monsterDatabaseMutex);
if (m_monsterDatabase) if (MonsterDatabasePtr monsterDb = m_monsterDatabase) {
m_monsterDatabase->cleanup(); locker.unlock();
monsterDb->cleanup();
}
} }
{ {
MutexLocker locker(m_assetsMutex); MutexLocker locker(m_assetsMutex);
if (m_assets) if (AssetsPtr assets = m_assets) {
m_assets->cleanup(); locker.unlock();
assets->cleanup();
}
} }
{ {
MutexLocker locker(m_tenantDatabaseMutex); MutexLocker locker(m_tenantDatabaseMutex);
if (m_tenantDatabase) if (TenantDatabasePtr tenantDb = m_tenantDatabase) {
m_tenantDatabase->cleanup(); locker.unlock();
tenantDb->cleanup();
}
} }
Random::addEntropy(); Random::addEntropy();