diff --git a/common/ruletypes.h b/common/ruletypes.h index ff061c5f8..f1925f421 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -347,6 +347,7 @@ RULE_STRING(World, SupportedClients, "RoF2", "Comma-delimited list of clients to RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom files and sends a key to validate. Empty string to disable. Example: eqcustom_v1") RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files") RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified") +RULE_BOOL(World, RealTimeCalculateGuilds, false, "(Temp feature flag) If true, guilds will be calculated in real time instead of at zone boot. This is a performance hit but allows for more dynamic guilds.") RULE_CATEGORY_END() RULE_CATEGORY(Zone) diff --git a/world/clientlist.cpp b/world/clientlist.cpp index 5f5d6c516..afa365aef 100644 --- a/world/clientlist.cpp +++ b/world/clientlist.cpp @@ -42,11 +42,16 @@ extern ZSList zoneserver_list; uint32 numplayers = 0; //this really wants to be a member variable of ClientList... ClientList::ClientList() -: CLStale_timer(10000) + : CLStale_timer(10000), + m_poll_cache_timer(6000) { NextCLEID = 1; m_tick = std::make_unique(5000, true, std::bind(&ClientList::OnTick, this, std::placeholders::_1)); + + // pre-allocate / pin memory for the zone server caches + m_gm_zone_server_ids.reserve(512); + m_guild_zone_server_ids.reserve(1024); } ClientList::~ClientList() { @@ -57,6 +62,10 @@ void ClientList::Process() { if (CLStale_timer.Check()) CLCheckStale(); + if (m_poll_cache_timer.Check()) { + RebuildZoneServerCaches(); + } + LinkedListIterator iterator(list); iterator.Reset(); @@ -384,6 +393,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s } else { cle->Update(zoneserver, scl); + AddToZoneServerCaches(cle); } return; } @@ -458,6 +468,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s ); clientlist.Insert(cle); + AddToZoneServerCaches(cle); zoneserver->ChangeWID(scl->charid, cle->GetID()); } @@ -1857,71 +1868,97 @@ std::map ClientList::GetGuildClientsWithTributeOptIn( return guild_members; } -#include +void ClientList::RebuildZoneServerCaches() +{ + // Clear without freeing memory (buckets stay allocated) + m_gm_zone_server_ids.clear(); + m_guild_zone_server_ids.clear(); + + LinkedListIterator iterator(clientlist); + iterator.Reset(); + + while (iterator.MoreElements()) { + ClientListEntry* cle = iterator.GetData(); + + if (cle->Online() != CLE_Status::InZone || !cle->Server()) { + iterator.Advance(); + continue; + } + + uint32_t server_id = cle->Server()->GetID(); + + // Track GM zone server + if (cle->GetGM()) { + m_gm_zone_server_ids.insert(server_id); + } + + // Track guild zone servers + if (cle->GuildID() > 0) { + auto& guild_set = m_guild_zone_server_ids[cle->GuildID()]; + guild_set.insert(server_id); + } + + iterator.Advance(); + } +} std::vector ClientList::GetGuildZoneServers(uint32 guild_id) { - std::vector zone_server_ids; - std::unordered_set seen_ids; + if (RuleB(World, RealTimeCalculateGuilds)) { + std::vector zone_server_ids; + std::unordered_set seen_ids; - LinkedListIterator iterator(clientlist); + LinkedListIterator iterator(clientlist); - iterator.Reset(); - while (iterator.MoreElements()) { - ClientListEntry *cle = iterator.GetData(); + iterator.Reset(); + while (iterator.MoreElements()) { + ClientListEntry *cle = iterator.GetData(); - if (cle->Online() != CLE_Status::InZone) { - iterator.Advance(); - continue; - } - - if (!cle->Server()) { - iterator.Advance(); - continue; - } - - if (cle->GuildID() == guild_id) { - uint32_t id = cle->Server()->GetID(); - if (seen_ids.insert(id).second) { - zone_server_ids.emplace_back(id); + if (cle->Online() != CLE_Status::InZone) { + iterator.Advance(); + continue; } + + if (!cle->Server()) { + iterator.Advance(); + continue; + } + + if (cle->GuildID() == guild_id) { + uint32_t id = cle->Server()->GetID(); + if (seen_ids.insert(id).second) { + zone_server_ids.emplace_back(id); + } + } + + iterator.Advance(); } - iterator.Advance(); + return zone_server_ids; } - return zone_server_ids; + auto it = m_guild_zone_server_ids.find(guild_id); + if (it == m_guild_zone_server_ids.end()) { + return {}; + } + return {it->second.begin(), it->second.end()}; } -std::vector ClientList::GetZoneServersWithGMs() +void ClientList::AddToZoneServerCaches(ClientListEntry* cle) { - std::vector zone_server_ids; - std::unordered_set seen_ids; - LinkedListIterator iterator(clientlist); - - iterator.Reset(); - while (iterator.MoreElements()) { - ClientListEntry *cle = iterator.GetData(); - - if (cle->Online() != CLE_Status::InZone) { - iterator.Advance(); - continue; - } - - if (!cle->Server()) { - iterator.Advance(); - continue; - } - - if (cle->Admin() > 0) { - uint32_t id = cle->Server()->GetID(); - if (seen_ids.insert(id).second) { - zone_server_ids.emplace_back(id); - } - } - - iterator.Advance(); + if (!cle || cle->Online() != CLE_Status::InZone || !cle->Server()) { + return; } - return zone_server_ids; + uint32_t server_id = cle->Server()->GetID(); + + // Add GM zone server if applicable + if (cle->GetGM()) { + m_gm_zone_server_ids.insert(server_id); + } + + // Add guild zone server if applicable + if (cle->GuildID() > 0) { + m_guild_zone_server_ids[cle->GuildID()].insert(server_id); + } } diff --git a/world/clientlist.h b/world/clientlist.h index 5578f9e7c..fbf8c290c 100644 --- a/world/clientlist.h +++ b/world/clientlist.h @@ -60,8 +60,6 @@ public: void CLCheckStale(); void CLEKeepAlive(uint32 numupdates, uint32* wid); void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0); - std::vector GetGuildZoneServers(uint32 guild_id); - std::vector GetZoneServersWithGMs(); void UpdateClientGuild(uint32 char_id, uint32 guild_id); bool IsAccountInGame(uint32 iLSID); @@ -78,6 +76,15 @@ public: void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list args = {}); void SendCharacterMessageID(ClientListEntry* character, int chat_type, int eqstr_id, std::initializer_list args = {}); + void AddToZoneServerCaches(ClientListEntry* cle); + void RebuildZoneServerCaches(); + + std::vector GetGuildZoneServers(uint32 guild_id); + inline std::vector GetZoneServersWithGMs() + { + return {m_gm_zone_server_ids.begin(), m_gm_zone_server_ids.end()}; + } + private: void OnTick(EQ::Timer *t); inline uint32 GetNextCLEID() { return NextCLEID++; } @@ -92,6 +99,11 @@ private: std::unique_ptr m_tick; + + // Zone server routing caches + Timer m_poll_cache_timer; + std::unordered_set m_gm_zone_server_ids; + std::unordered_map> m_guild_zone_server_ids; }; #endif /*CLIENTLIST_H_*/