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

Refactor quadtree: Extracted into tfs::map::quadtree namespace with code improvements and tests #4798

Draft
wants to merge 31 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0065b9e
Rework quadtree
ramon-bernardo Oct 5, 2024
35e0ee0
Use stl array
ramon-bernardo Oct 5, 2024
eee435c
Use inline instead static constexpr
ramon-bernardo Oct 5, 2024
e9d4f5f
Use int32_t instead of fast int32_t and auto when possible; move east…
ramon-bernardo Oct 5, 2024
30976c9
Remove remove erase check and fix comparison qt::find param
ramon-bernardo Oct 5, 2024
b7c3159
Fix use map quadtree
ramon-bernardo Oct 5, 2024
081114f
Rename namespace tfs::quadtree
ramon-bernardo Oct 5, 2024
18e1ea4
Add wildtree doc and some fix in namespace
ramon-bernardo Oct 5, 2024
cc7d235
Add quadtree doc
ramon-bernardo Oct 5, 2024
fe6172e
Simplify namespace doc
ramon-bernardo Oct 5, 2024
50b41c9
Add quadtree doc
ramon-bernardo Oct 5, 2024
d7cf93e
Rename push method, some doc fix
ramon-bernardo Oct 5, 2024
05f887e
Fix quadtree
ramon-bernardo Oct 5, 2024
e8c8d4d
Fix quadtree
ramon-bernardo Oct 6, 2024
ade4eb8
Remove unused z position
ramon-bernardo Oct 6, 2024
10594d5
Impl destructors
ramon-bernardo Oct 6, 2024
657d756
Impl destructors
ramon-bernardo Oct 6, 2024
26420d3
Use generator isntead comparasion
ramon-bernardo Oct 6, 2024
6c301d9
Remove const
ramon-bernardo Oct 6, 2024
33f3b2a
Fix nullptr check
ramon-bernardo Oct 6, 2024
9ad4755
Fix the creation of quadtree leaf nodes
ramon-bernardo Oct 6, 2024
fce5839
Add quadtree test
ramon-bernardo Oct 6, 2024
ab8a668
Fix quadtree function
ramon-bernardo Oct 6, 2024
94f660a
Fix quadtree
ramon-bernardo Oct 6, 2024
8877e35
Add tfs::generator
ramon-bernardo Oct 6, 2024
1ef0a43
Bump cmake C++23
ramon-bernardo Oct 6, 2024
d083eac
Add tfs::generator
ramon-bernardo Oct 6, 2024
01ed2d8
Add tfs::generator
ramon-bernardo Oct 6, 2024
9ff7d47
Add tfs::generator
ramon-bernardo Oct 6, 2024
33f00bd
Test bump gh os
ramon-bernardo Oct 6, 2024
20b994b
Revert "Test bump gh os"
ramon-bernardo Oct 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/protocollogin.cpp
${CMAKE_CURRENT_LIST_DIR}/protocolold.cpp
${CMAKE_CURRENT_LIST_DIR}/protocolstatus.cpp
${CMAKE_CURRENT_LIST_DIR}/quadtree.cpp
${CMAKE_CURRENT_LIST_DIR}/rsa.cpp
${CMAKE_CURRENT_LIST_DIR}/scheduler.cpp
${CMAKE_CURRENT_LIST_DIR}/script.cpp
Expand Down Expand Up @@ -143,6 +144,7 @@ set(tfs_HDR
${CMAKE_CURRENT_LIST_DIR}/protocolold.h
${CMAKE_CURRENT_LIST_DIR}/protocolstatus.h
${CMAKE_CURRENT_LIST_DIR}/pugicast.h
${CMAKE_CURRENT_LIST_DIR}/quadtree.h
${CMAKE_CURRENT_LIST_DIR}/rsa.h
${CMAKE_CURRENT_LIST_DIR}/scheduler.h
${CMAKE_CURRENT_LIST_DIR}/script.h
Expand Down
289 changes: 55 additions & 234 deletions src/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "iomap.h"
#include "iomapserialize.h"
#include "monster.h"
#include "quadtree.h"
#include "spectators.h"

extern Game g_game;
Expand Down Expand Up @@ -67,17 +68,7 @@ Tile* Map::getTile(uint16_t x, uint16_t y, uint8_t z) const
if (z >= MAP_MAX_LAYERS) {
return nullptr;
}

const QTreeLeafNode* leaf = QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, x, y);
if (!leaf) {
return nullptr;
}

