From a4ffdb798213a64cce000f2fed2b4e0f4f32607a Mon Sep 17 00:00:00 2001 From: Jerzy Skalski Date: Fri, 6 Sep 2019 12:49:48 +0200 Subject: [PATCH 01/12] gitignore CLion files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 04b373f71b..1c05e5ebbf 100644 --- a/.gitignore +++ b/.gitignore @@ -172,6 +172,10 @@ pip-log.txt .*.swp .*.swo +# CLion files +.idea +cmake-build* + ############# ## TFS / OT ############# From ecbdd68990e86717fb24af0e89f2f68b316e8c89 Mon Sep 17 00:00:00 2001 From: Jerzy Skalski Date: Fri, 6 Sep 2019 12:55:22 +0200 Subject: [PATCH 02/12] refs #1150 DBInsert with settable DB connection --- src/database.cpp | 11 ++++++++--- src/database.h | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/database.cpp b/src/database.cpp index 8e74801e72..2b5e60bc79 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -245,9 +245,14 @@ bool DBResult::next() return row != nullptr; } -DBInsert::DBInsert(std::string query) : query(std::move(query)) +DBInsert::DBInsert(std::string query, Database* db /* = nullptr */) : query(std::move(query)) { this->length = this->query.length(); + this->database = db; + + if (!this->database) { + this->database = &Database::getInstance(); + } } bool DBInsert::addRow(const std::string& row) @@ -255,7 +260,7 @@ bool DBInsert::addRow(const std::string& row) // adds new row to buffer const size_t rowLength = row.length(); length += rowLength; - if (length > Database::getInstance().getMaxPacketSize() && !execute()) { + if (length > database->getMaxPacketSize() && !execute()) { return false; } @@ -288,7 +293,7 @@ bool DBInsert::execute() } // executes buffer - bool res = Database::getInstance().executeQuery(query + values); + bool res = database->executeQuery(query + values); values.clear(); length = query.length(); return res; diff --git a/src/database.h b/src/database.h index 51c3d78e04..218f819e32 100644 --- a/src/database.h +++ b/src/database.h @@ -189,7 +189,7 @@ class DBResult class DBInsert { public: - explicit DBInsert(std::string query); + explicit DBInsert(std::string query, Database* db = nullptr); bool addRow(const std::string& row); bool addRow(std::ostringstream& row); bool execute(); @@ -198,6 +198,7 @@ class DBInsert std::string query; std::string values; size_t length; + Database* database; }; class DBTransaction From 6784f3b3ba54567538a6d1f5e848800947eb34f4 Mon Sep 17 00:00:00 2001 From: Jerzy Skalski Date: Fri, 6 Sep 2019 16:06:37 +0200 Subject: [PATCH 03/12] refs #1150 cache player items v1, no LUA functions, not tested --- config.lua.dist | 1 + src/configmanager.cpp | 1 + src/configmanager.h | 1 + src/game.cpp | 3 + src/iologindata.cpp | 546 +++++++++++++++++++++++++++++++----------- src/iologindata.h | 57 ++++- src/item.cpp | 10 + src/item.h | 1 + src/otserv.cpp | 7 + src/player.h | 1 + 10 files changed, 491 insertions(+), 137 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index 2860d8407d..f5289a7147 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -75,6 +75,7 @@ emoteSpells = false classicEquipmentSlots = false classicAttackSpeed = false showScriptsLogInConsole = true +playerItemsCache = true -- Rates -- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 31180b9a4a..7b857f81ec 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -136,6 +136,7 @@ bool ConfigManager::load() boolean[CLASSIC_EQUIPMENT_SLOTS] = getGlobalBoolean(L, "classicEquipmentSlots", false); boolean[CLASSIC_ATTACK_SPEED] = getGlobalBoolean(L, "classicAttackSpeed", false); boolean[SCRIPTS_CONSOLE_LOGS] = getGlobalBoolean(L, "showScriptsLogInConsole", true); + boolean[PLAYER_ITEMS_CACHE] = getGlobalBoolean(L, "playerItemsCache", true); string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); string[SERVER_NAME] = getGlobalString(L, "serverName", ""); diff --git a/src/configmanager.h b/src/configmanager.h index 3e6a40b45f..08b1871e6b 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -42,6 +42,7 @@ class ConfigManager CLASSIC_EQUIPMENT_SLOTS, CLASSIC_ATTACK_SPEED, SCRIPTS_CONSOLE_LOGS, + PLAYER_ITEMS_CACHE, LAST_BOOLEAN_CONFIG /* this must be the last one */ }; diff --git a/src/game.cpp b/src/game.cpp index b33c28c3b2..f143bdd86a 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -55,6 +55,7 @@ extern Monsters g_monsters; extern MoveEvents* g_moveEvents; extern Weapons* g_weapons; extern Scripts* g_scripts; +extern PlayerCacheManager g_playerCacheManager; Game::Game() { @@ -148,6 +149,7 @@ void Game::setGameState(GameState_t newState) g_scheduler.stop(); g_databaseTasks.stop(); g_dispatcher.stop(); + g_playerCacheManager.stop(); break; } @@ -4536,6 +4538,7 @@ void Game::shutdown() g_scheduler.shutdown(); g_databaseTasks.shutdown(); g_dispatcher.shutdown(); + g_playerCacheManager.shutdown(); map.spawns.clear(); raids.clear(); diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 040688c3db..dc38a82801 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -25,6 +25,7 @@ extern ConfigManager g_config; extern Game g_game; +PlayerCacheManager g_playerCacheManager; Account IOLoginData::loadAccount(uint32_t accno) { @@ -451,91 +452,97 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } while (result->next()); } - //load inventory items - ItemMap itemMap; + if (!g_config.getBoolean(ConfigManager::PLAYER_ITEMS_CACHE) || + !g_playerCacheManager.loadCachedPlayer(player->getGUID(), player)) { + //load inventory items + ItemMap itemMap; - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { - loadItems(itemMap, result); - - for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { - const std::pair& pair = it->second; - Item* item = pair.first; - int32_t pid = pair.second; - if (pid >= 1 && pid <= 10) { - player->internalAddThing(pid, item); - } else { - ItemMap::const_iterator it2 = itemMap.find(pid); - if (it2 == itemMap.end()) { - continue; - } - - Container* container = it2->second.first->getContainer(); - if (container) { - container->internalAddThing(item); + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " + << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db.storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair &pair = it->second; + Item *item = pair.first; + int32_t pid = pair.second; + if (pid >= 1 && pid <= 10) { + player->internalAddThing(pid, item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } + + Container *container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } } } } - } - //load depot items - itemMap.clear(); + //load depot items + itemMap.clear(); - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { - loadItems(itemMap, result); - - for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { - const std::pair& pair = it->second; - Item* item = pair.first; - - int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { - DepotChest* depotChest = player->getDepotChest(pid, true); - if (depotChest) { - depotChest->internalAddThing(item); - } - } else { - ItemMap::const_iterator it2 = itemMap.find(pid); - if (it2 == itemMap.end()) { - continue; - } - - Container* container = it2->second.first->getContainer(); - if (container) { - container->internalAddThing(item); + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " + << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db.storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair &pair = it->second; + Item *item = pair.first; + + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { + DepotChest *depotChest = player->getDepotChest(pid, true); + if (depotChest) { + depotChest->internalAddThing(item); + } + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } + + Container *container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } } } } - } - //load inbox items - itemMap.clear(); + //load inbox items + itemMap.clear(); - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { - loadItems(itemMap, result); - - for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { - const std::pair& pair = it->second; - Item* item = pair.first; - int32_t pid = pair.second; - - if (pid >= 0 && pid < 100) { - player->getInbox()->internalAddThing(item); - } else { - ItemMap::const_iterator it2 = itemMap.find(pid); - - if (it2 == itemMap.end()) { - continue; - } - - Container* container = it2->second.first->getContainer(); - if (container) { - container->internalAddThing(item); + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = " + << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db.storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair &pair = it->second; + Item *item = pair.first; + int32_t pid = pair.second; + + if (pid >= 0 && pid < 100) { + player->getInbox()->internalAddThing(item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + + if (it2 == itemMap.end()) { + continue; + } + + Container *container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } } } } @@ -565,7 +572,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) return true; } -bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) +bool IOLoginData::saveItems(uint32_t guid, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) { std::ostringstream ss; @@ -586,7 +593,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); + ss << guid << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); if (!query_insert.addRow(ss)) { return false; } @@ -616,7 +623,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); + ss << guid << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); if (!query_insert.addRow(ss)) { return false; } @@ -625,6 +632,79 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, return query_insert.execute(); } +bool IOLoginData::savePlayerItems(uint32_t guid, Item** inventory, std::map& depotChests, + Inbox* inbox, int16_t lastDepotId, Database* db /* = nullptr */) { + if (!db) { + db = &Database::getInstance(); + } + + std::ostringstream query; + PropWriteStream propWriteStream; + + query << "DELETE FROM `player_items` WHERE `player_id` = " << guid; + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); + + ItemBlockList itemList; + for (int32_t slotId = 1; slotId <= 10; ++slotId) { + Item* item = inventory[slotId]; + if (item) { + itemList.emplace_back(slotId, item); + } + } + + if (!saveItems(guid, itemList, itemsQuery, propWriteStream)) { + return false; + } + + if (lastDepotId != -1) { + //save depot items + query.str(std::string()); + query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << guid; + + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); + itemList.clear(); + + for (const auto& it : depotChests) { + DepotChest* depotChest = it.second; + for (Item* item : depotChest->getItemList()) { + itemList.emplace_back(it.first, item); + } + } + + if (!saveItems(guid, itemList, depotQuery, propWriteStream)) { + return false; + } + } + + //save inbox items + query.str(std::string()); + query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << guid; + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); + itemList.clear(); + + for (Item* item : inbox->getItemList()) { + itemList.emplace_back(0, item); + } + + if (!saveItems(guid, itemList, inboxQuery, propWriteStream)) { + return false; + } + + return true; +} + bool IOLoginData::savePlayer(Player* player) { if (player->getHealth() <= 0) { @@ -773,66 +853,16 @@ bool IOLoginData::savePlayer(Player* player) return false; } - //item saving - query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - return false; - } - - DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - - ItemBlockList itemList; - for (int32_t slotId = 1; slotId <= 10; ++slotId) { - Item* item = player->inventory[slotId]; - if (item) { - itemList.emplace_back(slotId, item); - } - } - - if (!saveItems(player, itemList, itemsQuery, propWriteStream)) { - return false; - } - - if (player->lastDepotId != -1) { - //save depot items - query.str(std::string()); - query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); - - if (!db.executeQuery(query.str())) { - return false; - } - - DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - itemList.clear(); - - for (const auto& it : player->depotChests) { - DepotChest* depotChest = it.second; - for (Item* item : depotChest->getItemList()) { - itemList.emplace_back(it.first, item); - } - } - - if (!saveItems(player, itemList, depotQuery, propWriteStream)) { - return false; - } - } - - //save inbox items - query.str(std::string()); - query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - return false; - } - - DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - itemList.clear(); - - for (Item* item : player->getInbox()->getItemList()) { - itemList.emplace_back(0, item); + if (g_config.getBoolean(ConfigManager::PLAYER_ITEMS_CACHE)) { + g_playerCacheManager.cachePlayer(player->getGUID(), player); } - - if (!saveItems(player, itemList, inboxQuery, propWriteStream)) { - return false; + else { + savePlayerItems(player->getGUID(), + player->inventory, + player->depotChests, + player->inbox, + player->lastDepotId, + &db); } query.str(std::string()); @@ -1029,3 +1059,247 @@ void IOLoginData::removePremiumDays(uint32_t accountId, int32_t removeDays) query << "UPDATE `accounts` SET `premdays` = `premdays` - " << removeDays << " WHERE `id` = " << accountId; Database::getInstance().executeQuery(query.str()); } + +PlayerCacheData* PlayerCacheManager::getCachedPlayer(uint32_t guid, bool autoCreate /* = false */) +{ + PlayerCacheData* playerCacheData; + + listLock.lock(); + auto it = playersCache.find(guid); + if (it == playersCache.end()) { + if (autoCreate) { + playerCacheData = new PlayerCacheData(); + playersCache.emplace(guid, playerCacheData); + } + else { + listLock.unlock(); + return nullptr; + } + } + else { + playerCacheData = it->second; + } + + listLock.unlock(); + return playerCacheData; +} + +bool PlayerCacheManager::loadCachedPlayer(uint32_t guid, Player* player) +{ + PlayerCacheData* playerCacheData = getCachedPlayer(guid); + + if (!playerCacheData) { + return false; + } + + playerCacheData->copyDataToPlayer(player); + + return true; +} + +void PlayerCacheManager::cachePlayer(uint32_t guid, Player* player) +{ + PlayerCacheData* playerCacheData = getCachedPlayer(guid, true); + playerCacheData->copyDataFromPlayer(player); + addToSaveList(guid); +} + +void PlayerCacheManager::start() +{ + db.connect(); + ThreadHolder::start(); +} + +void PlayerCacheManager::threadMain() +{ + std::unique_lock listLockUnique(listLock, std::defer_lock); + while (getState() != THREAD_STATE_TERMINATED) { + listLockUnique.lock(); + if (toSaveList.empty()) { + listSignal.wait(listLockUnique); + } + + if (!toSaveList.empty()) { + uint32_t guidToSave = toSaveList.front(); + toSaveList.pop_front(); + listLockUnique.unlock(); + if (!saveCachedItems(guidToSave)) { + std::cout << "Error while saving player items: " << guidToSave << std::endl; + } + } + else { + listLockUnique.unlock(); + } + } +} + +void PlayerCacheManager::addToSaveList(uint32_t guid) +{ + bool signal = false; + listLock.lock(); + + signal = toSaveList.empty(); + toSaveList.emplace_back(guid); + + listLock.unlock(); + + if (signal) { + listSignal.notify_one(); + } +} + +bool PlayerCacheManager::saveCachedItems(uint32_t guid) +{ + PlayerCacheData* playerCacheData = getCachedPlayer(guid); + + if (!playerCacheData) { + return false; + } + + PlayerCacheData* playerCacheDataClone = playerCacheData->clone(); + + bool saved = IOLoginData::savePlayerItems(guid, playerCacheDataClone->inventory, playerCacheDataClone->depotChests, playerCacheDataClone->inbox, playerCacheDataClone->lastDepotId, &db); + + delete playerCacheDataClone; + + return saved; +} + +void PlayerCacheManager::flush() +{ + std::unique_lock guard{ listLock }; + while (!toSaveList.empty()) { + auto guidToSave = toSaveList.front(); + toSaveList.pop_front(); + guard.unlock(); + if (!saveCachedItems(guidToSave)) { + std::cout << "Error while saving player items: " << guidToSave << std::endl; + } + guard.lock(); + } +} + +void PlayerCacheManager::shutdown() +{ + listLock.lock(); + setState(THREAD_STATE_TERMINATED); + listLock.unlock(); + + flush(); + listSignal.notify_one(); +} + +PlayerCacheData::~PlayerCacheData() { + clear(); +} + +PlayerCacheData* PlayerCacheData::clone() { + dataLock.lock(); + + auto clone = new PlayerCacheData(); + + for (uint8_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { + Item* slotItem = inventory[slotId]; + + if (slotItem) { + clone->inventory[slotId] = slotItem->cloneWithoutDecay(); + } + } + + for (const auto& it : depotChests) { + auto depotId = it.first; + DepotChest* depotChest = it.second; + + clone->depotChests[depotId] = (DepotChest*)depotChest->cloneWithoutDecay(); + } + + if (inbox) { + clone->inbox = (Inbox*)inbox->cloneWithoutDecay(); + } + + clone->lastDepotId = lastDepotId; + + dataLock.unlock(); + + return clone; +} + +void PlayerCacheData::copyDataFromPlayer(Player* player) { + clear(); + + dataLock.lock(); + + for (uint8_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { + Item* slotItem = player->inventory[slotId]; + + if (slotItem) { + inventory[slotId] = slotItem->cloneWithoutDecay(); + inventory[slotId]->setParent(nullptr); + } + } + + for (const auto& it : player->depotChests) { + auto depotId = it.first; + DepotChest* depotChest = it.second; + + depotChests[depotId] = (DepotChest*)depotChest->cloneWithoutDecay(); + depotChests[depotId]->setParent(nullptr); + } + + Inbox* playerInbox = player->inbox; + + if (playerInbox) { + inbox = (Inbox*)playerInbox->cloneWithoutDecay(); + inbox->setParent(nullptr); + } + + lastDepotId = player->lastDepotId; + + dataLock.unlock(); +} + +void PlayerCacheData::copyDataToPlayer(Player* player) { + dataLock.lock(); + + for (uint8_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { + Item* slotItem = inventory[slotId]; + if (slotItem) { + player->internalAddThing(slotId, slotItem->cloneWithoutDecay()); + } + } + + for (const auto& it : depotChests) { + auto depotId = it.first; + DepotChest* depotChest = it.second; + player->depotChests[depotId] = (DepotChest*)depotChest->cloneWithoutDecay(); + } + + if (inbox) { + player->inbox = (Inbox*)inbox->cloneWithoutDecay(); + } + + dataLock.unlock(); +} + +void PlayerCacheData::clear() { + dataLock.lock(); + + for (uint8_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { + if (inventory[slotId]) { + delete inventory[slotId]; + inventory[slotId] = nullptr; + } + } + + for (auto& it : depotChests) { + delete it.second; + } + depotChests.clear(); + + if (inbox) { + delete inbox; + inbox = nullptr; + } + + dataLock.unlock(); +} diff --git a/src/iologindata.h b/src/iologindata.h index 446e361ad6..f570ad8266 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -23,6 +23,7 @@ #include "account.h" #include "player.h" #include "database.h" +#include "inbox.h" using ItemBlockList = std::list>; @@ -59,11 +60,65 @@ class IOLoginData static void addPremiumDays(uint32_t accountId, int32_t addDays); static void removePremiumDays(uint32_t accountId, int32_t removeDays); + static bool savePlayerItems(uint32_t guid, Item** inventory, std::map& depotChests, Inbox* inbox, int16_t lastDepotId, Database* database = nullptr); + private: using ItemMap = std::map>; static void loadItems(ItemMap& itemMap, DBResult_ptr result); - static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream); + static bool saveItems(uint32_t guid, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream); +}; + +class PlayerCacheData; + +class PlayerCacheManager : public ThreadHolder +{ + public: + PlayerCacheManager() = default; + + bool loadCachedPlayer(uint32_t guid, Player* player); + void cachePlayer(uint32_t guid, Player* player); + + void start(); + void flush(); + void shutdown(); + + void addToSaveList(uint32_t guid); + + void threadMain(); + + private: + PlayerCacheData* getCachedPlayer(uint32_t guid, bool autoCreate = false); + + bool saveCachedItems(uint32_t guid); + + Database db; + std::thread thread; + std::list toSaveList; + std::mutex listLock; + std::condition_variable listSignal; + + std::map playersCache; +}; + +class PlayerCacheData +{ + public: + ~PlayerCacheData(); + PlayerCacheData* clone(); + void copyDataFromPlayer(Player* player); + void copyDataToPlayer(Player* player); + + private: + void clear(); + + Item* inventory[CONST_SLOT_LAST + 1] = {}; + std::map depotChests; + Inbox* inbox = nullptr; + int16_t lastDepotId = -1; + std::mutex dataLock; + + friend class PlayerCacheManager; }; #endif diff --git a/src/item.cpp b/src/item.cpp index 07862ad7f6..6932188424 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -190,6 +190,16 @@ Item* Item::clone() const return item; } +Item* Item::cloneWithoutDecay() const +{ + Item* item = Item::CreateItem(id, count); + if (attributes) { + item->attributes.reset(new ItemAttributes(*attributes)); + item->setDecaying(DECAYING_FALSE); + } + return item; +} + bool Item::equals(const Item* otherItem) const { if (!otherItem || id != otherItem->id) { diff --git a/src/item.h b/src/item.h index 97795bf395..0d37bdcea6 100644 --- a/src/item.h +++ b/src/item.h @@ -526,6 +526,7 @@ class Item : virtual public Thing Item(const uint16_t type, uint16_t count = 0); Item(const Item& i); virtual Item* clone() const; + virtual Item* cloneWithoutDecay() const; virtual ~Item() = default; diff --git a/src/otserv.cpp b/src/otserv.cpp index c47501c2bc..7b3aafcc2e 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -35,11 +35,13 @@ #include "scheduler.h" #include "databasetasks.h" #include "script.h" +#include "iologindata.h" #include DatabaseTasks g_databaseTasks; Dispatcher g_dispatcher; Scheduler g_scheduler; +extern PlayerCacheManager g_playerCacheManager; Game g_game; ConfigManager g_config; @@ -90,11 +92,15 @@ int main(int argc, char* argv[]) g_scheduler.shutdown(); g_databaseTasks.shutdown(); g_dispatcher.shutdown(); + g_playerCacheManager.shutdown(); } g_scheduler.join(); g_databaseTasks.join(); g_dispatcher.join(); + std::cout << ">> Saving player items." << std::endl; + g_playerCacheManager.flush(); + g_playerCacheManager.join(); return 0; } @@ -182,6 +188,7 @@ void mainLoader(int, char*[], ServiceManager* services) return; } g_databaseTasks.start(); + g_playerCacheManager.start(); DatabaseManager::updateDatabase(); diff --git a/src/player.h b/src/player.h index db0dc4e44c..35c1200012 100644 --- a/src/player.h +++ b/src/player.h @@ -1350,6 +1350,7 @@ class Player final : public Creature, public Cylinder friend class Actions; friend class IOLoginData; friend class ProtocolGame; + friend class PlayerCacheData; }; #endif From e7488fd5d963d560b6f3d8fa0e333be4b8ff872e Mon Sep 17 00:00:00 2001 From: Jerzy Skalski Date: Fri, 6 Sep 2019 16:43:16 +0200 Subject: [PATCH 04/12] refs #1150 cache player items v2, moved player items loading to separate function, no LUA functions, not tested --- src/iologindata.cpp | 187 ++++++++++++++++++++++---------------------- src/iologindata.h | 1 + 2 files changed, 96 insertions(+), 92 deletions(-) diff --git a/src/iologindata.cpp b/src/iologindata.cpp index dc38a82801..30da9d0055 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -246,6 +246,100 @@ bool IOLoginData::loadPlayerByName(Player* player, const std::string& name) return loadPlayer(player, db.storeQuery(query.str())); } +void IOLoginData::loadPlayerItems(Player* player, Database* db) { + std::ostringstream query; + DBResult_ptr result; + + //load inventory items + ItemMap itemMap; + + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair &pair = it->second; + Item *item = pair.first; + int32_t pid = pair.second; + if (pid >= 1 && pid <= 10) { + player->internalAddThing(pid, item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } + + Container *container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } + } + } + } + + //load depot items + itemMap.clear(); + + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair &pair = it->second; + Item *item = pair.first; + + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { + DepotChest *depotChest = player->getDepotChest(pid, true); + if (depotChest) { + depotChest->internalAddThing(item); + } + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } + + Container *container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } + } + } + } + + //load inbox items + itemMap.clear(); + + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair &pair = it->second; + Item *item = pair.first; + int32_t pid = pair.second; + + if (pid >= 0 && pid < 100) { + player->getInbox()->internalAddThing(item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + + if (it2 == itemMap.end()) { + continue; + } + + Container *container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } + } + } + } +} bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) { if (!result) { @@ -454,98 +548,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) if (!g_config.getBoolean(ConfigManager::PLAYER_ITEMS_CACHE) || !g_playerCacheManager.loadCachedPlayer(player->getGUID(), player)) { - //load inventory items - ItemMap itemMap; - - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " - << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { - loadItems(itemMap, result); - - for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { - const std::pair &pair = it->second; - Item *item = pair.first; - int32_t pid = pair.second; - if (pid >= 1 && pid <= 10) { - player->internalAddThing(pid, item); - } else { - ItemMap::const_iterator it2 = itemMap.find(pid); - if (it2 == itemMap.end()) { - continue; - } - - Container *container = it2->second.first->getContainer(); - if (container) { - container->internalAddThing(item); - } - } - } - } - - //load depot items - itemMap.clear(); - - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " - << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { - loadItems(itemMap, result); - - for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { - const std::pair &pair = it->second; - Item *item = pair.first; - - int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { - DepotChest *depotChest = player->getDepotChest(pid, true); - if (depotChest) { - depotChest->internalAddThing(item); - } - } else { - ItemMap::const_iterator it2 = itemMap.find(pid); - if (it2 == itemMap.end()) { - continue; - } - - Container *container = it2->second.first->getContainer(); - if (container) { - container->internalAddThing(item); - } - } - } - } - - //load inbox items - itemMap.clear(); - - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = " - << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { - loadItems(itemMap, result); - - for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { - const std::pair &pair = it->second; - Item *item = pair.first; - int32_t pid = pair.second; - - if (pid >= 0 && pid < 100) { - player->getInbox()->internalAddThing(item); - } else { - ItemMap::const_iterator it2 = itemMap.find(pid); - - if (it2 == itemMap.end()) { - continue; - } - - Container *container = it2->second.first->getContainer(); - if (container) { - container->internalAddThing(item); - } - } - } - } + loadPlayerItems(player, &db); } //load storage map diff --git a/src/iologindata.h b/src/iologindata.h index f570ad8266..aa30559944 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -65,6 +65,7 @@ class IOLoginData private: using ItemMap = std::map>; + static void loadPlayerItems(Player* player, Database* db); static void loadItems(ItemMap& itemMap, DBResult_ptr result); static bool saveItems(uint32_t guid, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream); }; From c239da45f947ae01e76ea7b657d152cb7c109031 Mon Sep 17 00:00:00 2001 From: Jerzy Skalski Date: Fri, 6 Sep 2019 17:28:45 +0200 Subject: [PATCH 05/12] refs #1150 cache player items v3, added LUA functions, not tested --- src/iologindata.cpp | 25 +++++++++++++++++++++++++ src/iologindata.h | 2 ++ src/luascript.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ src/luascript.h | 5 +++++ 4 files changed, 73 insertions(+) diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 30da9d0055..f270fc455f 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -1107,6 +1107,31 @@ void PlayerCacheManager::cachePlayer(uint32_t guid, Player* player) addToSaveList(guid); } +void PlayerCacheManager::clear() +{ + listLock.lock(); + + for (auto& it : playersCache) { + delete it.second; + } + playersCache.clear(); + + listLock.unlock(); +} + +void PlayerCacheManager::clear(uint32_t guid) +{ + listLock.lock(); + + auto it = playersCache.find(guid); + if (it != playersCache.end()) { + delete it->second; + playersCache.erase(it); + } + + listLock.unlock(); +} + void PlayerCacheManager::start() { db.connect(); diff --git a/src/iologindata.h b/src/iologindata.h index aa30559944..1429f57bc4 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -79,6 +79,8 @@ class PlayerCacheManager : public ThreadHolder bool loadCachedPlayer(uint32_t guid, Player* player); void cachePlayer(uint32_t guid, Player* player); + void clear(); + void clear(uint32_t guid); void start(); void flush(); diff --git a/src/luascript.cpp b/src/luascript.cpp index 6b5801beec..20e84bb772 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -53,6 +53,7 @@ extern MoveEvents* g_moveEvents; extern GlobalEvents* g_globalEvents; extern Scripts* g_scripts; extern Weapons* g_weapons; +extern PlayerCacheManager g_playerCacheManager; ScriptEnvironment::DBResultMap ScriptEnvironment::tempResults; uint32_t ScriptEnvironment::lastResultId = 0; @@ -2429,6 +2430,13 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "hasSecureMode", LuaScriptInterface::luaPlayerHasSecureMode); registerMethod("Player", "getFightMode", LuaScriptInterface::luaPlayerGetFightMode); + // Game + registerTable("PlayerCacheManager"); + + registerMethod("PlayerCacheManager", "clearCache", LuaScriptInterface::luaPlayerCacheManagerClearCache); + registerMethod("PlayerCacheManager", "clearPlayerCache", LuaScriptInterface::luaPlayerCacheManagerClearPlayerCache); + registerMethod("PlayerCacheManager", "loadPlayerCache", LuaScriptInterface::luaPlayerCacheManagerLoadPlayerCache); + // Monster registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -9970,6 +9978,39 @@ int LuaScriptInterface::luaPlayerGetFightMode(lua_State* L) return 1; } +// PlayerCacheManager +int LuaScriptInterface::luaPlayerCacheManagerClearCache(lua_State* L) +{ + // PlayerCacheManager.clearCache() + g_playerCacheManager.clear(); + return 1; +} + +int LuaScriptInterface::luaPlayerCacheManagerClearPlayerCache(lua_State* L) +{ + // PlayerCacheManager.clearPlayerCache(guid) + auto guid = getNumber(L, 2); + g_playerCacheManager.clear(guid); + return 1; +} + +int LuaScriptInterface::luaPlayerCacheManagerLoadPlayerCache(lua_State* L) +{ + // PlayerCacheManager.loadPlayerCache(guid) + auto guid = getNumber(L, 2); + + Player tmpPlayer(nullptr); + g_playerCacheManager.clear(guid); + if (!IOLoginData::loadPlayerById(&tmpPlayer, guid)) { + pushBoolean(L, false); + return 1; + } + + g_playerCacheManager.cachePlayer(guid, &tmpPlayer); + pushBoolean(L, true); + return 1; +} + // Monster int LuaScriptInterface::luaMonsterCreate(lua_State* L) { diff --git a/src/luascript.h b/src/luascript.h index 1434eee702..015a285ff1 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -992,6 +992,11 @@ class LuaScriptInterface static int luaPlayerHasSecureMode(lua_State* L); static int luaPlayerGetFightMode(lua_State* L); + // PlayerCacheManager + static int luaPlayerCacheManagerClearCache(lua_State* L); + static int luaPlayerCacheManagerClearPlayerCache(lua_State* L); + static int luaPlayerCacheManagerLoadPlayerCache(lua_State* L); + // Monster static int luaMonsterCreate(lua_State* L); From ed06e2797936a7441bd49e73562777b8f164d8c9 Mon Sep 17 00:00:00 2001 From: gesior Date: Sun, 8 Sep 2019 14:56:54 +0200 Subject: [PATCH 06/12] refs #1150 cache player items v4, fixed containers save, tested --- src/container.cpp | 10 ++++++++++ src/container.h | 1 + 2 files changed, 11 insertions(+) diff --git a/src/container.cpp b/src/container.cpp index 1fe81175a0..e51946cce8 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -76,6 +76,16 @@ Item* Container::clone() const return clone; } +Item* Container::cloneWithoutDecay() const +{ + Container* clone = static_cast(Item::cloneWithoutDecay()); + for (Item* item : itemlist) { + clone->addItem(item->cloneWithoutDecay()); + } + clone->totalWeight = totalWeight; + return clone; +} + Container* Container::getParentContainer() { Thing* thing = getParent(); diff --git a/src/container.h b/src/container.h index c4a683843c..2111412267 100644 --- a/src/container.h +++ b/src/container.h @@ -59,6 +59,7 @@ class Container : public Item, public Cylinder Container& operator=(const Container&) = delete; Item* clone() const override final; + Item* cloneWithoutDecay() const override final; Container* getContainer() override final { return this; From 826e3ff620e82bfdec2cdc635b1fd00d5348d4ca Mon Sep 17 00:00:00 2001 From: gesior Date: Sun, 8 Sep 2019 17:47:32 +0200 Subject: [PATCH 07/12] refs #1150 cache player items v5, added save threshold, refactor code --- config.lua.dist | 1 + src/configmanager.cpp | 1 + src/configmanager.h | 1 + src/iologindata.cpp | 48 +++++++++++++++++++++++-------------------- src/iologindata.h | 6 ++---- src/luascript.cpp | 9 ++++++++ src/luascript.h | 1 + 7 files changed, 41 insertions(+), 26 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index f5289a7147..7b18c47a3c 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -76,6 +76,7 @@ classicEquipmentSlots = false classicAttackSpeed = false showScriptsLogInConsole = true playerItemsCache = true +playerItemsCacheSaveThreshold = 5000 -- Rates -- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 7b857f81ec..571163effa 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -173,6 +173,7 @@ bool ConfigManager::load() integer[CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES] = getGlobalNumber(L, "checkExpiredMarketOffersEachMinutes", 60); integer[MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER] = getGlobalNumber(L, "maxMarketOffersAtATimePerPlayer", 100); integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25); + integer[PLAYER_ITEMS_CACHE_SAVE_THRESHOLD] = getGlobalNumber(L, "playerItemsCacheSaveThreshold", 5000); loaded = true; lua_close(L); diff --git a/src/configmanager.h b/src/configmanager.h index 08b1871e6b..fa21cd02f5 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -101,6 +101,7 @@ class ConfigManager MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, EXP_FROM_PLAYERS_LEVEL_RANGE, MAX_PACKETS_PER_SECOND, + PLAYER_ITEMS_CACHE_SAVE_THRESHOLD, LAST_INTEGER_CONFIG /* this must be the last one */ }; diff --git a/src/iologindata.cpp b/src/iologindata.cpp index f270fc455f..7cc502d52e 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -1141,39 +1141,40 @@ void PlayerCacheManager::start() void PlayerCacheManager::threadMain() { std::unique_lock listLockUnique(listLock, std::defer_lock); + while (getState() != THREAD_STATE_TERMINATED) { listLockUnique.lock(); - if (toSaveList.empty()) { - listSignal.wait(listLockUnique); - } - if (!toSaveList.empty()) { - uint32_t guidToSave = toSaveList.front(); - toSaveList.pop_front(); + if (!toSave.empty()) { + int64_t currentTime = OTSYS_TIME(); + uint32_t guidToSave = 0; + for (auto it : toSave) { + if (it.second + g_config.getNumber(ConfigManager::PLAYER_ITEMS_CACHE_SAVE_THRESHOLD) < currentTime) { + guidToSave = it.first; + toSave.erase(it.first); + break; + } + } listLockUnique.unlock(); - if (!saveCachedItems(guidToSave)) { + + if (guidToSave > 0 && !saveCachedItems(guidToSave)) { std::cout << "Error while saving player items: " << guidToSave << std::endl; } } else { listLockUnique.unlock(); } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } void PlayerCacheManager::addToSaveList(uint32_t guid) { - bool signal = false; listLock.lock(); - signal = toSaveList.empty(); - toSaveList.emplace_back(guid); + toSave[guid] = OTSYS_TIME(); listLock.unlock(); - - if (signal) { - listSignal.notify_one(); - } } bool PlayerCacheManager::saveCachedItems(uint32_t guid) @@ -1185,9 +1186,7 @@ bool PlayerCacheManager::saveCachedItems(uint32_t guid) } PlayerCacheData* playerCacheDataClone = playerCacheData->clone(); - bool saved = IOLoginData::savePlayerItems(guid, playerCacheDataClone->inventory, playerCacheDataClone->depotChests, playerCacheDataClone->inbox, playerCacheDataClone->lastDepotId, &db); - delete playerCacheDataClone; return saved; @@ -1196,13 +1195,19 @@ bool PlayerCacheManager::saveCachedItems(uint32_t guid) void PlayerCacheManager::flush() { std::unique_lock guard{ listLock }; - while (!toSaveList.empty()) { - auto guidToSave = toSaveList.front(); - toSaveList.pop_front(); + while (!toSave.empty()) { + uint32_t guidToSave = 0; + for (auto it : toSave) { + guidToSave = it.first; + toSave.erase(it.first); + break; + } guard.unlock(); + if (!saveCachedItems(guidToSave)) { std::cout << "Error while saving player items: " << guidToSave << std::endl; } + guard.lock(); } } @@ -1210,11 +1215,10 @@ void PlayerCacheManager::flush() void PlayerCacheManager::shutdown() { listLock.lock(); + setState(THREAD_STATE_TERMINATED); - listLock.unlock(); - flush(); - listSignal.notify_one(); + listLock.unlock(); } PlayerCacheData::~PlayerCacheData() { diff --git a/src/iologindata.h b/src/iologindata.h index 1429f57bc4..8823bcee05 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -87,19 +87,17 @@ class PlayerCacheManager : public ThreadHolder void shutdown(); void addToSaveList(uint32_t guid); + bool saveCachedItems(uint32_t guid); void threadMain(); private: PlayerCacheData* getCachedPlayer(uint32_t guid, bool autoCreate = false); - bool saveCachedItems(uint32_t guid); - Database db; std::thread thread; - std::list toSaveList; + std::map toSave; std::mutex listLock; - std::condition_variable listSignal; std::map playersCache; }; diff --git a/src/luascript.cpp b/src/luascript.cpp index 20e84bb772..b0622aea59 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -2436,6 +2436,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("PlayerCacheManager", "clearCache", LuaScriptInterface::luaPlayerCacheManagerClearCache); registerMethod("PlayerCacheManager", "clearPlayerCache", LuaScriptInterface::luaPlayerCacheManagerClearPlayerCache); registerMethod("PlayerCacheManager", "loadPlayerCache", LuaScriptInterface::luaPlayerCacheManagerLoadPlayerCache); + registerMethod("PlayerCacheManager", "savePlayerCache", LuaScriptInterface::luaPlayerCacheManagerSavePlayerCache); // Monster registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); @@ -10011,6 +10012,14 @@ int LuaScriptInterface::luaPlayerCacheManagerLoadPlayerCache(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerCacheManagerSavePlayerCache(lua_State* L) +{ + // PlayerCacheManager.savePlayerCache(guid) + auto guid = getNumber(L, 2); + g_playerCacheManager.saveCachedItems(guid); + return 1; +} + // Monster int LuaScriptInterface::luaMonsterCreate(lua_State* L) { diff --git a/src/luascript.h b/src/luascript.h index 015a285ff1..45d4fdb8e9 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -996,6 +996,7 @@ class LuaScriptInterface static int luaPlayerCacheManagerClearCache(lua_State* L); static int luaPlayerCacheManagerClearPlayerCache(lua_State* L); static int luaPlayerCacheManagerLoadPlayerCache(lua_State* L); + static int luaPlayerCacheManagerSavePlayerCache(lua_State* L); // Monster static int luaMonsterCreate(lua_State* L); From 4f7a53238ca2d7b35caf8f14e8920860c8f2a18e Mon Sep 17 00:00:00 2001 From: gesior Date: Sun, 8 Sep 2019 20:47:33 +0200 Subject: [PATCH 08/12] refs #1150 save player items in binary format --- config.lua.dist | 1 + data/migrations/24.lua | 6 +- data/migrations/25.lua | 3 + src/configmanager.cpp | 1 + src/configmanager.h | 1 + src/const.h | 7 + src/iologindata.cpp | 371 ++++++++++++++++++++++++++++++----------- src/iologindata.h | 2 +- 8 files changed, 296 insertions(+), 96 deletions(-) create mode 100644 data/migrations/25.lua diff --git a/config.lua.dist b/config.lua.dist index 7b18c47a3c..55135c12b1 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -77,6 +77,7 @@ classicAttackSpeed = false showScriptsLogInConsole = true playerItemsCache = true playerItemsCacheSaveThreshold = 5000 +binaryPlayerItems = true -- Rates -- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml diff --git a/data/migrations/24.lua b/data/migrations/24.lua index d0ffd9c0cb..6296694e4c 100644 --- a/data/migrations/24.lua +++ b/data/migrations/24.lua @@ -1,3 +1,7 @@ function onUpdateDatabase() - return false + print("> Updating database to version 25 (Binary player items)") + db.query("CREATE TABLE IF NOT EXISTS `player_binary_items` (`player_id` int(11) NOT NULL, `type` int(11) NOT NULL, `items` longblob NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8") + db.query("ALTER TABLE `player_binary_items` ADD UNIQUE KEY `player_id_2` (`player_id`,`type`)") + db.query("ALTER TABLE `player_binary_items` ADD CONSTRAINT `player_binary_items_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE") + return true end diff --git a/data/migrations/25.lua b/data/migrations/25.lua new file mode 100644 index 0000000000..d0ffd9c0cb --- /dev/null +++ b/data/migrations/25.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false +end diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 571163effa..37b79489e7 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -137,6 +137,7 @@ bool ConfigManager::load() boolean[CLASSIC_ATTACK_SPEED] = getGlobalBoolean(L, "classicAttackSpeed", false); boolean[SCRIPTS_CONSOLE_LOGS] = getGlobalBoolean(L, "showScriptsLogInConsole", true); boolean[PLAYER_ITEMS_CACHE] = getGlobalBoolean(L, "playerItemsCache", true); + boolean[BINARY_PLAYER_ITEMS] = getGlobalBoolean(L, "binaryPlayerItems", true); string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); string[SERVER_NAME] = getGlobalString(L, "serverName", ""); diff --git a/src/configmanager.h b/src/configmanager.h index fa21cd02f5..afbee41770 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -43,6 +43,7 @@ class ConfigManager CLASSIC_ATTACK_SPEED, SCRIPTS_CONSOLE_LOGS, PLAYER_ITEMS_CACHE, + BINARY_PLAYER_ITEMS, LAST_BOOLEAN_CONFIG /* this must be the last one */ }; diff --git a/src/const.h b/src/const.h index 85bf986ae0..8006e8a9ba 100644 --- a/src/const.h +++ b/src/const.h @@ -550,6 +550,13 @@ enum ReloadTypes_t : uint8_t { RELOAD_TYPE_WEAPONS, }; +enum BinaryPlayerDataType : uint8_t { + BINARY_TYPE_NONE = 0, + BINARY_TYPE_ITEMS = 1, + BINARY_TYPE_DEPOTITEMS = 2, + BINARY_TYPE_INBOX = 3, +}; + static constexpr int32_t CHANNEL_GUILD = 0x00; static constexpr int32_t CHANNEL_PARTY = 0x01; static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF; diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 7cc502d52e..632a583d4b 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -254,7 +254,12 @@ void IOLoginData::loadPlayerItems(Player* player, Database* db) { ItemMap itemMap; query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if (g_config.getBoolean(ConfigManager::BINARY_PLAYER_ITEMS)) { + query << "SELECT `items` FROM `player_binary_items` WHERE `player_id` = " << player->getGUID() << " AND `type` = " << BINARY_TYPE_ITEMS; + } + else { + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + } if ((result = db->storeQuery(query.str()))) { loadItems(itemMap, result); @@ -282,7 +287,12 @@ void IOLoginData::loadPlayerItems(Player* player, Database* db) { itemMap.clear(); query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if (g_config.getBoolean(ConfigManager::BINARY_PLAYER_ITEMS)) { + query << "SELECT `items` FROM `player_binary_items` WHERE `player_id` = " << player->getGUID() << " AND `type` = " << BINARY_TYPE_DEPOTITEMS; + } + else { + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + } if ((result = db->storeQuery(query.str()))) { loadItems(itemMap, result); @@ -314,7 +324,12 @@ void IOLoginData::loadPlayerItems(Player* player, Database* db) { itemMap.clear(); query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if (g_config.getBoolean(ConfigManager::BINARY_PLAYER_ITEMS)) { + query << "SELECT `items` FROM `player_binary_items` WHERE `player_id` = " << player->getGUID() << " AND `type` = " << BINARY_TYPE_INBOX; + } + else { + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + } if ((result = db->storeQuery(query.str()))) { loadItems(itemMap, result); @@ -575,50 +590,87 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) return true; } -bool IOLoginData::saveItems(uint32_t guid, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) +bool IOLoginData::saveItems(uint32_t guid, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream, BinaryPlayerDataType type /* = BINARY_TYPE_NONE */) { - std::ostringstream ss; + if (g_config.getBoolean(ConfigManager::BINARY_PLAYER_ITEMS)) { + std::list listContainer; + std::ostringstream ss; - using ContainerBlock = std::pair; - std::list queue; + using ContainerBlock = std::pair; + std::list queue; - int32_t runningId = 100; + int32_t runningId = 100; - Database& db = Database::getInstance(); - for (const auto& it : itemList) { - int32_t pid = it.first; - Item* item = it.second; - ++runningId; + PropWriteStream binData; + for (const auto& it : itemList) { + int32_t pid = it.first; + Item* item = it.second; + ++runningId; - propWriteStream.clear(); - item->serializeAttr(propWriteStream); + size_t attributesSize; - size_t attributesSize; - const char* attributes = propWriteStream.getStream(attributesSize); + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + const char* attributes = propWriteStream.getStream(attributesSize); - ss << guid << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); - if (!query_insert.addRow(ss)) { - return false; + binData.write(pid); + binData.write(runningId); + binData.write(item->getID()); + binData.write(item->getSubType()); + binData.writeString(std::string(attributes, attributesSize)); + + if (Container * container = item->getContainer()) { + queue.emplace_back(container, runningId); + } + } + + while (!queue.empty()) { + const ContainerBlock& cb = queue.front(); + Container* container = cb.first; + int32_t parentId = cb.second; + queue.pop_front(); + + for (Item* item : container->getItemList()) { + ++runningId; + if (Container * sub = item->getContainer()) { + queue.emplace_back(sub, runningId); + } + + size_t attributesSize; + + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + const char* attributes = propWriteStream.getStream(attributesSize); + + binData.write(parentId); + binData.write(runningId); + binData.write(item->getID()); + binData.write(item->getSubType()); + binData.writeString(std::string(attributes, attributesSize)); + } } - if (Container* container = item->getContainer()) { - queue.emplace_back(container, runningId); + size_t itemsSize; + const char* items = binData.getStream(itemsSize); + ss << guid << ", " << type << ", " << Database::getInstance().escapeBlob(items, itemsSize); + + if (!query_insert.addRow(ss)) { + return false; } } + else { + std::ostringstream ss; - while (!queue.empty()) { - const ContainerBlock& cb = queue.front(); - Container* container = cb.first; - int32_t parentId = cb.second; - queue.pop_front(); + using ContainerBlock = std::pair; + std::list queue; - for (Item* item : container->getItemList()) { - ++runningId; + int32_t runningId = 100; - Container* subContainer = item->getContainer(); - if (subContainer) { - queue.emplace_back(subContainer, runningId); - } + Database& db = Database::getInstance(); + for (const auto& it : itemList) { + int32_t pid = it.first; + Item* item = it.second; + ++runningId; propWriteStream.clear(); item->serializeAttr(propWriteStream); @@ -626,12 +678,44 @@ bool IOLoginData::saveItems(uint32_t guid, const ItemBlockList& itemList, DBInse size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << guid << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); + ss << guid << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); if (!query_insert.addRow(ss)) { return false; } + + if (Container * container = item->getContainer()) { + queue.emplace_back(container, runningId); + } + } + + while (!queue.empty()) { + const ContainerBlock& cb = queue.front(); + Container* container = cb.first; + int32_t parentId = cb.second; + queue.pop_front(); + + for (Item* item : container->getItemList()) { + ++runningId; + + Container* subContainer = item->getContainer(); + if (subContainer) { + queue.emplace_back(subContainer, runningId); + } + + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + + size_t attributesSize; + const char* attributes = propWriteStream.getStream(attributesSize); + + ss << guid << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); + if (!query_insert.addRow(ss)) { + return false; + } + } } } + return query_insert.execute(); } @@ -641,68 +725,135 @@ bool IOLoginData::savePlayerItems(uint32_t guid, Item** inventory, std::mapgetItemList()) { + itemList.emplace_back(it.first, item); + } + } + + if (!saveItems(guid, itemList, depotQuery, propWriteStream, BINARY_TYPE_DEPOTITEMS)) { + return false; + } + } + //save inbox items + query.str(std::string()); + query << "DELETE FROM `player_binary_items` WHERE `player_id` = " << guid << " AND `type` = " << BINARY_TYPE_INBOX; if (!db->executeQuery(query.str())) { return false; } - DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); + DBInsert inboxQuery("INSERT INTO `player_binary_items` (`player_id`, `type`, `items`) VALUES ", db); itemList.clear(); - for (const auto& it : depotChests) { - DepotChest* depotChest = it.second; - for (Item* item : depotChest->getItemList()) { - itemList.emplace_back(it.first, item); - } + for (Item* item : inbox->getItemList()) { + itemList.emplace_back(0, item); } - if (!saveItems(guid, itemList, depotQuery, propWriteStream)) { + if (!saveItems(guid, itemList, inboxQuery, propWriteStream, BINARY_TYPE_INBOX)) { return false; } } + else { + std::ostringstream query; + PropWriteStream propWriteStream; - //save inbox items - query.str(std::string()); - query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << guid; - if (!db->executeQuery(query.str())) { - return false; - } + query << "DELETE FROM `player_items` WHERE `player_id` = " << guid; + if (!db->executeQuery(query.str())) { + return false; + } - DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); - itemList.clear(); + DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); - for (Item* item : inbox->getItemList()) { - itemList.emplace_back(0, item); - } + ItemBlockList itemList; + for (int32_t slotId = 1; slotId <= 10; ++slotId) { + Item* item = inventory[slotId]; + if (item) { + itemList.emplace_back(slotId, item); + } + } - if (!saveItems(guid, itemList, inboxQuery, propWriteStream)) { - return false; + if (!saveItems(guid, itemList, itemsQuery, propWriteStream)) { + return false; + } + + if (lastDepotId != -1) { + //save depot items + query.str(std::string()); + query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << guid; + + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); + itemList.clear(); + + for (const auto& it : depotChests) { + DepotChest* depotChest = it.second; + for (Item* item : depotChest->getItemList()) { + itemList.emplace_back(it.first, item); + } + } + + if (!saveItems(guid, itemList, depotQuery, propWriteStream)) { + return false; + } + } + + //save inbox items + query.str(std::string()); + query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << guid; + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); + itemList.clear(); + + for (Item* item : inbox->getItemList()) { + itemList.emplace_back(0, item); + } + + if (!saveItems(guid, itemList, inboxQuery, propWriteStream)) { + return false; + } } return true; @@ -962,28 +1113,60 @@ bool IOLoginData::formatPlayerName(std::string& name) void IOLoginData::loadItems(ItemMap& itemMap, DBResult_ptr result) { - do { - uint32_t sid = result->getNumber("sid"); - uint32_t pid = result->getNumber("pid"); - uint16_t type = result->getNumber("itemtype"); - uint16_t count = result->getNumber("count"); - - unsigned long attrSize; - const char* attr = result->getStream("attributes", attrSize); - - PropStream propStream; - propStream.init(attr, attrSize); - - Item* item = Item::CreateItem(type, count); - if (item) { - if (!item->unserializeAttr(propStream)) { - std::cout << "WARNING: Serialize error in IOLoginData::loadItems" << std::endl; + if (g_config.getBoolean(ConfigManager::BINARY_PLAYER_ITEMS)) { + unsigned long itemsSize; + const char* items = result->getStream("items", itemsSize); + PropStream itemsStream; + itemsStream.init(items, itemsSize); + + while (itemsStream.size() >= 14) { + uint32_t pid, sid; + uint16_t type, count; + std::string attr; + itemsStream.read(pid); + itemsStream.read(sid); + itemsStream.read(type); + itemsStream.read(count); + itemsStream.readString(attr); + + PropStream propStream; + propStream.init(attr.c_str(), attr.size()); + + Item* item = Item::CreateItem(type, count); + if (item) { + if (!item->unserializeAttr(propStream)) { + std::cout << "WARNING: Serialize error in IOLoginData::loadItems" << std::endl; + } + + std::pair pair(item, pid); + itemMap[sid] = pair; } - - std::pair pair(item, pid); - itemMap[sid] = pair; } - } while (result->next()); + } + else { + do { + uint32_t sid = result->getNumber("sid"); + uint32_t pid = result->getNumber("pid"); + uint16_t type = result->getNumber("itemtype"); + uint16_t count = result->getNumber("count"); + + unsigned long attrSize; + const char* attr = result->getStream("attributes", attrSize); + + PropStream propStream; + propStream.init(attr, attrSize); + + Item* item = Item::CreateItem(type, count); + if (item) { + if (!item->unserializeAttr(propStream)) { + std::cout << "WARNING: Serialize error in IOLoginData::loadItems" << std::endl; + } + + std::pair pair(item, pid); + itemMap[sid] = pair; + } + } while (result->next()); + } } void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) diff --git a/src/iologindata.h b/src/iologindata.h index 8823bcee05..90a2887c54 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -67,7 +67,7 @@ class IOLoginData static void loadPlayerItems(Player* player, Database* db); static void loadItems(ItemMap& itemMap, DBResult_ptr result); - static bool saveItems(uint32_t guid, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream); + static bool saveItems(uint32_t guid, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream, BinaryPlayerDataType type = BINARY_TYPE_NONE); }; class PlayerCacheData; From 44a83dba66254ee81a918f4585c094cdf8da4f82 Mon Sep 17 00:00:00 2001 From: "Stefan A. Brannfjell" Date: Mon, 9 Sep 2019 20:01:01 +0200 Subject: [PATCH 09/12] Schema migration 25.lua file --- data/migrations/25.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/migrations/25.lua b/data/migrations/25.lua index d0ffd9c0cb..2d55a2e86e 100644 --- a/data/migrations/25.lua +++ b/data/migrations/25.lua @@ -1,3 +1,7 @@ function onUpdateDatabase() - return false + print("> Updating database to version 26 (Binary player items)") + db.query("CREATE TABLE IF NOT EXISTS `player_binary_items` (`player_id` int(11) NOT NULL, `type` int(11) NOT NULL, `items` longblob NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8") + db.query("ALTER TABLE `player_binary_items` ADD UNIQUE KEY `player_id_2` (`player_id`,`type`)") + db.query("ALTER TABLE `player_binary_items` ADD CONSTRAINT `player_binary_items_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE") + return true end From 261360a09f3739a2a1e9c9c1d6a30073aa07be89 Mon Sep 17 00:00:00 2001 From: "Stefan A. Brannfjell" Date: Mon, 9 Sep 2019 20:01:35 +0200 Subject: [PATCH 10/12] Empty 26 file. --- data/migrations/26.lua | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 data/migrations/26.lua diff --git a/data/migrations/26.lua b/data/migrations/26.lua new file mode 100644 index 0000000000..d0ffd9c0cb --- /dev/null +++ b/data/migrations/26.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false +end From 97d806ae4d12e08620b2b700964275aeb078f78e Mon Sep 17 00:00:00 2001 From: "Stefan A. Brannfjell" Date: Mon, 9 Sep 2019 20:02:21 +0200 Subject: [PATCH 11/12] schema.sql db_version bump --- schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema.sql b/schema.sql index 8da345ef24..005786517c 100644 --- a/schema.sql +++ b/schema.sql @@ -339,7 +339,7 @@ CREATE TABLE IF NOT EXISTS `towns` ( UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; -INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '24'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); +INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '25'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); DROP TRIGGER IF EXISTS `ondelete_players`; DROP TRIGGER IF EXISTS `oncreate_guilds`; From 7a94db34693028500766f78552e7a646c3cfe0ae Mon Sep 17 00:00:00 2001 From: gesior Date: Wed, 11 Sep 2019 11:57:18 +0200 Subject: [PATCH 12/12] refs #1150 fix Travis trusty compatibility --- src/iologindata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 632a583d4b..abf5e5224b 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -1121,7 +1121,7 @@ void IOLoginData::loadItems(ItemMap& itemMap, DBResult_ptr result) while (itemsStream.size() >= 14) { uint32_t pid, sid; - uint16_t type, count; + uint16_t type = 0, count = 0; std::string attr; itemsStream.read(pid); itemsStream.read(sid);