From 8993dd5a7aa588bf8ddb98690a935343e96f3082 Mon Sep 17 00:00:00 2001 From: stribog Date: Sat, 6 Jun 2026 12:42:48 +0200 Subject: [PATCH 1/2] =?UTF-8?q?perf(point=5Fupdate):=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=B0=D1=82=D1=8B=D0=B2=D0=B0=D1=82=D1=8C=20=D1=82?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2?= =?UTF-8?q?=D0=BD=D1=8B=D1=85,=20=D0=B1=D0=B5=D0=B7=20=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=20character=5Flist=20(#3414)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Пример решения по #3414 (шаг к #3180, без новых глобалов). point_update раньше каждый MUD-час шёл по всему character_list (все мобы мира, десятки тысяч), пропуская не-активных фильтром in_used_zone -- сам скан и давал спайк ~111 мс. Теперь активный набор ведётся в самом реестре Characters (а не новый глобал): - m_active -- мобы, помеченные mobile_activity (одна O(1)-вставка в точке ++processed_mobs), сбрасывается каждый point_update; - m_players -- все игроки в игре (наполняется в push_front, чистится в remove), чтобы поведение PC не изменилось (в т.ч. link-dead). point_update берёт снимок active()+players() и идёт по нему; висячие указатели исключены чисткой в Characters::remove + проверкой purged(). Семантика мобов меняется: регенерируются только активные мобы (для простаивающих безвредно). На валидацию. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/engine/db/world_characters.cpp | 6 ++++++ src/engine/db/world_characters.h | 10 ++++++++++ src/gameplay/ai/mobact.cpp | 1 + src/gameplay/core/game_limits.cpp | 21 ++++++++++++++++----- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/engine/db/world_characters.cpp b/src/engine/db/world_characters.cpp index 6183ef3b40..885181aee1 100644 --- a/src/engine/db/world_characters.cpp +++ b/src/engine/db/world_characters.cpp @@ -52,6 +52,10 @@ void Characters::push_front(const CharData::shared_ptr &character) { } character->subscribe_for_rnum_changes(m_rnum_change_observer); + if (!character->IsNpc()) { + m_players.insert(character.get()); // #3414: все игроки в игре + } + if (character->purged()) { /* * Anton Gorev (2017/10/29): It is possible is character quit the game without @@ -166,6 +170,8 @@ void Characters::remove(CharData *character) { character->ZeroCooldowns(); chardata_cooldown_list.erase(clist); } + m_active.erase(character); // #3414: убрать из активного набора + m_players.erase(character); // #3414: убрать из списка игроков m_list.erase(index_i->second); m_character_raw_ptr_to_character_ptr.erase(index_i); auto tmp_it = std::find_if(combat_list.begin(), combat_list.end(), [character] (auto it) {return (it.ch == character); }); diff --git a/src/engine/db/world_characters.h b/src/engine/db/world_characters.h index 081e9b162f..9d88b6a902 100644 --- a/src/engine/db/world_characters.h +++ b/src/engine/db/world_characters.h @@ -54,6 +54,14 @@ class Characters { void AddToExtractedList(CharData *ch); void PurgeExtractedList(); + // #3414: активный набор для point_update. Мобы помечаются в mobile_activity + // (mark_active) и сбрасываются каждый point_update (clear_active); игроки + // держатся постоянно (наполняется в push_front, чистится в remove). + void mark_active(CharData *ch) { m_active.insert(ch); } + const auto &active() const { return m_active; } + const auto &players() const { return m_players; } + void clear_active() { m_active.clear(); } + private: using character_raw_ptr_to_character_ptr_t = std::unordered_map; using set_t = std::unordered_set; @@ -63,6 +71,8 @@ class Characters { std::unordered_set m_extracted_list; character_raw_ptr_to_character_ptr_t m_character_raw_ptr_to_character_ptr; vnum_to_characters_set_t m_vnum_to_characters_set; + std::unordered_set m_active; // #3414: активные мобы (за окно между point_update) + std::unordered_set m_players; // #3414: все игроки в игре CharacterRNum_ChangeObserver::shared_ptr m_rnum_change_observer; list_t m_purge_list; set_t m_purge_set; diff --git a/src/gameplay/ai/mobact.cpp b/src/gameplay/ai/mobact.cpp index dfd52d61d9..1e301a0553 100644 --- a/src/gameplay/ai/mobact.cpp +++ b/src/gameplay/ai/mobact.cpp @@ -931,6 +931,7 @@ void mobile_activity(int activity_level, int missed_pulses) { continue; } ++processed_mobs; + character_list.mark_active(ch.get()); // #3414: запомнить активного моба для point_update // Examine call for special procedure if (ch->IsFlagged(EMobFlag::kSpec) && !no_specials) { diff --git a/src/gameplay/core/game_limits.cpp b/src/gameplay/core/game_limits.cpp index 32027078a0..07c9ef1e2e 100644 --- a/src/gameplay/core/game_limits.cpp +++ b/src/gameplay/core/game_limits.cpp @@ -1534,13 +1534,24 @@ void point_update() { double t_cond = 0.0, t_hp = 0.0, t_mob = 0.0, t_move = 0.0, t_pos = 0.0, t_idle = 0.0; std::size_t scanned = 0, profiled_chars = 0; - for (auto &ch : character_list) { - const auto i = ch.get(); - ++scanned; + // #3414: вместо скана всего character_list -- только активные мобы + // (их помечает mobile_activity) и все игроки в игре. Снимок указателей, + // чтобы отложенные extract не ломали обход set'ов между итерациями. + std::vector to_update; + to_update.reserve(character_list.active().size() + character_list.players().size()); + for (auto *m : character_list.active()) { + to_update.push_back(m); + } + for (auto *p : character_list.players()) { + to_update.push_back(p); + } + character_list.clear_active(); - if (i->purged() || (i->IsNpc() && !i->in_used_zone())) { + for (auto *i : to_update) { + ++scanned; + if (i->purged()) { continue; - } + } if (i->IsNpc()) { i->inc_restore_timer(kSecsPerMudHour); From e92a263b592b849d9b9e9abf2b63c332820bce2a Mon Sep 17 00:00:00 2001 From: stribog Date: Sat, 6 Jun 2026 15:21:10 +0200 Subject: [PATCH 2/2] =?UTF-8?q?refactor(point=5Fupdate):=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20=D1=81=D0=B1=D0=BE=D1=80=20?= =?UTF-8?q?=D1=81=D0=BD=D0=B8=D0=BC=D0=BA=D0=B0=20=D0=B2=20Characters::act?= =?UTF-8?q?ive=5Fand=5Fplayers()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit По ревью @kvirund: два цикла active()+players() в point_update заменены на один вызов метода реестра. Читается проще и логика "активные+игроки" уезжает к своему владельцу Characters (в духе #3180). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/engine/db/world_characters.h | 9 +++++++++ src/gameplay/core/game_limits.cpp | 15 ++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/engine/db/world_characters.h b/src/engine/db/world_characters.h index 9d88b6a902..9185b33f67 100644 --- a/src/engine/db/world_characters.h +++ b/src/engine/db/world_characters.h @@ -7,6 +7,7 @@ #include #include #include +#include class Characters { public: @@ -61,6 +62,14 @@ class Characters { const auto &active() const { return m_active; } const auto &players() const { return m_players; } void clear_active() { m_active.clear(); } + // Снимок набора для point_update: активные мобы + все игроки (одним вектором). + std::vector active_and_players() const { + std::vector result; + result.reserve(m_active.size() + m_players.size()); + result.insert(result.end(), m_active.begin(), m_active.end()); + result.insert(result.end(), m_players.begin(), m_players.end()); + return result; + } private: using character_raw_ptr_to_character_ptr_t = std::unordered_map; diff --git a/src/gameplay/core/game_limits.cpp b/src/gameplay/core/game_limits.cpp index 07c9ef1e2e..dd84b45f2a 100644 --- a/src/gameplay/core/game_limits.cpp +++ b/src/gameplay/core/game_limits.cpp @@ -1534,17 +1534,10 @@ void point_update() { double t_cond = 0.0, t_hp = 0.0, t_mob = 0.0, t_move = 0.0, t_pos = 0.0, t_idle = 0.0; std::size_t scanned = 0, profiled_chars = 0; - // #3414: вместо скана всего character_list -- только активные мобы - // (их помечает mobile_activity) и все игроки в игре. Снимок указателей, - // чтобы отложенные extract не ломали обход set'ов между итерациями. - std::vector to_update; - to_update.reserve(character_list.active().size() + character_list.players().size()); - for (auto *m : character_list.active()) { - to_update.push_back(m); - } - for (auto *p : character_list.players()) { - to_update.push_back(p); - } + // #3414: вместо скана всего character_list -- только активные мобы (их + // помечает mobile_activity) и все игроки в игре. Снимок, чтобы отложенные + // extract не ломали обход set'ов между итерациями. + const auto to_update = character_list.active_and_players(); character_list.clear_active(); for (auto *i : to_update) {