Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: async task execution for player SaveManager #3050

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion data-otservbr-global/migrations/46.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
function onUpdateDatabase()
return false -- true = There are others migrations file | false = this is the last migration file
logger.info("Updating database to version 47 (player forge history unique done_at)")

db.query([[
UPDATE forge_history
SET done_at = done_at * 1000
WHERE done_at < 1000000000000;
]])

db.query([[
UPDATE forge_history AS f1
JOIN (
SELECT
id,
done_at,
ROW_NUMBER() OVER (PARTITION BY done_at ORDER BY id) AS row_num
FROM forge_history
) AS duplicates ON f1.id = duplicates.id
SET f1.done_at = f1.done_at + (duplicates.row_num * 1)
WHERE duplicates.row_num > 1;
]])

local success = db.query("ALTER TABLE forge_history ADD UNIQUE KEY unique_done_at (done_at);")

if not success then
logger.error("Failed to add unique key to 'done_at'.")
return false
end

return true
end
15 changes: 15 additions & 0 deletions data-otservbr-global/migrations/47.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function onUpdateDatabase()
logger.info("Updating database to version 48 (player bosstiary and player kills unique key)")

db.query([[
ALTER TABLE `player_bosstiary`
ADD PRIMARY KEY (`player_id`)
]])

db.query([[
ALTER TABLE `player_kills`
ADD PRIMARY KEY (`player_id`, `target`, `time`);
]])

