Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: MapSector system for improved performance and flexibility #2496

Merged
merged 13 commits into from
May 22, 2024
2 changes: 1 addition & 1 deletion src/items/tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1272,7 +1272,7 @@ void Tile::removeThing(std::shared_ptr<Thing> thing, uint32_t count) {
}

void Tile::removeCreature(std::shared_ptr<Creature> creature) {
g_game().map.getQTNode(tilePos.x, tilePos.y)->removeCreature(creature);
g_game().map.getMapSector(tilePos.x, tilePos.y)->removeCreature(creature);
removeThing(creature, 0);
}

Expand Down
2 changes: 1 addition & 1 deletion src/lua/functions/core/game/global_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ int GlobalFunctions::luaSaveServer(lua_State* L) {
}

int GlobalFunctions::luaCleanMap(lua_State* L) {
lua_pushnumber(L, Map::clean());
lua_pushnumber(L, g_game().map.clean());
return 1;
}

Expand Down
2 changes: 1 addition & 1 deletion src/map/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ target_sources(${PROJECT_NAME}_lib PRIVATE
house/house.cpp
house/housetile.cpp
utils/astarnodes.cpp
utils/qtreenode.cpp
utils/mapsector.cpp
map.cpp
mapcache.cpp
spectators.cpp
Expand Down
67 changes: 41 additions & 26 deletions src/map/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ std::shared_ptr<Tile> Map::getLoadedTile(uint16_t x, uint16_t y, uint8_t z) {
return nullptr;
}

const auto leaf = getQTNode(x, y);
const auto leaf = getMapSector(x, y);
if (!leaf) {
return nullptr;
}
Expand All @@ -182,12 +182,12 @@ std::shared_ptr<Tile> Map::getTile(uint16_t x, uint16_t y, uint8_t z) {
return nullptr;
}

const auto leaf = getQTNode(x, y);
if (!leaf) {
const auto sector = getMapSector(x, y);
if (!sector) {
return nullptr;
}

const auto &floor = leaf->getFloor(z);
const auto &floor = sector->getFloor(z);
if (!floor) {
return nullptr;
}
Expand Down Expand Up @@ -215,10 +215,10 @@ void Map::setTile(uint16_t x, uint16_t y, uint8_t z, std::shared_ptr<Tile> newTi
return;
}

if (const auto leaf = getQTNode(x, y)) {
leaf->createFloor(z)->setTile(x, y, newTile);
if (const auto sector = getMapSector(x, y)) {
sector->createFloor(z)->setTile(x, y, newTile);
} else {
root.getBestLeaf(x, y, 15)->createFloor(z)->setTile(x, y, newTile);
getBestMapSector(x, y)->createFloor(z)->setTile(x, y, newTile);
}
}

Expand Down Expand Up @@ -315,7 +315,7 @@ bool Map::placeCreature(const Position &centerPos, std::shared_ptr<Creature> cre
toCylinder->internalAddThing(creature);

const Position &dest = toCylinder->getPosition();
getQTNode(dest.x, dest.y)->addCreature(creature);
getMapSector(dest.x, dest.y)->addCreature(creature);
return true;
}

Expand Down Expand Up @@ -351,13 +351,13 @@ void Map::moveCreature(const std::shared_ptr<Creature> &creature, const std::sha
// remove the creature
oldTile->removeThing(creature, 0);

auto leaf = getQTNode(oldPos.x, oldPos.y);
auto new_leaf = getQTNode(newPos.x, newPos.y);
MapSector* old_sector = getMapSector(oldPos.x, oldPos.y);
MapSector* new_sector = getMapSector(newPos.x, newPos.y);

// Switch the node ownership
if (leaf != new_leaf) {
leaf->removeCreature(creature);
new_leaf->addCreature(creature);
if (old_sector != new_sector) {
old_sector->removeCreature(creature);
new_sector->addCreature(creature);
}

// add the creature
Expand Down Expand Up @@ -687,39 +687,54 @@ bool Map::getPathMatching(const std::shared_ptr<Creature> &creature, const Posit

uint32_t Map::clean() {
uint64_t start = OTSYS_TIME();
size_t tiles = 0;
size_t qntTiles = 0;

if (g_game().getGameState() == GAME_STATE_NORMAL) {
g_game().setGameState(GAME_STATE_MAINTAIN);
}

std::vector<std::shared_ptr<Item>> toRemove;
for (const auto &tile : g_game().getTilesToClean()) {
if (!tile) {
continue;
}
if (const auto items = tile->getItemList()) {
++tiles;
for (const auto &item : *items) {
if (item->isCleanable()) {
toRemove.emplace_back(item);
ItemVector toRemove;
toRemove.reserve(128);
for (const auto &mit : mapSectors) {
for (uint8_t z = 0; z < MAP_MAX_LAYERS; ++z) {
if (const auto &floor = mit.second.getFloor(z)) {
for (auto &tiles : floor->getTiles()) {
for (const auto &[tile, cachedTile] : tiles) {
if (!tile || tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
continue;
}

TileItemVector* itemList = tile->getItemList();
if (!itemList) {
continue;
}

++qntTiles;

for (auto it = ItemVector::const_reverse_iterator(itemList->getEndDownItem()), end = ItemVector::const_reverse_iterator(itemList->getBeginDownItem()); it != end; ++it) {
const auto &item = *it;
if (item->isCleanable()) {
toRemove.push_back(item);
}
}
}
}
}
}
}

const size_t count = toRemove.size();
for (const auto &item : toRemove) {
g_game().internalRemoveItem(item, -1);
}

size_t count = toRemove.size();
g_game().clearTilesToClean();

if (g_game().getGameState() == GAME_STATE_MAINTAIN) {
g_game().setGameState(GAME_STATE_NORMAL);
}

uint64_t end = OTSYS_TIME();
g_logger().info("CLEAN: Removed {} item{} from {} tile{} in {} seconds", count, (count != 1 ? "s" : ""), tiles, (tiles != 1 ? "s" : ""), (end - start) / (1000.f));
g_logger().info("CLEAN: Removed {} item{} from {} tile{} in {} seconds", count, (count != 1 ? "s" : ""), qntTiles, (qntTiles != 1 ? "s" : ""), (end - start) / (1000.f));
return count;
}
8 changes: 2 additions & 6 deletions src/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ class FrozenPathingConditionCall;
* Map class.
* Holds all the actual map-data
*/
class Map : protected MapCache {
class Map : public MapCache {
public:
static uint32_t clean();
uint32_t clean();

std::filesystem::path getPath() const {
return path;
Expand Down Expand Up @@ -131,10 +131,6 @@ class Map : protected MapCache {

std::map<std::string, Position> waypoints;

QTreeLeafNode* getQTNode(uint16_t x, uint16_t y) {
return QTreeNode::getLeafStatic<QTreeLeafNode*, QTreeNode*>(&root, x, y);
}

// Storage made by "loadFromXML" of houses, monsters and npcs for main map
SpawnsMonster spawnsMonster;
SpawnsNpc spawnsNpc;
Expand Down
7 changes: 4 additions & 3 deletions src/map/map_const.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ static constexpr int8_t MAP_MAX_LAYERS = 16;
static constexpr int8_t MAP_INIT_SURFACE_LAYER = 7; // (MAP_MAX_LAYERS / 2) -1
static constexpr int8_t MAP_LAYER_VIEW_LIMIT = 2;

static constexpr int32_t FLOOR_BITS = 3;
static constexpr int32_t FLOOR_SIZE = (1 << FLOOR_BITS);
static constexpr int32_t FLOOR_MASK = (FLOOR_SIZE - 1);
// SECTOR_SIZE must be power of 2 value
// The bigger the SECTOR_SIZE is the less hash map collision there should be but it'll consume more memory
static constexpr int32_t SECTOR_SIZE = 16;
static constexpr int32_t SECTOR_MASK = SECTOR_SIZE - 1;
46 changes: 43 additions & 3 deletions src/map/mapcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,57 @@ void MapCache::setBasicTile(uint16_t x, uint16_t y, uint8_t z, const std::shared
}

const auto tile = static_tryGetTileFromCache(newTile);
if (const auto leaf = QTreeNode::getLeafStatic<QTreeLeafNode*, QTreeNode*>(&root, x, y)) {
leaf->createFloor(z)->setTileCache(x, y, tile);
if (const auto sector = getMapSector(x, y)) {
sector->createFloor(z)->setTileCache(x, y, tile);
} else {
root.getBestLeaf(x, y, 15)->createFloor(z)->setTileCache(x, y, tile);
getBestMapSector(x, y)->createFloor(z)->setTileCache(x, y, tile);
}
}

std::shared_ptr<BasicItem> MapCache::tryReplaceItemFromCache(const std::shared_ptr<BasicItem> &ref) {
return static_tryGetItemFromCache(ref);
}

MapSector* MapCache::createMapSector(const uint32_t x, const uint32_t y) {
const uint32_t index = x / SECTOR_SIZE | y / SECTOR_SIZE << 16;
const auto it = mapSectors.find(index);
if (it != mapSectors.end()) {
return &it->second;
}

MapSector::newSector = true;
return &mapSectors[index];
}

MapSector* MapCache::getBestMapSector(uint32_t x, uint32_t y) {
MapSector::newSector = false;
const auto sector = createMapSector(x, y);

if (MapSector::newSector) {
// update north sector
if (const auto northSector = getMapSector(x, y - SECTOR_SIZE)) {
northSector->sectorS = sector;
}

// update west sector
if (const auto westSector = getMapSector(x - SECTOR_SIZE, y)) {
westSector->sectorE = sector;
}

// update south sector
if (const auto southSector = getMapSector(x, y + SECTOR_SIZE)) {
sector->sectorS = southSector;
}

// update east sector
if (const auto eastSector = getMapSector(x + SECTOR_SIZE, y)) {
sector->sectorE = eastSector;
}
}

return sector;
}

void BasicTile::hash(size_t &h) const {
std::array<uint32_t, 4> arr = { flags, houseId, type, isStatic };
for (const auto v : arr) {
Expand Down
61 changes: 23 additions & 38 deletions src/map/mapcache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#pragma once

#include "items/items_definitions.hpp"
#include "utils/qtreenode.hpp"
#include "utils/mapsector.hpp"

class Map;
class Tile;
Expand Down Expand Up @@ -79,42 +79,6 @@ struct BasicTile {

#pragma pack()

struct Floor {
explicit Floor(uint8_t z) :
z(z) { }

std::shared_ptr<Tile> getTile(uint16_t x, uint16_t y) const {
std::shared_lock sl(mutex);
return tiles[x & FLOOR_MASK][y & FLOOR_MASK].first;
}

void setTile(uint16_t x, uint16_t y, std::shared_ptr<Tile> tile) {
tiles[x & FLOOR_MASK][y & FLOOR_MASK].first = tile;
}

std::shared_ptr<BasicTile> getTileCache(uint16_t x, uint16_t y) const {
std::shared_lock sl(mutex);
return tiles[x & FLOOR_MASK][y & FLOOR_MASK].second;
}

void setTileCache(uint16_t x, uint16_t y, const std::shared_ptr<BasicTile> &newTile) {
tiles[x & FLOOR_MASK][y & FLOOR_MASK].second = newTile;
}

uint8_t getZ() const {
return z;
}

auto &getMutex() const {
return mutex;
}

private:
std::pair<std::shared_ptr<Tile>, std::shared_ptr<BasicTile>> tiles[FLOOR_SIZE][FLOOR_SIZE] = {};
mutable std::shared_mutex mutex;
uint8_t z { 0 };
};

class MapCache {
public:
virtual ~MapCache() = default;
Expand All @@ -125,10 +89,31 @@ class MapCache {

void flush();

/**
* Creates a map sector.
* \returns A pointer to that map sector.
*/
MapSector* createMapSector(uint32_t x, uint32_t y);
MapSector* getBestMapSector(uint32_t x, uint32_t y);

/**
* Gets a map sector.
* \returns A pointer to that map sector.
*/
MapSector* getMapSector(const uint32_t x, const uint32_t y) {
const auto it = mapSectors.find(x / SECTOR_SIZE | y / SECTOR_SIZE << 16);
return it != mapSectors.end() ? &it->second : nullptr;
}

const MapSector* getMapSector(const uint32_t x, const uint32_t y) const {
const auto it = mapSectors.find(x / SECTOR_SIZE | y / SECTOR_SIZE << 16);
return it != mapSectors.end() ? &it->second : nullptr;
}

protected:
std::shared_ptr<Tile> getOrCreateTileFromCache(const std::unique_ptr<Floor> &floor, uint16_t x, uint16_t y);

QTreeNode root;
std::unordered_map<uint32_t, MapSector> mapSectors;

private:
void parseItemAttr(const std::shared_ptr<BasicItem> &BasicItem, std::shared_ptr<Item> item);
Expand Down
Loading
Loading