1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Protect every access to zone tiles with a mutex

This commit is contained in:
Tomasz Zieliński 2024-03-27 06:16:48 +01:00
parent cfc4a26f55
commit d8c93cb222
20 changed files with 385 additions and 225 deletions

View File

@ -418,7 +418,7 @@ void CMapGenerator::fillZones()
}
auto grailZone = *RandomGeneratorUtil::nextItem(treasureZones, rand);
map->getMap(this).grailPos = *RandomGeneratorUtil::nextItem(grailZone->freePaths().getTiles(), rand);
map->getMap(this).grailPos = *RandomGeneratorUtil::nextItem(grailZone->freePaths()->getTiles(), rand);
logGlobal->info("Zones filled successfully");

View File

@ -858,7 +858,7 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr<Zone> & zone) -> void
{
int3 total(0, 0, 0);
auto tiles = zone->area().getTiles();
auto tiles = zone->area()->getTiles();
for(const auto & tile : tiles)
{
total += tile;
@ -892,14 +892,14 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
{
distances.emplace_back(zone.second, static_cast<float>(pos.dist2dSQ(zone.second->getPos())));
}
boost::min_element(distances, compareByDistance)->first->area().add(pos); //closest tile belongs to zone
boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone
}
}
}
for(const auto & zone : zones)
{
if(zone.second->area().empty())
if(zone.second->area()->empty())
throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size");
moveZoneToCenterOfMass(zone.second);
@ -948,14 +948,14 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
//Tile closest to vertex belongs to zone
auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first;
closestZone->area().add(pos);
closestZone->area()->add(pos);
map.setZoneID(pos, closestZone->getId());
}
}
for(const auto & zone : zonesOnLevel[level])
{
if(zone.second->area().empty())
if(zone.second->area()->empty())
{
// FIXME: Some vertices are duplicated, but it's not a source of problem
logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString());
@ -981,12 +981,12 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
{
auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f);
for(const auto & t : discardTiles)
zone.second->area().erase(t);
zone.second->area()->erase(t);
}
//make sure that terrain inside zone is not a rock
auto v = zone.second->getArea().getTilesVector();
auto v = zone.second->area()->getTilesVector();
map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN);
}
}

View File

