Skip to content

Commit e7c1591

Browse files
authored
perf: npc/monster storage with vector indexing (#3209)
This modifies the internal storage of NPCs and monsters in the Game class from std::unordered_map to a combination of std::vector and std::unordered_map indexes for improved lookup efficiency and better memory locality. Motivation Previously, every time a search was performed for an NPC or a monster, the unordered map was iterated, and even though it provides average O(1) complexity for lookups, the actual cost becomes significant when handling a large dataset (e.g., 80,000 monsters). This cost arises from the overhead of hashing and poor memory locality, as unordered maps store elements in a hash table. Switching to a vector with index-based lookup improves the following: 1. Memory locality: Vectors store elements contiguously in memory, improving cache efficiency when accessing elements sequentially or repeatedly. 2. Lookup efficiency: Using an unordered map as an index to the vector allows leveraging direct index-based access (constant time) to the actual data, combining the best of both structures.
1 parent 09024a7 commit e7c1591

10 files changed

Lines changed: 146 additions & 53 deletions

File tree

src/creatures/combat/condition.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,6 +1769,10 @@ bool ConditionDamage::getNextDamage(int32_t &damage) {
17691769
}
17701770

17711771
bool ConditionDamage::doDamage(const std::shared_ptr<Creature> &creature, int32_t healthChange) const {
1772+
if (owner == 0) {
1773+
return false;
1774+
}
1775+
17721776
const auto &attacker = g_game().getPlayerByGUID(owner) ? g_game().getPlayerByGUID(owner)->getCreature() : g_game().getCreatureByID(owner);
17731777
bool isPlayer = attacker && attacker->getPlayer();
17741778
if (creature->isSuppress(getType(), isPlayer)) {

src/creatures/monsters/monster.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ std::shared_ptr<Monster> Monster::createMonster(const std::string &name) {
3535
}
3636

3737
Monster::Monster(const std::shared_ptr<MonsterType> &mType) :
38+
m_lowerName(asLowerCaseString(mType->name)),
3839
nameDescription(asLowerCaseString(mType->nameDescription)),
3940
mType(mType) {
4041
defaultOutfit = mType->info.outfit;

src/creatures/monsters/monster.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ class Monster final : public Creature {
4949
void setNameDescription(std::string_view nameDescription);
5050
std::string getDescription(int32_t) override;
5151

52+
const std::string &getLowerName() const {
53+
return m_lowerName;
54+
}
55+
5256
CreatureType_t getType() const override;
5357

5458
const Position &getMasterPos() const;
@@ -244,6 +248,7 @@ class Monster final : public Creature {
244248
ForgeClassifications_t monsterForgeClassification = ForgeClassifications_t::FORGE_NORMAL_MONSTER;
245249

246250
std::string name;
251+
std::string m_lowerName;
247252
std::string nameDescription;
248253

249254
std::shared_ptr<MonsterType> mType;

src/creatures/npcs/npc.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ void Npc::setName(std::string newName) const {
9797
npcType->name = std::move(newName);
9898
}
9999

100+
const std::string &Npc::getLowerName() const {
101+
return npcType->m_lowerName;
102+
}
103+
100104
CreatureType_t Npc::getType() const {
101105
return CREATURETYPE_NPC;
102106
}

src/creatures/npcs/npc.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class Npc final : public Creature {
5151

5252
void setName(std::string newName) const;
5353

54+
const std::string &getLowerName() const;
55+
5456
CreatureType_t getType() const override;
5557

5658
const Position &getMasterPos() const;

src/creatures/npcs/npcs.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include "lua/scripts/scripts.hpp"
1717
#include "lib/di/container.hpp"
1818

19+
NpcType::NpcType(const std::string &initName) :
20+
name(initName), m_lowerName(asLowerCaseString(initName)), typeName(initName), nameDescription(initName) {};
21+
1922
bool NpcType::canSpawn(const Position &pos) const {
2023
bool canSpawn = true;
2124
const bool isDay = g_game().gameIsDay();

src/creatures/npcs/npcs.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ class NpcType final : public SharedObject {
7777

7878
public:
7979
NpcType() = default;
80-
explicit NpcType(const std::string &initName) :
81-
name(initName), typeName(initName), nameDescription(initName) {};
80+
explicit NpcType(const std::string &initName);
8281

8382
// non-copyable
8483
NpcType(const NpcType &) = delete;
8584
NpcType &operator=(const NpcType &) = delete;
8685

8786
std::string name;
87+
std::string m_lowerName;
8888
std::string typeName;
8989
std::string nameDescription;
9090
NpcInfo info;

src/game/game.cpp

Lines changed: 97 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -424,15 +424,15 @@ Game &Game::getInstance() {
424424
}
425425

426426
void Game::resetMonsters() const {
427-
for (const auto &[monsterId, monster] : getMonsters()) {
427+
for (const auto &monster : getMonsters()) {
428428
monster->clearTargetList();
429429
monster->clearFriendList();
430430
}
431431
}
432432

433433
void Game::resetNpcs() const {
434434
// Close shop window from all npcs and reset the shopPlayerSet
435-
for (const auto &[npcId, npc] : getNpcs()) {
435+
for (const auto &npc : getNpcs()) {
436436
npc->closeAllShopWindows();
437437
npc->resetPlayerInteractions();
438438
}
@@ -954,23 +954,29 @@ std::shared_ptr<Monster> Game::getMonsterByID(uint32_t id) {
954954
return nullptr;
955955
}
956956

957-
auto it = monsters.find(id);
958-
if (it == monsters.end()) {
957+
auto it = monstersIdIndex.find(id);
958+
if (it == monstersIdIndex.end()) {
959959
return nullptr;
960960
}
961-
return it->second;
961+
962+
if (it->second >= monsters.size()) {
963+
return nullptr;
964+
}
965+
966+
return monsters[it->second];
962967
}
963968

964969
std::shared_ptr<Npc> Game::getNpcByID(uint32_t id) {
965970
if (id == 0) {
966971
return nullptr;
967972
}
968973

969-
auto it = npcs.find(id);
970-
if (it == npcs.end()) {
974+
auto it = npcsIdIndex.find(id);
975+
if (it == npcsIdIndex.end()) {
971976
return nullptr;
972977
}
973-
return it->second;
978+
979+
return npcs[it->second];
974980
}
975981

976982
std::shared_ptr<Player> Game::getPlayerByID(uint32_t id, bool allowOffline /* = false */) {
@@ -990,43 +996,41 @@ std::shared_ptr<Player> Game::getPlayerByID(uint32_t id, bool allowOffline /* =
990996
return tmpPlayer;
991997
}
992998

993-
std::shared_ptr<Creature> Game::getCreatureByName(const std::string &s) {
994-
if (s.empty()) {
999+
std::shared_ptr<Creature> Game::getCreatureByName(const std::string &creatureName) {
1000+
if (creatureName.empty()) {
9951001
return nullptr;
9961002
}
9971003

998-
const std::string &lowerCaseName = asLowerCaseString(s);
1004+
const std::string &lowerCaseName = asLowerCaseString(creatureName);
9991005

10001006
auto m_it = mappedPlayerNames.find(lowerCaseName);
10011007
if (m_it != mappedPlayerNames.end()) {
10021008
return m_it->second.lock();
10031009
}
10041010

1005-
for (const auto &it : npcs) {
1006-
if (lowerCaseName == asLowerCaseString(it.second->getName())) {
1007-
return it.second;
1008-
}
1011+
auto npcIterator = npcsNameIndex.find(lowerCaseName);
1012+
if (npcIterator != npcsNameIndex.end()) {
1013+
return npcs[npcIterator->second];
10091014
}
10101015

1011-
for (const auto &it : monsters) {
1012-
if (lowerCaseName == asLowerCaseString(it.second->getName())) {
1013-
return it.second;
1014-
}
1016+
auto monsterIterator = monstersNameIndex.find(lowerCaseName);
1017+
if (monsterIterator != monstersNameIndex.end()) {
1018+
return monsters[monsterIterator->second];
10151019
}
10161020
return nullptr;
10171021
}
10181022

1019-
std::shared_ptr<Npc> Game::getNpcByName(const std::string &s) {
1020-
if (s.empty()) {
1023+
std::shared_ptr<Npc> Game::getNpcByName(const std::string &npcName) {
1024+
if (npcName.empty()) {
10211025
return nullptr;
10221026
}
10231027

1024-
const char* npcName = s.c_str();
1025-
for (const auto &it : npcs) {
1026-
if (strcasecmp(npcName, it.second->getName().c_str()) == 0) {
1027-
return it.second;
1028-
}
1028+
const std::string lowerCaseName = asLowerCaseString(npcName);
1029+
auto it = npcsNameIndex.find(lowerCaseName);
1030+
if (it != npcsNameIndex.end()) {
1031+
return npcs[it->second];
10291032
}
1033+
10301034
return nullptr;
10311035
}
10321036

@@ -3184,13 +3188,18 @@ ReturnValue Game::internalCollectManagedItems(const std::shared_ptr<Player> &pla
31843188

31853189
ReturnValue Game::collectRewardChestItems(const std::shared_ptr<Player> &player, uint32_t maxMoveItems /* = 0*/) {
31863190
// Check if have item on player reward chest
3187-
std::shared_ptr<RewardChest> rewardChest = player->getRewardChest();
3191+
const std::shared_ptr<RewardChest> &rewardChest = player->getRewardChest();
31883192
if (rewardChest->empty()) {
31893193
g_logger().debug("Reward chest is empty");
31903194
return RETURNVALUE_REWARDCHESTISEMPTY;
31913195
}
31923196

3193-
auto rewardItemsVector = player->getRewardsFromContainer(rewardChest->getContainer());
3197+
const auto &container = rewardChest->getContainer();
3198+
if (!container) {
3199+
return RETURNVALUE_REWARDCHESTISEMPTY;
3200+
}
3201+
3202+
auto rewardItemsVector = player->getRewardsFromContainer(container);
31943203
auto rewardCount = rewardItemsVector.size();
31953204
uint32_t movedRewardItems = 0;
31963205
std::string lootedItemsMessage;
@@ -9931,19 +9940,72 @@ void Game::removePlayer(const std::shared_ptr<Player> &player) {
99319940
}
99329941

99339942
void Game::addNpc(const std::shared_ptr<Npc> &npc) {
9934-
npcs[npc->getID()] = npc;
9943+
npcs.push_back(npc);
9944+
size_t index = npcs.size() - 1;
9945+
npcsNameIndex[npc->getLowerName()] = index;
9946+
npcsIdIndex[npc->getID()] = index;
99359947
}
99369948

99379949
void Game::removeNpc(const std::shared_ptr<Npc> &npc) {
9938-
npcs.erase(npc->getID());
9950+
if (!npc) {
9951+
return;
9952+
}
9953+
9954+
auto npcId = npc->getID();
9955+
const auto &npcLowerName = npc->getLowerName();
9956+
auto it = npcsIdIndex.find(npcId);
9957+
if (it != npcsIdIndex.end()) {
9958+
size_t index = it->second;
9959+
npcsNameIndex.erase(npcLowerName);
9960+
npcsIdIndex.erase(npcId);
9961+
9962+
if (index != npcs.size() - 1) {
9963+
std::swap(npcs[index], npcs.back());
9964+
9965+
const auto &movedNpc = npcs[index];
9966+
npcsNameIndex[movedNpc->getLowerName()] = index;
9967+
npcsIdIndex[movedNpc->getID()] = index;
9968+
}
9969+
9970+
npcs.pop_back();
9971+
}
99399972
}
99409973

99419974
void Game::addMonster(const std::shared_ptr<Monster> &monster) {
9942-
monsters[monster->getID()] = monster;
9975+
if (!monster) {
9976+
return;
9977+
}
9978+
9979+
const auto &lowerName = monster->getLowerName();
9980+
monsters.push_back(monster);
9981+
size_t index = monsters.size() - 1;
9982+
monstersNameIndex[lowerName] = index;
9983+
monstersIdIndex[monster->getID()] = index;
99439984
}
99449985

99459986
void Game::removeMonster(const std::shared_ptr<Monster> &monster) {
9946-
monsters.erase(monster->getID());
9987+
if (!monster) {
9988+
return;
9989+
}
9990+
9991+
auto monsterId = monster->getID();
9992+
const auto &monsterLowerName = monster->getLowerName();
9993+
auto it = monstersIdIndex.find(monsterId);
9994+
if (it != monstersIdIndex.end()) {
9995+
size_t index = it->second;
9996+
monstersNameIndex.erase(monsterLowerName);
9997+
monstersIdIndex.erase(monsterId);
9998+
9999+
if (index != monsters.size() - 1) {
10000+
std::swap(monsters[index], monsters.back());
10001+
10002+
const auto &movedMonster = monsters[index];
10003+
monstersNameIndex[movedMonster->getLowerName()] = index;
10004+
monstersIdIndex[movedMonster->getID()] = index;
10005+
}
10006+
10007+
monsters.pop_back();
10008+
}
994710009
}
994810010

994910011
std::shared_ptr<Guild> Game::getGuild(uint32_t id, bool allowOffline /* = flase */) const {
@@ -10152,7 +10214,7 @@ uint32_t Game::makeFiendishMonster(uint32_t forgeableMonsterId /* = 0*/, bool cr
1015210214
forgeableMonsters.clear();
1015310215
// If the forgeable monsters haven't been created
1015410216
// Then we'll create them so they don't return in the next if (forgeableMonsters.empty())
10155-
for (const auto &[monsterId, monster] : monsters) {
10217+
for (const auto &monster : monsters) {
1015610218
auto monsterTile = monster->getTile();
1015710219
if (!monster || !monsterTile) {
1015810220
continue;
@@ -10325,7 +10387,7 @@ void Game::updateForgeableMonsters() {
1032510387
if (auto influencedLimit = g_configManager().getNumber(FORGE_INFLUENCED_CREATURES_LIMIT);
1032610388
forgeableMonsters.size() < influencedLimit) {
1032710389
forgeableMonsters.clear();
10328-
for (const auto &[monsterId, monster] : monsters) {
10390+
for (const auto &monster : monsters) {
1032910391
const auto &monsterTile = monster->getTile();
1033010392
if (!monsterTile) {
1033110393
continue;
@@ -10541,7 +10603,7 @@ void Game::playerRewardChestCollect(uint32_t playerId, const Position &pos, uint
1054110603
}
1054210604

1054310605
// Updates the parent of the reward chest and reward containers to avoid memory usage after cleaning
10544-
auto playerRewardChest = player->getRewardChest();
10606+
const auto &playerRewardChest = player->getRewardChest();
1054510607
if (playerRewardChest && playerRewardChest->empty()) {
1054610608
player->sendCancelMessage(RETURNVALUE_REWARDCHESTISEMPTY);
1054710609
return;

src/game/game.hpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -522,10 +522,10 @@ class Game {
522522
const phmap::parallel_flat_hash_map<uint32_t, std::shared_ptr<Player>> &getPlayers() const {
523523
return players;
524524
}
525-
const std::map<uint32_t, std::shared_ptr<Monster>> &getMonsters() const {
525+
const auto &getMonsters() const {
526526
return monsters;
527527
}
528-
const std::map<uint32_t, std::shared_ptr<Npc>> &getNpcs() const {
528+
const auto &getNpcs() const {
529529
return npcs;
530530
}
531531

@@ -539,8 +539,8 @@ class Game {
539539
void addNpc(const std::shared_ptr<Npc> &npc);
540540
void removeNpc(const std::shared_ptr<Npc> &npc);
541541

542-
void addMonster(const std::shared_ptr<Monster> &npc);
543-
void removeMonster(const std::shared_ptr<Monster> &npc);
542+
void addMonster(const std::shared_ptr<Monster> &monster);
543+
void removeMonster(const std::shared_ptr<Monster> &monster);
544544

545545
std::shared_ptr<Guild> getGuild(uint32_t id, bool allowOffline = false) const;
546546
std::shared_ptr<Guild> getGuildByName(const std::string &name, bool allowOffline = false) const;
@@ -851,8 +851,16 @@ class Game {
851851

852852
std::shared_ptr<WildcardTreeNode> wildcardTree = nullptr;
853853

854-
std::map<uint32_t, std::shared_ptr<Npc>> npcs;
855-
std::map<uint32_t, std::shared_ptr<Monster>> monsters;
854+
std::vector<std::shared_ptr<Monster>> monsters;
855+
// This works only for unique monsters (bosses, quest monsters, etc)
856+
std::unordered_map<std::string, size_t> monstersNameIndex;
857+
std::unordered_map<uint32_t, size_t> monstersIdIndex;
858+
859+
std::vector<std::shared_ptr<Npc>> npcs;
860+
// This works only for unique npcs (quest npcs, etc)
861+
std::unordered_map<std::string, size_t> npcsNameIndex;
862+
std::unordered_map<uint32_t, size_t> npcsIdIndex;
863+
856864
std::vector<uint32_t> forgeableMonsters;
857865

858866
std::map<uint32_t, std::unique_ptr<TeamFinder>> teamFinderMap; // [leaderGUID] = TeamFinder*

src/lua/functions/creatures/monster/monster_type_functions.cpp

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,18 +1170,22 @@ int MonsterTypeFunctions::luaMonsterTypeGetCreatureEvents(lua_State* L) {
11701170
int MonsterTypeFunctions::luaMonsterTypeRegisterEvent(lua_State* L) {
11711171
// monsterType:registerEvent(name)
11721172
const auto &monsterType = Lua::getUserdataShared<MonsterType>(L, 1);
1173-
if (monsterType) {
1174-
const auto eventName = Lua::getString(L, 2);
1175-
monsterType->info.scripts.insert(eventName);
1176-
for (const auto &[_, monster] : g_game().getMonsters()) {
1177-
if (monster->getMonsterType() == monsterType) {
1178-
monster->registerCreatureEvent(eventName);
1179-
}
1180-
}
1181-
Lua::pushBoolean(L, true);
1182-
} else {
1173+
if (!monsterType) {
11831174
lua_pushnil(L);
1175+
return 1;
11841176
}
1177+
1178+
const auto eventName = Lua::getString(L, 2);
1179+
monsterType->info.scripts.insert(eventName);
1180+
1181+
for (const auto &monster : g_game().getMonsters()) {
1182+
const auto monsterTypeCompare = monster->getMonsterType();
1183+
if (monsterTypeCompare == monsterType) {
1184+
monster->registerCreatureEvent(eventName);
1185+
}
1186+
}
1187+
1188+
Lua::pushBoolean(L, true);
11851189
return 1;
11861190
}
11871191

0 commit comments

Comments
 (0)