From 2cef00f699d1c0aa2ca0b78ec1f49535332f452d Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 1 Nov 2024 19:48:38 -0300 Subject: [PATCH 01/20] perf: improve players saving Co-Authored-By: Renato Machado --- schema.sql | 3 +- src/creatures/CMakeLists.txt | 3 + .../components/player_forge_history.cpp | 110 +++ .../components/player_forge_history.hpp | 57 ++ .../players/components/player_stash.cpp | 141 ++++ .../players/components/player_stash.hpp | 40 + .../players/components/player_storage.cpp | 181 ++++ .../players/components/player_storage.hpp | 36 + .../players/imbuements/imbuements.cpp | 3 +- src/creatures/players/player.cpp | 149 ++-- src/creatures/players/player.hpp | 60 +- src/creatures/players/wheel/player_wheel.cpp | 5 +- src/database/database.cpp | 5 + src/database/database.hpp | 3 + src/game/scheduling/save_manager.cpp | 139 ++-- src/game/scheduling/save_manager.hpp | 19 + src/io/functions/iologindata_load_player.cpp | 53 -- src/io/functions/iologindata_load_player.hpp | 3 - src/io/functions/iologindata_save_player.cpp | 787 ++++++++---------- src/io/functions/iologindata_save_player.hpp | 3 - src/io/io_bosstiary.cpp | 5 +- src/io/iologindata.cpp | 15 +- .../creatures/player/player_functions.cpp | 9 +- src/server/network/protocol/protocolgame.cpp | 6 +- 24 files changed, 1138 insertions(+), 697 deletions(-) create mode 100644 src/creatures/players/components/player_forge_history.cpp create mode 100644 src/creatures/players/components/player_forge_history.hpp create mode 100644 src/creatures/players/components/player_stash.cpp create mode 100644 src/creatures/players/components/player_stash.hpp create mode 100644 src/creatures/players/components/player_storage.cpp create mode 100644 src/creatures/players/components/player_storage.hpp diff --git a/schema.sql b/schema.sql index af245067057..d81c2914156 100644 --- a/schema.sql +++ b/schema.sql @@ -732,7 +732,8 @@ CREATE TABLE IF NOT EXISTS `player_bosstiary` ( `bossIdSlotOne` int NOT NULL DEFAULT 0, `bossIdSlotTwo` int NOT NULL DEFAULT 0, `removeTimes` int NOT NULL DEFAULT 1, - `tracker` blob NOT NULL + `tracker` blob NOT NULL, + PRIMARY KEY (`player_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Table structure `player_rewards` diff --git a/src/creatures/CMakeLists.txt b/src/creatures/CMakeLists.txt index c87a45a0aa0..8606d17796e 100644 --- a/src/creatures/CMakeLists.txt +++ b/src/creatures/CMakeLists.txt @@ -12,6 +12,9 @@ target_sources(${PROJECT_NAME}_lib PRIVATE npcs/npc.cpp npcs/npcs.cpp npcs/spawns/spawn_npc.cpp + players/components/player_forge_history.cpp + players/components/player_storage.cpp + players/components/player_stash.cpp players/grouping/familiars.cpp players/grouping/groups.cpp players/grouping/guild.cpp diff --git a/src/creatures/players/components/player_forge_history.cpp b/src/creatures/players/components/player_forge_history.cpp new file mode 100644 index 00000000000..9089731bbf5 --- /dev/null +++ b/src/creatures/players/components/player_forge_history.cpp @@ -0,0 +1,110 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.org/ + */ + +#include "creatures/players/components/player_forge_history.hpp" + +#include "database/database.hpp" +#include "creatures/players/player.hpp" +#include "utils/tools.hpp" +#include "game/scheduling/save_manager.hpp" + +PlayerForgeHistory::PlayerForgeHistory(Player &player) : + m_player(player) { } + +const std::vector &PlayerForgeHistory::get() const { + return m_history; +} + +void PlayerForgeHistory::add(const ForgeHistory &history) { + m_history.push_back(history); + m_modifiedHistory.push_back(history); +} + +void PlayerForgeHistory::remove(int historyId) { + m_removedHistoryIds.push_back(historyId); + m_history.erase( + std::remove_if(m_history.begin(), m_history.end(), [historyId](const ForgeHistory &h) { return h.id == historyId; }), + m_history.end() + ); +} + +bool PlayerForgeHistory::load() { + auto playerGUID = m_player.getGUID(); + Database &db = Database::getInstance(); + auto query = fmt::format("SELECT * FROM forge_history WHERE player_id = {}", playerGUID); + const DBResult_ptr &result = db.storeQuery(query); + if (!result) { + g_logger().debug("Failed to load forge history for player with ID: {}", playerGUID); + return false; + } + + do { + ForgeHistory history; + history.id = result->getNumber("id"); + history.actionType = static_cast(result->getNumber("action_type")); + history.description = result->getString("description"); + history.createdAt = result->getNumber("done_at"); + history.success = result->getNumber("is_success"); + m_history.push_back(history); + } while (result->next()); + + return true; +} + +bool PlayerForgeHistory::save() { + if (m_modifiedHistory.empty() && m_removedHistoryIds.empty()) { + return true; + } + + auto playerGUID = m_player.getGUID(); + auto removedHistoryIds = m_removedHistoryIds; + auto modifiedHistory = m_modifiedHistory; + + auto forgeHistorySaveTask = [playerGUID, removedHistoryIds, modifiedHistory]() mutable { + Database &db = Database::getInstance(); + + if (!removedHistoryIds.empty()) { + std::string idsToDelete = fmt::format("{}", fmt::join(removedHistoryIds, ", ")); + std::string deleteQuery = fmt::format( + "DELETE FROM `forge_history` WHERE `player_id` = {} AND `id` IN ({})", + playerGUID, idsToDelete + ); + + if (!db.executeQuery(deleteQuery)) { + g_logger().error("Failed to delete forge history entries for player with ID: {}", playerGUID); + return; + } + + removedHistoryIds.clear(); + } + + DBInsert insertQuery("INSERT INTO `forge_history` (`id`, `player_id`, `action_type`, `description`, `done_at`, `is_success`) VALUES "); + insertQuery.upsert({ "action_type", "description", "done_at", "is_success" }); + + for (const auto &history : modifiedHistory) { + auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, db.escapeString(history.description), history.createdAt, history.success ? 1 : 0); + + if (!insertQuery.addRow(row)) { + g_logger().warn("Failed to add forge history entry for player with ID: {}", playerGUID); + return; + } + + g_logger().debug("Added forge history entry date: {}, for player with ID: {}", formatDate(history.createdAt / 1000), playerGUID); + } + + if (!insertQuery.execute()) { + g_logger().error("Failed to execute insertion for forge history entries for player with ID: {}", playerGUID); + return; + } + }; + + g_saveManager().addTask(forgeHistorySaveTask, "PlayerForgeHistory::save - forgeHistorySaveTask"); + m_modifiedHistory.clear(); + return true; +} diff --git a/src/creatures/players/components/player_forge_history.hpp b/src/creatures/players/components/player_forge_history.hpp new file mode 100644 index 00000000000..9f0086dd41c --- /dev/null +++ b/src/creatures/players/components/player_forge_history.hpp @@ -0,0 +1,57 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.org/ + */ + +#pragma once + +#include "enums/forge_conversion.hpp" + +class Player; + +struct ForgeHistory { + ForgeAction_t actionType = ForgeAction_t::FUSION; + uint8_t tier = 0; + uint8_t bonus = 0; + + uint64_t createdAt; + + uint16_t id = 0; + + uint64_t cost = 0; + uint64_t dustCost = 0; + uint64_t coresCost = 0; + uint64_t gained = 0; + + bool success = false; + bool tierLoss = false; + bool successCore = false; + bool tierCore = false; + bool convergence = false; + + std::string description; + std::string firstItemName; + std::string secondItemName; +}; + +class PlayerForgeHistory { +public: + PlayerForgeHistory(Player &player); + + const std::vector &get() const; + void add(const ForgeHistory &history); + void remove(int historyId); + + bool load(); + bool save(); + +private: + std::vector m_history; + std::vector m_modifiedHistory; + std::vector m_removedHistoryIds; + Player &m_player; +}; diff --git a/src/creatures/players/components/player_stash.cpp b/src/creatures/players/components/player_stash.cpp new file mode 100644 index 00000000000..72ca8ecd11e --- /dev/null +++ b/src/creatures/players/components/player_stash.cpp @@ -0,0 +1,141 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.org/ + */ + +#include "creatures/players/components/player_stash.hpp" + +#include "database/database.hpp" +#include "items/item.hpp" +#include "creatures/players/player.hpp" +#include "game/scheduling/save_manager.hpp" + +PlayerStash::PlayerStash(Player &player) : + m_player(player) { } + +void PlayerStash::add(uint16_t itemId, uint32_t amount) { + const auto it = m_stashItems.find(itemId); + if (it != m_stashItems.end()) { + m_stashItems[itemId] += amount; + } else { + m_stashItems[itemId] = amount; + } + + m_modifiedItems[itemId] = m_stashItems[itemId]; // Track added/modified items + m_removedItems.erase(itemId); // Remove from removed if it was there +} + +uint32_t PlayerStash::getCount(uint16_t itemId) const { + const auto it = m_stashItems.find(itemId); + if (it != m_stashItems.end()) { + return it->second; + } + return 0; +} + +bool PlayerStash::remove(uint16_t itemId, uint32_t amount) { + auto it = m_stashItems.find(itemId); + if (it != m_stashItems.end()) { + if (it->second > amount) { + m_stashItems[itemId] -= amount; + m_modifiedItems[itemId] = m_stashItems[itemId]; // Track modified item + } else if (it->second == amount) { + m_stashItems.erase(itemId); + m_removedItems.insert(itemId); // Track removed item + m_modifiedItems.erase(itemId); // Ensure it's not in added items + } else { + return false; + } + return true; + } + return false; +} + +bool PlayerStash::find(uint16_t itemId) const { + return m_stashItems.find(itemId) != m_stashItems.end(); +} + +const std::map &PlayerStash::getItems() const { + return m_stashItems; +} + +uint16_t PlayerStash::getSize() const { + uint16_t size = 0; + for (const auto &[itemId, itemCount] : m_stashItems) { + size += ceil(itemCount / static_cast(Item::items[itemId].stackSize)); + } + return size; +} + +bool PlayerStash::save() { + if (m_modifiedItems.empty() && m_removedItems.empty()) { + return true; + } + + const auto playerGUID = m_player.getGUID(); + auto removedItems = m_removedItems; + auto modifiedItems = m_modifiedItems; + + auto stashSaveTask = [playerGUID, removedItems, modifiedItems]() mutable { + Database &db = Database::getInstance(); + + if (!removedItems.empty()) { + std::string removedItemIds = fmt::format("{}", fmt::join(removedItems, ", ")); + std::string deleteQuery = fmt::format( + "DELETE FROM `player_stash` WHERE `player_id` = {} AND `item_id` IN ({})", + playerGUID, removedItemIds + ); + + if (!db.executeQuery(deleteQuery)) { + g_logger().error("[PlayerStash::save] - Failed to delete removed items for player: {}", playerGUID); + return; + } + + removedItems.clear(); + } + + DBInsert insertQuery("INSERT INTO `player_stash` (`player_id`, `item_id`, `item_count`) VALUES "); + insertQuery.upsert({ "item_count" }); + + for (const auto &[itemId, itemCount] : modifiedItems) { + auto row = fmt::format("{}, {}, {}", playerGUID, itemId, itemCount); + if (!insertQuery.addRow(row)) { + g_logger().warn("[PlayerStash::save] - Failed to add row for stash item: {}", itemId); + return; + } + } + + if (!insertQuery.execute()) { + g_logger().error("[PlayerStash::save] - Failed to execute insertion for modified stash items for player: {}", playerGUID); + return; + } + }; + + g_saveManager().addTask(stashSaveTask, "PlayerStash::save - stashSaveTask"); + m_modifiedItems.clear(); + + return true; +} + +bool PlayerStash::load() { + Database &db = Database::getInstance(); + auto query = fmt::format("SELECT `item_count`, `item_id` FROM `player_stash` WHERE `player_id` = {}", m_player.getGUID()); + const DBResult_ptr &result = db.storeQuery(query); + if (!result) { + g_logger().debug("[PlayerStash::load] - Failed to load stash items for player: {}", m_player.getName()); + return false; + } + + do { + int itemId = result->getNumber("item_id"); + int itemCount = result->getNumber("item_count"); + // The PlayerStash::add function should not be used, to avoid incrementing the add/modified maps + m_stashItems[itemId] = itemCount; + } while (result->next()); + + return true; +} diff --git a/src/creatures/players/components/player_stash.hpp b/src/creatures/players/components/player_stash.hpp new file mode 100644 index 00000000000..b3c83b248c4 --- /dev/null +++ b/src/creatures/players/components/player_stash.hpp @@ -0,0 +1,40 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.org/ + */ + +#pragma once + +class Player; + +class PlayerStash { +public: + PlayerStash(Player &player); + + void add(uint16_t itemId, uint32_t count = 1); + + uint32_t getCount(uint16_t itemId) const; + + bool remove(uint16_t itemId, uint32_t amount); + + bool find(uint16_t itemId) const; + + const std::map &getItems() const; + + uint16_t getSize() const; + + bool load(); + + bool save(); + +private: + // Map of for the stash + std::map m_stashItems; + std::map m_modifiedItems; + std::set m_removedItems; + Player &m_player; +}; diff --git a/src/creatures/players/components/player_storage.cpp b/src/creatures/players/components/player_storage.cpp new file mode 100644 index 00000000000..2570abd5044 --- /dev/null +++ b/src/creatures/players/components/player_storage.cpp @@ -0,0 +1,181 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#include "creatures/players/components/player_storage.hpp" + +#include "database/database.hpp" +#include "game/scheduling/dispatcher.hpp" +#include "lua/callbacks/events_callbacks.hpp" +#include "lua/creature/events.hpp" +#include "creatures/appearance/outfit/outfit.hpp" +#include "creatures/players/grouping/familiars.hpp" +#include "creatures/players/player.hpp" +#include "creatures/players/storages/storages.hpp" +#include "game/scheduling/save_manager.hpp" + +PlayerStorage::PlayerStorage(Player &player) : + m_player(player) { } + +void PlayerStorage::add(const uint32_t key, const int32_t value, const bool shouldStorageUpdate /* = false*/, const bool shouldTrackModification /*= true*/) { + if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { + if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) { + m_player.outfits.emplace_back( + value >> 16, + value & 0xFF + ); + return; + } + if (IS_IN_KEYRANGE(key, MOUNTS_RANGE)) { + // do nothing + } else if (IS_IN_KEYRANGE(key, FAMILIARS_RANGE)) { + m_player.familiars.emplace_back( + value >> 16 + ); + return; + } else { + g_logger().warn("Unknown reserved key: {} for player: {}", key, m_player.getName()); + return; + } + } + + if (value != -1) { + int32_t oldValue = get(key); + m_storageMap[key] = value; + + if (shouldTrackModification) { + m_modifiedKeys.insert(key); + } + if (!shouldStorageUpdate) { + auto currentFrameTime = g_dispatcher().getDispatcherCycle(); + const auto &player = m_player.getPlayer(); + g_events().eventOnStorageUpdate(player, key, value, oldValue, currentFrameTime); + g_callbacks().executeCallback(EventCallback_t::playerOnStorageUpdate, &EventCallback::playerOnStorageUpdate, player, key, value, oldValue, currentFrameTime); + } + } else { + m_storageMap.erase(key); + m_modifiedKeys.erase(key); + m_removedKeys.insert(key); + } +} + +int32_t PlayerStorage::get(const uint32_t key) const { + int32_t value = -1; + const auto it = m_storageMap.find(key); + if (it == m_storageMap.end()) { + return value; + } + + value = it->second; + return value; +} + +bool PlayerStorage::has(uint32_t key) const { + return m_storageMap.find(key) != m_storageMap.end(); +} + +int32_t PlayerStorage::get(const std::string &storageName) const { + const auto it = g_storages().getStorageMap().find(storageName); + if (it == g_storages().getStorageMap().end()) { + return -1; + } + const uint32_t key = it->second; + + return get(key); +} + +void PlayerStorage::add(const std::string &storageName, const int32_t value) { + auto it = g_storages().getStorageMap().find(storageName); + if (it == g_storages().getStorageMap().end()) { + g_logger().error("[{}] Storage name '{}' not found in storage map, register your storage in 'storages.xml' first for use", __func__, storageName); + return; + } + const uint32_t key = it->second; + add(key, value); +} + +bool PlayerStorage::save() { + if (m_removedKeys.empty() && m_modifiedKeys.empty()) { + return true; + } + + auto saveTask = [removedKeys = m_removedKeys, modifiedKeys = m_modifiedKeys, playerGUID = m_player.getGUID(), playerName = m_player.getName(), storageMap = m_storageMap]() mutable { + if (!removedKeys.empty()) { + std::string keysList = fmt::format("{}", fmt::join(removedKeys, ", ")); + std::string deleteQuery = fmt::format( + "DELETE FROM `player_storage` WHERE `player_id` = {} AND `key` IN ({})", + playerGUID, keysList + ); + + if (!g_database().executeQuery(deleteQuery)) { + g_logger().error("[SaveManager::playerStorageSaveTask] - Failed to delete storage keys for player: {}", playerName); + return; + } + removedKeys.clear(); + } + + if (!modifiedKeys.empty()) { + DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); + storageQuery.upsert({ "value" }); + + for (const auto &key : modifiedKeys) { + auto row = fmt::format("{}, {}, {}", playerGUID, key, storageMap.at(key)); + if (!storageQuery.addRow(row)) { + g_logger().warn("[SaveManager::playerStorageSaveTask] - Failed to add row for player storage: {}", playerName); + return; + } + } + + if (!storageQuery.execute()) { + g_logger().error("[SaveManager::playerStorageSaveTask] - Failed to execute storage insertion for player: {}", playerName); + return; + } + + modifiedKeys.clear(); + } + }; + + g_saveManager().addTask(saveTask, "PlayerStorage::save - playerStorageSaveTask"); + return true; +} + +bool PlayerStorage::load() { + Database &db = Database::getInstance(); + auto query = fmt::format("SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = {}", m_player.getGUID()); + const DBResult_ptr &result = db.storeQuery(query); + if (!result) { + g_logger().debug("[PlayerStorage::load] - Failed to load storage keys for player: {}", m_player.getName()); + return false; + } + + do { + uint32_t key = result->getNumber("key"); + int32_t value = result->getNumber("value"); + add(key, value, true, false); + } while (result->next()); + + return true; +} + +void PlayerStorage::getReservedRange() { + // Generate outfits range + uint32_t outfits_key = PSTRG_OUTFITS_RANGE_START; + for (const auto &entry : m_player.outfits) { + uint32_t key = ++outfits_key; + m_storageMap[key] = (entry.lookType << 16) | entry.addons; + m_modifiedKeys.insert(key); // Track the key for saving + } + + // Generate familiars range + uint32_t familiar_key = PSTRG_FAMILIARS_RANGE_START; + for (const auto &entry : m_player.familiars) { + uint32_t key = ++familiar_key; + m_storageMap[key] = (entry.lookType << 16); + m_modifiedKeys.insert(key); // Track the key for saving + } +} diff --git a/src/creatures/players/components/player_storage.hpp b/src/creatures/players/components/player_storage.hpp new file mode 100644 index 00000000000..d7344b017ec --- /dev/null +++ b/src/creatures/players/components/player_storage.hpp @@ -0,0 +1,36 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.org/ + */ + +#pragma once + +class Player; + +class PlayerStorage { +public: + explicit PlayerStorage(Player &player); + + void add(const uint32_t key, const int32_t value, const bool shouldStorageUpdate = false, const bool shouldTrackModification = true); + bool remove(const uint32_t key); + bool has(const uint32_t key) const; + int32_t get(const uint32_t key) const; + + int32_t get(const std::string &storageName) const; + void add(const std::string &storageName, const int32_t value); + + bool save(); + bool load(); + +private: + void getReservedRange(); + + Player &m_player; + std::map m_storageMap; + std::set m_modifiedKeys; + std::set m_removedKeys; +}; diff --git a/src/creatures/players/imbuements/imbuements.cpp b/src/creatures/players/imbuements/imbuements.cpp index c71a56fd588..fc6867d4c27 100644 --- a/src/creatures/players/imbuements/imbuements.cpp +++ b/src/creatures/players/imbuements/imbuements.cpp @@ -11,6 +11,7 @@ #include "config/configmanager.hpp" #include "creatures/players/player.hpp" +#include "creatures/players/components/player_storage.hpp" #include "items/item.hpp" #include "lib/di/container.hpp" #include "utils/pugicast.hpp" @@ -356,7 +357,7 @@ std::vector Imbuements::getImbuements(const std::shared_ptr // Parse the storages for each imbuement in imbuements.xml and config.lua (enable/disable storage) if (g_configManager().getBoolean(TOGGLE_IMBUEMENT_SHRINE_STORAGE) && imbuement->getStorage() != 0 - && player->getStorageValue(imbuement->getStorage() == -1) + && player->storage()->get(imbuement->getStorage() == -1) && imbuement->getBaseID() >= 1 && imbuement->getBaseID() <= 3) { continue; } diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 65ace44f5f6..f240390c247 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -18,6 +18,9 @@ #include "creatures/monsters/monster.hpp" #include "creatures/monsters/monsters.hpp" #include "creatures/npcs/npc.hpp" +#include "creatures/players/components/player_forge_history.hpp" +#include "creatures/players/components/player_stash.hpp" +#include "creatures/players/components/player_storage.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "creatures/players/wheel/wheel_gems.hpp" #include "creatures/players/achievement/player_achievement.hpp" @@ -75,6 +78,9 @@ Player::Player(std::shared_ptr p) : m_playerBadge = std::make_unique(*this); m_playerCyclopedia = std::make_unique(*this); m_playerTitle = std::make_unique(*this); + m_stashPlayer = std::make_unique(*this); + m_forgeHistoryPlayer = std::make_unique(*this); + m_storagePlayer = std::make_unique(*this); } Player::~Player() { @@ -1041,73 +1047,6 @@ uint16_t Player::getLookCorpse() const { return ITEM_MALE_CORPSE; } -void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin /* = false*/) { - if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { - if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) { - outfits.emplace_back( - value >> 16, - value & 0xFF - ); - return; - } - if (IS_IN_KEYRANGE(key, MOUNTS_RANGE)) { - // do nothing - } else if (IS_IN_KEYRANGE(key, FAMILIARS_RANGE)) { - familiars.emplace_back( - value >> 16 - ); - return; - } else { - g_logger().warn("Unknown reserved key: {} for player: {}", key, getName()); - return; - } - } - - if (value != -1) { - int32_t oldValue = getStorageValue(key); - storageMap[key] = value; - - if (!isLogin) { - auto currentFrameTime = g_dispatcher().getDispatcherCycle(); - g_events().eventOnStorageUpdate(static_self_cast(), key, value, oldValue, currentFrameTime); - g_callbacks().executeCallback(EventCallback_t::playerOnStorageUpdate, &EventCallback::playerOnStorageUpdate, getPlayer(), key, value, oldValue, currentFrameTime); - } - } else { - storageMap.erase(key); - } -} - -int32_t Player::getStorageValue(const uint32_t key) const { - int32_t value = -1; - const auto it = storageMap.find(key); - if (it == storageMap.end()) { - return value; - } - - value = it->second; - return value; -} - -int32_t Player::getStorageValueByName(const std::string &storageName) const { - const auto it = g_storages().getStorageMap().find(storageName); - if (it == g_storages().getStorageMap().end()) { - return -1; - } - const uint32_t key = it->second; - - return getStorageValue(key); -} - -void Player::addStorageValueByName(const std::string &storageName, const int32_t value, const bool isLogin /* = false*/) { - auto it = g_storages().getStorageMap().find(storageName); - if (it == g_storages().getStorageMap().end()) { - g_logger().error("[{}] Storage name '{}' not found in storage map, register your storage in 'storages.xml' first for use", __func__, storageName); - return; - } - const uint32_t key = it->second; - addStorageValue(key, value, isLogin); -} - std::shared_ptr Player::kv() const { return g_kv().scoped("player")->scoped(fmt::format("{}", getGUID())); } @@ -4845,7 +4784,7 @@ void Player::setHazardSystemPoints(int32_t count) { if (!g_configManager().getBoolean(TOGGLE_HAZARDSYSTEM)) { return; } - addStorageValue(STORAGEVALUE_HAZARDCOUNT, std::max(0, std::min(0xFFFF, count)), true); + storage()->add(STORAGEVALUE_HAZARDCOUNT, std::max(0, std::min(0xFFFF, count)), true); reloadHazardSystemPointsCounter = true; if (count > 0) { setIcon("hazard", CreatureIcon(CreatureIconQuests_t::Hazard, count)); @@ -4855,7 +4794,7 @@ void Player::setHazardSystemPoints(int32_t count) { } uint16_t Player::getHazardSystemPoints() const { - const int32_t points = getStorageValue(STORAGEVALUE_HAZARDCOUNT); + const int32_t points = storage()->get(STORAGEVALUE_HAZARDCOUNT); if (points <= 0) { return 0; } @@ -5847,19 +5786,6 @@ bool Player::canWear(uint16_t lookType, uint8_t addons) const { return false; } -void Player::genReservedStorageRange() { - // generate outfits range - uint32_t outfits_key = PSTRG_OUTFITS_RANGE_START; - for (const auto &entry : outfits) { - storageMap[++outfits_key] = (entry.lookType << 16) | entry.addons; - } - // generate familiars range - uint32_t familiar_key = PSTRG_FAMILIARS_RANGE_START; - for (const auto &entry : familiars) { - storageMap[++familiar_key] = (entry.lookType << 16); - } -} - void Player::setSpecialMenuAvailable(bool supplyStashBool, bool marketMenuBool, bool depotSearchBool) { // Closing depot search when player have special container disabled and it's still open. if (isDepotSearchOpen() && !depotSearchBool && depotSearch) { @@ -6810,7 +6736,7 @@ void Player::sendUnjustifiedPoints() const { } uint8_t Player::getLastMount() const { - const int32_t value = getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT); + const int32_t value = storage()->get(PSTRG_MOUNTS_CURRENTMOUNT); if (value > 0) { return value; } @@ -6818,7 +6744,7 @@ uint8_t Player::getLastMount() const { } uint8_t Player::getCurrentMount() const { - const int32_t value = getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT); + const int32_t value = storage()->get(PSTRG_MOUNTS_CURRENTMOUNT); if (value > 0) { return value; } @@ -6826,7 +6752,7 @@ uint8_t Player::getCurrentMount() const { } void Player::setCurrentMount(uint8_t mount) { - addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mount); + storage()->add(PSTRG_MOUNTS_CURRENTMOUNT, mount); } bool Player::hasAnyMount() const { @@ -6936,14 +6862,14 @@ bool Player::tameMount(uint8_t mountId) { const uint8_t tmpMountId = mountId - 1; const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31); - int32_t value = getStorageValue(key); + int32_t value = storage()->get(key); if (value != -1) { value |= (1 << (tmpMountId % 31)); } else { value = (1 << (tmpMountId % 31)); } - addStorageValue(key, value); + storage()->add(key, value); return true; } @@ -6955,13 +6881,13 @@ bool Player::untameMount(uint8_t mountId) { const uint8_t tmpMountId = mountId - 1; const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31); - int32_t value = getStorageValue(key); + int32_t value = storage()->get(key); if (value == -1) { return true; } value &= ~(1 << (tmpMountId % 31)); - addStorageValue(key, value); + storage()->add(key, value); if (getCurrentMount() == mountId) { if (isMounted()) { @@ -6987,7 +6913,7 @@ bool Player::hasMount(const std::shared_ptr &mount) const { const uint8_t tmpMountId = mount->id - 1; - const int32_t value = getStorageValue(PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31)); + const int32_t value = storage()->get(PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31)); if (value == -1) { return false; } @@ -8698,7 +8624,7 @@ bool Player::saySpell(SpeakClasses type, const std::string &text, bool isGhostMo for (const auto &spectator : spectators) { if (const auto &tmpPlayer = spectator->getPlayer()) { if (g_configManager().getBoolean(EMOTE_SPELLS)) { - valueEmote = tmpPlayer->getStorageValue(STORAGEVALUE_EMOTE); + valueEmote = tmpPlayer->storage()->get(STORAGEVALUE_EMOTE); } if (!isGhostMode || tmpPlayer->canSeeCreature(static_self_cast())) { if (valueEmote == 1) { @@ -9366,14 +9292,6 @@ uint64_t Player::getForgeDustLevel() const { return forgeDustLevel; } -std::vector &Player::getForgeHistory() { - return forgeHistoryVector; -} - -void Player::setForgeHistory(const ForgeHistory &history) { - forgeHistoryVector.emplace_back(history); -} - void Player::registerForgeHistoryDescription(ForgeHistory history) { std::string successfulString = history.success ? "Successful" : "Unsuccessful"; std::string historyTierString = history.tier > 0 ? "tier - 1" : "consumed"; @@ -9524,7 +9442,7 @@ void Player::registerForgeHistoryDescription(ForgeHistory history) { history.description = detailsResponse.str(); - setForgeHistory(history); + forgeHistory()->add(history); } // Quickloot @@ -10247,6 +10165,33 @@ const std::unique_ptr &Player::cyclopedia() const { return m_playerCyclopedia; } +// Stash interface +std::unique_ptr &Player::stash() { + return m_stashPlayer; +} + +const std::unique_ptr &Player::stash() const { + return m_stashPlayer; +} + +// Forge history interface +std::unique_ptr &Player::forgeHistory() { + return m_forgeHistoryPlayer; +} + +const std::unique_ptr &Player::forgeHistory() const { + return m_forgeHistoryPlayer; +} + +// Storage interface +std::unique_ptr &Player::storage() { + return m_storagePlayer; +} + +const std::unique_ptr &Player::storage() const { + return m_storagePlayer; +} + void Player::sendLootMessage(const std::string &message) const { const auto &party = getParty(); if (!party) { @@ -10357,12 +10302,12 @@ void Player::BestiarysendCharms() const { void Player::addBestiaryKillCount(uint16_t raceid, uint32_t amount) { const uint32_t oldCount = getBestiaryKillCount(raceid); const uint32_t key = STORAGEVALUE_BESTIARYKILLCOUNT + raceid; - addStorageValue(key, static_cast(oldCount + amount), true); + storage()->add(key, static_cast(oldCount + amount), true); } uint32_t Player::getBestiaryKillCount(uint16_t raceid) const { const uint32_t key = STORAGEVALUE_BESTIARYKILLCOUNT + raceid; - const auto value = getStorageValue(key); + const auto value = storage()->get(key); return value > 0 ? static_cast(value) : 0; } diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 03f248cfa2f..c645925e081 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -48,6 +48,9 @@ class Container; class KV; class BedItem; class Npc; +class PlayerStorage; +class PlayerStash; +class PlayerForgeHistory; struct ModalWindow; struct Achievement; @@ -76,30 +79,7 @@ using ItemVector = std::vector>; using UsersMap = std::map>; using InvitedMap = std::map>; -struct ForgeHistory { - ForgeAction_t actionType = ForgeAction_t::FUSION; - uint8_t tier = 0; - uint8_t bonus = 0; - - time_t createdAt; - - uint16_t historyId = 0; - - uint64_t cost = 0; - uint64_t dustCost = 0; - uint64_t coresCost = 0; - uint64_t gained = 0; - - bool success = false; - bool tierLoss = false; - bool successCore = false; - bool tierCore = false; - bool convergence = false; - - std::string description; - std::string firstItemName; - std::string secondItemName; -}; +struct ForgeHistory; struct OpenContainer { std::shared_ptr container; @@ -366,16 +346,8 @@ class Player final : public Creature, public Cylinder, public Bankable { bool canOpenCorpse(uint32_t ownerId) const; - void addStorageValue(uint32_t key, int32_t value, bool isLogin = false); - int32_t getStorageValue(uint32_t key) const; - - int32_t getStorageValueByName(const std::string &storageName) const; - void addStorageValueByName(const std::string &storageName, int32_t value, bool isLogin = false); - std::shared_ptr kv() const; - void genReservedStorageRange(); - void setGroup(std::shared_ptr newGroup) { group = std::move(newGroup); } @@ -1150,10 +1122,6 @@ class Player final : public Creature, public Cylinder, public Bankable { void removeForgeDustLevel(uint64_t amount); uint64_t getForgeDustLevel() const; - std::vector &getForgeHistory(); - - void setForgeHistory(const ForgeHistory &history); - void registerForgeHistoryDescription(ForgeHistory history); void setBossPoints(uint32_t amount); @@ -1250,6 +1218,18 @@ class Player final : public Creature, public Cylinder, public Bankable { std::unique_ptr &vip(); const std::unique_ptr &vip() const; + // Player stash methods interface + std::unique_ptr &stash(); + const std::unique_ptr &stash() const; + + // Player stash methods interface + std::unique_ptr &forgeHistory(); + const std::unique_ptr &forgeHistory() const; + + // Player storage methods interface + std::unique_ptr &storage(); + const std::unique_ptr &storage() const; + void sendLootMessage(const std::string &message) const; std::shared_ptr getLootPouch(); @@ -1341,13 +1321,11 @@ class Player final : public Creature, public Cylinder, public Bankable { std::map> depotLockerMap; std::map> depotChests; std::map moduleDelayMap; - std::map storageMap; std::map itemPriceMap; std::map> rewardMap; std::map, std::shared_ptr>> m_managedContainers; - std::vector forgeHistoryVector; std::vector quickLootListItemIds; @@ -1614,6 +1592,9 @@ class Player final : public Creature, public Cylinder, public Bankable { friend class PlayerCyclopedia; friend class PlayerTitle; friend class PlayerVIP; + friend class PlayerStorage; + friend class PlayerStash; + friend class PlayerForgeHistory; std::unique_ptr m_wheelPlayer; std::unique_ptr m_playerAchievement; @@ -1621,6 +1602,9 @@ class Player final : public Creature, public Cylinder, public Bankable { std::unique_ptr m_playerCyclopedia; std::unique_ptr m_playerTitle; std::unique_ptr m_playerVIP; + std::unique_ptr m_stashPlayer; + std::unique_ptr m_forgeHistoryPlayer; + std::unique_ptr m_storagePlayer; std::mutex quickLootMutex; diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index 6d3ee2713a7..f59e33bcc10 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -13,6 +13,7 @@ #include "creatures/combat/condition.hpp" #include "creatures/combat/spells.hpp" #include "creatures/players/player.hpp" +#include "creatures/players/components/player_storage.hpp" #include "creatures/players/vocations/vocation.hpp" #include "creatures/players/wheel/wheel_gems.hpp" #include "enums/player_wheel.hpp" @@ -3533,7 +3534,7 @@ uint8_t PlayerWheel::getGiftOfLifeValue() const { } int32_t PlayerWheel::getGiftOfCooldown() const { - const int32_t value = m_player.getStorageValue(STORAGEVALUE_GIFT_OF_LIFE_COOLDOWN_WOD); + const int32_t value = m_player.storage()->get(STORAGEVALUE_GIFT_OF_LIFE_COOLDOWN_WOD); if (value <= 0) { return 0; } @@ -3541,7 +3542,7 @@ int32_t PlayerWheel::getGiftOfCooldown() const { } void PlayerWheel::setGiftOfCooldown(int32_t value, bool isOnThink) { - m_player.addStorageValue(STORAGEVALUE_GIFT_OF_LIFE_COOLDOWN_WOD, value, true); + m_player.storage()->add(STORAGEVALUE_GIFT_OF_LIFE_COOLDOWN_WOD, value, true); if (!isOnThink) { setOnThinkTimer(WheelOnThink_t::GIFT_OF_LIFE, OTSYS_TIME() + 1000); } diff --git a/src/database/database.cpp b/src/database/database.cpp index fcb7371708d..61a325ee058 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -210,6 +210,7 @@ DBResult::DBResult(MYSQL_RES* res) { handle = res; int num_fields = mysql_num_fields(handle); + m_numFields = num_fields; const MYSQL_FIELD* fields = mysql_fetch_fields(handle); for (size_t i = 0; i < num_fields; i++) { @@ -275,6 +276,10 @@ size_t DBResult::countResults() const { return static_cast(mysql_num_rows(handle)); } +size_t DBResult::countColumns() const { + return m_numFields; +} + bool DBResult::hasNext() const { return row != nullptr; } diff --git a/src/database/database.hpp b/src/database/database.hpp index 69c47d324ad..8bd587716a7 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -164,6 +164,7 @@ class DBResult { static int8_t getInt8FromString(const std::string &string, const std::string &function); size_t countResults() const; + size_t countColumns() const; bool hasNext() const; bool next(); @@ -171,6 +172,8 @@ class DBResult { MYSQL_RES* handle; MYSQL_ROW row; + size_t m_numFields; + std::map listNames; friend class Database; diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index 9cccc4052a7..879ec64b464 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -16,9 +16,15 @@ #include "io/iologindata.hpp" #include "kv/kv.hpp" #include "lib/di/container.hpp" +#include "game/scheduling/task.hpp" SaveManager::SaveManager(ThreadPool &threadPool, KVStore &kvStore, Logger &logger, Game &game) : - threadPool(threadPool), kv(kvStore), logger(logger), game(game) { } + threadPool(threadPool), kv(kvStore), logger(logger), game(game) { + m_threads.reserve(threadPool.get_thread_count() + 1); + for (uint_fast16_t i = 0; i < m_threads.capacity(); ++i) { + m_threads.emplace_back(std::make_unique()); + } +} SaveManager &SaveManager::getInstance() { return inject(); @@ -27,40 +33,49 @@ SaveManager &SaveManager::getInstance() { void SaveManager::saveAll() { Benchmark bm_saveAll; logger.info("Saving server..."); - const auto players = game.getPlayers(); + Benchmark bm_players; + const auto &players = game.getPlayers(); + logger.info("Saving {} players...", players.size()); for (const auto &[_, player] : players) { player->loginPosition = player->getPosition(); doSavePlayer(player); } - auto guilds = game.getGuilds(); + double duration_players = bm_players.duration(); + if (duration_players > 1000.0) { + logger.info("Players saved in {:.2f} seconds.", duration_players / 1000.0); + } else { + logger.info("Players saved in {} milliseconds.", duration_players); + } + + Benchmark bm_guilds; + const auto &guilds = game.getGuilds(); for (const auto &[_, guild] : guilds) { saveGuild(guild); } + double duration_guilds = bm_guilds.duration(); + if (duration_guilds > 1000.0) { + logger.info("Guilds saved in {:.2f} seconds.", duration_guilds / 1000.0); + } else { + logger.info("Guilds saved in {} milliseconds.", duration_guilds); + } saveMap(); saveKV(); - logger.info("Server saved in {} milliseconds.", bm_saveAll.duration()); + + double duration_saveAll = bm_saveAll.duration(); + if (duration_saveAll > 1000.0) { + logger.info("Server saved in {:.2f} seconds.", duration_saveAll / 1000.0); + } else { + logger.info("Server saved in {} milliseconds.", duration_saveAll); + } } void SaveManager::scheduleAll() { auto scheduledAt = std::chrono::steady_clock::now(); m_scheduledAt = scheduledAt; - - // Disable save async if the config is set to false - if (!g_configManager().getBoolean(TOGGLE_SAVE_ASYNC)) { - saveAll(); - return; - } - - threadPool.detach_task([this, scheduledAt]() { - if (m_scheduledAt.load() != scheduledAt) { - logger.warn("Skipping save for server because another save has been scheduled."); - return; - } - saveAll(); - }); + saveAll(); } void SaveManager::schedulePlayer(std::weak_ptr playerPtr) { @@ -70,30 +85,7 @@ void SaveManager::schedulePlayer(std::weak_ptr playerPtr) { return; } - // Disable save async if the config is set to false - if (!g_configManager().getBoolean(TOGGLE_SAVE_ASYNC)) { - if (g_game().getGameState() == GAME_STATE_NORMAL) { - logger.debug("Saving player {}.", playerToSave->getName()); - } - doSavePlayer(playerToSave); - return; - } - - logger.debug("Scheduling player {} for saving.", playerToSave->getName()); - auto scheduledAt = std::chrono::steady_clock::now(); - m_playerMap[playerToSave->getGUID()] = scheduledAt; - threadPool.detach_task([this, playerPtr, scheduledAt]() { - auto player = playerPtr.lock(); - if (!player) { - logger.debug("Skipping save for player because player is no longer online."); - return; - } - if (m_playerMap[player->getGUID()] != scheduledAt) { - logger.warn("Skipping save for player because another save has been scheduled."); - return; - } - doSavePlayer(player); - }); + doSavePlayer(playerToSave); } bool SaveManager::doSavePlayer(std::shared_ptr player) { @@ -105,13 +97,12 @@ bool SaveManager::doSavePlayer(std::shared_ptr player) { Benchmark bm_savePlayer; Player::PlayerLock lock(player); m_playerMap.erase(player->getGUID()); - if (g_game().getGameState() == GAME_STATE_NORMAL) { - logger.debug("Saving player {}.", player->getName()); - } bool saveSuccess = IOLoginData::savePlayer(player); if (!saveSuccess) { logger.error("Failed to save player {}.", player->getName()); + } else { + executeTasks(); } auto duration = bm_savePlayer.duration(); @@ -122,6 +113,7 @@ bool SaveManager::doSavePlayer(std::shared_ptr player) { bool SaveManager::savePlayer(std::shared_ptr player) { if (player->isOnline()) { schedulePlayer(player); + logger.debug("saving player {}, but is online scheduling....", player->getName()); return true; } return doSavePlayer(player); @@ -132,35 +124,74 @@ void SaveManager::saveGuild(std::shared_ptr guild) { logger.debug("Failed to save guild because guild is null."); return; } - Benchmark bm_saveGuild; logger.debug("Saving guild {}...", guild->getName()); IOGuild::saveGuild(guild); - auto duration = bm_saveGuild.duration(); - logger.debug("Saving guild {} took {} milliseconds.", guild->getName(), duration); + if (duration > 100) { + logger.warn("Saving guild {} took {} milliseconds.", guild->getName(), duration); + } else { + logger.debug("Saving guild {} took {} milliseconds.", guild->getName(), duration); + } } void SaveManager::saveMap() { Benchmark bm_saveMap; - logger.debug("Saving map..."); + logger.info("Saving map..."); bool saveSuccess = Map::save(); if (!saveSuccess) { logger.error("Failed to save map."); } - auto duration = bm_saveMap.duration(); - logger.debug("Map saved in {} milliseconds.", duration); + double duration_map = bm_saveMap.duration(); + if (duration_map > 1000.0) { + logger.info("Map saved in {:.2f} seconds.", duration_map / 1000.0); + } else { + logger.info("Map saved in {} milliseconds.", duration_map); + } } void SaveManager::saveKV() { Benchmark bm_saveKV; - logger.debug("Saving key-value store..."); + logger.info("Saving key-value store..."); bool saveSuccess = kv.saveAll(); if (!saveSuccess) { logger.error("Failed to save key-value store."); } - auto duration = bm_saveKV.duration(); - logger.debug("Key-value store saved in {} milliseconds.", duration); + double duration_kv = bm_saveKV.duration(); + if (duration_kv > 1000.0) { + logger.info("KV store saved in {:.2f} seconds.", duration_kv / 1000.0); + } else { + logger.info("KV store saved in {} milliseconds.", duration_kv); + } +} + +void SaveManager::addTask(std::function &&f, std::string_view context, uint32_t expiresAfterMs /* = 0*/) { + const auto &thread = getThreadTask(); + std::scoped_lock lock(thread->mutex); + thread->tasks.emplace_back(expiresAfterMs, std::move(f), context); +} + +SaveManager::ThreadTask::ThreadTask() { + tasks.reserve(2000); +} + +void SaveManager::executeTasks() { + for (const auto &thread : m_threads) { + std::scoped_lock lock(thread->mutex); + auto &threadTasks = thread->tasks; + if (!threadTasks.empty()) { + m_tasks.insert(m_tasks.end(), make_move_iterator(thread->tasks.begin()), make_move_iterator(thread->tasks.end())); + threadTasks.clear(); + } + } + + threadPool.detach_task([tasks = std::move(m_tasks)] { + for (const auto &task : tasks) { + task.execute(); + } + }); + + m_tasks.clear(); } diff --git a/src/game/scheduling/save_manager.hpp b/src/game/scheduling/save_manager.hpp index 4bd66a7966f..13c4fb33a4d 100644 --- a/src/game/scheduling/save_manager.hpp +++ b/src/game/scheduling/save_manager.hpp @@ -16,6 +16,7 @@ class Logger; class Game; class Player; class Guild; +class Task; class SaveManager { public: @@ -32,7 +33,25 @@ class SaveManager { bool savePlayer(std::shared_ptr player); void saveGuild(std::shared_ptr guild); + void addTask(std::function &&f, std::string_view context, uint32_t expiresAfterMs = 0); + private: + struct ThreadTask { + ThreadTask(); + + std::vector tasks; + std::mutex mutex; + }; + + void executeTasks(); + + const auto &getThreadTask() const { + return m_threads[ThreadPool::getThreadId()]; + } + + std::vector> m_threads; + std::vector m_tasks; + void saveMap(); void saveKV(); diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 02f58bec04b..bc3c5a5dbae 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -398,22 +398,6 @@ void IOLoginDataLoad::loadPlayerGuild(const std::shared_ptr &player, DBR } } -void IOLoginDataLoad::loadPlayerStashItems(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); - return; - } - - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `item_count`, `item_id` FROM `player_stash` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - do { - player->addItemOnStash(result->getNumber("item_id"), result->getNumber("item_count")); - } while (result->next()); - } -} - void IOLoginDataLoad::loadPlayerBestiaryCharms(const std::shared_ptr &player, DBResult_ptr result) { if (!result || !player) { g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); @@ -690,22 +674,6 @@ void IOLoginDataLoad::loadPlayerInboxItems(const std::shared_ptr &player } } -void IOLoginDataLoad::loadPlayerStorageMap(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); - return; - } - - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - do { - player->addStorageValue(result->getNumber("key"), result->getNumber("value"), true); - } while (result->next()); - } -} - void IOLoginDataLoad::loadPlayerVip(const std::shared_ptr &player, DBResult_ptr result) { if (!result || !player) { g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); @@ -841,27 +809,6 @@ void IOLoginDataLoad::loadPlayerTaskHuntingClass(const std::shared_ptr & } } -void IOLoginDataLoad::loadPlayerForgeHistory(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); - return; - } - - std::ostringstream query; - query << "SELECT * FROM `forge_history` WHERE `player_id` = " << player->getGUID(); - if ((result = Database::getInstance().storeQuery(query.str()))) { - do { - auto actionEnum = magic_enum::enum_value(result->getNumber("action_type")); - ForgeHistory history; - history.actionType = actionEnum; - history.description = result->getString("description"); - history.createdAt = result->getNumber("done_at"); - history.success = result->getNumber("is_success"); - player->setForgeHistory(history); - } while (result->next()); - } -} - void IOLoginDataLoad::loadPlayerBosstiary(const std::shared_ptr &player, DBResult_ptr result) { if (!result) { g_logger().warn("[{}] - Result nullptr", __FUNCTION__); diff --git a/src/io/functions/iologindata_load_player.hpp b/src/io/functions/iologindata_load_player.hpp index 2d1a4305cf3..da3c3ad1095 100644 --- a/src/io/functions/iologindata_load_player.hpp +++ b/src/io/functions/iologindata_load_player.hpp @@ -23,7 +23,6 @@ class IOLoginDataLoad : public IOLoginData { static void loadPlayerSkill(const std::shared_ptr &player, const DBResult_ptr &result); static void loadPlayerKills(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerGuild(const std::shared_ptr &player, DBResult_ptr result); - static void loadPlayerStashItems(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerBestiaryCharms(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerInstantSpellList(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerInventoryItems(const std::shared_ptr &player, DBResult_ptr result); @@ -31,11 +30,9 @@ class IOLoginDataLoad : public IOLoginData { static void loadPlayerDepotItems(const std::shared_ptr &player, DBResult_ptr result); static void loadRewardItems(const std::shared_ptr &player); static void loadPlayerInboxItems(const std::shared_ptr &player, DBResult_ptr result); - static void loadPlayerStorageMap(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerVip(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerPreyClass(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerTaskHuntingClass(const std::shared_ptr &player, DBResult_ptr result); - static void loadPlayerForgeHistory(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerBosstiary(const std::shared_ptr &player, DBResult_ptr result); static void loadPlayerInitializeSystem(const std::shared_ptr &player); static void loadPlayerUpdateSystem(const std::shared_ptr &player); diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index c6b92514969..0d1a89d0781 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -17,6 +17,7 @@ #include "items/containers/depot/depotchest.hpp" #include "items/containers/inbox/inbox.hpp" #include "items/containers/rewards/reward.hpp" +#include "game/scheduling/save_manager.hpp" bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, DBInsert &query_insert, PropWriteStream &propWriteStream) { if (!player) { @@ -144,17 +145,12 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite queue.pop_front(); } - // Execute query - if (!query_insert.execute()) { - g_logger().error("Error executing query."); - return false; - } return true; } bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + g_logger().warn("[IOLoginDataSave::savePlayerFirst] - Player nullptr: {}", __FUNCTION__); return false; } @@ -164,75 +160,84 @@ bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID(); - DBResult_ptr result = db.storeQuery(query.str()); + // Check if `save` flag is set + auto result = g_database().storeQuery(fmt::format("SELECT `save` FROM `players` WHERE `id` = {}", player->getGUID())); if (!result) { - g_logger().warn("[IOLoginData::savePlayer] - Error for select result query from player: {}", player->getName()); + g_logger().warn("[IOLoginDataSave::savePlayerFirst] - Error retrieving save flag for player: {}", player->getName()); return false; } + // Quick update if `save` flag is 0 if (result->getNumber("save") == 0) { - query.str(""); - query << "UPDATE `players` SET `lastlogin` = " << player->lastLoginSaved << ", `lastip` = " << player->lastIP << " WHERE `id` = " << player->getGUID(); - return db.executeQuery(query.str()); - } - - // First, an UPDATE query to write the player itself - query.str(""); - query << "UPDATE `players` SET "; - query << "`name` = " << db.escapeString(player->name) << ","; - query << "`level` = " << player->level << ","; - query << "`group_id` = " << player->group->id << ","; - query << "`vocation` = " << player->getVocationId() << ","; - query << "`health` = " << player->health << ","; - query << "`healthmax` = " << player->healthMax << ","; - query << "`experience` = " << player->experience << ","; - query << "`lookbody` = " << static_cast(player->defaultOutfit.lookBody) << ","; - query << "`lookfeet` = " << static_cast(player->defaultOutfit.lookFeet) << ","; - query << "`lookhead` = " << static_cast(player->defaultOutfit.lookHead) << ","; - query << "`looklegs` = " << static_cast(player->defaultOutfit.lookLegs) << ","; - query << "`looktype` = " << player->defaultOutfit.lookType << ","; - query << "`lookaddons` = " << static_cast(player->defaultOutfit.lookAddons) << ","; - query << "`lookmountbody` = " << static_cast(player->defaultOutfit.lookMountBody) << ","; - query << "`lookmountfeet` = " << static_cast(player->defaultOutfit.lookMountFeet) << ","; - query << "`lookmounthead` = " << static_cast(player->defaultOutfit.lookMountHead) << ","; - query << "`lookmountlegs` = " << static_cast(player->defaultOutfit.lookMountLegs) << ","; - query << "`lookfamiliarstype` = " << player->defaultOutfit.lookFamiliarsType << ","; - query << "`isreward` = " << static_cast(player->isDailyReward) << ","; - query << "`maglevel` = " << player->magLevel << ","; - query << "`mana` = " << player->mana << ","; - query << "`manamax` = " << player->manaMax << ","; - query << "`manaspent` = " << player->manaSpent << ","; - query << "`soul` = " << static_cast(player->soul) << ","; - if (player->town) { - query << "`town_id` = " << player->town->getID() << ","; - } - - const Position &loginPosition = player->getLoginPosition(); - query << "`posx` = " << loginPosition.getX() << ","; - query << "`posy` = " << loginPosition.getY() << ","; - query << "`posz` = " << loginPosition.getZ() << ","; - - query << "`prey_wildcard` = " << player->getPreyCards() << ","; - query << "`task_points` = " << player->getTaskHuntingPoints() << ","; - query << "`boss_points` = " << player->getBossPoints() << ","; - query << "`forge_dusts` = " << player->getForgeDusts() << ","; - query << "`forge_dust_level` = " << player->getForgeDustLevel() << ","; - query << "`randomize_mount` = " << static_cast(player->isRandomMounted()) << ","; - - query << "`cap` = " << (player->capacity / 100) << ","; - query << "`sex` = " << static_cast(player->sex) << ","; + auto quickUpdateTask = [queryStr = fmt::format( + "UPDATE `players` SET `lastlogin` = {}, `lastip` = {} WHERE `id` = {}", + player->lastLoginSaved, player->lastIP, player->getGUID() + )]() { + Database &db = Database::getInstance(); + if (!g_database().executeQuery(queryStr)) { + g_logger().warn("[SaveManager::quickUpdateTask] - Failed to execute quick update for player."); + return; + } + }; - if (player->lastLoginSaved != 0) { - query << "`lastlogin` = " << player->lastLoginSaved << ","; + g_saveManager().addTask(quickUpdateTask, "IOLoginDataSave::savePlayerFirst - quickUpdateTask"); + return true; } - if (player->lastIP != 0) { - query << "`lastip` = " << player->lastIP << ","; + // Build the list of column-value pairs + std::vector columns; + columns.reserve(result->countColumns()); + + // Basic Player Information + columns.push_back(fmt::format("`name` = {}", g_database().escapeString(player->name))); + columns.push_back(fmt::format("`level` = {}", player->level)); + columns.push_back(fmt::format("`group_id` = {}", player->group->id)); + columns.push_back(fmt::format("`vocation` = {}", player->getVocationId())); + columns.push_back(fmt::format("`health` = {}", player->health)); + columns.push_back(fmt::format("`healthmax` = {}", player->healthMax)); + columns.push_back(fmt::format("`experience` = {}", player->experience)); + + // Appearance Attributes + columns.push_back(fmt::format("`lookbody` = {}", static_cast(player->defaultOutfit.lookBody))); + columns.push_back(fmt::format("`lookfeet` = {}", static_cast(player->defaultOutfit.lookFeet))); + columns.push_back(fmt::format("`lookhead` = {}", static_cast(player->defaultOutfit.lookHead))); + columns.push_back(fmt::format("`looklegs` = {}", static_cast(player->defaultOutfit.lookLegs))); + columns.push_back(fmt::format("`looktype` = {}", player->defaultOutfit.lookType)); + columns.push_back(fmt::format("`lookaddons` = {}", static_cast(player->defaultOutfit.lookAddons))); + columns.push_back(fmt::format("`lookmountbody` = {}", static_cast(player->defaultOutfit.lookMountBody))); + columns.push_back(fmt::format("`lookmountfeet` = {}", static_cast(player->defaultOutfit.lookMountFeet))); + columns.push_back(fmt::format("`lookmounthead` = {}", static_cast(player->defaultOutfit.lookMountHead))); + columns.push_back(fmt::format("`lookmountlegs` = {}", static_cast(player->defaultOutfit.lookMountLegs))); + columns.push_back(fmt::format("`lookfamiliarstype` = {}", player->defaultOutfit.lookFamiliarsType)); + + columns.push_back(fmt::format("`isreward` = {}", static_cast(player->isDailyReward))); + columns.push_back(fmt::format("`maglevel` = {}", player->magLevel)); + columns.push_back(fmt::format("`mana` = {}", player->mana)); + + // Gameplay Stats + columns.push_back(fmt::format("`manamax` = {}", player->manaMax)); + columns.push_back(fmt::format("`manaspent` = {}", player->manaSpent)); + columns.push_back(fmt::format("`soul` = {}", static_cast(player->soul))); + columns.push_back(fmt::format("`town_id` = {}", player->town->getID())); + columns.push_back(fmt::format("`posx` = {}", player->getLoginPosition().getX())); + columns.push_back(fmt::format("`posy` = {}", player->getLoginPosition().getY())); + columns.push_back(fmt::format("`posz` = {}", player->getLoginPosition().getZ())); + columns.push_back(fmt::format("`prey_wildcard` = {}", player->getPreyCards())); + columns.push_back(fmt::format("`task_points` = {}", player->getTaskHuntingPoints())); + columns.push_back(fmt::format("`boss_points` = {}", player->getBossPoints())); + columns.push_back(fmt::format("`forge_dusts` = {}", player->getForgeDusts())); + columns.push_back(fmt::format("`forge_dust_level` = {}", player->getForgeDustLevel())); + columns.push_back(fmt::format("`randomize_mount` = {}", static_cast(player->isRandomMounted()))); + columns.push_back(fmt::format("`cap` = {}", (player->capacity / 100))); + columns.push_back(fmt::format("`sex` = {}", static_cast(player->sex))); + if (player->lastLoginSaved != 0) { + columns.push_back(fmt::format("`lastlogin` = {}", player->lastLoginSaved)); + } + if (player->lastIP) { + columns.push_back(fmt::format("`lastip` = {}", player->lastIP)); } - // serialize conditions + // Serialize conditions PropWriteStream propWriteStream; for (const auto &condition : player->conditions) { if (condition->isPersistent()) { @@ -243,234 +248,211 @@ bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); + if (attributesSize) { + columns.push_back(fmt::format("`conditions` = {}", g_database().escapeBlob(attributes, static_cast(attributesSize)))); + } - query << "`conditions` = " << db.escapeBlob(attributes, static_cast(attributesSize)) << ","; - + // Skull attributes, based on world type if (g_game().getWorldType() != WORLD_TYPE_PVP_ENFORCED) { int64_t skullTime = 0; - if (player->skullTicks > 0) { - auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - skullTime = now + player->skullTicks; + auto now = std::chrono::system_clock::now(); + skullTime = std::chrono::duration_cast(now.time_since_epoch()).count() + player->skullTicks; } - - query << "`skulltime` = " << skullTime << ","; - - Skulls_t skull = SKULL_NONE; - if (player->skull == SKULL_RED) { - skull = SKULL_RED; - } else if (player->skull == SKULL_BLACK) { - skull = SKULL_BLACK; - } - query << "`skull` = " << static_cast(skull) << ","; - } - - query << "`lastlogout` = " << player->getLastLogout() << ","; - query << "`balance` = " << player->bankBalance << ","; - query << "`offlinetraining_time` = " << player->getOfflineTrainingTime() / 1000 << ","; - query << "`offlinetraining_skill` = " << std::to_string(player->getOfflineTrainingSkill()) << ","; - query << "`stamina` = " << player->getStaminaMinutes() << ","; - query << "`skill_fist` = " << player->skills[SKILL_FIST].level << ","; - query << "`skill_fist_tries` = " << player->skills[SKILL_FIST].tries << ","; - query << "`skill_club` = " << player->skills[SKILL_CLUB].level << ","; - query << "`skill_club_tries` = " << player->skills[SKILL_CLUB].tries << ","; - query << "`skill_sword` = " << player->skills[SKILL_SWORD].level << ","; - query << "`skill_sword_tries` = " << player->skills[SKILL_SWORD].tries << ","; - query << "`skill_axe` = " << player->skills[SKILL_AXE].level << ","; - query << "`skill_axe_tries` = " << player->skills[SKILL_AXE].tries << ","; - query << "`skill_dist` = " << player->skills[SKILL_DISTANCE].level << ","; - query << "`skill_dist_tries` = " << player->skills[SKILL_DISTANCE].tries << ","; - query << "`skill_shielding` = " << player->skills[SKILL_SHIELD].level << ","; - query << "`skill_shielding_tries` = " << player->skills[SKILL_SHIELD].tries << ","; - query << "`skill_fishing` = " << player->skills[SKILL_FISHING].level << ","; - query << "`skill_fishing_tries` = " << player->skills[SKILL_FISHING].tries << ","; - query << "`skill_critical_hit_chance` = " << player->skills[SKILL_CRITICAL_HIT_CHANCE].level << ","; - query << "`skill_critical_hit_chance_tries` = " << player->skills[SKILL_CRITICAL_HIT_CHANCE].tries << ","; - query << "`skill_critical_hit_damage` = " << player->skills[SKILL_CRITICAL_HIT_DAMAGE].level << ","; - query << "`skill_critical_hit_damage_tries` = " << player->skills[SKILL_CRITICAL_HIT_DAMAGE].tries << ","; - query << "`skill_life_leech_chance` = " << player->skills[SKILL_LIFE_LEECH_CHANCE].level << ","; - query << "`skill_life_leech_chance_tries` = " << player->skills[SKILL_LIFE_LEECH_CHANCE].tries << ","; - query << "`skill_life_leech_amount` = " << player->skills[SKILL_LIFE_LEECH_AMOUNT].level << ","; - query << "`skill_life_leech_amount_tries` = " << player->skills[SKILL_LIFE_LEECH_AMOUNT].tries << ","; - query << "`skill_mana_leech_chance` = " << player->skills[SKILL_MANA_LEECH_CHANCE].level << ","; - query << "`skill_mana_leech_chance_tries` = " << player->skills[SKILL_MANA_LEECH_CHANCE].tries << ","; - query << "`skill_mana_leech_amount` = " << player->skills[SKILL_MANA_LEECH_AMOUNT].level << ","; - query << "`skill_mana_leech_amount_tries` = " << player->skills[SKILL_MANA_LEECH_AMOUNT].tries << ","; - query << "`manashield` = " << player->getManaShield() << ","; - query << "`max_manashield` = " << player->getMaxManaShield() << ","; - query << "`xpboost_value` = " << player->getXpBoostPercent() << ","; - query << "`xpboost_stamina` = " << player->getXpBoostTime() << ","; - query << "`quickloot_fallback` = " << (player->quickLootFallbackToMainContainer ? 1 : 0) << ","; - - if (!player->isOffline()) { - auto now = std::chrono::system_clock::now(); - auto lastLoginSaved = std::chrono::system_clock::from_time_t(player->lastLoginSaved); - query << "`onlinetime` = `onlinetime` + " << std::chrono::duration_cast(now - lastLoginSaved).count() << ","; - } - - for (int i = 1; i <= 8; i++) { - query << "`blessings" << i << "`" - << " = " << static_cast(player->getBlessingCount(static_cast(i))) << ((i == 8) ? " " : ","); - } - query << " WHERE `id` = " << player->getGUID(); - - if (!db.executeQuery(query.str())) { - return false; - } - return true; -} - -bool IOLoginDataSave::savePlayerStash(const std::shared_ptr &player) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - - Database &db = Database::getInstance(); - std::ostringstream query; - query << "DELETE FROM `player_stash` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - return false; - } - - query.str(""); - - DBInsert stashQuery("INSERT INTO `player_stash` (`player_id`,`item_id`,`item_count`) VALUES "); - for (const auto &[itemId, itemCount] : player->getStashItems()) { - query << player->getGUID() << ',' << itemId << ',' << itemCount; - if (!stashQuery.addRow(query)) { - return false; + columns.push_back(fmt::format("`skulltime` = {}", skullTime)); + columns.push_back(fmt::format("`skull` = {}", static_cast(player->skull))); + } + + // Additional fields + columns.push_back(fmt::format("`lastlogout` = {}", player->getLastLogout())); + columns.push_back(fmt::format("`balance` = {}", player->bankBalance)); + columns.push_back(fmt::format("`offlinetraining_time` = {}", player->getOfflineTrainingTime() / 1000)); + columns.push_back(fmt::format("`offlinetraining_skill` = {}", player->getOfflineTrainingSkill())); + columns.push_back(fmt::format("`skill_fist` = {}", player->skills[SKILL_FIST].level)); + columns.push_back(fmt::format("`skill_fist_tries` = {}", player->skills[SKILL_FIST].tries)); + columns.push_back(fmt::format("`skill_club` = {}", player->skills[SKILL_CLUB].level)); + columns.push_back(fmt::format("`skill_club_tries` = {}", player->skills[SKILL_CLUB].tries)); + columns.push_back(fmt::format("`skill_sword` = {}", player->skills[SKILL_SWORD].level)); + columns.push_back(fmt::format("`skill_sword_tries` = {}", player->skills[SKILL_SWORD].tries)); + columns.push_back(fmt::format("`skill_axe` = {}", player->skills[SKILL_AXE].level)); + columns.push_back(fmt::format("`skill_axe_tries` = {}", player->skills[SKILL_AXE].tries)); + columns.push_back(fmt::format("`skill_dist` = {}", player->skills[SKILL_DISTANCE].level)); + columns.push_back(fmt::format("`skill_dist_tries` = {}", player->skills[SKILL_DISTANCE].tries)); + columns.push_back(fmt::format("`skill_shielding` = {}", player->skills[SKILL_SHIELD].level)); + columns.push_back(fmt::format("`skill_shielding_tries` = {}", player->skills[SKILL_SHIELD].tries)); + columns.push_back(fmt::format("`skill_fishing` = {}", player->skills[SKILL_FISHING].level)); + columns.push_back(fmt::format("`skill_fishing_tries` = {}", player->skills[SKILL_FISHING].tries)); + columns.push_back(fmt::format("`skill_critical_hit_chance` = {}", player->skills[SKILL_CRITICAL_HIT_CHANCE].level)); + columns.push_back(fmt::format("`skill_critical_hit_chance_tries` = {}", player->skills[SKILL_CRITICAL_HIT_CHANCE].tries)); + columns.push_back(fmt::format("`skill_critical_hit_damage` = {}", player->skills[SKILL_CRITICAL_HIT_DAMAGE].level)); + columns.push_back(fmt::format("`skill_critical_hit_damage_tries` = {}", player->skills[SKILL_CRITICAL_HIT_DAMAGE].tries)); + columns.push_back(fmt::format("`skill_life_leech_chance` = {}", player->skills[SKILL_LIFE_LEECH_CHANCE].level)); + columns.push_back(fmt::format("`skill_life_leech_chance_tries` = {}", player->skills[SKILL_LIFE_LEECH_CHANCE].tries)); + columns.push_back(fmt::format("`skill_life_leech_amount` = {}", player->skills[SKILL_LIFE_LEECH_AMOUNT].level)); + columns.push_back(fmt::format("`skill_life_leech_amount_tries` = {}", player->skills[SKILL_LIFE_LEECH_AMOUNT].tries)); + columns.push_back(fmt::format("`skill_mana_leech_chance` = {}", player->skills[SKILL_MANA_LEECH_CHANCE].level)); + columns.push_back(fmt::format("`skill_mana_leech_chance_tries` = {}", player->skills[SKILL_MANA_LEECH_CHANCE].tries)); + columns.push_back(fmt::format("`skill_mana_leech_amount` = {}", player->skills[SKILL_MANA_LEECH_AMOUNT].level)); + columns.push_back(fmt::format("`skill_mana_leech_amount_tries` = {}", player->skills[SKILL_MANA_LEECH_AMOUNT].tries)); + columns.push_back(fmt::format("`stamina` = {}", player->getStaminaMinutes())); + columns.push_back(fmt::format("`manashield` = {}", player->getManaShield())); + columns.push_back(fmt::format("`max_manashield` = {}", player->getMaxManaShield())); + columns.push_back(fmt::format("`xpboost_value` = {}", player->getXpBoostPercent())); + columns.push_back(fmt::format("`xpboost_stamina` = {}", player->getXpBoostTime())); + columns.push_back(fmt::format("`quickloot_fallback` = {}", player->quickLootFallbackToMainContainer ? 1 : 0)); + + // Blessings + for (int i = 1; i <= 8; ++i) { + columns.push_back(fmt::format("`blessings{}` = {}", i, static_cast(player->getBlessingCount(static_cast(i))))); + } + + // Create the task for the query execution + auto savePlayerTask = [joinColums = std::move(columns), playerName = player->getName(), playerGUID = player->getGUID()]() { + // Now join the columns into a single string + std::string setClause = fmt::to_string(fmt::join(joinColums, ", ")); + // Construct the final query + std::string queryStr = fmt::format("UPDATE `players` SET {} WHERE `id` = {}", setClause, playerGUID); + if (!g_database().executeQuery(queryStr)) { + g_logger().warn("[SaveManager::savePlayerTask] - Error executing player first save for player: {}", playerName); } - } + }; - if (!stashQuery.execute()) { - return false; - } + // Add the task to the SaveManager + g_saveManager().addTask(savePlayerTask, "IOLoginData::savePlayerFirst - savePlayerTask"); return true; } bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - - Database &db = Database::getInstance(); - std::ostringstream query; - query << "DELETE FROM `player_spells` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + g_logger().warn("[IOLoginDataSave::savePlayerSpells] - Player nullptr: {}", __FUNCTION__); return false; } - query.str(""); - - DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES "); + auto deleteQueryStr = fmt::format("DELETE FROM `player_spells` WHERE `player_id` = {}", player->getGUID()); + DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name`) VALUES "); for (const std::string &spellName : player->learnedInstantSpellList) { - query << player->getGUID() << ',' << db.escapeString(spellName); - if (!spellsQuery.addRow(query)) { + std::string row = fmt::format("{},{}", player->getGUID(), g_database().escapeString(spellName)); + if (!spellsQuery.addRow(row)) { + g_logger().warn("[IOLoginDataSave::savePlayerSpells] - Failed to add row for player spells"); return false; } } - if (!spellsQuery.execute()) { - return false; - } + auto spellsSaveTask = [deleteQueryStr, spellsQuery]() mutable { + if (!g_database().executeQuery(deleteQueryStr)) { + g_logger().error("[SaveManager::spellsSaveTask] - Failed to execute delete query for player spells"); + return; + } + + if (!spellsQuery.execute()) { + g_logger().warn("[SaveManager::spellsSaveTask] - Failed to execute insert query for player spells"); + } + }; + + g_saveManager().addTask(spellsSaveTask, "IOLoginData::savePlayerSpells - spellsSaveTask"); return true; } bool IOLoginDataSave::savePlayerKills(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - - Database &db = Database::getInstance(); - std::ostringstream query; - query << "DELETE FROM `player_kills` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + g_logger().warn("[IOLoginDataSave::savePlayerKills] - Player nullptr: {}", __FUNCTION__); return false; } - query.str(""); - + auto deleteQueryStr = fmt::format("DELETE FROM `player_kills` WHERE `player_id` = {}", player->getGUID()); DBInsert killsQuery("INSERT INTO `player_kills` (`player_id`, `target`, `time`, `unavenged`) VALUES"); for (const auto &kill : player->unjustifiedKills) { - query << player->getGUID() << ',' << kill.target << ',' << kill.time << ',' << kill.unavenged; - if (!killsQuery.addRow(query)) { + std::string row = fmt::format("{},{},{},{}", player->getGUID(), kill.target, kill.time, kill.unavenged); + if (!killsQuery.addRow(row)) { + g_logger().warn("[IOLoginDataSave::savePlayerKills] - Failed to add row for player kills"); return false; } } - if (!killsQuery.execute()) { - return false; - } + auto killsSaveTask = [deleteQueryStr, killsQuery]() mutable { + if (!g_database().executeQuery(deleteQueryStr)) { + g_logger().warn("[SaveManager::killsSaveTask] - Failed to execute delete query for player kills"); + return; + } + + if (!killsQuery.execute()) { + g_logger().warn("[SaveManager::killsSaveTask] - Failed to execute insert query for player kills"); + } + }; + + g_saveManager().addTask(killsSaveTask, "IOLoginData::savePlayerKills - killsSaveTask"); return true; } bool IOLoginDataSave::savePlayerBestiarySystem(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + g_logger().warn("[IOLoginDataSave::savePlayerBestiarySystem] - Player nullptr: {}", __FUNCTION__); return false; } Database &db = Database::getInstance(); - std::ostringstream query; - query << "UPDATE `player_charms` SET "; - query << "`charm_points` = " << player->charmPoints << ","; - query << "`charm_expansion` = " << ((player->charmExpansion) ? 1 : 0) << ","; - query << "`rune_wound` = " << player->charmRuneWound << ","; - query << "`rune_enflame` = " << player->charmRuneEnflame << ","; - query << "`rune_poison` = " << player->charmRunePoison << ","; - query << "`rune_freeze` = " << player->charmRuneFreeze << ","; - query << "`rune_zap` = " << player->charmRuneZap << ","; - query << "`rune_curse` = " << player->charmRuneCurse << ","; - query << "`rune_cripple` = " << player->charmRuneCripple << ","; - query << "`rune_parry` = " << player->charmRuneParry << ","; - query << "`rune_dodge` = " << player->charmRuneDodge << ","; - query << "`rune_adrenaline` = " << player->charmRuneAdrenaline << ","; - query << "`rune_numb` = " << player->charmRuneNumb << ","; - query << "`rune_cleanse` = " << player->charmRuneCleanse << ","; - query << "`rune_bless` = " << player->charmRuneBless << ","; - query << "`rune_scavenge` = " << player->charmRuneScavenge << ","; - query << "`rune_gut` = " << player->charmRuneGut << ","; - query << "`rune_low_blow` = " << player->charmRuneLowBlow << ","; - query << "`rune_divine` = " << player->charmRuneDivine << ","; - query << "`rune_vamp` = " << player->charmRuneVamp << ","; - query << "`rune_void` = " << player->charmRuneVoid << ","; - query << "`UsedRunesBit` = " << player->UsedRunesBit << ","; - query << "`UnlockedRunesBit` = " << player->UnlockedRunesBit << ","; - PropWriteStream propBestiaryStream; for (const auto &trackedType : player->getCyclopediaMonsterTrackerSet(false)) { propBestiaryStream.write(trackedType->info.raceid); } size_t trackerSize; const char* trackerList = propBestiaryStream.getStream(trackerSize); - query << " `tracker list` = " << db.escapeBlob(trackerList, static_cast(trackerSize)); - query << " WHERE `player_guid` = " << player->getGUID(); + auto escapedTrackerList = g_database().escapeBlob(trackerList, static_cast(trackerSize)); + + std::string updateQuery = fmt::format( + "UPDATE `player_charms` SET `charm_points` = {}, `charm_expansion` = {}, " + "`rune_wound` = {}, `rune_enflame` = {}, `rune_poison` = {}, `rune_freeze` = {}, " + "`rune_zap` = {}, `rune_curse` = {}, `rune_cripple` = {}, `rune_parry` = {}, " + "`rune_dodge` = {}, `rune_adrenaline` = {}, `rune_numb` = {}, `rune_cleanse` = {}, " + "`rune_bless` = {}, `rune_scavenge` = {}, `rune_gut` = {}, `rune_low_blow` = {}, " + "`rune_divine` = {}, `rune_vamp` = {}, `rune_void` = {}, `UsedRunesBit` = {}, " + "`UnlockedRunesBit` = {}, `tracker list` = {} WHERE `player_guid` = {}", + player->charmPoints, + player->charmExpansion ? 1 : 0, + player->charmRuneWound, + player->charmRuneEnflame, + player->charmRunePoison, + player->charmRuneFreeze, + player->charmRuneZap, + player->charmRuneCurse, + player->charmRuneCripple, + player->charmRuneParry, + player->charmRuneDodge, + player->charmRuneAdrenaline, + player->charmRuneNumb, + player->charmRuneCleanse, + player->charmRuneBless, + player->charmRuneScavenge, + player->charmRuneGut, + player->charmRuneLowBlow, + player->charmRuneDivine, + player->charmRuneVamp, + player->charmRuneVoid, + player->UsedRunesBit, + player->UnlockedRunesBit, + escapedTrackerList, + player->getGUID() + ); + + auto bestiarySaveTask = [updateQuery]() { + if (!g_database().executeQuery(updateQuery)) { + g_logger().warn("[SaveManager::bestiarySaveTask] - Failed to execute bestiary data update query"); + } + }; - if (!db.executeQuery(query.str())) { - g_logger().warn("[IOLoginData::savePlayer] - Error saving bestiary data from player: {}", player->getName()); - return false; - } + g_saveManager().addTask(bestiarySaveTask, "IOLoginData::savePlayerBestiarySystem - bestiarySaveTask"); return true; } bool IOLoginDataSave::savePlayerItem(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + g_logger().warn("[IOLoginDataSave::savePlayerItem] - Player nullptr: {}", __FUNCTION__); return false; } - Database &db = Database::getInstance(); PropWriteStream propWriteStream; - std::ostringstream query; - query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - g_logger().warn("[IOLoginData::savePlayer] - Error delete query 'player_items' from player: {}", player->getName()); - return false; - } + std::string deleteQueryStr = fmt::format("DELETE FROM `player_items` WHERE `player_id` = {}", player->getGUID()); DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); @@ -483,320 +465,273 @@ bool IOLoginDataSave::savePlayerItem(const std::shared_ptr &player) { } if (!saveItems(player, itemList, itemsQuery, propWriteStream)) { - g_logger().warn("[IOLoginData::savePlayer] - Failed for save items from player: {}", player->getName()); + g_logger().warn("[IOLoginDataSave::savePlayerItem] - Failed to save items for player: {}", player->getName()); return false; } + + auto itemsSaveTask = [deleteQueryStr, itemsQuery]() mutable { + if (!g_database().executeQuery(deleteQueryStr)) { + g_logger().warn("[SaveManager::itemsSaveTask] - Failed to execute delete query for player items"); + return; + } + + if (!itemsQuery.execute()) { + g_logger().warn("[SaveManager::itemsSaveTask] - Failed to execute insert query for player items"); + } + }; + + g_saveManager().addTask(itemsSaveTask, "IOLoginData::savePlayerItem - itemsSaveTask"); return true; } bool IOLoginDataSave::savePlayerDepotItems(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + g_logger().warn("[IOLoginDataSave::savePlayerDepotItems] - Player nullptr: {}", __FUNCTION__); return false; } - Database &db = Database::getInstance(); - PropWriteStream propWriteStream; - ItemDepotList depotList; - if (player->lastDepotId != -1) { - std::ostringstream query; - query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); + auto deleteQueryStr = fmt::format("DELETE FROM `player_depotitems` WHERE `player_id` = {}", player->getGUID()); + DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - if (!db.executeQuery(query.str())) { - return false; + ItemDepotList depotList; + for (const auto &[pid, depotChest] : player->depotChests) { + for (const std::shared_ptr &item : depotChest->getItemList()) { + depotList.emplace_back(pid, item); } + } - query.str(""); - - DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + PropWriteStream propWriteStream; + if (!saveItems(player, depotList, depotQuery, propWriteStream)) { + g_logger().warn("[IOLoginDataSave::savePlayerDepotItems] - Failed to save depot items for player: {}", player->getName()); + return false; + } - for (const auto &[pid, depotChest] : player->depotChests) { - for (const std::shared_ptr &item : depotChest->getItemList()) { - depotList.emplace_back(pid, item); - } + auto depotItemsSaveTask = [deleteQueryStr, depotQuery]() mutable { + if (!g_database().executeQuery(deleteQueryStr)) { + g_logger().warn("[SaveManager::depotItemsSaveTask] - Failed to execute delete query for depot items"); + return; } - if (!saveItems(player, depotList, depotQuery, propWriteStream)) { - return false; + if (!depotQuery.execute()) { + g_logger().warn("[SaveManager::depotItemsSaveTask] - Failed to execute insert query for depot items"); } - return true; - } + }; + + g_saveManager().addTask(depotItemsSaveTask, "IOLoginData::savePlayerDepotItems - depotItemsSaveTask"); return true; } bool IOLoginDataSave::saveRewardItems(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + g_logger().warn("[IOLoginDataSave::saveRewardItems] - Player nullptr: {}", __FUNCTION__); return false; } - std::ostringstream query; - query << "DELETE FROM `player_rewards` WHERE `player_id` = " << player->getGUID(); - - if (!Database::getInstance().executeQuery(query.str())) { - return false; - } + auto deleteQueryStr = fmt::format("DELETE FROM `player_rewards` WHERE `player_id` = {}", player->getGUID()); + DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + ItemRewardList rewardListItems; std::vector rewardList; player->getRewardList(rewardList); - ItemRewardList rewardListItems; if (!rewardList.empty()) { for (const auto &rewardId : rewardList) { - auto reward = player->getReward(rewardId, false); - if (!reward->empty() && (getTimeMsNow() - rewardId <= 1000 * 60 * 60 * 24 * 7)) { + const auto &reward = player->getReward(rewardId, false); + if (reward && !reward->empty() && (getTimeMsNow() - rewardId <= 1000 * 60 * 60 * 24 * 7)) { rewardListItems.emplace_back(0, reward); } } + } - DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - PropWriteStream propWriteStream; - if (!saveItems(player, rewardListItems, rewardQuery, propWriteStream)) { - return false; - } + PropWriteStream propWriteStream; + if (!saveItems(player, rewardListItems, rewardQuery, propWriteStream)) { + g_logger().warn("[IOLoginDataSave::saveRewardItems] - Failed to save reward items for the player: {}", player->getName()); + return false; } + + auto rewardItemsSaveTask = [deleteQueryStr, rewardQuery]() mutable { + if (!g_database().executeQuery(deleteQueryStr)) { + g_logger().warn("[SaveManager::rewardItemsSaveTask] - Failed to execute delete query for reward items"); + return; + } + + if (!rewardQuery.execute()) { + g_logger().warn("[SaveManager::rewardItemsSaveTask] - Failed to execute insert query for reward items"); + } + }; + + g_saveManager().addTask(rewardItemsSaveTask, "IOLoginDataSave::saveRewardItems - rewardItemsSaveTask"); return true; } bool IOLoginDataSave::savePlayerInbox(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - - Database &db = Database::getInstance(); - PropWriteStream propWriteStream; - ItemInboxList inboxList; - std::ostringstream query; - query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + g_logger().warn("[IOLoginDataSave::savePlayerInbox] - Player nullptr: {}", __FUNCTION__); return false; } - query.str(""); + auto deleteQueryStr = fmt::format("DELETE FROM `player_inboxitems` WHERE `player_id` = {}", player->getGUID()); DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + ItemInboxList inboxList; for (const auto &item : player->getInbox()->getItemList()) { inboxList.emplace_back(0, item); } + PropWriteStream propWriteStream; if (!saveItems(player, inboxList, inboxQuery, propWriteStream)) { + g_logger().warn("[IOLoginDataSave::savePlayerInbox] - Failed to save inbox items for player: {}", player->getName()); return false; } + + auto inboxSaveTask = [deleteQueryStr, inboxQuery]() mutable { + if (!g_database().executeQuery(deleteQueryStr)) { + g_logger().warn("[SaveManager::inboxSaveTask] - Failed to execute delete query for inbox items"); + return; + } + + if (!inboxQuery.execute()) { + g_logger().warn("[SaveManager::inboxSaveTask] - Failed to execute insert query for inbox items"); + } + }; + + g_saveManager().addTask(inboxSaveTask, "IOLoginDataSave::savePlayerInbox - inboxSaveTask"); return true; } bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + g_logger().warn("[IOLoginDataSave::savePlayerPreyClass] - Player nullptr: {}", __FUNCTION__); return false; } - Database &db = Database::getInstance(); if (g_configManager().getBoolean(PREY_ENABLED)) { - std::ostringstream query; + DBInsert preyQuery("INSERT INTO player_prey " + "(`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, " + "`bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) VALUES "); + preyQuery.upsert({ "state", "raceid", "option", "bonus_type", "bonus_rarity", + "bonus_percentage", "bonus_time", "free_reroll", "monster_list" }); + + auto playerGUID = player->getGUID(); for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { if (const auto &slot = player->getPreySlotById(static_cast(slotId))) { - query.str(std::string()); - query << "INSERT INTO player_prey (`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, `bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) " - << "VALUES (" << player->getGUID() << ", " - << static_cast(slot->id) << ", " - << static_cast(slot->state) << ", " - << slot->selectedRaceId << ", " - << static_cast(slot->option) << ", " - << static_cast(slot->bonus) << ", " - << static_cast(slot->bonusRarity) << ", " - << slot->bonusPercentage << ", " - << slot->bonusTimeLeft << ", " - << slot->freeRerollTimeStamp << ", "; - PropWriteStream propPreyStream; - std::ranges::for_each(slot->raceIdList, [&propPreyStream](uint16_t raceId) { + for (uint16_t raceId : slot->raceIdList) { propPreyStream.write(raceId); - }); + } size_t preySize; const char* preyList = propPreyStream.getStream(preySize); - query << db.escapeBlob(preyList, static_cast(preySize)) << ")"; - - query << " ON DUPLICATE KEY UPDATE " - << "`state` = VALUES(`state`), " - << "`raceid` = VALUES(`raceid`), " - << "`option` = VALUES(`option`), " - << "`bonus_type` = VALUES(`bonus_type`), " - << "`bonus_rarity` = VALUES(`bonus_rarity`), " - << "`bonus_percentage` = VALUES(`bonus_percentage`), " - << "`bonus_time` = VALUES(`bonus_time`), " - << "`free_reroll` = VALUES(`free_reroll`), " - << "`monster_list` = VALUES(`monster_list`)"; - - if (!db.executeQuery(query.str())) { - g_logger().warn("[IOLoginData::savePlayer] - Error saving prey slot data from player: {}", player->getName()); + auto escapedPreyList = g_database().escapeBlob(preyList, static_cast(preySize)); + + // Format row data for batch insert + auto row = fmt::format("{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}", playerGUID, static_cast(slot->id), static_cast(slot->state), slot->selectedRaceId, static_cast(slot->option), static_cast(slot->bonus), static_cast(slot->bonusRarity), slot->bonusPercentage, slot->bonusTimeLeft, slot->freeRerollTimeStamp, escapedPreyList); + + if (!preyQuery.addRow(row)) { + g_logger().warn("[IOLoginDataSave::savePlayerPreyClass] - Failed to add prey slot data for player: {}", player->getName()); return false; } } } + + auto preySaveTask = [preyQuery]() mutable { + Database &db = Database::getInstance(); + if (!preyQuery.execute()) { + g_logger().warn("[SaveManager::preySaveTask] - Failed to execute prey slot data insertion"); + } + }; + + g_saveManager().addTask(preySaveTask, "IOLoginDataSave::savePlayerPreyClass - preySaveTask"); } + return true; } bool IOLoginDataSave::savePlayerTaskHuntingClass(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + g_logger().warn("[IOLoginDataSave::savePlayerTaskHuntingClass] - Player nullptr: {}", __FUNCTION__); return false; } - Database &db = Database::getInstance(); if (g_configManager().getBoolean(TASK_HUNTING_ENABLED)) { - std::ostringstream query; + DBInsert taskHuntQuery("INSERT INTO `player_taskhunt` " + "(`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, " + "`kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES "); + taskHuntQuery.upsert({ "state", "raceid", "upgrade", "rarity", "kills", "disabled_time", + "free_reroll", "monster_list" }); + + auto playerGUID = player->getGUID(); for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { if (const auto &slot = player->getTaskHuntingSlotById(static_cast(slotId))) { - query.str(""); - query << "INSERT INTO `player_taskhunt` (`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, `kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES ("; - query << player->getGUID() << ", "; - query << static_cast(slot->id) << ", "; - query << static_cast(slot->state) << ", "; - query << slot->selectedRaceId << ", "; - query << (slot->upgrade ? 1 : 0) << ", "; - query << static_cast(slot->rarity) << ", "; - query << slot->currentKills << ", "; - query << slot->disabledUntilTimeStamp << ", "; - query << slot->freeRerollTimeStamp << ", "; - PropWriteStream propTaskHuntingStream; - std::ranges::for_each(slot->raceIdList, [&propTaskHuntingStream](uint16_t raceId) { + for (uint16_t raceId : slot->raceIdList) { propTaskHuntingStream.write(raceId); - }); + } size_t taskHuntingSize; const char* taskHuntingList = propTaskHuntingStream.getStream(taskHuntingSize); - query << db.escapeBlob(taskHuntingList, static_cast(taskHuntingSize)) << ")"; - - query << " ON DUPLICATE KEY UPDATE " - << "`state` = VALUES(`state`), " - << "`raceid` = VALUES(`raceid`), " - << "`upgrade` = VALUES(`upgrade`), " - << "`rarity` = VALUES(`rarity`), " - << "`kills` = VALUES(`kills`), " - << "`disabled_time` = VALUES(`disabled_time`), " - << "`free_reroll` = VALUES(`free_reroll`), " - << "`monster_list` = VALUES(`monster_list`)"; - - if (!db.executeQuery(query.str())) { - g_logger().warn("[IOLoginData::savePlayer] - Error saving task hunting slot data from player: {}", player->getName()); + auto escapedTaskHuntingList = g_database().escapeBlob(taskHuntingList, static_cast(taskHuntingSize)); + + // Construct row for batch insert + auto row = fmt::format("{}, {}, {}, {}, {}, {}, {}, {}, {}, {}", playerGUID, static_cast(slot->id), static_cast(slot->state), slot->selectedRaceId, slot->upgrade ? 1 : 0, static_cast(slot->rarity), slot->currentKills, slot->disabledUntilTimeStamp, slot->freeRerollTimeStamp, escapedTaskHuntingList); + + if (!taskHuntQuery.addRow(row)) { + g_logger().warn("[IOLoginDataSave::savePlayerTaskHuntingClass] - Failed to add task hunting slot data for player: {}", player->getName()); return false; } } } - } - return true; -} -bool IOLoginDataSave::savePlayerForgeHistory(const std::shared_ptr &player) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } + auto taskHuntingSaveTask = [taskHuntQuery]() mutable { + Database &db = Database::getInstance(); + if (!taskHuntQuery.execute()) { + g_logger().warn("[SaveManager::taskHuntingSaveTask] - Failed to execute task hunting data insertion"); + } + }; - std::ostringstream query; - query << "DELETE FROM `forge_history` WHERE `player_id` = " << player->getGUID(); - if (!Database::getInstance().executeQuery(query.str())) { - return false; + g_saveManager().addTask(taskHuntingSaveTask, "IOLoginDataSave::savePlayerTaskHuntingClass - taskHuntingSaveTask"); } - query.str(""); - DBInsert insertQuery("INSERT INTO `forge_history` (`player_id`, `action_type`, `description`, `done_at`, `is_success`) VALUES"); - for (const auto &history : player->getForgeHistory()) { - const auto stringDescription = Database::getInstance().escapeString(history.description); - auto actionString = magic_enum::enum_integer(history.actionType); - // Append query informations - query << player->getGUID() << ',' - << std::to_string(actionString) << ',' - << stringDescription << ',' - << history.createdAt << ',' - << history.success; - - if (!insertQuery.addRow(query)) { - return false; - } - } - if (!insertQuery.execute()) { - return false; - } return true; } bool IOLoginDataSave::savePlayerBosstiary(const std::shared_ptr &player) { if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - - std::ostringstream query; - query << "DELETE FROM `player_bosstiary` WHERE `player_id` = " << player->getGUID(); - if (!Database::getInstance().executeQuery(query.str())) { + g_logger().warn("[IOLoginDataSave::savePlayerBosstiary] - Player nullptr: {}", __FUNCTION__); return false; } - query.str(""); - DBInsert insertQuery("INSERT INTO `player_bosstiary` (`player_id`, `bossIdSlotOne`, `bossIdSlotTwo`, `removeTimes`, `tracker`) VALUES"); + DBInsert insertQuery("INSERT INTO `player_bosstiary` " + "(`player_id`, `bossIdSlotOne`, `bossIdSlotTwo`, `removeTimes`, `tracker`) VALUES "); + insertQuery.upsert({ "bossIdSlotOne", "bossIdSlotTwo", "removeTimes", "tracker" }); - // Bosstiary tracker + // Prepare tracker data using PropWriteStream PropWriteStream stream; for (const auto &monsterType : player->getCyclopediaMonsterTrackerSet(true)) { if (!monsterType) { continue; } - stream.write(monsterType->info.raceid); } - size_t size; - const char* chars = stream.getStream(size); - // Append query informations - query << player->getGUID() << ',' - << player->getSlotBossId(1) << ',' - << player->getSlotBossId(2) << ',' - << std::to_string(player->getRemoveTimes()) << ',' - << Database::getInstance().escapeBlob(chars, static_cast(size)); - - if (!insertQuery.addRow(query)) { - return false; - } - if (!insertQuery.execute()) { - return false; - } + size_t size; + const char* trackerBlob = stream.getStream(size); + auto escapedTrackerBlob = g_database().escapeBlob(trackerBlob, static_cast(size)); - return true; -} + // Construct row for batch insert + auto row = fmt::format("{}, {}, {}, {}, {}", player->getGUID(), player->getSlotBossId(1), player->getSlotBossId(2), player->getRemoveTimes(), escapedTrackerBlob); -bool IOLoginDataSave::savePlayerStorage(const std::shared_ptr &player) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + if (!insertQuery.addRow(row)) { + g_logger().warn("[IOLoginDataSave::savePlayerBosstiary] - Failed to add bosstiary data for player: {}", player->getName()); return false; } - Database &db = Database::getInstance(); - std::ostringstream query; - query << "DELETE FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - return false; - } - - query.str(""); - - DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); - player->genReservedStorageRange(); - - for (const auto &[key, value] : player->storageMap) { - query << player->getGUID() << ',' << key << ',' << value; - if (!storageQuery.addRow(query)) { - return false; + auto bosstiarySaveTask = [insertQuery]() mutable { + if (!insertQuery.execute()) { + g_logger().warn("[SaveManager::bosstiarySaveTask] - Error executing bosstiary data insertion"); } - } + }; - if (!storageQuery.execute()) { - return false; - } + g_saveManager().addTask(bosstiarySaveTask, "IOLoginDataSave::savePlayerBosstiary - bosstiarySaveTask"); return true; } diff --git a/src/io/functions/iologindata_save_player.hpp b/src/io/functions/iologindata_save_player.hpp index abafc2c186a..c888795d74f 100644 --- a/src/io/functions/iologindata_save_player.hpp +++ b/src/io/functions/iologindata_save_player.hpp @@ -16,7 +16,6 @@ class PropWriteStream; class IOLoginDataSave : public IOLoginData { public: static bool savePlayerFirst(const std::shared_ptr &player); - static bool savePlayerStash(const std::shared_ptr &player); static bool savePlayerSpells(const std::shared_ptr &player); static bool savePlayerKills(const std::shared_ptr &player); static bool savePlayerBestiarySystem(const std::shared_ptr &player); @@ -26,9 +25,7 @@ class IOLoginDataSave : public IOLoginData { static bool savePlayerInbox(const std::shared_ptr &player); static bool savePlayerPreyClass(const std::shared_ptr &player); static bool savePlayerTaskHuntingClass(const std::shared_ptr &player); - static bool savePlayerForgeHistory(const std::shared_ptr &player); static bool savePlayerBosstiary(const std::shared_ptr &player); - static bool savePlayerStorage(const std::shared_ptr &player); protected: using ItemBlockList = std::list>>; diff --git a/src/io/io_bosstiary.cpp b/src/io/io_bosstiary.cpp index 3e8a05524e6..c29d3d81f44 100644 --- a/src/io/io_bosstiary.cpp +++ b/src/io/io_bosstiary.cpp @@ -11,6 +11,7 @@ #include "creatures/monsters/monsters.hpp" #include "creatures/players/player.hpp" +#include "creatures/players/components/player_storage.hpp" #include "game/game.hpp" #include "lib/di/container.hpp" #include "utils/tools.hpp" @@ -196,7 +197,7 @@ void IOBosstiary::addBosstiaryKill(const std::shared_ptr &player, const auto pointsForCurrentLevel = infoForCurrentRace[newBossLevel - 1].points; player->addBossPoints(pointsForCurrentLevel); - int32_t value = player->getStorageValue(STORAGEVALUE_PODIUM); + int32_t value = player->storage()->get(STORAGEVALUE_PODIUM); if (value != 1 && newBossLevel == 2) { auto returnValue = g_game().addItemStoreInbox(player, ITEM_PODIUM_OF_VIGOUR); if (!returnValue) { @@ -209,7 +210,7 @@ void IOBosstiary::addBosstiaryKill(const std::shared_ptr &player, const "Use it to display bosses for which you have reached at least the Expertise level."; player->sendTextMessage(MESSAGE_GAME_HIGHLIGHT, podiumMessage); - player->addStorageValue(STORAGEVALUE_PODIUM, 1); + player->storage()->add(STORAGEVALUE_PODIUM, 1); } } diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 0249d70a517..e721045a7f3 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -14,6 +14,9 @@ #include "io/functions/iologindata_save_player.hpp" #include "game/game.hpp" #include "creatures/monsters/monster.hpp" +#include "creatures/players/components/player_forge_history.hpp" +#include "creatures/players/components/player_stash.hpp" +#include "creatures/players/components/player_storage.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "lib/metrics/metrics.hpp" #include "enums/account_type.hpp" @@ -126,7 +129,7 @@ bool IOLoginData::loadPlayer(const std::shared_ptr &player, const DBResu IOLoginDataLoad::loadPlayerGuild(player, result); // stash load items - IOLoginDataLoad::loadPlayerStashItems(player, result); + player->stash()->load(); // bestiary charms IOLoginDataLoad::loadPlayerBestiaryCharms(player, result); @@ -147,7 +150,7 @@ bool IOLoginData::loadPlayer(const std::shared_ptr &player, const DBResu IOLoginDataLoad::loadPlayerInboxItems(player, result); // load storage map - IOLoginDataLoad::loadPlayerStorageMap(player, result); + player->storage()->load(); // load vip IOLoginDataLoad::loadPlayerVip(player, result); @@ -166,7 +169,7 @@ bool IOLoginData::loadPlayer(const std::shared_ptr &player, const DBResu } // load forge history - IOLoginDataLoad::loadPlayerForgeHistory(player, result); + player->stash()->load(); // load bosstiary IOLoginDataLoad::loadPlayerBosstiary(player, result); @@ -211,7 +214,7 @@ bool IOLoginData::savePlayerGuard(const std::shared_ptr &player) { throw DatabaseException("[" + std::string(__FUNCTION__) + "] - Failed to save player first: " + player->getName()); } - if (!IOLoginDataSave::savePlayerStash(player)) { + if (!player->stash()->save()) { throw DatabaseException("[IOLoginDataSave::savePlayerFirst] - Failed to save player stash: " + player->getName()); } @@ -251,7 +254,7 @@ bool IOLoginData::savePlayerGuard(const std::shared_ptr &player) { throw DatabaseException("[IOLoginDataSave::savePlayerTaskHuntingClass] - Failed to save player task hunting class: " + player->getName()); } - if (!IOLoginDataSave::savePlayerForgeHistory(player)) { + if (!player->forgeHistory()->save()) { throw DatabaseException("[IOLoginDataSave::savePlayerForgeHistory] - Failed to save player forge history: " + player->getName()); } @@ -263,7 +266,7 @@ bool IOLoginData::savePlayerGuard(const std::shared_ptr &player) { throw DatabaseException("[PlayerWheel::saveDBPlayerSlotPointsOnLogout] - Failed to save player wheel info: " + player->getName()); } - if (!IOLoginDataSave::savePlayerStorage(player)) { + if (!player->storage()->save()) { throw DatabaseException("[IOLoginDataSave::savePlayerStorage] - Failed to save player storage: " + player->getName()); } diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index c4c2faa8cee..2788a63551d 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -17,6 +17,7 @@ #include "creatures/players/achievement/player_achievement.hpp" #include "creatures/players/cyclopedia/player_cyclopedia.hpp" #include "creatures/players/cyclopedia/player_title.hpp" +#include "creatures/players/components/player_storage.hpp" #include "creatures/players/player.hpp" #include "creatures/players/vip/player_vip.hpp" #include "creatures/players/vocations/vocation.hpp" @@ -1734,7 +1735,7 @@ int PlayerFunctions::luaPlayerGetStorageValue(lua_State* L) { } const uint32_t key = getNumber(L, 2); - lua_pushnumber(L, player->getStorageValue(key)); + lua_pushnumber(L, player->storage()->get(key)); return 1; } @@ -1757,7 +1758,7 @@ int PlayerFunctions::luaPlayerSetStorageValue(lua_State* L) { } if (player) { - player->addStorageValue(key, value); + player->storage()->add(key, value); pushBoolean(L, true); } else { lua_pushnil(L); @@ -1776,7 +1777,7 @@ int PlayerFunctions::luaPlayerGetStorageValueByName(lua_State* L) { g_logger().warn("The function 'player:getStorageValueByName' is deprecated and will be removed in future versions, please use KV system"); const auto name = getString(L, 2); - lua_pushnumber(L, player->getStorageValueByName(name)); + lua_pushnumber(L, player->storage()->get(name)); return 1; } @@ -1793,7 +1794,7 @@ int PlayerFunctions::luaPlayerSetStorageValueByName(lua_State* L) { const auto storageName = getString(L, 2); const int32_t value = getNumber(L, 3); - player->addStorageValueByName(storageName, value); + player->storage()->add(storageName, value); pushBoolean(L, true); return 1; } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 99c8a960677..f5c07c25a5a 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -30,6 +30,8 @@ #include "creatures/players/management/waitlist.hpp" #include "creatures/players/player.hpp" #include "creatures/players/vip/player_vip.hpp" +#include "creatures/players/components/player_forge_history.hpp" +#include "creatures/players/components/player_storage.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "enums/player_icons.hpp" #include "game/game.hpp" @@ -5676,7 +5678,7 @@ void ProtocolGame::sendForgeResult(ForgeAction_t actionType, uint16_t leftItemId void ProtocolGame::sendForgeHistory(uint8_t page) { page = page + 1; - auto historyVector = player->getForgeHistory(); + auto historyVector = player->forgeHistory()->get(); auto historyVectorLen = getVectorIterationIncreaseCount(historyVector); uint16_t lastPage = (1 < std::floor((historyVectorLen - 1) / 9) + 1) ? static_cast(std::floor((historyVectorLen - 1) / 9) + 1) : 1; @@ -8312,7 +8314,7 @@ void ProtocolGame::AddHiddenShopItem(NetworkMessage &msg) { void ProtocolGame::AddShopItem(NetworkMessage &msg, const ShopBlock &shopBlock) { // Sends the item information empty if the player doesn't have the storage to buy/sell a certain item - if (shopBlock.itemStorageKey != 0 && player->getStorageValue(shopBlock.itemStorageKey) < shopBlock.itemStorageValue) { + if (shopBlock.itemStorageKey != 0 && player->storage()->get(shopBlock.itemStorageKey) < shopBlock.itemStorageValue) { AddHiddenShopItem(msg); return; } From 67c078494db30e6f55de4d44abcb20c1370ff95d Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 1 Nov 2024 19:54:43 -0300 Subject: [PATCH 02/20] improve: change forge_history for set "unique key" done_at Converted to miliseconds and set the "done_at" to unique key. --- data-otservbr-global/migrations/46.lua | 30 +++++++++++++++++++++++++- data-otservbr-global/migrations/47.lua | 3 +++ schema.sql | 5 +++-- 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 data-otservbr-global/migrations/47.lua diff --git a/data-otservbr-global/migrations/46.lua b/data-otservbr-global/migrations/46.lua index 86a6d8ffec1..bdd0c851987 100644 --- a/data-otservbr-global/migrations/46.lua +++ b/data-otservbr-global/migrations/46.lua @@ -1,3 +1,31 @@ function onUpdateDatabase() - return false -- true = There are others migrations file | false = this is the last migration file + logger.info("Updating database to version 52 (player forge history unique done_at)") + + db.query([[ + UPDATE forge_history + SET done_at = done_at * 1000 + WHERE done_at < 1000000000000; + ]]) + + db.query([[ + UPDATE forge_history AS f1 + JOIN ( + SELECT + id, + done_at, + ROW_NUMBER() OVER (PARTITION BY done_at ORDER BY id) AS row_num + FROM forge_history + ) AS duplicates ON f1.id = duplicates.id + SET f1.done_at = f1.done_at + (duplicates.row_num * 1) + WHERE duplicates.row_num > 1; + ]]) + + local success = db.query("ALTER TABLE forge_history ADD UNIQUE KEY unique_done_at (done_at);") + + if not success then + logger.error("Failed to add unique key to 'done_at'.") + return false + end + + return true end diff --git a/data-otservbr-global/migrations/47.lua b/data-otservbr-global/migrations/47.lua new file mode 100644 index 00000000000..86a6d8ffec1 --- /dev/null +++ b/data-otservbr-global/migrations/47.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false -- true = There are others migrations file | false = this is the last migration file +end diff --git a/schema.sql b/schema.sql index d81c2914156..43180363baf 100644 --- a/schema.sql +++ b/schema.sql @@ -302,7 +302,7 @@ CREATE TABLE IF NOT EXISTS `daily_reward_history` ( ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; --- Tabble Structure `forge_history` +-- Table Structure `forge_history` CREATE TABLE IF NOT EXISTS `forge_history` ( `id` int NOT NULL AUTO_INCREMENT, `player_id` int NOT NULL, @@ -314,7 +314,8 @@ CREATE TABLE IF NOT EXISTS `forge_history` ( `done_at_date` datetime DEFAULT NOW(), `cost` bigint UNSIGNED NOT NULL DEFAULT '0', `gained` bigint UNSIGNED NOT NULL DEFAULT '0', - CONSTRAINT `forge_history_pk` PRIMARY KEY (`id`), + PRIMARY KEY (`id`), + UNIQUE KEY `unique_done_at` (`done_at`), FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; From e3629530c7e238fa283156f987935b58e0020e5a Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 2 Nov 2024 00:09:27 -0300 Subject: [PATCH 03/20] improve: some reworks in load player --- .../components/player_forge_history.cpp | 41 +- .../components/player_forge_history.hpp | 2 +- .../players/components/player_stash.cpp | 23 +- .../players/components/player_stash.hpp | 2 +- .../players/components/player_storage.cpp | 35 +- src/creatures/players/player.cpp | 4 +- src/game/scheduling/save_manager.cpp | 12 +- src/io/functions/iologindata_load_player.cpp | 675 +++++++++--------- src/io/functions/iologindata_save_player.cpp | 36 +- 9 files changed, 451 insertions(+), 379 deletions(-) diff --git a/src/creatures/players/components/player_forge_history.cpp b/src/creatures/players/components/player_forge_history.cpp index 9089731bbf5..a2b5d871769 100644 --- a/src/creatures/players/components/player_forge_history.cpp +++ b/src/creatures/players/components/player_forge_history.cpp @@ -28,17 +28,16 @@ void PlayerForgeHistory::add(const ForgeHistory &history) { void PlayerForgeHistory::remove(int historyId) { m_removedHistoryIds.push_back(historyId); - m_history.erase( - std::remove_if(m_history.begin(), m_history.end(), [historyId](const ForgeHistory &h) { return h.id == historyId; }), - m_history.end() - ); + auto it = std::ranges::remove_if(m_history, [historyId](const ForgeHistory &h) { + return h.id == historyId; + }); + m_history.erase(it.begin(), it.end()); } bool PlayerForgeHistory::load() { auto playerGUID = m_player.getGUID(); - Database &db = Database::getInstance(); auto query = fmt::format("SELECT * FROM forge_history WHERE player_id = {}", playerGUID); - const DBResult_ptr &result = db.storeQuery(query); + const DBResult_ptr &result = g_database().storeQuery(query); if (!result) { g_logger().debug("Failed to load forge history for player with ID: {}", playerGUID); return false; @@ -66,9 +65,7 @@ bool PlayerForgeHistory::save() { auto removedHistoryIds = m_removedHistoryIds; auto modifiedHistory = m_modifiedHistory; - auto forgeHistorySaveTask = [playerGUID, removedHistoryIds, modifiedHistory]() mutable { - Database &db = Database::getInstance(); - + auto deleteForgeHistoryEntries = [playerGUID, removedHistoryIds]() mutable { if (!removedHistoryIds.empty()) { std::string idsToDelete = fmt::format("{}", fmt::join(removedHistoryIds, ", ")); std::string deleteQuery = fmt::format( @@ -76,30 +73,44 @@ bool PlayerForgeHistory::save() { playerGUID, idsToDelete ); - if (!db.executeQuery(deleteQuery)) { + if (!g_database().executeQuery(deleteQuery)) { g_logger().error("Failed to delete forge history entries for player with ID: {}", playerGUID); - return; + return false; } removedHistoryIds.clear(); } + return true; + }; + + auto insertModifiedHistory = [playerGUID, modifiedHistory]() mutable { DBInsert insertQuery("INSERT INTO `forge_history` (`id`, `player_id`, `action_type`, `description`, `done_at`, `is_success`) VALUES "); insertQuery.upsert({ "action_type", "description", "done_at", "is_success" }); for (const auto &history : modifiedHistory) { - auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, db.escapeString(history.description), history.createdAt, history.success ? 1 : 0); - + auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, g_database().escapeString(history.description), history.createdAt, history.success ? 1 : 0); if (!insertQuery.addRow(row)) { g_logger().warn("Failed to add forge history entry for player with ID: {}", playerGUID); - return; + return false; } - g_logger().debug("Added forge history entry date: {}, for player with ID: {}", formatDate(history.createdAt / 1000), playerGUID); } if (!insertQuery.execute()) { g_logger().error("Failed to execute insertion for forge history entries for player with ID: {}", playerGUID); + return false; + } + + return true; + }; + + auto forgeHistorySaveTask = [deleteForgeHistoryEntries, insertModifiedHistory]() mutable { + if (!deleteForgeHistoryEntries()) { + return; + } + + if (!insertModifiedHistory()) { return; } }; diff --git a/src/creatures/players/components/player_forge_history.hpp b/src/creatures/players/components/player_forge_history.hpp index 9f0086dd41c..ed2b90b91ca 100644 --- a/src/creatures/players/components/player_forge_history.hpp +++ b/src/creatures/players/components/player_forge_history.hpp @@ -40,7 +40,7 @@ struct ForgeHistory { class PlayerForgeHistory { public: - PlayerForgeHistory(Player &player); + explicit PlayerForgeHistory(Player &player); const std::vector &get() const; void add(const ForgeHistory &history); diff --git a/src/creatures/players/components/player_stash.cpp b/src/creatures/players/components/player_stash.cpp index 72ca8ecd11e..975e9017336 100644 --- a/src/creatures/players/components/player_stash.cpp +++ b/src/creatures/players/components/player_stash.cpp @@ -80,7 +80,7 @@ bool PlayerStash::save() { auto removedItems = m_removedItems; auto modifiedItems = m_modifiedItems; - auto stashSaveTask = [playerGUID, removedItems, modifiedItems]() mutable { + auto deleteStashItems = [playerGUID, removedItems]() mutable { Database &db = Database::getInstance(); if (!removedItems.empty()) { @@ -92,12 +92,17 @@ bool PlayerStash::save() { if (!db.executeQuery(deleteQuery)) { g_logger().error("[PlayerStash::save] - Failed to delete removed items for player: {}", playerGUID); - return; + return false; } removedItems.clear(); } + return true; + }; + + auto insertModifiedStashItems = [playerGUID, modifiedItems]() mutable { + Database &db = Database::getInstance(); DBInsert insertQuery("INSERT INTO `player_stash` (`player_id`, `item_id`, `item_count`) VALUES "); insertQuery.upsert({ "item_count" }); @@ -105,12 +110,24 @@ bool PlayerStash::save() { auto row = fmt::format("{}, {}, {}", playerGUID, itemId, itemCount); if (!insertQuery.addRow(row)) { g_logger().warn("[PlayerStash::save] - Failed to add row for stash item: {}", itemId); - return; + return false; } } if (!insertQuery.execute()) { g_logger().error("[PlayerStash::save] - Failed to execute insertion for modified stash items for player: {}", playerGUID); + return false; + } + + return true; + }; + + auto stashSaveTask = [deleteStashItems, insertModifiedStashItems]() mutable { + if (!deleteStashItems()) { + return; + } + + if (!insertModifiedStashItems()) { return; } }; diff --git a/src/creatures/players/components/player_stash.hpp b/src/creatures/players/components/player_stash.hpp index b3c83b248c4..e1b7b94a27a 100644 --- a/src/creatures/players/components/player_stash.hpp +++ b/src/creatures/players/components/player_stash.hpp @@ -13,7 +13,7 @@ class Player; class PlayerStash { public: - PlayerStash(Player &player); + explicit PlayerStash(Player &player); void add(uint16_t itemId, uint32_t count = 1); diff --git a/src/creatures/players/components/player_storage.cpp b/src/creatures/players/components/player_storage.cpp index 2570abd5044..6e2aebb50a0 100644 --- a/src/creatures/players/components/player_storage.cpp +++ b/src/creatures/players/components/player_storage.cpp @@ -104,7 +104,13 @@ bool PlayerStorage::save() { return true; } - auto saveTask = [removedKeys = m_removedKeys, modifiedKeys = m_modifiedKeys, playerGUID = m_player.getGUID(), playerName = m_player.getName(), storageMap = m_storageMap]() mutable { + auto playerGUID = m_player.getGUID(); + auto playerName = m_player.getName(); + auto removedKeys = m_removedKeys; + auto modifiedKeys = m_modifiedKeys; + auto storageMap = m_storageMap; + + auto deleteStorageKeys = [playerGUID, playerName, removedKeys]() mutable { if (!removedKeys.empty()) { std::string keysList = fmt::format("{}", fmt::join(removedKeys, ", ")); std::string deleteQuery = fmt::format( @@ -114,11 +120,13 @@ bool PlayerStorage::save() { if (!g_database().executeQuery(deleteQuery)) { g_logger().error("[SaveManager::playerStorageSaveTask] - Failed to delete storage keys for player: {}", playerName); - return; + return false; } - removedKeys.clear(); } + return true; + }; + auto insertModifiedStorageKeys = [playerGUID, playerName, modifiedKeys, storageMap]() mutable { if (!modifiedKeys.empty()) { DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); storageQuery.upsert({ "value" }); @@ -127,16 +135,25 @@ bool PlayerStorage::save() { auto row = fmt::format("{}, {}, {}", playerGUID, key, storageMap.at(key)); if (!storageQuery.addRow(row)) { g_logger().warn("[SaveManager::playerStorageSaveTask] - Failed to add row for player storage: {}", playerName); - return; + return false; } } if (!storageQuery.execute()) { g_logger().error("[SaveManager::playerStorageSaveTask] - Failed to execute storage insertion for player: {}", playerName); - return; + return false; } + } + return true; + }; - modifiedKeys.clear(); + auto saveTask = [deleteStorageKeys, insertModifiedStorageKeys]() mutable { + if (!deleteStorageKeys()) { + return; + } + + if (!insertModifiedStorageKeys()) { + return; } }; @@ -166,7 +183,8 @@ void PlayerStorage::getReservedRange() { // Generate outfits range uint32_t outfits_key = PSTRG_OUTFITS_RANGE_START; for (const auto &entry : m_player.outfits) { - uint32_t key = ++outfits_key; + outfits_key++; + uint32_t key = outfits_key; m_storageMap[key] = (entry.lookType << 16) | entry.addons; m_modifiedKeys.insert(key); // Track the key for saving } @@ -174,7 +192,8 @@ void PlayerStorage::getReservedRange() { // Generate familiars range uint32_t familiar_key = PSTRG_FAMILIARS_RANGE_START; for (const auto &entry : m_player.familiars) { - uint32_t key = ++familiar_key; + familiar_key++; + uint32_t key = familiar_key; m_storageMap[key] = (entry.lookType << 16); m_modifiedKeys.insert(key); // Track the key for saving } diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index f240390c247..9bcb4fa97aa 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -981,9 +981,9 @@ void Player::closeContainer(uint8_t cid) { if (container && container->isAnyKindOfRewardChest() && !hasOtherRewardContainerOpen(container)) { removeEmptyRewards(); } + + // Be careful when using the "container" after this, it may crash since the iterator has been invalidated openContainers.erase(it); - if (container && container->getID() == ITEM_BROWSEFIELD) { - } } void Player::removeEmptyRewards() { diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index 879ec64b464..0ba421ca354 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -187,11 +187,17 @@ void SaveManager::executeTasks() { } } - threadPool.detach_task([tasks = std::move(m_tasks)] { - for (const auto &task : tasks) { + if (g_configManager().getBoolean(TOGGLE_SAVE_ASYNC)) { + threadPool.detach_task([tasks = std::move(m_tasks)] { + for (const auto &task : tasks) { + task.execute(); + } + }); + } else { + for (const auto &task : m_tasks) { task.execute(); } - }); + } m_tasks.clear(); } diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index bc3c5a5dbae..7ba9c6dc413 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -32,44 +32,39 @@ #include "utils/tools.hpp" void IOLoginDataLoad::loadItems(ItemsMap &itemsMap, const DBResult_ptr &result, const std::shared_ptr &player) { - try { - do { - auto sid = result->getNumber("sid"); - auto pid = result->getNumber("pid"); - auto type = result->getNumber("itemtype"); - auto count = result->getNumber("count"); - unsigned long attrSize; - const char* attr = result->getStream("attributes", attrSize); - PropStream propStream; - propStream.init(attr, attrSize); - - try { - const auto &item = Item::CreateItem(type, count); - if (item) { - if (!item->unserializeAttr(propStream)) { - g_logger().warn("[{}] - Failed to deserialize item attributes {}, from player {}, from account id {}", __FUNCTION__, item->getID(), player->getName(), player->getAccountId()); - continue; - } - itemsMap[sid] = std::make_pair(item, pid); - } else { - g_logger().warn("[{}] - Failed to create item of type {} for player {}, from account id {}", __FUNCTION__, type, player->getName(), player->getAccountId()); - } - } catch (const std::exception &e) { - g_logger().warn("[{}] - Exception during the creation or deserialization of the item: {}", __FUNCTION__, e.what()); - continue; - } - } while (result->next()); - } catch (const std::exception &e) { - g_logger().error("[{}] - General exception during item loading: {}", __FUNCTION__, e.what()); + if (!result || !player) { + g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + return; } + + do { + auto sid = result->getNumber("sid"); + auto pid = result->getNumber("pid"); + auto type = result->getNumber("itemtype"); + auto count = result->getNumber("count"); + unsigned long attrSize; + const char* attr = result->getStream("attributes", attrSize); + PropStream propStream; + propStream.init(attr, attrSize); + + const auto &item = Item::CreateItem(type, count); + if (!item) { + g_logger().warn("[{}] - Failed to create item of type {} for player {}, from account id {}", __FUNCTION__, type, player->getName(), player->getAccountId()); + continue; + } + + if (!item->unserializeAttr(propStream)) { + g_logger().warn("[{}] - Failed to deserialize attributes for item: {}, from player: {}, from account id: {}", __FUNCTION__, item->getID(), player->getName(), player->getAccountId()); + continue; + } + + itemsMap[sid] = std::make_pair(item, pid); + } while (result->next()); } bool IOLoginDataLoad::preLoadPlayer(const std::shared_ptr &player, const std::string &name) { - Database &db = Database::getInstance(); - - std::ostringstream query; - query << "SELECT `id`, `account_id`, `group_id`, `deletion` FROM `players` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + std::string query = fmt::format("SELECT `id`, `account_id`, `group_id`, `deletion` FROM `players` WHERE `name` = {}", g_database().escapeString(name)); + const DBResult_ptr &result = g_database().storeQuery(query); if (!result) { return false; } @@ -330,86 +325,106 @@ void IOLoginDataLoad::loadPlayerSkill(const std::shared_ptr &player, con } void IOLoginDataLoad::loadPlayerKills(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + if (!player) { + g_logger().warn("[{}] - Player nullptr", __FUNCTION__); return; } - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `player_id`, `time`, `target`, `unavenged` FROM `player_kills` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - do { - auto killTime = result->getNumber("time"); - if ((time(nullptr) - killTime) <= g_configManager().getNumber(FRAG_TIME)) { - player->unjustifiedKills.emplace_back(result->getNumber("target"), killTime, result->getNumber("unavenged")); - } - } while (result->next()); + std::string query = fmt::format("SELECT `player_id`, `time`, `target`, `unavenged` FROM `player_kills` WHERE `player_id` = {}", player->getGUID()); + + result = g_database().storeQuery(query); + if (!result) { + return; } + + do { + auto killTime = result->getNumber("time"); + if ((time(nullptr) - killTime) <= g_configManager().getNumber(FRAG_TIME)) { + player->unjustifiedKills.emplace_back( + result->getNumber("target"), + killTime, + result->getNumber("unavenged") + ); + } + } while (result->next()); } void IOLoginDataLoad::loadPlayerGuild(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + if (!player) { + g_logger().warn("[{}] - Player nullptr", __FUNCTION__); return; } - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `guild_id`, `rank_id`, `nick` FROM `guild_membership` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - auto guildId = result->getNumber("guild_id"); - auto playerRankId = result->getNumber("rank_id"); - player->guildNick = result->getString("nick"); + // Query to get player guild membership information + std::string query = fmt::format("SELECT `guild_id`, `rank_id`, `nick` FROM `guild_membership` WHERE `player_id` = {}", player->getGUID()); + result = g_database().storeQuery(query); + if (!result) { + return; + } - auto guild = g_game().getGuild(guildId); - if (!guild) { - guild = IOGuild::loadGuild(guildId); - g_game().addGuild(guild); - } + auto guildId = result->getNumber("guild_id"); + auto playerRankId = result->getNumber("rank_id"); + player->guildNick = result->getString("nick"); - if (guild) { - player->guild = guild; - GuildRank_ptr rank = guild->getRankById(playerRankId); - if (!rank) { - query.str(""); - query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = " << playerRankId; + // Get guild from cache or load it if not present + auto guild = g_game().getGuild(guildId); + if (!guild) { + guild = IOGuild::loadGuild(guildId); + g_game().addGuild(guild); + } - if ((result = db.storeQuery(query.str()))) { - guild->addRank(result->getNumber("id"), result->getString("name"), static_cast(result->getNumber("level"))); - } + if (!guild) { + return; + } - rank = guild->getRankById(playerRankId); - if (!rank) { - player->guild = nullptr; - } - } + player->guild = guild; - player->guildRank = rank; + // Get rank from guild or load it if not present + auto rank = guild->getRankById(playerRankId); + if (!rank) { + query = fmt::format("SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = {}", playerRankId); + result = g_database().storeQuery(query); + if (result) { + guild->addRank( + result->getNumber("id"), + result->getString("name"), + static_cast(result->getNumber("level")) + ); + rank = guild->getRankById(playerRankId); + } + } - IOGuild::getWarList(guildId, player->guildWarVector); + player->guildRank = rank; - query.str(""); - query << "SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = " << guildId; - if ((result = db.storeQuery(query.str()))) { - guild->setMemberCount(result->getNumber("members")); - } - } + if (!rank) { + player->guild = nullptr; + return; + } + + // Load war list for guild + IOGuild::getWarList(guildId, player->guildWarVector); + + // Update guild member count + query = fmt::format("SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = {}", guildId); + result = g_database().storeQuery(query); + if (result) { + guild->setMemberCount(result->getNumber("members")); } } void IOLoginDataLoad::loadPlayerBestiaryCharms(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + if (!player) { + g_logger().warn("[{}] - Player nullptr", __FUNCTION__); return; } - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT * FROM `player_charms` WHERE `player_guid` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { + // Query to get player charms information + std::string query = fmt::format("SELECT * FROM `player_charms` WHERE `player_guid` = {}", player->getGUID()); + result = g_database().storeQuery(query); + if (result) { player->charmPoints = result->getNumber("charm_points"); player->charmExpansion = result->getNumber("charm_expansion"); + player->charmRuneWound = result->getNumber("rune_wound"); player->charmRuneEnflame = result->getNumber("rune_enflame"); player->charmRunePoison = result->getNumber("rune_poison"); @@ -429,13 +444,14 @@ void IOLoginDataLoad::loadPlayerBestiaryCharms(const std::shared_ptr &pl player->charmRuneDivine = result->getNumber("rune_divine"); player->charmRuneVamp = result->getNumber("rune_vamp"); player->charmRuneVoid = result->getNumber("rune_void"); + player->UsedRunesBit = result->getNumber("UsedRunesBit"); player->UnlockedRunesBit = result->getNumber("UnlockedRunesBit"); unsigned long attrBestSize; - const char* Bestattr = result->getStream("tracker list", attrBestSize); + const char* bestiaryAttr = result->getStream("tracker list", attrBestSize); PropStream propBestStream; - propBestStream.init(Bestattr, attrBestSize); + propBestStream.init(bestiaryAttr, attrBestSize); uint16_t monsterRaceId; while (propBestStream.read(monsterRaceId)) { @@ -445,9 +461,9 @@ void IOLoginDataLoad::loadPlayerBestiaryCharms(const std::shared_ptr &pl } } } else { - query.str(""); - query << "INSERT INTO `player_charms` (`player_guid`) VALUES (" << player->getGUID() << ')'; - Database::getInstance().executeQuery(query.str()); + // Insert default row if no player charms data exists + query = fmt::format("INSERT INTO `player_charms` (`player_guid`) VALUES ({})", player->getGUID()); + Database::getInstance().executeQuery(query); } } @@ -457,10 +473,9 @@ void IOLoginDataLoad::loadPlayerInstantSpellList(const std::shared_ptr & return; } - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { + std::string query = fmt::format("SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = {}", player->getGUID()); + result = g_database().storeQuery(query); + if (result) { do { player->learnedInstantSpellList.emplace_back(result->getString("name")); } while (result->next()); @@ -468,89 +483,91 @@ void IOLoginDataLoad::loadPlayerInstantSpellList(const std::shared_ptr & } void IOLoginDataLoad::loadPlayerInventoryItems(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + if (!player) { + g_logger().warn("[{}] - Player nullptr", __FUNCTION__); return; } bool oldProtocol = g_configManager().getBoolean(OLD_PROTOCOL) && player->getProtocolVersion() < 1200; auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_items WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); + result = g_database().storeQuery(query); + if (!result) { + return; + } + ItemsMap inventoryItems; std::vector>> openContainersList; std::vector> itemsToStartDecaying; - try { - if ((result = g_database().storeQuery(query))) { - loadItems(inventoryItems, result, player); + loadItems(inventoryItems, result, player); - for (auto it = inventoryItems.rbegin(), end = inventoryItems.rend(); it != end; ++it) { - const std::pair, int32_t> &pair = it->second; - const auto &item = pair.first; - if (!item) { - continue; - } + for (const auto &[slot, secondMap] : std::views::reverse(inventoryItems)) { + const auto &[item, pid] = secondMap; + if (!item) { + continue; + } - int32_t pid = pair.second; - if (pid >= CONST_SLOT_FIRST && pid <= CONST_SLOT_LAST) { - player->internalAddThing(pid, item); - item->startDecaying(); - } else { - ItemsMap::const_iterator it2 = inventoryItems.find(pid); - if (it2 == inventoryItems.end()) { - continue; - } - - const std::shared_ptr &container = it2->second.first->getContainer(); - if (container) { - container->internalAddThing(item); - // Here, the sub-containers do not yet have a parent, since the main backpack has not yet been added to the player, so we need to postpone - itemsToStartDecaying.emplace_back(item); - } - } + // Adding items to player's inventory or containers + if (pid >= CONST_SLOT_FIRST && pid <= CONST_SLOT_LAST) { + player->internalAddThing(pid, item); + item->startDecaying(); + } else { + auto it = inventoryItems.find(pid); + if (it == inventoryItems.end()) { + continue; + } - const std::shared_ptr &itemContainer = item->getContainer(); - if (itemContainer) { - if (!oldProtocol) { - auto cid = item->getAttribute(ItemAttribute_t::OPENCONTAINER); - if (cid > 0) { - openContainersList.emplace_back(cid, itemContainer); - } - } - for (const bool isLootContainer : { true, false }) { - const auto checkAttribute = isLootContainer ? ItemAttribute_t::QUICKLOOTCONTAINER : ItemAttribute_t::OBTAINCONTAINER; - if (item->hasAttribute(checkAttribute)) { - const auto flags = item->getAttribute(checkAttribute); - - for (uint8_t category = OBJECTCATEGORY_FIRST; category <= OBJECTCATEGORY_LAST; category++) { - if (hasBitSet(1 << category, flags)) { - player->refreshManagedContainer(static_cast(category), itemContainer, isLootContainer, true); - } - } - } - } - } + const auto &container = it->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + itemsToStartDecaying.emplace_back(item); } } - // Now that all items and containers have been added and parent chain is established, start decay - for (const auto &item : itemsToStartDecaying) { - item->startDecaying(); + const auto &itemContainer = item->getContainer(); + if (!itemContainer) { + continue; } + // Managing open containers and attributes for quickloot and obtain containers if (!oldProtocol) { - std::ranges::sort(openContainersList.begin(), openContainersList.end(), [](const std::pair> &left, const std::pair> &right) { - return left.first < right.first; - }); + auto cid = item->getAttribute(ItemAttribute_t::OPENCONTAINER); + if (cid > 0) { + openContainersList.emplace_back(cid, itemContainer); + } + } + + for (const bool isLootContainer : { true, false }) { + const auto checkAttribute = isLootContainer ? ItemAttribute_t::QUICKLOOTCONTAINER : ItemAttribute_t::OBTAINCONTAINER; + if (!item->hasAttribute(checkAttribute)) { + continue; + } - for (auto &it : openContainersList) { - player->addContainer(it.first - 1, it.second); - player->onSendContainer(it.second); + auto flags = item->getAttribute(checkAttribute); + for (uint8_t category = OBJECTCATEGORY_FIRST; category <= OBJECTCATEGORY_LAST; category++) { + if (hasBitSet(1 << category, flags)) { + player->refreshManagedContainer(static_cast(category), itemContainer, isLootContainer, true); + } } } + } - } catch (const std::exception &e) { - g_logger().error("[IOLoginDataLoad::loadPlayerInventoryItems] - Exception during inventory loading: {}", e.what()); + // Starting decay for items + for (const auto &item : itemsToStartDecaying) { + item->startDecaying(); + } + + // Sorting open containers and adding them to player + if (!oldProtocol) { + std::ranges::sort(openContainersList, [](const auto &left, const auto &right) { + return left.first < right.first; + }); + + for (auto &[cid, container] : openContainersList) { + player->addContainer(cid - 1, container); + player->onSendContainer(container); + } } } @@ -571,12 +588,10 @@ void IOLoginDataLoad::loadRewardItems(const std::shared_ptr &player) { return; } + auto query = fmt::format("SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_rewards` WHERE `player_id` = {} ORDER BY `pid`, `sid` ASC", player->getGUID()); + ItemsMap rewardItems; - std::ostringstream query; - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_rewards` WHERE `player_id` = " - << player->getGUID() << " ORDER BY `pid`, `sid` ASC"; - if (auto result = Database::getInstance().storeQuery(query.str())) { + if (const auto &result = Database::getInstance().storeQuery(query)) { loadItems(rewardItems, result, player); bindRewardBag(player, rewardItems); insertItemsIntoRewardBag(rewardItems); @@ -584,91 +599,96 @@ void IOLoginDataLoad::loadRewardItems(const std::shared_ptr &player) { } void IOLoginDataLoad::loadPlayerDepotItems(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + if (!player) { + g_logger().warn("[{}] - Player nullptr", __FUNCTION__); return; } + auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_depotitems WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); + ItemsMap depotItems; std::vector> itemsToStartDecaying; - auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_depotitems WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); - if ((result = g_database().storeQuery(query))) { - loadItems(depotItems, result, player); - for (auto it = depotItems.rbegin(), end = depotItems.rend(); it != end; ++it) { - const std::pair, int32_t> &pair = it->second; - const auto &item = pair.first; - if (!item) { - continue; - } - int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { - const std::shared_ptr &depotChest = player->getDepotChest(pid, true); - if (depotChest) { - depotChest->internalAddThing(item); - item->startDecaying(); - } - } else { - auto depotIt = depotItems.find(pid); - if (depotIt == depotItems.end()) { - continue; - } + result = g_database().storeQuery(query); + if (!result) { + return; + } + + loadItems(depotItems, result, player); + for (const auto &[slot, secondMap] : std::views::reverse(depotItems)) { + const auto &[item, pid] = secondMap; - const std::shared_ptr &container = depotIt->second.first->getContainer(); - if (container) { + if (!item) { + continue; + } + + // Adding items to player's depot chest or containers + if (pid >= 0 && pid < 100) { + if (const auto &depotChest = player->getDepotChest(pid, true)) { + depotChest->internalAddThing(item); + item->startDecaying(); + } + } else { + if (const auto depotIt = depotItems.find(pid); depotIt != depotItems.end()) { + if (const auto &container = depotIt->second.first->getContainer()) { container->internalAddThing(item); - // Here, the sub-containers do not yet have a parent, since the main backpack has not yet been added to the player, so we need to postpone itemsToStartDecaying.emplace_back(item); } } } } - // Now that all items and containers have been added and parent chain is established, start decay + // Start decay for items that were postponed for (const auto &item : itemsToStartDecaying) { item->startDecaying(); } } void IOLoginDataLoad::loadPlayerInboxItems(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + if (!player) { + g_logger().warn("[{}] - Player nullptr", __FUNCTION__); return; } - std::vector> itemsToStartDecaying; auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_inboxitems WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); - if ((result = g_database().storeQuery(query))) { - ItemsMap inboxItems; - loadItems(inboxItems, result, player); - for (auto it = inboxItems.rbegin(), end = inboxItems.rend(); it != end; ++it) { - const std::pair, int32_t> &pair = it->second; - const auto &item = pair.first; - if (!item) { - continue; - } + std::vector> itemsToStartDecaying; + result = g_database().storeQuery(query); + if (!result) { + return; + } - int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { - player->getInbox()->internalAddThing(item); - item->startDecaying(); - } else { - auto inboxIt = inboxItems.find(pid); - if (inboxIt == inboxItems.end()) { - continue; - } + ItemsMap inboxItems; + loadItems(inboxItems, result, player); - const std::shared_ptr &container = inboxIt->second.first->getContainer(); - if (container) { - container->internalAddThing(item); - itemsToStartDecaying.emplace_back(item); - } - } + for (const auto &[slot, secondMap] : std::views::reverse(inboxItems)) { + const auto &[item, pid] = secondMap; + if (!item) { + continue; + } + + // Adding items to player's inbox or containers + if (pid >= 0 && pid < 100) { + player->getInbox()->internalAddThing(item); + item->startDecaying(); + continue; + } + + const auto inboxIt = inboxItems.find(pid); + if (inboxIt == inboxItems.end()) { + continue; + } + + const auto &container = item->getContainer(); + if (!container) { + continue; } + + container->internalAddThing(item); + itemsToStartDecaying.emplace_back(item); } - // Now that all items and containers have been added and parent chain is established, start decay + // Start decay for items that were postponed for (const auto &item : itemsToStartDecaying) { item->startDecaying(); } @@ -682,16 +702,15 @@ void IOLoginDataLoad::loadPlayerVip(const std::shared_ptr &player, DBRes uint32_t accountId = player->getAccountId(); - Database &db = Database::getInstance(); std::string query = fmt::format("SELECT `player_id` FROM `account_viplist` WHERE `account_id` = {}", accountId); - if ((result = db.storeQuery(query))) { + if ((result = g_database().storeQuery(query))) { do { player->vip()->addInternal(result->getNumber("player_id")); } while (result->next()); } query = fmt::format("SELECT `id`, `name`, `customizable` FROM `account_vipgroups` WHERE `account_id` = {}", accountId); - if ((result = db.storeQuery(query))) { + if ((result = g_database().storeQuery(query))) { do { player->vip()->addGroupInternal( result->getNumber("id"), @@ -702,7 +721,7 @@ void IOLoginDataLoad::loadPlayerVip(const std::shared_ptr &player, DBRes } query = fmt::format("SELECT `player_id`, `vipgroup_id` FROM `account_vipgrouplist` WHERE `account_id` = {}", accountId); - if ((result = db.storeQuery(query))) { + if ((result = g_database().storeQuery(query))) { do { player->vip()->addGuidToGroupInternal( result->getNumber("vipgroup_id"), @@ -718,45 +737,50 @@ void IOLoginDataLoad::loadPlayerPreyClass(const std::shared_ptr &player, return; } - if (g_configManager().getBoolean(PREY_ENABLED)) { - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT * FROM `player_prey` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - do { - auto slot = std::make_unique(static_cast(result->getNumber("slot"))); - auto state = static_cast(result->getNumber("state")); - if (slot->id == PreySlot_Two && state == PreyDataState_Locked) { - if (!player->isPremium()) { - slot->state = PreyDataState_Locked; - } else { - slot->state = PreyDataState_Selection; - } - } else { - slot->state = state; - } - slot->selectedRaceId = result->getNumber("raceid"); - slot->option = static_cast(result->getNumber("option")); - slot->bonus = static_cast(result->getNumber("bonus_type")); - slot->bonusRarity = static_cast(result->getNumber("bonus_rarity")); - slot->bonusPercentage = result->getNumber("bonus_percentage"); - slot->bonusTimeLeft = result->getNumber("bonus_time"); - slot->freeRerollTimeStamp = result->getNumber("free_reroll"); - - unsigned long preySize; - const char* preyStream = result->getStream("monster_list", preySize); - PropStream propPreyStream; - propPreyStream.init(preyStream, preySize); - - uint16_t raceId; - while (propPreyStream.read(raceId)) { - slot->raceIdList.push_back(raceId); - } + if (!g_configManager().getBoolean(PREY_ENABLED)) { + return; + } - player->setPreySlotClass(slot); - } while (result->next()); - } + std::string query = fmt::format("SELECT * FROM `player_prey` WHERE `player_id` = {}", player->getGUID()); + result = g_database().storeQuery(query); + if (!result) { + return; } + + do { + auto slot = std::make_unique(static_cast(result->getNumber("slot"))); + auto state = static_cast(result->getNumber("state")); + + if (slot->id == PreySlot_Two && state == PreyDataState_Locked) { + if (!player->isPremium()) { + slot->state = PreyDataState_Locked; + } else { + slot->state = PreyDataState_Selection; + } + } else { + slot->state = state; + } + + slot->selectedRaceId = result->getNumber("raceid"); + slot->option = static_cast(result->getNumber("option")); + slot->bonus = static_cast(result->getNumber("bonus_type")); + slot->bonusRarity = static_cast(result->getNumber("bonus_rarity")); + slot->bonusPercentage = result->getNumber("bonus_percentage"); + slot->bonusTimeLeft = result->getNumber("bonus_time"); + slot->freeRerollTimeStamp = result->getNumber("free_reroll"); + + unsigned long preySize; + const char* preyStream = result->getStream("monster_list", preySize); + PropStream propPreyStream; + propPreyStream.init(preyStream, preySize); + + uint16_t raceId; + while (propPreyStream.read(raceId)) { + slot->raceIdList.push_back(raceId); + } + + player->setPreySlotClass(slot); + } while (result->next()); } void IOLoginDataLoad::loadPlayerTaskHuntingClass(const std::shared_ptr &player, DBResult_ptr result) { @@ -765,85 +789,82 @@ void IOLoginDataLoad::loadPlayerTaskHuntingClass(const std::shared_ptr & return; } - if (g_configManager().getBoolean(TASK_HUNTING_ENABLED)) { - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT * FROM `player_taskhunt` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - do { - auto slot = std::make_unique(static_cast(result->getNumber("slot"))); - auto state = static_cast(result->getNumber("state")); - if (slot->id == PreySlot_Two && state == PreyTaskDataState_Locked) { - if (!player->isPremium()) { - slot->state = PreyTaskDataState_Locked; - } else { - slot->state = PreyTaskDataState_Selection; - } - } else { - slot->state = state; - } - slot->selectedRaceId = result->getNumber("raceid"); - slot->upgrade = result->getNumber("upgrade"); - slot->rarity = static_cast(result->getNumber("rarity")); - slot->currentKills = result->getNumber("kills"); - slot->disabledUntilTimeStamp = result->getNumber("disabled_time"); - slot->freeRerollTimeStamp = result->getNumber("free_reroll"); - - unsigned long taskHuntSize; - const char* taskHuntStream = result->getStream("monster_list", taskHuntSize); - PropStream propTaskHuntStream; - propTaskHuntStream.init(taskHuntStream, taskHuntSize); - - uint16_t raceId; - while (propTaskHuntStream.read(raceId)) { - slot->raceIdList.push_back(raceId); - } + if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) { + return; + } - if (slot->state == PreyTaskDataState_Inactive && slot->disabledUntilTimeStamp < OTSYS_TIME()) { - slot->state = PreyTaskDataState_Selection; - } + std::string query = fmt::format("SELECT * FROM `player_taskhunt` WHERE `player_id` = {}", player->getGUID()); + result = g_database().storeQuery(query); + if (!result) { + return; + } - player->setTaskHuntingSlotClass(slot); - } while (result->next()); + do { + auto slot = std::make_unique(static_cast(result->getNumber("slot"))); + auto state = static_cast(result->getNumber("state")); + + if (slot->id == PreySlot_Two && state == PreyTaskDataState_Locked && player->isPremium()) { + slot->state = PreyTaskDataState_Selection; + } else { + slot->state = state; } - } + + slot->selectedRaceId = result->getNumber("raceid"); + slot->upgrade = result->getNumber("upgrade"); + slot->rarity = static_cast(result->getNumber("rarity")); + slot->currentKills = result->getNumber("kills"); + slot->disabledUntilTimeStamp = result->getNumber("disabled_time"); + slot->freeRerollTimeStamp = result->getNumber("free_reroll"); + + unsigned long taskHuntSize; + const char* taskHuntStream = result->getStream("monster_list", taskHuntSize); + PropStream propTaskHuntStream; + propTaskHuntStream.init(taskHuntStream, taskHuntSize); + + uint16_t raceId; + while (propTaskHuntStream.read(raceId)) { + slot->raceIdList.push_back(raceId); + } + + if (slot->state == PreyTaskDataState_Inactive && slot->disabledUntilTimeStamp < OTSYS_TIME()) { + slot->state = PreyTaskDataState_Selection; + } + + player->setTaskHuntingSlotClass(slot); + } while (result->next()); } void IOLoginDataLoad::loadPlayerBosstiary(const std::shared_ptr &player, DBResult_ptr result) { - if (!result) { - g_logger().warn("[{}] - Result nullptr", __FUNCTION__); + if (!result || !player) { + g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); return; } - if (!player) { - g_logger().warn("[{}] - Player nullptr", __FUNCTION__); + auto query = fmt::format("SELECT * FROM `player_bosstiary` WHERE `player_id` = {}", player->getGUID()); + result = Database::getInstance().storeQuery(query); + if (!result) { return; } - std::ostringstream query; - query << "SELECT * FROM `player_bosstiary` WHERE `player_id` = " << player->getGUID(); - if ((result = Database::getInstance().storeQuery(query.str()))) { - do { - player->setSlotBossId(1, result->getNumber("bossIdSlotOne")); - player->setSlotBossId(2, result->getNumber("bossIdSlotTwo")); - player->setRemoveBossTime(result->getU8FromString(result->getString("removeTimes"), __FUNCTION__)); - - // Tracker - unsigned long size; - const char* chars = result->getStream("tracker", size); - PropStream stream; - stream.init(chars, size); - uint16_t bossid; - while (stream.read(bossid)) { - const auto monsterType = g_monsters().getMonsterTypeByRaceId(bossid, true); - if (!monsterType) { - continue; - } - - player->addMonsterToCyclopediaTrackerList(monsterType, true, false); + do { + player->setSlotBossId(1, result->getNumber("bossIdSlotOne")); + player->setSlotBossId(2, result->getNumber("bossIdSlotTwo")); + player->setRemoveBossTime(result->getU8FromString(result->getString("removeTimes"), __FUNCTION__)); + + // Tracker + unsigned long size; + const char* chars = result->getStream("tracker", size); + PropStream stream; + stream.init(chars, size); + uint16_t bossid; + while (stream.read(bossid)) { + const auto &monsterType = g_monsters().getMonsterTypeByRaceId(bossid, true); + if (!monsterType) { + continue; } - } while (result->next()); - } + player->addMonsterToCyclopediaTrackerList(monsterType, true, false); + } + } while (result->next()); } void IOLoginDataLoad::bindRewardBag(const std::shared_ptr &player, ItemsMap &rewardItemsMap) { @@ -866,14 +887,12 @@ void IOLoginDataLoad::bindRewardBag(const std::shared_ptr &player, Items } void IOLoginDataLoad::insertItemsIntoRewardBag(const ItemsMap &rewardItemsMap) { - for (const auto &it : std::views::reverse(rewardItemsMap)) { - const std::pair, int32_t> &pair = it.second; - const auto &item = pair.first; + for (const auto &[slotId, secondMap] : std::views::reverse(rewardItemsMap)) { + const auto &[item, pid] = secondMap; if (!item) { continue; } - int32_t pid = pair.second; if (pid == 0) { break; } diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index 0d1a89d0781..865b0b3e614 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -338,13 +338,13 @@ bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { } } - auto spellsSaveTask = [deleteQueryStr, spellsQuery]() mutable { + auto spellsSaveTask = [deleteQueryStr, spellsTaskQuery = std::move(spellsQuery)]() mutable { if (!g_database().executeQuery(deleteQueryStr)) { g_logger().error("[SaveManager::spellsSaveTask] - Failed to execute delete query for player spells"); return; } - if (!spellsQuery.execute()) { + if (!spellsTaskQuery.execute()) { g_logger().warn("[SaveManager::spellsSaveTask] - Failed to execute insert query for player spells"); } }; @@ -369,13 +369,13 @@ bool IOLoginDataSave::savePlayerKills(const std::shared_ptr &player) { } } - auto killsSaveTask = [deleteQueryStr, killsQuery]() mutable { + auto killsSaveTask = [deleteQueryStr, killsTaskQuery = std::move(killsQuery)]() mutable { if (!g_database().executeQuery(deleteQueryStr)) { g_logger().warn("[SaveManager::killsSaveTask] - Failed to execute delete query for player kills"); return; } - if (!killsQuery.execute()) { + if (!killsTaskQuery.execute()) { g_logger().warn("[SaveManager::killsSaveTask] - Failed to execute insert query for player kills"); } }; @@ -469,13 +469,13 @@ bool IOLoginDataSave::savePlayerItem(const std::shared_ptr &player) { return false; } - auto itemsSaveTask = [deleteQueryStr, itemsQuery]() mutable { + auto itemsSaveTask = [deleteQueryStr, taskItemsQuery = std::move(itemsQuery)]() mutable { if (!g_database().executeQuery(deleteQueryStr)) { g_logger().warn("[SaveManager::itemsSaveTask] - Failed to execute delete query for player items"); return; } - if (!itemsQuery.execute()) { + if (!taskItemsQuery.execute()) { g_logger().warn("[SaveManager::itemsSaveTask] - Failed to execute insert query for player items"); } }; @@ -506,13 +506,13 @@ bool IOLoginDataSave::savePlayerDepotItems(const std::shared_ptr &player return false; } - auto depotItemsSaveTask = [deleteQueryStr, depotQuery]() mutable { + auto depotItemsSaveTask = [deleteQueryStr, taskDepotQuery = std::move(depotQuery)]() mutable { if (!g_database().executeQuery(deleteQueryStr)) { g_logger().warn("[SaveManager::depotItemsSaveTask] - Failed to execute delete query for depot items"); return; } - if (!depotQuery.execute()) { + if (!taskDepotQuery.execute()) { g_logger().warn("[SaveManager::depotItemsSaveTask] - Failed to execute insert query for depot items"); } }; @@ -549,13 +549,13 @@ bool IOLoginDataSave::saveRewardItems(const std::shared_ptr &player) { return false; } - auto rewardItemsSaveTask = [deleteQueryStr, rewardQuery]() mutable { + auto rewardItemsSaveTask = [deleteQueryStr, taskRewardQuery = std::move(rewardQuery)]() mutable { if (!g_database().executeQuery(deleteQueryStr)) { g_logger().warn("[SaveManager::rewardItemsSaveTask] - Failed to execute delete query for reward items"); return; } - if (!rewardQuery.execute()) { + if (!taskRewardQuery.execute()) { g_logger().warn("[SaveManager::rewardItemsSaveTask] - Failed to execute insert query for reward items"); } }; @@ -584,13 +584,13 @@ bool IOLoginDataSave::savePlayerInbox(const std::shared_ptr &player) { return false; } - auto inboxSaveTask = [deleteQueryStr, inboxQuery]() mutable { + auto inboxSaveTask = [deleteQueryStr, taskInboxQuery = std::move(inboxQuery)]() mutable { if (!g_database().executeQuery(deleteQueryStr)) { g_logger().warn("[SaveManager::inboxSaveTask] - Failed to execute delete query for inbox items"); return; } - if (!inboxQuery.execute()) { + if (!taskInboxQuery.execute()) { g_logger().warn("[SaveManager::inboxSaveTask] - Failed to execute insert query for inbox items"); } }; @@ -634,9 +634,9 @@ bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) } } - auto preySaveTask = [preyQuery]() mutable { + auto preySaveTask = [taskPreyQuery = std::move(preyQuery)]() mutable { Database &db = Database::getInstance(); - if (!preyQuery.execute()) { + if (!taskPreyQuery.execute()) { g_logger().warn("[SaveManager::preySaveTask] - Failed to execute prey slot data insertion"); } }; @@ -682,9 +682,9 @@ bool IOLoginDataSave::savePlayerTaskHuntingClass(const std::shared_ptr & } } - auto taskHuntingSaveTask = [taskHuntQuery]() mutable { + auto taskHuntingSaveTask = [executeTaskHuntQUery = std::move(taskHuntQuery)]() mutable { Database &db = Database::getInstance(); - if (!taskHuntQuery.execute()) { + if (!executeTaskHuntQUery.execute()) { g_logger().warn("[SaveManager::taskHuntingSaveTask] - Failed to execute task hunting data insertion"); } }; @@ -726,8 +726,8 @@ bool IOLoginDataSave::savePlayerBosstiary(const std::shared_ptr &player) return false; } - auto bosstiarySaveTask = [insertQuery]() mutable { - if (!insertQuery.execute()) { + auto bosstiarySaveTask = [insertTaskQuery = std::move(insertQuery)]() mutable { + if (!insertTaskQuery.execute()) { g_logger().warn("[SaveManager::bosstiarySaveTask] - Error executing bosstiary data insertion"); } }; From e8639c7e107ba0f689250b97b44c9221419dca81 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 2 Nov 2024 00:13:13 -0300 Subject: [PATCH 04/20] improve: add "async" identifier to save all players --- src/game/scheduling/save_manager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index 0ba421ca354..58e1cefd563 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -36,7 +36,8 @@ void SaveManager::saveAll() { Benchmark bm_players; const auto &players = game.getPlayers(); - logger.info("Saving {} players...", players.size()); + const auto async = g_configManager().getBoolean(TOGGLE_SAVE_ASYNC); + logger.info("Saving {} players... (Async: {})", players.size(), async ? "Enabled" : "Disabled"); for (const auto &[_, player] : players) { player->loginPosition = player->getPosition(); doSavePlayer(player); From 16760077654212518340343ab70d8a8d23703fce Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 2 Nov 2024 00:36:49 -0300 Subject: [PATCH 05/20] perf: migrate stash to the new PlayerStash class with items caching --- .../players/components/player_stash.cpp | 8 +-- .../players/components/player_storage.cpp | 3 +- src/creatures/players/player.cpp | 57 ++++--------------- src/creatures/players/player.hpp | 6 -- src/game/game.cpp | 11 ++-- src/game/scheduling/save_manager.cpp | 2 +- src/io/functions/iologindata_save_player.cpp | 19 ++----- .../creatures/player/player_functions.cpp | 9 +-- src/server/network/protocol/protocolgame.cpp | 12 ++-- 9 files changed, 37 insertions(+), 90 deletions(-) diff --git a/src/creatures/players/components/player_stash.cpp b/src/creatures/players/components/player_stash.cpp index 975e9017336..91f44f4b703 100644 --- a/src/creatures/players/components/player_stash.cpp +++ b/src/creatures/players/components/player_stash.cpp @@ -81,8 +81,6 @@ bool PlayerStash::save() { auto modifiedItems = m_modifiedItems; auto deleteStashItems = [playerGUID, removedItems]() mutable { - Database &db = Database::getInstance(); - if (!removedItems.empty()) { std::string removedItemIds = fmt::format("{}", fmt::join(removedItems, ", ")); std::string deleteQuery = fmt::format( @@ -90,7 +88,7 @@ bool PlayerStash::save() { playerGUID, removedItemIds ); - if (!db.executeQuery(deleteQuery)) { + if (!g_database().executeQuery(deleteQuery)) { g_logger().error("[PlayerStash::save] - Failed to delete removed items for player: {}", playerGUID); return false; } @@ -102,7 +100,6 @@ bool PlayerStash::save() { }; auto insertModifiedStashItems = [playerGUID, modifiedItems]() mutable { - Database &db = Database::getInstance(); DBInsert insertQuery("INSERT INTO `player_stash` (`player_id`, `item_id`, `item_count`) VALUES "); insertQuery.upsert({ "item_count" }); @@ -139,9 +136,8 @@ bool PlayerStash::save() { } bool PlayerStash::load() { - Database &db = Database::getInstance(); auto query = fmt::format("SELECT `item_count`, `item_id` FROM `player_stash` WHERE `player_id` = {}", m_player.getGUID()); - const DBResult_ptr &result = db.storeQuery(query); + const DBResult_ptr &result = g_database().storeQuery(query); if (!result) { g_logger().debug("[PlayerStash::load] - Failed to load stash items for player: {}", m_player.getName()); return false; diff --git a/src/creatures/players/components/player_storage.cpp b/src/creatures/players/components/player_storage.cpp index 6e2aebb50a0..88f9f045419 100644 --- a/src/creatures/players/components/player_storage.cpp +++ b/src/creatures/players/components/player_storage.cpp @@ -162,9 +162,8 @@ bool PlayerStorage::save() { } bool PlayerStorage::load() { - Database &db = Database::getInstance(); auto query = fmt::format("SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = {}", m_player.getGUID()); - const DBResult_ptr &result = db.storeQuery(query); + const DBResult_ptr &result = g_database().storeQuery(query); if (!result) { g_logger().debug("[PlayerStorage::load] - Failed to load storage keys for player: {}", m_player.getName()); return false; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 9bcb4fa97aa..9f500632bb5 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1196,7 +1196,7 @@ std::pair Player::getForgeSliversAndCores() const { } // Check items from stash - for (const auto &stashToSend = getStashItems(); + for (const auto &stashToSend = stash()->getItems(); const auto &[itemId, itemCount] : stashToSend) { if (itemId == ITEM_FORGE_SLIVER) { sliverCount += itemCount; @@ -2220,7 +2220,7 @@ void Player::onApplyImbuement(const Imbuement* imbuement, const std::shared_ptr< const auto &items = imbuement->getItems(); for (auto &[key, value] : items) { const ItemType &itemType = Item::items[key]; - if (static_self_cast()->getItemTypeCount(key) + this->getStashItemCount(itemType.id) < value) { + if (static_self_cast()->getItemTypeCount(key) + stash()->getCount(itemType.id) < value) { this->sendImbuementResult("You don't have all necessary items."); return; } @@ -2261,7 +2261,7 @@ void Player::onApplyImbuement(const Imbuement* imbuement, const std::shared_ptr< const ItemType &itemType = Item::items[key]; withdrawItemMessage << "Using " << mathItemCount << "x " << itemType.name << " from your supply stash. "; - withdrawItem(itemType.id, mathItemCount); + stash()->remove(itemType.id, mathItemCount); sendTextMessage(MESSAGE_STATUS, withdrawItemMessage.str()); } @@ -4402,7 +4402,7 @@ void Player::stashContainer(const StashContainerList &itemDict) { stashItemDict[item->getID()] = itemCount; } - for (const auto &[itemId, itemCount] : stashItems) { + for (const auto &[itemId, itemCount] : stash()->getItems()) { if (!stashItemDict[itemId]) { stashItemDict[itemId] = itemCount; } else { @@ -4424,7 +4424,7 @@ void Player::stashContainer(const StashContainerList &itemDict) { } const uint16_t iteratorCID = item->getID(); if (g_game().internalRemoveItem(item, itemCount) == RETURNVALUE_NOERROR) { - addItemOnStash(iteratorCID, itemCount); + stash()->add(iteratorCID, itemCount); totalStowed += itemCount; if (isDepotSearchOpenOnItem(iteratorCID)) { refreshDepotSearchOnItem = iteratorCID; @@ -4516,7 +4516,7 @@ bool Player::hasItemCountById(uint16_t itemId, uint32_t itemAmount, bool checkSt } // Check items from stash - for (StashItemList stashToSend = getStashItems(); + for (const auto &stashToSend = stash()->getItems(); const auto &[stashItemId, itemCount] : stashToSend) { if (!checkStash) { break; @@ -4555,50 +4555,13 @@ bool Player::removeItemCountById(uint16_t itemId, uint32_t itemAmount, bool remo } // If there are not enough items in the inventory, we will remove the remaining from stash - if (removeFromStash && amountToRemove > 0 && withdrawItem(itemId, amountToRemove)) { + if (removeFromStash && amountToRemove > 0 && stash()->remove(itemId, amountToRemove)) { return true; } return false; } -void Player::addItemOnStash(uint16_t itemId, uint32_t amount) { - const auto it = stashItems.find(itemId); - if (it != stashItems.end()) { - stashItems[itemId] += amount; - return; - } - - stashItems[itemId] = amount; -} - -uint32_t Player::getStashItemCount(uint16_t itemId) const { - const auto it = stashItems.find(itemId); - if (it != stashItems.end()) { - return it->second; - } - return 0; -} - -bool Player::withdrawItem(uint16_t itemId, uint32_t amount) { - const auto it = stashItems.find(itemId); - if (it != stashItems.end()) { - if (it->second > amount) { - stashItems[itemId] -= amount; - } else if (it->second == amount) { - stashItems.erase(itemId); - } else { - return false; - } - return true; - } - return false; -} - -StashItemList Player::getStashItems() const { - return stashItems; -} - uint32_t Player::getBaseCapacity() const { if (hasFlag(PlayerFlags_t::CannotPickupItem)) { return 0; @@ -8341,7 +8304,7 @@ void Player::requestDepotItems() { } } - for (const auto &[itemId, itemCount] : getStashItems()) { + for (const auto &[itemId, itemCount] : stash()->getItems()) { auto itemMap_it = itemMap.find(itemId); // Stackable items not have upgrade classification if (Item::items[itemId].upgradeClassification > 0) { @@ -8375,7 +8338,7 @@ void Player::requestDepotSearchItem(uint16_t itemId, uint8_t tier) { if (const ItemType &iType = Item::items[itemId]; iType.stackable && iType.wareId > 0) { - stashCount = getStashItemCount(itemId); + stashCount = stash()->getCount(itemId); } const auto &depotLocker = getDepotLocker(getLastDepotId()); @@ -8557,7 +8520,7 @@ std::pair>, std::mapgetItems(); for (const auto &[itemId, itemCount] : stashToSend) { const ItemType &itemType = Item::items[itemId]; if (itemType.wareId != 0) { diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index c645925e081..2983ecbed23 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -490,11 +490,6 @@ class Player final : public Creature, public Cylinder, public Bankable { */ bool removeItemCountById(uint16_t itemId, uint32_t itemAmount, bool removeFromStash = true); - void addItemOnStash(uint16_t itemId, uint32_t amount); - uint32_t getStashItemCount(uint16_t itemId) const; - bool withdrawItem(uint16_t itemId, uint32_t amount); - StashItemList getStashItems() const; - uint32_t getBaseCapacity() const; uint32_t getCapacity() const; @@ -1455,7 +1450,6 @@ class Player final : public Creature, public Cylinder, public Bankable { uint16_t xpBoostPercent = 0; uint16_t staminaXpBoost = 100; int16_t lastDepotId = -1; - StashItemList stashItems; // [ItemID] = amount uint32_t movedItems = 0; // Depot search system diff --git a/src/game/game.cpp b/src/game/game.cpp index 49f8bf21d98..2a8307b56a2 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -26,6 +26,7 @@ #include "creatures/players/highscore_category.hpp" #include "creatures/players/imbuements/imbuements.hpp" #include "creatures/players/player.hpp" +#include "creatures/players/components/player_stash.hpp" #include "creatures/players/vip/player_vip.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "enums/player_wheel.hpp" @@ -4807,7 +4808,7 @@ void Game::playerStashWithdraw(uint32_t playerId, uint16_t itemId, uint32_t coun player->sendTextMessage(MESSAGE_STATUS, ss.str()); - if (player->withdrawItem(itemId, WithdrawCount)) { + if (player->stash()->remove(itemId, WithdrawCount)) { player->addItemFromStash(it.id, WithdrawCount); } else { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); @@ -8472,7 +8473,7 @@ void Game::playerCyclopediaCharacterInfo(const std::shared_ptr &player, case CYCLOPEDIA_CHARACTERINFO_ITEMSUMMARY: { const ItemsTierCountList &inventoryItems = player->getInventoryItemsId(true); const ItemsTierCountList &storeInboxItems = player->getStoreInboxItemsId(); - const StashItemList &supplyStashItems = player->getStashItems(); + const StashItemList &supplyStashItems = player->stash()->getItems(); const ItemsTierCountList &depotBoxItems = player->getDepotChestItemsId(); const ItemsTierCountList &inboxItems = player->getDepotInboxItemsId(); @@ -8851,13 +8852,13 @@ namespace { uint16_t removeAmount = amount; if ( // Init-statement - auto stashItemCount = player->getStashItemCount(itemType.wareId); + auto stashItemCount = player->stash()->getCount(itemType.wareId); // Condition stashItemCount > 0 ) { - if (removeAmount > stashItemCount && player->withdrawItem(itemType.wareId, stashItemCount)) { + if (removeAmount > stashItemCount && player->stash()->remove(itemType.wareId, stashItemCount)) { removeAmount -= stashItemCount; - } else if (player->withdrawItem(itemType.wareId, removeAmount)) { + } else if (player->stash()->remove(itemType.wareId, removeAmount)) { removeAmount = 0; } else { offerStatus << "Failed to remove stash items from player " << player->getName(); diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index 58e1cefd563..6767f71513a 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -114,9 +114,9 @@ bool SaveManager::doSavePlayer(std::shared_ptr player) { bool SaveManager::savePlayer(std::shared_ptr player) { if (player->isOnline()) { schedulePlayer(player); - logger.debug("saving player {}, but is online scheduling....", player->getName()); return true; } + return doSavePlayer(player); } diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index 865b0b3e614..8b328028138 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -25,9 +25,6 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite return false; } - const Database &db = Database::getInstance(); - std::ostringstream ss; - // Initialize variables using ContainerBlock = std::pair, int32_t>; std::list queue; @@ -79,8 +76,8 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite const char* attributes = propWriteStream.getStream(attributesSize); // Build query string and add row - ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, static_cast(attributesSize)); - if (!query_insert.addRow(ss)) { + auto row = fmt::format("{},{},{},{},{},{}", player->getGUID(), pid, runningId, item->getID(), item->getSubType(), g_database().escapeBlob(attributes, static_cast(attributesSize))); + if (!query_insert.addRow(row)) { g_logger().error("Error adding row to query."); return false; } @@ -91,6 +88,7 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite const ContainerBlock &cb = queue.front(); const std::shared_ptr &container = cb.first; if (!container) { + queue.pop_front(); continue; } @@ -134,8 +132,8 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite const char* attributes = propWriteStream.getStream(attributesSize); // Build query string and add row - ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, static_cast(attributesSize)); - if (!query_insert.addRow(ss)) { + auto row = fmt::format("{},{},{},{},{},{}", player->getGUID(), parentId, runningId, item->getID(), item->getSubType(), g_database().escapeBlob(attributes, static_cast(attributesSize))); + if (!query_insert.addRow(row)) { g_logger().error("Error adding row to query for container item."); return false; } @@ -158,8 +156,6 @@ bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { player->changeHealth(1); } - Database &db = Database::getInstance(); - // Check if `save` flag is set auto result = g_database().storeQuery(fmt::format("SELECT `save` FROM `players` WHERE `id` = {}", player->getGUID())); if (!result) { @@ -173,7 +169,6 @@ bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { "UPDATE `players` SET `lastlogin` = {}, `lastip` = {} WHERE `id` = {}", player->lastLoginSaved, player->lastIP, player->getGUID() )]() { - Database &db = Database::getInstance(); if (!g_database().executeQuery(queryStr)) { g_logger().warn("[SaveManager::quickUpdateTask] - Failed to execute quick update for player."); return; @@ -390,8 +385,6 @@ bool IOLoginDataSave::savePlayerBestiarySystem(const std::shared_ptr &pl return false; } - Database &db = Database::getInstance(); - PropWriteStream propBestiaryStream; for (const auto &trackedType : player->getCyclopediaMonsterTrackerSet(false)) { propBestiaryStream.write(trackedType->info.raceid); @@ -635,7 +628,6 @@ bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) } auto preySaveTask = [taskPreyQuery = std::move(preyQuery)]() mutable { - Database &db = Database::getInstance(); if (!taskPreyQuery.execute()) { g_logger().warn("[SaveManager::preySaveTask] - Failed to execute prey slot data insertion"); } @@ -683,7 +675,6 @@ bool IOLoginDataSave::savePlayerTaskHuntingClass(const std::shared_ptr & } auto taskHuntingSaveTask = [executeTaskHuntQUery = std::move(taskHuntQuery)]() mutable { - Database &db = Database::getInstance(); if (!executeTaskHuntQUery.execute()) { g_logger().warn("[SaveManager::taskHuntingSaveTask] - Failed to execute task hunting data insertion"); } diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 2788a63551d..3d223093a25 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -17,6 +17,7 @@ #include "creatures/players/achievement/player_achievement.hpp" #include "creatures/players/cyclopedia/player_cyclopedia.hpp" #include "creatures/players/cyclopedia/player_title.hpp" +#include "creatures/players/components/player_stash.hpp" #include "creatures/players/components/player_storage.hpp" #include "creatures/players/player.hpp" #include "creatures/players/vip/player_vip.hpp" @@ -761,7 +762,7 @@ int PlayerFunctions::luaPlayerGetStashCounter(lua_State* L) { // player:getStashCount() const auto &player = getUserdataShared(L, 1); if (player) { - const uint16_t sizeStash = getStashSize(player->getStashItems()); + const uint16_t sizeStash = player->stash()->getSize(); lua_pushnumber(L, sizeStash); } else { lua_pushnil(L); @@ -1339,7 +1340,7 @@ int PlayerFunctions::luaPlayerGetStashItemCount(lua_State* L) { return 1; } - lua_pushnumber(L, player->getStashItemCount(itemType.id)); + lua_pushnumber(L, player->stash()->getCount(itemType.id)); return 1; } @@ -1941,7 +1942,7 @@ int PlayerFunctions::luaPlayerAddItemStash(lua_State* L) { const auto itemId = getNumber(L, 2); const auto count = getNumber(L, 3, 1); - player->addItemOnStash(itemId, count); + player->stash()->add(itemId, count); pushBoolean(L, true); return 1; } @@ -1972,7 +1973,7 @@ int PlayerFunctions::luaPlayerRemoveStashItem(lua_State* L) { } const uint32_t count = getNumber(L, 3); - pushBoolean(L, player->withdrawItem(itemType.id, count)); + pushBoolean(L, player->stash()->remove(itemType.id, count)); return 1; } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index f5c07c25a5a..f03c795d330 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -31,6 +31,7 @@ #include "creatures/players/player.hpp" #include "creatures/players/vip/player_vip.hpp" #include "creatures/players/components/player_forge_history.hpp" +#include "creatures/players/components/player_stash.hpp" #include "creatures/players/components/player_storage.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "enums/player_icons.hpp" @@ -7946,7 +7947,7 @@ void ProtocolGame::openImbuementWindow(const std::shared_ptr &item) { for (const auto &itm : items) { if (!needItems.count(itm.first)) { needItems[itm.first] = player->getItemTypeCount(itm.first); - uint32_t stashCount = player->getStashItemCount(Item::items[itm.first].id); + uint32_t stashCount = player->stash()->getCount(Item::items[itm.first].id); if (stashCount > 0) { needItems[itm.first] += stashCount; } @@ -8508,13 +8509,14 @@ void ProtocolGame::sendOpenStash() { NetworkMessage msg; msg.addByte(0x29); - StashItemList list = player->getStashItems(); - msg.add(list.size()); - for (auto item : list) { + const auto &stashItems = player->stash()->getItems(); + msg.add(stashItems.size()); + for (auto item : stashItems) { msg.add(item.first); msg.add(item.second); } - msg.add(static_cast(g_configManager().getNumber(STASH_ITEMS) - getStashSize(list))); + const auto stashSize = player->stash()->getSize(); + msg.add(static_cast(g_configManager().getNumber(STASH_ITEMS) - stashSize)); writeToOutputBuffer(msg); } From 142f6b2adf863942c26c3d00fb3051c4957149de Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 2 Nov 2024 01:14:36 -0300 Subject: [PATCH 06/20] fix: sonar --- src/io/functions/iologindata_save_player.cpp | 122 ++++++++++--------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index 8b328028138..72b4741928c 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -598,43 +598,45 @@ bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) return false; } - if (g_configManager().getBoolean(PREY_ENABLED)) { - DBInsert preyQuery("INSERT INTO player_prey " - "(`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, " - "`bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) VALUES "); - preyQuery.upsert({ "state", "raceid", "option", "bonus_type", "bonus_rarity", - "bonus_percentage", "bonus_time", "free_reroll", "monster_list" }); - - auto playerGUID = player->getGUID(); - for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { - if (const auto &slot = player->getPreySlotById(static_cast(slotId))) { - PropWriteStream propPreyStream; - for (uint16_t raceId : slot->raceIdList) { - propPreyStream.write(raceId); - } + if (!g_configManager().getBoolean(PREY_ENABLED)) { + return true; + } - size_t preySize; - const char* preyList = propPreyStream.getStream(preySize); - auto escapedPreyList = g_database().escapeBlob(preyList, static_cast(preySize)); + DBInsert preyQuery("INSERT INTO player_prey " + "(`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, " + "`bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) VALUES "); + preyQuery.upsert({ "state", "raceid", "option", "bonus_type", "bonus_rarity", + "bonus_percentage", "bonus_time", "free_reroll", "monster_list" }); - // Format row data for batch insert - auto row = fmt::format("{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}", playerGUID, static_cast(slot->id), static_cast(slot->state), slot->selectedRaceId, static_cast(slot->option), static_cast(slot->bonus), static_cast(slot->bonusRarity), slot->bonusPercentage, slot->bonusTimeLeft, slot->freeRerollTimeStamp, escapedPreyList); + auto playerGUID = player->getGUID(); + for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { + if (const auto &slot = player->getPreySlotById(static_cast(slotId))) { + PropWriteStream propPreyStream; + for (uint16_t raceId : slot->raceIdList) { + propPreyStream.write(raceId); + } - if (!preyQuery.addRow(row)) { - g_logger().warn("[IOLoginDataSave::savePlayerPreyClass] - Failed to add prey slot data for player: {}", player->getName()); - return false; - } + size_t preySize; + const char* preyList = propPreyStream.getStream(preySize); + auto escapedPreyList = g_database().escapeBlob(preyList, static_cast(preySize)); + + // Format row data for batch insert + auto row = fmt::format("{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}", playerGUID, static_cast(slot->id), static_cast(slot->state), slot->selectedRaceId, static_cast(slot->option), static_cast(slot->bonus), static_cast(slot->bonusRarity), slot->bonusPercentage, slot->bonusTimeLeft, slot->freeRerollTimeStamp, escapedPreyList); + + if (!preyQuery.addRow(row)) { + g_logger().warn("[IOLoginDataSave::savePlayerPreyClass] - Failed to add prey slot data for player: {}", player->getName()); + return false; } } + } - auto preySaveTask = [taskPreyQuery = std::move(preyQuery)]() mutable { - if (!taskPreyQuery.execute()) { - g_logger().warn("[SaveManager::preySaveTask] - Failed to execute prey slot data insertion"); - } - }; + auto preySaveTask = [taskPreyQuery = std::move(preyQuery)]() mutable { + if (!taskPreyQuery.execute()) { + g_logger().warn("[SaveManager::preySaveTask] - Failed to execute prey slot data insertion"); + } + }; - g_saveManager().addTask(preySaveTask, "IOLoginDataSave::savePlayerPreyClass - preySaveTask"); - } + g_saveManager().addTask(preySaveTask, "IOLoginDataSave::savePlayerPreyClass - preySaveTask"); return true; } @@ -645,43 +647,45 @@ bool IOLoginDataSave::savePlayerTaskHuntingClass(const std::shared_ptr & return false; } - if (g_configManager().getBoolean(TASK_HUNTING_ENABLED)) { - DBInsert taskHuntQuery("INSERT INTO `player_taskhunt` " - "(`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, " - "`kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES "); - taskHuntQuery.upsert({ "state", "raceid", "upgrade", "rarity", "kills", "disabled_time", - "free_reroll", "monster_list" }); + if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) { + return true; + } - auto playerGUID = player->getGUID(); - for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { - if (const auto &slot = player->getTaskHuntingSlotById(static_cast(slotId))) { - PropWriteStream propTaskHuntingStream; - for (uint16_t raceId : slot->raceIdList) { - propTaskHuntingStream.write(raceId); - } + DBInsert taskHuntQuery("INSERT INTO `player_taskhunt` " + "(`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, " + "`kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES "); + taskHuntQuery.upsert({ "state", "raceid", "upgrade", "rarity", "kills", "disabled_time", + "free_reroll", "monster_list" }); - size_t taskHuntingSize; - const char* taskHuntingList = propTaskHuntingStream.getStream(taskHuntingSize); - auto escapedTaskHuntingList = g_database().escapeBlob(taskHuntingList, static_cast(taskHuntingSize)); + auto playerGUID = player->getGUID(); + for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { + if (const auto &slot = player->getTaskHuntingSlotById(static_cast(slotId))) { + PropWriteStream propTaskHuntingStream; + for (uint16_t raceId : slot->raceIdList) { + propTaskHuntingStream.write(raceId); + } - // Construct row for batch insert - auto row = fmt::format("{}, {}, {}, {}, {}, {}, {}, {}, {}, {}", playerGUID, static_cast(slot->id), static_cast(slot->state), slot->selectedRaceId, slot->upgrade ? 1 : 0, static_cast(slot->rarity), slot->currentKills, slot->disabledUntilTimeStamp, slot->freeRerollTimeStamp, escapedTaskHuntingList); + size_t taskHuntingSize; + const char* taskHuntingList = propTaskHuntingStream.getStream(taskHuntingSize); + auto escapedTaskHuntingList = g_database().escapeBlob(taskHuntingList, static_cast(taskHuntingSize)); - if (!taskHuntQuery.addRow(row)) { - g_logger().warn("[IOLoginDataSave::savePlayerTaskHuntingClass] - Failed to add task hunting slot data for player: {}", player->getName()); - return false; - } + // Construct row for batch insert + auto row = fmt::format("{}, {}, {}, {}, {}, {}, {}, {}, {}, {}", playerGUID, static_cast(slot->id), static_cast(slot->state), slot->selectedRaceId, slot->upgrade ? 1 : 0, static_cast(slot->rarity), slot->currentKills, slot->disabledUntilTimeStamp, slot->freeRerollTimeStamp, escapedTaskHuntingList); + + if (!taskHuntQuery.addRow(row)) { + g_logger().warn("[IOLoginDataSave::savePlayerTaskHuntingClass] - Failed to add task hunting slot data for player: {}", player->getName()); + return false; } } + } - auto taskHuntingSaveTask = [executeTaskHuntQUery = std::move(taskHuntQuery)]() mutable { - if (!executeTaskHuntQUery.execute()) { - g_logger().warn("[SaveManager::taskHuntingSaveTask] - Failed to execute task hunting data insertion"); - } - }; + auto taskHuntingSaveTask = [executeTaskHuntQUery = std::move(taskHuntQuery)]() mutable { + if (!executeTaskHuntQUery.execute()) { + g_logger().warn("[SaveManager::taskHuntingSaveTask] - Failed to execute task hunting data insertion"); + } + }; - g_saveManager().addTask(taskHuntingSaveTask, "IOLoginDataSave::savePlayerTaskHuntingClass - taskHuntingSaveTask"); - } + g_saveManager().addTask(taskHuntingSaveTask, "IOLoginDataSave::savePlayerTaskHuntingClass - taskHuntingSaveTask"); return true; } From 6dbf894600788723e35f8fa51d1ab7dfcc328c28 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 2 Nov 2024 15:24:11 -0300 Subject: [PATCH 07/20] fix: loading of forge history --- data-otservbr-global/migrations/46.lua | 2 +- src/io/iologindata.cpp | 2 +- src/server/network/protocol/protocolgame.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data-otservbr-global/migrations/46.lua b/data-otservbr-global/migrations/46.lua index bdd0c851987..e0f60f6ac7a 100644 --- a/data-otservbr-global/migrations/46.lua +++ b/data-otservbr-global/migrations/46.lua @@ -1,5 +1,5 @@ function onUpdateDatabase() - logger.info("Updating database to version 52 (player forge history unique done_at)") + logger.info("Updating database to version 47 (player forge history unique done_at)") db.query([[ UPDATE forge_history diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index e721045a7f3..d30c46dc4ed 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -169,7 +169,7 @@ bool IOLoginData::loadPlayer(const std::shared_ptr &player, const DBResu } // load forge history - player->stash()->load(); + player->forgeHistory()->load(); // load bosstiary IOLoginDataLoad::loadPlayerBosstiary(player, result); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index f03c795d330..3273d4a0645 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -5703,7 +5703,7 @@ void ProtocolGame::sendForgeHistory(uint8_t page) { if (historyPageToSend > 0) { for (const auto &history : historyPerPage) { auto action = magic_enum::enum_integer(history.actionType); - msg.add(static_cast(history.createdAt)); + msg.add(history.createdAt / 1000); msg.addByte(action); msg.addString(history.description); msg.addByte((history.bonus >= 1 && history.bonus < 8) ? 0x01 : 0x00); From 322402ff2dda50c83316b6dcb6fb118706c57b20 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 5 Nov 2024 02:16:16 -0300 Subject: [PATCH 08/20] fix: getTImeMsNow --- src/creatures/players/player.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 9f500632bb5..b72ce40951f 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -8956,7 +8956,7 @@ void Player::forgeFuseItems(ForgeAction_t actionType, uint16_t firstItemId, uint history.firstItemName = firstForgingItem->getName(); history.secondItemName = secondForgingItem->getName(); history.bonus = bonus; - history.createdAt = getTimeNow(); + history.createdAt = getTimeMsNow(); history.convergence = convergence; registerForgeHistoryDescription(history); @@ -9089,7 +9089,7 @@ void Player::forgeTransferItemTier(ForgeAction_t actionType, uint16_t donorItemI history.firstItemName = Item::items[donorItemId].name; history.secondItemName = newReceiveItem->getName(); - history.createdAt = getTimeNow(); + history.createdAt = getTimeMsNow(); history.convergence = convergence; registerForgeHistoryDescription(history); @@ -9173,7 +9173,7 @@ void Player::forgeResourceConversion(ForgeAction_t actionType) { addForgeDustLevel(1); } - history.createdAt = getTimeNow(); + history.createdAt = getTimeMsNow(); registerForgeHistoryDescription(history); sendForgingData(); } From 7c088c1500274dfb5b9a043b79f584a4f650e929 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 5 Nov 2024 03:02:33 -0300 Subject: [PATCH 09/20] fix: some reviews from Pedrin --- .../players/components/player_forge_history.cpp | 10 +++++----- src/creatures/players/components/player_stash.cpp | 12 ++++++------ src/creatures/players/components/player_storage.cpp | 12 ++++++------ src/io/functions/iologindata_load_player.cpp | 6 +----- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/creatures/players/components/player_forge_history.cpp b/src/creatures/players/components/player_forge_history.cpp index a2b5d871769..4cd990e8e27 100644 --- a/src/creatures/players/components/player_forge_history.cpp +++ b/src/creatures/players/components/player_forge_history.cpp @@ -22,7 +22,7 @@ const std::vector &PlayerForgeHistory::get() const { } void PlayerForgeHistory::add(const ForgeHistory &history) { - m_history.push_back(history); + m_history.emplace_back(history); m_modifiedHistory.push_back(history); } @@ -50,7 +50,7 @@ bool PlayerForgeHistory::load() { history.description = result->getString("description"); history.createdAt = result->getNumber("done_at"); history.success = result->getNumber("is_success"); - m_history.push_back(history); + m_history.emplace_back(history); } while (result->next()); return true; @@ -74,7 +74,7 @@ bool PlayerForgeHistory::save() { ); if (!g_database().executeQuery(deleteQuery)) { - g_logger().error("Failed to delete forge history entries for player with ID: {}", playerGUID); + g_logger().error("[{}] failed to delete forge history entries for player with ID: {}", std::source_location::current().function_name(), playerGUID); return false; } @@ -91,14 +91,14 @@ bool PlayerForgeHistory::save() { for (const auto &history : modifiedHistory) { auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, g_database().escapeString(history.description), history.createdAt, history.success ? 1 : 0); if (!insertQuery.addRow(row)) { - g_logger().warn("Failed to add forge history entry for player with ID: {}", playerGUID); + g_logger().warn("[{}] failed to add forge history entry for player with ID: {}", std::source_location::current().function_name(), playerGUID); return false; } g_logger().debug("Added forge history entry date: {}, for player with ID: {}", formatDate(history.createdAt / 1000), playerGUID); } if (!insertQuery.execute()) { - g_logger().error("Failed to execute insertion for forge history entries for player with ID: {}", playerGUID); + g_logger().error("[{}] failed to execute insertion for forge history entries for player with ID: {}", std::source_location::current().function_name(), playerGUID); return false; } diff --git a/src/creatures/players/components/player_stash.cpp b/src/creatures/players/components/player_stash.cpp index 91f44f4b703..b24a24af4ba 100644 --- a/src/creatures/players/components/player_stash.cpp +++ b/src/creatures/players/components/player_stash.cpp @@ -80,7 +80,7 @@ bool PlayerStash::save() { auto removedItems = m_removedItems; auto modifiedItems = m_modifiedItems; - auto deleteStashItems = [playerGUID, removedItems]() mutable { + auto deleteStashItems = [playerGUID, removedItems = std::move(removedItems)]() mutable { if (!removedItems.empty()) { std::string removedItemIds = fmt::format("{}", fmt::join(removedItems, ", ")); std::string deleteQuery = fmt::format( @@ -89,7 +89,7 @@ bool PlayerStash::save() { ); if (!g_database().executeQuery(deleteQuery)) { - g_logger().error("[PlayerStash::save] - Failed to delete removed items for player: {}", playerGUID); + g_logger().error("[{}] failed to delete removed items for player: {}", std::source_location::current().function_name(), playerGUID); return false; } @@ -99,20 +99,20 @@ bool PlayerStash::save() { return true; }; - auto insertModifiedStashItems = [playerGUID, modifiedItems]() mutable { + auto insertModifiedStashItems = [playerGUID, modifiedItems = std::move(modifiedItems)]() mutable { DBInsert insertQuery("INSERT INTO `player_stash` (`player_id`, `item_id`, `item_count`) VALUES "); insertQuery.upsert({ "item_count" }); for (const auto &[itemId, itemCount] : modifiedItems) { auto row = fmt::format("{}, {}, {}", playerGUID, itemId, itemCount); if (!insertQuery.addRow(row)) { - g_logger().warn("[PlayerStash::save] - Failed to add row for stash item: {}", itemId); + g_logger().warn("[{}] - Failed to add row for stash item: {}", std::source_location::current().function_name(), itemId); return false; } } if (!insertQuery.execute()) { - g_logger().error("[PlayerStash::save] - Failed to execute insertion for modified stash items for player: {}", playerGUID); + g_logger().error("[{}] - Failed to execute insertion for modified stash items for player: {}", std::source_location::current().function_name(), playerGUID); return false; } @@ -139,7 +139,7 @@ bool PlayerStash::load() { auto query = fmt::format("SELECT `item_count`, `item_id` FROM `player_stash` WHERE `player_id` = {}", m_player.getGUID()); const DBResult_ptr &result = g_database().storeQuery(query); if (!result) { - g_logger().debug("[PlayerStash::load] - Failed to load stash items for player: {}", m_player.getName()); + g_logger().debug("[{}] - Failed to load stash items for player: {}", std::source_location::current().function_name(), m_player.getGUID()); return false; } diff --git a/src/creatures/players/components/player_storage.cpp b/src/creatures/players/components/player_storage.cpp index 88f9f045419..d22d2e75138 100644 --- a/src/creatures/players/components/player_storage.cpp +++ b/src/creatures/players/components/player_storage.cpp @@ -39,7 +39,7 @@ void PlayerStorage::add(const uint32_t key, const int32_t value, const bool shou ); return; } else { - g_logger().warn("Unknown reserved key: {} for player: {}", key, m_player.getName()); + g_logger().warn("[{}] unknown reserved key: {} for player: {}", std::source_location::current().function_name(), key, m_player.getName()); return; } } @@ -92,7 +92,7 @@ int32_t PlayerStorage::get(const std::string &storageName) const { void PlayerStorage::add(const std::string &storageName, const int32_t value) { auto it = g_storages().getStorageMap().find(storageName); if (it == g_storages().getStorageMap().end()) { - g_logger().error("[{}] Storage name '{}' not found in storage map, register your storage in 'storages.xml' first for use", __func__, storageName); + g_logger().error("[{}] storage name '{}' not found in storage map, register your storage in 'storages.xml' first for use", std::source_location::current().function_name(), storageName); return; } const uint32_t key = it->second; @@ -119,7 +119,7 @@ bool PlayerStorage::save() { ); if (!g_database().executeQuery(deleteQuery)) { - g_logger().error("[SaveManager::playerStorageSaveTask] - Failed to delete storage keys for player: {}", playerName); + g_logger().error("[{}] failed to delete storage keys for player: {}", std::source_location::current().function_name(), playerName); return false; } } @@ -134,13 +134,13 @@ bool PlayerStorage::save() { for (const auto &key : modifiedKeys) { auto row = fmt::format("{}, {}, {}", playerGUID, key, storageMap.at(key)); if (!storageQuery.addRow(row)) { - g_logger().warn("[SaveManager::playerStorageSaveTask] - Failed to add row for player storage: {}", playerName); + g_logger().warn("[{}] failed to add row for player storage: {}", std::source_location::current().function_name(), playerName); return false; } } if (!storageQuery.execute()) { - g_logger().error("[SaveManager::playerStorageSaveTask] - Failed to execute storage insertion for player: {}", playerName); + g_logger().error("[{}] failed to execute storage insertion for player: {}", std::source_location::current().function_name(), playerName); return false; } } @@ -165,7 +165,7 @@ bool PlayerStorage::load() { auto query = fmt::format("SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = {}", m_player.getGUID()); const DBResult_ptr &result = g_database().storeQuery(query); if (!result) { - g_logger().debug("[PlayerStorage::load] - Failed to load storage keys for player: {}", m_player.getName()); + g_logger().debug("[{}] failed to load storage keys for player: {}", std::source_location::current().function_name(), m_player.getName()); return false; } diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 7ba9c6dc413..2a0b140ef91 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -752,11 +752,7 @@ void IOLoginDataLoad::loadPlayerPreyClass(const std::shared_ptr &player, auto state = static_cast(result->getNumber("state")); if (slot->id == PreySlot_Two && state == PreyDataState_Locked) { - if (!player->isPremium()) { - slot->state = PreyDataState_Locked; - } else { - slot->state = PreyDataState_Selection; - } + slot->state = player->isPremium() ? PreyDataState_Selection : PreyDataState_Locked; } else { slot->state = state; } From 261622b2fe6561d93074b63a5cdb8cf8b2d775d9 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 5 Nov 2024 18:06:07 -0300 Subject: [PATCH 10/20] fix: simplify logic --- src/game/scheduling/save_manager.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index 6767f71513a..d9434111f0a 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -188,16 +188,16 @@ void SaveManager::executeTasks() { } } - if (g_configManager().getBoolean(TOGGLE_SAVE_ASYNC)) { - threadPool.detach_task([tasks = std::move(m_tasks)] { - for (const auto &task : tasks) { - task.execute(); - } - }); - } else { - for (const auto &task : m_tasks) { + auto executeTasks = [tasks = std::move(m_tasks)]() { + for (const auto &task : tasks) { task.execute(); } + }; + + if (g_configManager().getBoolean(TOGGLE_SAVE_ASYNC)) { + threadPool.detach_task(executeTasks); + } else { + executeTasks(); } m_tasks.clear(); From 361c4a2e1fb90497613fa43ad91385c413bc238d Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 6 Nov 2024 02:24:03 -0300 Subject: [PATCH 11/20] fix: logic error --- src/io/functions/iologindata_load_player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 2a0b140ef91..f6a03edd26d 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -679,7 +679,7 @@ void IOLoginDataLoad::loadPlayerInboxItems(const std::shared_ptr &player continue; } - const auto &container = item->getContainer(); + const auto &container = inboxIt->second.first->getContainer(); if (!container) { continue; } From d0ef23fc4901ee496d3525e516de4b2f05096c84 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 6 Nov 2024 22:25:51 -0300 Subject: [PATCH 12/20] fix: from flat hash map to unordered map Iterating with "for" range based on phmap::flat_hash_map is very expensive. --- src/account/account.cpp | 2 +- src/account/account.hpp | 2 +- src/account/account_info.hpp | 3 +-- src/creatures/npcs/npc.cpp | 2 +- src/game/game.hpp | 22 +++++++++++----------- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/account/account.cpp b/src/account/account.cpp index 93596f77b15..ceb3c055cbe 100644 --- a/src/account/account.cpp +++ b/src/account/account.cpp @@ -249,7 +249,7 @@ void Account::updatePremiumTime() { } } -std::tuple, AccountErrors_t> +std::tuple, AccountErrors_t> Account::getAccountPlayers() const { using enum AccountErrors_t; auto valueToReturn = m_accLoaded ? Ok : NotInitialized; diff --git a/src/account/account.hpp b/src/account/account.hpp index 2c6098a8dbd..13090d075c9 100644 --- a/src/account/account.hpp +++ b/src/account/account.hpp @@ -117,7 +117,7 @@ class Account { void updatePremiumTime(); - std::tuple, AccountErrors_t> getAccountPlayers() const; + std::tuple, AccountErrors_t> getAccountPlayers() const; // Old protocol compat void setProtocolCompat(bool toggle); diff --git a/src/account/account_info.hpp b/src/account/account_info.hpp index b9dad60dbbc..da66c00b7e5 100644 --- a/src/account/account_info.hpp +++ b/src/account/account_info.hpp @@ -10,7 +10,6 @@ #pragma once #ifndef USE_PRECOMPILED_HEADERS - #include #include #endif @@ -23,7 +22,7 @@ struct AccountInfo { uint32_t premiumRemainingDays = 0; time_t premiumLastDay = 0; AccountType accountType = ACCOUNT_TYPE_NONE; - phmap::flat_hash_map players; + std::unordered_map players; bool oldProtocol = false; time_t sessionExpires = 0; uint32_t premiumDaysPurchased = 0; diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp index e2ab99a1dd4..2252a51e7e2 100644 --- a/src/creatures/npcs/npc.cpp +++ b/src/creatures/npcs/npc.cpp @@ -442,7 +442,7 @@ void Npc::onPlayerSellAllLoot(uint32_t playerId, uint16_t itemId, bool ignore, u } bool hasMore = false; uint64_t toSellCount = 0; - phmap::flat_hash_map toSell; + std::unordered_map toSell; for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { if (toSellCount >= 500) { hasMore = true; diff --git a/src/game/game.hpp b/src/game/game.hpp index b1afd810100..7f5911751da 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -509,10 +509,10 @@ class Game { const std::map> &getItemsPrice() const { return itemsPriceMap; } - const phmap::parallel_flat_hash_map> &getGuilds() const { + const std::unordered_map> &getGuilds() const { return guilds; } - const phmap::parallel_flat_hash_map> &getPlayers() const { + const std::unordered_map> &getPlayers() const { return players; } const std::map> &getMonsters() const { @@ -540,7 +540,7 @@ class Game { void addGuild(const std::shared_ptr &guild); void removeGuild(uint32_t guildId); - phmap::flat_hash_map, std::weak_ptr> browseFields; + std::unordered_map, std::weak_ptr> browseFields; void internalRemoveItems(const std::vector> &itemVector, uint32_t amount, bool stackable); @@ -814,15 +814,15 @@ class Game { */ ReturnValue collectRewardChestItems(const std::shared_ptr &player, uint32_t maxMoveItems = 0); - phmap::flat_hash_map queryCache; - phmap::flat_hash_map highscoreCache; + std::unordered_map queryCache; + std::unordered_map highscoreCache; - phmap::flat_hash_map> m_uniqueLoginPlayerNames; - phmap::parallel_flat_hash_map> players; - phmap::flat_hash_map> mappedPlayerNames; - phmap::parallel_flat_hash_map> guilds; - phmap::flat_hash_map> uniqueItems; - phmap::parallel_flat_hash_map m_playerNameCache; + std::unordered_map> m_uniqueLoginPlayerNames; + std::unordered_map> players; + std::unordered_map> mappedPlayerNames; + std::unordered_map> guilds; + std::unordered_map> uniqueItems; + std::unordered_map m_playerNameCache; /* Items stored from the lua scripts positions * For example: ActionFunctions::luaActionPosition From 7f21324393f247dff5809da943c12c8c63f25278 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 6 Nov 2024 23:33:22 -0300 Subject: [PATCH 13/20] feat: batch save for players and guilds --- src/game/scheduling/save_manager.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index d9434111f0a..4e757ff5c22 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -35,13 +35,17 @@ void SaveManager::saveAll() { logger.info("Saving server..."); Benchmark bm_players; - const auto &players = game.getPlayers(); const auto async = g_configManager().getBoolean(TOGGLE_SAVE_ASYNC); + + const auto &players = std::vector>>(game.getPlayers().begin(), game.getPlayers().end()); logger.info("Saving {} players... (Async: {})", players.size(), async ? "Enabled" : "Disabled"); - for (const auto &[_, player] : players) { - player->loginPosition = player->getPosition(); - doSavePlayer(player); - } + + threadPool.submit_loop(static_cast(0), static_cast(players.size()), [this, &players](const int i) { + const auto &player = players[i].second; + player->loginPosition = player->getPosition(); + doSavePlayer(player); + }) + .wait(); double duration_players = bm_players.duration(); if (duration_players > 1000.0) { @@ -51,10 +55,12 @@ void SaveManager::saveAll() { } Benchmark bm_guilds; - const auto &guilds = game.getGuilds(); - for (const auto &[_, guild] : guilds) { - saveGuild(guild); - } + const auto &guilds = std::vector>>(game.getGuilds().begin(), game.getGuilds().end()); + threadPool.submit_loop(static_cast(0), static_cast(guilds.size()), [this, &guilds](const int i) { + saveGuild(guilds[i].second); + }) + .wait(); + double duration_guilds = bm_guilds.duration(); if (duration_guilds > 1000.0) { logger.info("Guilds saved in {:.2f} seconds.", duration_guilds / 1000.0); From de2f63f1080ae7fb39479cc4dd9d95f04e36fa2c Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 6 Nov 2024 23:53:58 -0300 Subject: [PATCH 14/20] fix: from phmap to std::unordered_map --- src/kv/kv.cpp | 4 ---- src/kv/kv.hpp | 18 +++--------------- src/kv/kv_definitions.hpp | 3 +-- src/kv/value_wrapper.cpp | 2 +- src/kv/value_wrapper.hpp | 2 +- src/kv/value_wrapper_proto.hpp | 9 +-------- src/lua/functions/core/libs/kv_functions.hpp | 5 +---- 7 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/kv/kv.cpp b/src/kv/kv.cpp index 95b19ca8590..6928497cc60 100644 --- a/src/kv/kv.cpp +++ b/src/kv/kv.cpp @@ -14,7 +14,6 @@ int64_t KV::lastTimestamp_ = 0; uint64_t KV::counter_ = 0; -std::mutex KV::mutex_ = {}; KVStore &KVStore::getInstance() { return inject(); @@ -31,7 +30,6 @@ void KVStore::set(const std::string &key, const std::initializer_list KVStore::get(const std::string &key, bool forceLoad /*= false */) { logger.trace("KVStore::get({})", key); - std::scoped_lock lock(mutex_); if (forceLoad || !store_.contains(key)) { auto value = load(key); if (value) { @@ -77,7 +74,6 @@ std::optional KVStore::get(const std::string &key, bool forceLoad } std::unordered_set KVStore::keys(const std::string &prefix /*= ""*/) { - std::scoped_lock lock(mutex_); std::unordered_set keys; for (const auto &[key, value] : store_) { if (key.find(prefix) == 0) { diff --git a/src/kv/kv.hpp b/src/kv/kv.hpp index eefc97d83c5..ad019f3dc85 100644 --- a/src/kv/kv.hpp +++ b/src/kv/kv.hpp @@ -11,9 +11,7 @@ #ifndef USE_PRECOMPILED_HEADERS #include - #include #include - #include #include #include #include @@ -47,8 +45,6 @@ class KV : public std::enable_shared_from_this { } static std::string generateUUID() { - std::lock_guard lock(mutex_); - const auto now = std::chrono::system_clock::now().time_since_epoch(); const auto milliseconds = std::chrono::duration_cast(now).count(); @@ -69,7 +65,6 @@ class KV : public std::enable_shared_from_this { private: static int64_t lastTimestamp_; static uint64_t counter_; - static std::mutex mutex_; }; class KVStore : public KV { @@ -87,7 +82,6 @@ class KVStore : public KV { std::optional get(const std::string &key, bool forceLoad = false) override; void flush() override { - std::scoped_lock lock(mutex_); KV::flush(); store_.clear(); } @@ -96,13 +90,8 @@ class KVStore : public KV { std::unordered_set keys(const std::string &prefix = "") override; protected: - phmap::parallel_flat_hash_map::iterator>> getStore() { - std::scoped_lock lock(mutex_); - phmap::parallel_flat_hash_map::iterator>> copy; - for (const auto &[key, value] : store_) { - copy.try_emplace(key, value); - } - return copy; + const std::unordered_map::iterator>> &getStore() { + return store_; } protected: @@ -115,9 +104,8 @@ class KVStore : public KV { private: void setLocked(const std::string &key, const ValueWrapper &value); - phmap::parallel_flat_hash_map::iterator>> store_; + std::unordered_map::iterator>> store_; std::list lruQueue_; - std::mutex mutex_; }; class ScopedKV final : public KV { diff --git a/src/kv/kv_definitions.hpp b/src/kv/kv_definitions.hpp index e53c8b0f24a..915e42c7275 100644 --- a/src/kv/kv_definitions.hpp +++ b/src/kv/kv_definitions.hpp @@ -16,7 +16,6 @@ class ValueWrapper; #include #include #include - #include #endif using StringType = std::string; @@ -24,6 +23,6 @@ using BooleanType = bool; using IntType = int; using DoubleType = double; using ArrayType = std::vector; -using MapType = phmap::flat_hash_map>; +using MapType = std::unordered_map>; using ValueVariant = std::variant; diff --git a/src/kv/value_wrapper.cpp b/src/kv/value_wrapper.cpp index 69fb942bab4..444a0835cd9 100644 --- a/src/kv/value_wrapper.cpp +++ b/src/kv/value_wrapper.cpp @@ -29,7 +29,7 @@ ValueWrapper::ValueWrapper(int value, uint64_t timestamp) : ValueWrapper::ValueWrapper(double value, uint64_t timestamp) : data_(value), timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } -ValueWrapper::ValueWrapper(const phmap::flat_hash_map &value, uint64_t timestamp) : +ValueWrapper::ValueWrapper(const std::unordered_map &value, uint64_t timestamp) : data_(createMapFromRange(value.begin(), value.end(), timestamp)), timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } diff --git a/src/kv/value_wrapper.hpp b/src/kv/value_wrapper.hpp index 4bb844dcb97..05d0591ebd2 100644 --- a/src/kv/value_wrapper.hpp +++ b/src/kv/value_wrapper.hpp @@ -29,7 +29,7 @@ class ValueWrapper { explicit(false) ValueWrapper(bool value, uint64_t timestamp = 0); explicit(false) ValueWrapper(int value, uint64_t timestamp = 0); explicit(false) ValueWrapper(double value, uint64_t timestamp = 0); - explicit(false) ValueWrapper(const phmap::flat_hash_map &value, uint64_t timestamp = 0); + explicit(false) ValueWrapper(const std::unordered_map &value, uint64_t timestamp = 0); explicit(false) ValueWrapper(const std::initializer_list> &init_list, uint64_t timestamp = 0); static ValueWrapper deleted() { diff --git a/src/kv/value_wrapper_proto.hpp b/src/kv/value_wrapper_proto.hpp index 9d5a61865ab..f64b1ed5744 100644 --- a/src/kv/value_wrapper_proto.hpp +++ b/src/kv/value_wrapper_proto.hpp @@ -9,14 +9,7 @@ #pragma once -class ValueWrapper; - -using StringType = std::string; -using BooleanType = bool; -using IntType = int; -using DoubleType = double; -using ArrayType = std::vector; -using MapType = phmap::flat_hash_map>; +#include "kv/kv_definitions.hpp" using ValueVariant = std::variant; diff --git a/src/lua/functions/core/libs/kv_functions.hpp b/src/lua/functions/core/libs/kv_functions.hpp index 4ef47333f2f..e50b4c94893 100644 --- a/src/lua/functions/core/libs/kv_functions.hpp +++ b/src/lua/functions/core/libs/kv_functions.hpp @@ -10,8 +10,7 @@ #pragma once #include "lua/scripts/luascript.hpp" - -class ValueWrapper; +#include "kv/kv_definitions.hpp" #ifndef USE_PRECOMPILED_HEADERS #include @@ -21,8 +20,6 @@ class ValueWrapper; #include #endif -using MapType = phmap::flat_hash_map>; - struct lua_State; class KVFunctions final : LuaScriptInterface { From 352d80dd25e7f8487349172bbb8d67f1b3835a22 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 8 Nov 2024 21:39:49 -0300 Subject: [PATCH 15/20] fix: storage range and crash with async save --- .../players/components/player_storage.cpp | 3 ++- src/game/scheduling/save_manager.cpp | 26 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/creatures/players/components/player_storage.cpp b/src/creatures/players/components/player_storage.cpp index d22d2e75138..ec78e81d995 100644 --- a/src/creatures/players/components/player_storage.cpp +++ b/src/creatures/players/components/player_storage.cpp @@ -126,7 +126,8 @@ bool PlayerStorage::save() { return true; }; - auto insertModifiedStorageKeys = [playerGUID, playerName, modifiedKeys, storageMap]() mutable { + auto insertModifiedStorageKeys = [this, playerGUID, playerName, modifiedKeys, storageMap]() mutable { + getReservedRange(); if (!modifiedKeys.empty()) { DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); storageQuery.upsert({ "value" }); diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index 4e757ff5c22..ab8abbbfae8 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -16,7 +16,7 @@ #include "io/iologindata.hpp" #include "kv/kv.hpp" #include "lib/di/container.hpp" -#include "game/scheduling/task.hpp" +#include "game/scheduling/dispatcher.hpp" SaveManager::SaveManager(ThreadPool &threadPool, KVStore &kvStore, Logger &logger, Game &game) : threadPool(threadPool), kv(kvStore), logger(logger), game(game) { @@ -34,18 +34,19 @@ void SaveManager::saveAll() { Benchmark bm_saveAll; logger.info("Saving server..."); - Benchmark bm_players; const auto async = g_configManager().getBoolean(TOGGLE_SAVE_ASYNC); + Benchmark bm_players; + const auto &players = std::vector>>(game.getPlayers().begin(), game.getPlayers().end()); logger.info("Saving {} players... (Async: {})", players.size(), async ? "Enabled" : "Disabled"); - threadPool.submit_loop(static_cast(0), static_cast(players.size()), [this, &players](const int i) { - const auto &player = players[i].second; - player->loginPosition = player->getPosition(); - doSavePlayer(player); - }) - .wait(); + g_dispatcher().asyncWait(players.size(), [this, &players](size_t i) { + if(const auto& player = players[i].second) { + player->loginPosition = player->getPosition(); + doSavePlayer(player); + } + }); double duration_players = bm_players.duration(); if (duration_players > 1000.0) { @@ -56,10 +57,11 @@ void SaveManager::saveAll() { Benchmark bm_guilds; const auto &guilds = std::vector>>(game.getGuilds().begin(), game.getGuilds().end()); - threadPool.submit_loop(static_cast(0), static_cast(guilds.size()), [this, &guilds](const int i) { - saveGuild(guilds[i].second); - }) - .wait(); + g_dispatcher().asyncWait(guilds.size(), [this, &guilds](size_t i) { + if (const auto& guild = guilds[i].second) { + saveGuild(guild); + } + }); double duration_guilds = bm_guilds.duration(); if (duration_guilds > 1000.0) { From fce5e95096346ba2b4732191a02008a9f57f6607 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 9 Nov 2024 00:48:04 +0000 Subject: [PATCH 16/20] Code format - (Clang-format) --- src/game/scheduling/save_manager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index ab8abbbfae8..da08fff37c1 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -42,7 +42,7 @@ void SaveManager::saveAll() { logger.info("Saving {} players... (Async: {})", players.size(), async ? "Enabled" : "Disabled"); g_dispatcher().asyncWait(players.size(), [this, &players](size_t i) { - if(const auto& player = players[i].second) { + if (const auto &player = players[i].second) { player->loginPosition = player->getPosition(); doSavePlayer(player); } @@ -58,7 +58,7 @@ void SaveManager::saveAll() { Benchmark bm_guilds; const auto &guilds = std::vector>>(game.getGuilds().begin(), game.getGuilds().end()); g_dispatcher().asyncWait(guilds.size(), [this, &guilds](size_t i) { - if (const auto& guild = guilds[i].second) { + if (const auto &guild = guilds[i].second) { saveGuild(guild); } }); From 6f5310a42783d4252270571d1f3ea520f4d67a44 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sun, 24 Nov 2024 00:39:58 -0300 Subject: [PATCH 17/20] feat: save async fixes --- data-otservbr-global/migrations/47.lua | 14 +- data-otservbr-global/migrations/48.lua | 3 + schema.sql | 3 +- .../components/player_forge_history.cpp | 80 ++--- .../players/components/player_stash.cpp | 70 ++-- .../players/components/player_storage.cpp | 72 ++-- src/game/scheduling/save_manager.cpp | 97 ++---- src/game/scheduling/save_manager.hpp | 18 - src/io/functions/iologindata_save_player.cpp | 317 +++++++----------- 9 files changed, 269 insertions(+), 405 deletions(-) create mode 100644 data-otservbr-global/migrations/48.lua diff --git a/data-otservbr-global/migrations/47.lua b/data-otservbr-global/migrations/47.lua index 86a6d8ffec1..d7d8ad9e48c 100644 --- a/data-otservbr-global/migrations/47.lua +++ b/data-otservbr-global/migrations/47.lua @@ -1,3 +1,15 @@ function onUpdateDatabase() - return false -- true = There are others migrations file | false = this is the last migration file + logger.info("Updating database to version 48 (player bosstiary and player kills unique key)") + + db.query([[ + ALTER TABLE `player_bosstiary` + ADD PRIMARY KEY (`player_id`) + ]]) + + db.query([[ + ALTER TABLE `player_kills` + ADD PRIMARY KEY (`player_id`, `target`, `time`); + ]]) + + return true end diff --git a/data-otservbr-global/migrations/48.lua b/data-otservbr-global/migrations/48.lua new file mode 100644 index 00000000000..86a6d8ffec1 --- /dev/null +++ b/data-otservbr-global/migrations/48.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false -- true = There are others migrations file | false = this is the last migration file +end diff --git a/schema.sql b/schema.sql index 43180363baf..a06e088d60f 100644 --- a/schema.sql +++ b/schema.sql @@ -675,7 +675,8 @@ CREATE TABLE IF NOT EXISTS `player_kills` ( `player_id` int(11) NOT NULL, `time` bigint(20) UNSIGNED NOT NULL DEFAULT '0', `target` int(11) NOT NULL, - `unavenged` tinyint(1) NOT NULL DEFAULT '0' + `unavenged` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`player_id`, `target`, `time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Table structure `player_namelocks` diff --git a/src/creatures/players/components/player_forge_history.cpp b/src/creatures/players/components/player_forge_history.cpp index 4cd990e8e27..c857d64fa55 100644 --- a/src/creatures/players/components/player_forge_history.cpp +++ b/src/creatures/players/components/player_forge_history.cpp @@ -62,60 +62,50 @@ bool PlayerForgeHistory::save() { } auto playerGUID = m_player.getGUID(); - auto removedHistoryIds = m_removedHistoryIds; - auto modifiedHistory = m_modifiedHistory; - - auto deleteForgeHistoryEntries = [playerGUID, removedHistoryIds]() mutable { - if (!removedHistoryIds.empty()) { - std::string idsToDelete = fmt::format("{}", fmt::join(removedHistoryIds, ", ")); - std::string deleteQuery = fmt::format( - "DELETE FROM `forge_history` WHERE `player_id` = {} AND `id` IN ({})", - playerGUID, idsToDelete - ); - - if (!g_database().executeQuery(deleteQuery)) { - g_logger().error("[{}] failed to delete forge history entries for player with ID: {}", std::source_location::current().function_name(), playerGUID); - return false; - } - - removedHistoryIds.clear(); - } - return true; - }; - - auto insertModifiedHistory = [playerGUID, modifiedHistory]() mutable { - DBInsert insertQuery("INSERT INTO `forge_history` (`id`, `player_id`, `action_type`, `description`, `done_at`, `is_success`) VALUES "); - insertQuery.upsert({ "action_type", "description", "done_at", "is_success" }); - - for (const auto &history : modifiedHistory) { - auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, g_database().escapeString(history.description), history.createdAt, history.success ? 1 : 0); - if (!insertQuery.addRow(row)) { - g_logger().warn("[{}] failed to add forge history entry for player with ID: {}", std::source_location::current().function_name(), playerGUID); - return false; - } - g_logger().debug("Added forge history entry date: {}, for player with ID: {}", formatDate(history.createdAt / 1000), playerGUID); - } + Database& db = Database::getInstance(); + + if (!m_removedHistoryIds.empty()) { + std::string idsToDelete = fmt::format("{}", fmt::join(m_removedHistoryIds, ", ")); + std::string deleteQuery = fmt::format( + "DELETE FROM `forge_history` WHERE `player_id` = {} AND `id` IN ({})", + playerGUID, idsToDelete + ); - if (!insertQuery.execute()) { - g_logger().error("[{}] failed to execute insertion for forge history entries for player with ID: {}", std::source_location::current().function_name(), playerGUID); + if (!db.executeQuery(deleteQuery)) { + g_logger().error("Failed to delete forge history entries for player with ID: {}", playerGUID); return false; } - return true; - }; + m_removedHistoryIds.clear(); + } - auto forgeHistorySaveTask = [deleteForgeHistoryEntries, insertModifiedHistory]() mutable { - if (!deleteForgeHistoryEntries()) { - return; - } + DBInsert insertQuery("INSERT INTO `forge_history` (`id`, `player_id`, `action_type`, `description`, `done_at`, `is_success`) VALUES "); + insertQuery.upsert({ "action_type", "description", "done_at", "is_success" }); + + for (const auto& history : m_modifiedHistory) { + auto row = fmt::format("{}, {}, {}, {}, {}, {}", + history.id, + playerGUID, + history.actionType, + db.escapeString(history.description), + history.createdAt, + history.success ? 1 : 0); - if (!insertModifiedHistory()) { - return; + if (!insertQuery.addRow(row)) { + g_logger().warn("Failed to add forge history entry for player with ID: {}", playerGUID); + return false; } - }; - g_saveManager().addTask(forgeHistorySaveTask, "PlayerForgeHistory::save - forgeHistorySaveTask"); + g_logger().debug("Added forge history entry date: {}, for player with ID: {}", formatDate(history.createdAt / 1000), playerGUID); + } + + if (!insertQuery.execute()) { + g_logger().error("Failed to execute insertion for forge history entries for player with ID: {}", playerGUID); + return false; + } + m_modifiedHistory.clear(); + return true; } diff --git a/src/creatures/players/components/player_stash.cpp b/src/creatures/players/components/player_stash.cpp index b24a24af4ba..1b55b6779a9 100644 --- a/src/creatures/players/components/player_stash.cpp +++ b/src/creatures/players/components/player_stash.cpp @@ -77,59 +77,37 @@ bool PlayerStash::save() { } const auto playerGUID = m_player.getGUID(); - auto removedItems = m_removedItems; - auto modifiedItems = m_modifiedItems; - - auto deleteStashItems = [playerGUID, removedItems = std::move(removedItems)]() mutable { - if (!removedItems.empty()) { - std::string removedItemIds = fmt::format("{}", fmt::join(removedItems, ", ")); - std::string deleteQuery = fmt::format( - "DELETE FROM `player_stash` WHERE `player_id` = {} AND `item_id` IN ({})", - playerGUID, removedItemIds - ); - - if (!g_database().executeQuery(deleteQuery)) { - g_logger().error("[{}] failed to delete removed items for player: {}", std::source_location::current().function_name(), playerGUID); - return false; - } - - removedItems.clear(); - } - - return true; - }; - - auto insertModifiedStashItems = [playerGUID, modifiedItems = std::move(modifiedItems)]() mutable { - DBInsert insertQuery("INSERT INTO `player_stash` (`player_id`, `item_id`, `item_count`) VALUES "); - insertQuery.upsert({ "item_count" }); - - for (const auto &[itemId, itemCount] : modifiedItems) { - auto row = fmt::format("{}, {}, {}", playerGUID, itemId, itemCount); - if (!insertQuery.addRow(row)) { - g_logger().warn("[{}] - Failed to add row for stash item: {}", std::source_location::current().function_name(), itemId); - return false; - } - } - - if (!insertQuery.execute()) { - g_logger().error("[{}] - Failed to execute insertion for modified stash items for player: {}", std::source_location::current().function_name(), playerGUID); + if (!m_removedItems.empty()) { + std::string removedItemIds = fmt::format("{}", fmt::join(m_removedItems, ", ")); + std::string deleteQuery = fmt::format( + "DELETE FROM `player_stash` WHERE `player_id` = {} AND `item_id` IN ({})", + playerGUID, removedItemIds + ); + + if (!g_database().executeQuery(deleteQuery)) { + g_logger().error("[PlayerStash::save] - Failed to delete removed items for player: {}", m_player.getName()); return false; } - return true; - }; + m_removedItems.clear(); + } - auto stashSaveTask = [deleteStashItems, insertModifiedStashItems]() mutable { - if (!deleteStashItems()) { - return; - } + DBInsert insertQuery("INSERT INTO `player_stash` (`player_id`, `item_id`, `item_count`) VALUES "); + insertQuery.upsert({ "item_count" }); - if (!insertModifiedStashItems()) { - return; + for (const auto& [itemId, itemCount] : m_modifiedItems) { + auto row = fmt::format("{}, {}, {}", playerGUID, itemId, itemCount); + if (!insertQuery.addRow(row)) { + g_logger().warn("[PlayerStash::save] - Failed to add row for stash item: {}", itemId); + return false; } - }; + } + + if (!insertQuery.execute()) { + g_logger().error("[PlayerStash::save] - Failed to execute insertion for modified stash items for player: {}", m_player.getName()); + return false; + } - g_saveManager().addTask(stashSaveTask, "PlayerStash::save - stashSaveTask"); m_modifiedItems.clear(); return true; diff --git a/src/creatures/players/components/player_storage.cpp b/src/creatures/players/components/player_storage.cpp index ec78e81d995..addac66208e 100644 --- a/src/creatures/players/components/player_storage.cpp +++ b/src/creatures/players/components/player_storage.cpp @@ -104,61 +104,43 @@ bool PlayerStorage::save() { return true; } - auto playerGUID = m_player.getGUID(); - auto playerName = m_player.getName(); - auto removedKeys = m_removedKeys; - auto modifiedKeys = m_modifiedKeys; - auto storageMap = m_storageMap; - - auto deleteStorageKeys = [playerGUID, playerName, removedKeys]() mutable { - if (!removedKeys.empty()) { - std::string keysList = fmt::format("{}", fmt::join(removedKeys, ", ")); - std::string deleteQuery = fmt::format( - "DELETE FROM `player_storage` WHERE `player_id` = {} AND `key` IN ({})", - playerGUID, keysList - ); - - if (!g_database().executeQuery(deleteQuery)) { - g_logger().error("[{}] failed to delete storage keys for player: {}", std::source_location::current().function_name(), playerName); - return false; - } + if (!m_removedKeys.empty()) { + std::string keysList = fmt::format("{}", fmt::join(m_removedKeys, ", ")); + std::string deleteQuery = fmt::format( + "DELETE FROM `player_storage` WHERE `player_id` = {} AND `key` IN ({})", + m_player.getGUID(), keysList + ); + + if (!g_database().executeQuery(deleteQuery)) { + g_logger().error("[PlayerStorage::save] - Falha ao deletar as chaves de storage para o jogador: {}", m_player.getName()); + return false; } - return true; - }; - auto insertModifiedStorageKeys = [this, playerGUID, playerName, modifiedKeys, storageMap]() mutable { - getReservedRange(); - if (!modifiedKeys.empty()) { - DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); - storageQuery.upsert({ "value" }); - - for (const auto &key : modifiedKeys) { - auto row = fmt::format("{}, {}, {}", playerGUID, key, storageMap.at(key)); - if (!storageQuery.addRow(row)) { - g_logger().warn("[{}] failed to add row for player storage: {}", std::source_location::current().function_name(), playerName); - return false; - } - } + m_removedKeys.clear(); + } - if (!storageQuery.execute()) { - g_logger().error("[{}] failed to execute storage insertion for player: {}", std::source_location::current().function_name(), playerName); + if (!m_modifiedKeys.empty()) { + getReservedRange(); + DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); + storageQuery.upsert({ "value" }); + + auto playerGUID = m_player.getGUID(); + for (const auto &key : m_modifiedKeys) { + auto row = fmt::format("{}, {}, {}", playerGUID, key, m_storageMap.at(key)); + if (!storageQuery.addRow(row)) { + g_logger().warn("[PlayerStorage::save] - Failed to add row for player storage: {}", m_player.getName()); return false; } } - return true; - }; - auto saveTask = [deleteStorageKeys, insertModifiedStorageKeys]() mutable { - if (!deleteStorageKeys()) { - return; + if (!storageQuery.execute()) { + g_logger().error("[PlayerStorage::save] - Failed to execute storage insertion for player: {}", m_player.getName()); + return false; } - if (!insertModifiedStorageKeys()) { - return; - } - }; + m_modifiedKeys.clear(); + } - g_saveManager().addTask(saveTask, "PlayerStorage::save - playerStorageSaveTask"); return true; } diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index da08fff37c1..69eaa745cef 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -16,37 +16,52 @@ #include "io/iologindata.hpp" #include "kv/kv.hpp" #include "lib/di/container.hpp" -#include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/task.hpp" SaveManager::SaveManager(ThreadPool &threadPool, KVStore &kvStore, Logger &logger, Game &game) : threadPool(threadPool), kv(kvStore), logger(logger), game(game) { - m_threads.reserve(threadPool.get_thread_count() + 1); - for (uint_fast16_t i = 0; i < m_threads.capacity(); ++i) { - m_threads.emplace_back(std::make_unique()); - } } SaveManager &SaveManager::getInstance() { return inject(); } +const auto ASYNC_SAVE = g_configManager().getBoolean(TOGGLE_SAVE_ASYNC); + void SaveManager::saveAll() { Benchmark bm_saveAll; logger.info("Saving server..."); - - const auto async = g_configManager().getBoolean(TOGGLE_SAVE_ASYNC); - Benchmark bm_players; - - const auto &players = std::vector>>(game.getPlayers().begin(), game.getPlayers().end()); - logger.info("Saving {} players... (Async: {})", players.size(), async ? "Enabled" : "Disabled"); - - g_dispatcher().asyncWait(players.size(), [this, &players](size_t i) { - if (const auto &player = players[i].second) { - player->loginPosition = player->getPosition(); - doSavePlayer(player); + const auto &players = game.getPlayers(); + std::vector> futures; + logger.info("Saving {} players... (Async: {})", players.size(), ASYNC_SAVE ? "Enabled" : "Disabled"); + for (const auto &[_, player] : players) { + player->loginPosition = player->getPosition(); + + auto promise = std::make_shared>(); + futures.push_back(promise->get_future()); + + threadPool.detach_task([this, player, promise]() { + try { + doSavePlayer(player); + promise->set_value(); + } catch (const std::exception &e) { + logger.error("Exception occurred while saving player {}: {}", player->getName(), e.what()); + promise->set_exception(std::current_exception()); + } catch (...) { + logger.error("Unknown error occurred while saving player {}.", player->getName()); + promise->set_exception(std::current_exception()); + } + }); + } + + for (auto &future : futures) { + try { + future.get(); + } catch (const std::exception &e) { + logger.error("Failed to save player due to exception: {}", e.what()); } - }); + } double duration_players = bm_players.duration(); if (duration_players > 1000.0) { @@ -56,13 +71,10 @@ void SaveManager::saveAll() { } Benchmark bm_guilds; - const auto &guilds = std::vector>>(game.getGuilds().begin(), game.getGuilds().end()); - g_dispatcher().asyncWait(guilds.size(), [this, &guilds](size_t i) { - if (const auto &guild = guilds[i].second) { - saveGuild(guild); - } - }); - + const auto &guilds = game.getGuilds(); + for (const auto &[_, guild] : guilds) { + saveGuild(guild); + } double duration_guilds = bm_guilds.duration(); if (duration_guilds > 1000.0) { logger.info("Guilds saved in {:.2f} seconds.", duration_guilds / 1000.0); @@ -110,8 +122,6 @@ bool SaveManager::doSavePlayer(std::shared_ptr player) { bool saveSuccess = IOLoginData::savePlayer(player); if (!saveSuccess) { logger.error("Failed to save player {}.", player->getName()); - } else { - executeTasks(); } auto duration = bm_savePlayer.duration(); @@ -175,38 +185,3 @@ void SaveManager::saveKV() { logger.info("KV store saved in {} milliseconds.", duration_kv); } } - -void SaveManager::addTask(std::function &&f, std::string_view context, uint32_t expiresAfterMs /* = 0*/) { - const auto &thread = getThreadTask(); - std::scoped_lock lock(thread->mutex); - thread->tasks.emplace_back(expiresAfterMs, std::move(f), context); -} - -SaveManager::ThreadTask::ThreadTask() { - tasks.reserve(2000); -} - -void SaveManager::executeTasks() { - for (const auto &thread : m_threads) { - std::scoped_lock lock(thread->mutex); - auto &threadTasks = thread->tasks; - if (!threadTasks.empty()) { - m_tasks.insert(m_tasks.end(), make_move_iterator(thread->tasks.begin()), make_move_iterator(thread->tasks.end())); - threadTasks.clear(); - } - } - - auto executeTasks = [tasks = std::move(m_tasks)]() { - for (const auto &task : tasks) { - task.execute(); - } - }; - - if (g_configManager().getBoolean(TOGGLE_SAVE_ASYNC)) { - threadPool.detach_task(executeTasks); - } else { - executeTasks(); - } - - m_tasks.clear(); -} diff --git a/src/game/scheduling/save_manager.hpp b/src/game/scheduling/save_manager.hpp index 13c4fb33a4d..f7a219fd8cf 100644 --- a/src/game/scheduling/save_manager.hpp +++ b/src/game/scheduling/save_manager.hpp @@ -33,25 +33,7 @@ class SaveManager { bool savePlayer(std::shared_ptr player); void saveGuild(std::shared_ptr guild); - void addTask(std::function &&f, std::string_view context, uint32_t expiresAfterMs = 0); - private: - struct ThreadTask { - ThreadTask(); - - std::vector tasks; - std::mutex mutex; - }; - - void executeTasks(); - - const auto &getThreadTask() const { - return m_threads[ThreadPool::getThreadId()]; - } - - std::vector> m_threads; - std::vector m_tasks; - void saveMap(); void saveKV(); diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index 72b4741928c..61333767928 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -1,6 +1,6 @@ /** * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2024 OpenTibiaBR + * Copyright (©) 2019-2022 OpenTibiaBR * Repository: https://github.com/opentibiabr/canary * License: https://github.com/opentibiabr/canary/blob/main/LICENSE * Contributors: https://github.com/opentibiabr/canary/graphs/contributors @@ -17,32 +17,31 @@ #include "items/containers/depot/depotchest.hpp" #include "items/containers/inbox/inbox.hpp" #include "items/containers/rewards/reward.hpp" -#include "game/scheduling/save_manager.hpp" -bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, DBInsert &query_insert, PropWriteStream &propWriteStream) { +bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const ItemBlockList &items, DBInsert &query_insert, PropWriteStream &propWriteStream) { if (!player) { g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); return false; } - // Initialize variables - using ContainerBlock = std::pair, int32_t>; - std::list queue; int32_t runningId = 100; - // Loop through each item in itemList + using ContainerBlock = std::pair, int32_t>; + std::deque queue; + const auto &openContainers = player->getOpenContainers(); - for (const auto &it : itemList) { + const bool hasOpenContainers = !openContainers.empty(); + const uint32_t playerGUID = player->getGUID(); + + for (const auto &it : items) { const auto &item = it.second; if (!item) { continue; } int32_t pid = it.first; - ++runningId; - // Update container attributes if necessary if (const std::shared_ptr &container = item->getContainer()) { if (!container) { continue; @@ -52,38 +51,36 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite container->setAttribute(ItemAttribute_t::OPENCONTAINER, 0); } - if (!openContainers.empty()) { + if (hasOpenContainers) { for (const auto &its : openContainers) { - auto openContainer = its.second; - auto opcontainer = openContainer.container; + const auto &openContainer = its.second; + const auto &opcontainer = openContainer.container; if (opcontainer == container) { - container->setAttribute(ItemAttribute_t::OPENCONTAINER, ((int)its.first) + 1); + container->setAttribute(ItemAttribute_t::OPENCONTAINER, static_cast(its.first) + 1); break; } } } - // Add container to queue queue.emplace_back(container, runningId); } - // Serialize item attributes propWriteStream.clear(); item->serializeAttr(propWriteStream); size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); + auto escapedAttributes = g_database().escapeBlob(attributes, static_cast(attributesSize)); + + auto row = fmt::format("{},{},{},{},{},{}", playerGUID, pid, runningId, item->getID(), item->getSubType(), escapedAttributes); - // Build query string and add row - auto row = fmt::format("{},{},{},{},{},{}", player->getGUID(), pid, runningId, item->getID(), item->getSubType(), g_database().escapeBlob(attributes, static_cast(attributesSize))); if (!query_insert.addRow(row)) { g_logger().error("Error adding row to query."); return false; } } - // Loop through containers in queue while (!queue.empty()) { const ContainerBlock &cb = queue.front(); const std::shared_ptr &container = cb.first; @@ -94,45 +91,40 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite int32_t parentId = cb.second; - // Loop through items in container - for (auto &item : container->getItemList()) { + for (const auto &item : container->getItemList()) { if (!item) { continue; } ++runningId; - // Update sub-container attributes if necessary - const auto &subContainer = item->getContainer(); - if (subContainer) { + if (const auto &subContainer = item->getContainer()) { queue.emplace_back(subContainer, runningId); if (subContainer->getAttribute(ItemAttribute_t::OPENCONTAINER) > 0) { subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, 0); } - if (!openContainers.empty()) { + if (hasOpenContainers) { for (const auto &it : openContainers) { - auto openContainer = it.second; - auto opcontainer = openContainer.container; + const auto &openContainer = it.second; + const auto &opcontainer = openContainer.container; if (opcontainer == subContainer) { - subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, (it.first) + 1); + subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, it.first + 1); break; } } } } - // Serialize item attributes propWriteStream.clear(); item->serializeAttr(propWriteStream); - item->stopDecaying(); size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); + auto escapedAttributes = g_database().escapeBlob(attributes, static_cast(attributesSize)); - // Build query string and add row - auto row = fmt::format("{},{},{},{},{},{}", player->getGUID(), parentId, runningId, item->getID(), item->getSubType(), g_database().escapeBlob(attributes, static_cast(attributesSize))); + auto row = fmt::format("{},{},{},{},{},{}", playerGUID, parentId, runningId, item->getID(), item->getSubType(), escapedAttributes); if (!query_insert.addRow(row)) { g_logger().error("Error adding row to query for container item."); return false; @@ -143,6 +135,11 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite queue.pop_front(); } + if (!query_insert.execute()) { + g_logger().error("Error executing query."); + return false; + } + return true; } @@ -165,18 +162,10 @@ bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { // Quick update if `save` flag is 0 if (result->getNumber("save") == 0) { - auto quickUpdateTask = [queryStr = fmt::format( - "UPDATE `players` SET `lastlogin` = {}, `lastip` = {} WHERE `id` = {}", - player->lastLoginSaved, player->lastIP, player->getGUID() - )]() { - if (!g_database().executeQuery(queryStr)) { - g_logger().warn("[SaveManager::quickUpdateTask] - Failed to execute quick update for player."); - return; - } - }; - - g_saveManager().addTask(quickUpdateTask, "IOLoginDataSave::savePlayerFirst - quickUpdateTask"); - return true; + return g_database().executeQuery(fmt::format( + "UPDATE `players` SET `lastlogin` = {}, `lastip` = {} WHERE `id` = {}", + player->lastLoginSaved, player->lastIP, player->getGUID() + )); } // Build the list of column-value pairs @@ -301,19 +290,18 @@ bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { columns.push_back(fmt::format("`blessings{}` = {}", i, static_cast(player->getBlessingCount(static_cast(i))))); } - // Create the task for the query execution - auto savePlayerTask = [joinColums = std::move(columns), playerName = player->getName(), playerGUID = player->getGUID()]() { - // Now join the columns into a single string - std::string setClause = fmt::to_string(fmt::join(joinColums, ", ")); - // Construct the final query - std::string queryStr = fmt::format("UPDATE `players` SET {} WHERE `id` = {}", setClause, playerGUID); - if (!g_database().executeQuery(queryStr)) { - g_logger().warn("[SaveManager::savePlayerTask] - Error executing player first save for player: {}", playerName); - } - }; + // Now join the columns into a single string + std::string setClause = fmt::to_string(fmt::join(columns, ", ")); + + // Construct the final query + std::string queryStr = fmt::format("UPDATE `players` SET {} WHERE `id` = {}", setClause, player->getGUID()); + + // Execute the query + if (!g_database().executeQuery(queryStr)) { + g_logger().warn("[IOLoginDataSave::savePlayerFirst] - Error executing player first save for player: {}", player->getName()); + return false; + } - // Add the task to the SaveManager - g_saveManager().addTask(savePlayerTask, "IOLoginData::savePlayerFirst - savePlayerTask"); return true; } @@ -323,28 +311,25 @@ bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { return false; } - auto deleteQueryStr = fmt::format("DELETE FROM `player_spells` WHERE `player_id` = {}", player->getGUID()); + // Use UPSERT to avoid the DELETE operation DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name`) VALUES "); - for (const std::string &spellName : player->learnedInstantSpellList) { - std::string row = fmt::format("{},{}", player->getGUID(), g_database().escapeString(spellName)); + spellsQuery.upsert({ "name" }); + + // Populate spells list with player's learned spells + auto playerGUID = player->getGUID(); + for (const std::string& spellName : player->learnedInstantSpellList) { + auto row = fmt::format("{}, {}", playerGUID, g_database().escapeString(spellName)); if (!spellsQuery.addRow(row)) { - g_logger().warn("[IOLoginDataSave::savePlayerSpells] - Failed to add row for player spells"); + g_logger().warn("[IOLoginDataSave::savePlayerSpells] - Failed to add spell data for player: {}", player->getName()); return false; } } - auto spellsSaveTask = [deleteQueryStr, spellsTaskQuery = std::move(spellsQuery)]() mutable { - if (!g_database().executeQuery(deleteQueryStr)) { - g_logger().error("[SaveManager::spellsSaveTask] - Failed to execute delete query for player spells"); - return; - } - - if (!spellsTaskQuery.execute()) { - g_logger().warn("[SaveManager::spellsSaveTask] - Failed to execute insert query for player spells"); - } - }; + if (!spellsQuery.execute()) { + g_logger().warn("[IOLoginDataSave::savePlayerSpells] - Error executing spells data insertion for player: {}", player->getName()); + return false; + } - g_saveManager().addTask(spellsSaveTask, "IOLoginData::savePlayerSpells - spellsSaveTask"); return true; } @@ -354,28 +339,24 @@ bool IOLoginDataSave::savePlayerKills(const std::shared_ptr &player) { return false; } - auto deleteQueryStr = fmt::format("DELETE FROM `player_kills` WHERE `player_id` = {}", player->getGUID()); - DBInsert killsQuery("INSERT INTO `player_kills` (`player_id`, `target`, `time`, `unavenged`) VALUES"); - for (const auto &kill : player->unjustifiedKills) { - std::string row = fmt::format("{},{},{},{}", player->getGUID(), kill.target, kill.time, kill.unavenged); + DBInsert killsQuery("INSERT INTO `player_kills` (`player_id`, `target`, `time`, `unavenged`) VALUES "); + killsQuery.upsert({ "target", "time", "unavenged" }); + + // Add rows for each kill entry + auto playerGUID = player->getGUID(); + for (const auto& kill : player->unjustifiedKills) { + auto row = fmt::format("{}, {}, {}, {}", playerGUID, kill.target, kill.time, kill.unavenged); if (!killsQuery.addRow(row)) { - g_logger().warn("[IOLoginDataSave::savePlayerKills] - Failed to add row for player kills"); + g_logger().warn("[IOLoginDataSave::savePlayerKills] - Failed to add kill data for player: {}", player->getName()); return false; } } - auto killsSaveTask = [deleteQueryStr, killsTaskQuery = std::move(killsQuery)]() mutable { - if (!g_database().executeQuery(deleteQueryStr)) { - g_logger().warn("[SaveManager::killsSaveTask] - Failed to execute delete query for player kills"); - return; - } - - if (!killsTaskQuery.execute()) { - g_logger().warn("[SaveManager::killsSaveTask] - Failed to execute insert query for player kills"); - } - }; + if (!killsQuery.execute()) { + g_logger().warn("[IOLoginDataSave::savePlayerKills] - Error executing kills data insertion for player: {}", player->getName()); + return false; + } - g_saveManager().addTask(killsSaveTask, "IOLoginData::savePlayerKills - killsSaveTask"); return true; } @@ -385,6 +366,7 @@ bool IOLoginDataSave::savePlayerBestiarySystem(const std::shared_ptr &pl return false; } + // Serialize the bestiary tracker list PropWriteStream propBestiaryStream; for (const auto &trackedType : player->getCyclopediaMonsterTrackerSet(false)) { propBestiaryStream.write(trackedType->info.raceid); @@ -393,6 +375,7 @@ bool IOLoginDataSave::savePlayerBestiarySystem(const std::shared_ptr &pl const char* trackerList = propBestiaryStream.getStream(trackerSize); auto escapedTrackerList = g_database().escapeBlob(trackerList, static_cast(trackerSize)); + // Construct the query using fmt::format std::string updateQuery = fmt::format( "UPDATE `player_charms` SET `charm_points` = {}, `charm_expansion` = {}, " "`rune_wound` = {}, `rune_enflame` = {}, `rune_poison` = {}, `rune_freeze` = {}, " @@ -428,13 +411,11 @@ bool IOLoginDataSave::savePlayerBestiarySystem(const std::shared_ptr &pl player->getGUID() ); - auto bestiarySaveTask = [updateQuery]() { - if (!g_database().executeQuery(updateQuery)) { - g_logger().warn("[SaveManager::bestiarySaveTask] - Failed to execute bestiary data update query"); - } - }; + if (!g_database().executeQuery(updateQuery)) { + g_logger().warn("[IOLoginDataSave::savePlayerBestiarySystem] - Error executing bestiary data update for player: {}", player->getName()); + return false; + } - g_saveManager().addTask(bestiarySaveTask, "IOLoginData::savePlayerBestiarySystem - bestiarySaveTask"); return true; } @@ -444,36 +425,27 @@ bool IOLoginDataSave::savePlayerItem(const std::shared_ptr &player) { return false; } - PropWriteStream propWriteStream; - std::string deleteQueryStr = fmt::format("DELETE FROM `player_items` WHERE `player_id` = {}", player->getGUID()); + if (!g_database().executeQuery(fmt::format("DELETE FROM `player_items` WHERE `player_id` = {}", player->getGUID()))) { + g_logger().warn("[IOLoginDataSave::savePlayerItem] - Failed to delete items for player: {}", player->getName()); + return false; + } DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + PropWriteStream propWriteStream; - ItemBlockList itemList; + ItemBlockList items; for (int32_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { const auto &item = player->inventory[slotId]; if (item) { - itemList.emplace_back(slotId, item); + items.emplace_back(slotId, item); } } - if (!saveItems(player, itemList, itemsQuery, propWriteStream)) { + if (!saveItems(player, items, itemsQuery, propWriteStream)) { g_logger().warn("[IOLoginDataSave::savePlayerItem] - Failed to save items for player: {}", player->getName()); return false; } - auto itemsSaveTask = [deleteQueryStr, taskItemsQuery = std::move(itemsQuery)]() mutable { - if (!g_database().executeQuery(deleteQueryStr)) { - g_logger().warn("[SaveManager::itemsSaveTask] - Failed to execute delete query for player items"); - return; - } - - if (!taskItemsQuery.execute()) { - g_logger().warn("[SaveManager::itemsSaveTask] - Failed to execute insert query for player items"); - } - }; - - g_saveManager().addTask(itemsSaveTask, "IOLoginData::savePlayerItem - itemsSaveTask"); return true; } @@ -482,8 +454,13 @@ bool IOLoginDataSave::savePlayerDepotItems(const std::shared_ptr &player g_logger().warn("[IOLoginDataSave::savePlayerDepotItems] - Player nullptr: {}", __FUNCTION__); return false; } + PropWriteStream propWriteStream; + + if (!g_database().executeQuery(fmt::format("DELETE FROM `player_depotitems` WHERE `player_id` = {}", player->getGUID()))) { + g_logger().warn("[IOLoginDataSave::savePlayerDepotItems] - Failed to delete depot items for player: {}", player->getName()); + return false; + } - auto deleteQueryStr = fmt::format("DELETE FROM `player_depotitems` WHERE `player_id` = {}", player->getGUID()); DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); ItemDepotList depotList; @@ -493,24 +470,11 @@ bool IOLoginDataSave::savePlayerDepotItems(const std::shared_ptr &player } } - PropWriteStream propWriteStream; if (!saveItems(player, depotList, depotQuery, propWriteStream)) { g_logger().warn("[IOLoginDataSave::savePlayerDepotItems] - Failed to save depot items for player: {}", player->getName()); return false; } - auto depotItemsSaveTask = [deleteQueryStr, taskDepotQuery = std::move(depotQuery)]() mutable { - if (!g_database().executeQuery(deleteQueryStr)) { - g_logger().warn("[SaveManager::depotItemsSaveTask] - Failed to execute delete query for depot items"); - return; - } - - if (!taskDepotQuery.execute()) { - g_logger().warn("[SaveManager::depotItemsSaveTask] - Failed to execute insert query for depot items"); - } - }; - - g_saveManager().addTask(depotItemsSaveTask, "IOLoginData::savePlayerDepotItems - depotItemsSaveTask"); return true; } @@ -520,7 +484,13 @@ bool IOLoginDataSave::saveRewardItems(const std::shared_ptr &player) { return false; } - auto deleteQueryStr = fmt::format("DELETE FROM `player_rewards` WHERE `player_id` = {}", player->getGUID()); + if (!g_database().executeQuery(fmt::format("DELETE FROM `player_rewards` WHERE `player_id` = {}", player->getGUID()))) { + g_logger().warn("[IOLoginDataSave::saveRewardItems] - Failed to delete depot items for player: {}", player->getName()); + return false; + } + + PropWriteStream propWriteStream; + DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); ItemRewardList rewardListItems; @@ -536,24 +506,11 @@ bool IOLoginDataSave::saveRewardItems(const std::shared_ptr &player) { } } - PropWriteStream propWriteStream; if (!saveItems(player, rewardListItems, rewardQuery, propWriteStream)) { g_logger().warn("[IOLoginDataSave::saveRewardItems] - Failed to save reward items for the player: {}", player->getName()); return false; } - auto rewardItemsSaveTask = [deleteQueryStr, taskRewardQuery = std::move(rewardQuery)]() mutable { - if (!g_database().executeQuery(deleteQueryStr)) { - g_logger().warn("[SaveManager::rewardItemsSaveTask] - Failed to execute delete query for reward items"); - return; - } - - if (!taskRewardQuery.execute()) { - g_logger().warn("[SaveManager::rewardItemsSaveTask] - Failed to execute insert query for reward items"); - } - }; - - g_saveManager().addTask(rewardItemsSaveTask, "IOLoginDataSave::saveRewardItems - rewardItemsSaveTask"); return true; } @@ -562,8 +519,13 @@ bool IOLoginDataSave::savePlayerInbox(const std::shared_ptr &player) { g_logger().warn("[IOLoginDataSave::savePlayerInbox] - Player nullptr: {}", __FUNCTION__); return false; } + if (!g_database().executeQuery(fmt::format("DELETE FROM `player_inboxitems` WHERE `player_id` = {}", player->getGUID()))) { + g_logger().warn("[IOLoginDataSave::savePlayerInbox] - Failed to delete depot items for player: {}", player->getName()); + return false; + } + + PropWriteStream propWriteStream; - auto deleteQueryStr = fmt::format("DELETE FROM `player_inboxitems` WHERE `player_id` = {}", player->getGUID()); DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); ItemInboxList inboxList; @@ -571,42 +533,29 @@ bool IOLoginDataSave::savePlayerInbox(const std::shared_ptr &player) { inboxList.emplace_back(0, item); } - PropWriteStream propWriteStream; if (!saveItems(player, inboxList, inboxQuery, propWriteStream)) { g_logger().warn("[IOLoginDataSave::savePlayerInbox] - Failed to save inbox items for player: {}", player->getName()); return false; } - auto inboxSaveTask = [deleteQueryStr, taskInboxQuery = std::move(inboxQuery)]() mutable { - if (!g_database().executeQuery(deleteQueryStr)) { - g_logger().warn("[SaveManager::inboxSaveTask] - Failed to execute delete query for inbox items"); - return; - } - - if (!taskInboxQuery.execute()) { - g_logger().warn("[SaveManager::inboxSaveTask] - Failed to execute insert query for inbox items"); - } - }; - - g_saveManager().addTask(inboxSaveTask, "IOLoginDataSave::savePlayerInbox - inboxSaveTask"); return true; } bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) { + if (!g_configManager().getBoolean(PREY_ENABLED)) { + return true; + } + if (!player) { g_logger().warn("[IOLoginDataSave::savePlayerPreyClass] - Player nullptr: {}", __FUNCTION__); return false; } - if (!g_configManager().getBoolean(PREY_ENABLED)) { - return true; - } - DBInsert preyQuery("INSERT INTO player_prey " - "(`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, " - "`bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) VALUES "); + "(`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, " + "`bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) VALUES "); preyQuery.upsert({ "state", "raceid", "option", "bonus_type", "bonus_rarity", - "bonus_percentage", "bonus_time", "free_reroll", "monster_list" }); + "bonus_percentage", "bonus_time", "free_reroll", "monster_list" }); auto playerGUID = player->getGUID(); for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { @@ -630,32 +579,28 @@ bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) } } - auto preySaveTask = [taskPreyQuery = std::move(preyQuery)]() mutable { - if (!taskPreyQuery.execute()) { - g_logger().warn("[SaveManager::preySaveTask] - Failed to execute prey slot data insertion"); - } - }; - - g_saveManager().addTask(preySaveTask, "IOLoginDataSave::savePlayerPreyClass - preySaveTask"); + if (!preyQuery.execute()) { + g_logger().warn("[IOLoginDataSave::savePlayerPreyClass] - Error executing prey slot data insertion for player: {}", player->getName()); + return false; + } return true; } bool IOLoginDataSave::savePlayerTaskHuntingClass(const std::shared_ptr &player) { - if (!player) { - g_logger().warn("[IOLoginDataSave::savePlayerTaskHuntingClass] - Player nullptr: {}", __FUNCTION__); - return false; - } - if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) { return true; } + if (!player) { + g_logger().warn("[IOLoginDataSave::savePlayerTaskHuntingClass] - Player nullptr: {}", __FUNCTION__); + return false; + } DBInsert taskHuntQuery("INSERT INTO `player_taskhunt` " - "(`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, " - "`kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES "); + "(`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, " + "`kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES "); taskHuntQuery.upsert({ "state", "raceid", "upgrade", "rarity", "kills", "disabled_time", - "free_reroll", "monster_list" }); + "free_reroll", "monster_list" }); auto playerGUID = player->getGUID(); for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { @@ -679,13 +624,10 @@ bool IOLoginDataSave::savePlayerTaskHuntingClass(const std::shared_ptr & } } - auto taskHuntingSaveTask = [executeTaskHuntQUery = std::move(taskHuntQuery)]() mutable { - if (!executeTaskHuntQUery.execute()) { - g_logger().warn("[SaveManager::taskHuntingSaveTask] - Failed to execute task hunting data insertion"); - } - }; - - g_saveManager().addTask(taskHuntingSaveTask, "IOLoginDataSave::savePlayerTaskHuntingClass - taskHuntingSaveTask"); + if (!taskHuntQuery.execute()) { + g_logger().warn("[IOLoginDataSave::savePlayerTaskHuntingClass] - Error executing task hunting data insertion for player: {}", player->getName()); + return false; + } return true; } @@ -696,8 +638,9 @@ bool IOLoginDataSave::savePlayerBosstiary(const std::shared_ptr &player) return false; } + // Use UPSERT to avoid the DELETE operation DBInsert insertQuery("INSERT INTO `player_bosstiary` " - "(`player_id`, `bossIdSlotOne`, `bossIdSlotTwo`, `removeTimes`, `tracker`) VALUES "); + "(`player_id`, `bossIdSlotOne`, `bossIdSlotTwo`, `removeTimes`, `tracker`) VALUES "); insertQuery.upsert({ "bossIdSlotOne", "bossIdSlotTwo", "removeTimes", "tracker" }); // Prepare tracker data using PropWriteStream @@ -721,12 +664,10 @@ bool IOLoginDataSave::savePlayerBosstiary(const std::shared_ptr &player) return false; } - auto bosstiarySaveTask = [insertTaskQuery = std::move(insertQuery)]() mutable { - if (!insertTaskQuery.execute()) { - g_logger().warn("[SaveManager::bosstiarySaveTask] - Error executing bosstiary data insertion"); - } - }; + if (!insertQuery.execute()) { + g_logger().warn("[IOLoginDataSave::savePlayerBosstiary] - Error executing bosstiary data insertion for player: {}", player->getName()); + return false; + } - g_saveManager().addTask(bosstiarySaveTask, "IOLoginDataSave::savePlayerBosstiary - bosstiarySaveTask"); return true; } From 00f2619539d8e09efb99153bf8eb0b97248c80cf Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 24 Nov 2024 03:40:41 +0000 Subject: [PATCH 18/20] Code format - (Clang-format) --- .../components/player_forge_history.cpp | 12 +++--------- .../players/components/player_stash.cpp | 2 +- src/io/functions/iologindata_save_player.cpp | 18 +++++++++--------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/creatures/players/components/player_forge_history.cpp b/src/creatures/players/components/player_forge_history.cpp index c857d64fa55..b03190e56cd 100644 --- a/src/creatures/players/components/player_forge_history.cpp +++ b/src/creatures/players/components/player_forge_history.cpp @@ -63,7 +63,7 @@ bool PlayerForgeHistory::save() { auto playerGUID = m_player.getGUID(); - Database& db = Database::getInstance(); + Database &db = Database::getInstance(); if (!m_removedHistoryIds.empty()) { std::string idsToDelete = fmt::format("{}", fmt::join(m_removedHistoryIds, ", ")); @@ -83,14 +83,8 @@ bool PlayerForgeHistory::save() { DBInsert insertQuery("INSERT INTO `forge_history` (`id`, `player_id`, `action_type`, `description`, `done_at`, `is_success`) VALUES "); insertQuery.upsert({ "action_type", "description", "done_at", "is_success" }); - for (const auto& history : m_modifiedHistory) { - auto row = fmt::format("{}, {}, {}, {}, {}, {}", - history.id, - playerGUID, - history.actionType, - db.escapeString(history.description), - history.createdAt, - history.success ? 1 : 0); + for (const auto &history : m_modifiedHistory) { + auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, db.escapeString(history.description), history.createdAt, history.success ? 1 : 0); if (!insertQuery.addRow(row)) { g_logger().warn("Failed to add forge history entry for player with ID: {}", playerGUID); diff --git a/src/creatures/players/components/player_stash.cpp b/src/creatures/players/components/player_stash.cpp index 1b55b6779a9..0650f358e82 100644 --- a/src/creatures/players/components/player_stash.cpp +++ b/src/creatures/players/components/player_stash.cpp @@ -95,7 +95,7 @@ bool PlayerStash::save() { DBInsert insertQuery("INSERT INTO `player_stash` (`player_id`, `item_id`, `item_count`) VALUES "); insertQuery.upsert({ "item_count" }); - for (const auto& [itemId, itemCount] : m_modifiedItems) { + for (const auto &[itemId, itemCount] : m_modifiedItems) { auto row = fmt::format("{}, {}, {}", playerGUID, itemId, itemCount); if (!insertQuery.addRow(row)) { g_logger().warn("[PlayerStash::save] - Failed to add row for stash item: {}", itemId); diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index 61333767928..552fe6a51d7 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -317,7 +317,7 @@ bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { // Populate spells list with player's learned spells auto playerGUID = player->getGUID(); - for (const std::string& spellName : player->learnedInstantSpellList) { + for (const std::string &spellName : player->learnedInstantSpellList) { auto row = fmt::format("{}, {}", playerGUID, g_database().escapeString(spellName)); if (!spellsQuery.addRow(row)) { g_logger().warn("[IOLoginDataSave::savePlayerSpells] - Failed to add spell data for player: {}", player->getName()); @@ -344,7 +344,7 @@ bool IOLoginDataSave::savePlayerKills(const std::shared_ptr &player) { // Add rows for each kill entry auto playerGUID = player->getGUID(); - for (const auto& kill : player->unjustifiedKills) { + for (const auto &kill : player->unjustifiedKills) { auto row = fmt::format("{}, {}, {}, {}", playerGUID, kill.target, kill.time, kill.unavenged); if (!killsQuery.addRow(row)) { g_logger().warn("[IOLoginDataSave::savePlayerKills] - Failed to add kill data for player: {}", player->getName()); @@ -552,10 +552,10 @@ bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) } DBInsert preyQuery("INSERT INTO player_prey " - "(`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, " - "`bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) VALUES "); + "(`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, " + "`bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) VALUES "); preyQuery.upsert({ "state", "raceid", "option", "bonus_type", "bonus_rarity", - "bonus_percentage", "bonus_time", "free_reroll", "monster_list" }); + "bonus_percentage", "bonus_time", "free_reroll", "monster_list" }); auto playerGUID = player->getGUID(); for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { @@ -597,10 +597,10 @@ bool IOLoginDataSave::savePlayerTaskHuntingClass(const std::shared_ptr & return false; } DBInsert taskHuntQuery("INSERT INTO `player_taskhunt` " - "(`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, " - "`kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES "); + "(`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, " + "`kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES "); taskHuntQuery.upsert({ "state", "raceid", "upgrade", "rarity", "kills", "disabled_time", - "free_reroll", "monster_list" }); + "free_reroll", "monster_list" }); auto playerGUID = player->getGUID(); for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { @@ -640,7 +640,7 @@ bool IOLoginDataSave::savePlayerBosstiary(const std::shared_ptr &player) // Use UPSERT to avoid the DELETE operation DBInsert insertQuery("INSERT INTO `player_bosstiary` " - "(`player_id`, `bossIdSlotOne`, `bossIdSlotTwo`, `removeTimes`, `tracker`) VALUES "); + "(`player_id`, `bossIdSlotOne`, `bossIdSlotTwo`, `removeTimes`, `tracker`) VALUES "); insertQuery.upsert({ "bossIdSlotOne", "bossIdSlotTwo", "removeTimes", "tracker" }); // Prepare tracker data using PropWriteStream From 245dec7aa675801f89291a3fa0fa382038c01324 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sun, 24 Nov 2024 00:42:26 -0300 Subject: [PATCH 19/20] fix: correct date --- src/creatures/players/components/player_forge_history.cpp | 2 +- src/creatures/players/components/player_forge_history.hpp | 2 +- src/creatures/players/components/player_stash.cpp | 2 +- src/creatures/players/components/player_stash.hpp | 2 +- src/creatures/players/components/player_storage.cpp | 2 +- src/creatures/players/components/player_storage.hpp | 2 +- src/io/functions/iologindata_save_player.cpp | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/creatures/players/components/player_forge_history.cpp b/src/creatures/players/components/player_forge_history.cpp index b03190e56cd..f2728395912 100644 --- a/src/creatures/players/components/player_forge_history.cpp +++ b/src/creatures/players/components/player_forge_history.cpp @@ -1,6 +1,6 @@ /** * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR + * Copyright (©) 2019-2024 OpenTibiaBR * Repository: https://github.com/opentibiabr/canary * License: https://github.com/opentibiabr/canary/blob/main/LICENSE * Contributors: https://github.com/opentibiabr/canary/graphs/contributors diff --git a/src/creatures/players/components/player_forge_history.hpp b/src/creatures/players/components/player_forge_history.hpp index ed2b90b91ca..6ce4c10f2b8 100644 --- a/src/creatures/players/components/player_forge_history.hpp +++ b/src/creatures/players/components/player_forge_history.hpp @@ -1,6 +1,6 @@ /** * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR + * Copyright (©) 2019-2024 OpenTibiaBR * Repository: https://github.com/opentibiabr/canary * License: https://github.com/opentibiabr/canary/blob/main/LICENSE * Contributors: https://github.com/opentibiabr/canary/graphs/contributors diff --git a/src/creatures/players/components/player_stash.cpp b/src/creatures/players/components/player_stash.cpp index 0650f358e82..b5fa75af348 100644 --- a/src/creatures/players/components/player_stash.cpp +++ b/src/creatures/players/components/player_stash.cpp @@ -1,6 +1,6 @@ /** * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR + * Copyright (©) 2019-2024 OpenTibiaBR * Repository: https://github.com/opentibiabr/canary * License: https://github.com/opentibiabr/canary/blob/main/LICENSE * Contributors: https://github.com/opentibiabr/canary/graphs/contributors diff --git a/src/creatures/players/components/player_stash.hpp b/src/creatures/players/components/player_stash.hpp index e1b7b94a27a..ede3dbe20eb 100644 --- a/src/creatures/players/components/player_stash.hpp +++ b/src/creatures/players/components/player_stash.hpp @@ -1,6 +1,6 @@ /** * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR + * Copyright (©) 2019-2024 OpenTibiaBR * Repository: https://github.com/opentibiabr/canary * License: https://github.com/opentibiabr/canary/blob/main/LICENSE * Contributors: https://github.com/opentibiabr/canary/graphs/contributors diff --git a/src/creatures/players/components/player_storage.cpp b/src/creatures/players/components/player_storage.cpp index addac66208e..a3415ffc00d 100644 --- a/src/creatures/players/components/player_storage.cpp +++ b/src/creatures/players/components/player_storage.cpp @@ -1,6 +1,6 @@ /** * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR + * Copyright (©) 2019-2024 OpenTibiaBR * Repository: https://github.com/opentibiabr/canary * License: https://github.com/opentibiabr/canary/blob/main/LICENSE * Contributors: https://github.com/opentibiabr/canary/graphs/contributors diff --git a/src/creatures/players/components/player_storage.hpp b/src/creatures/players/components/player_storage.hpp index d7344b017ec..1432c9d2a9b 100644 --- a/src/creatures/players/components/player_storage.hpp +++ b/src/creatures/players/components/player_storage.hpp @@ -1,6 +1,6 @@ /** * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR + * Copyright (©) 2019-2024 OpenTibiaBR * Repository: https://github.com/opentibiabr/canary * License: https://github.com/opentibiabr/canary/blob/main/LICENSE * Contributors: https://github.com/opentibiabr/canary/graphs/contributors diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index 552fe6a51d7..4f37b0c0082 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -1,6 +1,6 @@ /** * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR + * Copyright (©) 2019-2024 OpenTibiaBR * Repository: https://github.com/opentibiabr/canary * License: https://github.com/opentibiabr/canary/blob/main/LICENSE * Contributors: https://github.com/opentibiabr/canary/graphs/contributors From 02bc3dd84fb8560c8486136eca346a3e8f87a428 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sun, 24 Nov 2024 00:43:21 -0300 Subject: [PATCH 20/20] fix: remove db --- src/creatures/players/components/player_forge_history.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/creatures/players/components/player_forge_history.cpp b/src/creatures/players/components/player_forge_history.cpp index f2728395912..ac79f20a862 100644 --- a/src/creatures/players/components/player_forge_history.cpp +++ b/src/creatures/players/components/player_forge_history.cpp @@ -63,8 +63,6 @@ bool PlayerForgeHistory::save() { auto playerGUID = m_player.getGUID(); - Database &db = Database::getInstance(); - if (!m_removedHistoryIds.empty()) { std::string idsToDelete = fmt::format("{}", fmt::join(m_removedHistoryIds, ", ")); std::string deleteQuery = fmt::format( @@ -72,7 +70,7 @@ bool PlayerForgeHistory::save() { playerGUID, idsToDelete ); - if (!db.executeQuery(deleteQuery)) { + if (!g_database().executeQuery(deleteQuery)) { g_logger().error("Failed to delete forge history entries for player with ID: {}", playerGUID); return false; } @@ -84,7 +82,7 @@ bool PlayerForgeHistory::save() { insertQuery.upsert({ "action_type", "description", "done_at", "is_success" }); for (const auto &history : m_modifiedHistory) { - auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, db.escapeString(history.description), history.createdAt, history.success ? 1 : 0); + auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, g_database().escapeString(history.description), history.createdAt, history.success ? 1 : 0); if (!insertQuery.addRow(row)) { g_logger().warn("Failed to add forge history entry for player with ID: {}", playerGUID);