diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 157b487e3..754e7c31a 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -62,6 +62,42 @@ DynamicZone DynamicZone::LoadDzFromDatabase(uint32_t instance_id) return dynamic_zone; } +std::unordered_map DynamicZone::LoadMultipleDzFromDatabase( + const std::vector& instance_ids) +{ + LogDynamicZonesDetail("Loading dynamic zone data for [{}] instances", instance_ids.size()); + + std::string in_instance_ids_query; + for (const auto& instance_id : instance_ids) + { + fmt::format_to(std::back_inserter(in_instance_ids_query), "{},", instance_id); + } + + std::unordered_map dynamic_zones; + + if (!in_instance_ids_query.empty()) + { + in_instance_ids_query.pop_back(); // trailing comma + + std::string query = fmt::format(SQL( + {} WHERE dynamic_zones.instance_id IN ({}); + ), DynamicZoneSelectQuery(), in_instance_ids_query); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + DynamicZone dz; + dz.LoadDatabaseResult(row); + dynamic_zones.emplace(dz.GetInstanceID(), dz); + } + } + } + + return dynamic_zones; +} + uint32_t DynamicZone::CreateInstance() { if (m_instance_id) @@ -107,26 +143,11 @@ uint32_t DynamicZone::CreateInstance() return m_instance_id; } -void DynamicZone::LoadFromDatabase(uint32_t instance_id) +std::string DynamicZone::DynamicZoneSelectQuery() { - if (instance_id == 0) - { - return; - } - - if (m_instance_id) - { - LogDynamicZones( - "Loading instance data for [{}] failed, instance id [{}] data already loaded", - instance_id, m_instance_id - ); - return; - } - - LogDynamicZonesDetail("Loading dz instance [{}] from database", instance_id); - - std::string query = fmt::format(SQL( + return std::string(SQL( SELECT + instance_list.id, instance_list.zone, instance_list.version, instance_list.start_time, @@ -149,36 +170,53 @@ void DynamicZone::LoadFromDatabase(uint32_t instance_id) dynamic_zones.has_zone_in FROM dynamic_zones INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id - WHERE dynamic_zones.instance_id = {}; - ), instance_id); + )); +} + +void DynamicZone::LoadDatabaseResult(MySQLRequestRow& row) +{ + m_instance_id = strtoul(row[0], nullptr, 10); + m_zone_id = strtoul(row[1], nullptr, 10); + m_version = strtoul(row[2], nullptr, 10); + m_start_time = strtoul(row[3], nullptr, 10); + m_duration = strtoul(row[4], nullptr, 10); + m_never_expires = (strtoul(row[5], nullptr, 10) != 0); + m_type = static_cast(strtoul(row[6], nullptr, 10)); + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + m_compass.zone_id = strtoul(row[7], nullptr, 10); + m_compass.x = strtof(row[8], nullptr); + m_compass.y = strtof(row[9], nullptr); + m_compass.z = strtof(row[10], nullptr); + m_safereturn.zone_id = strtoul(row[11], nullptr, 10); + m_safereturn.x = strtof(row[12], nullptr); + m_safereturn.y = strtof(row[13], nullptr); + m_safereturn.z = strtof(row[14], nullptr); + m_safereturn.heading = strtof(row[15], nullptr); + m_zonein.x = strtof(row[16], nullptr); + m_zonein.y = strtof(row[17], nullptr); + m_zonein.z = strtof(row[18], nullptr); + m_zonein.heading = strtof(row[19], nullptr); + m_has_zonein = (strtoul(row[20], nullptr, 10) != 0); +} + +void DynamicZone::LoadFromDatabase(uint32_t instance_id) +{ + if (instance_id == 0) + { + return; + } + + LogDynamicZonesDetail("Loading dz instance [{}] from database", instance_id); + + std::string query = fmt::format(SQL( + {} WHERE dynamic_zones.instance_id = {}; + ), DynamicZoneSelectQuery(), instance_id); auto results = database.QueryDatabase(query); if (results.Success() && results.RowCount() > 0) { auto row = results.begin(); - - m_instance_id = instance_id; - m_zone_id = strtoul(row[0], nullptr, 10); - m_version = strtoul(row[1], nullptr, 10); - m_start_time = strtoul(row[2], nullptr, 10); - m_duration = strtoul(row[3], nullptr, 10); - m_never_expires = (strtoul(row[4], nullptr, 10) != 0); - m_type = static_cast(strtoul(row[5], nullptr, 10)); - m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); - m_compass.zone_id = strtoul(row[6], nullptr, 10); - m_compass.x = strtof(row[7], nullptr); - m_compass.y = strtof(row[8], nullptr); - m_compass.z = strtof(row[9], nullptr); - m_safereturn.zone_id = strtoul(row[10], nullptr, 10); - m_safereturn.x = strtof(row[11], nullptr); - m_safereturn.y = strtof(row[12], nullptr); - m_safereturn.z = strtof(row[13], nullptr); - m_safereturn.heading = strtof(row[14], nullptr); - m_zonein.x = strtof(row[15], nullptr); - m_zonein.y = strtof(row[16], nullptr); - m_zonein.z = strtof(row[17], nullptr); - m_zonein.heading = strtof(row[18], nullptr); - m_has_zonein = (strtoul(row[19], nullptr, 10) != 0); + LoadDatabaseResult(row); } } diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index f9a64276b..54c58be9f 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -24,12 +24,14 @@ #include #include #include +#include #include #include +class MySQLRequestRow; class ServerPacket; -enum class DynamicZoneType : uint8_t // DynamicZoneActiveType +enum class DynamicZoneType : uint8_t { None = 0, Expedition, @@ -47,7 +49,7 @@ struct DynamicZoneLocation float z = 0.0f; float heading = 0.0f; - DynamicZoneLocation() {} + DynamicZoneLocation() = default; DynamicZoneLocation(uint32_t zone_id_, float x_, float y_, float z_, float heading_) : zone_id(zone_id_), x(x_), y(y_), z(z_), heading(heading_) {} }; @@ -58,9 +60,12 @@ public: DynamicZone() = default; DynamicZone(uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type); DynamicZone(std::string zone_shortname, uint32_t version, uint32_t duration, DynamicZoneType type); - DynamicZone(DynamicZoneType type) : m_type(type) { } + DynamicZone(uint32_t instance_id) : m_instance_id(instance_id) {} + DynamicZone(DynamicZoneType type) : m_type(type) {} static DynamicZone LoadDzFromDatabase(uint32_t instance_id); + static std::unordered_map LoadMultipleDzFromDatabase( + const std::vector& instance_ids); static void HandleWorldMessage(ServerPacket* pack); DynamicZoneType GetType() const { return m_type; } @@ -95,6 +100,8 @@ public: uint32_t SaveToDatabase(); private: + static std::string DynamicZoneSelectQuery(); + void LoadDatabaseResult(MySQLRequestRow& row); void DeleteFromDatabase(); void SaveCompassToDatabase(); void SaveSafeReturnToDatabase(); @@ -111,7 +118,7 @@ private: DynamicZoneLocation m_compass; DynamicZoneLocation m_safereturn; DynamicZoneLocation m_zonein; - std::chrono::time_point m_expire_time; //uint64_t m_expire_time = 0; + std::chrono::time_point m_expire_time; }; #endif diff --git a/zone/expedition.cpp b/zone/expedition.cpp index d326131b3..2938882d4 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -153,58 +153,95 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) return; } + std::vector expedition_ids; + std::vector instance_ids; + + using col = LoadExpeditionColumns::eLoadExpeditionColumns; + + Expedition* current_expedition = nullptr; uint32_t last_expedition_id = 0; + for (auto row = results.begin(); row != results.end(); ++row) { - auto expedition_id = strtoul(row[0], nullptr, 10); + auto expedition_id = strtoul(row[col::id], nullptr, 10); + if (expedition_id != last_expedition_id) { - auto leader_id = static_cast(strtoul(row[3], nullptr, 10)); - ExpeditionMember leader{ leader_id, row[9] }; // id, name - auto instance_id = row[1] ? strtoul(row[1], nullptr, 10) : 0; // can be null from fk constraint + // finished parsing previous expedition members, send member updates + if (current_expedition) + { + current_expedition->SendWorldGetOnlineMembers(); + current_expedition->SendUpdatesToZoneMembers(); + } - DynamicZone dynamic_zone = DynamicZone::LoadDzFromDatabase(instance_id); + expedition_ids.emplace_back(expedition_id); + + uint32_t leader_id = strtoul(row[col::leader_id], nullptr, 10); + uint32_t instance_id = row[col::instance_id] ? strtoul(row[col::instance_id], nullptr, 10) : 0; + if (instance_id) // can be null from fk constraint + { + instance_ids.emplace_back(instance_id); + } std::unique_ptr expedition = std::unique_ptr(new Expedition( expedition_id, - dynamic_zone, - row[2], // expedition name - leader, // expedition leader - strtoul(row[4], nullptr, 10), // min_players - strtoul(row[5], nullptr, 10), // max_players - (strtoul(row[6], nullptr, 10) != 0) // has_replay_timer + DynamicZone{instance_id}, + row[col::expedition_name], // expedition name + ExpeditionMember{leader_id, row[col::leader_name]}, // expedition leader id, name + strtoul(row[col::min_players], nullptr, 10), // min_players + strtoul(row[col::max_players], nullptr, 10), // max_players + (strtoul(row[col::has_replay_timer], nullptr, 10) != 0) // has_replay_timer )); - bool add_replay_on_join = (strtoul(row[7], nullptr, 10) != 0); - bool is_locked = (strtoul(row[8], nullptr, 10) != 0); + bool add_replay_on_join = (strtoul(row[col::add_replay_on_join], nullptr, 10) != 0); + bool is_locked = (strtoul(row[col::is_locked], nullptr, 10) != 0); expedition->SetReplayLockoutOnMemberJoin(add_replay_on_join); expedition->SetLocked(is_locked); - expedition->LoadMembers(); - expedition->SendUpdatesToZoneMembers(); - // don't bother caching empty expeditions - if (expedition->GetMemberCount() > 0) - { - zone->expedition_cache.emplace(expedition_id, std::move(expedition)); - } + zone->expedition_cache.emplace(expedition_id, std::move(expedition)); } last_expedition_id = expedition_id; - // optional lockouts from left join - if (row[10] && row[11] && row[12] && row[13]) + // looping expedition members + current_expedition = Expedition::FindCachedExpeditionByID(last_expedition_id); + if (current_expedition) { - auto it = zone->expedition_cache.find(last_expedition_id); - if (it != zone->expedition_cache.end()) + auto member_id = strtoul(row[col::member_id], nullptr, 10); + bool is_current_member = (strtoul(row[col::is_current_member], nullptr, 10) != 0); + current_expedition->AddInternalMember( + row[col::member_name], member_id, ExpeditionMemberStatus::Offline, is_current_member + ); + } + } + + // update for the last cached expedition + if (current_expedition) + { + current_expedition->SendWorldGetOnlineMembers(); + current_expedition->SendUpdatesToZoneMembers(); + } + + // bulk load dynamic zone data and expedition lockouts for cached expeditions + auto dynamic_zones = DynamicZone::LoadMultipleDzFromDatabase(instance_ids); + auto expedition_lockouts = ExpeditionDatabase::LoadMultipleExpeditionLockouts(expedition_ids); + + for (const auto& expedition_id : expedition_ids) + { + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) + { + auto dz_iter = dynamic_zones.find(expedition->GetInstanceID()); + if (dz_iter != dynamic_zones.end()) { - it->second->AddInternalLockout(ExpeditionLockoutTimer{ - row[2], // expedition_name - row[10], // event_name - strtoull(row[11], nullptr, 10), // expire_time - static_cast(strtoul(row[12], nullptr, 10)), // original duration - (strtoul(row[13], nullptr, 10) != 0) // is_inherited - }); + expedition->m_dynamiczone = dz_iter->second; + } + + auto lockout_iter = expedition_lockouts.find(expedition->GetID()); + if (lockout_iter != expedition_lockouts.end()) + { + expedition->m_lockouts = lockout_iter->second; } } } @@ -257,23 +294,6 @@ bool Expedition::CacheAllFromDatabase() return true; } -void Expedition::LoadMembers() -{ - m_members.clear(); - - auto results = ExpeditionDatabase::LoadExpeditionMembers(m_id); - if (results.Success()) - { - for (auto row = results.begin(); row != results.end(); ++row) - { - auto character_id = strtoul(row[0], nullptr, 10); - bool is_current_member = strtoul(row[1], nullptr, 10); - AddInternalMember(row[2], character_id, ExpeditionMemberStatus::Offline, is_current_member); - } - SendWorldGetOnlineMembers(); - } -} - void Expedition::SaveLockouts(ExpeditionRequest& request) { m_lockouts = std::move(request).TakeLockouts(); diff --git a/zone/expedition.h b/zone/expedition.h index 403dff438..11bc6fda4 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -139,7 +139,6 @@ private: void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status, bool is_current_member = true); bool ChooseNewLeader(); bool ConfirmLeaderCommand(Client* requester); - void LoadMembers(); bool ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping); void ProcessLeaderChanged(uint32_t new_leader_id, const std::string& new_leader_name); void ProcessLockoutUpdate(const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index fb913a9b2..1c4bf6207 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -48,11 +48,9 @@ uint32_t ExpeditionDatabase::InsertExpedition( return results.LastInsertedID(); } -MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) +std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() { - LogExpeditionsDetail("Loading expedition [{}]", expedition_id); - - std::string query = fmt::format(SQL( + return std::string(SQL( SELECT expedition_details.id, expedition_details.instance_id, @@ -64,53 +62,36 @@ MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) expedition_details.add_replay_on_join, expedition_details.is_locked, character_data.name leader_name, - expedition_lockouts.event_name, - UNIX_TIMESTAMP(expedition_lockouts.expire_time), - expedition_lockouts.duration, - expedition_lockouts.is_inherited + expedition_members.character_id, + expedition_members.is_current_member, + member_data.name FROM expedition_details INNER JOIN character_data ON expedition_details.leader_id = character_data.id - LEFT JOIN expedition_lockouts - ON expedition_details.id = expedition_lockouts.expedition_id - AND expedition_lockouts.expire_time > NOW() - WHERE expedition_details.id = {}; - ), expedition_id); + INNER JOIN expedition_members ON expedition_details.id = expedition_members.expedition_id + INNER JOIN character_data member_data ON expedition_members.character_id = member_data.id + )); +} - auto results = database.QueryDatabase(query); - return results; +MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) +{ + LogExpeditionsDetail("Loading expedition [{}]", expedition_id); + + std::string query = fmt::format(SQL( + {} WHERE expedition_details.id = {}; + ), LoadExpeditionsSelectQuery(), expedition_id); + + return database.QueryDatabase(query); } MySQLRequestResult ExpeditionDatabase::LoadAllExpeditions() { LogExpeditionsDetail("Loading all expeditions from database"); - // load all active expeditions and members to current zone cache - std::string query = SQL( - SELECT - expedition_details.id, - expedition_details.instance_id, - expedition_details.expedition_name, - expedition_details.leader_id, - expedition_details.min_players, - expedition_details.max_players, - expedition_details.has_replay_timer, - expedition_details.add_replay_on_join, - expedition_details.is_locked, - character_data.name leader_name, - expedition_lockouts.event_name, - UNIX_TIMESTAMP(expedition_lockouts.expire_time), - expedition_lockouts.duration, - expedition_lockouts.is_inherited - FROM expedition_details - INNER JOIN character_data ON expedition_details.leader_id = character_data.id - LEFT JOIN expedition_lockouts - ON expedition_details.id = expedition_lockouts.expedition_id - AND expedition_lockouts.expire_time > NOW() - ORDER BY expedition_details.id; - ); + std::string query = fmt::format(SQL( + {} ORDER BY expedition_details.id; + ), LoadExpeditionsSelectQuery()); - auto results = database.QueryDatabase(query); - return results; + return database.QueryDatabase(query); } MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts(uint32_t character_id) @@ -151,26 +132,58 @@ MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts( return database.QueryDatabase(query); } -MySQLRequestResult ExpeditionDatabase::LoadExpeditionMembers(uint32_t expedition_id) +std::unordered_map> +ExpeditionDatabase::LoadMultipleExpeditionLockouts( + const std::vector& expedition_ids) { - LogExpeditionsDetail("Loading all members for expedition [{}]", expedition_id); + LogExpeditionsDetail("Loading internal lockouts for [{}] expeditions", expedition_ids.size()); - std::string query = fmt::format(SQL( - SELECT - expedition_members.character_id, - expedition_members.is_current_member, - character_data.name - FROM expedition_members - INNER JOIN character_data ON expedition_members.character_id = character_data.id - WHERE expedition_id = {}; - ), expedition_id); - - auto results = database.QueryDatabase(query); - if (!results.Success()) + std::string in_expedition_ids_query; + for (const auto& expedition_id : expedition_ids) { - LogExpeditions("Failed to load expedition [{}] members from db", expedition_id); + fmt::format_to(std::back_inserter(in_expedition_ids_query), "{},", expedition_id); } - return results; + + // these are loaded into the same container type expedition's use to store lockouts + std::unordered_map> lockouts; + + if (!in_expedition_ids_query.empty()) + { + in_expedition_ids_query.pop_back(); // trailing comma + + std::string query = fmt::format(SQL( + SELECT + expedition_lockouts.expedition_id, + expedition_lockouts.event_name, + UNIX_TIMESTAMP(expedition_lockouts.expire_time), + expedition_lockouts.duration, + expedition_lockouts.is_inherited, + expedition_details.expedition_name + FROM expedition_lockouts + INNER JOIN expedition_details ON expedition_lockouts.expedition_id = expedition_details.id + WHERE expedition_id IN ({}) + ORDER BY expedition_id; + ), in_expedition_ids_query); + + auto results = database.QueryDatabase(query); + + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + auto expedition_id = strtoul(row[0], nullptr, 10); + lockouts[expedition_id].emplace(row[1], ExpeditionLockoutTimer{ + row[5], // expedition_name + row[1], // event_name + strtoull(row[2], nullptr, 10), // expire_time + static_cast(strtoul(row[3], nullptr, 10)), // original duration + (strtoul(row[4], nullptr, 10) != 0) // is_inherited + }); + } + } + } + + return lockouts; } MySQLRequestResult ExpeditionDatabase::LoadValidationData( @@ -200,8 +213,7 @@ MySQLRequestResult ExpeditionDatabase::LoadValidationData( ORDER BY character_data.id; ), expedition_name, character_names); - auto results = database.QueryDatabase(query); - return results; + return database.QueryDatabase(query); } void ExpeditionDatabase::DeleteAllCharacterLockouts(uint32_t character_id) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 1773d2550..e6e35368e 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -38,12 +38,14 @@ namespace ExpeditionDatabase uint32_t InsertExpedition( uint32_t instance_id, const std::string& expedition_name, uint32_t leader_id, uint32_t min_players, uint32_t max_players, bool has_replay_lockout); + std::string LoadExpeditionsSelectQuery(); MySQLRequestResult LoadExpedition(uint32_t expedition_id); MySQLRequestResult LoadAllExpeditions(); MySQLRequestResult LoadCharacterLockouts(uint32_t character_id); MySQLRequestResult LoadCharacterLockouts(uint32_t character_id, const std::string& expedition_name); - MySQLRequestResult LoadExpeditionMembers(uint32_t expedition_id); MySQLRequestResult LoadValidationData(const std::string& character_names_query, const std::string& expedition_name); + std::unordered_map> + LoadMultipleExpeditionLockouts(const std::vector& expedition_ids); void DeleteAllCharacterLockouts(uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id, const std::string& expedition_name); void DeleteCharacterLockout(uint32_t character_id, const std::string& expedition_name, const std::string& event_name); @@ -70,4 +72,24 @@ namespace ExpeditionDatabase void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); }; +namespace LoadExpeditionColumns +{ + enum eLoadExpeditionColumns + { + id = 0, + instance_id, + expedition_name, + leader_id, + min_players, + max_players, + has_replay_timer, + add_replay_on_join, + is_locked, + leader_name, + member_id, + is_current_member, + member_name + }; +}; + #endif