Skip to content

Commit

Permalink
perf: npc/monster storage with vector indexing
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dudantas committed Jan 4, 2025
1 parent 145da70 commit 52a83ce
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 53 deletions.
4 changes: 4 additions & 0 deletions src/creatures/combat/condition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,10 @@ bool ConditionDamage::getNextDamage(int32_t &damage) {
}

bool ConditionDamage::doDamage(const std::shared_ptr<Creature> &creature, int32_t healthChange) const {
if (owner == 0) {
return false;
}

const auto &attacker = g_game().getPlayerByGUID(owner) ? g_game().getPlayerByGUID(owner)->getCreature() : g_game().getCreatureByID(owner);
bool isPlayer = attacker && attacker->getPlayer();
if (creature->isSuppress(getType(), isPlayer)) {
Expand Down
1 change: 1 addition & 0 deletions src/creatures/monsters/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ std::shared_ptr<Monster> Monster::createMonster(const std::string &name) {
}

Monster::Monster(const std::shared_ptr<MonsterType> &mType) :
m_lowerName(asLowerCaseString(mType->name)),
nameDescription(asLowerCaseString(mType->nameDescription)),
mType(mType) {
defaultOutfit = mType->info.outfit;
Expand Down
5 changes: 5 additions & 0 deletions src/creatures/monsters/monster.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class Monster final : public Creature {
void setNameDescription(std::string_view nameDescription);
std::string getDescription(int32_t) override;

const std::string &getLowerName() const {
return m_lowerName;
}

CreatureType_t getType() const override;

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

std::string name;
std::string m_lowerName;
std::string nameDescription;

std::shared_ptr<MonsterType> mType;
Expand Down
4 changes: 4 additions & 0 deletions src/creatures/npcs/npc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class Npc final : public Creature {

void setName(std::string newName) const;

const std::string &getLowerName() const {
return npcType->m_lowerName;

Check failure on line 55 in src/creatures/npcs/npc.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-release

invalid use of incomplete type ‘using std::__shared_ptr_access<NpcType, __gnu_cxx::_S_atomic, false, false>::element_type = class NpcType’ {aka ‘class NpcType’}

Check failure on line 55 in src/creatures/npcs/npc.hpp

View workflow job for this annotation

GitHub Actions / windows-2022-windows-release

use of undefined type 'NpcType'

Check failure on line 55 in src/creatures/npcs/npc.hpp

View workflow job for this annotation

GitHub Actions / windows-2022-windows-release

'm_lowerName': is not a member of 'std::shared_ptr<NpcType>'

Check failure on line 55 in src/creatures/npcs/npc.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-debug

invalid use of incomplete type ‘using std::__shared_ptr_access<NpcType, __gnu_cxx::_S_atomic, false, false>::element_type = class NpcType’ {aka ‘class NpcType’}

Check failure on line 55 in src/creatures/npcs/npc.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-24.04-linux-release

invalid use of incomplete type ‘using std::__shared_ptr_access<NpcType, __gnu_cxx::_S_atomic, false, false>::element_type = class NpcType’ {aka ‘class NpcType’}

Check failure on line 55 in src/creatures/npcs/npc.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-24.04-linux-debug

invalid use of incomplete type ‘using std::__shared_ptr_access<NpcType, __gnu_cxx::_S_atomic, false, false>::element_type = class NpcType’ {aka ‘class NpcType’}
}

CreatureType_t getType() const override;

const Position &getMasterPos() const;
Expand Down
3 changes: 2 additions & 1 deletion src/creatures/npcs/npcs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,14 @@ class NpcType final : public SharedObject {
public:
NpcType() = default;
explicit NpcType(const std::string &initName) :
name(initName), typeName(initName), nameDescription(initName) {};
name(initName), m_lowerName(asLowerCaseString(initName)), typeName(initName), nameDescription(initName) {};

Check failure on line 81 in src/creatures/npcs/npcs.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-debug

‘asLowerCaseString’ was not declared in this scope

Check failure on line 81 in src/creatures/npcs/npcs.hpp

View workflow job for this annotation

GitHub Actions / ubuntu-24.04-linux-debug

‘asLowerCaseString’ was not declared in this scope

// non-copyable
NpcType(const NpcType &) = delete;
NpcType &operator=(const NpcType &) = delete;

std::string name;
std::string m_lowerName;
std::string typeName;
std::string nameDescription;
NpcInfo info;
Expand Down
132 changes: 97 additions & 35 deletions src/game/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,15 +424,15 @@ Game &Game::getInstance() {
}

void Game::resetMonsters() const {
for (const auto &[monsterId, monster] : getMonsters()) {
for (const auto &monster : getMonsters()) {
monster->clearTargetList();
monster->clearFriendList();
}
}

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

auto it = monsters.find(id);
if (it == monsters.end()) {
auto it = monstersIdIndex.find(id);
if (it == monstersIdIndex.end()) {
return nullptr;
}
return it->second;

if (it->second >= monsters.size()) {
return nullptr;
}

return monsters[it->second];
}

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

auto it = npcs.find(id);
if (it == npcs.end()) {
auto it = npcsIdIndex.find(id);
if (it == npcsIdIndex.end()) {
return nullptr;
}
return it->second;

return npcs[it->second];
}

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

std::shared_ptr<Creature> Game::getCreatureByName(const std::string &s) {
if (s.empty()) {
std::shared_ptr<Creature> Game::getCreatureByName(const std::string &creatureName) {
if (creatureName.empty()) {
return nullptr;
}

const std::string &lowerCaseName = asLowerCaseString(s);
const std::string &lowerCaseName = asLowerCaseString(creatureName);

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

for (const auto &it : npcs) {
if (lowerCaseName == asLowerCaseString(it.second->getName())) {
return it.second;
}
auto npcIterator = npcsNameIndex.find(lowerCaseName);
if (npcIterator != npcsNameIndex.end()) {
return npcs[npcIterator->second];
}

for (const auto &it : monsters) {
if (lowerCaseName == asLowerCaseString(it.second->getName())) {
return it.second;
}
auto monsterIterator = monstersNameIndex.find(lowerCaseName);
if (monsterIterator != monstersNameIndex.end()) {
return monsters[monsterIterator->second];
}
return nullptr;
}

std::shared_ptr<Npc> Game::getNpcByName(const std::string &s) {
if (s.empty()) {
std::shared_ptr<Npc> Game::getNpcByName(const std::string &npcName) {
if (npcName.empty()) {
return nullptr;
}

const char* npcName = s.c_str();
for (const auto &it : npcs) {
if (strcasecmp(npcName, it.second->getName().c_str()) == 0) {
return it.second;
}
const std::string lowerCaseName = asLowerCaseString(npcName);
auto it = npcsNameIndex.find(lowerCaseName);
if (it != npcsNameIndex.end()) {
return npcs[it->second];
}

return nullptr;
}

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

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

auto rewardItemsVector = player->getRewardsFromContainer(rewardChest->getContainer());
const auto &container = rewardChest->getContainer();
if (!container) {
return RETURNVALUE_REWARDCHESTISEMPTY;
}

auto rewardItemsVector = player->getRewardsFromContainer(container);
auto rewardCount = rewardItemsVector.size();
uint32_t movedRewardItems = 0;
std::string lootedItemsMessage;
Expand Down Expand Up @@ -9930,19 +9939,72 @@ void Game::removePlayer(const std::shared_ptr<Player> &player) {
}

void Game::addNpc(const std::shared_ptr<Npc> &npc) {
npcs[npc->getID()] = npc;
npcs.push_back(npc);
size_t index = npcs.size() - 1;
npcsNameIndex[npc->getLowerName()] = index;
npcsIdIndex[npc->getID()] = index;
}

void Game::removeNpc(const std::shared_ptr<Npc> &npc) {
npcs.erase(npc->getID());
if (!npc) {
return;
}

auto npcId = npc->getID();
const auto &npcLowerName = npc->getLowerName();
auto it = npcsIdIndex.find(npcId);
if (it != npcsIdIndex.end()) {
size_t index = it->second;
npcsNameIndex.erase(npcLowerName);
npcsIdIndex.erase(npcId);

if (index != npcs.size() - 1) {
std::swap(npcs[index], npcs.back());

const auto &movedNpc = npcs[index];
npcsNameIndex[movedNpc->getLowerName()] = index;
npcsIdIndex[movedNpc->getID()] = index;
}

npcs.pop_back();
}
}

void Game::addMonster(const std::shared_ptr<Monster> &monster) {
monsters[monster->getID()] = monster;
if (!monster) {
return;
}

const auto &lowerName = monster->getLowerName();
monsters.push_back(monster);
size_t index = monsters.size() - 1;
monstersNameIndex[lowerName] = index;
monstersIdIndex[monster->getID()] = index;
}

void Game::removeMonster(const std::shared_ptr<Monster> &monster) {
monsters.erase(monster->getID());
if (!monster) {
return;
}

auto monsterId = monster->getID();
const auto &monsterLowerName = monster->getLowerName();
auto it = monstersIdIndex.find(monsterId);
if (it != monstersIdIndex.end()) {
size_t index = it->second;
monstersNameIndex.erase(monsterLowerName);
monstersIdIndex.erase(monsterId);

if (index != monsters.size() - 1) {
std::swap(monsters[index], monsters.back());

const auto &movedMonster = monsters[index];
monstersNameIndex[movedMonster->getLowerName()] = index;
monstersIdIndex[movedMonster->getID()] = index;
}

monsters.pop_back();
}
}

std::shared_ptr<Guild> Game::getGuild(uint32_t id, bool allowOffline /* = flase */) const {
Expand Down Expand Up @@ -10151,7 +10213,7 @@ uint32_t Game::makeFiendishMonster(uint32_t forgeableMonsterId /* = 0*/, bool cr
forgeableMonsters.clear();
// If the forgeable monsters haven't been created
// Then we'll create them so they don't return in the next if (forgeableMonsters.empty())
for (const auto &[monsterId, monster] : monsters) {
for (const auto &monster : monsters) {
auto monsterTile = monster->getTile();
if (!monster || !monsterTile) {
continue;
Expand Down Expand Up @@ -10324,7 +10386,7 @@ void Game::updateForgeableMonsters() {
if (auto influencedLimit = g_configManager().getNumber(FORGE_INFLUENCED_CREATURES_LIMIT);
forgeableMonsters.size() < influencedLimit) {
forgeableMonsters.clear();
for (const auto &[monsterId, monster] : monsters) {
for (const auto &monster : monsters) {
const auto &monsterTile = monster->getTile();
if (!monsterTile) {
continue;
Expand Down Expand Up @@ -10540,7 +10602,7 @@ void Game::playerRewardChestCollect(uint32_t playerId, const Position &pos, uint
}

// Updates the parent of the reward chest and reward containers to avoid memory usage after cleaning
auto playerRewardChest = player->getRewardChest();
const auto &playerRewardChest = player->getRewardChest();
if (playerRewardChest && playerRewardChest->empty()) {
player->sendCancelMessage(RETURNVALUE_REWARDCHESTISEMPTY);
return;
Expand Down
20 changes: 14 additions & 6 deletions src/game/game.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,10 +511,10 @@ class Game {
const phmap::parallel_flat_hash_map<uint32_t, std::shared_ptr<Player>> &getPlayers() const {
return players;
}
const std::map<uint32_t, std::shared_ptr<Monster>> &getMonsters() const {
const auto &getMonsters() const {
return monsters;
}
const std::map<uint32_t, std::shared_ptr<Npc>> &getNpcs() const {
const auto &getNpcs() const {
return npcs;
}

Expand All @@ -528,8 +528,8 @@ class Game {
void addNpc(const std::shared_ptr<Npc> &npc);
void removeNpc(const std::shared_ptr<Npc> &npc);

void addMonster(const std::shared_ptr<Monster> &npc);
void removeMonster(const std::shared_ptr<Monster> &npc);
void addMonster(const std::shared_ptr<Monster> &monster);
void removeMonster(const std::shared_ptr<Monster> &monster);

std::shared_ptr<Guild> getGuild(uint32_t id, bool allowOffline = false) const;
std::shared_ptr<Guild> getGuildByName(const std::string &name, bool allowOffline = false) const;
Expand Down Expand Up @@ -840,8 +840,16 @@ class Game {

std::shared_ptr<WildcardTreeNode> wildcardTree = nullptr;

std::map<uint32_t, std::shared_ptr<Npc>> npcs;
std::map<uint32_t, std::shared_ptr<Monster>> monsters;
std::vector<std::shared_ptr<Monster>> monsters;
// This works only for unique monsters (bosses, quest monsters, etc)
std::unordered_map<std::string, size_t> monstersNameIndex;
std::unordered_map<uint32_t, size_t> monstersIdIndex;

std::vector<std::shared_ptr<Npc>> npcs;
// This works only for unique npcs (quest npcs, etc)
std::unordered_map<std::string, size_t> npcsNameIndex;
std::unordered_map<uint32_t, size_t> npcsIdIndex;

std::vector<uint32_t> forgeableMonsters;

std::map<uint32_t, std::unique_ptr<TeamFinder>> teamFinderMap; // [leaderGUID] = TeamFinder*
Expand Down
26 changes: 15 additions & 11 deletions src/lua/functions/creatures/monster/monster_type_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1169,19 +1169,23 @@ int MonsterTypeFunctions::luaMonsterTypeGetCreatureEvents(lua_State* L) {

int MonsterTypeFunctions::luaMonsterTypeRegisterEvent(lua_State* L) {
// monsterType:registerEvent(name)
const auto &monsterType = Lua::getUserdataShared<MonsterType>(L, 1);
if (monsterType) {
const auto eventName = Lua::getString(L, 2);
monsterType->info.scripts.insert(eventName);
for (const auto &[_, monster] : g_game().getMonsters()) {
if (monster->getMonsterType() == monsterType) {
monster->registerCreatureEvent(eventName);
}
}
Lua::pushBoolean(L, true);
} else {
const auto &monsterType = getUserdataShared<MonsterType>(L, 1);
if (!monsterType) {
lua_pushnil(L);
return 1;
}

const auto eventName = getString(L, 2);
monsterType->info.scripts.insert(eventName);

for (const auto &monster : g_game().getMonsters()) {
const auto monsterTypeCompare = monster->getMonsterType();
if (monsterTypeCompare == monsterType) {
monster->registerCreatureEvent(eventName);
}
}

Lua::pushBoolean(L, true);
return 1;
}

Expand Down

0 comments on commit 52a83ce

Please sign in to comment.