return true
end
3 changes: 3 additions & 0 deletions data-otservbr-global/migrations/48.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function onUpdateDatabase()
return false -- true = There are others migrations file | false = this is the last migration file
end
11 changes: 7 additions & 4 deletions schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ CREATE TABLE IF NOT EXISTS `daily_reward_history` (
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Tabble Structure `forge_history`
-- Table Structure `forge_history`
CREATE TABLE IF NOT EXISTS `forge_history` (
`id` int NOT NULL AUTO_INCREMENT,
`player_id` int NOT NULL,
Expand All @@ -314,7 +314,8 @@ CREATE TABLE IF NOT EXISTS `forge_history` (
`done_at_date` datetime DEFAULT NOW(),
`cost` bigint UNSIGNED NOT NULL DEFAULT '0',
`gained` bigint UNSIGNED NOT NULL DEFAULT '0',
CONSTRAINT `forge_history_pk` PRIMARY KEY (`id`),
PRIMARY KEY (`id`),
UNIQUE KEY `unique_done_at` (`done_at`),
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Expand Down Expand Up @@ -674,7 +675,8 @@ CREATE TABLE IF NOT EXISTS `player_kills` (
`player_id` int(11) NOT NULL,
`time` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
`target` int(11) NOT NULL,
`unavenged` tinyint(1) NOT NULL DEFAULT '0'
`unavenged` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`player_id`, `target`, `time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Table structure `player_namelocks`
Expand Down Expand Up @@ -732,7 +734,8 @@ CREATE TABLE IF NOT EXISTS `player_bosstiary` (
`bossIdSlotOne` int NOT NULL DEFAULT 0,
`bossIdSlotTwo` int NOT NULL DEFAULT 0,
`removeTimes` int NOT NULL DEFAULT 1,
`tracker` blob NOT NULL
`tracker` blob NOT NULL,
PRIMARY KEY (`player_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Table structure `player_rewards`
Expand Down
2 changes: 1 addition & 1 deletion src/account/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ void Account::updatePremiumTime() {
}
}

std::tuple<phmap::flat_hash_map<std::string, uint64_t>, AccountErrors_t>
std::tuple<std::unordered_map<std::string, uint64_t>, AccountErrors_t>
Account::getAccountPlayers() const {
using enum AccountErrors_t;
auto valueToReturn = m_accLoaded ? Ok : NotInitialized;
Expand Down
2 changes: 1 addition & 1 deletion src/account/account.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class Account {

void updatePremiumTime();

std::tuple<phmap::flat_hash_map<std::string, uint64_t>, AccountErrors_t> getAccountPlayers() const;
std::tuple<std::unordered_map<std::string, uint64_t>, AccountErrors_t> getAccountPlayers() const;

// Old protocol compat
void setProtocolCompat(bool toggle);
Expand Down
3 changes: 1 addition & 2 deletions src/account/account_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#pragma once

#ifndef USE_PRECOMPILED_HEADERS
#include <parallel_hashmap/phmap.h>
#include <cstdint>
#endif

Expand All @@ -23,7 +22,7 @@ struct AccountInfo {
uint32_t premiumRemainingDays = 0;
time_t premiumLastDay = 0;
AccountType accountType = ACCOUNT_TYPE_NONE;
phmap::flat_hash_map<std::string, uint64_t> players;
std::unordered_map<std::string, uint64_t> players;
bool oldProtocol = false;
time_t sessionExpires = 0;
uint32_t premiumDaysPurchased = 0;
Expand Down
3 changes: 3 additions & 0 deletions src/creatures/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ target_sources(${PROJECT_NAME}_lib PRIVATE
npcs/npc.cpp
npcs/npcs.cpp
npcs/spawns/spawn_npc.cpp
players/components/player_forge_history.cpp
players/components/player_storage.cpp
players/components/player_stash.cpp
players/grouping/familiars.cpp
players/grouping/groups.cpp
players/grouping/guild.cpp
Expand Down
2 changes: 1 addition & 1 deletion src/creatures/npcs/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ void Npc::onPlayerSellAllLoot(uint32_t playerId, uint16_t itemId, bool ignore, u
}
bool hasMore = false;
uint64_t toSellCount = 0;
phmap::flat_hash_map<uint16_t, uint16_t> toSell;
std::unordered_map<uint16_t, uint16_t> toSell;
for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
if (toSellCount >= 500) {
hasMore = true;
Expand Down
103 changes: 103 additions & 0 deletions src/creatures/players/components/player_forge_history.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Canary - A free and open-source MMORPG server emulator
* Copyright (©) 2019-2024 OpenTibiaBR <[email protected]>
* Repository: https://github.com/opentibiabr/canary
* License: https://github.com/opentibiabr/canary/blob/main/LICENSE
* Contributors: https://github.com/opentibiabr/canary/graphs/contributors
* Website: https://docs.opentibiabr.org/
*/

#include "creatures/players/components/player_forge_history.hpp"

#include "database/database.hpp"
#include "creatures/players/player.hpp"
#include "utils/tools.hpp"
#include "game/scheduling/save_manager.hpp"

PlayerForgeHistory::PlayerForgeHistory(Player &player) :
m_player(player) { }

const std::vector<ForgeHistory> &PlayerForgeHistory::get() const {
return m_history;
}

void PlayerForgeHistory::add(const ForgeHistory &history) {
m_history.emplace_back(history);
m_modifiedHistory.push_back(history);
}

void PlayerForgeHistory::remove(int historyId) {
m_removedHistoryIds.push_back(historyId);
auto it = std::ranges::remove_if(m_history, [historyId](const ForgeHistory &h) {
return h.id == historyId;
});
m_history.erase(it.begin(), it.end());
}

bool PlayerForgeHistory::load() {
auto playerGUID = m_player.getGUID();
auto query = fmt::format("SELECT * FROM forge_history WHERE player_id = {}", playerGUID);
const DBResult_ptr &result = g_database().storeQuery(query);
if (!result) {
g_logger().debug("Failed to load forge history for player with ID: {}", playerGUID);
return false;
}

do {
ForgeHistory history;
history.id = result->getNumber<int>("id");
history.actionType = static_cast<ForgeAction_t>(result->getNumber<uint8_t>("action_type"));
history.description = result->getString("description");
history.createdAt = result->getNumber<uint64_t>("done_at");
history.success = result->getNumber<bool>("is_success");
m_history.emplace_back(history);
} while (result->next());

return true;
}

bool PlayerForgeHistory::save() {
if (m_modifiedHistory.empty() && m_removedHistoryIds.empty()) {
return true;
}

auto playerGUID = m_player.getGUID();

if (!m_removedHistoryIds.empty()) {
std::string idsToDelete = fmt::format("{}", fmt::join(m_removedHistoryIds, ", "));
std::string deleteQuery = fmt::format(
"DELETE FROM `forge_history` WHERE `player_id` = {} AND `id` IN ({})",
playerGUID, idsToDelete
);

if (!g_database().executeQuery(deleteQuery)) {
g_logger().error("Failed to delete forge history entries for player with ID: {}", playerGUID);
return false;
}

m_removedHistoryIds.clear();
}

DBInsert insertQuery("INSERT INTO `forge_history` (`id`, `player_id`, `action_type`, `description`, `done_at`, `is_success`) VALUES ");
insertQuery.upsert({ "action_type", "description", "done_at", "is_success" });

for (const auto &history : m_modifiedHistory) {
auto row = fmt::format("{}, {}, {}, {}, {}, {}", history.id, playerGUID, history.actionType, g_database().escapeString(history.description), history.createdAt, history.success ? 1 : 0);

if (!insertQuery.addRow(row)) {
g_logger().warn("Failed to add forge history entry for player with ID: {}", playerGUID);
return false;
}

g_logger().debug("Added forge history entry date: {}, for player with ID: {}", formatDate(history.createdAt / 1000), playerGUID);
}

if (!insertQuery.execute()) {
g_logger().error("Failed to execute insertion for forge history entries for player with ID: {}", playerGUID);
return false;
}

m_modifiedHistory.clear();

return true;
}
57 changes: 57 additions & 0 deletions src/creatures/players/components/player_forge_history.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Canary - A free and open-source MMORPG server emulator
* Copyright (©) 2019-2024 OpenTibiaBR <[email protected]>
* Repository: https://github.com/opentibiabr/canary
* License: https://github.com/opentibiabr/canary/blob/main/LICENSE
* Contributors: https://github.com/opentibiabr/canary/graphs/contributors
* Website: https://docs.opentibiabr.org/
*/

#pragma once

#include "enums/forge_conversion.hpp"

class Player;

struct ForgeHistory {
ForgeAction_t actionType = ForgeAction_t::FUSION;
uint8_t tier = 0;
uint8_t bonus = 0;

uint64_t createdAt;

uint16_t id = 0;

uint64_t cost = 0;
uint64_t dustCost = 0;
uint64_t coresCost = 0;
uint64_t gained = 0;

bool success = false;
bool tierLoss = false;
bool successCore = false;
bool tierCore = false;
bool convergence = false;

std::string description;
std::string firstItemName;
std::string secondItemName;
};

class PlayerForgeHistory {
public:
explicit PlayerForgeHistory(Player &player);

const std::vector<ForgeHistory> &get() const;
void add(const ForgeHistory &history);
void remove(int historyId);

bool load();
bool save();

private:
std::vector<ForgeHistory> m_history;
std::vector<ForgeHistory> m_modifiedHistory;
std::vector<uint16_t> m_removedHistoryIds;
Player &m_player;
};
Loading
Loading