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()) {
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");
icon->showDurability(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 (container->iconItem()) {
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");
String title = this->title();
if (title.empty())
@ -259,7 +259,7 @@ void CraftingPane::update(float dt) {
auto description = fetchChild<Widget>("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<ItemRecipe> 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<ItemRecipe> 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

View File

@ -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<ButtonWidget>("spinCount.up")->enable();
findChild<ButtonWidget>("spinCount.down")->enable();
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 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();

View File

@ -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<float> level, Maybe<uint64_t> seed) const {
ItemPtr ItemDatabase::itemShared(ItemDescriptor descriptor, Maybe<float> level, Maybe<uint64_t> 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<float> level, Maybe<uint64_t> 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<CurrencyItem>(item(id));
auto currencyItem = as<CurrencyItem>(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<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 {
if (auto p = m_items.ptr(name))
return *p;
@ -492,7 +525,7 @@ ItemRecipe ItemDatabase::makeRecipe(List<ItemDescriptor> 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))

View File

@ -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<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;
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<float> level = {}, Maybe<uint64_t> seed = {}) const;
ItemData const& itemData(String const& name) const;
ItemRecipe makeRecipe(List<ItemDescriptor> inputs, ItemDescriptor output, float duration, StringSet groups) const;
@ -171,6 +179,11 @@ private:
mutable RecursiveMutex m_luaMutex;
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>

View File

@ -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);
}

View File

@ -279,12 +279,12 @@ String questParamText(QuestParam const& parameter) {
if (parameter.detail.is<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>()) {
QuestItemList itemList = parameter.detail.get<QuestItemList>();
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(", ");

View File

@ -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();