const Floor* floor = leaf->getFloor(z);
if (!floor) {
return nullptr;
}
return floor->tiles[x & FLOOR_MASK][y & FLOOR_MASK];
return tfs::map::quadtree::find_tile(x, y, z);
}

void Map::setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile)
Expand All @@ -87,41 +78,7 @@ void Map::setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile)
return;
}

QTreeLeafNode::newLeaf = false;
QTreeLeafNode* leaf = root.createLeaf(x, y, 15);

if (QTreeLeafNode::newLeaf) {
// update north
QTreeLeafNode* northLeaf = root.getLeaf(x, y - FLOOR_SIZE);
if (northLeaf) {
northLeaf->leafS = leaf;
}

// update west leaf
QTreeLeafNode* westLeaf = root.getLeaf(x - FLOOR_SIZE, y);
if (westLeaf) {
westLeaf->leafE = leaf;
}

// update south
QTreeLeafNode* southLeaf = root.getLeaf(x, y + FLOOR_SIZE);
if (southLeaf) {
leaf->leafS = southLeaf;
}

// update east
QTreeLeafNode* eastLeaf = root.getLeaf(x + FLOOR_SIZE, y);
if (eastLeaf) {
leaf->leafE = eastLeaf;
}
}

Floor* floor = leaf->createFloor(z);
uint32_t offsetX = x & FLOOR_MASK;
uint32_t offsetY = y & FLOOR_MASK;

Tile*& tile = floor->tiles[offsetX][offsetY];
if (tile) {
if (auto tile = tfs::map::quadtree::find_tile(x, y, z)) {
TileItemVector* items = newTile->getItemList();
if (items) {
for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
Expand All @@ -137,7 +94,7 @@ void Map::setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile)
}
delete newTile;
} else {
tile = newTile;
tfs::map::quadtree::create_tile(x, y, z, newTile);
}
}

Expand All @@ -147,39 +104,31 @@ void Map::removeTile(uint16_t x, uint16_t y, uint8_t z)
return;
}

const QTreeLeafNode* leaf = QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, x, y);
if (!leaf) {
return;
}

