From e96e679f07276a5a579e9fec3e29b6b202b3e414 Mon Sep 17 00:00:00 2001 From: Gunz Date: Wed, 8 Nov 2023 14:57:26 +0100 Subject: [PATCH 01/27] Complete zero-blocking Inbox implementation using separate thread and single std::recursive_mutex, TODO: Deliver items using IOInbox::pushDeliveryItems when Inbox is not yet loaded for player --- src/CMakeLists.txt | 2 + src/container.cpp | 11 +- src/database.cpp | 6 +- src/database.h | 12 +- src/game.cpp | 6 + src/house.cpp | 16 ++- src/ioinbox.cpp | 317 ++++++++++++++++++++++++++++++++++++++++++++ src/ioinbox.h | 65 +++++++++ src/iologindata.cpp | 50 +------ src/iologindata.h | 2 +- src/otserv.cpp | 5 + src/player.cpp | 26 +++- src/player.h | 1 + src/signals.cpp | 1 + src/tasks.h | 1 + 15 files changed, 456 insertions(+), 65 deletions(-) create mode 100644 src/ioinbox.cpp create mode 100644 src/ioinbox.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e5bedaa438..d31002a494 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/housetile.cpp ${CMAKE_CURRENT_LIST_DIR}/inbox.cpp ${CMAKE_CURRENT_LIST_DIR}/iologindata.cpp + ${CMAKE_CURRENT_LIST_DIR}/ioinbox.cpp ${CMAKE_CURRENT_LIST_DIR}/iomap.cpp ${CMAKE_CURRENT_LIST_DIR}/iomapserialize.cpp ${CMAKE_CURRENT_LIST_DIR}/iomarket.cpp @@ -111,6 +112,7 @@ set(tfs_HDR ${CMAKE_CURRENT_LIST_DIR}/housetile.h ${CMAKE_CURRENT_LIST_DIR}/inbox.h ${CMAKE_CURRENT_LIST_DIR}/iologindata.h + ${CMAKE_CURRENT_LIST_DIR}/ioinbox.h ${CMAKE_CURRENT_LIST_DIR}/iomap.h ${CMAKE_CURRENT_LIST_DIR}/iomapserialize.h ${CMAKE_CURRENT_LIST_DIR}/iomarket.h diff --git a/src/container.cpp b/src/container.cpp index 9cf461c91d..5d0785891c 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -682,13 +682,22 @@ void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, void Container::internalAddThing(Thing* thing) { internalAddThing(0, thing); } -void Container::internalAddThing(uint32_t, Thing* thing) +void Container::internalAddThing(uint32_t index, Thing* thing) { Item* item = thing->getItem(); if (!item) { return; } + if (index == 0 || itemlist.empty()) { + itemlist.push_front(item); + } else { + size_t pos = std::min(std::max(0, itemlist.size() - 1), index); + auto it = itemlist.begin(); + std::advance(it, pos); + itemlist.insert(it, item); + } + item->setParent(this); itemlist.push_front(item); updateItemWeight(item->getWeight()); diff --git a/src/database.cpp b/src/database.cpp index d7d53a172e..2ba0273532 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -213,14 +213,14 @@ bool DBResult::next() return row; } -DBInsert::DBInsert(std::string query) : query(std::move(query)) { this->length = this->query.length(); } +DBInsert::DBInsert(std::string query, Database& db) : query(std::move(query)), db(db) { this->length = this->query.length(); } 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 > db.getMaxPacketSize() && !execute()) { return false; } @@ -253,7 +253,7 @@ bool DBInsert::execute() } // executes buffer - bool res = Database::getInstance().executeQuery(query + values); + bool res = db.executeQuery(query + values); values.clear(); length = query.length(); return res; diff --git a/src/database.h b/src/database.h index c9a901f615..224752eb83 100644 --- a/src/database.h +++ b/src/database.h @@ -162,13 +162,14 @@ class DBResult class DBInsert { public: - explicit DBInsert(std::string query); + explicit DBInsert(std::string query, Database& db = Database::getInstance()); bool addRow(const std::string& row); bool addRow(std::ostringstream& row); bool execute(); private: std::string query; + Database& db; std::string values; size_t length; }; @@ -176,12 +177,12 @@ class DBInsert class DBTransaction { public: - constexpr DBTransaction() = default; + constexpr DBTransaction(Database& db = Database::getInstance()) : db(db) {}; ~DBTransaction() { if (state == STATE_START) { - Database::getInstance().rollback(); + db.rollback(); } } @@ -192,7 +193,7 @@ class DBTransaction bool begin() { state = STATE_START; - return Database::getInstance().beginTransaction(); + return db.beginTransaction(); } bool commit() @@ -202,10 +203,11 @@ class DBTransaction } state = STATE_COMMIT; - return Database::getInstance().commit(); + return db.commit(); } private: + Database& db; enum TransactionStates_t { STATE_NO_START, diff --git a/src/game.cpp b/src/game.cpp index 6de690c024..4711c129c1 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -16,6 +16,7 @@ #include "globalevent.h" #include "housetile.h" #include "inbox.h" +#include "ioinbox.h" #include "iologindata.h" #include "iomarket.h" #include "items.h" @@ -127,10 +128,15 @@ void Game::setGameState(GameState_t newState) saveGameState(); + g_dispatcherInbox.addTask([] () { + IOInbox::getInstance().flushDeliverItems(); + }); + g_dispatcher.addTask([this]() { shutdown(); }); g_scheduler.stop(); g_databaseTasks.stop(); + g_dispatcherInbox.stop(); g_dispatcher.stop(); break; } diff --git a/src/house.cpp b/src/house.cpp index 42335177f9..e585c852f1 100644 --- a/src/house.cpp +++ b/src/house.cpp @@ -10,6 +10,7 @@ #include "game.h" #include "housetile.h" #include "inbox.h" +#include "ioinbox.h" #include "iologindata.h" #include "pugicast.h" @@ -230,9 +231,18 @@ bool House::transferToDepot(Player* player) const } } - for (Item* item : moveItemList) { - g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), - nullptr, FLAG_NOLIMIT); + if (!player->getInbox()) { + ItemBlockList itemList; + for (Item* item : moveItemList) { + itemList.emplace_back(0, item->clone()); + g_game.internalRemoveItem(item); + } + IOInbox::getInstance().pushDeliveryItems(player->getGUID(), itemList); + } else { + for (Item* item : moveItemList) { + g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), + nullptr, FLAG_NOLIMIT); + } } return true; } diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp new file mode 100644 index 0000000000..bcbcb454ee --- /dev/null +++ b/src/ioinbox.cpp @@ -0,0 +1,317 @@ +// Copyright 2023 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#include "otpch.h" + +#include "ioinbox.h" + +#include "game.h" +#include "tasks.h" + +extern Game g_game; + +IOInbox::IOInbox() +{ + db.connect(); +} + +void IOInbox::savePlayer(Player* player) +{ + if (Inbox* inbox = player->getInbox()) { + saveInbox(player->getGUID(), inbox, player); + g_dispatcherInbox.addTask([this, guid = player->getGUID()]() { + asyncSave(guid); + }); + } +} + +void IOInbox::saveInbox(uint32_t guid, Inbox* inbox, Player* player /* = nullptr */) +{ + // dispatcher thread + ItemBlockList itemList; + for (Item* item : inbox->getItemList()) { + itemList.emplace_back(0, item); + } + DBEntryListPtr inboxPtr = saveItems(player, itemList); + std::unique_lock lock(taskLock); + inboxCache[guid] = std::make_shared(false, inboxPtr); +} + +void IOInbox::loadInboxLogin(uint32_t guid) +{ + if (loading.insert(guid).second) { + g_dispatcherInbox.addTask([this, guid]() { asyncLoad(guid); }); + } +} + +Inbox* IOInbox::loadInbox(uint32_t guid) +{ + // dispatcherInbox thread + std::unique_lock lock(taskLock); + + DBEntryListPtr inboxPtr; + auto it = inboxCache.find(guid); + if (it != inboxCache.end()) { + inboxPtr = it->second->items; + lock.unlock(); + } else { + lock.unlock(); + inboxPtr = std::make_shared(); + DBResult_ptr result; + if ((result = db.storeQuery(fmt::format( + "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", + guid)))) { + do { + inboxPtr->push_back(DBEntry{result->getNumber("pid"), result->getNumber("sid"), result->getNumber("itemtype"), result->getNumber("count"), std::string(result->getString("attributes"))}); + } while (result->next()); + } + } + + ItemMap itemMap; + for (auto& itemDBEntry : *inboxPtr) { + PropStream propStream; + propStream.init(itemDBEntry.attributes.data(), itemDBEntry.attributes.size()); + + Item* item = Item::CreateItem(itemDBEntry.itemtype, itemDBEntry.count); + if (item) { + if (!item->unserializeAttr(propStream)) { + std::cout << "WARNING: Serialize error in IOInbox::loadInbox" << std::endl; + } + + std::pair pair(item, itemDBEntry.pid); + itemMap[itemDBEntry.sid] = pair; + } + } + + Inbox* inbox = new Inbox(ITEM_INBOX); + inbox->incrementReferenceCounter(); + 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) { + inbox->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); + } + } + } + + return inbox; +} + +DBEntryListPtr IOInbox::saveItems(Player* player, const ItemBlockList& itemList) +{ + DBEntryListPtr list = std::make_shared(); + using ContainerBlock = std::pair; + std::vector containers; + containers.reserve(32); + + int32_t runningId = 100; + std::map openContainers; + if (player) { + openContainers = player->getOpenContainers(); + } + + PropWriteStream propWriteStream; + for (const auto& it : itemList) { + int32_t pid = it.first; + Item* item = it.second; + ++runningId; + + if (Container* container = item->getContainer()) { + if (player) { + if (container->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)) { + container->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, 0); + } + + if (!openContainers.empty()) { + for (const auto& its : openContainers) { + auto openContainer = its.second; + auto opcontainer = openContainer.container; + + if (opcontainer == container) { + container->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, static_cast(its.first) + 1); + break; + } + } + } + } + + containers.emplace_back(container, runningId); + } + + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + + list->emplace_back(DBEntry{pid, runningId, item->getID(), item->getSubType(), std::string(propWriteStream.getStream())}); + } + + for (size_t i = 0; i < containers.size(); i++) { + const ContainerBlock& cb = containers[i]; + Container* container = cb.first; + int32_t parentId = cb.second; + + for (Item* item : container->getItemList()) { + ++runningId; + + Container* subContainer = item->getContainer(); + if (subContainer) { + containers.emplace_back(subContainer, runningId); + + if (player) { + if (subContainer->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)) { + subContainer->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, 0); + } + + if (!openContainers.empty()) { + for (const auto& it : openContainers) { + auto openContainer = it.second; + auto opcontainer = openContainer.container; + + if (opcontainer == subContainer) { + subContainer->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, it.first + 1); + break; + } + } + } + } + } + + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + + list->emplace_back(DBEntry{parentId, runningId, item->getID(), item->getSubType(), std::string(propWriteStream.getStream())}); + } + } + return list; +} + +void IOInbox::asyncSave(uint32_t guid) +{ + // dispatcherInbox thread + std::unique_lock lock(taskLock); + auto it = inboxCache.find(guid); + if (it == inboxCache.end() || it->second->saved) { + return; + } + PlayerDBEntryPtr playerDBEntry = it->second; + lock.unlock(); + + // disable foreign key checks to prevent locking of the players table during insert + // we do not except that the player is deleted while saving the inbox + db.executeQuery("SET foreign_key_checks = 0;"); + + std::ostringstream query; + DBTransaction transaction(db); + if (transaction.begin()) { + if (db.executeQuery(fmt::format("DELETE FROM `player_inboxitems` WHERE `player_id` = {:d}", guid))) { + DBInsert query_insert("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); + for(auto& itemEntry : *playerDBEntry->items) { + if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", guid, itemEntry.pid, itemEntry.sid, + itemEntry.itemtype, itemEntry.count, + db.escapeString(itemEntry.attributes)))) { + return; + } + } + if (!query_insert.execute()) { + return; + } + } + if (transaction.commit()) { + playerDBEntry->saved = true; + } + } +} + +void IOInbox::asyncLoad(uint32_t guid) +{ + // dispatcherInbox thread + Inbox* inbox = loadInbox(guid); + if (deliverItems(guid, inbox)) { + saveInbox(guid, inbox); + asyncSave(guid); + } + g_dispatcher.addTask([this, guid, inbox]() { + assignInbox(guid, inbox); + }); +} + +void IOInbox::assignInbox(uint32_t guid, Inbox* inbox) +{ + // dispatcher thread + std::unique_lock lock(taskLock); + loading.erase(guid); + lock.unlock(); + + Player* player = g_game.getPlayerByGUID(guid); + if (!player) { + g_dispatcherInbox.addTask([inbox]() { + delete inbox; + }); + return; + } + deliverItems(guid, inbox); + player->setInbox(inbox); +} + +bool IOInbox::deliverItems(uint32_t guid, Inbox* inbox) +{ + // any thread + std::unique_lock lock(taskLock); + auto it = deliverItemsMap.find(guid); + if (it != deliverItemsMap.end()) { + for (auto& itemList : it->second) { + for (auto& itemBlock : itemList) { + inbox->internalAddThing(itemBlock.second); + } + } + deliverItemsMap.erase(it); + return true; + } + return false; +} + +void IOInbox::asyncDeliverItems(uint32_t guid) +{ + // dispatcherInbox thread + std::unique_lock lock(taskLock); + if (loading.find(guid) == loading.end() && deliverItemsMap.find(guid) != deliverItemsMap.end()) { + lock.unlock(); + Inbox* inbox = loadInbox(guid); + deliverItems(guid, inbox); + saveInbox(guid, inbox); + asyncSave(guid); + delete inbox; + } +} + +void IOInbox::pushDeliveryItems(uint32_t guid, ItemBlockList& itemList) +{ + // dispatcher thread + std::unique_lock lock(taskLock); + deliverItemsMap[guid].push_back(std::move(itemList)); + if (loading.find(guid) == loading.end()) { + g_dispatcherInbox.addTask([this, guid]() { + asyncDeliverItems(guid); + }); + } +} + +void IOInbox::flushDeliverItems() +{ + // dispatcherInbox thread - should be called as last task + while (!deliverItemsMap.empty()) { + auto it = deliverItemsMap.begin(); + asyncDeliverItems(it->first); + } +} diff --git a/src/ioinbox.h b/src/ioinbox.h new file mode 100644 index 0000000000..70b3f33e2f --- /dev/null +++ b/src/ioinbox.h @@ -0,0 +1,65 @@ +// Copyright 2023 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + + +#ifndef FS_IOINBOX_H +#define FS_IOINBOX_H + +#include "database.h" +#include "inbox.h" +#include "iologindata.h" +#include "player.h" + +struct DBEntry { + int32_t pid; + int32_t sid; + uint16_t itemtype; + uint16_t count; + std::string attributes; +}; + +using DBEntryList = std::list; +using DBEntryListPtr = std::shared_ptr; + +struct PlayerDBEntry { + bool saved = false; + DBEntryListPtr items; +}; + +using PlayerDBEntryPtr = std::shared_ptr; + +class IOInbox +{ +public: + static IOInbox& getInstance() + { + static IOInbox instance; + return instance; + } + + void savePlayer(Player* player); + void loadInboxLogin(uint32_t guid); + void pushDeliveryItems(uint32_t guid, ItemBlockList& itemList); + void flushDeliverItems(); + +private: + IOInbox(); + + static DBEntryListPtr saveItems(Player* player, const ItemBlockList& itemList); + + void saveInbox(uint32_t guid, Inbox* inbox, Player* player = nullptr); + Inbox* loadInbox(uint32_t guid); + void asyncSave(uint32_t guid); + void asyncLoad(uint32_t guid); + void assignInbox(uint32_t guid, Inbox* inbox); + bool deliverItems(uint32_t guid, Inbox* inbox); + void asyncDeliverItems(uint32_t guid); + + Database db; + std::recursive_mutex taskLock; + std::map inboxCache; + std::map> deliverItemsMap; + std::set loading; +}; + +#endif // FS_IOINBOX_H diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 7fb6a52ed2..dd76ff31e0 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -10,6 +10,7 @@ #include "depotchest.h" #include "game.h" #include "inbox.h" +#include "ioinbox.h" #include "storeinbox.h" extern ConfigManager g_config; @@ -542,36 +543,6 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } } - // load inbox items - itemMap.clear(); - - if ((result = db.storeQuery(fmt::format( - "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", - player->getGUID())))) { - 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); - } - } - } - } - // load store inbox items itemMap.clear(); @@ -857,6 +828,9 @@ bool IOLoginData::savePlayer(Player* player) return false; } + // save inbox + IOInbox::getInstance().savePlayer(player); + // item saving if (!db.executeQuery(fmt::format("DELETE FROM `player_items` WHERE `player_id` = {:d}", player->getGUID()))) { return false; @@ -896,22 +870,6 @@ bool IOLoginData::savePlayer(Player* player) return false; } - // save inbox items - if (!db.executeQuery(fmt::format("DELETE FROM `player_inboxitems` WHERE `player_id` = {:d}", player->getGUID()))) { - 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 (!saveItems(player, itemList, inboxQuery, propWriteStream)) { - return false; - } // save store inbox items if (!db.executeQuery( diff --git a/src/iologindata.h b/src/iologindata.h index 1997d2bb85..b0a7ccc3c3 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -13,6 +13,7 @@ class PropWriteStream; struct VIPEntry; using ItemBlockList = std::list>; +using ItemMap = std::map>; class IOLoginData { @@ -53,7 +54,6 @@ class IOLoginData static void updatePremiumTime(uint32_t accountId, time_t endTime); 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, diff --git a/src/otserv.cpp b/src/otserv.cpp index 99851898e2..4c00fe19ff 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -27,6 +27,7 @@ DatabaseTasks g_databaseTasks; Dispatcher g_dispatcher; +Dispatcher g_dispatcherInbox; Scheduler g_scheduler; Game g_game; @@ -70,6 +71,7 @@ int main(int argc, char* argv[]) ServiceManager serviceManager; g_dispatcher.start(); + g_scheduler.start(); g_dispatcher.addTask([=, services = &serviceManager]() { mainLoader(argc, argv, services); }); @@ -84,11 +86,13 @@ int main(int argc, char* argv[]) std::cout << ">> No services running. The server is NOT online." << std::endl; g_scheduler.shutdown(); g_databaseTasks.shutdown(); + g_dispatcherInbox.shutdown(); g_dispatcher.shutdown(); } g_scheduler.join(); g_databaseTasks.join(); + g_dispatcherInbox.join(); g_dispatcher.join(); return 0; } @@ -207,6 +211,7 @@ void mainLoader(int, char*[], ServiceManager* services) return; } g_databaseTasks.start(); + g_dispatcherInbox.start(); DatabaseManager::updateDatabase(); diff --git a/src/player.cpp b/src/player.cpp index d82125e8ac..8786f4acc4 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -14,6 +14,7 @@ #include "events.h" #include "game.h" #include "inbox.h" +#include "ioinbox.h" #include "iologindata.h" #include "monster.h" #include "movement.h" @@ -45,11 +46,9 @@ Player::Player(ProtocolGame_ptr p) : lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)), - inbox(new Inbox(ITEM_INBOX)), + inbox(nullptr), storeInbox(new StoreInbox(ITEM_STORE_INBOX)) { - inbox->incrementReferenceCounter(); - storeInbox->setParent(this); storeInbox->incrementReferenceCounter(); } @@ -63,11 +62,13 @@ Player::~Player() } } - if (depotLocker) { + if (depotLocker && inbox) { depotLocker->removeInbox(inbox); } - inbox->decrementReferenceCounter(); + if (inbox) { + inbox->decrementReferenceCounter(); + } storeInbox->setParent(nullptr); storeInbox->decrementReferenceCounter(); @@ -851,7 +852,9 @@ DepotLocker& Player::getDepotLocker() if (!depotLocker) { depotLocker = std::make_shared(ITEM_LOCKER); depotLocker->internalAddThing(Item::CreateItem(ITEM_MARKET)); - depotLocker->internalAddThing(inbox); + if (inbox) { + depotLocker->internalAddThing(inbox); + } DepotChest* depotChest = new DepotChest(ITEM_DEPOT, false); // adding in reverse to align them from first to last @@ -3666,7 +3669,9 @@ void Player::onPlacedCreature() // scripting event - onLogin if (!g_creatureEvents->playerLogin(this)) { kickPlayer(true); + return; } + IOInbox::getInstance().loadInboxLogin(guid); } void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points) @@ -4136,6 +4141,15 @@ bool Player::isInWarList(uint32_t guildId) const return std::find(guildWarVector.begin(), guildWarVector.end(), guildId) != guildWarVector.end(); } +void Player::setInbox(Inbox* _inbox) +{ + inbox = _inbox; + if (depotLocker) { + depotLocker->internalAddThing(2, inbox); + onSendContainer(depotLocker.get()); + } +} + bool Player::isPremium() const { if (g_config.getBoolean(ConfigManager::FREE_PREMIUM) || hasFlag(PlayerFlag_IsAlwaysPremium)) { diff --git a/src/player.h b/src/player.h index f13577da03..6e9c002ef4 100644 --- a/src/player.h +++ b/src/player.h @@ -189,6 +189,7 @@ class Player final : public Creature, public Cylinder void setLastWalkthroughAttempt(int64_t walkthroughAttempt) { lastWalkthroughAttempt = walkthroughAttempt; } void setLastWalkthroughPosition(Position walkthroughPosition) { lastWalkthroughPosition = walkthroughPosition; } + void setInbox(Inbox* inbox); Inbox* getInbox() const { return inbox; } StoreInbox* getStoreInbox() const { return storeInbox; } diff --git a/src/signals.cpp b/src/signals.cpp index 754b73a108..81b26a2626 100644 --- a/src/signals.cpp +++ b/src/signals.cpp @@ -157,6 +157,7 @@ void dispatchSignalHandler(int signal) // hold the thread until other threads end g_scheduler.join(); g_databaseTasks.join(); + g_databaseInbox.join(); g_dispatcher.join(); break; #endif diff --git a/src/tasks.h b/src/tasks.h index 1f39aa5519..e4a575d779 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -68,5 +68,6 @@ class Dispatcher : public ThreadHolder }; extern Dispatcher g_dispatcher; +extern Dispatcher g_dispatcherInbox; #endif // FS_TASKS_H From e799c78ac4e3e72a9965d5171207357ed5c2134d Mon Sep 17 00:00:00 2001 From: Gunz Date: Wed, 8 Nov 2023 15:18:25 +0100 Subject: [PATCH 02/27] don't include inbox in market when it's yet been loaded --- src/game.cpp | 4 +++- src/protocolgame.cpp | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 4711c129c1..23812f51cc 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5701,7 +5701,9 @@ void Game::parsePlayerNetworkMessage(uint32_t playerId, uint8_t recvByte, Networ std::vector Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, const Player& player) { uint16_t count = 0; - std::list containers{player.getInbox()}; + if (Inbox* inbox = player.getInbox()) { + std::list containers{inbox}; + } for (const auto& chest : player.depotChests) { if (!chest.second->empty()) { diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index e3951b65f2..99582544bf 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -2148,7 +2148,10 @@ void ProtocolGame::sendMarketEnter() player->setInMarket(true); std::map depotItems; - std::forward_list containerList{player->getInbox()}; + std::forward_list containerList; + if (Inbox* inbox = player->getInbox()) { + containerList.push_front(inbox); + } for (const auto& chest : player->depotChests) { if (!chest.second->empty()) { From a73866b5288aadc3918630d9234f000a77e638e1 Mon Sep 17 00:00:00 2001 From: Gunz Date: Wed, 8 Nov 2023 15:31:07 +0100 Subject: [PATCH 03/27] fix compiling error --- src/game.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/game.cpp b/src/game.cpp index 23812f51cc..fe76f364cd 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5701,8 +5701,9 @@ void Game::parsePlayerNetworkMessage(uint32_t playerId, uint8_t recvByte, Networ std::vector Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, const Player& player) { uint16_t count = 0; + std::list containers; if (Inbox* inbox = player.getInbox()) { - std::list containers{inbox}; + containers.push_front(inbox); } for (const auto& chest : player.depotChests) { From b9b5841da19657039d69efc708297c02af60799a Mon Sep 17 00:00:00 2001 From: Gunz Date: Wed, 8 Nov 2023 15:40:20 +0100 Subject: [PATCH 04/27] fix some of the clang formatting --- src/game.cpp | 4 +--- src/ioinbox.cpp | 24 ++++++------------------ src/iologindata.cpp | 1 - 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index fe76f364cd..f286676119 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -128,9 +128,7 @@ void Game::setGameState(GameState_t newState) saveGameState(); - g_dispatcherInbox.addTask([] () { - IOInbox::getInstance().flushDeliverItems(); - }); + g_dispatcherInbox.addTask([]() { IOInbox::getInstance().flushDeliverItems(); }); g_dispatcher.addTask([this]() { shutdown(); }); diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index bcbcb454ee..e7cc2f4a46 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -10,18 +10,13 @@ extern Game g_game; -IOInbox::IOInbox() -{ - db.connect(); -} +IOInbox::IOInbox() { db.connect(); } void IOInbox::savePlayer(Player* player) { if (Inbox* inbox = player->getInbox()) { saveInbox(player->getGUID(), inbox, player); - g_dispatcherInbox.addTask([this, guid = player->getGUID()]() { - asyncSave(guid); - }); + g_dispatcherInbox.addTask([this, guid = player->getGUID()]() { asyncSave(guid); }); } } @@ -59,8 +54,7 @@ Inbox* IOInbox::loadInbox(uint32_t guid) inboxPtr = std::make_shared(); DBResult_ptr result; if ((result = db.storeQuery(fmt::format( - "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", - guid)))) { + "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", guid)))) { do { inboxPtr->push_back(DBEntry{result->getNumber("pid"), result->getNumber("sid"), result->getNumber("itemtype"), result->getNumber("count"), std::string(result->getString("attributes"))}); } while (result->next()); @@ -241,9 +235,7 @@ void IOInbox::asyncLoad(uint32_t guid) saveInbox(guid, inbox); asyncSave(guid); } - g_dispatcher.addTask([this, guid, inbox]() { - assignInbox(guid, inbox); - }); + g_dispatcher.addTask([this, guid, inbox]() { assignInbox(guid, inbox); }); } void IOInbox::assignInbox(uint32_t guid, Inbox* inbox) @@ -255,9 +247,7 @@ void IOInbox::assignInbox(uint32_t guid, Inbox* inbox) Player* player = g_game.getPlayerByGUID(guid); if (!player) { - g_dispatcherInbox.addTask([inbox]() { - delete inbox; - }); + g_dispatcherInbox.addTask([inbox]() { delete inbox; }); return; } deliverItems(guid, inbox); @@ -301,9 +291,7 @@ void IOInbox::pushDeliveryItems(uint32_t guid, ItemBlockList& itemList) std::unique_lock lock(taskLock); deliverItemsMap[guid].push_back(std::move(itemList)); if (loading.find(guid) == loading.end()) { - g_dispatcherInbox.addTask([this, guid]() { - asyncDeliverItems(guid); - }); + g_dispatcherInbox.addTask([this, guid]() { asyncDeliverItems(guid); }); } } diff --git a/src/iologindata.cpp b/src/iologindata.cpp index dd76ff31e0..919504da05 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -870,7 +870,6 @@ bool IOLoginData::savePlayer(Player* player) return false; } - // save store inbox items if (!db.executeQuery( fmt::format("DELETE FROM `player_storeinboxitems` WHERE `player_id` = {:d}", player->getGUID()))) { From b5f725fce0ce7bd7860fb7ba5ca62f296d6c075a Mon Sep 17 00:00:00 2001 From: Gunz Date: Wed, 8 Nov 2023 15:59:52 +0100 Subject: [PATCH 05/27] fix more clang formations --- src/database.cpp | 5 ++++- src/database.h | 2 +- src/ioinbox.cpp | 24 ++++++++++++++++-------- src/ioinbox.h | 7 ++++--- src/iologindata.h | 1 - 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/database.cpp b/src/database.cpp index 2ba0273532..b2a14e7813 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -213,7 +213,10 @@ bool DBResult::next() return row; } -DBInsert::DBInsert(std::string query, Database& db) : query(std::move(query)), db(db) { this->length = this->query.length(); } +DBInsert::DBInsert(std::string query, Database& db) : query(std::move(query)), db(db) +{ + this->length = this->query.length(); +} bool DBInsert::addRow(const std::string& row) { diff --git a/src/database.h b/src/database.h index 224752eb83..6c611d6dfb 100644 --- a/src/database.h +++ b/src/database.h @@ -177,7 +177,7 @@ class DBInsert class DBTransaction { public: - constexpr DBTransaction(Database& db = Database::getInstance()) : db(db) {}; + constexpr DBTransaction(Database& db = Database::getInstance()) : db(db){}; ~DBTransaction() { diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index e7cc2f4a46..5b3c77a1f7 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -54,9 +54,13 @@ Inbox* IOInbox::loadInbox(uint32_t guid) inboxPtr = std::make_shared(); DBResult_ptr result; if ((result = db.storeQuery(fmt::format( - "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", guid)))) { + "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", + guid)))) { do { - inboxPtr->push_back(DBEntry{result->getNumber("pid"), result->getNumber("sid"), result->getNumber("itemtype"), result->getNumber("count"), std::string(result->getString("attributes"))}); + inboxPtr->push_back(DBEntry{result->getNumber("pid"), result->getNumber("sid"), + result->getNumber("itemtype"), + result->getNumber("count"), + std::string(result->getString("attributes"))}); } while (result->next()); } } @@ -147,7 +151,8 @@ DBEntryListPtr IOInbox::saveItems(Player* player, const ItemBlockList& itemList) propWriteStream.clear(); item->serializeAttr(propWriteStream); - list->emplace_back(DBEntry{pid, runningId, item->getID(), item->getSubType(), std::string(propWriteStream.getStream())}); + list->emplace_back( + DBEntry{pid, runningId, item->getID(), item->getSubType(), std::string(propWriteStream.getStream())}); } for (size_t i = 0; i < containers.size(); i++) { @@ -184,7 +189,8 @@ DBEntryListPtr IOInbox::saveItems(Player* player, const ItemBlockList& itemList) propWriteStream.clear(); item->serializeAttr(propWriteStream); - list->emplace_back(DBEntry{parentId, runningId, item->getID(), item->getSubType(), std::string(propWriteStream.getStream())}); + list->emplace_back(DBEntry{parentId, runningId, item->getID(), item->getSubType(), + std::string(propWriteStream.getStream())}); } } return list; @@ -209,10 +215,12 @@ void IOInbox::asyncSave(uint32_t guid) DBTransaction transaction(db); if (transaction.begin()) { if (db.executeQuery(fmt::format("DELETE FROM `player_inboxitems` WHERE `player_id` = {:d}", guid))) { - DBInsert query_insert("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", db); - for(auto& itemEntry : *playerDBEntry->items) { - if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", guid, itemEntry.pid, itemEntry.sid, - itemEntry.itemtype, itemEntry.count, + DBInsert query_insert( + "INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ", + db); + for (auto& itemEntry : *playerDBEntry->items) { + if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", guid, itemEntry.pid, + itemEntry.sid, itemEntry.itemtype, itemEntry.count, db.escapeString(itemEntry.attributes)))) { return; } diff --git a/src/ioinbox.h b/src/ioinbox.h index 70b3f33e2f..036be687b0 100644 --- a/src/ioinbox.h +++ b/src/ioinbox.h @@ -1,7 +1,6 @@ // Copyright 2023 The Forgotten Server Authors. All rights reserved. // Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. - #ifndef FS_IOINBOX_H #define FS_IOINBOX_H @@ -10,7 +9,8 @@ #include "iologindata.h" #include "player.h" -struct DBEntry { +struct DBEntry +{ int32_t pid; int32_t sid; uint16_t itemtype; @@ -21,7 +21,8 @@ struct DBEntry { using DBEntryList = std::list; using DBEntryListPtr = std::shared_ptr; -struct PlayerDBEntry { +struct PlayerDBEntry +{ bool saved = false; DBEntryListPtr items; }; diff --git a/src/iologindata.h b/src/iologindata.h index b0a7ccc3c3..202986831f 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -54,7 +54,6 @@ class IOLoginData static void updatePremiumTime(uint32_t accountId, time_t endTime); private: - static void loadItems(ItemMap& itemMap, DBResult_ptr result); static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream); From 9eb9667e10af1b2fca8dd7e219134bc164fe61be Mon Sep 17 00:00:00 2001 From: gunz Date: Wed, 8 Nov 2023 16:47:01 +0100 Subject: [PATCH 06/27] fix PlayerDBEntry initialization --- src/ioinbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index 5b3c77a1f7..ca23697849 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -29,7 +29,7 @@ void IOInbox::saveInbox(uint32_t guid, Inbox* inbox, Player* player /* = nullptr } DBEntryListPtr inboxPtr = saveItems(player, itemList); std::unique_lock lock(taskLock); - inboxCache[guid] = std::make_shared(false, inboxPtr); + inboxCache[guid] = std::make_shared(PlayerDBEntry{false, inboxPtr}); } void IOInbox::loadInboxLogin(uint32_t guid) From 39db25ff4ff964990242fb2b7167b8c40f67bddf Mon Sep 17 00:00:00 2001 From: gunz Date: Wed, 8 Nov 2023 17:11:39 +0100 Subject: [PATCH 07/27] naming bugfix for win32 implementation --- src/signals.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/signals.cpp b/src/signals.cpp index 81b26a2626..f2bf6492f1 100644 --- a/src/signals.cpp +++ b/src/signals.cpp @@ -157,7 +157,7 @@ void dispatchSignalHandler(int signal) // hold the thread until other threads end g_scheduler.join(); g_databaseTasks.join(); - g_databaseInbox.join(); + g_dispatcherInbox.join(); g_dispatcher.join(); break; #endif From 59a8fcebe3ff67414a77a660222d0d0ee0da441c Mon Sep 17 00:00:00 2001 From: gunz Date: Wed, 8 Nov 2023 17:13:07 +0100 Subject: [PATCH 08/27] naming bugfix for win32 implementation --- src/signals.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/signals.cpp b/src/signals.cpp index f2bf6492f1..fadc3e6cc2 100644 --- a/src/signals.cpp +++ b/src/signals.cpp @@ -26,6 +26,7 @@ extern Scheduler g_scheduler; extern DatabaseTasks g_databaseTasks; +extern Dispatcher g_dispatcherInbox; extern Dispatcher g_dispatcher; extern ConfigManager g_config; From 5b17a28abec9bae9b3e7218baebc040138d240d8 Mon Sep 17 00:00:00 2001 From: gunz Date: Wed, 8 Nov 2023 21:00:09 +0100 Subject: [PATCH 09/27] send to offline player's inbox using IOInbox::pushDeliveryItems --- src/game.cpp | 155 ++++++++++++++++++++++++++++++++++------------- src/house.cpp | 8 ++- src/iomarket.cpp | 54 ++++++++++++----- src/mailbox.cpp | 19 +++--- 4 files changed, 172 insertions(+), 64 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index f286676119..63b55768d1 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5460,15 +5460,28 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 if (it.stackable) { uint16_t tmpAmount = offer.amount; - while (tmpAmount > 0) { - int32_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; + if (!player->getInbox()) { + ItemBlockList inboxDelivery; + while (tmpAmount > 0) { + uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (item) { + inboxDelivery.emplace_back(0, item); + } + tmpAmount -= stackCount; + } + IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); + } else { + while (tmpAmount > 0) { + uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { + delete item; + break; + } + tmpAmount -= stackCount; } - - tmpAmount -= stackCount; } } else { int32_t subType; @@ -5478,11 +5491,23 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 subType = -1; } - for (uint16_t i = 0; i < offer.amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; + if (!player->getInbox()) { + ItemBlockList inboxDelivery; + for (uint16_t i = 0; i < offer.amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (item) { + inboxDelivery.emplace_back(0, item); + } + } + IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); + } else { + for (uint16_t i = 0; i < offer.amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { + delete item; + break; + } } } } @@ -5568,16 +5593,28 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 if (it.stackable) { uint16_t tmpAmount = amount; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; + if (!buyerPlayer->getInbox()) { + ItemBlockList inboxDelivery; + while (tmpAmount > 0) { + uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (item) { + inboxDelivery.emplace_back(0, item); + } + tmpAmount -= stackCount; + } + IOInbox::getInstance().pushDeliveryItems(buyerPlayer->getGUID(), inboxDelivery); + } else { + while (tmpAmount > 0) { + uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { + delete item; + break; + } + tmpAmount -= stackCount; } - - tmpAmount -= stackCount; } } else { int32_t subType; @@ -5587,12 +5624,23 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 subType = -1; } - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; + if (!buyerPlayer->getInbox()) { + ItemBlockList inboxDelivery; + for (uint16_t i = 0; i < offer.amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (item) { + inboxDelivery.emplace_back(0, item); + } + } + IOInbox::getInstance().pushDeliveryItems(buyerPlayer->getGUID(), inboxDelivery); + } else { + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { + delete item; + break; + } } } } @@ -5615,15 +5663,28 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 if (it.stackable) { uint16_t tmpAmount = amount; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; + if (!player->getInbox()) { + ItemBlockList inboxDelivery; + while (tmpAmount > 0) { + uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (item) { + inboxDelivery.emplace_back(0, item); + } + tmpAmount -= stackCount; + } + IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); + } else { + while (tmpAmount > 0) { + uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { + delete item; + break; + } + tmpAmount -= stackCount; } - - tmpAmount -= stackCount; } } else { int32_t subType; @@ -5633,11 +5694,23 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 subType = -1; } - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; + if (!player->getInbox()) { + ItemBlockList inboxDelivery; + for (uint16_t i = 0; i < offer.amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (item) { + inboxDelivery.emplace_back(0, item); + } + } + IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); + } else { + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { + delete item; + break; + } } } } diff --git a/src/house.cpp b/src/house.cpp index e585c852f1..2fbaeee548 100644 --- a/src/house.cpp +++ b/src/house.cpp @@ -705,7 +705,13 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const letter->setText(fmt::format( "Warning! \nThe {:s} rent of {:d} gold for your house \"{:s}\" is payable. Have it within {:d} days or you will lose this house.", period, house->getRent(), house->getName(), daysLeft)); - g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (!player.getInbox()) { + ItemBlockList inboxDelivery; + inboxDelivery.emplace_back(0, letter); + IOInbox::getInstance().pushDeliveryItems(player.getGUID(), inboxDelivery); + } else { + g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); + } house->setPayRentWarnings(house->getPayRentWarnings() + 1); } else { house->setOwner(0, true, &player); diff --git a/src/iomarket.cpp b/src/iomarket.cpp index c2116aff8d..d086ba5473 100644 --- a/src/iomarket.cpp +++ b/src/iomarket.cpp @@ -9,6 +9,7 @@ #include "databasetasks.h" #include "game.h" #include "inbox.h" +#include "ioinbox.h" #include "iologindata.h" #include "scheduler.h" @@ -130,16 +131,28 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) if (itemType.stackable) { uint16_t tmpAmount = amount; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(itemType.id, stackCount); - if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; + if (!player->getInbox()) { + ItemBlockList inboxDelivery; + while (tmpAmount > 0) { + uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); + Item* item = Item::CreateItem(itemType.id, stackCount); + if (item) { + inboxDelivery.emplace_back(0, item); + } + tmpAmount -= stackCount; + } + IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); + } else { + while (tmpAmount > 0) { + uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); + Item* item = Item::CreateItem(itemType.id, stackCount); + if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { + delete item; + break; + } + tmpAmount -= stackCount; } - - tmpAmount -= stackCount; } } else { int32_t subType; @@ -149,12 +162,23 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) subType = -1; } - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(itemType.id, subType); - if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; + if (!player->getInbox()) { + ItemBlockList inboxDelivery; + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(itemType.id, subType); + if (item) { + inboxDelivery.emplace_back(0, item); + } + } + IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); + } else { + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(itemType.id, subType); + if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { + delete item; + break; + } } } } diff --git a/src/mailbox.cpp b/src/mailbox.cpp index dd10e40ab7..b84af7fe1e 100644 --- a/src/mailbox.cpp +++ b/src/mailbox.cpp @@ -7,6 +7,7 @@ #include "game.h" #include "inbox.h" +#include "ioinbox.h" #include "iologindata.h" extern Game g_game; @@ -81,7 +82,7 @@ bool Mailbox::sendItem(Item* item) const } Player* player = g_game.getPlayerByName(receiver); - if (player) { + if (player && player->getInbox()) { if (g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { g_game.transformItem(item, item->getID() + 1); @@ -89,16 +90,20 @@ bool Mailbox::sendItem(Item* item) const return true; } } else { - Player tmpPlayer(nullptr); - if (!IOLoginData::loadPlayerByName(&tmpPlayer, receiver)) { + uint32_t receiverGuid = player ? player->getGUID() : IOLoginData::getGuidByName(receiver); + if (!receiverGuid) { return false; } - if (g_game.internalMoveItem(item->getParent(), tmpPlayer.getInbox(), INDEX_WHEREEVER, item, - item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { - g_game.transformItem(item, item->getID() + 1); - IOLoginData::savePlayer(&tmpPlayer); + ItemBlockList inboxDelivery; + Item* cloneItem = item->clone(); + if (g_game.internalRemoveItem(item) == RETURNVALUE_NOERROR) { + cloneItem->setID(cloneItem->getID() + 1); + inboxDelivery.emplace_back(0, cloneItem); + IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); return true; + } else { + delete cloneItem; } } return false; From ae2488347507c1498895c6c6abbc94a3b3215935 Mon Sep 17 00:00:00 2001 From: gunz Date: Wed, 8 Nov 2023 21:21:15 +0100 Subject: [PATCH 10/27] clear loading set before making flush --- src/ioinbox.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index ca23697849..3050c159cf 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -305,7 +305,9 @@ void IOInbox::pushDeliveryItems(uint32_t guid, ItemBlockList& itemList) void IOInbox::flushDeliverItems() { - // dispatcherInbox thread - should be called as last task + // dispatcherInbox thread - only called as ultimate task on shutdown + std::lock_guard lock(taskLock); + loading.clear(); while (!deliverItemsMap.empty()) { auto it = deliverItemsMap.begin(); asyncDeliverItems(it->first); From aa16531615c850d06325ab358884782f9d5612b2 Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 06:58:46 +0100 Subject: [PATCH 11/27] simplify internalAddThing --- src/container.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/container.cpp b/src/container.cpp index 5d0785891c..716a2534e1 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -689,14 +689,8 @@ void Container::internalAddThing(uint32_t index, Thing* thing) return; } - if (index == 0 || itemlist.empty()) { - itemlist.push_front(item); - } else { - size_t pos = std::min(std::max(0, itemlist.size() - 1), index); - auto it = itemlist.begin(); - std::advance(it, pos); - itemlist.insert(it, item); - } + auto it = itemlist.begin() + index; + itemlist.insert(it, item); item->setParent(this); itemlist.push_front(item); From 9b7ca7d302e5d3a178fed5ad55c425b9bc62b694 Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 07:00:37 +0100 Subject: [PATCH 12/27] corrected receiver GUID in mailbox --- src/mailbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mailbox.cpp b/src/mailbox.cpp index b84af7fe1e..14300961b5 100644 --- a/src/mailbox.cpp +++ b/src/mailbox.cpp @@ -100,7 +100,7 @@ bool Mailbox::sendItem(Item* item) const if (g_game.internalRemoveItem(item) == RETURNVALUE_NOERROR) { cloneItem->setID(cloneItem->getID() + 1); inboxDelivery.emplace_back(0, cloneItem); - IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); + IOInbox::getInstance().pushDeliveryItems(receiverGuid, inboxDelivery); return true; } else { delete cloneItem; From 62a7dc5a1b3402e006df4a021e3c826275087c8e Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 08:12:59 +0100 Subject: [PATCH 13/27] applied modifications from ramon-bernardo --- src/game.cpp | 164 +----------------------- src/house.cpp | 26 ++-- src/ioinbox.cpp | 219 ++++++++++++++++++++------------ src/ioinbox.h | 37 ++++-- src/iomarket.cpp | 54 +------- src/mailbox.cpp | 48 ++++--- src/player.cpp | 72 +++++++++-- src/player.h | 3 + vc17/theforgottenserver.vcxproj | 2 + 9 files changed, 278 insertions(+), 347 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 63b55768d1..ffde850973 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -128,7 +128,7 @@ void Game::setGameState(GameState_t newState) saveGameState(); - g_dispatcherInbox.addTask([]() { IOInbox::getInstance().flushDeliverItems(); }); + g_dispatcherInbox.addTask([]() { IOInbox::getInstance().flush(); }); g_dispatcher.addTask([this]() { shutdown(); }); @@ -5458,59 +5458,7 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 return; } - if (it.stackable) { - uint16_t tmpAmount = offer.amount; - if (!player->getInbox()) { - ItemBlockList inboxDelivery; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (item) { - inboxDelivery.emplace_back(0, item); - } - tmpAmount -= stackCount; - } - IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); - } else { - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; - } - tmpAmount -= stackCount; - } - } - } else { - int32_t subType; - if (it.charges != 0) { - subType = it.charges; - } else { - subType = -1; - } - - if (!player->getInbox()) { - ItemBlockList inboxDelivery; - for (uint16_t i = 0; i < offer.amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (item) { - inboxDelivery.emplace_back(0, item); - } - } - IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); - } else { - for (uint16_t i = 0; i < offer.amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; - } - } - } - } + player->sendItemInbox(it, offer.amount); } IOMarket::moveOfferToHistory(offer.id, OFFERSTATE_CANCELLED); @@ -5591,59 +5539,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 player->bankBalance += totalPrice; - if (it.stackable) { - uint16_t tmpAmount = amount; - if (!buyerPlayer->getInbox()) { - ItemBlockList inboxDelivery; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (item) { - inboxDelivery.emplace_back(0, item); - } - tmpAmount -= stackCount; - } - IOInbox::getInstance().pushDeliveryItems(buyerPlayer->getGUID(), inboxDelivery); - } else { - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; - } - tmpAmount -= stackCount; - } - } - } else { - int32_t subType; - if (it.charges != 0) { - subType = it.charges; - } else { - subType = -1; - } - - if (!buyerPlayer->getInbox()) { - ItemBlockList inboxDelivery; - for (uint16_t i = 0; i < offer.amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (item) { - inboxDelivery.emplace_back(0, item); - } - } - IOInbox::getInstance().pushDeliveryItems(buyerPlayer->getGUID(), inboxDelivery); - } else { - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; - } - } - } - } + buyerPlayer->sendItemInbox(it, amount); if (buyerPlayer->isOffline()) { IOLoginData::savePlayer(buyerPlayer); @@ -5661,59 +5557,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 removeMoney(player, debitCash); player->bankBalance -= debitBank; - if (it.stackable) { - uint16_t tmpAmount = amount; - if (!player->getInbox()) { - ItemBlockList inboxDelivery; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (item) { - inboxDelivery.emplace_back(0, item); - } - tmpAmount -= stackCount; - } - IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); - } else { - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; - } - tmpAmount -= stackCount; - } - } - } else { - int32_t subType; - if (it.charges != 0) { - subType = it.charges; - } else { - subType = -1; - } - - if (!player->getInbox()) { - ItemBlockList inboxDelivery; - for (uint16_t i = 0; i < offer.amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (item) { - inboxDelivery.emplace_back(0, item); - } - } - IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); - } else { - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; - } - } - } - } + player->sendItemInbox(it, amount); Player* sellerPlayer = getPlayerByGUID(offer.playerId); if (sellerPlayer) { diff --git a/src/house.cpp b/src/house.cpp index 2fbaeee548..d5c80e053f 100644 --- a/src/house.cpp +++ b/src/house.cpp @@ -231,18 +231,24 @@ bool House::transferToDepot(Player* player) const } } - if (!player->getInbox()) { - ItemBlockList itemList; + if (Inbox* inbox = player->getInbox()) { for (Item* item : moveItemList) { - itemList.emplace_back(0, item->clone()); - g_game.internalRemoveItem(item); + g_game.internalMoveItem(item->getParent(), inbox, INDEX_WHEREEVER, item, item->getItemCount(), + nullptr, FLAG_NOLIMIT); } - IOInbox::getInstance().pushDeliveryItems(player->getGUID(), itemList); } else { + ItemBlockList itemList; for (Item* item : moveItemList) { - g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), - nullptr, FLAG_NOLIMIT); + Item* cloneItem = item->clone(); + + ReturnValue ret = g_game.internalRemoveItem(item); + if (ret == RETURNVALUE_NOERROR) { + itemList.emplace_back(0, cloneItem); + } else { + delete cloneItem; + } } + IOInbox::getInstance().savePlayerItems(player, itemList); } return true; } @@ -706,9 +712,9 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const "Warning! \nThe {:s} rent of {:d} gold for your house \"{:s}\" is payable. Have it within {:d} days or you will lose this house.", period, house->getRent(), house->getName(), daysLeft)); if (!player.getInbox()) { - ItemBlockList inboxDelivery; - inboxDelivery.emplace_back(0, letter); - IOInbox::getInstance().pushDeliveryItems(player.getGUID(), inboxDelivery); + ItemBlockList itemList; + itemList.emplace_back(0, letter); + IOInbox::getInstance().savePlayerItems(player.getGUID(), itemList); } else { g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); } diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index 3050c159cf..292c51a5a2 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -12,61 +12,51 @@ extern Game g_game; IOInbox::IOInbox() { db.connect(); } -void IOInbox::savePlayer(Player* player) +void IOInbox::loadPlayer(const Player* player) +{ + std::unique_lock lockClass(lock); + auto result = pendingPlayerSet.insert(player->getGUID()); + if (result.second) { + g_dispatcherInbox.addTask([this, guid = player->getGUID()]() { loadPlayerAsync(guid); }); + } +} + +void IOInbox::savePlayer(const Player* player) { if (Inbox* inbox = player->getInbox()) { saveInbox(player->getGUID(), inbox, player); - g_dispatcherInbox.addTask([this, guid = player->getGUID()]() { asyncSave(guid); }); + g_dispatcherInbox.addTask([this, guid = player->getGUID()]() { savePlayerAsync(guid); }); } } -void IOInbox::saveInbox(uint32_t guid, Inbox* inbox, Player* player /* = nullptr */) +void IOInbox::savePlayerItems(const Player* player, ItemBlockList& itemList) { - // dispatcher thread - ItemBlockList itemList; - for (Item* item : inbox->getItemList()) { - itemList.emplace_back(0, item); - } - DBEntryListPtr inboxPtr = saveItems(player, itemList); - std::unique_lock lock(taskLock); - inboxCache[guid] = std::make_shared(PlayerDBEntry{false, inboxPtr}); + savePlayerItems(player->getGUID(), itemList); } -void IOInbox::loadInboxLogin(uint32_t guid) +void IOInbox::savePlayerItems(const uint32_t& guid, ItemBlockList& itemList) { - if (loading.insert(guid).second) { - g_dispatcherInbox.addTask([this, guid]() { asyncLoad(guid); }); + // dispatcher thread + std::unique_lock lockClass(lock); + pendingItemsToSave[guid].push_back(std::move(itemList)); + + auto it = pendingPlayerSet.find(guid); + if (it == pendingPlayerSet.end()) { + g_dispatcherInbox.addTask([this, guid]() { savePlayerItemsAsync(guid); }); } } -Inbox* IOInbox::loadInbox(uint32_t guid) +Inbox* IOInbox::loadInbox(const uint32_t& guid) { // dispatcherInbox thread - std::unique_lock lock(taskLock); - DBEntryListPtr inboxPtr; - auto it = inboxCache.find(guid); - if (it != inboxCache.end()) { - inboxPtr = it->second->items; - lock.unlock(); - } else { - lock.unlock(); - inboxPtr = std::make_shared(); - DBResult_ptr result; - if ((result = db.storeQuery(fmt::format( - "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", - guid)))) { - do { - inboxPtr->push_back(DBEntry{result->getNumber("pid"), result->getNumber("sid"), - result->getNumber("itemtype"), - result->getNumber("count"), - std::string(result->getString("attributes"))}); - } while (result->next()); - } + DBEntryListPtr tmpInbox = getPlayerInbox(guid); + if (!tmpInbox) { + tmpInbox = loadPlayerInbox(guid); } ItemMap itemMap; - for (auto& itemDBEntry : *inboxPtr) { + for (auto& itemDBEntry : *tmpInbox) { PropStream propStream; propStream.init(itemDBEntry.attributes.data(), itemDBEntry.attributes.size()); @@ -81,24 +71,39 @@ Inbox* IOInbox::loadInbox(uint32_t guid) } } + return createInboxItem(itemMap); +} + +void IOInbox::saveInbox(const uint32_t& guid, Inbox* inbox, const Player* player /* = nullptr */) +{ + // dispatcher thread + ItemBlockList itemList; + for (Item* item : inbox->getItemList()) { + itemList.emplace_back(0, item); + } + DBEntryListPtr inboxPtr = saveItems(player, itemList); + addPlayerInbox(guid, inboxPtr); +} + +Inbox* IOInbox::createInboxItem(const ItemMap& items) +{ Inbox* inbox = new Inbox(ITEM_INBOX); inbox->incrementReferenceCounter(); - for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + + for (ItemMap::const_reverse_iterator it = items.rbegin(), end = items.rend(); it != end; ++it) { const std::pair& pair = it->second; + Item* item = pair.first; int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { inbox->internalAddThing(item); } else { - ItemMap::const_iterator it2 = itemMap.find(pid); - - if (it2 == itemMap.end()) { + ItemMap::const_iterator it2 = items.find(pid); + if (it2 == items.end()) { continue; } - Container* container = it2->second.first->getContainer(); - if (container) { + if (Container* container = it2->second.first->getContainer()) { container->internalAddThing(item); } } @@ -107,7 +112,63 @@ Inbox* IOInbox::loadInbox(uint32_t guid) return inbox; } -DBEntryListPtr IOInbox::saveItems(Player* player, const ItemBlockList& itemList) +bool IOInbox::canSavePlayerItems(const uint32_t& guid) +{ + // dispatcherInbox thread + std::unique_lock lockClass(lock); + + auto playerIt = pendingPlayerSet.find(guid); + if (playerIt != pendingPlayerSet.end()) { + return false; + } + + auto itemIt = pendingItemsToSave.find(guid); + return itemIt != pendingItemsToSave.end(); +} + +DBEntryListPtr IOInbox::getPlayerInbox(const uint32_t& guid) +{ + // dispatcherInbox thread + std::unique_lock lockClass(lock); + + auto it = inboxCache.find(guid); + if (it == inboxCache.end()) { + return nullptr; + } + return it->second->items; +} + +void IOInbox::addPlayerInbox(const uint32_t& guid, DBEntryListPtr inbox) +{ + // dispatcher thread + std::unique_lock lockClass(lock); + + inboxCache[guid] = std::make_shared(PlayerDBEntry{false, inbox}); +} + +DBEntryListPtr IOInbox::loadPlayerInbox(const uint32_t& guid) +{ + auto inbox = std::make_shared(); + + DBResult_ptr result = db.storeQuery(fmt::format( + "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", + guid)); + if (result) { + do { + DBEntry entry; + entry.pid = result->getNumber("pid"); + entry.sid = result->getNumber("sid"); + entry.itemtype = result->getNumber("itemtype"); + entry.count = result->getNumber("count"); + entry.attributes = result->getString("attributes"); + inbox->push_back(entry); + } while (result->next()); + } + + return inbox; +} + +DBEntryListPtr IOInbox::saveItems(const Player* player, const ItemBlockList& itemList) { DBEntryListPtr list = std::make_shared(); using ContainerBlock = std::pair; @@ -196,10 +257,21 @@ DBEntryListPtr IOInbox::saveItems(Player* player, const ItemBlockList& itemList) return list; } -void IOInbox::asyncSave(uint32_t guid) +void IOInbox::loadPlayerAsync(const uint32_t& guid) +{ + // dispatcherInbox thread + Inbox* inbox = loadInbox(guid); + if (deliverItems(guid, inbox)) { + saveInbox(guid, inbox); + savePlayerAsync(guid); + } + g_dispatcher.addTask([this, guid, inbox]() { assignInbox(guid, inbox); }); +} + +void IOInbox::savePlayerAsync(const uint32_t& guid) { // dispatcherInbox thread - std::unique_lock lock(taskLock); + std::unique_lock lockClass(lock); auto it = inboxCache.find(guid); if (it == inboxCache.end() || it->second->saved) { return; @@ -235,22 +307,11 @@ void IOInbox::asyncSave(uint32_t guid) } } -void IOInbox::asyncLoad(uint32_t guid) -{ - // dispatcherInbox thread - Inbox* inbox = loadInbox(guid); - if (deliverItems(guid, inbox)) { - saveInbox(guid, inbox); - asyncSave(guid); - } - g_dispatcher.addTask([this, guid, inbox]() { assignInbox(guid, inbox); }); -} - void IOInbox::assignInbox(uint32_t guid, Inbox* inbox) { // dispatcher thread - std::unique_lock lock(taskLock); - loading.erase(guid); + std::unique_lock lockClass(lock); + pendingPlayerSet.erase(guid); lock.unlock(); Player* player = g_game.getPlayerByGUID(guid); @@ -265,51 +326,41 @@ void IOInbox::assignInbox(uint32_t guid, Inbox* inbox) bool IOInbox::deliverItems(uint32_t guid, Inbox* inbox) { // any thread - std::unique_lock lock(taskLock); - auto it = deliverItemsMap.find(guid); - if (it != deliverItemsMap.end()) { + std::unique_lock lockClass(lock); + auto it = pendingItemsToSave.find(guid); + if (it != pendingItemsToSave.end()) { for (auto& itemList : it->second) { for (auto& itemBlock : itemList) { inbox->internalAddThing(itemBlock.second); } } - deliverItemsMap.erase(it); + pendingItemsToSave.erase(it); return true; } return false; } -void IOInbox::asyncDeliverItems(uint32_t guid) +void IOInbox::savePlayerItemsAsync(uint32_t guid) { // dispatcherInbox thread - std::unique_lock lock(taskLock); - if (loading.find(guid) == loading.end() && deliverItemsMap.find(guid) != deliverItemsMap.end()) { - lock.unlock(); + if (canSavePlayerItems(guid)) { Inbox* inbox = loadInbox(guid); deliverItems(guid, inbox); saveInbox(guid, inbox); - asyncSave(guid); + savePlayerAsync(guid); delete inbox; } } -void IOInbox::pushDeliveryItems(uint32_t guid, ItemBlockList& itemList) -{ - // dispatcher thread - std::unique_lock lock(taskLock); - deliverItemsMap[guid].push_back(std::move(itemList)); - if (loading.find(guid) == loading.end()) { - g_dispatcherInbox.addTask([this, guid]() { asyncDeliverItems(guid); }); - } -} - -void IOInbox::flushDeliverItems() +void IOInbox::flush() { // dispatcherInbox thread - only called as ultimate task on shutdown - std::lock_guard lock(taskLock); - loading.clear(); - while (!deliverItemsMap.empty()) { - auto it = deliverItemsMap.begin(); - asyncDeliverItems(it->first); + std::lock_guard lockClass(lock); + + pendingPlayerSet.clear(); + + while (!pendingItemsToSave.empty()) { + auto it = pendingItemsToSave.begin(); + savePlayerItemsAsync(it->first); } } diff --git a/src/ioinbox.h b/src/ioinbox.h index 036be687b0..9a1cf31f56 100644 --- a/src/ioinbox.h +++ b/src/ioinbox.h @@ -38,29 +38,40 @@ class IOInbox return instance; } - void savePlayer(Player* player); - void loadInboxLogin(uint32_t guid); - void pushDeliveryItems(uint32_t guid, ItemBlockList& itemList); - void flushDeliverItems(); + void savePlayer(const Player* player); + void loadPlayer(const Player* player); + + void savePlayerItems(const Player* player, ItemBlockList& itemList); + void savePlayerItems(const uint32_t& guid, ItemBlockList& itemList); + + void flush(); private: IOInbox(); - static DBEntryListPtr saveItems(Player* player, const ItemBlockList& itemList); + static DBEntryListPtr saveItems(const Player* player, const ItemBlockList& itemList); + + void savePlayerAsync(const uint32_t& guid); + void loadPlayerAsync(const uint32_t& guid); + + Inbox* loadInbox(const uint32_t& guid); + void saveInbox(const uint32_t& guid, Inbox* inbox, const Player* player = nullptr); + Inbox* createInboxItem(const ItemMap& items); + + bool canSavePlayerItems(const uint32_t& guid); + DBEntryListPtr getPlayerInbox(const uint32_t& guid); + void addPlayerInbox(const uint32_t& guid, DBEntryListPtr inbox); + DBEntryListPtr loadPlayerInbox(const uint32_t& guid); - void saveInbox(uint32_t guid, Inbox* inbox, Player* player = nullptr); - Inbox* loadInbox(uint32_t guid); - void asyncSave(uint32_t guid); - void asyncLoad(uint32_t guid); void assignInbox(uint32_t guid, Inbox* inbox); bool deliverItems(uint32_t guid, Inbox* inbox); - void asyncDeliverItems(uint32_t guid); + void savePlayerItemsAsync(uint32_t guid); Database db; - std::recursive_mutex taskLock; + std::recursive_mutex lock; std::map inboxCache; - std::map> deliverItemsMap; - std::set loading; + std::map> pendingItemsToSave; + std::set pendingPlayerSet; }; #endif // FS_IOINBOX_H diff --git a/src/iomarket.cpp b/src/iomarket.cpp index d086ba5473..3766f47c72 100644 --- a/src/iomarket.cpp +++ b/src/iomarket.cpp @@ -129,59 +129,7 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) } } - if (itemType.stackable) { - uint16_t tmpAmount = amount; - if (!player->getInbox()) { - ItemBlockList inboxDelivery; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(itemType.id, stackCount); - if (item) { - inboxDelivery.emplace_back(0, item); - } - tmpAmount -= stackCount; - } - IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); - } else { - while (tmpAmount > 0) { - uint16_t stackCount = std::min(ITEM_STACK_SIZE, tmpAmount); - Item* item = Item::CreateItem(itemType.id, stackCount); - if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; - } - tmpAmount -= stackCount; - } - } - } else { - int32_t subType; - if (itemType.charges != 0) { - subType = itemType.charges; - } else { - subType = -1; - } - - if (!player->getInbox()) { - ItemBlockList inboxDelivery; - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(itemType.id, subType); - if (item) { - inboxDelivery.emplace_back(0, item); - } - } - IOInbox::getInstance().pushDeliveryItems(player->getGUID(), inboxDelivery); - } else { - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(itemType.id, subType); - if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != - RETURNVALUE_NOERROR) { - delete item; - break; - } - } - } - } + player->sendItemInbox(itemType, amount); if (player->isOffline()) { IOLoginData::savePlayer(player); diff --git a/src/mailbox.cpp b/src/mailbox.cpp index 14300961b5..6f5b075973 100644 --- a/src/mailbox.cpp +++ b/src/mailbox.cpp @@ -81,26 +81,40 @@ bool Mailbox::sendItem(Item* item) const return false; } - Player* player = g_game.getPlayerByName(receiver); - if (player && player->getInbox()) { - if (g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), - nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { - g_game.transformItem(item, item->getID() + 1); - player->onReceiveMail(); - return true; - } - } else { - uint32_t receiverGuid = player ? player->getGUID() : IOLoginData::getGuidByName(receiver); - if (!receiverGuid) { - return false; - } + if (Player* player = g_game.getPlayerByName(receiver)) { + if (Inbox* inbox = player->getInbox()) { + ReturnValue ret = g_game.internalMoveItem(item->getParent(), inbox, INDEX_WHEREEVER, item, + item->getItemCount(), nullptr, FLAG_NOLIMIT); + if (ret == RETURNVALUE_NOERROR) { + g_game.transformItem(item, item->getID() + 1); + player->onReceiveMail(); + return true; + } + } else { + Item* cloneItem = item->clone(); - ItemBlockList inboxDelivery; + ReturnValue ret = g_game.internalRemoveItem(item); + if (ret == RETURNVALUE_NOERROR) { + cloneItem->setID(cloneItem->getID() + 1); + + ItemBlockList itemList; + itemList.emplace_back(0, cloneItem); + IOInbox::getInstance().savePlayerItems(player, itemList); + return true; + } else { + delete cloneItem; + } + } + } else if (const uint32_t guid = IOLoginData::getGuidByName(receiver)) { Item* cloneItem = item->clone(); - if (g_game.internalRemoveItem(item) == RETURNVALUE_NOERROR) { + + ReturnValue ret = g_game.internalRemoveItem(item); + if (ret == RETURNVALUE_NOERROR) { cloneItem->setID(cloneItem->getID() + 1); - inboxDelivery.emplace_back(0, cloneItem); - IOInbox::getInstance().pushDeliveryItems(receiverGuid, inboxDelivery); + + ItemBlockList itemList; + itemList.emplace_back(0, cloneItem); + IOInbox::getInstance().savePlayerItems(guid, itemList); return true; } else { delete cloneItem; diff --git a/src/player.cpp b/src/player.cpp index 8786f4acc4..870047da0e 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -62,11 +62,10 @@ Player::~Player() } } - if (depotLocker && inbox) { - depotLocker->removeInbox(inbox); - } - if (inbox) { + if (depotLocker) { + depotLocker->removeInbox(inbox); + } inbox->decrementReferenceCounter(); } @@ -3667,11 +3666,11 @@ void Player::onIdleStatus() void Player::onPlacedCreature() { // scripting event - onLogin - if (!g_creatureEvents->playerLogin(this)) { + if (g_creatureEvents->playerLogin(this)) { + IOInbox::getInstance().loadPlayer(this); + } else { kickPlayer(true); - return; } - IOInbox::getInstance().loadInboxLogin(guid); } void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points) @@ -4141,15 +4140,68 @@ bool Player::isInWarList(uint32_t guildId) const return std::find(guildWarVector.begin(), guildWarVector.end(), guildId) != guildWarVector.end(); } -void Player::setInbox(Inbox* _inbox) +void Player::setInbox(Inbox* inbox) { - inbox = _inbox; + this->inbox = inbox; if (depotLocker) { - depotLocker->internalAddThing(2, inbox); + depotLocker->internalAddThing(INBOX_INDEX, inbox); onSendContainer(depotLocker.get()); } } +void Player::sendItemInbox(const ItemType& itemType, uint16_t amount) +{ + if (Inbox* inbox = getInbox()) { + if (itemType.stackable) { + while (amount > 0) { + uint16_t count = std::min(ITEM_STACK_SIZE, amount); + if (Item* item = Item::CreateItem(itemType.id, count)) { + ReturnValue ret = g_game.internalAddItem(inbox, item, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (ret != RETURNVALUE_NOERROR) { + delete item; + break; + } + } + amount -= count; + } + } else { + uint16_t subType = itemType.charges != 0 ? itemType.charges : -1; + + for (uint16_t i = 0; i < amount; ++i) { + if (Item* item = Item::CreateItem(itemType.id, subType)) { + ReturnValue ret = g_game.internalAddItem(inbox, item, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (ret != RETURNVALUE_NOERROR) { + delete item; + break; + } + } + } + } + } else { + ItemBlockList itemList; + + if (itemType.stackable) { + while (amount > 0) { + uint16_t count = std::min(ITEM_STACK_SIZE, amount); + if (Item* item = Item::CreateItem(itemType.id, count)) { + itemList.emplace_back(0, item); + } + amount -= count; + } + } else { + uint16_t subType = itemType.charges != 0 ? itemType.charges : -1; + + for (uint16_t i = 0; i < amount; ++i) { + if (Item* item = Item::CreateItem(itemType.id, subType)) { + itemList.emplace_back(0, item); + } + } + } + + IOInbox::getInstance().savePlayerItems(this, itemList); + } +} + bool Player::isPremium() const { if (g_config.getBoolean(ConfigManager::FREE_PREMIUM) || hasFlag(PlayerFlag_IsAlwaysPremium)) { diff --git a/src/player.h b/src/player.h index 6e9c002ef4..c58d82f328 100644 --- a/src/player.h +++ b/src/player.h @@ -95,6 +95,8 @@ static constexpr int32_t PLAYER_MIN_SPEED = 10; static constexpr int32_t NOTIFY_DEPOT_BOX_RANGE = 1; +static constexpr uint32_t INBOX_INDEX = 2; + class Player final : public Creature, public Cylinder { public: @@ -191,6 +193,7 @@ class Player final : public Creature, public Cylinder void setInbox(Inbox* inbox); Inbox* getInbox() const { return inbox; } + void sendItemInbox(const ItemType& itemType, uint16_t amount); StoreInbox* getStoreInbox() const { return storeInbox; } diff --git a/vc17/theforgottenserver.vcxproj b/vc17/theforgottenserver.vcxproj index 0d040e5932..c8c7f8e238 100644 --- a/vc17/theforgottenserver.vcxproj +++ b/vc17/theforgottenserver.vcxproj @@ -186,6 +186,7 @@ + @@ -275,6 +276,7 @@ + From df3c96735167605dcd0a07b972a04680991a8ae9 Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 08:15:49 +0100 Subject: [PATCH 14/27] house.cpp format fix --- src/house.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/house.cpp b/src/house.cpp index d5c80e053f..9b109fff94 100644 --- a/src/house.cpp +++ b/src/house.cpp @@ -233,8 +233,8 @@ bool House::transferToDepot(Player* player) const if (Inbox* inbox = player->getInbox()) { for (Item* item : moveItemList) { - g_game.internalMoveItem(item->getParent(), inbox, INDEX_WHEREEVER, item, item->getItemCount(), - nullptr, FLAG_NOLIMIT); + g_game.internalMoveItem(item->getParent(), inbox, INDEX_WHEREEVER, item, item->getItemCount(), nullptr, + FLAG_NOLIMIT); } } else { ItemBlockList itemList; From ffefcfb14bea9c507fea4a716cfb0bd9caf86793 Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 08:43:30 +0100 Subject: [PATCH 15/27] properly unlock mutex --- src/ioinbox.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index 292c51a5a2..d64567e8b5 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -277,7 +277,7 @@ void IOInbox::savePlayerAsync(const uint32_t& guid) return; } PlayerDBEntryPtr playerDBEntry = it->second; - lock.unlock(); + lockClass.unlock(); // disable foreign key checks to prevent locking of the players table during insert // we do not except that the player is deleted while saving the inbox @@ -312,7 +312,7 @@ void IOInbox::assignInbox(uint32_t guid, Inbox* inbox) // dispatcher thread std::unique_lock lockClass(lock); pendingPlayerSet.erase(guid); - lock.unlock(); + lockClass.unlock(); Player* player = g_game.getPlayerByGUID(guid); if (!player) { From 58e50cb1891bea368f3a7fed8070f797c20af5b4 Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 08:46:49 +0100 Subject: [PATCH 16/27] use const uint32_t& guid for all IOInbox functions --- src/ioinbox.cpp | 6 +++--- src/ioinbox.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index d64567e8b5..bdf2ae2e3c 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -307,7 +307,7 @@ void IOInbox::savePlayerAsync(const uint32_t& guid) } } -void IOInbox::assignInbox(uint32_t guid, Inbox* inbox) +void IOInbox::assignInbox(const uint32_t& guid, Inbox* inbox) { // dispatcher thread std::unique_lock lockClass(lock); @@ -323,7 +323,7 @@ void IOInbox::assignInbox(uint32_t guid, Inbox* inbox) player->setInbox(inbox); } -bool IOInbox::deliverItems(uint32_t guid, Inbox* inbox) +bool IOInbox::deliverItems(const uint32_t& guid, Inbox* inbox) { // any thread std::unique_lock lockClass(lock); @@ -340,7 +340,7 @@ bool IOInbox::deliverItems(uint32_t guid, Inbox* inbox) return false; } -void IOInbox::savePlayerItemsAsync(uint32_t guid) +void IOInbox::savePlayerItemsAsync(const uint32_t& guid) { // dispatcherInbox thread if (canSavePlayerItems(guid)) { diff --git a/src/ioinbox.h b/src/ioinbox.h index 9a1cf31f56..067ff32181 100644 --- a/src/ioinbox.h +++ b/src/ioinbox.h @@ -63,9 +63,9 @@ class IOInbox void addPlayerInbox(const uint32_t& guid, DBEntryListPtr inbox); DBEntryListPtr loadPlayerInbox(const uint32_t& guid); - void assignInbox(uint32_t guid, Inbox* inbox); - bool deliverItems(uint32_t guid, Inbox* inbox); - void savePlayerItemsAsync(uint32_t guid); + void assignInbox(const uint32_t& guid, Inbox* inbox); + bool deliverItems(const uint32_t& guid, Inbox* inbox); + void savePlayerItemsAsync(const uint32_t& guid); Database db; std::recursive_mutex lock; From 991ebff2fe0b3a8cb654ffa4ea72aa27f18994dc Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 22:09:51 +0100 Subject: [PATCH 17/27] corrected misleading info about thread --- src/ioinbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index bdf2ae2e3c..ecbdfed3f7 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -76,7 +76,7 @@ Inbox* IOInbox::loadInbox(const uint32_t& guid) void IOInbox::saveInbox(const uint32_t& guid, Inbox* inbox, const Player* player /* = nullptr */) { - // dispatcher thread + // any thread ItemBlockList itemList; for (Item* item : inbox->getItemList()) { itemList.emplace_back(0, item); From 99dc92962e70261f2e831f09b1e8aa8641750ce1 Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 22:20:57 +0100 Subject: [PATCH 18/27] use static for IOInbox::createInboxItem --- src/ioinbox.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ioinbox.h b/src/ioinbox.h index 067ff32181..fe10858856 100644 --- a/src/ioinbox.h +++ b/src/ioinbox.h @@ -50,13 +50,13 @@ class IOInbox IOInbox(); static DBEntryListPtr saveItems(const Player* player, const ItemBlockList& itemList); + static Inbox* createInboxItem(const ItemMap& items); void savePlayerAsync(const uint32_t& guid); void loadPlayerAsync(const uint32_t& guid); Inbox* loadInbox(const uint32_t& guid); void saveInbox(const uint32_t& guid, Inbox* inbox, const Player* player = nullptr); - Inbox* createInboxItem(const ItemMap& items); bool canSavePlayerItems(const uint32_t& guid); DBEntryListPtr getPlayerInbox(const uint32_t& guid); From 25ce4b5f9d0d2966ff1ccd3442e3391ba017c90a Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 22:24:00 +0100 Subject: [PATCH 19/27] use const in saveInbox --- src/ioinbox.cpp | 2 +- src/ioinbox.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index ecbdfed3f7..b3976c9eb1 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -74,7 +74,7 @@ Inbox* IOInbox::loadInbox(const uint32_t& guid) return createInboxItem(itemMap); } -void IOInbox::saveInbox(const uint32_t& guid, Inbox* inbox, const Player* player /* = nullptr */) +void IOInbox::saveInbox(const uint32_t& guid, const Inbox* inbox, const Player* player /* = nullptr */) { // any thread ItemBlockList itemList; diff --git a/src/ioinbox.h b/src/ioinbox.h index fe10858856..e43ae2541e 100644 --- a/src/ioinbox.h +++ b/src/ioinbox.h @@ -56,7 +56,7 @@ class IOInbox void loadPlayerAsync(const uint32_t& guid); Inbox* loadInbox(const uint32_t& guid); - void saveInbox(const uint32_t& guid, Inbox* inbox, const Player* player = nullptr); + void saveInbox(const uint32_t& guid, const Inbox* inbox, const Player* player = nullptr); bool canSavePlayerItems(const uint32_t& guid); DBEntryListPtr getPlayerInbox(const uint32_t& guid); From 8d9b2b6ca2d9b7cae0b43fa403d63893f131969d Mon Sep 17 00:00:00 2001 From: gunz Date: Thu, 9 Nov 2023 22:41:44 +0100 Subject: [PATCH 20/27] deliver items to inbox in situation when player logs out during loading inbox, the delivery should now be guaranteed and IOInbox::flush is now only necessary when g_dispatcherInbox does not accept new tasks, needs further research if IOInbox::flush() can be removed --- src/ioinbox.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index b3976c9eb1..50dddb1643 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -316,7 +316,14 @@ void IOInbox::assignInbox(const uint32_t& guid, Inbox* inbox) Player* player = g_game.getPlayerByGUID(guid); if (!player) { - g_dispatcherInbox.addTask([inbox]() { delete inbox; }); + g_dispatcherInbox.addTask([this, guid, inbox]() { + if (canSavePlayerItems(guid)) { + deliverItems(guid, inbox); + saveInbox(guid, inbox); + savePlayerAsync(guid); + } + delete inbox; + }); return; } deliverItems(guid, inbox); From 1bd9b53fa93a48a41c735966b1b13c352db9c4c2 Mon Sep 17 00:00:00 2001 From: Gunz Date: Tue, 21 Nov 2023 11:01:21 +0100 Subject: [PATCH 21/27] rename g_dispatcherInbox to g_asyncTasks, preparation for async house saving --- src/database.h | 6 ++++++ src/game.cpp | 4 ++-- src/ioinbox.cpp | 26 +++++++++++++------------- src/ioinbox.h | 2 +- src/otserv.cpp | 10 ++++++---- src/signals.cpp | 4 ++-- src/tasks.h | 2 +- 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/database.h b/src/database.h index 6c611d6dfb..40e64816c4 100644 --- a/src/database.h +++ b/src/database.h @@ -30,6 +30,12 @@ class Database return instance; } + static Database& getAsyncInstance() + { + static Database asyncInstance; + return asyncInstance; + } + /** * Connects to the database * diff --git a/src/game.cpp b/src/game.cpp index ffde850973..49d214a682 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -128,13 +128,13 @@ void Game::setGameState(GameState_t newState) saveGameState(); - g_dispatcherInbox.addTask([]() { IOInbox::getInstance().flush(); }); + g_asyncTasks.addTask([]() { IOInbox::getInstance().flush(); }); g_dispatcher.addTask([this]() { shutdown(); }); g_scheduler.stop(); g_databaseTasks.stop(); - g_dispatcherInbox.stop(); + g_asyncTasks.stop(); g_dispatcher.stop(); break; } diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index 50dddb1643..6b9046d507 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -10,14 +10,14 @@ extern Game g_game; -IOInbox::IOInbox() { db.connect(); } +IOInbox::IOInbox() : db(Database::getAsyncInstance()) {} void IOInbox::loadPlayer(const Player* player) { std::unique_lock lockClass(lock); auto result = pendingPlayerSet.insert(player->getGUID()); if (result.second) { - g_dispatcherInbox.addTask([this, guid = player->getGUID()]() { loadPlayerAsync(guid); }); + g_asyncTasks.addTask([this, guid = player->getGUID()]() { loadPlayerAsync(guid); }); } } @@ -25,7 +25,7 @@ void IOInbox::savePlayer(const Player* player) { if (Inbox* inbox = player->getInbox()) { saveInbox(player->getGUID(), inbox, player); - g_dispatcherInbox.addTask([this, guid = player->getGUID()]() { savePlayerAsync(guid); }); + g_asyncTasks.addTask([this, guid = player->getGUID()]() { savePlayerAsync(guid); }); } } @@ -36,19 +36,19 @@ void IOInbox::savePlayerItems(const Player* player, ItemBlockList& itemList) void IOInbox::savePlayerItems(const uint32_t& guid, ItemBlockList& itemList) { - // dispatcher thread + // any thread std::unique_lock lockClass(lock); pendingItemsToSave[guid].push_back(std::move(itemList)); auto it = pendingPlayerSet.find(guid); if (it == pendingPlayerSet.end()) { - g_dispatcherInbox.addTask([this, guid]() { savePlayerItemsAsync(guid); }); + g_asyncTasks.addTask([this, guid]() { savePlayerItemsAsync(guid); }); } } Inbox* IOInbox::loadInbox(const uint32_t& guid) { - // dispatcherInbox thread + // any thread DBEntryListPtr tmpInbox = getPlayerInbox(guid); if (!tmpInbox) { @@ -114,7 +114,7 @@ Inbox* IOInbox::createInboxItem(const ItemMap& items) bool IOInbox::canSavePlayerItems(const uint32_t& guid) { - // dispatcherInbox thread + // any thread std::unique_lock lockClass(lock); auto playerIt = pendingPlayerSet.find(guid); @@ -128,7 +128,7 @@ bool IOInbox::canSavePlayerItems(const uint32_t& guid) DBEntryListPtr IOInbox::getPlayerInbox(const uint32_t& guid) { - // dispatcherInbox thread + // any thread std::unique_lock lockClass(lock); auto it = inboxCache.find(guid); @@ -259,7 +259,7 @@ DBEntryListPtr IOInbox::saveItems(const Player* player, const ItemBlockList& ite void IOInbox::loadPlayerAsync(const uint32_t& guid) { - // dispatcherInbox thread + // asyncTasks thread Inbox* inbox = loadInbox(guid); if (deliverItems(guid, inbox)) { saveInbox(guid, inbox); @@ -270,7 +270,7 @@ void IOInbox::loadPlayerAsync(const uint32_t& guid) void IOInbox::savePlayerAsync(const uint32_t& guid) { - // dispatcherInbox thread + // asyncTasks thread std::unique_lock lockClass(lock); auto it = inboxCache.find(guid); if (it == inboxCache.end() || it->second->saved) { @@ -316,7 +316,7 @@ void IOInbox::assignInbox(const uint32_t& guid, Inbox* inbox) Player* player = g_game.getPlayerByGUID(guid); if (!player) { - g_dispatcherInbox.addTask([this, guid, inbox]() { + g_asyncTasks.addTask([this, guid, inbox]() { if (canSavePlayerItems(guid)) { deliverItems(guid, inbox); saveInbox(guid, inbox); @@ -349,7 +349,7 @@ bool IOInbox::deliverItems(const uint32_t& guid, Inbox* inbox) void IOInbox::savePlayerItemsAsync(const uint32_t& guid) { - // dispatcherInbox thread + // asyncTasks thread if (canSavePlayerItems(guid)) { Inbox* inbox = loadInbox(guid); deliverItems(guid, inbox); @@ -361,7 +361,7 @@ void IOInbox::savePlayerItemsAsync(const uint32_t& guid) void IOInbox::flush() { - // dispatcherInbox thread - only called as ultimate task on shutdown + // asyncTasks thread - only called as ultimate task on shutdown std::lock_guard lockClass(lock); pendingPlayerSet.clear(); diff --git a/src/ioinbox.h b/src/ioinbox.h index e43ae2541e..8eb0909016 100644 --- a/src/ioinbox.h +++ b/src/ioinbox.h @@ -67,7 +67,7 @@ class IOInbox bool deliverItems(const uint32_t& guid, Inbox* inbox); void savePlayerItemsAsync(const uint32_t& guid); - Database db; + Database& db; std::recursive_mutex lock; std::map inboxCache; std::map> pendingItemsToSave; diff --git a/src/otserv.cpp b/src/otserv.cpp index 4c00fe19ff..3a0a4f6725 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -27,7 +27,7 @@ DatabaseTasks g_databaseTasks; Dispatcher g_dispatcher; -Dispatcher g_dispatcherInbox; +Dispatcher g_asyncTasks; Scheduler g_scheduler; Game g_game; @@ -86,13 +86,13 @@ int main(int argc, char* argv[]) std::cout << ">> No services running. The server is NOT online." << std::endl; g_scheduler.shutdown(); g_databaseTasks.shutdown(); - g_dispatcherInbox.shutdown(); + g_asyncTasks.shutdown(); g_dispatcher.shutdown(); } g_scheduler.join(); g_databaseTasks.join(); - g_dispatcherInbox.join(); + g_asyncTasks.join(); g_dispatcher.join(); return 0; } @@ -211,10 +211,12 @@ void mainLoader(int, char*[], ServiceManager* services) return; } g_databaseTasks.start(); - g_dispatcherInbox.start(); + g_asyncTasks.start(); DatabaseManager::updateDatabase(); + g_asyncTasks.addTask([]() { Database::getAsyncInstance().connect(); }); + if (g_config.getBoolean(ConfigManager::OPTIMIZE_DATABASE) && !DatabaseManager::optimizeTables()) { std::cout << "> No tables were optimized." << std::endl; } diff --git a/src/signals.cpp b/src/signals.cpp index fadc3e6cc2..2990a00983 100644 --- a/src/signals.cpp +++ b/src/signals.cpp @@ -26,7 +26,7 @@ extern Scheduler g_scheduler; extern DatabaseTasks g_databaseTasks; -extern Dispatcher g_dispatcherInbox; +extern Dispatcher g_asyncTasks; extern Dispatcher g_dispatcher; extern ConfigManager g_config; @@ -158,7 +158,7 @@ void dispatchSignalHandler(int signal) // hold the thread until other threads end g_scheduler.join(); g_databaseTasks.join(); - g_dispatcherInbox.join(); + g_asyncTasks.join(); g_dispatcher.join(); break; #endif diff --git a/src/tasks.h b/src/tasks.h index e4a575d779..b205d4af5a 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -68,6 +68,6 @@ class Dispatcher : public ThreadHolder }; extern Dispatcher g_dispatcher; -extern Dispatcher g_dispatcherInbox; +extern Dispatcher g_asyncTasks; #endif // FS_TASKS_H From 9de7e6a79dc9b65ec6e9309407ba85d5ab35b058 Mon Sep 17 00:00:00 2001 From: Gunz Date: Tue, 21 Nov 2023 12:36:22 +0100 Subject: [PATCH 22/27] asynchronous, zero-blocking house saving --- src/iomapserialize.cpp | 219 +++++++++++++++++++++++++---------------- src/iomapserialize.h | 36 +++++-- src/luascript.cpp | 3 +- src/map.cpp | 8 +- 4 files changed, 170 insertions(+), 96 deletions(-) diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp index 57d68484eb..7d2414be58 100644 --- a/src/iomapserialize.cpp +++ b/src/iomapserialize.cpp @@ -11,7 +11,7 @@ extern Game g_game; -void IOMapSerialize::loadHouseItems(Map* map) +void IOMapSerialize::loadHousesItems(Map* map) { int64_t start = OTSYS_TIME(); @@ -48,47 +48,37 @@ void IOMapSerialize::loadHouseItems(Map* map) std::cout << "> Loaded house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; } -bool IOMapSerialize::saveHouseItems() +bool IOMapSerialize::saveHousesItems(bool async /* = false*/) { int64_t start = OTSYS_TIME(); - Database& db = Database::getInstance(); - - // Start the transaction - DBTransaction transaction; - if (!transaction.begin()) { - return false; - } - - // clear old tile data - if (!db.executeQuery("DELETE FROM `tile_store`")) { - return false; - } - - DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES "); + bool success = true; PropWriteStream stream; for (const auto& it : g_game.map.houses.getHouses()) { // save house items House* house = it.second; - for (HouseTile* tile : house->getTiles()) { + DBHouseTileListPtr tileList = std::make_shared(); + for (const HouseTile* tile : house->getTiles()) { saveTile(stream, tile); - if (auto attributes = stream.getStream(); !attributes.empty()) { - if (!stmt.addRow(fmt::format("{:d}, {:s}", house->getId(), db.escapeString(attributes)))) { - return false; - } + if (auto attributes = stream.getStream(); attributes.size() > 0) { + tileList->push_back(std::string(attributes)); stream.clear(); } } + if (async) { + g_asyncTasks.addTask([houseId = house->getId(), tileList]() { + saveHouseItems(houseId, tileList, Database::getAsyncInstance()); + }); + } else { + if (!saveHouseItems(house->getId(), tileList, Database::getInstance())) { + std::cout << "> Error saving house items for ID: " << house->getId() << std::endl; + success = false; + } + } } - if (!stmt.execute()) { - return false; - } - - // End the transaction - bool success = transaction.commit(); - std::cout << "> Saved house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; + std::cout << "> Saved house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << (async ? " (async)" : " (sync)") << std::endl; return success; } @@ -250,7 +240,7 @@ void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) } } -bool IOMapSerialize::loadHouseInfo() +bool IOMapSerialize::loadHousesInfo() { Database& db = Database::getInstance(); @@ -280,71 +270,83 @@ bool IOMapSerialize::loadHouseInfo() return true; } -bool IOMapSerialize::saveHouseInfo() +bool IOMapSerialize::saveHousesInfo(bool async /* = false*/) { - Database& db = Database::getInstance(); - - DBTransaction transaction; - if (!transaction.begin()) { - return false; - } - - if (!db.executeQuery("DELETE FROM `house_lists`")) { - return false; - } - + bool success = true; + std::string listText; for (const auto& it : g_game.map.houses.getHouses()) { House* house = it.second; - DBResult_ptr result = db.storeQuery(fmt::format("SELECT `id` FROM `houses` WHERE `id` = {:d}", house->getId())); - if (result) { - db.executeQuery(fmt::format( - "UPDATE `houses` SET `owner` = {:d}, `paid` = {:d}, `warnings` = {:d}, `name` = {:s}, `town_id` = {:d}, `rent` = {:d}, `size` = {:d}, `beds` = {:d} WHERE `id` = {:d}", - house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), - db.escapeString(house->getName()), house->getTownId(), house->getRent(), house->getTiles().size(), - house->getBedCount(), house->getId())); - } else { - db.executeQuery(fmt::format( - "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES ({:d}, {:d}, {:d}, {:d}, {:s}, {:d}, {:d}, {:d}, {:d})", - house->getId(), house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), - db.escapeString(house->getName()), house->getTownId(), house->getRent(), house->getTiles().size(), - house->getBedCount())); - } - } + DBHouseInfoPtr houseInfo = std::make_shared( + DBHouseInfo{house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), house->getName(), + house->getTownId(), house->getRent(), house->getTiles().size(), house->getBedCount()}); - DBInsert stmt("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES "); - - for (const auto& it : g_game.map.houses.getHouses()) { - House* house = it.second; - - std::string listText; if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { - if (!stmt.addRow(fmt::format("{:d}, {:d}, {:s}", house->getId(), tfs::to_underlying(GUEST_LIST), - db.escapeString(listText)))) { - return false; - } + houseInfo->lists.push_back(DBHouseList{tfs::to_underlying(GUEST_LIST), listText}); listText.clear(); } if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { - if (!stmt.addRow(fmt::format("{:d}, {:d}, {:s}", house->getId(), tfs::to_underlying(SUBOWNER_LIST), - db.escapeString(listText)))) { - return false; - } + houseInfo->lists.push_back(DBHouseList{tfs::to_underlying(GUEST_LIST), listText}); listText.clear(); } for (Door* door : house->getDoors()) { if (door->getAccessList(listText) && !listText.empty()) { - if (!stmt.addRow(fmt::format("{:d}, {:d}, {:s}", house->getId(), door->getDoorId(), - db.escapeString(listText)))) { - return false; - } + houseInfo->lists.push_back(DBHouseList{door->getDoorId(), listText}); listText.clear(); } } + + if (async) { + g_asyncTasks.addTask([houseId = house->getId(), houseInfo]() { + saveHouseInfo(houseId, houseInfo, Database::getAsyncInstance()); + }); + } else { + if (!saveHouseInfo(house->getId(), houseInfo, Database::getInstance())) { + std::cout << "> Error saving house info for ID: " << house->getId() << std::endl; + success = false; + } + } + } + + return success; +} + +bool IOMapSerialize::saveHouseInfo(const uint32_t& houseId, DBHouseInfoPtr houseInfo, Database& db) +{ + DBTransaction transaction(db); + if (!transaction.begin()) { + return false; + } + + if (!db.executeQuery(fmt::format("DELETE FROM `house_lists` WHERE `house_id` = {:d}", houseId))) { + return false; + } + + DBResult_ptr result = db.storeQuery(fmt::format("SELECT `id` FROM `houses` WHERE `id` = {:d}", houseId)); + if (result) { + db.executeQuery(fmt::format( + "UPDATE `houses` SET `owner` = {:d}, `paid` = {:d}, `warnings` = {:d}, `name` = {:s}, `town_id` = {:d}, `rent` = {:d}, `size` = {:d}, `beds` = {:d} WHERE `id` = {:d}", + houseInfo->owner, houseInfo->paidUntil, houseInfo->payrentWarnings, db.escapeString(houseInfo->name), + houseInfo->townId, houseInfo->rent, houseInfo->size, houseInfo->bedCount, houseId)); + } else { + db.executeQuery(fmt::format( + "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES ({:d}, {:d}, {:d}, {:d}, {:s}, {:d}, {:d}, {:d}, {:d})", + houseId, houseInfo->owner, houseInfo->paidUntil, houseInfo->payrentWarnings, + db.escapeString(houseInfo->name), houseInfo->townId, houseInfo->rent, houseInfo->size, + houseInfo->bedCount)); + } + + DBInsert stmt("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES ", db); + + for (const DBHouseList& houseList : houseInfo->lists) { + if (!stmt.addRow(fmt::format("{:d}, {:d}, {:s}", houseId, houseList.listId, + db.escapeString(houseList.listText)))) { + return false; + } } if (!stmt.execute()) { @@ -354,31 +356,25 @@ bool IOMapSerialize::saveHouseInfo() return transaction.commit(); } -bool IOMapSerialize::saveHouse(House* house) +bool IOMapSerialize::saveHouseItems(const uint32_t& houseId, DBHouseTileListPtr tileList, Database& db) { - Database& db = Database::getInstance(); - // Start the transaction - DBTransaction transaction; + DBTransaction transaction(db); if (!transaction.begin()) { return false; } - uint32_t houseId = house->getId(); - // clear old tile data if (!db.executeQuery(fmt::format("DELETE FROM `tile_store` WHERE `house_id` = {:d}", houseId))) { return false; } - DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES "); + DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES ", db); PropWriteStream stream; - for (HouseTile* tile : house->getTiles()) { - saveTile(stream, tile); - + for (const std::string& tile : *tileList) { if (auto attributes = stream.getStream(); attributes.size() > 0) { - if (!stmt.addRow(fmt::format("{:d}, {:s}", houseId, db.escapeString(attributes)))) { + if (!stmt.addRow(fmt::format("{:d}, {:s}", houseId, db.escapeString(tile)))) { return false; } stream.clear(); @@ -392,3 +388,56 @@ bool IOMapSerialize::saveHouse(House* house) // End the transaction return transaction.commit(); } + +bool IOMapSerialize::saveHouse(House* house, bool async /* = false */) +{ + DBHouseInfoPtr houseInfo = std::make_shared( + DBHouseInfo{house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), house->getName(), + house->getTownId(), house->getRent(), house->getTiles().size(), house->getBedCount()}); + + + std::string listText; + if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { + houseInfo->lists.push_back(DBHouseList{tfs::to_underlying(GUEST_LIST), listText}); + + listText.clear(); + } + + if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { + houseInfo->lists.push_back(DBHouseList{tfs::to_underlying(GUEST_LIST), listText}); + + listText.clear(); + } + + for (Door* door : house->getDoors()) { + if (door->getAccessList(listText) && !listText.empty()) { + houseInfo->lists.push_back(DBHouseList{door->getDoorId(), listText}); + + listText.clear(); + } + } + + + DBHouseTileListPtr tileList = std::make_shared(); + PropWriteStream stream; + for (const HouseTile* tile : house->getTiles()) { + saveTile(stream, tile); + + if (auto attributes = stream.getStream(); attributes.size() > 0) { + tileList->push_back(std::string(attributes)); + stream.clear(); + } + } + + if (async) { + g_asyncTasks.addTask([houseId = house->getId(), houseInfo, tileList]() { + saveHouseInfo(houseId, houseInfo, Database::getAsyncInstance()); + saveHouseItems(houseId, tileList, Database::getAsyncInstance()); + }); + } else { + saveHouseInfo(house->getId(), houseInfo, Database::getInstance()); + saveHouseItems(house->getId(), tileList, Database::getInstance()); + } + return true; +} + diff --git a/src/iomapserialize.h b/src/iomapserialize.h index 8a7e649151..6798bb7908 100644 --- a/src/iomapserialize.h +++ b/src/iomapserialize.h @@ -6,6 +6,7 @@ class Container; class Cylinder; +class Database; class House; class Item; class Map; @@ -13,15 +14,35 @@ class PropStream; class PropWriteStream; class Tile; +struct DBHouseList { + uint32_t listId; + std::string listText; +}; + +struct DBHouseInfo { + uint32_t owner; + time_t paidUntil; + uint32_t payrentWarnings; + std::string name; + uint32_t townId; + uint32_t rent; + size_t size; + uint32_t bedCount; + std::list lists {}; +}; + +using DBHouseInfoPtr = std::shared_ptr; +using DBHouseTileList = std::list; +using DBHouseTileListPtr = std::shared_ptr; + class IOMapSerialize { public: - static void loadHouseItems(Map* map); - static bool saveHouseItems(); - static bool loadHouseInfo(); - static bool saveHouseInfo(); - - static bool saveHouse(House* house); + static void loadHousesItems(Map* map); + static bool saveHousesItems(bool async = false); + static bool loadHousesInfo(); + static bool saveHousesInfo(bool async = false); + static bool saveHouse(House* house, bool async = false); private: static void saveItem(PropWriteStream& stream, const Item* item); @@ -29,6 +50,9 @@ class IOMapSerialize static bool loadContainer(PropStream& propStream, Container* container); static bool loadItem(PropStream& propStream, Cylinder* parent); + + static bool saveHouseInfo(const uint32_t& houseId, DBHouseInfoPtr houseInfo, Database& db); + static bool saveHouseItems(const uint32_t& houseId, DBHouseTileListPtr tileList, Database& db); }; #endif // FS_IOMAPSERIALIZE_H diff --git a/src/luascript.cpp b/src/luascript.cpp index b790107acc..8ba82e399d 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -12561,8 +12561,9 @@ int LuaScriptInterface::luaHouseSave(lua_State* L) lua_pushnil(L); return 1; } + bool async = getBoolean(L, 2, false); - pushBoolean(L, IOMapSerialize::saveHouse(house)); + pushBoolean(L, IOMapSerialize::saveHouse(house, async)); return 1; } diff --git a/src/map.cpp b/src/map.cpp index 852cc09549..e4f7a43085 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -32,8 +32,8 @@ bool Map::loadMap(const std::string& identifier, bool loadHouses) std::cout << "[Warning - Map::loadMap] Failed to load house data." << std::endl; } - IOMapSerialize::loadHouseInfo(); - IOMapSerialize::loadHouseItems(this); + IOMapSerialize::loadHousesInfo(); + IOMapSerialize::loadHousesItems(this); } return true; } @@ -42,7 +42,7 @@ bool Map::save() { bool saved = false; for (uint32_t tries = 0; tries < 3; tries++) { - if (IOMapSerialize::saveHouseInfo()) { + if (IOMapSerialize::saveHousesInfo()) { saved = true; break; } @@ -54,7 +54,7 @@ bool Map::save() saved = false; for (uint32_t tries = 0; tries < 3; tries++) { - if (IOMapSerialize::saveHouseItems()) { + if (IOMapSerialize::saveHousesItems()) { saved = true; break; } From f9ebe3abb5cbfdde346da76546abbaaf82505fb0 Mon Sep 17 00:00:00 2001 From: Gunz Date: Tue, 21 Nov 2023 12:38:57 +0100 Subject: [PATCH 23/27] fix clang formatting --- src/iomapserialize.cpp | 10 ++++------ src/iomapserialize.h | 8 +++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp index 7d2414be58..821900f097 100644 --- a/src/iomapserialize.cpp +++ b/src/iomapserialize.cpp @@ -78,7 +78,8 @@ bool IOMapSerialize::saveHousesItems(bool async /* = false*/) } } - std::cout << "> Saved house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << (async ? " (async)" : " (sync)") << std::endl; + std::cout << "> Saved house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" + << (async ? " (async)" : " (sync)") << std::endl; return success; } @@ -343,8 +344,8 @@ bool IOMapSerialize::saveHouseInfo(const uint32_t& houseId, DBHouseInfoPtr house DBInsert stmt("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES ", db); for (const DBHouseList& houseList : houseInfo->lists) { - if (!stmt.addRow(fmt::format("{:d}, {:d}, {:s}", houseId, houseList.listId, - db.escapeString(houseList.listText)))) { + if (!stmt.addRow( + fmt::format("{:d}, {:d}, {:s}", houseId, houseList.listId, db.escapeString(houseList.listText)))) { return false; } } @@ -395,7 +396,6 @@ bool IOMapSerialize::saveHouse(House* house, bool async /* = false */) DBHouseInfo{house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), house->getName(), house->getTownId(), house->getRent(), house->getTiles().size(), house->getBedCount()}); - std::string listText; if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { houseInfo->lists.push_back(DBHouseList{tfs::to_underlying(GUEST_LIST), listText}); @@ -417,7 +417,6 @@ bool IOMapSerialize::saveHouse(House* house, bool async /* = false */) } } - DBHouseTileListPtr tileList = std::make_shared(); PropWriteStream stream; for (const HouseTile* tile : house->getTiles()) { @@ -440,4 +439,3 @@ bool IOMapSerialize::saveHouse(House* house, bool async /* = false */) } return true; } - diff --git a/src/iomapserialize.h b/src/iomapserialize.h index 6798bb7908..422d706f4b 100644 --- a/src/iomapserialize.h +++ b/src/iomapserialize.h @@ -14,12 +14,14 @@ class PropStream; class PropWriteStream; class Tile; -struct DBHouseList { +struct DBHouseList +{ uint32_t listId; std::string listText; }; -struct DBHouseInfo { +struct DBHouseInfo +{ uint32_t owner; time_t paidUntil; uint32_t payrentWarnings; @@ -28,7 +30,7 @@ struct DBHouseInfo { uint32_t rent; size_t size; uint32_t bedCount; - std::list lists {}; + std::list lists{}; }; using DBHouseInfoPtr = std::shared_ptr; From b23c27f8545dc2f4766243adcf309afc5dafc191 Mon Sep 17 00:00:00 2001 From: Gunz Date: Tue, 21 Nov 2023 12:41:48 +0100 Subject: [PATCH 24/27] return correct value when saving individual house --- src/iomapserialize.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp index 821900f097..fbbde1ae5d 100644 --- a/src/iomapserialize.cpp +++ b/src/iomapserialize.cpp @@ -434,8 +434,9 @@ bool IOMapSerialize::saveHouse(House* house, bool async /* = false */) saveHouseItems(houseId, tileList, Database::getAsyncInstance()); }); } else { - saveHouseInfo(house->getId(), houseInfo, Database::getInstance()); - saveHouseItems(house->getId(), tileList, Database::getInstance()); + bool saveInfo = saveHouseInfo(house->getId(), houseInfo, Database::getInstance()); + bool saveItems = saveHouseItems(house->getId(), tileList, Database::getInstance()); + return saveInfo && saveItems; } return true; } From 3a7b4bedeca170d034c7abfa4ffcf3c49dfe3a4c Mon Sep 17 00:00:00 2001 From: Gunz Date: Sat, 2 Dec 2023 10:21:15 +0100 Subject: [PATCH 25/27] moved private variables to .cpp namespace --- src/ioinbox.cpp | 12 ++++++++++-- src/ioinbox.h | 5 ----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ioinbox.cpp b/src/ioinbox.cpp index 6b9046d507..de4e5a4bba 100644 --- a/src/ioinbox.cpp +++ b/src/ioinbox.cpp @@ -10,7 +10,11 @@ extern Game g_game; -IOInbox::IOInbox() : db(Database::getAsyncInstance()) {} +namespace { +std::map inboxCache; +std::map> pendingItemsToSave; +std::set pendingPlayerSet; +} // namespace void IOInbox::loadPlayer(const Player* player) { @@ -48,7 +52,7 @@ void IOInbox::savePlayerItems(const uint32_t& guid, ItemBlockList& itemList) Inbox* IOInbox::loadInbox(const uint32_t& guid) { - // any thread + // asyncTasks thread DBEntryListPtr tmpInbox = getPlayerInbox(guid); if (!tmpInbox) { @@ -150,6 +154,8 @@ DBEntryListPtr IOInbox::loadPlayerInbox(const uint32_t& guid) { auto inbox = std::make_shared(); + Database& db = Database::getAsyncInstance(); + DBResult_ptr result = db.storeQuery(fmt::format( "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", guid)); @@ -279,6 +285,8 @@ void IOInbox::savePlayerAsync(const uint32_t& guid) PlayerDBEntryPtr playerDBEntry = it->second; lockClass.unlock(); + Database& db = Database::getAsyncInstance(); + // disable foreign key checks to prevent locking of the players table during insert // we do not except that the player is deleted while saving the inbox db.executeQuery("SET foreign_key_checks = 0;"); diff --git a/src/ioinbox.h b/src/ioinbox.h index 8eb0909016..d5c012f36b 100644 --- a/src/ioinbox.h +++ b/src/ioinbox.h @@ -47,7 +47,6 @@ class IOInbox void flush(); private: - IOInbox(); static DBEntryListPtr saveItems(const Player* player, const ItemBlockList& itemList); static Inbox* createInboxItem(const ItemMap& items); @@ -67,11 +66,7 @@ class IOInbox bool deliverItems(const uint32_t& guid, Inbox* inbox); void savePlayerItemsAsync(const uint32_t& guid); - Database& db; std::recursive_mutex lock; - std::map inboxCache; - std::map> pendingItemsToSave; - std::set pendingPlayerSet; }; #endif // FS_IOINBOX_H From 8fcdcde49b3053ccbe64e52261fc27a8edfbe3b5 Mon Sep 17 00:00:00 2001 From: Gunz Date: Sat, 2 Dec 2023 10:23:10 +0100 Subject: [PATCH 26/27] fixed clang formatting --- src/ioinbox.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ioinbox.h b/src/ioinbox.h index d5c012f36b..8d3e15ca65 100644 --- a/src/ioinbox.h +++ b/src/ioinbox.h @@ -47,7 +47,6 @@ class IOInbox void flush(); private: - static DBEntryListPtr saveItems(const Player* player, const ItemBlockList& itemList); static Inbox* createInboxItem(const ItemMap& items); From 93bc130f0ca279703c6318f59bb5ebbef10a4645 Mon Sep 17 00:00:00 2001 From: Gunz Date: Sun, 14 Jan 2024 08:19:43 +0100 Subject: [PATCH 27/27] fixed conflict --- src/otserv.cpp | 188 ++++++++++++++++++++----------------------------- src/otserv.h | 10 +++ 2 files changed, 86 insertions(+), 112 deletions(-) create mode 100644 src/otserv.h diff --git a/src/otserv.cpp b/src/otserv.cpp index 3a0a4f6725..26f6350335 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -3,6 +3,8 @@ #include "otpch.h" +#include "otserv.h" + #include "configmanager.h" #include "databasemanager.h" #include "databasetasks.h" @@ -41,99 +43,15 @@ std::mutex g_loaderLock; std::condition_variable g_loaderSignal; std::unique_lock g_loaderUniqueLock(g_loaderLock); +namespace { + void startupErrorMessage(const std::string& errorStr) { fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "> ERROR: {:s}\n", errorStr); g_loaderSignal.notify_all(); } -void mainLoader(int argc, char* argv[], ServiceManager* services); -bool argumentsHandler(const StringVector& args); - -[[noreturn]] void badAllocationHandler() -{ - // Use functions that only use stack allocation - puts("Allocation failed, server out of memory.\nDecrease the size of your map or compile in 64 bits mode.\n"); - getchar(); - exit(-1); -} - -int main(int argc, char* argv[]) -{ - StringVector args = StringVector(argv, argv + argc); - if (argc > 1 && !argumentsHandler(args)) { - return 0; - } - - // Setup bad allocation handler - std::set_new_handler(badAllocationHandler); - - ServiceManager serviceManager; - - g_dispatcher.start(); - - g_scheduler.start(); - - g_dispatcher.addTask([=, services = &serviceManager]() { mainLoader(argc, argv, services); }); - - g_loaderSignal.wait(g_loaderUniqueLock); - - if (serviceManager.is_running()) { - std::cout << ">> " << g_config.getString(ConfigManager::SERVER_NAME) << " Server Online!" << std::endl - << std::endl; - serviceManager.run(); - } else { - std::cout << ">> No services running. The server is NOT online." << std::endl; - g_scheduler.shutdown(); - g_databaseTasks.shutdown(); - g_asyncTasks.shutdown(); - g_dispatcher.shutdown(); - } - - g_scheduler.join(); - g_databaseTasks.join(); - g_asyncTasks.join(); - g_dispatcher.join(); - return 0; -} - -void printServerVersion() -{ -#if defined(GIT_RETRIEVED_STATE) && GIT_RETRIEVED_STATE - std::cout << STATUS_SERVER_NAME << " - Version " << GIT_DESCRIBE << std::endl; - std::cout << "Git SHA1 " << GIT_SHORT_SHA1 << " dated " << GIT_COMMIT_DATE_ISO8601 << std::endl; -#if GIT_IS_DIRTY - std::cout << "*** DIRTY - NOT OFFICIAL RELEASE ***" << std::endl; -#endif -#else - std::cout << STATUS_SERVER_NAME << " - Version " << STATUS_SERVER_VERSION << std::endl; -#endif - std::cout << std::endl; - - std::cout << "Compiled with " << BOOST_COMPILER << std::endl; - std::cout << "Compiled on " << __DATE__ << ' ' << __TIME__ << " for platform "; -#if defined(__amd64__) || defined(_M_X64) - std::cout << "x64" << std::endl; -#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) - std::cout << "x86" << std::endl; -#elif defined(__arm__) - std::cout << "ARM" << std::endl; -#else - std::cout << "unknown" << std::endl; -#endif -#if defined(LUAJIT_VERSION) - std::cout << "Linked with " << LUAJIT_VERSION << " for Lua support" << std::endl; -#else - std::cout << "Linked with " << LUA_RELEASE << " for Lua support" << std::endl; -#endif - std::cout << std::endl; - - std::cout << "A server developed by " << STATUS_SERVER_DEVELOPERS << std::endl; - std::cout << "Visit our forum for updates, support, and resources: https://otland.net/." << std::endl; - std::cout << std::endl; -} - -void mainLoader(int, char*[], ServiceManager* services) +void mainLoader(ServiceManager* services) { // dispatcher thread g_game.setGameState(GAME_STATE_STARTUP); @@ -343,34 +261,80 @@ void mainLoader(int, char*[], ServiceManager* services) g_loaderSignal.notify_all(); } -bool argumentsHandler(const StringVector& args) +[[noreturn]] void badAllocationHandler() { - for (const auto& arg : args) { - if (arg == "--help") { - std::clog << "Usage:\n" - "\n" - "\t--config=$1\t\tAlternate configuration file path.\n" - "\t--ip=$1\t\t\tIP address of the server.\n" - "\t\t\t\tShould be equal to the global IP.\n" - "\t--login-port=$1\tPort for login server to listen on.\n" - "\t--game-port=$1\tPort for game server to listen on.\n"; - return false; - } else if (arg == "--version") { - printServerVersion(); - return false; - } + // Use functions that only use stack allocation + puts("Allocation failed, server out of memory.\nDecrease the size of your map or compile in 64 bits mode.\n"); + getchar(); + exit(-1); +} - auto tmp = explodeString(arg, "="); +} // namespace - if (tmp[0] == "--config") - g_config.setString(ConfigManager::CONFIG_FILE, tmp[1]); - else if (tmp[0] == "--ip") - g_config.setString(ConfigManager::IP, tmp[1]); - else if (tmp[0] == "--login-port") - g_config.setNumber(ConfigManager::LOGIN_PORT, std::stoi(tmp[1].data())); - else if (tmp[0] == "--game-port") - g_config.setNumber(ConfigManager::GAME_PORT, std::stoi(tmp[1].data())); +void startServer() +{ + // Setup bad allocation handler + std::set_new_handler(badAllocationHandler); + + ServiceManager serviceManager; + + g_dispatcher.start(); + g_scheduler.start(); + + g_dispatcher.addTask([services = &serviceManager]() { mainLoader(services); }); + + g_loaderSignal.wait(g_loaderUniqueLock); + + if (serviceManager.is_running()) { + std::cout << ">> " << g_config.getString(ConfigManager::SERVER_NAME) << " Server Online!" << std::endl + << std::endl; + serviceManager.run(); + } else { + std::cout << ">> No services running. The server is NOT online." << std::endl; + g_scheduler.shutdown(); + g_databaseTasks.shutdown(); + g_asyncTasks.shutdown(); + g_dispatcher.shutdown(); } - return true; + g_scheduler.join(); + g_databaseTasks.join(); + g_asyncTasks.join(); + g_dispatcher.join(); +} + +void printServerVersion() +{ +#if defined(GIT_RETRIEVED_STATE) && GIT_RETRIEVED_STATE + std::cout << STATUS_SERVER_NAME << " - Version " << GIT_DESCRIBE << std::endl; + std::cout << "Git SHA1 " << GIT_SHORT_SHA1 << " dated " << GIT_COMMIT_DATE_ISO8601 << std::endl; +#if GIT_IS_DIRTY + std::cout << "*** DIRTY - NOT OFFICIAL RELEASE ***" << std::endl; +#endif +#else + std::cout << STATUS_SERVER_NAME << " - Version " << STATUS_SERVER_VERSION << std::endl; +#endif + std::cout << std::endl; + + std::cout << "Compiled with " << BOOST_COMPILER << std::endl; + std::cout << "Compiled on " << __DATE__ << ' ' << __TIME__ << " for platform "; +#if defined(__amd64__) || defined(_M_X64) + std::cout << "x64" << std::endl; +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) + std::cout << "x86" << std::endl; +#elif defined(__arm__) + std::cout << "ARM" << std::endl; +#else + std::cout << "unknown" << std::endl; +#endif +#if defined(LUAJIT_VERSION) + std::cout << "Linked with " << LUAJIT_VERSION << " for Lua support" << std::endl; +#else + std::cout << "Linked with " << LUA_RELEASE << " for Lua support" << std::endl; +#endif + std::cout << std::endl; + + std::cout << "A server developed by " << STATUS_SERVER_DEVELOPERS << std::endl; + std::cout << "Visit our forum for updates, support, and resources: https://otland.net/." << std::endl; + std::cout << std::endl; } diff --git a/src/otserv.h b/src/otserv.h new file mode 100644 index 0000000000..6a57013a42 --- /dev/null +++ b/src/otserv.h @@ -0,0 +1,10 @@ +// Copyright 2023 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_OTSERV_H +#define FS_OTSERV_H + +void printServerVersion(); +void startServer(); + +#endif