浏览代码

Protect every access to zone tiles with a mutex

Tomasz Zieliński 1 年之前
父节点
当前提交
d8c93cb222

+ 1 - 1
lib/rmg/CMapGenerator.cpp

@@ -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");
 

+ 7 - 7
lib/rmg/CZonePlacer.cpp

@@ -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);
 		}
 	}

+ 2 - 1
lib/rmg/Functions.cpp

@@ -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;
 	});

+ 40 - 21
lib/rmg/Zone.cpp

@@ -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);
@@ -282,6 +294,13 @@ void Zone::fractalize()
 	freeDistance = freeDistance * marginFactor;
 	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)
 	{
@@ -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)

+ 39 - 6
lib/rmg/Zone.h

@@ -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:

+ 61 - 31
lib/rmg/modificators/ConnectionsPlacer.cpp

@@ -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,8 +94,15 @@ void ConnectionsPlacer::init()
 	POSTFUNCTION(RoadPlacer);
 	POSTFUNCTION(ObjectManager);
 	
+	auto id = zone.getId();
 	for(auto c : map.getMapGenOptions().getMapTemplate()->getConnectedZoneIds())
-		addConnection(c);
+	{
+		// 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);
 			}
 		});
 	}

+ 1 - 0
lib/rmg/modificators/ConnectionsPlacer.h

@@ -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;

+ 8 - 6
lib/rmg/modificators/Modificator.cpp

@@ -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

+ 89 - 40
lib/rmg/modificators/ObjectManager.cpp

@@ -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,31 +579,33 @@ 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
-	if(keepVisitable)
-		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
-	{
-		auto guardedArea = object.instances().back()->getAccessibleArea();
-		guardedArea.add(object.instances().back()->getVisitablePosition());
-		auto areaToBlock = object.getAccessibleArea(true);
-		areaToBlock.subtract(guardedArea);
-		zone.areaPossible().subtract(areaToBlock);
-		for(const auto & i : areaToBlock.getTilesVector())
-			if(map.isOnMap(i) && map.isPossible(i))
-				map.setOccupied(i, ETileType::BLOCKED);
+	{
+		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
+		if(keepVisitable)
+			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
+		{
+			auto guardedArea = object.instances().back()->getAccessibleArea();
+			guardedArea.add(object.instances().back()->getVisitablePosition());
+			auto areaToBlock = object.getAccessibleArea(true);
+			areaToBlock.subtract(guardedArea);
+			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))
 			{

+ 17 - 13
lib/rmg/modificators/ObstaclePlacer.cpp

@@ -37,22 +37,26 @@ void ObstaclePlacer::process()
 	collectPossibleObstacles(zone.getTerrainType());
 	
 	{
-		Zone::Lock lock(zone.areaMutex);
-		blockedArea = zone.area().getSubarea([this](const int3& t)
-			{
-				return map.shouldBeBlocked(t);
-			});
-		blockedArea.subtract(zone.areaUsed());
-		zone.areaPossible().subtract(blockedArea);
+		//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(*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;

+ 14 - 9
lib/rmg/modificators/RiverPlacer.cpp

@@ -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);

+ 2 - 2
lib/rmg/modificators/RoadPlacer.cpp

@@ -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())

+ 2 - 2
lib/rmg/modificators/RockFiller.cpp

@@ -65,10 +65,10 @@ void RockFiller::init()
 }
 
 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);
 }

+ 14 - 11
lib/rmg/modificators/RockPlacer.cpp

@@ -40,7 +40,7 @@ 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);
 	});
@@ -48,17 +48,20 @@ void RockPlacer::blockRock()
 
 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)
 	{
-		return !map.getTile(t).terType->isPassable();
-	});
-	
-	zone.areaUsed().unite(rockArea);
-	zone.areaPossible().subtract(rockArea);
+		Zone::Lock lock(zone.areaMutex);
+		//Finally mark rock tiles as occupied, spawn no obstacles there
+		rockArea = zone.area()->getSubarea([this](const int3 & t)
+		{
+			return !map.getTile(t).terType->isPassable();
+		});
+		
+		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);
 }

+ 1 - 1
lib/rmg/modificators/TerrainPainter.cpp

@@ -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());
 }
 

+ 3 - 3
lib/rmg/modificators/TownPlacer.cpp

@@ -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);
 			}
 		}
 	}

+ 34 - 35
lib/rmg/modificators/TreasurePlacer.cpp

@@ -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,42 +916,45 @@ 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
 			{
-				auto ti = map.getTileInfo(tile);
-				return (ti.getNearestObjectDistance() < minDistance);
-			});
+				Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
+				// 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);
+				});
 
-			if (guarded)
-			{
-				path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
-					{
-						float bestDistance = 10e9;
-						for (const auto& t : rmgObject.getArea().getTilesVector())
+				if (guarded)
+				{
+					path = manager.placeAndConnectObject(searchArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
 						{
-							float distance = map.getTileInfo(t).getNearestObjectDistance();
-							if (distance < minDistance)
+							float bestDistance = 10e9;
+							for (const auto& t : rmgObject.getArea().getTilesVector())
+							{
+								float distance = map.getTileInfo(t).getNearestObjectDistance();
+								if (distance < minDistance)
+									return -1.f;
+								else
+									vstd::amin(bestDistance, distance);
+							}
+
+							const auto & guardedArea = rmgObject.instances().back()->getAccessibleArea();
+							const auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
+
+							if (zone.freePaths()->overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
 								return -1.f;
-							else
-								vstd::amin(bestDistance, distance);
-						}
 
-						const auto & guardedArea = rmgObject.instances().back()->getAccessibleArea();
-						const auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
-
-						if (zone.freePaths().overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
-							return -1.f;
-
-						return bestDistance;
-					}, guarded, false, ObjectManager::OptimizeType::BOTH);
-			}
-			else
-			{
-				path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
+							return bestDistance;
+						}, guarded, false, ObjectManager::OptimizeType::BOTH);
+				}
+				else
+				{
+					path = manager.placeAndConnectObject(searchArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
+				}
 			}
-			lock.unlock();
+			//lock.unlock();
 
 			if (path.valid())
 			{

+ 7 - 7
lib/rmg/modificators/WaterAdopter.cpp

@@ -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)

+ 25 - 19
lib/rmg/modificators/WaterProxy.cpp

@@ -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);

+ 19 - 11
lib/rmg/modificators/WaterRoutes.cpp

@@ -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 ' ';
 }