const Floor* floor = leaf->getFloor(z);
if (!floor) {
auto tile = tfs::map::quadtree::find_tile(x, y, z);
if (!tile) {
return;
}

Tile* tile = floor->tiles[x & FLOOR_MASK][y & FLOOR_MASK];
if (tile) {
if (const CreatureVector* creatures = tile->getCreatures()) {
for (int32_t i = creatures->size(); --i >= 0;) {
if (Player* player = (*creatures)[i]->getPlayer()) {
g_game.internalTeleport(player, player->getTown()->getTemplePosition(), false, FLAG_NOLIMIT);
} else {
g_game.removeCreature((*creatures)[i]);
}
if (const CreatureVector* creatures = tile->getCreatures()) {
for (int32_t i = creatures->size(); --i >= 0;) {
if (Player* player = (*creatures)[i]->getPlayer()) {
g_game.internalTeleport(player, player->getTown()->getTemplePosition(), false, FLAG_NOLIMIT);
} else {
g_game.removeCreature((*creatures)[i]);
}
}
}

if (TileItemVector* items = tile->getItemList()) {
for (auto it = items->begin(), end = items->end(); it != end; ++it) {
g_game.internalRemoveItem(*it);
}
if (TileItemVector* items = tile->getItemList()) {
for (auto it = items->begin(), end = items->end(); it != end; ++it) {
g_game.internalRemoveItem(*it);
}
}

Item* ground = tile->getGround();
if (ground) {
g_game.internalRemoveItem(ground);
tile->setGround(nullptr);
}
Item* ground = tile->getGround();
if (ground) {
g_game.internalRemoveItem(ground);
tile->setGround(nullptr);
}
}

Expand Down Expand Up @@ -243,8 +192,8 @@ bool Map::placeCreature(const Position& centerPos, Creature* creature, bool exte
Cylinder* toCylinder = tile->queryDestination(index, *creature, &toItem, flags);
toCylinder->internalAddThing(creature);

const Position& dest = toCylinder->getPosition();
getQTNode(dest.x, dest.y)->addCreature(creature);
const Position& destPos = toCylinder->getPosition();
tfs::map::quadtree::push_creature(destPos.x, destPos.y, creature);
return true;
}

Expand Down Expand Up @@ -281,14 +230,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport /*
// remove the creature
oldTile.removeThing(&creature, 0);

QTreeLeafNode* leaf = getQTNode(oldPos.x, oldPos.y);
QTreeLeafNode* new_leaf = getQTNode(newPos.x, newPos.y);

// Switch the node ownership
if (leaf != new_leaf) {
leaf->removeCreature(&creature);
new_leaf->addCreature(&creature);
}
tfs::map::quadtree::move_creature(oldPos.x, oldPos.y, newPos.x, newPos.y, &creature);

// add the creature
newTile.addThing(&creature);
Expand Down Expand Up @@ -329,66 +271,6 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport /*
newTile.postAddNotification(&creature, &oldTile, 0);
}

void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, int32_t minRangeX,
int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ,
int32_t maxRangeZ, bool onlyPlayers) const
{
auto min_y = centerPos.y + minRangeY;
auto min_x = centerPos.x + minRangeX;
auto max_y = centerPos.y + maxRangeY;
auto max_x = centerPos.x + maxRangeX;

int32_t minoffset = centerPos.getZ() - maxRangeZ;
uint16_t x1 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (min_x + minoffset)));
uint16_t y1 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (min_y + minoffset)));

int32_t maxoffset = centerPos.getZ() - minRangeZ;
uint16_t x2 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (max_x + maxoffset)));
uint16_t y2 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (max_y + maxoffset)));

int32_t startx1 = x1 - (x1 % FLOOR_SIZE);
int32_t starty1 = y1 - (y1 % FLOOR_SIZE);
int32_t endx2 = x2 - (x2 % FLOOR_SIZE);
int32_t endy2 = y2 - (y2 % FLOOR_SIZE);

const QTreeLeafNode* startLeaf =
QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, startx1, starty1);
const QTreeLeafNode* leafS = startLeaf;
const QTreeLeafNode* leafE;

for (int_fast32_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE) {
leafE = leafS;
for (int_fast32_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE) {
if (leafE) {
const CreatureVector& node_list = (onlyPlayers ? leafE->player_list : leafE->creature_list);
for (Creature* creature : node_list) {
const Position& cpos = creature->getPosition();
if (minRangeZ > cpos.z || maxRangeZ < cpos.z) {
continue;
}

int16_t offsetZ = centerPos.getOffsetZ(cpos);
if ((min_y + offsetZ) > cpos.y || (max_y + offsetZ) < cpos.y || (min_x + offsetZ) > cpos.x ||
(max_x + offsetZ) < cpos.x) {
continue;
}

spectators.emplace_back(creature);
}
leafE = leafE->leafE;
} else {
leafE = QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, nx + FLOOR_SIZE, ny);
}
}

if (leafS) {
leafS = leafS->leafS;
} else {
leafS = QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, startx1, ny + FLOOR_SIZE);
}
}
}

void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, bool multifloor /*= false*/,
bool onlyPlayers /*= false*/, int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/,
int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/)
Expand Down Expand Up @@ -470,8 +352,37 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo
maxRangeZ = centerPos.z;
}

getSpectatorsInternal(spectators, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ,
onlyPlayers);
auto min_y = centerPos.y + minRangeY;
auto min_x = centerPos.x + minRangeX;
auto max_y = centerPos.y + maxRangeY;
auto max_x = centerPos.x + maxRangeX;

int32_t minoffset = centerPos.getZ() - maxRangeZ;
uint16_t x1 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (min_x + minoffset)));
uint16_t y1 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (min_y + minoffset)));

