cache civzones so lookup is O(1) instead of O(N)

develop
myk002 2021-10-07 22:28:22 -07:00 committed by Myk
parent 41ea2527eb
commit 31397af99b
1 changed files with 95 additions and 35 deletions

@ -102,6 +102,7 @@ struct CoordHash {
};
static unordered_map<df::coord, int32_t, CoordHash> locationToBuilding;
static unordered_map<df::coord, vector<int32_t>, CoordHash> locationToCivzones;
static df::building_extents_type *getExtentTile(df::building_extents &extent, df::coord2d tile)
{
@ -305,20 +306,97 @@ df::building *Buildings::findAtTile(df::coord pos)
return NULL;
}
bool Buildings::findCivzonesAt(std::vector<df::building_civzonest*> *pvec, df::coord pos)
{
pvec->clear();
static unordered_map<int32_t, df::coord> corner1;
static unordered_map<int32_t, df::coord> corner2;
static void cacheBuilding(df::building *building) {
int32_t id = building->id;
bool is_civzone = !building->isSettingOccupancy();
df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z);
df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z);
if (!is_civzone) {
// civzones can be dynamically shrunk so we don't bother to cache
// their boundaries. findCivzonesAt() will trim the cache as needed.
corner1[id] = p1;
corner2[id] = p2;
}
auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE];
for (int32_t x = p1.x; x <= p2.x; x++) {
for (int32_t y = p1.y; y <= p2.y; y++) {
df::coord pt(x, y, building->z);
if (Buildings::containsTile(building, pt, is_civzone)) {
if (is_civzone)
locationToCivzones[pt].push_back(id);
else
locationToBuilding[pt] = id;
}
}
}
}
for (size_t i = 0; i < vec.size(); i++)
{
auto bld = strict_virtual_cast<df::building_civzonest>(vec[i]);
static int32_t nextCivzone = 0;
static void cacheNewCivzones() {
if (!world || !building_next_id)
return;
int32_t nextBuildingId = *building_next_id;
for (int32_t id = nextCivzone; id < nextBuildingId; ++id) {
auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE];
int32_t idx = df::building::binsearch_index(vec, id);
if (idx > -1)
cacheBuilding(vec[idx]);
}
nextCivzone = nextBuildingId;
}
bool Buildings::findCivzonesAt(std::vector<df::building_civzonest*> *pvec,
df::coord pos) {
pvec->clear();
if (!bld || bld->z != pos.z || !containsTile(bld, pos))
// Tiles have an occupancy->bits.building flag to quickly determine if it is
// covered by a buildling, but there is no such flag for civzones.
// Therefore, we need to make sure that our cache is authoratative.
// Otherwise, we would have to fall back to linearly scanning the list of
// all civzones on a cache miss.
//
// Since we guarantee our cache contains *at least* all tiles that are
// currently covered by civzones, we can conclude that if a tile is not in
// the cache, there is no civzone there. Civzones *can* be dynamically
// shrunk, so we still need to verify that civzones that once covered this
// tile continue to cover this tile.
cacheNewCivzones();
auto civzones_it = locationToCivzones.find(pos);
if (civzones_it == locationToCivzones.end())
return false;
set<int32_t> ids_to_remove;
auto &civzones = civzones_it->second;
for (int32_t id : civzones) {
int32_t idx = df::building::binsearch_index(
world->buildings.other[buildings_other_id::ANY_ZONE], id);
df::building_civzonest *civzone = NULL;
if (idx > -1)
civzone = world->buildings.other.ANY_ZONE[idx];
if (!civzone || civzone->z != pos.z ||
!containsTile(civzone, pos, true)) {
ids_to_remove.insert(id);
continue;
}
pvec->push_back(civzone);
}
pvec->push_back(bld);
// civzone no longer occupies this tile; update the cache
if (!ids_to_remove.empty()) {
for (auto it = civzones.begin(); it != civzones.end(); ) {
if (ids_to_remove.count(*it)) {
it = civzones.erase(it);
continue;
}
++it;
}
if (civzones.empty())
locationToCivzones.erase(pos);
}
return !pvec->empty();
@ -1218,47 +1296,29 @@ bool Buildings::markedForRemoval(df::building *bld)
return false;
}
static unordered_map<int32_t, df::coord> corner1;
static unordered_map<int32_t, df::coord> corner2;
void Buildings::clearBuildings(color_ostream& out) {
corner1.clear();
corner2.clear();
locationToBuilding.clear();
locationToCivzones.clear();
nextCivzone = 0;
}
void Buildings::updateBuildings(color_ostream& out, void* ptr)
void Buildings::updateBuildings(color_ostream&, void* ptr)
{
int32_t id = (int32_t)(intptr_t)ptr;
auto building = df::building::find(id);
if (building)
{
// Already cached -> weird, so bail out
if (corner1.count(id))
return;
// Civzones cannot be cached because they can
// overlap each other and normal buildings.
if (!building->isSettingOccupancy())
return;
df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z);
df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z);
corner1[id] = p1;
corner2[id] = p2;
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,building->z);
if (containsTile(building, pt, false))
locationToBuilding[pt] = id;
}
}
if (!corner1.count(id))
cacheBuilding(building);
}
else if (corner1.count(id))
{
//existing building: destroy it
// existing building: destroy it
// note that civzones are lazy-destroyed in findCivzonesAt() and are
// not handled here
df::coord p1 = corner1[id];
df::coord p2 = corner2[id];