@ -26,7 +26,8 @@ VCMI_LIB_NAMESPACE_BEGIN
rmg::Tileset collectDistantTiles(const Zone& zone, int distance)
{
uint32_t distanceSq = distance * distance;
auto subarea = zone.getArea().getSubarea([&zone, distanceSq](const int3 & t)
auto subarea = zone.area()->getSubarea([&zone, distanceSq](const int3 & t)
{
return t.dist2dSQ(zone.getPos()) > distanceSq;
});

View File

@ -75,25 +75,44 @@ void Zone::setPos(const int3 &Pos)
pos = Pos;
}
const rmg::Area & Zone::getArea() const
ThreadSafeProxy<rmg::Area> Zone::area()
{
return dArea;
return ThreadSafeProxy<rmg::Area>(dArea, areaMutex);
}
rmg::Area & Zone::area()
ThreadSafeProxy<const rmg::Area> Zone::area() const
{
return dArea;
return ThreadSafeProxy<const rmg::Area>(dArea, areaMutex);
}
rmg::Area & Zone::areaPossible()
ThreadSafeProxy<rmg::Area> Zone::areaPossible()
{
//FIXME: make const, only modify via mutex-protected interface
return dAreaPossible;
return ThreadSafeProxy<rmg::Area>(dAreaPossible, areaMutex);
}
rmg::Area & Zone::areaUsed()
ThreadSafeProxy<const rmg::Area> Zone::areaPossible() const
{
return dAreaUsed;
return ThreadSafeProxy<const rmg::Area>(dAreaPossible, areaMutex);
}
ThreadSafeProxy<rmg::Area> Zone::freePaths()
{
return ThreadSafeProxy<rmg::Area>(dAreaFree, areaMutex);
}
ThreadSafeProxy<const rmg::Area> Zone::freePaths() const
{
return ThreadSafeProxy<const rmg::Area>(dAreaFree, areaMutex);
}
ThreadSafeProxy<rmg::Area> Zone::areaUsed()
{
return ThreadSafeProxy<rmg::Area>(dAreaUsed, areaMutex);
}
ThreadSafeProxy<const rmg::Area> Zone::areaUsed() const
{
return ThreadSafeProxy<const rmg::Area>(dAreaUsed, areaMutex);
}
void Zone::clearTiles()
@ -122,11 +141,6 @@ void Zone::initFreeTiles()
}
}
rmg::Area & Zone::freePaths()
{
return dAreaFree;
}
FactionID Zone::getTownType() const
{
return townType;
@ -225,18 +239,16 @@ TModificators Zone::getModificators()
void Zone::connectPath(const rmg::Path & path)
///connect current tile to any other free tile within zone
{
dAreaPossible.subtract(path.getPathArea());
dAreaFree.unite(path.getPathArea());
//Lock lock(areaMutex);
areaPossible()->subtract(path.getPathArea());
freePaths()->unite(path.getPathArea());
for(const auto & t : path.getPathArea().getTilesVector())
map.setOccupied(t, ETileType::FREE);
}
void Zone::fractalize()
{
rmg::Area clearedTiles(dAreaFree);
rmg::Area possibleTiles(dAreaPossible);
rmg::Area tilesToIgnore; //will be erased in this iteration
//Squared
float minDistance = 9 * 9;
float freeDistance = pos.z ? (10 * 10) : (9 * 9);
@ -283,6 +295,13 @@ void Zone::fractalize()
vstd::amax(freeDistance, 4 * 4);
logGlobal->trace("Zone %d: treasureValue %d blockDistance: %2.f, freeDistance: %2.f", getId(), treasureValue, blockDistance, freeDistance);
Lock lock(areaMutex);
// FIXME: Do not access Area directly
rmg::Area clearedTiles(dAreaFree);
rmg::Area possibleTiles(dAreaPossible);
rmg::Area tilesToIgnore; //will be erased in this iteration
if(type != ETemplateZoneType::JUNCTION)
{
//junction is not fractalized, has only one straight path
@ -336,7 +355,7 @@ void Zone::fractalize()
}
}
Lock lock(areaMutex);
//Lock lock(areaMutex);
//Connect with free areas
auto areas = connectedAreas(clearedTiles, true);
for(auto & area : areas)

View File

@ -34,6 +34,36 @@ extern const std::function<bool(const int3 &)> AREA_NO_FILTER;
typedef std::list<std::shared_ptr<Modificator>> TModificators;
template<typename T>
class ThreadSafeProxy
{
public:
ThreadSafeProxy(T& resource, boost::recursive_mutex& mutex)
: resourceRef(resource), lock(mutex) {}
T* operator->() { return &resourceRef; }
const T* operator->() const { return &resourceRef; }
T& operator*() { return resourceRef; }
const T& operator*() const { return resourceRef; }
T& get() {return resourceRef;}
const T& get() const {return resourceRef;}
T operator+(const T & other)
{
return resourceRef + other;
}
T operator+(ThreadSafeProxy<T> & other)
{
return get() + other.get();
}
private:
T& resourceRef;
std::lock_guard<boost::recursive_mutex> lock;
};
class Zone : public rmg::ZoneOptions
{
public:
@ -48,11 +78,14 @@ public:
int3 getPos() const;
void setPos(const int3 &pos);
const rmg::Area & getArea() const;
rmg::Area & area();
rmg::Area & areaPossible();
rmg::Area & freePaths();
rmg::Area & areaUsed();
ThreadSafeProxy<rmg::Area> area();
ThreadSafeProxy<const rmg::Area> area() const;
ThreadSafeProxy<rmg::Area> areaPossible();
ThreadSafeProxy<const rmg::Area> areaPossible() const;
ThreadSafeProxy<rmg::Area> freePaths();
ThreadSafeProxy<const rmg::Area> freePaths() const;
ThreadSafeProxy<rmg::Area> areaUsed();
ThreadSafeProxy<const rmg::Area> areaUsed() const;
void initFreeTiles();
void clearTiles();
@ -89,7 +122,7 @@ public:
CRandomGenerator & getRand();
public:
boost::recursive_mutex areaMutex;
mutable boost::recursive_mutex areaMutex;
using Lock = boost::unique_lock<boost::recursive_mutex>;
protected:

View File

@ -28,6 +28,24 @@
VCMI_LIB_NAMESPACE_BEGIN
std::pair<Zone::Lock, Zone::Lock> ConnectionsPlacer::lockZones(std::shared_ptr<Zone> otherZone)
{
if (zone.getId() == otherZone->getId())
return {};
while (true)
{
// FIXME: What if every zone owns its own lock?
auto lock1 = Zone::Lock(zone.areaMutex, boost::try_to_lock);
auto lock2 = Zone::Lock(otherZone->areaMutex, boost::try_to_lock);
if (lock1.owns_lock() && lock2.owns_lock())
{
return { std::move(lock1), std::move(lock2) };
}
}
}
void ConnectionsPlacer::process()
{
collectNeighbourZones();
@ -36,16 +54,14 @@ void ConnectionsPlacer::process()
{
for (auto& c : dConnections)
{
if (c.getZoneA() != zone.getId() && c.getZoneB() != zone.getId())
continue;
auto otherZone = map.getZones().at(c.getZoneB());
auto* cp = otherZone->getModificator<ConnectionsPlacer>();
while (cp)
{
RecursiveLock lock1(externalAccessMutex, boost::try_to_lock_t{});
RecursiveLock lock2(cp->externalAccessMutex, boost::try_to_lock_t{});
// FIXME: It does lock ConnectionPlacer, but not areaMutex
RecursiveLock lock1(externalAccessMutex, boost::try_to_lock);
RecursiveLock lock2(cp->externalAccessMutex, boost::try_to_lock);
if (lock1.owns_lock() && lock2.owns_lock())
{
if (!vstd::contains(dCompleted, c))
@ -78,9 +94,16 @@ void ConnectionsPlacer::init()
POSTFUNCTION(RoadPlacer);
POSTFUNCTION(ObjectManager);
auto id = zone.getId();
for(auto c : map.getMapGenOptions().getMapTemplate()->getConnectedZoneIds())
{
// Only consider connected zones
if (c.getZoneA() == id || c.getZoneB() == id)
{
addConnection(c);
}
}
}
void ConnectionsPlacer::addConnection(const rmg::ZoneConnection& connection)
{
@ -106,6 +129,9 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
bool directProhibited = vstd::contains(ourTerrain->prohibitTransitions, otherZone->getTerrainType())
|| vstd::contains(otherTerrain->prohibitTransitions, zone.getTerrainType());
auto lock = lockZones(otherZone);
auto directConnectionIterator = dNeighbourZones.find(otherZoneId);
if (directConnectionIterator != dNeighbourZones.end())
@ -115,19 +141,19 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
for (auto borderPos : directConnectionIterator->second)
{
//TODO: Refactor common code with direct connection
int3 potentialPos = zone.areaPossible().nearest(borderPos);
int3 potentialPos = zone.areaPossible()->nearest(borderPos);
assert(borderPos != potentialPos);
auto safetyGap = rmg::Area({ potentialPos });
safetyGap.unite(safetyGap.getBorderOutside());
safetyGap.intersect(zone.areaPossible());
safetyGap.intersect(zone.areaPossible().get());
if (!safetyGap.empty())
{
safetyGap.intersect(otherZone->areaPossible());
safetyGap.intersect(otherZone->areaPossible().get());
if (safetyGap.empty())
{
rmg::Area border(zone.getArea().getBorder());
border.unite(otherZone->getArea().getBorder());
rmg::Area border(zone.area()->getBorder());
border.unite(otherZone->area()->getBorder());
auto costFunction = [&border](const int3& s, const int3& d)
{
@ -139,9 +165,9 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
theirArea.add(potentialPos);
rmg::Path ourPath(ourArea);
rmg::Path theirPath(theirArea);
ourPath.connect(zone.freePaths());
ourPath.connect(zone.freePaths().get());
ourPath = ourPath.search(potentialPos, true, costFunction);
theirPath.connect(otherZone->freePaths());
theirPath.connect(otherZone->freePaths().get());
theirPath = theirPath.search(potentialPos, true, costFunction);
if (ourPath.valid() && theirPath.valid())
@ -174,7 +200,7 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
int3 roadNode;
for (auto borderPos : directConnectionIterator->second)
{
int3 potentialPos = zone.areaPossible().nearest(borderPos);
int3 potentialPos = zone.areaPossible()->nearest(borderPos);
assert(borderPos != potentialPos);
//Check if guard pos doesn't touch any 3rd zone. This would create unwanted passage to 3rd zone
@ -200,10 +226,10 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
auto safetyGap = rmg::Area({ potentialPos });
safetyGap.unite(safetyGap.getBorderOutside());
safetyGap.intersect(zone.areaPossible());
safetyGap.intersect(zone.areaPossible().get());
if (!safetyGap.empty())
{
safetyGap.intersect(otherZone->areaPossible());
safetyGap.intersect(otherZone->areaPossible().get());
if (safetyGap.empty())
{
float distanceToCenter = zone.getPos().dist2d(potentialPos) * otherZone->getPos().dist2d(potentialPos);
@ -228,8 +254,8 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
auto & manager = *zone.getModificator<ObjectManager>();
auto * monsterType = manager.chooseGuard(connection.getGuardStrength(), true);
rmg::Area border(zone.getArea().getBorder());
border.unite(otherZone->getArea().getBorder());
rmg::Area border(zone.area()->getBorder());
border.unite(otherZone->area()->getBorder());
auto costFunction = [&border](const int3 & s, const int3 & d)
{
@ -241,9 +267,9 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
theirArea.add(guardPos);
rmg::Path ourPath(ourArea);
rmg::Path theirPath(theirArea);
ourPath.connect(zone.freePaths());
ourPath.connect(zone.freePaths().get());
ourPath = ourPath.search(guardPos, true, costFunction);
theirPath.connect(otherZone->freePaths());
theirPath.connect(otherZone->freePaths().get());
theirPath = theirPath.search(guardPos, true, costFunction);
if(ourPath.valid() && theirPath.valid())
@ -262,10 +288,11 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
else
{
//Update distances from empty passage, too
zone.areaPossible().erase(guardPos);
zone.freePaths().add(guardPos);
zone.areaPossible()->erase(guardPos);
zone.freePaths()->add(guardPos);
map.setOccupied(guardPos, ETileType::FREE);
manager.updateDistances(guardPos);
// FIXME: Accessing other manager might lead to deadlock
otherZone->getModificator<ObjectManager>()->updateDistances(guardPos);
}
@ -318,8 +345,10 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
{
int3 zShift(0, 0, zone.getPos().z - otherZone->getPos().z);
auto lock = lockZones(otherZone);
std::scoped_lock doubleLock(zone.areaMutex, otherZone->areaMutex);
auto commonArea = zone.areaPossible() * (otherZone->areaPossible() + zShift);
auto commonArea = zone.areaPossible().get() * (otherZone->areaPossible().get() + zShift);
if(!commonArea.empty())
{
assert(zone.getModificator<ObjectManager>());
@ -339,7 +368,7 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
bool guarded2 = managerOther.addGuard(rmgGate2, connection.getGuardStrength(), true);
int minDist = 3;
rmg::Path path2(otherZone->area());
rmg::Path path2(otherZone->area().get());
rmg::Path path1 = manager.placeAndConnectObject(commonArea, rmgGate1, [this, minDist, &path2, &rmgGate1, &zShift, guarded2, &managerOther, &rmgGate2 ](const int3 & tile)
{
auto ti = map.getTileInfo(tile);
@ -403,7 +432,7 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
void ConnectionsPlacer::collectNeighbourZones()
{
auto border = zone.area().getBorderOutside();
auto border = zone.area()->getBorderOutside();
for(const auto & i : border)
{
if(!map.isOnMap(i))
@ -423,8 +452,8 @@ bool ConnectionsPlacer::shouldGenerateRoad(const rmg::ZoneConnection& connection
void ConnectionsPlacer::createBorder()
{
rmg::Area borderArea(zone.getArea().getBorder());
rmg::Area borderOutsideArea(zone.getArea().getBorderOutside());
rmg::Area borderArea(zone.area()->getBorder());
rmg::Area borderOutsideArea(zone.area()->getBorderOutside());
auto blockBorder = borderArea.getSubarea([this, &borderOutsideArea](const int3 & t)
{
auto tile = borderOutsideArea.nearest(t);
@ -448,21 +477,22 @@ void ConnectionsPlacer::createBorder()
}
};
Zone::Lock lock(zone.areaMutex); //Protect from erasing same tiles again
//Zone::Lock lock(zone.areaMutex); //Protect from erasing same tiles again
auto areaPossible = zone.areaPossible();
for(const auto & tile : blockBorder.getTilesVector())
{
if(map.isPossible(tile))
{
map.setOccupied(tile, ETileType::BLOCKED);
zone.areaPossible().erase(tile);
areaPossible->erase(tile);
}
map.foreachDirectNeighbour(tile, [this](int3 &nearbyPos)
map.foreachDirectNeighbour(tile, [this, &areaPossible](int3 &nearbyPos)
{
if(map.isPossible(nearbyPos) && map.getZoneID(nearbyPos) == zone.getId())
{
map.setOccupied(nearbyPos, ETileType::BLOCKED);
zone.areaPossible().erase(nearbyPos);
areaPossible->erase(nearbyPos);
}
});
}

View File

@ -33,6 +33,7 @@ public:
protected:
void collectNeighbourZones();
std::pair<Zone::Lock, Zone::Lock> lockZones(std::shared_ptr<Zone> otherZone);
protected:
std::vector<rmg::ZoneConnection> dConnections;

View File

@ -35,7 +35,7 @@ const std::string & Modificator::getName() const
bool Modificator::isReady()
{
Lock lock(mx, boost::try_to_lock_t{});
Lock lock(mx, boost::try_to_lock);
if (!lock.owns_lock())
{
return false;
@ -63,7 +63,7 @@ bool Modificator::isReady()
bool Modificator::isFinished()
{
Lock lock(mx, boost::try_to_lock_t{});
Lock lock(mx, boost::try_to_lock);
if (!lock.owns_lock())
{
return false;
@ -119,6 +119,8 @@ void Modificator::postfunction(Modificator * modificator)
void Modificator::dump()
{
// TODO: Refactor to lock zone area only once
std::ofstream out(boost::str(boost::format("seed_%d_modzone_%d_%s.txt") % generator.getRandomSeed() % zone.getId() % getName()));
int levels = map.levels();
int width = map.width();
@ -140,13 +142,13 @@ void Modificator::dump()
char Modificator::dump(const int3 & t)
{
if(zone.freePaths().contains(t))
if(zone.freePaths()->contains(t))
return '.'; //free path
if(zone.areaPossible().contains(t))
if(zone.areaPossible()->contains(t))
return ' '; //possible
if(zone.areaUsed().contains(t))
if(zone.areaUsed()->contains(t))
return 'U'; //used
if(zone.area().contains(t))
if(zone.area()->contains(t))
{
if(map.shouldBeBlocked(t))
return '#'; //obstacle

View File

@ -40,7 +40,30 @@ void ObjectManager::process()
void ObjectManager::init()
{
DEPENDENCY(WaterAdopter);
DEPENDENCY_ALL(ConnectionsPlacer); //Monoliths can be placed by other zone, too
//Monoliths can be placed by other zone, too
// Consider only connected zones
auto id = zone.getId();
std::set<TRmgTemplateZoneId> connectedZones;
for(auto c : map.getMapGenOptions().getMapTemplate()->getConnectedZoneIds())
{
// Only consider connected zones
if (c.getZoneA() == id || c.getZoneB() == id)
{
connectedZones.insert(c.getZoneA());
connectedZones.insert(c.getZoneB());
}
}
auto zones = map.getZones();
for (auto zoneId : connectedZones)
{
auto * cp = zones.at(zoneId)->getModificator<ConnectionsPlacer>();
if (cp)
{
dependency(cp);
}
}
DEPENDENCY(TownPlacer); //Only secondary towns
DEPENDENCY(MinePlacer);
POSTFUNCTION(RoadPlacer);
@ -49,9 +72,11 @@ void ObjectManager::init()
void ObjectManager::createDistancesPriorityQueue()
{
const auto tiles = zone.areaPossible()->getTilesVector();
RecursiveLock lock(externalAccessMutex);
tilesByDistance.clear();
for(const auto & tile : zone.areaPossible().getTilesVector())
for(const auto & tile : tiles)
{
tilesByDistance.push(std::make_pair(tile, map.getNearestObjectDistance(tile)));
}
@ -93,9 +118,24 @@ void ObjectManager::updateDistances(const int3 & pos)
void ObjectManager::updateDistances(std::function<ui32(const int3 & tile)> distanceFunction)
{
RecursiveLock lock(externalAccessMutex);
// Workaround to avoid dealock when accessed from other zone
RecursiveLock lock(zone.areaMutex, boost::try_to_lock);
if (!lock.owns_lock())
{
// Sorry, unsolvable problem of mutual impact¯\_(ツ)_/¯
return;
}
/*
1. Update map distances - Requires lock on zone area only
2. Update tilesByDistance priority queue - Requires lock area AND externalAccessMutex
*/
const auto tiles = zone.areaPossible()->getTilesVector();
//RecursiveLock lock(externalAccessMutex);
tilesByDistance.clear();
for (const auto & tile : zone.areaPossible().getTilesVector()) //don't need to mark distance for not possible tiles
for (const auto & tile : tiles) //don't need to mark distance for not possible tiles
{
ui32 d = distanceFunction(tile);
map.setNearestObjectDistance(tile, std::min(static_cast<float>(d), map.getNearestObjectDistance(tile)));
@ -145,7 +185,10 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object
if(optimizer & OptimizeType::DISTANCE)
{
// Do not add or remove tiles while we iterate on them
//RecursiveLock lock(externalAccessMutex);
auto open = tilesByDistance;
while(!open.empty())
{
auto node = open.top();
@ -235,7 +278,7 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object
rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, OptimizeType optimizer) const
{
RecursiveLock lock(externalAccessMutex);
//RecursiveLock lock(externalAccessMutex);
return placeAndConnectObject(searchArea, obj, [this, min_dist, &obj](const int3 & tile)
{
float bestDistance = 10e9;
@ -372,7 +415,7 @@ bool ObjectManager::createMonoliths()
bool guarded = addGuard(rmgObject, objInfo.guardStrength, true);
Zone::Lock lock(zone.areaMutex);
auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE);
auto path = placeAndConnectObject(zone.areaPossible().get(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE);
if(!path.valid())
{
@ -395,7 +438,7 @@ bool ObjectManager::createRequiredObjects()
{
logGlobal->trace("Creating required objects");
//RecursiveLock lock(externalAccessMutex); //Why could requiredObjects be modified during the loop?
RecursiveLock lock(externalAccessMutex); //In case someone adds more objects
for(const auto & objInfo : requiredObjects)
{
rmg::Object rmgObject(*objInfo.obj);
@ -403,7 +446,7 @@ bool ObjectManager::createRequiredObjects()
bool guarded = addGuard(rmgObject, objInfo.guardStrength, (objInfo.obj->ID == Obj::MONOLITH_TWO_WAY));
Zone::Lock lock(zone.areaMutex);
auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE);
auto path = placeAndConnectObject(zone.areaPossible().get(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE);
if(!path.valid())
{
@ -421,7 +464,7 @@ bool ObjectManager::createRequiredObjects()
rmg::Object rmgNearObject(*nearby.obj);
rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
possibleArea.intersect(zone.areaPossible());
possibleArea.intersect(zone.areaPossible().get());
if(possibleArea.empty())
{
rmgNearObject.clear();
@ -436,12 +479,11 @@ bool ObjectManager::createRequiredObjects()
for(const auto & objInfo : closeObjects)
{
Zone::Lock lock(zone.areaMutex);
auto possibleArea = zone.areaPossible();
rmg::Object rmgObject(*objInfo.obj);
rmgObject.setTemplate(zone.getTerrainType(), zone.getRand());
bool guarded = addGuard(rmgObject, objInfo.guardStrength, (objInfo.obj->ID == Obj::MONOLITH_TWO_WAY));
auto path = placeAndConnectObject(zone.areaPossible(), rmgObject,
auto path = placeAndConnectObject(zone.areaPossible().get(), rmgObject,
[this, &rmgObject](const int3 & tile)
{
float dist = rmgObject.getArea().distanceSqr(zone.getPos());
@ -470,15 +512,15 @@ bool ObjectManager::createRequiredObjects()
rmg::Object rmgNearObject(*nearby.obj);
std::set<int3> blockedArea = targetObject->getBlockedPos();
rmg::Area possibleArea(rmg::Area(rmg::Tileset(blockedArea.begin(), blockedArea.end())).getBorderOutside());
possibleArea.intersect(zone.areaPossible());
if(possibleArea.empty())
rmg::Area areaForObject(rmg::Area(rmg::Tileset(blockedArea.begin(), blockedArea.end())).getBorderOutside());
areaForObject.intersect(zone.areaPossible().get());
if(areaForObject.empty())
{
rmgNearObject.clear();
continue;
}
rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand()));
rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(areaForObject.getTiles(), zone.getRand()));
placeObject(rmgNearObject, false, false);
auto path = zone.searchPath(rmgNearObject.getVisitablePosition(), false);
if (path.valid())
@ -515,8 +557,6 @@ bool ObjectManager::createRequiredObjects()
void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateDistance, bool createRoad/* = false*/)
{
//object.finalize(map);
if (object.instances().size() == 1 && object.instances().front()->object().ID == Obj::MONSTER)
{
//Fix for HoTA offset - lonely guards
@ -539,14 +579,16 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
}
object.finalize(map, zone.getRand());
{
Zone::Lock lock(zone.areaMutex);
zone.areaPossible().subtract(object.getArea());
bool keepVisitable = zone.freePaths().contains(object.getVisitablePosition());
zone.freePaths().subtract(object.getArea()); //just to avoid areas overlapping
zone.areaPossible()->subtract(object.getArea());
bool keepVisitable = zone.freePaths()->contains(object.getVisitablePosition());
zone.freePaths()->subtract(object.getArea()); //just to avoid areas overlapping
if(keepVisitable)
zone.freePaths().add(object.getVisitablePosition());
zone.areaUsed().unite(object.getArea());
zone.areaUsed().erase(object.getVisitablePosition());
zone.freePaths()->add(object.getVisitablePosition());
zone.areaUsed()->unite(object.getArea());
zone.areaUsed()->erase(object.getVisitablePosition());
if(guarded) //We assume the monster won't be guarded
{
@ -554,16 +596,16 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
guardedArea.add(object.instances().back()->getVisitablePosition());
auto areaToBlock = object.getAccessibleArea(true);
areaToBlock.subtract(guardedArea);
zone.areaPossible().subtract(areaToBlock);
zone.areaPossible()->subtract(areaToBlock);
for(const auto & i : areaToBlock.getTilesVector())
if(map.isOnMap(i) && map.isPossible(i))
map.setOccupied(i, ETileType::BLOCKED);
}
lock.unlock();
}
if (updateDistance)
{
//Update distances in every adjacent zone in case of wide connection
//Update distances in every adjacent zone (including this one) in case of wide connection
std::set<TRmgTemplateZoneId> adjacentZones;
auto objectArea = object.getArea();
@ -577,8 +619,15 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
}
}
for (auto id : adjacentZones)
// FIXME: Possible deadlock by two managers updating each other
// At this point areaMutex is locked
// TODO: Generic function for multiple spinlocks
//std::vector sorted(adjacentZones.begin(), adjacentZones.end());
//std::sort(sorted.begin(), sorted.end());
//for (auto id : adjacentZones)
..for (auto id : sorted)
{
//TODO: Test again with sorted order?
auto otherZone = map.getZones().at(id);
if ((otherZone->getType() == ETemplateZoneType::WATER) == (zone.getType() == ETemplateZoneType::WATER))
{

View File

@ -37,22 +37,26 @@ void ObstaclePlacer::process()
collectPossibleObstacles(zone.getTerrainType());
{
Zone::Lock lock(zone.areaMutex);
blockedArea = zone.area().getSubarea([this](const int3& t)
//Zone::Lock lock(zone.areaMutex);
auto area = zone.area();
auto areaPossible = zone.areaPossible();
auto areaUsed = zone.areaUsed();
blockedArea = area->getSubarea([this](const int3& t)
{
return map.shouldBeBlocked(t);
});
blockedArea.subtract(zone.areaUsed());
zone.areaPossible().subtract(blockedArea);
blockedArea.subtract(*areaUsed);
areaPossible->subtract(blockedArea);
prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea();
prohibitedArea = zone.freePaths() + areaUsed + manager->getVisitableArea();
//Progressively block tiles, but make sure they don't seal any gap between blocks
rmg::Area toBlock;
do
{
toBlock.clear();
for (const auto& tile : zone.areaPossible().getTilesVector())
for (const auto& tile : areaPossible->getTilesVector())
{
rmg::Area neighbors;
rmg::Area t;
@ -76,7 +80,7 @@ void ObstaclePlacer::process()
toBlock.add(tile);
}
}
zone.areaPossible().subtract(toBlock);
areaPossible->subtract(toBlock);
for (const auto& tile : toBlock.getTilesVector())
{
map.setOccupied(tile, ETileType::BLOCKED);
@ -84,7 +88,7 @@ void ObstaclePlacer::process()
} while (!toBlock.empty());
prohibitedArea.unite(zone.areaPossible());
prohibitedArea.unite(areaPossible.get());
}
auto objs = createObstacles(zone.getRand(), map.mapInstance->cb);
@ -119,7 +123,7 @@ void ObstaclePlacer::placeObject(rmg::Object & object, std::set<CGObjectInstance
std::pair<bool, bool> ObstaclePlacer::verifyCoverage(const int3 & t) const
{
return {map.shouldBeBlocked(t), zone.areaPossible().contains(t)};
return {map.shouldBeBlocked(t), zone.areaPossible()->contains(t)};
}
void ObstaclePlacer::postProcess(const rmg::Object & object)
@ -141,7 +145,7 @@ bool ObstaclePlacer::isProhibited(const rmg::Area & objArea) const
if(prohibitedArea.overlap(objArea))
return true;
if(!zone.area().contains(objArea))
if(!zone.area()->contains(objArea))
return true;
return false;

View File

@ -106,14 +106,14 @@ char RiverPlacer::dump(const int3 & t)
return '2';
if(source.contains(t))
return '1';
if(zone.area().contains(t))
if(zone.area()->contains(t))
return ' ';
return '?';
}
void RiverPlacer::addRiverNode(const int3 & node)
{
assert(zone.area().contains(node));
assert(zone.area()->contains(node));
riverNodes.insert(node);
}
@ -140,14 +140,17 @@ void RiverPlacer::prepareHeightmap()
roads.unite(m->getRoads());
}
for(const auto & t : zone.area().getTilesVector())
auto area = zone.area();
auto areaUsed = zone.areaUsed();
for(const auto & t : area->getTilesVector())
{
heightMap[t] = zone.getRand().nextInt(5);
if(roads.contains(t))
heightMap[t] += 30.f;
if(zone.areaUsed().contains(t))
if(areaUsed->contains(t))
heightMap[t] += 1000.f;
}
@ -157,7 +160,7 @@ void RiverPlacer::prepareHeightmap()
for(int i = 0; i < map.width(); i += 2)
{
int3 t{i, j, zone.getPos().z};
if(zone.area().contains(t))
if(area->contains(t))
heightMap[t] += 10.f;
}
}
@ -167,9 +170,10 @@ void RiverPlacer::preprocess()
{
rmg::Area outOfMapTiles;
std::map<TRmgTemplateZoneId, rmg::Area> neighbourZonesTiles;
rmg::Area borderArea(zone.getArea().getBorder());
rmg::Area borderArea(zone.area()->getBorder());
TRmgTemplateZoneId connectedToWaterZoneId = -1;
for(const auto & t : zone.getArea().getBorderOutside())
for(const auto & t : zone.area()->getBorderOutside())
{
if(!map.isOnMap(t))
{
@ -182,6 +186,7 @@ void RiverPlacer::preprocess()
neighbourZonesTiles[map.getZoneID(t)].add(t);
}
}
rmg::Area outOfMapInternal(outOfMapTiles.getBorderOutside());
outOfMapInternal.intersect(borderArea);
@ -297,7 +302,7 @@ void RiverPlacer::preprocess()
prepareHeightmap();
//decorative river
if(!sink.empty() && !source.empty() && riverNodes.empty() && !zone.areaPossible().empty())
if(!sink.empty() && !source.empty() && riverNodes.empty() && !zone.areaPossible()->empty())
{
addRiverNode(*RandomGeneratorUtil::nextItem(source.getTilesVector(), zone.getRand()));
}
@ -347,7 +352,7 @@ void RiverPlacer::connectRiver(const int3 & tile)
return cost;
};
auto availableArea = zone.area() - prohibit;
auto availableArea = zone.area().get() - prohibit;
rmg::Path pathToSource(availableArea);
pathToSource.connect(source);

View File

@ -144,8 +144,8 @@ void RoadPlacer::drawRoads(bool secondary)
return !terrain->isPassable() || !terrain->isLand();
});
zone.areaPossible().subtract(roads);
zone.freePaths().unite(roads);
zone.areaPossible()->subtract(roads);
zone.freePaths()->unite(roads);
}
if(!generator.getMapGenOptions().isRoadEnabled())

View File

@ -68,7 +68,7 @@ char RockFiller::dump(const int3 & t)
{
if(!map.getTile(t).terType->isPassable())
{
return zone.area().contains(t) ? 'R' : 'E';
return zone.area()->contains(t) ? 'R' : 'E';
}
return Modificator::dump(t);
}

View File

@ -40,25 +40,28 @@ void RockPlacer::blockRock()
accessibleArea.unite(m->getVisitableArea());
//negative approach - create rock tiles first, then make sure all accessible tiles have no rock
rockArea = zone.area().getSubarea([this](const int3 & t)
rockArea = zone.area()->getSubarea([this](const int3 & t)
{
return map.shouldBeBlocked(t);
});
}
void RockPlacer::postProcess()
{
{
Zone::Lock lock(zone.areaMutex);
//Finally mark rock tiles as occupied, spawn no obstacles there
rockArea = zone.area().getSubarea([this](const int3 & t)
rockArea = zone.area()->getSubarea([this](const int3 & t)
{
return !map.getTile(t).terType->isPassable();
});
zone.areaUsed().unite(rockArea);
zone.areaPossible().subtract(rockArea);
zone.areaUsed()->unite(rockArea);
zone.areaPossible()->subtract(rockArea);
}
//RecursiveLock lock(externalAccessMutex);
//TODO: Might need mutex here as well
if(auto * m = zone.getModificator<RiverPlacer>())
m->riverProhibit().unite(rockArea);
if(auto * m = zone.getModificator<RoadPlacer>())
@ -84,7 +87,7 @@ char RockPlacer::dump(const int3 & t)
{
if(!map.getTile(t).terType->isPassable())
{
return zone.area().contains(t) ? 'R' : 'E';
return zone.area()->contains(t) ? 'R' : 'E';
}
return Modificator::dump(t);
}

View File

@ -28,7 +28,7 @@ void TerrainPainter::process()
{
initTerrainType();
auto v = zone.getArea().getTilesVector();
auto v = zone.area()->getTilesVector();
mapProxy->drawTerrain(zone.getRand(), v, zone.getTerrainType());
}

View File

@ -146,7 +146,7 @@ int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town)
int3 position(-1, -1, -1);
{
Zone::Lock lock(zone.areaMutex);
position = manager.findPlaceForObject(zone.areaPossible(), rmgObject, [this](const int3& t)
position = manager.findPlaceForObject(zone.areaPossible().get(), rmgObject, [this](const int3& t)
{
float distance = zone.getPos().dist2dSQ(t);
return 100000.f - distance; //some big number
@ -169,8 +169,8 @@ void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject)
if (map.isOnMap(t))
{
map.setOccupied(t, ETileType::FREE);
zone.areaPossible().erase(t);
zone.freePaths().add(t);
zone.areaPossible()->erase(t);
zone.freePaths()->add(t);
}
}
}

View File

@ -852,11 +852,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
return oi1.value < oi2.value;
});
size_t size = 0;
{
Zone::Lock lock(zone.areaMutex);
size = zone.getArea().getTilesVector().size();
}
const size_t size = zone.area()->getTilesVector().size();
int totalDensity = 0;
@ -920,9 +916,11 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
auto path = rmg::Path::invalid();
{
Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
auto possibleArea = zone.areaPossible();
possibleArea.erase_if([this, &minDistance](const int3& tile) -> bool
// FIXME: Possible area will be regenerated for every object
auto searchArea = zone.areaPossible().get();
searchArea.erase_if([this, &minDistance](const int3& tile) -> bool
{
auto ti = map.getTileInfo(tile);
return (ti.getNearestObjectDistance() < minDistance);
@ -930,7 +928,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
if (guarded)
{
path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
path = manager.placeAndConnectObject(searchArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
{
float bestDistance = 10e9;
for (const auto& t : rmgObject.getArea().getTilesVector())
@ -945,7 +943,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
const auto & guardedArea = rmgObject.instances().back()->getAccessibleArea();
const auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
if (zone.freePaths().overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
if (zone.freePaths()->overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
return -1.f;
return bestDistance;
@ -953,9 +951,10 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
}
else
{
path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
path = manager.placeAndConnectObject(searchArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
}
lock.unlock();
}
//lock.unlock();
if (path.valid())
{

View File

@ -43,13 +43,13 @@ void WaterAdopter::createWater(EWaterContent::EWaterContent waterContent)
if(waterContent == EWaterContent::NONE || zone.isUnderground() || zone.getType() == ETemplateZoneType::WATER)
return; //do nothing
distanceMap = zone.area().computeDistanceMap(reverseDistanceMap);
distanceMap = zone.area()->computeDistanceMap(reverseDistanceMap);
//add border tiles as water for ISLANDS
if(waterContent == EWaterContent::ISLANDS)
{
waterArea.unite(collectDistantTiles(zone, zone.getSize() + 1));
waterArea.unite(zone.area().getBorder());
waterArea.unite(zone.area()->getBorder());
}
//protect some parts from water for NORMAL
@ -199,7 +199,7 @@ void WaterAdopter::createWater(EWaterContent::EWaterContent waterContent)
std::vector<int3> groundCoast;
map.foreachDirectNeighbour(tile, [this, &groundCoast](const int3 & t)
{
if(!waterArea.contains(t) && zone.area().contains(t)) //for ground tiles of same zone
if(!waterArea.contains(t) && zone.area()->contains(t)) //for ground tiles of same zone
{
groundCoast.push_back(t);
}
@ -223,12 +223,12 @@ void WaterAdopter::createWater(EWaterContent::EWaterContent waterContent)
{
Zone::Lock waterLock(map.getZones()[waterZoneId]->areaMutex);
map.getZones()[waterZoneId]->area().unite(waterArea);
map.getZones()[waterZoneId]->area()->unite(waterArea);
}
Zone::Lock lock(zone.areaMutex);
zone.area().subtract(waterArea);
zone.areaPossible().subtract(waterArea);
distanceMap = zone.area().computeDistanceMap(reverseDistanceMap);
zone.area()->subtract(waterArea);
zone.areaPossible()->subtract(waterArea);
distanceMap = zone.area()->computeDistanceMap(reverseDistanceMap);
}
void WaterAdopter::setWaterZone(TRmgTemplateZoneId water)

View File

@ -34,45 +34,51 @@ VCMI_LIB_NAMESPACE_BEGIN
void WaterProxy::process()
{
for(const auto & t : zone.area().getTilesVector())
auto area = zone.area();
for(const auto & t : area->getTilesVector())
{
map.setZoneID(t, zone.getId());
map.setOccupied(t, ETileType::POSSIBLE);
}
auto v = zone.getArea().getTilesVector();
auto v = area->getTilesVector();
mapProxy->drawTerrain(zone.getRand(), v, zone.getTerrainType());
//check terrain type
for([[maybe_unused]] const auto & t : zone.area().getTilesVector())
for([[maybe_unused]] const auto & t : area->getTilesVector())
{
assert(map.isOnMap(t));
assert(map.getTile(t).terType->getId() == zone.getTerrainType());
}
// FIXME: Possible deadlock for 2 zones
auto areaPossible = zone.areaPossible();
for(const auto & z : map.getZones())
{
if(z.second->getId() == zone.getId())
continue;
Zone::Lock lock(z.second->areaMutex);
for(const auto & t : z.second->area().getTilesVector())
auto secondArea = z.second->area();
auto secondAreaPossible = z.second->areaPossible();
for(const auto & t : secondArea->getTilesVector())
{
if(map.getTile(t).terType->getId() == zone.getTerrainType())
{
z.second->areaPossible().erase(t);
z.second->area().erase(t);
zone.area().add(t);
zone.areaPossible().add(t);
secondArea->erase(t);
secondAreaPossible->erase(t);
area->add(t);
areaPossible->add(t);
map.setZoneID(t, zone.getId());
map.setOccupied(t, ETileType::POSSIBLE);
}
}
}
if(!zone.area().contains(zone.getPos()))
if(!area->contains(zone.getPos()))
{
zone.setPos(zone.area().getTilesVector().front());
zone.setPos(area->getTilesVector().front());
}
zone.initFreeTiles();
@ -105,7 +111,7 @@ void WaterProxy::collectLakes()
{
RecursiveLock lock(externalAccessMutex);
int lakeId = 0;
for(const auto & lake : connectedAreas(zone.getArea(), true))
for(const auto & lake : connectedAreas(zone.area().get(), true))
{
lakes.push_back(Lake{});
lakes.back().area = lake;
@ -117,8 +123,8 @@ void WaterProxy::collectLakes()
lakeMap[t] = lakeId;
//each lake must have at least one free tile
if(!lake.overlap(zone.freePaths()))
zone.freePaths().add(*lakes.back().reverseDistanceMap[lakes.back().reverseDistanceMap.size() - 1].begin());
if(!lake.overlap(zone.freePaths().get()))
zone.freePaths()->add(*lakes.back().reverseDistanceMap[lakes.back().reverseDistanceMap.size() - 1].begin());
++lakeId;
}
@ -151,7 +157,7 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
}
Zone::Lock lock(dst.areaMutex);
dst.areaPossible().subtract(lake.neighbourZones[dst.getId()]);
dst.areaPossible()->subtract(lake.neighbourZones[dst.getId()]);
continue;
}
@ -349,7 +355,7 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, bool
}
//try to place shipyard close to boarding position and appropriate water access
auto path = manager->placeAndConnectObject(land.areaPossible(), rmgObject, [&rmgObject, &shipPositions, &boardingPosition](const int3 & tile)
auto path = manager->placeAndConnectObject(land.areaPossible().get(), rmgObject, [&rmgObject, &shipPositions, &boardingPosition](const int3 & tile)
{
//Must only check the border of shipyard and not the added guard
rmg::Area shipyardOut = rmgObject.instances().front()->getBlockedArea().getBorderOutside();
@ -361,9 +367,9 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, bool
}, guarded, true, ObjectManager::OptimizeType::NONE);
//search path to boarding position
auto searchArea = land.areaPossible() - rmgObject.getArea();
auto searchArea = land.areaPossible().get() - rmgObject.getArea();
rmg::Path pathToBoarding(searchArea);
pathToBoarding.connect(land.freePaths());
pathToBoarding.connect(land.freePaths().get());
pathToBoarding.connect(path);
pathToBoarding = pathToBoarding.search(boardingPosition, false);
@ -391,7 +397,7 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, bool
manager->placeObject(rmgObject, guarded, true, createRoad);
zone.areaPossible().subtract(shipyardOutToBlock);
zone.areaPossible()->subtract(shipyardOutToBlock);
for(const auto & i : shipyardOutToBlock.getTilesVector())
if(map.isOnMap(i) && map.isPossible(i))
map.setOccupied(i, ETileType::BLOCKED);

View File

@ -43,22 +43,27 @@ void WaterRoutes::process()
result.push_back(wproxy->waterRoute(*z.second));
}
Zone::Lock lock(zone.areaMutex);
//Zone::Lock lock(zone.areaMutex);
auto area = zone.area();
auto freePaths = zone.freePaths();
auto areaPossible = zone.areaPossible();
auto areaUsed = zone.areaUsed();
//prohibit to place objects on sealed off lakes
for(const auto & lake : wproxy->getLakes())
{
if((lake.area * zone.freePaths()).getTilesVector().size() == 1)
if((lake.area * freePaths.get()).getTilesVector().size() == 1)
{
zone.freePaths().subtract(lake.area);
zone.areaPossible().subtract(lake.area);
freePaths->subtract(lake.area);
areaPossible->subtract(lake.area);
}
}
//prohibit to place objects on the borders
for(const auto & t : zone.area().getBorder())
for(const auto & t : area->getBorder())
{
if(zone.areaPossible().contains(t))
if(areaPossible->contains(t))
{
std::vector<int3> landTiles;
map.foreachDirectNeighbour(t, [this, &landTiles, &t](const int3 & c)
@ -74,8 +79,8 @@ void WaterRoutes::process()
int3 o = landTiles[0] + landTiles[1];
if(o.x * o.x * o.y * o.y == 1)
{
zone.areaPossible().erase(t);
zone.areaUsed().add(t);
areaPossible->erase(t);
areaUsed->add(t);
}
}
}
@ -96,6 +101,9 @@ void WaterRoutes::init()
char WaterRoutes::dump(const int3 & t)
{
auto area = zone.area();
auto freePaths = zone.freePaths();
for(auto & i : result)
{
if(t == i.boarding)
@ -106,15 +114,15 @@ char WaterRoutes::dump(const int3 & t)
return '#';
if(i.water.contains(t))
{
if(zone.freePaths().contains(t))
if(freePaths->contains(t))
return '+';
else
return '-';
}
}
if(zone.freePaths().contains(t))
if(freePaths->contains(t))
return '.';
if(zone.area().contains(t))
if(area->contains(t))
return '~';
return ' ';
}