int32_t maxoffset = centerPos.getZ() - minRangeZ;
uint16_t x2 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (max_x + maxoffset)));
uint16_t y2 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (max_y + maxoffset)));

for (auto creature : tfs::map::quadtree::find_in_range(x1, y1, x2, y2)) {
if (onlyPlayers && !creature->getPlayer()) {
continue;
}

const auto& position = creature->getPosition();
if (minRangeZ > position.z || maxRangeZ < position.z) {
continue;
}

auto offsetZ = centerPos.getOffsetZ(position);
if ((min_y + offsetZ) > position.y || (max_y + offsetZ) < position.y || (min_x + offsetZ) > position.x ||
(max_x + offsetZ) < position.x) {
continue;
}

spectators.emplace_back(creature);
}

if (cacheResult) {
if (onlyPlayers) {
Expand Down Expand Up @@ -939,96 +850,6 @@ int_fast32_t AStarNodes::getTileWalkCost(const Creature& creature, const Tile* t
return cost;
}

// Floor
Floor::~Floor()
{
for (auto& row : tiles) {
for (auto tile : row) {
delete tile;
}
}
}

// QTreeNode
QTreeNode::~QTreeNode()
{
for (auto* ptr : child) {
delete ptr;
}
}

QTreeLeafNode* QTreeNode::getLeaf(uint32_t x, uint32_t y)
{
if (leaf) {
return static_cast<QTreeLeafNode*>(this);
}

QTreeNode* node = child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)];
if (!node) {
return nullptr;
}
return node->getLeaf(x << 1, y << 1);
}

QTreeLeafNode* QTreeNode::createLeaf(uint32_t x, uint32_t y, uint32_t level)
{
if (!isLeaf()) {
uint32_t index = ((x & 0x8000) >> 15) | ((y & 0x8000) >> 14);
if (!child[index]) {
if (level != FLOOR_BITS) {
child[index] = new QTreeNode();
} else {
child[index] = new QTreeLeafNode();
QTreeLeafNode::newLeaf = true;
}
}
return child[index]->createLeaf(x * 2, y * 2, level - 1);
}
return static_cast<QTreeLeafNode*>(this);
}

// QTreeLeafNode
bool QTreeLeafNode::newLeaf = false;

QTreeLeafNode::~QTreeLeafNode()
{
for (auto* ptr : array) {
delete ptr;
}
}

Floor* QTreeLeafNode::createFloor(uint32_t z)
{
if (!array[z]) {
array[z] = new Floor();
}
return array[z];
}

void QTreeLeafNode::addCreature(Creature* c)
{
creature_list.push_back(c);

if (c->getPlayer()) {
player_list.push_back(c);
}
}

void QTreeLeafNode::removeCreature(Creature* c)
{
auto iter = std::find(creature_list.begin(), creature_list.end(), c);
assert(iter != creature_list.end());
*iter = creature_list.back();
creature_list.pop_back();

if (c->getPlayer()) {
iter = std::find(player_list.begin(), player_list.end(), c);
assert(iter != player_list.end());
*iter = player_list.back();
player_list.pop_back();
}
}

uint32_t Map::clean() const
{
uint64_t start = OTSYS_TIME();
Expand Down
Loading
Loading